About this Book

This book was written to help you use PubSweet to build the publishing platform of your dreams. It was written by the Coko community with the hope that you will find it useful and join us in our efforts to liberate the scholarly publishing community from outdated workflows and expensive proprietary software.

First a word on the Coko Community and the origins of PubSweet. Coko is short for the Collaborative Knowledge Foundation, co-founded by Kristen Ratan and Adam Hyde. Soon after the Coko start up, Jure Triglav was employed to start building the open source PubSweet toolkit for building publishing platforms. This was at first a lonely affair until eLife and Hindawi joined at the same time. Both organizations wished to join the effort to improve PubSweet so they could build their own journal publishing systems. That moment was the start of the Coko community and from there it has grown to include many other organizations including EBI, Wormbase, perhaps soon the Organization for Human Brain Mapping, and others. The Coko community, sometimes refered to as the PubSweet community in this book, is a vibrant and growing group of people enthusiastic about building better publishing workflows and a better future for scholarly publishing. The Coko community wrote this book!

Who should read this book?

This book is targeted at a wide range of readers within the publishing industry on both the product and technology sides: funders and strategic stakeholders making technology or budgeting decisions, project managers, designers, and software engineers.

Those in non-technical roles will get the most from the first two sections, Introduction to PubSweet and The Future of PubSweet: these sections set out the business case for adopting PubSweet and describe how your organization can set about redesigning or optimizing its workflow. The more technically minded will benefit from the central sections on Technical Architecture, Development Help.

Of course, for anyone with sufficient time, we recommend reading everything!

Acknowledgements

The book was written over three days using the Book Sprint methodology. We wish to thank Barbara Rühling, our facilitator from Book Sprints for guiding us through this process and making all this hard work feel like fun. Also thanks to Henrik, Agathe, Raewyn and the rest of the Book Sprints crew who worked behind the scenes to make our work look great!

Participating in the Book Sprint were Yannis Barlas (Coko), Bogdan Cochior (Hindawi), Nick Duffield (eLife), Samuel Galson (eLife), Yuci Gou (EBI), Audrey Hamelers (EBI), Peter Hooper (eLife), Adam Hyde (Coko), Christos Kokosias (Coko), Tamlyn Rhodes (eLife), Paul Shannon (eLife), Julien Taquet (Coko), Alex Theg (Coko), and Jure Triglav (Coko). Thanks also to all the wonderful folks at Hindawi, eLife and EBI who generously made the time available for their teams to participate.

The book was written about PubSweet using Editoria, a book production platform built on top of PubSweet, as envisioned by founders Erich van Rijn of University of California Press and Catherine Mitchell of California Digital Library. Editoria was also made possible by a generous grant from the Mellon Foundation. We are eating our own dog food. Because Editoria is so great, we could output the content to epub and book- formatted PDF immediately… zero to printed and electronic book in 3 days… amazing. Book Sprints + Editoria = the best!

We also wish to thank the Shuttleworth Foundation for their generous support of Coko's co-founder Adam Hyde, and for the PubSweet Book Sprint.

Lastly, this book is dedicated to Alexis since he couldn't be here for the Book Sprint and yet the book was written with Editoria, and he wrote Editoria. It deserves a photo…

1

Introduction to PubSweet

Dixit, et ante tulit gressum, camposque nitentis

desuper ostentat;

So saying, he strode forth and led them on,

Till from that vantage they had prospect fair

Of a wide, shining land;

Aeneid VI, 677-8

1

What is PubSweet?

PubSweet is a free, open source framework for building state-of-the-art publishing platforms. PubSweet enables you to easily build a publishing platform tailored to your own needs. It is designed to be modular and flexible. PubSweet consists of a server (“back end”) and client (“front end”) that work together, and both can be modified and extended to add functionality to the system.

PubSweet is being used for book publishing, academic journal production, and micropublication platforms by a growing number of established academic organizations including the University of California Press, eLife, Hindawi, California Digital Library and others.

Each of these organizations is building their custom platform using PubSweet, and contributing reusable open source components back to the community. By drawing on this growing library of components, PubSweet can be used to rapidly create bespoke publishing systems. If the existing components do not completely meet your needs, you can focus development on building new components to provide just the new functionality required.

If someone has built an entire publishing platform which you like (for example, Editoria or xPub), you can use it as-is, or replicate then extend it, using your own ideas and components with minimal effort.

Why does PubSweet exist?

Today most of the publishing systems are “big box” platforms built years ago when the world of publishing was different. They have not evolved at the same rate as users' needs and are largely just data stores that track rather than enable or manage workflow. Most of these systems are also expensive, monolithic, and proprietary, so it is very difficult to change them to meet your organization's needs. Inevitably the reverse happens and you must change your organization to meet the prescriptive worldview of the software. The PubSweet community believes that software should be working for us, not the other way around.

When using off-the-shelf software, changing a publisher's workflow means changing their organization or switching platform vendors. Both options are slow and expensive, making them undesirable. In the past, this has meant that the publishing sector has not been able to fully embrace the latest technological developments (for example, the web) or experiment and innovate in their workflows. When you cannot own or control your own tools, you have little say in what those tools allow you to do; consequently, you become subject to them, losing control of your publication process.

This lock-in can be avoided by giving the ownership of the tools back to publishers. PubSweet is built to do just that - it is an open source framework for building publishing workflows. This means you can build the workflows you want without compromising and continually update and optimize them in the future.

What are the benefits of PubSweet?

PubSweet is free Open Source software, so you don't have to pay to use it. You can modify it as much as you like, and use it for any purpose, including commercially and for-profit. The license (a popular and permissive open source licence called the MIT license) grants a perpetual right to use PubSweet in this way forever.

Thanks to the focus on modularity, you can extend PubSweet with components. You can add new components, extend existing components, or recompose a set of components on both the front end and back end as much as you like. You can leverage the existing components contributed to the community to lessen your development time and speed up your delivery.

Assemble your own PubSweet platform

PubSweet is web native so you can bring your workflow to the web. Bring your whole workflow, not just your published articles, into the web, the most powerful collaborative workflow instrument of our time.

Lastly, one of the most powerful reasons to use PubSweet is the Coko community. The ever-growing community of organizations building with and using PubSweet leads to a richer ecosystem with more components to draw from, and more resources to help you build your platform. By working together with the community, you can share the effort to produce new and exciting features, or reuse what someone else has built. Software that is already in use is likely to be more robust, free from defects, and well-adapted to real users. By participating in the community and sharing what you know, you will benefit from many others doing the same.

2

Examples of platforms built with PubSweet

PubSweet is already in use in a number of different publishing contexts. The following examples illustrate some ways PubSweet can be used.

Books

Almost all scholarly monographs are written by authors in MS Word, then submitted as .docx files to publishers. The content work — editing, copyediting, and reviewing — usually happens on the Word files as well. Many presses style the .docx files using Word macros and add-on tools to semantically tag all the content. Passing static files back and forth between authors and editors can be cumbersome and slow, at best, and cause versioning problems, at worst: who has the latest files? who made these changes? Once the content is complete, books go through production to be paginated and converted to other formats, an expensive, slow, and sometimes error-prone endeavor.

No longer! Working with the University of California Press, the Collaborative Knowledge Foundation has created and continues to develop Editoria, an in-browser scholarly book production platform. Editors and authors upload author files into a book structure, then collaborate to style, copyedit, and review the content online, enabling fully automated export to a print-ready, paginated PDF, epub, and more.

Editoria Workspaces

There is strong interest in adoption from other university presses, and as of mid-2018, the University of North Carolina Press is planning to use Editoria to publish up to 150 Open Access, digital-first books in the next three years.

Journals

As a response to inflexible and expensive content and journal management systems, several PubSweet journal systems for submission, peer review, and publication are being developed by organizations including eLife, Hindawi, and Collabra: Psychology from the University of California Press. Collectively we call these PubSweet journal platforms 'xPub' (and refer to them each as xpub-Collabra, xpub-eLife etc).

Hindawi Workspaces

Academic journals share many similarities, but also tend to have slightly different workflow variations from one to the next. For example, almost all journals need an interface to search, invite and manage reviewers, and many do so in much the same way. However, the minutiae of editorial workflows also tends to vary greatly: does it go to a senior editor for further editor assignment, or straight to a handling editor? is the editor assigned or invited? does the author or the editor assign the submission type? are there different editor assignment chains for different submission types, and if not, should there be, were it possible?

This is perfect for PubSweet's use-what-you-want and build-only-what-you-need approach. Two separate journals might share the same reviewer assignment component, but both have separate reviewer forms and editor assignment workflows.

Its also important to note that PubSweet can be extended on the back end to accomodate journal requirements. For example, Semantic Extraction techniques are accommodated by PubSweet. Extraction is done within PubSweet using server-side components that are integrated into the workflow. There are many tools for extraction; currently the community is working together using ScienceBeam Judge as a test framework to help improve the tools. The Coko project INK can also be used to manage these kinds of processes and integrates with PubSweet very nicely.

Micropublications

PubSweet can also be used to support micropublications - such things as data depositions or case studies that fall short of what would be considered a traditional journal research article. Legacy publishing systems are built with generic use cases in mind (e.g. typical journals). Consequently, other use cases such as micropublications are often left behind. It is almost impossible to find existing softwares that match their workflows or can be easily extended.

An example: the Collaborative Knowledge Foundation is working with WormBase, a nematode model organism gene database, to create an elegant submission, curation, and publication platform.

Micropublications Workspaces

Authors often misclassify their submission type. With the opportunity to build a new system, the Wormbase staff decided that in the best workflow, curators - not authors - would set the submission type. The author submits some initial information, common to all submissions, a curator selects the submission type, then the author submits a follow-up form with the submission type-specific data. These kinds of tweaks can make an incredible difference to a workflow but are simply not possible without the ability to customize a platform.

Europe PMC plus

Europe PMC is a worldwide, free, life sciences literature repository for journal articles, preprints, books, patents and clinical guidelines. Researchers funded by the Europe PMC funders are required to submit their publications to Europe PMC plus (a manuscript submission system used by researchers, administrators submitting their behalf, and publishers who bulk-submit manuscripts via FTP) to comply with the funders' open access policies.

Europe PMC Plus Workspaces

As a repository, Europe PMC plus only accepts articles that have already been peer reviewed and accepted for publication, and requires a full publication citation before the submission is considered complete. The Europe PMC plus workflow and data requirements are therefore different from those of journal publishers using PubSweet. EMBL-EBI staff are using PubSweet components to develop and customize the Europe PMC plus workflow and functionalities to meet the requirements of all parties, including researchers, submitters, publishers, Europe PMC administrators, third party contractors (XML taggers) and funders.

3

Before you jump: things to know up front

Before you or your organization makes the decision to start using PubSweet, there are a few things it's important to know.

First, reach out to the Coko team and the community as soon as you are interested, and don't be afraid of asking basic or 'obvious' questions. The community is very welcoming, quite transparent and upfront, and happy to field questions of any kind.

If you decide to build a platform with PubSweet, it's critical to carefully understand or design your ideal workflow before you start building. This can be daunting, but doing it right pays enormous dividends in time, work, and effort down the line. We recommend talking to the Coko team and the PubSweet community about workflow and how to understand, optimize, and redesign it. There are many experienced people in the community with a wealth of lessons learned and they can save you a lot of time and heartache. You may also wish to read the section on Designing Workflow in this book.

If you are in the market for developers, choose intermediate or senior JavaScript developers, preferably with React experience. PubSweet is built with JavaScript and Node.js / React. There are plenty of JavaScript developers around, making finding programmers or contracting out work as easy as possible — but be sure to hire good ones!

Once you have built the platform, you have three options for its hosting:

Host it yourself — this could be done using a provider who offers Hardware-as-a-Service (HaaS), but this option also means you may have the overhead of managing the network and other infrastructure.

Pay a hosting-only provider — if you are happy with a default deployment you can opt to purchase a Software-as-a-Service (SaaS) or Platform-as-a-Service (PaaS).

Pay a hosting and publishing services provider — if you require hosting and publishing services you can opt for a publishing services provider who can provide all the support you need.

While some groups may be comfortable running a PubSweet platform in-house for a fraction of the cost of a hosting provider, others who want professional services might prefer to contract with a hosting and/or publishing services organization. There are groups out there who are interested in providing these services and we anticipate their numbers will grow over time.

Open Source is more than just a license — open source is about participating in a community. If you learn to work with the PubSweet community, it will provide enormous benefits to you, but it also comes with some expectations, norms, and rules of the road. Don't be scared of these aspects — they are such things as agreement to act in good faith, pay it forward, and treat others as you want to be treated.

If these are ideas you can get behind, read on.

Coko Community Meet, London

A quick word about Open Source and community

Many incorrectly assume that open source software implies low quality, unsustainable, lacking in thought and effort, no reliable support, or worse. In fact, PubSweet and the platforms being built on top of it are quite the opposite: they represent close collaboration between publishing industry practitioners, high quality code and UX, and are a serious effort at tackling common industry problems by a variety of extremely experienced and talented professional developers, designers, project managers, data scientists, publishing staff, and others. The folks who are involved are working together out of a recognition of shared needs and problems in the publishing industry, and a sincere belief that cooperation and good-faith action benefits everyone.

As mentioned, the open source and PubSweet communities come with their own expectations that will work to your benefit. There are many things to consider, and if you join the PubSweet community, we'll all make sure you have the opportunity to work alongside some experienced hands to help you get oriented. But it is good to know ahead of time that we hold dear the following two core principles:

Ask for help if you need it, but also answer someone else's question if you can.

Use as much of the free code and components as you can, but also remember to share your platform or component back to the community.

The PubSweet community has grown over time, to the point where there are plenty of ongoing projects and folks currently working with it. By encouraging engagement and good community citizenship, the community grows, the component library you can draw from grows, and the number of people who can give you a nudge in the right direction grows. Just like everything else in life, the more you put in, the more you'll get out.

How do I get involved?

For pretty much any starting point — whether you want to chat about what's possible, check whether a specific component you're looking for exists, have a specific question about the code, or something else — the first step is to get in touch with the PubSweet community. The best way to do this is via the Collaborative Knowledge Foundation's Mattermost web chat.

2

Platform Design

Daedalus ipse dolos tecti ambagesque resolvit

The crafty Daedalus himself betrayed

The secret of the maze

Aeneid VI, 29

4

Designing workflow

Unless you are starting a completely new publishing venture, your workflow has likely been at least partially dictated over the years by inflexible, prescriptive content management systems, and a combination of workflow steps and ad hoc platform modifications designed to get around your tools' limitations. The end result is legacy workflows with many cul-de-sacs and eddies that do not reflect anything like an ideal workflow.

We want workflow, not workarounds…

With PubSweet, publishers of all sizes have the opportunity to break this unhealthy pattern, and are empowered to re-imagine and build the workflow they really want. However workflow design isn't a job title and it is sometimes difficult to know where to start. This is also where the Coko community can help.

In "PubSweet, workflow and workspaces", we go over the core concepts of designing a workflow for PubSweet. The following chapter, “Workflow Sprints”, documents a method for working out your ideal workflow and mapping it onto a PubSweet platform, designed by Coko co-founder Adam Hyde. After that, in the “case study” chapters, we describe the experience of eLife, Hindawi, and Europe PMC in redesigning their workflow for PubSweet. The teams carried out a process influenced by the Workflow Sprints, modified to suit their organizations. Finally, in “A general model”, we collate lessons learned from these case studies.

Cindy (University of California Press) designing Editoria

5

PubSweet, workflow and workspaces

Before designing your PubSweet platform, there are some core PubSweet concepts to become familiar with.

The genesis of PubSweet followed the observation that many “big box” platforms had a single (and therefore prescriptive) approach to what journal publishing workflow should look like, or at least, how workflow should be tracked. PubSweet's proposition is that there is no one journal workflow, nor is there one book workflow, micropublications workflow, and so on, although they do all share some common fundamentals. PubSweet is not prescriptive about your workflow, but it does make some assumptions about how that workflow is realized. Just two will do for now - the rest are better described in other chapters.

The first assumption is that publishing workflows should occur in the web. Currently the web has not really affected publishing workflow, with two exceptions:

Using the browser as an interface to what are essentially data stores (manuscript submission systems or, in the case of books, title management systems).

Using the web to deliver books and articles.

The web offers the opportunity for publishing to leverage tools for collaboration that could reach beyond the submission moment (into the lab), past the point of publication (“living documents”) and everything in between. PubSweet takes the position that the web is the most powerful collaborative network of our time, and needs to be brought into the heart of publishing workflows.

Secondly, PubSweet has the concept of workspaces (often also refered to as “components”, “front end components” or just “spaces“). To bring publishing to the web, we need our workflows to inhabit the browser; we need to see the browser as a workspace, or rather a series of workspaces that instantiate our desired workflow.

Perhaps the easiest way to illustrate this idea of workspaces is by example. In your online experience, you have probably come across the notion of a dashboard. Many web platforms have them — Wordpress Dashboard for example, or Google Dashboard — a common space where you can get an overview of all things you need in order to get on with the particular task at hand. Dashboards are good examples of a particular kind of workspace. PubSweet enables your workflow by chaining together a series of workspaces, then directing the right people to the right workspaces at the right moments so they can do what they have to do.

For example, Editoria, as it is configured for the University of California Press, has the following roles: Production Editor, Copy Editor, Author. Works come in from the acquisitions department in the form of a folder of MS Word documents. The Production Editor creates a new book from the Dashboard and uploads these documents into Editoria. The Production Editor then collaborates with the Author and Copy Editor on each book by re-arranging the structure in the Book Builder workspace and editing individual chapters using the editor (you could also call it a chapter-editing-workspace, if you wish). There is a lot of other detail, such as controlling who should have access to what and when (more on all this later) but in essence, everything is performed in three workspaces: the Dashboard, The Book Builder, and the Editor. A high-level system architecture for the Editoria platform looks like this:

Editoria Workspaces

Let's contrast that with a completely different workflow. Collabra Psychology is a journal that also operates out of the University of California Press. Coko is building a new platform for Collabra on top of PubSweet. The roles are pretty standard for a journal: Managing Editor, Senior Editor, Handling Editor, Author, and Reviewer. The workflow is also pretty standard: An Author creates a new submission (from the Dashboard), creates and submits a manuscript and associated metadata from the Submission Page, then a series of Editors manage the workflow from the Decision Control workspace. Reviewers are assigned from the Manage Review workspace, and then reviewers read the article, write and submit reviews from dedicated Review workspaces.

The total number of workspaces for Collabra Psychology then is 6 - Dashboard, Submission Page, Manuscript, Decision Control, Manage Review, and the Review workspaces. The high level workspaces architecture looks like this:

Collabra Workspaces

Now you have an idea of how Workspaces work, the next thing you want to do, if you are designing a PubSweet platform, is to work out your ideal workflow, and map it onto PubSweet workspaces. This will be covered in the following chapters.

6

Workflow Sprints

The following methodology is a process designed by Coko co-founder Adam Hyde, who has successfully facilitated a number of publishers through this process, including Wormbase, Collabra Psychology, the Organization for Human Brain Mapping, ArXiv, Erudit and others. The aim of these sprints is to design an optimized publishing workflow and translate that into a high level architecture for a PubSweet platform using workspaces — in one day!

This process can be facilitated to work with new publishing projects, or to optimize tthe workflow of existing publishing organizations. To conduct a Workflow Sprint you need the following:

an afternoon's commitment from at least one person per role within the publisher

a room

a whiteboard

large pieces of paper

pens

an experienced facilitator

some nice snacks and drinks (coffee)!

Be warned — Workflow Sprints are fun and exciting, and great team building exercises, but they are also definitely exhausting. Make sure you have a nice space and plenty of good food and drinks to help your team enjoy the experience.

Step one: designing / optimizing workflow

The process of designing (in the case of new projects) a publishing workflow or optimizing an existing workflow is a process of open discussion. Like all good stories, you start at the beginning and ask what happens first. You want the people that are actually involved in that part of the story to tell it.

Aperture Workflow Sprint

If you can, make sure you also involve an author in the process to say what happens from their experience. The workflow is recorded in a very basic form in the following format:

Role and title of step

Action(s) for each step.

A basic step could look something like this:

Author starts new submission

If we were to go further, it might look like this:

Author starts new submission

Author completes submission

Author submits submission

Editor reviews submission...

When documenting these steps, write them down on a surface that all involved can easily see (such as a whiteboard). Some of these records will be revisited as you go, so leave room for alterations and additions.

Note that you are not documenting the current software, you are documenting the workflow as it is now and discussing as you go and looking for ways to improve it. Modify the steps so they reflect the improvements that come from the discusssions (we want to design an improved workflow, not document the existing workflow). Discussion and new ideas are very important as you want the participants to start leaving behind their legacy understanding of workflow based on their current software, to enable a new way of thinking about what they do. Later you will design a system to meet this optimal workflow design.

As you go through this process, discussion is facilitated. Anyone with a query needs to be able to ask a question about the step being discussed. You also want people to suggest better ways of doing things as you go, and to tease out as much nuance as possible about how things are currently done. This will require the facilitator to ask many clarifying questions about the current step, and it is important to drill down and get absolute clarity. It often means asking apparently stupid questions, but only by doing this will you uncover what is actually happening. This is absolutely critical to understand before you can attempt to improve upon the current way of doing things.

For example, it is not uncommon that one or more parts of the workflow involve someone moving to a specific computer with the tools for a certain task, which may actually be in another room or building. It is important that this detail is teased out so everyone can understand how absurd some of the things their colleagues have to do really are. If they understand, they will be motivated to help optimize the workflow to flush out these absurdities and improve their colleagues' workflow.

A word about facilitation of this process: you can facilitate this process for your own organization, but it's best to avoid this if possible. Experience has shown that existing hierarchies and inter-personal dynamics within an organization will hamper an internal facilitator's ability to challenge what is said and to provoke discussion on new ways of doing things. It is far better that a facilitator isn't subject to these dynamics, so if possible bring in an external facilitator.

The trick here is to get clarity but not get stuck in a quagmire of nuance. You will need to move the conversation along and to dig out the detail relatively quickly. In addition, be very careful to avoid magical thinking with regard to the role technology can play in this process. Often someone will say something like “Imagine if we could automatically....” That is one of a number of indicators that there's an investment in magical thinking. Be very realistic with what technology can and can’t do. Do not make the mistake of including a workflow step that magically makes a lot of steps disappear through automation unless you have that technology available and know its limits, as illustrated through your own experiences in production.

Avoiding quagmires and magical thinking is another reason why an experienced facilitator is recommended. It should take no longer than an afternoon to map out the optimized workflow. Don’t worry if this is not yet ‘as optimized’ as you would like: there will be plenty of time in the following processes to improve on things.

Note that you should be able to document and optimize at the same time. However, should you feel more comfortable with breaking this into a two-stage process, you can first document the current workflow, then discuss and work on optimizing it in a second session.

Whichever way you choose, document it all as you go.

Step two: mapping work to Workspaces

With the above information you could now make a flow diagram representing the workflow. However it is hard to design software based on a typical flow diagram representation. The workflow looks horribly convoluted, linear, inflexible; and recursive processes are awfully hard to document and parse.

So, let’s throw away the flow diagram approach for describing workflow, and find a better way. Let's connect each of the above steps directly to a the concept of a Workspace as described in the previous chapter.

Let’s look at how this applies with an example from the world of journals. For example, imagine a Managing Editor needs to sanity-check all new submissions. They log into the system (or they might receive an email notification) and see a new submission listed. Contained in the notification (email/dashboard, etc.) is a link, and they click through to the submission page and read through it. That submission page is a simple Workspace.

So this is pretty simple. Now step through every step in the workflow, as described in the section above, and map out the Workspaces. To do this, take each step already identified and connect it to a Workspace where the action is performed.

When listed out for any workflow, formatted as who (role) does what (actions) where (Workspace), the process will look something like this:

Author creates a new submission from the Dashboard

Author fills out submission data on the Submission page

Author Submits the submission from the Submission page

Managing Editor checks submission data on the Submission page

Managing Editor assigns Handling Editor from the Dashboard

…etc.

Keep it simple

An entire workflow could be mapped in the same way as the above example, one step after the other, describing a simple workspace for each step until the whole workflow is accounted for. But, there's a danger of creating a whole lot of unique workspaces, with each step showing a dashboard notification “directing the user” to a unique workspace. That’s not very helpful, as you would soon end up with hundreds of unique, one-action workspaces.

Instead, impose a few simple constraints on the team. The basic constraints are as follows:

Reuse as many workspaces as possible.

Only add a new workspace when absolutely sure the existing workspaces can’t be reused.

If this is done through the entire workflow and you stay disciplined, you will end up with a very simple diagram of workspaces. For example, in mapping the Collabra: Psychology journal, the following results:

Collabra Workspaces

This ‘bird's eye view’ of the workspace architecture shows just 6 workspaces that cover the entire workflow. Your goal is to design a similarly simple view for your own workflow.

Simple actions

It is important to keep one final principle in mind during the system design: simple single actions usually don't need their own dedicated workspace, and are best added to an existing workspace. Also, don’t put the same simple action feature in two places. One of those places is wrong — make a decision!

As with every part of system design, these trade offs are up to you to decide on.

Summary

The entire process is actually quite simple:

Write down each step of the workflow in the format - who (Role) does what (action), optimizing as you go.

Start with a Dashboard (it's a handy starting point) and go through the workflow step by step.

With each step try to reuse an existing Workspace.

If you can’t use an existing workspace, create a new one.

At each step also ask yourself: is this a simple single action, and if so can it be added to an existing Workspace or do I need different workspace?

There is a bit of wrangling needed, but this is a pretty effective way of capturing seemingly complex workflows in relatively simple systems, systems that can also be optimized over time.

7

eLife case study

eLife Sciences Publications Ltd is an Open Access journal that publishes promising research in the life and biomedical sciences. eLife is currently in the process of building an application based on PubSweet for its manuscript submission process.

At the start of the project, eLife's initial driver was to understand the publishing workflow from a user's perspective. To do this, eLife ran a three-day workshop with a representative group of users with the aim of understanding and recording the current pain points, goals and user's needs. The output from this activity was a rich set of highly dense information. To help feed the design process, these were turned into user stories and categorized using a Trello board.

The Coko development team were invited to visit the eLife offices to review the current Collabra Psychology version of xpub-Collabra components against eLife's user stories. This was done to see how closely the Collabra workflow matched eLife's, and also to understand whether any of the work could be reused.

There was plenty of overlap in ideas between eLife and Collabora's workflow, but the developers saw that eLife had a lot of organization-specific needs that would need to be catered to. Based on this understanding, eLife's current workflow was reviewed against the user stories by running a workflow mapping session with the editorial team.

Once eLife approved the resulting workflow diagram, the next step was to understand how a user might navigate each space and to outline some of the controls that might be needed per page. To make this easier to understand, the workflow was broken down per role. The diagram below shows a portion of this breakdown.

eLife Workflow by role (partial)

Wireframing

Based on the agreed diagrams, the development team started to build some wireframes in order to share their ideas with the Editorial team. At this stage there was no need to be too concerned about exactly how each control worked, but rather to determine whether the general controls per page would allow each user to carry out the tasks they needed to.

Wireframing provided a quick way to get some early feedback and ensure that the development team had covered the needs of the editorial team.

Testing

It was useful for eLife to print out a full set of wireframes and ask the eLife Editorial team to assume various roles that would interact with the system. eLife had tested their initial submission workflows, so they needed to assume the roles of Staff, Author, Deputy Editor and Senior Editor. The eLife product team acted as “the system” by presenting each user with a paper prototype screen and asking them to interact with it.

This process uncovered parts of the system that were either missing or hard to understand and helped the developers make refinements to the lo-fi designs. These were then shared again until it was clear the the series of basic screens and controls had effectively covered the editorial team's workflow.

Design Review

For the next stage, eLife reviewed what had been designed so far and discussed areas of the interface that needed refinement. The designs were laid out on the office floor and a post-it note session was run to allow participants to express any concerns. Finally, each design was checked to see if it had covered the list of user stories from the Trello board.

This step was very useful in refining the interface designs and led to significant changes to parts of the UI. eLife moved forward with the designs from this point by breaking the project into large components (Workspaces) such as assignment controls or submission wizard. This made it easier to work through parts of the interface that needed further consideration before increasing the fidelity of the designs to more closely match the final interface design.

8

Hindawi case study

Hindawi is one of the world’s largest publishers of peer-reviewed, fully Open Access journals. Built on an ethos of openness, Hindawi is passionate about working with the global academic community to promote open scholarly research to the world. Hindawi's experience with PubSweet is an excellent illustration of the option to “build-only-what-you-need” approach.

Hindawi currently operates its peer review process with a distributed Editorial Board model. PubSweet enabled Hindawi to experiment by adding additional oversight with a new Editor in Chief (EiC) role by building the requisite functionality into a new submission system. To start this process, Hindawi took many of the workflows and diagrams that existed for the current Hindawi system, and applied them to the a new EiC model.

As Hindawi uses its own peer review management system, many of the flows and requirements for a new platform were already established. They just needed to identify areas to improve, and potential new requirements for partners. Hindawi joined a two-day product design workshop to discuss workflows and user journeys based on the existing use cases, and list out all the key actions, statuses, and role requirements for the workflows.

The main outcome of the product design phase was a first set of wireframes from a UX/UI designer for the articulated workflows. Based on the wireframes, multiple rounds of feedback were gathered from the existing production team, internal researchers, and external partners, to ensure the manuscript's workflow was correct. That effort resulted in a definitive list of required workspaces.

Hindawi Workflow

Next, Hindawi reviewed how much of the workflow overlapped with xPub-Collabra and other existing PubSweet applications, and performed a gap analysis for PubSweet Components. This helped identify what could be reused and what needed to be built.

It is also worth mentioning that the logic governing the editor's process was deliberately left somewhat open-ended (e.g. no time limits, just reminders; no minimum number of reviewers, and so on). These details will be added once the flow of the manuscript works as expected. Hindawi also decided to leave complex email management out of the initial build to avoid complexity (i.e. certain email notifications are part of the system to begin with), but this functionality will need to be incorporated at a later stage. Discipline in choosing a minimum viable thin slice of functionality to build first — taking care not to build oneself into a corner — is an important guiding principle.

9

Europe PMC case study

Europe PMC is a repository providing worldwide access to life sciences articles, books, patents, preprints, and clinical guidelines. Europe PMC Funders' Group organisations mandate that published research arising from the grants they award must be made available through Europe PMC. One way this can be achieved is for authors to submit, via the submission system run by Europe PMC (Europe PMC plus), the manuscripts of such articles once they have been peer reviewed and accepted for publication.

Europe PMC staff at the European Bioinformatics Institute (EMBL-EBI) are currently in the process of redeveloping Europe PMC plus using PubSweet. Because Europe PMC plus only accepts articles that have already been peer reviewed and accepted for publication, and requires a full publication citation before the submission is considered complete, the Europe PMC plus workflow and data requirements are somewhat different from those of the journal publishers using PubSweet. The editorial staff and requirements are also much more circumscribed.

At the start of the redevelopment project, Europe PMC team members worked together with Adam Hyde in a Workflow Sprint (see Workflow Sprint chapter) to define an idealised workflow and identify any overlaps with existing PubSweet components.

Europe PMC plus submission workflow

Using this workflow and other excercises from the Workflow Sprint, the Europe PMC team came up with wireframes for three wizards that will comprise the new Plus system. Next, Europe PMC reviewed how much of the workflow and wireframes overlapped with xpub-Collabra, xpub-Faraday, xpub-eLife, and other existing PubSweet applications, and performed a gap analysis for the components that would still need to be developed.

Europe PMC follows a design and development process focused on User Experience (UX). The Europe PMC UX team improved the existing wireframes and developed user stories to expand upon the idealised workflow and provide detailed requirements for the components of each wizard. From this point, Europe PMC has quickly bootstrapped the Plus project based on xpub-Collabra. A UX designer is closely involved in the development of user interfaces from new and existing PubSweet components. Additionally, components and functionality are throroughly tested by the Europe PMC QA engineer, and user tested by the staff who administrate the Plus system. User testing with Europe PMC plus submitters will also be completed regularly throughout the development of the project.

Europe PMC plus will be hosted on EMBL-EBI's own computing infrastructure, such as its OpenShift container platform and its private Minio cloud storage.

10

A general model

As a result of collaboratively writing the use cases and the example of the Workflow Sprint method during this Book Sprint, we were able to come up with a useful abstraction of the different paths to designing a PubSweet platform.

In essence, Coko, eLife, Hindawi, and Europe PMC each started with an understanding of the publishing workflow. This is, in short, a high level description of who does what as described by roles (Editor in Chief, Handling Editor, Reviewer, Author etc). While these might be documented in different ways, such as in a list for Workflow Sprints, or in Trello for eLife, the effect is the same.

At this moment, the processes diverged. Workflow Sprints went rapidly towards designing the high level workspace diagram and then iterated on the UX/UI for each space later (during the build process), whereas Hindawi and eLife moved to UX/UI first, designing the micro-iteractions upfront, and that was the driver for understanding the workspaces needed.

Both pathways started from the same point — workflow — and ended in the same point — workflow mapped onto PubSweet workspaces. Both processes spend time understanding and optimising a publisher's workflow. Both also include UX/UI design and a way to describe their system in terms of workspaces. However the journey in between is quite different as you can see in the below diagram.

Different pathways from Workflow to Workspaces

The pathway for all starts at workflow. Workflow Sprints turns right and moves first to designing the block-level PubSweet workspace diagram before moving on to UI/UX. Hindawi and eLife both turn left to address UI/UX first, using that as a method for understanding the workspaces needed.

It should also be noted that in all cases the above diagram is convectional, in the sense that there are major flows but smaller iterative processes continuously move through each part of the above stages throughout the entire development cycle.

As the community grows and we can share more experiences like this, we will come to understand the strengths and weaknesses of each and learn which of these processes is good for which kind of situation. However it is interesting to note that all of the foundational Coko community partners place publishing workflow at the top of the mountain, and everything is driven by it - quite possibly a very different approach to the way publishing tool providers have done things to date.

3

Technical Architecture

…totamque infusa per artus

mens agitat molem et magno se corpore miscet.

Mind, immingled with the vast and general frame,

Fills every part and stirs the mighty whole.

Aeneid VI, 726-7

11

Architecture overview

If you are a developer who has jumped straight to the technical portion of this book, go back to the beginning now! PubSweet technical decisions are at their core motivated by the issues with workflow described in the previous sections. Understanding those issues will allow you to better understand why our software is structured the way it is. Equally, if you are a non-technical person, do at least skim the following sections! They will give you a better insight into the nature of our technology and our community of developers.

In this chapter we provide a brief overview of the main parts of a PubSweet application. For more detail you will need to consult the chapters that follow.

PubSweet is composed on a technical level out of the following:

pubsweet-server: an extensible Node.js server.

pubsweet-client: a set of core React components and Redux actions.

A data model: shared between client and server.

PubSweet components: extend the server and/or client's functionality.

authsome: encapsulates authorization logic.

pubsweet-cli : runs scripts to start an application and to manage the database.

Below is a diagram representing the high level architecture of a PubSweet application.

High level architecture of a PubSweet application

Server

The pubsweet-server package is the backbone of PubSweet applications. It is built on top of the Express web framework. It comes as a dependency to your project, removing the need to develop your server from scratch, while also allowing for extensibility, in order to cover different organizations' custom needs. It is responsible for:

Connecting to the database layer.

Defining the core data model.

REST API (deprecated)

GraphQL API (via Apollo Server)

The server can also extend its capabilities through server components. Common use cases for this would be providing a new API endpoint or GraphQL query and integrating to an external service, but not limited to these. Server components also have access to the application's context through the server. pubsweet-server needs a connection to PostgreSQL database in order to provide the full-featured functionality that a server is expected to have.

Client

The pubsweet-client package provides the core setup that your front end needs. This includes connecting to the server for data fetching and writing, as well as a set of low-level React components. The most important of these components is Root which sets up the React app that runs in the user's browser. It ensures that other PubSweet components have access to a consistent set of services including routing, theming, network access and state management.

pubsweet-client currently provides two options for handling network access and state management. Redux and an API helper are used for accessing the REST API, and Apollo Client is used for accessing the GraphQL API.

Redux

Redux is a predictable state container for JavaScript apps.

Redux uses actions to signal state-changing events, and reducers to handle updating the state based on the outcome of actions. pubsweet-client includes some actions and reducers for sending information to the pubsweet-server REST API and applying the results to the Redux state. Any component can also manage its own state using internal variables and choose not to interact with the Redux store at all. There's a balance in terms of what state should be global and what state can be local, based on that state affecting multiple components.

It is worth noting that Redux and the REST API are deprecated in PubSweet, in favour of using Apollo and GraphQL, covered in the following section.

Apollo

Apollo Client is designed to help you quickly build a UI that fetches data with GraphQL, and can be used with any JavaScript front-end.

Apollo Client is provided along with pubsweet-client in order to connect with pubsweet-server's GraphQL endpoint. It also uses a smart caching mechanism that updates its internal state when a mutation is run, along with useful helpers for handling queries, mutations and subscriptions.

Data model

Conceived after analyzing several use cases in the publishing world (blog, journal and book publishing platforms) and striving to represent the needed concepts as concisely as possible, the data model represented in PubSweet core (pubsweet-server and pubsweet-client) consists of the following entities:

Collection: A container, e.g. representing a journal, a volume or issue, a book. A Collection is mainly a container for several Fragments, but it can also store some associated metadata.

Fragment: A Fragment can represent such entities as a blogpost, a manuscript, an article, a chapter. Fragments could also potentially be used as a container, as it can contain other Fragments.

User: An entity representing registered users of an application.

Team: Teams represent groups of people, e.g. in the context of a journal publishing platform, a team can represent the editors of a journal or the reviewers of a manuscript. Teams can be associated with objects (e.g. a team of contributors on a blog post) or they can be global, i.e. have no associated objects (e.g. a group of senior editors). Teams are mainly used for establishing a user's permissions for an associated resource.

More recently these four core entities, while mapping well onto real world scenarios, have been found to be too limiting to represent the diverse information present in the publishing landscape. As such, a mechanism allowing the introduction of new models as components is planned. This will extend to data modelling the benefits of collaboration already present in components.

Components

A PubSweet component is a collection of modules that can extend or change the functionality of the server and/or client of a PubSweet app. By combining components, you can build any publishing platform or workflow. See the Components part of this book for much more on this topic.

Authentication and authorization

It is perhaps important to differentiate authentication from authorization, as these two concepts often get confused. Authentication is about establishing the identity of users, while authorization is controling user access to resources.

Authentication can be provided in various ways:

Username and password

Authentication token

External OAuth service, e.g. ORCID

Access control in PubSweet is applied through Authsome (see the “Authorization and Permissions” chapter).

Authsome is a flexible team-based authorization module, which means that authorization is determined based on team membership. A team consists of a group of members and belongs to one of the team types configured at load time. A team can also be conditionally active, i.e. only active if the current state of the object matches the requirement for team activation. Setting up and usage is covered in the Authsome chapter.

12

Why did we choose our technical stack?

The guiding principle has been to choose tools that are open source, mature and popular. In the open source world, the popularity of a project is important as it leads to a virtuous development cycle. More users mean more contributions, improved code quality, better tooling and increased scrutiny to security concerns all of which result in more users.

JavaScript

JavaScript is, along with HTML and CSS, one of the core languages of the web. After a period of stagnation in the 2000s, JavaScript is now enjoying a renaissance with important new features being standardised each year. The introduction of Node.js on the server and the rise of single page web applications means JavaScript is more important than ever, with some language popularity rankings putting it in the number one spot. This popularity comes with a host of benefits:

many ready-made software packages to choose from

easier to find software engineers

more learning resources

This makes it a natural choice for PubSweet as publishers often have undernourished technology resources. Consequently, PubSweet is trying to lower the bar as far as possible to help you build or extend a publishing platform. Indeed, we hope that this book enables you to work with local development houses to build or extend the publishing platform of your dreams.

Node.js

Node.js is by far the most common way of running JavaScript in a server environment. It has a large and very active community. The npm package manager is the biggest software repository on the planet, with more than double the number of packages as the next biggest.

React

React is a popular front end framework that takes the pain out of rendering complex, interactive user interfaces. It too has a large and very active community. However the feature that made it stand out is its component model which makes it an especially good fit for PubSweet.

React should not be confused with Web Components which is a technology standard currently being added to the DOM and HTML specifications by the W3C. Whilst this might seem to be a more attractive or interesting choice, browser support is still very poor, and the community is small.

PostgreSQL

PostgreSQL, while not always being the most popular open source database, is arguably the most mature, stable and feature rich. Some of these features allow it to be used both as a relational database and a document store. This makes it particularly suitable for a component-based system where parts of the schema may need to be loosely defined but still queryable.

GraphQL

GraphQL is an outlier in the list of PubSweet technologies as it is far less popular than the competing API standard, REST. However, the advantages it offers have been deemed compelling enough to warrant backing the less mature choice.

Some of these advantages are:

Declarative data binding – front end components can specify the data they need and hand off the responsibility for fetching that data to the system.

Self documenting – the API can be introspected to find the available queries and responses.

Extensibility built in – allows data types to be extended by components.

Clearer specification – REST is a very light specification which leads implementations to differ substantially in the details.

Some of the disadvantages are:

Whereas denial of service attacks against REST APIs can be mitigated by rate limiting at the network level, GraphQL APIs require a different approach. This is because queries can be arbitrarily complex and a single malicous request could result in hundreds of database queries.

Caching also requires a very different approach since there is no mapping from URLs to queries as in REST.

The GraphQL ecosystem is still young, but is growing at a pace and GraphQL is already used by many big players, including Facebook (where it originated).

Styled Components

Styled Components is a CSS-in-JS solution for styling React components. This is a very crowded space with many competing solutions and new ones being created all the time. With a large community, good documentation and first class theming support, Styled Components arose as a contender early on. The final decision to adopt it as a PubSweet technology was made by the community after comparisons with other solutions.

How were these technologies chosen?

Most decisions in PubSweet are done through a request for comments (RFC) process where:

anyone can suggest a change

it gets discussed by interested parties

a choice is made based on the presented arguments

An example of an RFC process is the styled-components discussion.

It's worth noting that major changes do happen. Two recent major changes include replacing PouchDb with PostgreSQL, and deprecating the REST API in favor of GraphQL. These decisions were made by the community through the RFC process, and we welcome your future participation.

4

Getting started with PubSweet

Facilis descensus Averno...

sed revocare gradum superasque evadere ad auras,

hoc opus, hic labor est.

To Avernus the downward path is easy…

But O! remounting to the world of light,

This is a task indeed

Aeneid VI, 126-9

13

Install PubSweet and set up an app

You'll need a couple of things installed on your machine before you get started: Node.js (version 8.9) or higher and yarn (version 1.3.2 or higher).

To start working on a PubSweet application, you could grab one that already exists from our GitLab, like Editoria or xPub. You could also start with pubsweet-starter that has been created with the sole purpose of getting devs up and running with minimal friction. Or, there's a tutorial on how to get up and running on the pubsweet.org website.

The rest of this chapter assumes that you chose neither of those paths and decided to create your application from scratch.

Before anything, you will need to start with an empty Node.js project to which you'll add the needed depencies by typing the following:

yarn add pubsweet \
pubsweet-server \
pubsweet-client \
pubsweet-component-login \
pubsweet-component-signup

Note: We use yarn in our examples, as we heavily use yarn workspaces in our code. Feel free to use npm, however, if you think that covers your use cases better.

Main concepts for your first app

pubsweet-client uses React. As such, it relies on an app.js file to serve as the root component, and a routes.js file to specify your app's endpoints. It also leverages popular libraries such as webpack and react-router to provide functionality for bundling and routing. Here's a simple example of how to get started.

You will need three main files: app.js, index.html, and routes.js. By convention, we add these files in an app folder in the root of the application.

index.html

Your index.html file is the web page into which your PubSweet app loads. All it contains is a <div id="root"> – which will be replaced with your React app's elements — and a script to load your bundled app.

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
</head>

<body>
  <div id="root"></div>
  <script type="text/javascript" charset="utf-8" src="/assets/app.js"></script>
</body>

</html>

Your index.html file should look pretty much like this example, although your script source might change if you choose to reconfigure your webpack settings.

app.js

This is your React app's entry point. It grabs the <div id="root"> from the index.html file and replaces it with the PubSweet app. This is also where you import a theme for your PubSweet app to use, if you choose to use one (for more information see the Theming section).

import 'regenerator-runtime/runtime'

import React from 'react'
import ReactDOM from 'react-dom'
import { hot } from 'react-hot-loader'

import createHistory from 'history/createBrowserHistory'

import { configureStore, Root } from 'pubsweet-client'
import theme from '@pubsweet/coko-theme'

import routes from './routes'

const history = createHistory()
const store = configureStore(history, {})

const rootEl = document.getElementById('root')

ReactDOM.render(
  <Root history={history} routes={routes} store={store} theme={theme} />,
  rootEl,
)

export default hot(module)(Root)

routes.js

Finally, you'll need a routes.js file for your app.js file to reference. This file indicates what components should be served up from different routes in the app's URL.

import React from 'react'
import { Redirect, Route, Switch } from 'react-router-dom'

import { AuthenticatedComponent as Private } from 'pubsweet-client'

import Login from 'pubsweet-component-login/LoginContainer'
import Signup from 'pubsweet-component-signup/SignupContainer'
import PasswordReset from 'pubsweet-component-password-reset-frontend/PasswordReset'

import Dashboard from './components/Dashboard'

const routes = (
  <Switch>
    <Route component={Login} exact path="/login" />
    <Route component={Signup} exact path="/signup" />
    <Route component={PasswordReset} exact path="/password-reset" />

    <Private>
        <Route component={NavigationBar} path="/" />
        <Switch>
          <Route component={Dashboard} exact path="/dashboard" />
          <Redirect to="/dashboard" /> // redirect other routes
        </Switch>
    </Private>
  </Switch>
)

export default routes

You'll want to configure this file to reflect your desired paths, components, and public/private pages.

Webpack

As mentioned earlier in this chapter, pubsweet-client uses webpack to provide app bundles. Create a folder called webpack in the root of your application and add the file webpack.development.config.js to it. Refer to webpack's documentation for what goes in the config or feel free to copy one of the existing webpack configuration files. It is possible to add a configuration file for each environment you'll be working in (for instance, you can add a webpack.production.config.js to define Webpack rules for the production environment) .

One thing to note is that PubSweet components are written in ES6 (including proposals down to Stage 2) and do not come precompiled to ES5. This means that you have to explicitly tell webpack to compile them, by including something similar to the following in your module rules config.

  {
    test: /\.js$|\.jsx$/,
    loader: 'string-replace-loader',
    query: {
      search: 'PUBSWEET_COMPONENTS',
      replace: `[${clientComponents
        .map(component => `require('${component}')`)
        .join(', ')}]`,
    },
    include: [
      // include app folder
      path.join(__dirname, '..', 'app'),
      // include pubsweet packages which are published untranspiled
      /pubsweet-[^/\\]+\/(?!node_modules)/,
      /@pubsweet\/[^/\\]+\/(?!node_modules)/,
      // include other packages when this repo is mounted in a workspace
      /packages\/[^/\\]+\/(?!node_modules)/,
    ]
  }

Configuration

PubSweet provides options to configure your application by adding configuration files to a config folder you'll create in the root of your application. More on the files required and the different configuration options for PubSweet applications can be found in the following chapter.

Running the application

Once you have your configuration set up, the application can be started. This is generally done using a script from package.json, which in turn executes pubsweet start. Alternatively it can be run directly from a terminal with npx pubsweet start.

14

Configuring a PubSweet app

Configuration files for PubSweet applications exist inside the config folder in each application's root directory. Node-config

is used for handling different files for different environments. A good general file structure would be the following, sorted by reversed priority (for example, an option in a more specific file will overwrite the same option in the default.js):

default.js defines options that apply to all environments

[ENVIRONMENT].js defines options that apply to this environment only, but should be commited (eg. development.js, production.js)

local-environment.js defines the options that apply to the local setup of a specific environment. These files should be gitignored. (eg. local-development.js), and

custom-environment-variables.js looks by default for environment variables.

Please refer to node-config's documentation for different options and extending the above. Also, it is important to note that you could, for example, use .yaml files instead of JavaScript ones, or not use environment variables at all. We don't require one way or the other – it's up to you.

Configuration options

Configuration files internally adhere to the following structure:

module.exports = {
  <component-name>: {
    <config-option-1>: 'value-1',
    <config-option-2>: 'value-2',
  }
}

Let's take a look at the options that you will most likely need.

Authsome

Config options:

mode: Declares where your authsome mode exists.

teams: Declares the teams that exist in the application.

authsome: {
  // This should be either an npm package or an absolute path, not a relative path.
  mode: path.join(__dirname, 'auth.js'),
  teams: {
    copyEditors: { // team type
        name: 'Copy Editors' // name for display purposes
    },
    productionEditors: {
      name: 'Production Editors'
    },
  }
}

See the Authsome chapter for more information.

dbManager

The options here will introduce a user to the database when pubsweet setupdb is run.

admin: Whether the user is admin user or not.

email: The user's email.

password: The user's password.

username: The user's username.

dbManager: {
  admin: true,
  email: 'john@example.com',
  password: 'somepassword',
  username: 'john'
}

mailer

Configure how emails are being sent out by the system.

from: Who the emails should appear as being sent from.

transport: The mechanism used to send the email out.

The example below uses Ethereal Mail which can be useful during testing as the emails are not sent to users but are held online so you can see what would have been sent out. Replace the auth credentials with your own account, or if using a different SMTP server, replace the host and port accordingly:

mailer: {
  from: 'nobody@example.com',
  transport: {
    host: 'smtp.ethereal.email',
    port: 587,
    auth: {
      user: 'xxx@ethereal.email',
      pass: 'xxx',
    },
  }
}

publicKeys

The whole config should not be available to pubsweet-client as it might include sensitive information (e.g. the secret). The publicKeys config option lets developers add a whitelist of keys that should be available in the client. To use this option, you need to use or copy our Webpack config.

publicKeys: [
  'authsome',
  'pubsweet',
  'pubsweet-client',
  'validations'
]

pubsweet

components: List of components in the system. It is necessary to add new PubSweet components here if they extend the system in some way (e.g. adding an API endpoint or a Redux action)

pubsweet: {
  components: [
    "pubsweet-component-login",
    "pubsweet-component-signup",
    "pubsweet-component-password-reset-backend",
    "pubsweet-component-password-reset-frontend"
  ]
}

pubsweet-client

API_ENDPOINT: Defines what the URL of the api is.

login-redirect: Where you're redirected to after login.

redux-log: Whether the redux logger is turned on or off (if you're using redux).

'pubsweet-client': {
  API_ENDPOINT: '/api'
}

pubsweet-server

baseUrl: The base URL of the actual deployment (eg. localhost:3ooo or myapp.coko.foundation)

db: Database configuration options which are simply get passed to node-postgres. Check the package's docs for valid options.

enableExperimentalGraphql: Toggles whether the graphql endpoint is registered on the server.

logger: The logging mechanism (eg. a winston config).

port: The port the server will run on.

secret: The secret for signing web tokens.

uploads: A filepath for the uploads folder.

'pubsweet-server': {
  baseUrl: 'localhost:3000',
  db: {
    database: 'mydb',
    password: 'mypass',
    port: 5432,
    user: 'myuser'
  },
  enableExperimentalGraphql: true,
  logger: new winston.Logger({
    transports: [
      new winston.transports.Console({
        colorize: true,
      }),
    ],
  }),
  port: 3000,
  secret: 'notsosecret',
  uploads: 'uploads',
}

Validations

Links to your type validations when writing to the database. PubSweet uses joi for validations.

validations: {
  fragment: {
    fragmentType: Joi.string(),
    kind: Joi.string(),
    title: Joi.string(),
    published: Joi.bool(),
    published_at: Joi.string(),
    source: Joi.any(),
    presentation: Joi.string(),
  },
  collection: {
    title: Joi.string(),
  },
}

Component configuration options

Components sometimes expect their own config options. Refer to each component's documentation for details. The most important thing to know is that the name of the component is the key.

'pubsweet-component-ink-backend': {
  inkEndpoint: 'http://ink.coko.foundation/'
  email: 'someuser@example.com',
  password: 'somepassword',
  recipes: {
    'editoria-typescript': 1
  }
}

Custom configuration options

Beyond the above, you could add new configuration options and use them at will. This would be most useful when adding configuration options for your custom components. Nothing is stopping you, however, from adding extra options to existing keys.

'pubsweet-server': {
  'secret': 'notsosecret',
  'mycustomoption': true
},
'my-custom-component': {
  'enableFeature': false
}

5

PubSweet Components

Centum complexa nepotes,

omnes caelicolas, omnes supera alta tenentes.

Embracing her hundred sons divine,

All numbered with the gods, all throned on high.

Aeneid VI, 786-7

15

What are components?

Background

Before explaining what a component is, it is important to provide some context on how a component fulfills part of the workflow model that you, as a developer, are working towards.

The Workflow is built using a number of Workspaces. Each workspace provide a certain piece of functionality to the PubSweet application. Each of these spaces is implemented by using a component (eg. a login page), which itself is usually composed of a number of smaller components (eg. a couple of input fields and a button).

Components

A PubSweet component is a JavaScript module that can extend or change the functionality of a PubSweet application. Developers and designers can use any combination of components they can imagine to build the publishing platform they need.

PubSweet components are broadly divided in two categories: server components and client components, depending on whether they aim to extend pubsweet-server or pubsweet-client respectively. Both types are expected to follow the naming convention of pubsweet-component- or @pubsweet/component- to allow for easy discoverability.

PubSweet components export certain keys from their main file. This export makes them loadable by PubSweet applications and tooling.

Server components

A server component can export the following keys:

server: This key allows Express.js routes and/or middleware to extend the server. The value for this key is a function that returns another function that accepts an Express.js app as its first argument. This function can do whatever it wants with the app (eg. add new routes, apply middleware functions). Take a look at the password reset component for a starting point.

typedefs: GraphQL type definitions

resolvers: GraphQL resolvers

module.exports = {
  server: () => app => {
    app.get('someroute', someRouteFunction)
    app.use(someMiddleware)
  },
  typeDefs: `GraphQL type definitions`,
  resolvers: { }
}

Client components

All client component options are exported under the client key. The options that can be nested under this key are as follows:

components: An array of functions that return React components

actions (deprecated): A function that returns an array of Redux actions to be introduced into the system

reducers (deprecated): A function that returns an array of Redux reducers

module.exports = {
  client: {
    components: [
      () => someReactComponent,
      () => anotherReactComponent
    ],
    actions: () => arrayOfReduxActions,
    reducers: () => arrayOfReduxReducers
  }
}

The login component is a good standing example of a client component. It (predictably) provides basic user login functionality in the client. It does this by exporting:

Redux actions for authenticating and de-authenticating users with the server

Redux reducers for managing user data and authentication tokens

A React login form that provides a login interface and uses the login Redux actions and reducers

You can install the login component and instantly have login functionality in your app. You can also pick and choose which parts of the component you use. For example you could build your own React login form and still make use of the Redux actions and reducers from the login component.

Components Library

As mentioned already, PubSweet components follow the naming convention of starting with pubsweet-component- or @pubsweet/component- to make them easily discoverable on the npm registry. However, this is not a requirement. In fact, components don't have to be published at all - they can live in a directory or a git repository.

A number of PubSweet components are already available for developers. Some of them exist in the PubSweet repository and are ready to use.

PubSweet UI

Most of the components in the Components Library use a number of smaller highly reusable components. From small components like input fields and buttons to a configurable form, there are a lot of small components that can be useful to any number of larger components.

PubSweet UI (@pubsweet/ui) is an important PubSweet package which intends to fill that gap. It makes an ever-expanding list of these components available for developers, and plays a central role in the development flow of a front end component. UI elements are categorized based on a loose adherence to the principles of atomic design.

PubSweet also provides a complementary library (@pubsweet/ui-toolkit) that provides some helper functions (more on that in the theming section of this book), as well as bits of reusable CSS (eg. rotation keyframes animations, fade ins etc.).

Conventional Commits

All changes to components are documented using Conventional Commits. A good example of how this might look like in practice is our commit log. All contributions to these libraries are required to follow this particular commit structure.

16

How do you create a component?

The component library is a resource that makes PubSweet better for everyone. The more components in the library, and the more people helping maintain them, the better the PubSweet ecosystem becomes for all of us.

When thinking of adding a component, be sure you check beforehand in the PubSweet component library for existing ones. As the library is constantly growing, it might be that a similar component has already been submitted.

Usually components are developed with reusabilty in mind, but they also follow YAGNI and KISS principles. It's up to the contributor to decide if a component can be reused, extended, or a new one could be created.

If you develop components, please add them to the library. If you use components developed by others, please consider helping maintain them by reporting issues and contributing bug fixes.

The process for contributing a component, or any other code, is described in the Contributing to PubSweet chapter. The technical requirements and suggestions for components are outlined below:

Development

The easiest way to start with a new component is to take an existing component from the component library, the closest one to what you need, and use it as a starting point.

A PubSweet component, whether a server or client component, is an npm package and should respect the following file structure:

index.js (conventional structure to register the component);

package.json (used for npm, declaring component dependencies);

README.md (documentation for installation, usage, customization etc.);

CHANGELOG.md (register version, changes, breaking-changes etc.);

Component files or folders.

Things to keep in mind when creating a new component:

Start with reusability in mind;

Follow the principles of DRY, KISS, YAGNI;

Use variables, theme variables for styling, React props etc. and not hardcoded values;

Follow Conventional Commits or keep a healthy changelog;

Write documentation;

Write tests.

Design

Layout and what makes a UI component

Relying on Brad Frost's principles of Atomic Design, PubSweet UI (@pubsweet/ui) is a single component offering an ensemble of smaller reusable elements classified as either atoms or molecules. We use the word component to refer both to a component released as an independent package and to any of the elements inside PubSweet UI.

Atoms and Molecules

The goal of PubSweet UI naming is to bring the same vocabulary across the applications and members of the community.

An atom is the smallest bit of UI that you can reuse in your application (a button, an input, etc.) You could call an atom any element that it wouldn't make sense to divide into multiple parts: a radio button without a label for instance.

A molecule is a reusable component made of multiple atoms. A login form, for instance, is made of two atoms of input (username and password) and an atom of a button (login).

You can use any of the atoms and molecules when creating a component. To be able to choose the atoms and molecules you need, you can have a look at PubSweet styleguide.

To be able to contribute a UI element, you need to be sure that it's usable within the starter-kit default theme without any CSS override.

Because an atom has no way to know how it’s going to be used in different molecules, it should be ready for all options. In particular, it should not define any layout that will determine where it appears on the page.

Let’s take the example of a series of radio buttons. In a group of two (yes and no for example), the radio buttons should be laid out inline. But if you need to provide more than two options, the brain will have an easier time scanning from top to bottom so the layout will be different.

Theming-ready component

As PubSweet provides a set of tools to easily change the look and feel of any UI element, your atom or molecule should be developed using the set of variables as the minimum required CSS.

For example, the primary button atom is set to use the primary color for its background color:

const Button = styled.button`
        background: ${props => props.theme.colorPrimary};
`

For a complete list of variables, you can check the pubsweet/design repo regarding variables.

Accessibility

Accessibility must be an important consideration in the choices of design and UI/UX made for PubSweet. As a minimum, all PubSweet components must comply with the Web Content Accessibility Guidelines (WCAG) version 2.0 at level AA. There is no simple checklist or automated test for this but here are some things to keep in mind:

Ensure that all forms and controls can be operated using the keyboard only. This includes having a recognisable style on the currently focused element.

Test components and pages in a screen reader such as ChromeVox, VoiceOver (Mac) or Narrator (Windows) and check that the order in which things are announced makes sense.

Use the Accessible Rich Internet Applications (ARIA) specification to your advantage. It provides a number of attributes that allow custom UI components to be imparted with the same accessibility features as native components such as <button> or <select>.

Colors should be chosen with contrast in mind and ratio should pass at least the AA grade for the WCAG 2.0 test.

Further reading

W3C Accessibility homepage

WCAG Quick Reference

Authoring Tool Accessibility Guidelines (ATAG)

The A11Y project

Inclusive Components

Setting the vertical rhythm

A vertical rhythm is a way to bring consistency in spacing, so each UI component fits into place and none seems awkward (unless you design it that way). Therefore each component should use the same vertical grid for its internal layout. Setting the line-height of your body text as a gridUnit variable is a good way to keep a coherent relationship between your text and your non-textual elements.

In HTML, the height of an element is always the result of this formula: border-top + padding-top + content + padding-bottom + border-bottom. Be sure that the result of the calculation is always a multiple of your gridUnit.

17

How do you use components?

Using a component is as simple as installing it, configuring it, and then including the functionality it exports in your own app's code.

Installing

Components can be installed using the PubSweet CLI:

pubsweet add login

This installs the pubsweet-component-login package, and adds the component to the app config so PubSweet knows to load it.

Note that the CLI adds the pubsweet-component- prefix automatically if it's not already present.

You can also manually install components using npm or yarn:

npm install --save pubsweet-component-login
//or
yarn add pubsweet-component-login

yarn add pubsweet-component-login

If you do that, you'll need to add the component manually to to config/components.json.

Configuring

There are two aspects to configuring components:

The app can be configured to know which components to load.

The component itself can be configured.

Specify which components to load

PubSweet will load any components in the pubsweet.components array of the configuration object.

You can add components to this array manually, or if you install the component using the CLI they will be added for you.

In a pubsweet CLI-generated app, the list of loaded components is in its own JSON file at ./config/components.json.

[
  "pubsweet-component-login",
  "pubsweet-component-signup",
  "pubsweet-component-wizard"
]

You need to use the full npm package name in the config array, i.e. pubsweet-component-login not login.

Configuring a component

Any component can define its own configurable options, and can access general configuration from the client or server.

Each component's README.md should specify which configuration options can be used, and what they do.

Example snippets from AppBar component. The app bar appears at the top of every page of the application.

Functionality customisation:

//It displays the name of the application (as a link to the home page), the
//username of the current user, and a link to sign out.
<AppBar brand="xpub" user={{ username: 'user', admin: true }} />

//When the user is not signed in, only the login link is displayed.
<AppBar brand="xpub" />

//Can optionally pass navigation links.
<AppBar
  brand="xpub"
  navLinkComponents={[
    <Action active to="/home">
      Home
    </Action>,
    <Action to="/about">About</Action>,
  ]}
  user={{
    username: 'user',
    admin: true,
  }}
/>

//Can use a custom component as right element.
//Right component props - the right component will be passed the following props:
// - user - the current logged in user; null if no user is logged in
// - onLogoutClick - the logout function
// - loginLink - link to redirect users to login
const RightComponent = ({ user, loginLink, onLogoutClick }) => (
  <div
    style={{
      display: 'flex',
      width: 200,
      justifyContent: 'space-between',
    }}
  >
    <span>{user ? user.username : 'admin'}</span>
    <button onClick={onLogoutClick}>logout</button>
  </div>
)

<AppBar
  brand="xpub"
  user={{ username: 'userName', admin: true }}
  rightComponent={RightComponent}
  onLogoutClick={() => console.log('Logout clicked')}
/>

Style customization:

Edit your theme.js file under cssOverrides to override desired properties:

const cokoTheme = {
//....
  cssOverrides: {
    AppBar: {
      Root: css`
        box-shadow: 0 0 1px ${th('colorPrimary')};
        margin-bottom: 1px;
      `,
      LogoLink: css`
        &:hover:before {
          visibility: hidden;
        }
      `,
    }
  }
}
export const cokoTheme

6

Authorization and Permissions

Procul, O procul este, profani

Away, away, O souls profane!

Aeneid VI, 258

18

Why Authsome?

The philosophy of workflow design articulated in the “Platform Design” section places emphasis on making the same Workspaces reusable by many different kinds of users. The system for deciding how a workspace should appear for different kinds of users must therefore encapsulate a significant amount of the workflow logic, and needs to be highly flexible. PubSweet's answer to this problem is the Authsome module.

The publishing world has a unique combination of requirements when it comes to authorization:

highly complex hierarchical roles (many different roles with very specific tasks),

collaborative work on documents (many people working on the same document at the same time)

and different states of documents (e.g. a document can be in a 'submitted' state, or in a 'review' state).

This combination of requirements is not trivial to address using typical approaches for permissions in a web applications.

For example, a baseline approach would be a role-based system (RBAC), so let's try to work through an example of a journal publishing system using roles. In RBAC, you assign roles to users of your system, and that assignment of a role grants them permissions. Let's try to imagine a simple journal publishing system, and the roles that describe it:

Author: users with the Author role can create and edit submissions.

Reviewer: users with the Reviewer role can create and edit reviews.

Editor: users with this role are all-powerful.

So far so good. These seem very sensible roles and permissions. But let's work through the example further. One of the requirements is that reviewers can only create a review for specific submissions, to which they are assigned. We can address this by enabling the linking of roles to resources; so, for example, the reviewer role can also be a resource-based role, linked to a specific manuscript: User X has the Reviewer role for manuscript Y. This seems sensible too. But let's continue working through the requirements.

Another requirement is that manuscripts should not be edited by the author while they are in review. This is where the combination of requirements becomes an issue for RBAC and similar systems and they start to break down. Specifically, permissions are often based on the state of the resource as well as other factors. This kind of granular control is nearly impossible with RBAC, but is achievable with RBAC's evolution, attributes-based access control or ABAC.

In ABAC, permissions can depend on any attribute in the system, not just roles. This gives you a lot of flexiblity in terms of how to structure your authorization principles.

As we have learned with other bits and pieces of the PubSweet ecosystem, the more flexiblity there is in a given system, the less likely it is that reusable solutions will come out of it. Some rigidity and structure is good for reusability, so we designed a flexible but well defined system within an ABAC context: Authsome. On the highest level, Authsome consists of a question-like interface: “Can user X do action Y to object Z?”, and a completely flexible mechanism for answering these types of questions in so-called authsome modes. Combining this approach with PubSweet's core Team model (teams of different types, e.g. team of Reviewers, linking groups of people to a specific resource), creates PubSweet's flexible but structured answer to the incredibly varied requirements of the publishing landscape.

19

How to use Authsome

Authsome is a minimal module with some syntactic sugar surrounding a flexible attribute-based access control (ABAC) core.

Each PubSweet app has a configuration file where you can specify by filepath an authsome “mode” (set of authorization functions) you'd like your application to use and what team types should exist in the system. For example:

module.exports = {
  authsome: {
    mode: path.resolve(__dirname, 'authsome.js'),
    teams: {
      seniorEditor: {
        name: 'Senior Editors',
      },
      handlingEditor: {
        name: 'Handling Editors',
      },
      managingEditor: {
        name: 'Managing Editors',
      },
      reviewer: {
        name: 'Reviewer',
      },
    },
  },
}

See also https://gitlab.coko.foundation/xpub/xpub/blob/master/config/default.js#L5-22:

The way your mode handles an authorization query is completely flexible, for example, the simplest mode could look like:

const yourMode = async function (user, operation, object, context) {
  return true
}

The above will just return true for any kind of authorization query, so it's not really useful outside of the imaginary educational world (take a look at example modes below).

Authsome is designed to enable isomorphic use on the client and server. Changing the authsome mode therefore has the potential to change the behaviour of the entire application, from all of the REST endpoints, the GraphQL API, through to the client-side app.

The Authsome API

The Authsome external API consists of:

const authsome = new Authsome({mode: yourMode}, context)
const permission = authsome.can(user, operation, object)

The call to authsome.can is called an “authorization query” or “question” in discussions and you'll see these terms pop up in various places.

A useful but still minimal example might be something like the below, where you instantiate a mode with more information about the environment where authorization questions are asked (such as how to find a user in the database):

const authsome = new Authsome({mode: yourMode}, {
  models: {
    User: {
      find: id => User.find(id)
    }
  }
})

This would proxy models.User.find to your User.find, which is accessible from where you instantiate Authsome, e.g. in your server or your client, and in practice this gives your mode access to your models or similarly important context. An authsome mode that knows about the User model can then do things like this:

module.exports = {
  before: async function (userId, operation, object, context) {
    const user = await context.models.User.find(userId)
    if (user.admin) {
      return true
    }
  },
  someOperation: async function (userId, operation, object, context) {
    const user = await context.models.User.find(userId)
    if(user.allowedSomeOperation) {
      return true
    }
  }

The above gives you a good way to structure your authorization modes, where each property of your mode corresponds to handling of authorization queries for a certain operation. The one exception is the before property of a mode, which is evaluated before everything else, and is used to give, for example, admin users the permissions to do everything. The other property of the above example mode is someOperation, which corresponds to the operation parameter in an authsome.can(user, someOperation, object) query.

The client provides a helper component that make working with authorization on the client-side easier — the Authorize helper:

import Authorize from 'pubsweet-client'

<Authorize object={manuscript} operation="DELETE">
  <Button onClick={this.onDestroyClick} plain>
    Delete
  </Button>
</Authorize>

Above is an example of showing/hiding UI that relates to an authorization request, for another example, perhaps a Create Blogpost button .

How to write your mode

Note that we can replicate an RBAC system with Authsome if we wish, by storing whether or not a user can do someOperation on the User model. To give more flexibility, however, we recommend using the concept of Teams. Teams are groups of users that are either:

Object-based: linked to particular objects (a team of reviewers may be reviewers for only one manuscript)

Global: not linked to particular objects (admin permissions are the same for every kind of object)

The permissions of teams on objects are then stored in the authsome mode itself. That is, the authsome mode checks which whether the teams that are linked to a particular object can validly access that object in its current state. An example of a team is, e.g. a team of reviewers, which is linked to a manuscript object. When the question of authsome.can(reviewerUser, 'review', manuscript) is considered, the mode checks if the status property of the manuscript is 'in-review', and returns true if it is.

This system involves storing some redundant data, since we create a new set of teams for each new object in the system, but we find the flexibility gained to be worthwhile.

Example: How can I prevent the Editor in Chief from accepting their own manuscript?

For some reason this question gets asked often in various contexts and variations. We don't know why exactly; maybe it's because it's something that's hard to do in current systems. With authsome it's straightforward.

If you have authorization failure conditions (as opposed to authorization granting conditions) you have to address those first. In the case of this question, authsome.can(editor, 'accept', manuscript), you'd first check if the authenticated user (the editor) is a member of a global team of type 'Editor in Chief' and also one of the authors of the manuscript – and return false/deny access if they are, but allow every other action. A simpler example would be if one imagines a President, who has the power to issue pardons, but should not be able to pardon himself. In authsome, you'd simply do this in the mode:

module.exports = {
  pardon: async function (userId, operation, object, context) {
    const user = await context.models.User.find(userId)
    if (user === object) {
      return false
    }
    return true
  }

Using authsome you can then ask questions like:

authsome.can(president, 'pardon', president)

With this approach even generally omnipotent users' permissions can be finely tuned.

Example 2 : PubSweet starter (a science blog)

In PubSweet starter, we have two types of teams. One is a “contributor” type, which allows you to create blogposts for the blog. The other is a “coauthor” team type, which allows you to update a specific blogpost (write it with someone). These are the only two team types.

When you're managing teams, you create a new team with a certain type, and a certain object (in the case of Contributors, you would choose the blog object as the object of the team). You can then add members to this team, and those members can then ask for and receive authorization to create blogposts for the blog.

Further Examples

For examples of Authsome in action look at the built in example modes:

Blog

Journal

Noon

Specific

The modes above are used mostly to test that authsome works, and continues to work, for all the use cases it's built to support. There are also real world/advanced examples out there for xpub (PubSweet-based journal publishing platform) and Editoria (PubSweet-based book production platform). To see how Teams are used to address authorization requirements, consult these.

7

Theming

discolor unde auri per ramos aura refulsit

Where through green boughs

Flames forth the glowing gold's contrasted hue.

Aeneid VI, 204

20

Using themes with PubSweet

Overview

It's desirable to have a coherent, consistent user experience throughout a platform. Buttons should all behave the same way, links should all have the same look, tooltips should all use the same font, and so on. Such consistency improves the user experience by providing familiarity and reassurance. PubSweet achieves this consistency using a theming mechanism built on top of React and Styled Components. This mechanism provides a number of options which can be mixed and matched.

The simplest option is to use one of the existing PubSweet themes such as the default Coko theme.

The second and most commonly used option is to customise the theme variables of an existing theme. Choose one you like the look of, then edit the colors and fonts to more closely fit your brand.

If you wish to change a style not accounted for by the theme variables, many components provide extension hooks. These allow the theme to specify arbitrary CSS to be applied to components. With this approach, care must be taken to ensure the new style does not adversely affect the usability and accessibility of the affected components.

Despite these extension points, sometimes the theme is not enough and the component itself must be modified. In this case, instead of forking (copying and editing) the component, we recommend that you reach out to the community to see whether your proposed modification could instead be integrated into PubSweet.

Hindawi and eLife collaborating on component styling

As a last resort, you always have the choice of creating your own custom components. See Creating a component for more information, and the chapter on Contributing to PubSweet to learn how and when to feed such efforts back to the community.

Using themes

To get started with the default PubSweet theme, you need to pass it as a prop to the Root React component frompubsweet-client, generally in your app.js file. This will make it available to all the Styled Components in the app as props.theme. See Getting Started with PubSweet for an example app.js file.

About theme variables

In its most basic form a PubSweet theme is a list of variables and their corresponding values. UI components can use these variables to adapt their look and feel to the current theme.

For example, take a look at the component below.

const Button = styled.button`
        background: ${props => props.theme.colorPrimary};
`

Refer to the Styled Components documentation if this looks new to you.

This component expects a colorPrimary variable to have been declared in the theme, and it will apply that variable's value to its background. Other components can also apply the same color for their properties, giving your application a consistent look and feel that can be modified with minimal effort.

Use of variables in components is encouraged, as it enhances their reusability. This way you can use different themes to deliver a distinct experience when using the same components.

A more realistic version of a button component would look more like the code below.

const Button = styled.button`
        background: ${th('colorSecondary')};
  border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')};
  border-radius: ${th('borderRadius')};
  color: ${th('colorText')};
  font-family: ${th('fontInterface')};
  font-size: ${th('fontSizeBase')};
  line-height: ${th('gridUnit')};
  min-width: calc(${th('gridUnit')} * 4);
  padding: calc(${th('gridUnit')} / 2);
`

Since accessing theme variables is such a common case, a helper function th is provided. th('name') is equivalent to props => props.theme.name.

It's generally advisable to give the list of variables that you use some thought in advance, or you'll quickly find yourself in variable mayhem. A bad example of a variable name would be varForItalicWordInTitlesWhenFirstOnPage. This is unlikely to be reused in the application, as it’s too specific.

Variables are named according to a convention using camel case and two or optionally three parts where the parts are as follows: categoryElement or categoryElementModifier.

Core theme variables

Colors

colorBackground: The background in your pages (eg. on the body HTML tag)

colorBackgroundHue: Used to create a discrete contrast with the default background color

colorPrimary: Indicates a primary call to action

colorSecondary: Default color for non-primary actions

colorBorder: For borders around elements

colorFurniture: Meant to be applied to elements that indicate content division

colorError: Used to indicate an error in validation

colorSuccess: Used to indicate a successful validation state

colorText: Default font color

colorTextReverse: Reverse font color

colorTextPlaceholder: Used for text field placeholders

Typography

fontInterface: Used for elements by default

fontReading: Main reading text

fontWriting: Font used for writing (eg. inside an editor)

fontSizeBase: Default font size

fontSizeBaseSmall: Smaller variation of the default font size

fontSizeHeading1: Size for Heading 1

fontSizeHeading2: Size for Heading 2

fontSizeHeading3: Size for Heading 3

fontSizeHeading4: Size for Heading 4

fontSizeHeading5: Size for Heading 5

fontSizeHeading6: Size for Heading 6

fontLineHeight: Default line height

Spacing

gridUnit: Base interface space measurement used by elements and typography

Border

borderRadius: Radius value applied to borders

borderWidth: Width value applied to borders

borderStyle: Style applied to borders (eg. solid, dashed)

Shadow

dropShadow: Default shadow that is applied to elements that float (eg. tooltips, modals)

Transition

transitionDuration: How long transitions should last

transitionTimingFunction: Which function should be applied to transitions (eg. easein)

transitionDelay: How long transitions should be delayed before they begin

In order to make it easy to get started, we also provide the default theme with PubSweet. The default theme has all the variables that have been listed above, with values that we consider a good general starting point. This theme has intentionally steered away from bold stylistic choices and is intended to be as vanilla as possible.

If you find the default theme doesn't meet your needs, it can still serve as a great starting point to create your own theme. Simply copy the list of variables into your own theme, then change the variable values until they suit your taste.

Existing PubSweet components, and those contributed by the community, must use this core set of variables. This ensures that any component can be used out-of-the-box with any theme. You may choose to define further variables in your theme and use these in your own components, but be aware that this will make it harder to contribute your components back to the community. It is recommended, but not required, that you include your style-related assets (e.g. fonts) in your theme, especially if you plan to publish it as an npm package.

It may be that the set of core theme variables grows with time as new interface elements emerge which need to be standardised across the UI. If you have a use case for such an addition, we encourage you to discuss it with the community.

21

Advanced theming

Beyond variables

We have covered how changing a variable can affect the look and feel of components that use that variable. But what about scenarios where there is no variable to effect the necessary change?

Let's look at the following component:

const MyComp = styled.div`
        background-color: ${props => props.theme.colorBackground};

        &:hover {
                background-color: none;
        }

        ${props => props.theme.cssOverrides.MyComp};
`

The background color uses a theme variable, but the hover color does not as there is no theme variable for it. However, the code on the last line provides an extension point for the theme to override the style of the component.

const myTheme = {
        colorBackground: 'white';

  cssOverrides: {
                MyComp: css`
                        &:hover {
                                background-color: 'magenta';
                        }
                `
        }
}

Now the component will turn its background color to magenta when hovered over. This allows us to apply arbitrary CSS to components by modifying only the theme. This way we can also keep our components clean, letting them provide some sane defaults, but making sure that different style needs are not a blocker for reuse. Note that it's important that the overrides are injected last in the component CSS to ensure that all styles can be overridden.

Helper functions

There are a small number of functions to help with theming (and UI component development in general). These are available in the @pubsweet/ui-toolkit package.

th

The th function simplifies accessing theme values from a styled component. Instead of

const MyComp = styled.div`
        background-color: ${props => props.theme.colorPrimary};
`

you can write

const MyComp = styled.div`
        background-color: ${th('colorPrimary')}
`

override

In a similar fashion to the th function, override is meant to simplify the writing of the override lines that come at the end of components' style sections and provide a hook to connect to the theme's overrides section.

This is what an override would look like in a component without the helper function.

const MyComp = styled.div`
        background-color: ${th('colorPrimary')};

        ${props => props.theme.cssOverrides.MyComp};
        ${props => props.theme.cssOverrides.MyComp.Root};
`

There are two lines here to enable writing an override in the theme, either (a) directly with the css function that styled-components provides, or (b) as a key in a larger object. In the case of (a) the theme would be defined as follows:

const MyTheme = {
        colorBackground: 'white',
  colorPrimary: 'blue',

  cssOverrides: {
        MyComp: css`
                        background: 'green'
                `
  }
}

In the case of (b), when part of a larger object, the theme is defined:

const MyTheme = {
        colorBackground: 'white',
  colorPrimary: 'blue',

  cssOverrides: {
        MyComp: {
        Root: css`
                          background: 'green'
                  `
    }
  }
}

Using the override function our component code could be rewritten as follows:

const MyComp = styled.div`
        background-color: ${th('colorPrimary')};

        ${override('MyComp')};
`

Or to get more specific in the object hierarchy:

const MyComp = styled.div`
        background-color: ${th('colorPrimary')};

        ${override('MyComp.Label')};
`

Note that the override function makes the assumption that your theme's overrides exist under the cssOverrides key in your theme object.

darken / lighten

You can use our darken and lighten functions to adjust colors' lightness.

const MyComp = styled.div`
        background-color: ${darken('colorPrimary', 0.5)};
`

This is particularly useful for avoiding the need to introduce new variables to account for a darker version of a color. For example, if two variations are needed for green in dealing with successful validations, the variables colorSuccess and colorSuccessDark might be used. Then, if a change to colorSuccess is needed, a change to colorSuccessDark will also be needed. By using the darken and lighten functions, a single variable and any associated variations are automatically updated.

These functions are flexible in their input, allowing for hex values, rgb values or percentage points instead of decimal points to be given as arguments. All of the statements below are valid.

const MyComp = styled.div`
        background-color: ${darken('#AAAAAA', 0.3)};
  background-color: ${lighten('#AAA', 0.7)};
  background-color: ${darken('rgb(255, 255, 255)', 0.2)};
        background-color: ${darken('colorPrimary', 0.3)};
  background-color: ${lighten('someProperty.customColor', 0.5)};
  background-color: ${darken('colorPrimary', 50)};
`

headingScale

The headingScale function provides an easy way to conform to a heading scale derived from a base value without the need to look up the scale's values online. The function will receive a base font size, a scale and a heading level (1 to 6) and return the appropriate font size by multiplying the scale by the previous heading's font size.

const MyTheme = {
  fontSizeHeading4: `${headingScale(12, 1.2, 4)}px`   // evaluates to 17.28
}

Note that the scale function returns values as integers so units must be appended (eg. px).

8

Deployment

Foliis tantum ne carmina manda

O, not on leaves, light leaves, inscribe thy songs!

Aeneid VI, 74-5

22

Deployment essentials

Once you have built your app, you will be ready to deploy it. PubSweet has been written according to the principles of the 12 factor app, so it makes minimal assumptions about how it will be deployed. For example:

Configuration is stored in the environment. In other words, it is separate from the application itself and can be injected at deployment time.

Backing services are detachable: they are also features of the environment, not of the application. For example, in AWS you could use S3, or in a different cloud provider use a different storage service.

Logs can be configured with any transport, which means there are no assumptions about how you do you logging and you can choose the method you prefer. You can even configure the PubSweet logger to use a different logger entirely.

You are therefore free to deploy the application in the way that suits you.

Configuring a deployment

We use the node-config npm module to manage configuration (see the 'Configuring a PubSweet App' chapter). Configuration files will be selectively loaded based on the value of NODE_ENV. It is important to note that any configuration in the config files can be overridden at runtime by setting the NODE_CONFIG environment variable.

All environments require at minimum a PostgreSQL database; further services may be required depending on the PubSweet modules installed.

Building and running an app

PubSweet apps can be started using pubsweet server, but this is not recommended for production: startup time would be very long as this command starts a webpack dev server and so compiles assets every time it is run. Instead you should have a pipeline with a separate build step that runs yarn install then runs pubsweet build to build the static assets. It is important that NODE_ENV is set to production when running pubsweet build or the wrong app and webpack config will be loaded.

You will need an entry point that calls the startServer function from pubsweet-server. In pubsweet-starter, the entry point for the server is app.js in the project root: you can run this directly or using a process manager of your choice.

Building with Docker

Coko uses a hierarchy of Docker images to reduce build times and standardise images as far as possible. The base image is built inside the pubsweet/infra Gitlab repository, (see images/pubsweet-base) and only defines the Node.js version. You can extend this base image to ensure you are always running on a compatible Node.js version.

Our application Dockerfiles, such as the one in pubsweet-starter, installs additional dependencies required for testing, namely Chrome and Firefox.

There is also an xpub/xpub:base image stored in the pubsweet/deployment-config repo which builds a yarn offline cache to speed up the installs on subsequent layers (containers that use this image).

23

CI/CD pipeline

This chapter gives some insight into how Coko have set up their continuous integration (CI) and continuous deployment (CD) pipeline. You can choose to copy it entirely, or take bits and pieces. The purpose of the pipeline is to build Docker images from the latest code, run tests and linting, and create deployments for every branch pushed to our repos.

You can find below a graph illustrating the CI/CD pipeline.

CI/CD pipeline

Decoupled design

Deployment configuration and scripts are decoupled to allow reuse between different projects with different requirements. This means that to get an overall picture of how deployment works for Coko, you need to look in three places:

.gitlab-ci.yml: in the project root, defines the pipeline steps.

pubsweet/deployer image: carries out deployment to Kubernetes.

Configuration repository: holds Kubernetes configuration templates. For example pubsweet/deployment-config-postgres.

In your own implementation you are free to provide a custom version of any of these components.

GitLab runners

Coko uses GitLab CI which is the open-source continuous integration service included with GitLab. GitLab CI acts as coordinator for individual jobs that are executed by runners. A runner is a computer, usually in the cloud, running the gitlab-ci-runner software. You can either use shared GitLab runners, or any set up your own external one(s). Documentation is contained in the GitLab docs repository.

Shared GitLab runners

If you have your Git repository hosted in gitlab.coko.foundation, you don't have to set up the runner yourself. Shared GitLab Runners have already been set up there and registered with the CI/CD service. In order to set up the pipeline you will need to create and populate a .gitlab-ci.yml file in your project (see below) and configure the project to use shared runners.

External runners

If you opt not to use the shared GitLab runners provided by Coko, you can set up external Runners and integrate them with your GitLab CI/CD. To setup your external runners, you need the URL of your GitLab server and also the registration token for your GitLab repository, which you can find in the CI/CD settings of your GitLab project.

Jobs configuration

On how to configure your jobs with .gitlab-ci.yml, you can refer to GitLab's .gitlab-ci.yml file configuration page, and refer to the xPub project as an example.

Coko's current pipeline is divided into five stages: build, test, review, staging and demo.

Build: build the Docker image, tagged with the CI commit hash, and push it to the Docker Hub. Subsequent stages pull this image.

Test: run unit and end-to-end tests. There are two things to note: 1. we do not need to provide Postgres ourselves as the services feature of GitLab CI does this for us; 2. unit tests and end-to-end tests and linting are all run in parallel as separate jobs with the test stage.

Review: the review stage makes use of the pubsweet/deployer image (see next section), to deploy the built image on Kubernetes. The review stage has two jobs, one of which is run automatically after test to create the deployment, and another which is run when the branch is deleted to delete the deployment. The URLs for review deployments are generated dynamically from the branch name.

Staging: staging is run automatically on merge to the master branch to deploy master to a fixed address.

Demo: the demo stage is not run automatically, but can be triggered to deploy any branch, to a fixed demo address.

PubSweet deployer image

The PubSweet deployer image is used on review, staging and demo stages in tandem with the configuration repository to transform environment variables in GitLab CI into Kubernetes configuration objects and push the configuration objects to the cluster.

The image contains the Kubernetes client (kubectl), gettext for template substitution, and a deployment script (deploy.sh). To use it, the repository to be deployed must meet the following requirements:

Kubernetes integration must be set up to enable kubectl to authenticate to the cluster . See here.

.gitlab-ci.yaml must provide the right variables to deploy.sh: 1. An environment name and url must be supplied for the CI job; The url must evaluate to less than 64 characters. 2: If deploying from a monorepo, the job must define a PACKAGE_NAME variable that will be used to select templates for deploying that package.

There must be a CONFIGURATION_REPOSITORY variable containing the https git url of the repository holding Kubernetes object templates (which must be publicly accessible).

The configuration repository must be in the right format (see the dedicated section below).

Example Usage

Include something like the following in your .gitlab-ci.yml:

variables:
  BASE_DOMAIN: myapp.example.com
  CONFIGURATION_REPOSITORY: https://gitlab.coko.foundation/xpub/deployment-config.git

stages:
  - deploy

deploy:
  image: pubsweet/deployer
  stage: deploy
  environment:
    name: production
    url: "https://${$CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
  script:
    - source deploy.sh
    - create_deployment

By setting environment.name and environment.url for our job, we tell GitLab how to label our deployment and link to it from the environments page. GitLab also generates a unique CI_ENVIRONMENT_SLUG for each environment, which we use inside our templates to name and label our Kubernetes objects. Through its dynamic environments feature, GitLab will automatically connect an environment labelled review/$CI_COMMIT_REF_NAME to any merge requests for that commit ref.

Monorepo

If deploying multiple apps from a monorepo, be sure to set PACKAGE_NAME like this:

deploy_mypackage:
  image: pubsweet/deployer
  stage: deploy
  variables:
    PACKAGE_NAME: mypackage
    REQUIRES_PROVISIONING: "yes"
  environment:
    name: production
    url: "https://${CI_ENVIRONMENT_SLUG}-${PACKAGE_NAME}.${BASE_DOMAIN}"
  script:
    - source deploy.sh
    - create_deployment

Undeploying

deploy.sh also provides a delete_deployment function for use in stopping review apps. It is used in the same way:

  script:
    - source deploy.sh
    - delete_deployment

This function will delete everything deployed except Persistent Volume Claims, which remain in case data needs to be salvaged. To delete PVCs as well run delete_objects_in_environment pvc.

Some gotchas:

REQUIRES_PROVISIONING should be set for this job too, if it was set in the deploy job, or services will not be deleted

PVCs must never be deleted before delete_deployment is run. Kubernetes has bugs which can make deleting a StatefulSet difficult once its associated PVC has gone.

Configuration repository

The configuration repository holds templates for Kubernetes objects. PubSweet apps follow a common pattern in terms of their template requirements. We define four types of Kubernetes objects:

1. ConfigMap

These contain configuration that will be injected into the containers to be run. xpub-elife has two: one containing a script for seeding the database, another containing node config.

2. Deployment

This object specifies what will actually be run, linking to any ConfigMaps or volumes and defining the endpoints Kubernetes will use to check the container status. A deployment is a recipe for creating pods (one or more), which are sets of containers running on a single node. That means if your deployment specifies that two replicas should be maintained, removing one of the two pods will trigger the creation of a new one. Deployments are stateless: responsibility for maintaining state is handed to the database, which is operated using a StatefulSet and a PersistentVolumeClaim.

The xpub-elife deployment specifies a main container and an initContainer. The latter must exit successfully before the main container will be started and is appropriate for seeding.

3. Service

Because pods are ephemeral, we require another object to keep track of their network addresses: this is the service. It provides stable routing to all pods with the label defined under selector via Kubernetes' native service-discovery mechanism.

4. Ingress

The last piece of the puzzle is the ingress object, which defines how requests to the public gateway should map to backend services. These are referred to by the name and port defined in the service object.

Note, finally, that we deploy all these objects inside a single Kubernetes namespace, which is that provided by GitLab integration as KUBE_NAMESPACE

Secrets

The deployment templates will likely require several custom variables to be present in the environment. The easiest way to get them there is to add them as secret variables to the project that you want to deploy. Some variables you might need are:

DOCKERHUB_USERNAME (for use in .gitlab-ci.yaml build job)

DOCKERHUB_PASSWORD (for use in .gitlab-ci.yaml build job)

INK_EMAIL (PubSweet app config)

INK_PASSWORD (PubSweet app config)

PUBSWEET_EMAIL (PubSweet app config)

PUBSWEET_PASSWORD(PubSweet app config)

PUBSWEET_USERNAME (PubSweet app config)

PUBSWEET_SERVER_SECRET (PubSweet app config).

You can examine the templates to see how the PUBSWEET_* and INK_* variables are used.

Repository and template format

As noted above, the deployer image expects the configuration repository to be in the right format, as below:

├── provisioning
│   ├── example-template.yaml
│   ├── another-example-template.yaml
└── templates
    ├── example-template.yaml
    ├── another-example-template.yaml

Kubernetes object templates for apps must be located in the templates folder. If you are deploying from a monorepo and not all apps require some services, place templates for those services in a directory called provisioning. Templates will only be deployed from this directory if a special variable REQUIRES_PROVISIONING is set on the job. Other requirements are:

Templates for individual packages in a monorepo must be named such as PACKAGE_NAME-*.yaml, where PACKAGE_NAME is the variable set on the CI job.

TLS fields should not be added to Kubernetes Ingresses in templates. The deployment script infers whether TLS is necessary from the environment.url and uses this script to generate the appropriate templates. TLS depends on kube-lego being installed on the cluster.

The templates must be formatted for envsubst (part of gettext).

SECURITY NOTE: all deployed objects except ConfigMaps will appear on stdout. To avoid publicising secrets, make sure they are stored in ConfigMaps or, better, use Kubernetes secrets.

9

Development Help

Doceas iter et sacra ostia pandas.

Show me the way, and open wide yon holy doors.

Aeneid VI, 109

24

Where can I ask questions?

So. You've got this far and you're eager to get started. But you still have so many questions!

If you have any questions about PubSweet – from doubts about how it would fit in your organization, to queries about specific error messages – or you just want to say hello, your starting point should be the Coko Mattermost chat server. It has a number of channels such as #xpub or #PubSweet, so if you know where your question fits, feel free to go directly to those channels. If you're unsure, go for #Town Square, and we'll figure it out together.

The PubSweet community is worldwide, so there's usually someone around to field questions, no matter the hour. They may not know the answer, but they should be able to direct you to someone who does.

If you already know your question relates to a bug or feature request, then you can go straight to the PubSweet GitLab repository (it's just one repository for all packages, so no need to go looking for the right place), and create a new issue.

Tamlyn (eLife) and Yannis (Coko) pondering questions about PubSweet

25

How can I debug?

Are you struggling to get your application to run, and no-one on Mattermost can figure it out? It might be time to have a go at debugging yourself. Depending on what you are trying to debug, the client side or the server side, the approaches vary.

Debugging code running on the client

The following instructions are for Google Chrome but the process is similar for other web browsers.

Chrome Developer Tools is accessible from the menu. The most important tabs are Network, which shows HTTP requests and responses, Console, which shows errors and logging, and Sources, which allows setting execution breakpoints.

The React Developer Tools extension is highly recommended as it enables viewing of the component hierarchy and inspecton of the data passed as props to each component.

The Apollo Developer Tools extension provides an alternative view of the GraphQL HTTP requests and also shows the contents of the local cache.

Debugging the code running on the server

Node.js has a built in debugger which can use Chrome Developer Tools as an interface. Enabling the debugger is a multi step process (Mac/Linux only):

Start the application as usual e.g. pubsweet start

In another terminal/tab run pgrep -f startup/start.js | xargs kill -sigusr1

You should see the text "Debugger attached" in the terminal in which the app is running.

Open a new tab in Chrome and visit the URL chrome://inspect

You should see one entry listed under "Remote Target".

Click "inspect".

You can now set breakpoints and step through execution of your server code.

Note that you must repeat the process if you restart the server or if it is restarted automatically due to a code change. This is not ideal, and an alternative, simpler method of debugging is planned. As an alternative, debugging with console.log is a surprisingly good option.

26

Contributing to PubSweet

PubSweet is both an open source software project and an open community, part of the broader Collaborative Knowledge Foundation (Coko) community. We welcome people of all kinds to join the community and contribute with knowledge, skills, expertise. Everyone is welcome in our chat room.

Coko Community Meeting

The core modules and components are developed and maintained by a dedicated team and a community of contributors. Anyone is welcome to contribute ideas, issues, and code. Development is managed in the PubSweet project on the Coko Foundation GitLab instance. We track high-level PubSweet issues and discussion in the project-wide issue tracker.

In order to contribute to PubSweet,you're expected to follow a few sensible guidelines.

Search first, ask questions later

If you want to create a new component or if you've experienced a bug or want to discuss something in the issue trackers, please search before you start developing to find out whether it already exists.

Discuss your contribution before you build

Please let us know about the contribution you plan to make before you start it. Either comment on a relevant existing issue, or open a new issue if you can't find an existing one. This helps us avoid duplicating effort and to ensure contributions are likely to be accepted. You can also ask in the chat room if you are unsure.

For contributions made as discussions and suggestions, you can at any time open an RFC in our issue tracker and PubSweet community members will be happy to jump into a discussion.

Use merge requests

We maintain master of the PubSweet monorepo (a Lerna-managed single repository with multiple packages) as the production branch, and tag it with release names. If you wish to contribute to PubSweet, you need to make a branch and then issue a pull request following this procedure:

Create a user account on Coko GitLab

Clone master with git clone git@gitlab.coko.foundation:pubsweet/pubsweet.git

Create a new branch and work off that. Please name the branch which sensibly identifies the feature you are working on. You can push the branch to Coko Gitlab at anytime.

Use Conventional Commits

An automated process will reject your commit if it doesn't follow the commit guidelines. All notable changes to the work will be automatically documented in a CHANGELOG.md file. See Conventional Commits for commit guidelines.

Merge request workflow

Merge requests should be marked as WIP: (Work In Progress) until they are completely ready to be reviewed. When you think the MR is ready, remove the WIP label and a member of the PubSweet team will review the changes. Once any requested changes or problems have been resolved, the MR will be considered for merging. We encourage feedback and discussion from as many people as possible on Merge Requests.

Before merging, all MRs must fulfill these three simple rules:

It must pass the tests.

It must not reduce the test coverage.

If the MR is a bug fix, it must include a regression test.

27

Testing

PubSweet uses Jest for unit and integration testing of both client and server components.

Client components

Client PubSweet components tend to be composed of one or more React components. Writing meaningful tests for React components can be tricky, and will vary depending which paradigm the component uses (e.g. presentational and container components, higher order components, render props).

We recommend using enzyme to shallow render components and focus on testing logic rather than rendering.

For a simple presentational component, it may be appropriate to have a single test that just ensures it can render without errors. If a container component defines no logic then it can be seen simply as configuration. In this case it may be appropriate to have no unit tests, provided end-to-end test coverage is sufficient.

More commonly, a component will have a number of tests which execute the various logical branches rendering the component with different props and, optionally, simulating events. Here is an example test for a component with some basic conditional logic:

import React from 'react'
import { shallow } from 'enzyme'
import MyComponent from './my-component.js'

const makeWrapper = (props = {}) => shallow(<MyComponent {...props} />)

describe('myComponent', () => {
  it('is not loading initially', () => {
    const wrapper = makeWrapper()
    expect(wrapper.text()).not.toContain('Loading')
  })

  it('displays loading message', () => {
    const wrapper = makeWrapper({ loading: true })
    expect(wrapper.text()).toContain('Loading')
  })
})

Server components

Server PubSweet components will tend to define one or more REST endpoints or GraphQL queries and resolvers. Strict unit testing of these functions requires mocking the database which is a lot of effort and doesn't catch certain errors such as incorrect database queries. Integration testing has been found to be a more productive approach. PubSweet includes test helpers to setup an empty PostgreSQL database for each test suite. Tests can then set up the data as necessary before each test, run the query and then assert against either the response or the final state of the database.

Below is an example of how we might test a server component that uses GraphQL. Note that we test the resolver directly instead of starting the server:

const { createTables } = require('@pubsweet/db-manager')
const { Mutation } = require('./resolvers')
const db = require('./db-test-helpers')

const testData = {
  username: 'testuser',
}

describe('My component', () => {
  beforeEach(async () => {
    await createTables(true)
  })

  it('Saves a user', async () => {
    await Mutation.saveUser(_, testData)
    expect(db.fetchUsers()[0]).toMatchObject(testData)
  })
})

Components that access remote services (such as ORCID) can use node-replay to record and then playback the HTTP responses. This allows easy writing and updating of tests while ensuring that future test runs are not affected by network issues or the remote service being temporarily unavailable.

How to test an app

End-to-end testing is useful for testing complete flows through the app and ensuring that all the components work together. The test runner loads the app in a real browser and allows test scripts to interact with it as a user would.

Since these tests reside in your application, you are free to use any test runner, however we use and recommend TestCafe. Unlike most other end-to-end testing tools, TestCafe is not based on Selenium. This allows it to be both faster and less susceptible to test 'flake' where subtle timing issues cause tests to fail intermittently. Furthermore, using the same tools as other PubSweet community members leads to increased opportunities for code and knowledge sharing.

Below is an example TestCafe test to check that we can fill out part of a form (a dropdown and a text field):

test('Happy path', async t => {
  await t
    .expect(Selector('[name=title]').value)
    .eql('This is a title')
    .click('[role=listbox] button')
    .click(Selector('[role=option]').nth(0))
    .click(Selector('[id=myform-select'))
    .pressKey('enter')
    .pressKey('down')
    .pressKey('enter')
    .click(Selector('[name="myform.somefield"]').parent())
    .typeText(
      '[name="myform.somefield"]',
      'Hello!',
    )
    .click('[data-test-id=submit]')
})

10

The Future

...“Poscere fata

tempus” ait; “deus, ecce, deus!”

“Ask now thy doom!—the god! the god is nigh!”

Aeneid VI, 45-6

28

Where are we going now?

Where are we going now with PubSweet?

The community will decide. PubSweet doesn't determine its own direction: it is a community effort. As members of the community identify needs, we move those needs to the front of the queue and try to meet them together. At the risk of sounding glib, the answer to “Where is PubSweet headed?” is “We don't know. You tell us.”

However we can speak about some issues the comunity is currently discusssing. Possibly the biggest issue for now and the foreseeable future is reuse. Reuse is a core principle of the PubSweet approach and the Coko community. Reuse, however, is hard. Identifying the best unit of reuse, is hard, and so is identifying the best practices. Finding the right approach requires quite a bit of upfront communication and analysis that can at times feel like slow going. When the community comes to a consensus, however, implementation is rapid.

We have made a lot of progress on reuse issues already, notably with the sharing of UI components — atoms and molecules. This achievement is a significant, as sharing of design resources is seldom attempted in open source projects. With no established open source norms to follow, we had to pursue our own process of discovery and resolution. In other words, we had to make it up as we went along. In exchange for a modicum of delay, we pioneered a new way of doing things and gained valuable experience that we can feed back to the wider open source community.

It is obvious now that we need to address issues about reuse on the data model level as well. How prescriptive do we have to be? How open ended can we be? Prescriptiveness helps reuse, but stifles individual approaches to a problem, or doesn't anticipate questions we had yet to consider. Open-endedness fuels creative individual solutions and new use cases, but hampers reuse. We will address these questions in forthcoming PubSweet community meets and find effective ways to walk the line.

So it is with community. One step at a time. It often feels like the same pattern: identify a problem, talk about it, develop a shared lexicon, learn to talk increasingly accurately about the problem, refine the problem, propose targeted solutions that get better with discussion, drive towards consensus.

As we grow, maintaining effective communication channels will become a challenge. How do open up the close communication between the three founding partners – Hindawi, eLife and Coko – to new organizations? We are very careful as we bring on board new organisations to help them get started developing their platform rapidly and to include them in these communication channels, and help orient them with the community. How will this work when we have 5 more organisations join the community, or 10, or 50? We don't know right now, but we'll figure it out.

Were you looking for a chapter about the future of the technology?

PubSweet is a product of the community. The community is the future.

Glossary

PubSweet terms

Atom — Smallest bit of reusable UI from the PubSweet-UI component

Authsome — Attribute-based authorization module that's tightly integrated into PubSweet

CSS Override — a flexible way of overriding the style of a component without editing it

Component — A composable part (a client or server part) that makes up a PubSweet application

Editoria — the book production platform designed by and for the University of California Press and built with PubSweet.

Editoria Typescript — "Editoria-flavored" HTML produced by XSweet for importing into Editoria

Gitlab — the source code managament software on which PubSweet is hosted

INK — the Coko project designed to process jobs of any kind that don't fit into the PubSweet framework e.g. file conversion.

Jitsi — the video conferencing software with which Coko meetings are undertaken

Manuscript — in book and journal workflows, this is the publishable object you are working on before it gets published. After publication, the same objects are refered to respectively as books and articles.

Mattermost — the chat software which serves as a hub for PubSweet community discussions

Molecule — UI component made of at least two atoms

Monorepo — a single source code repository that defines multiple, related packages

PubSweet — the thing this book is about

PubSweet CLI — A command-line interface to PubSweet and PubSweet apps

PubSweet Starter — A minimal starting point for a PubSweet application

PubSweet Styleguide — A live styleguide of PubSweet's React components

PubSweet UI — A package containing many UI components (buttons, drop-downs etc.) that are reused across PubSweet applications

Team — in Authsome, one or more users with the same access rights

Team-based access control — A way to use teams to determine permissions that a user has for an object

Theme — A collection of set theme variables

Theme variable — A variable dealing with a certain styling aspect, such as the color of the background

Typescript — HTML produced by XSweet. Not to be confused with TypeScript which is a programming language.

Workspace — the 'block' level spaces used in the PubSweet universe to support your desired workflow eg. Dashboard, Article Page etc

XSweet — the tool produced by Coko for converting docx to HTML. Free your docs and your workflow will follow!

xPub — the general category name for all the journal systems made using PubSweet. This group didn't really need a name but xPub sounded so cool we thought we might as well use it.

Colophon

This book written with Editoria and facilitated by Book Sprints. The book was rendered to print ready PDF by Vivliostyle. The body text of the book is set in Spectral, designed by Production Type for Google and available on Google fonts while the headings are composed in Fira Sans and all the coding is set in Fira Code, both designed by Erik Spiekermann, and available from Mozilla. This book is licensed CC-BY-SA.

The book was printed by Pixart printing during the month of June 2018.