Kontent.AI enables the creation of complex editorial workflows, but visualising them can be challenging. Transforming workflows into flow diagrams allows easy comparison with design plans. A LINQPad script can generate a dynamic Mermaid diagram, providing clear visualisation that can be integrated into documentation.
Kontent.AI provides the ability to build workflows that support the editorial process for your content.
While the editor for workflows lets us build something fairly complex, it can be difficult to - at a glance - understand how content can move through the workflow due to the linear layout of the workflow.
In a recent project, I wanted to be able to quickly determine if my workflows match what we had designed in our Miro board, but also be able to share documentation about the implemented workflow. As kontet.AIs doncumeantion on workflow says, a drawing prevents gray hairs. The simplest way I could think of to do this was to make it in to a flow diagram.
I didn't want to have to keep manually updating the flow diagram each time there were some changes made to the workflow. My solution was to create a simple script in LINQPad that would create a Mermaid diagram on demand.
Building the Solution
First, create a dictionary to store the steps in. This is just improve the readability of the code when we do lookups.
var workflows = await managementClient.ListWorkflowsAsync();
var workflow = workflows.First();
// Precompute steps in a dictionary for faster lookup
var stepDictionary = workflow.Steps.ToDictionary(s => s.Id, s => s.Codename);
Next, we create a dictionary of the colours in Kontent.AI. Without it, the output is quite bland. As the colour names in Kontent.AI are not just standard CSS colours, and because we also want to change the text colour we use this lookup.
var colorDictionary = new Dictionary<string, (string Fill, string Color)> {
{ "gray", ("#6F6F6F", "#FFFFFF") },
{ "red", ("#DB0000", "#FFFFFF")},
{ "rose", ("#D9006F", "#FFFFFF")},
{ "lightpurple", ("#DAB8FF", "#000000")},
{ "darkpurple", ("#AD00FB", "#FFFFFF")},
{ "darkblue", ("#006CDC", "#FFFFFF")},
{ "lightblue", ("#B1C5FF", "#000000")},
{ "skyblue", ("#72D1FF", "#000000")},
{ "mintgreen", ("#00E293", "#000000")},
{ "persiangreen", ("#007F51", "#FFFFFF")},
{ "darkgreen", ("#4B7C00", "#FFFFFF")},
{ "lightgreen", ("#89DC00", "#000000")},
{ "yellow", ("#FFB865", "#000000")},
{ "pink", ("#FE9A9A", "#000000")},
{ "orange", ("#C54300", "#FFFFFF")},
{ "brown", ("#996400", "#FFFFFF")},
};
With that in place - using LINQPad's Dump
command - we then initialise the Mermaid diagram with flowchart TD
to create a top-down flow chart, and then also write out the styles from our colour dictionary. In addition, because Published and Archived don't live in the Steps
collection in the workflow, we'll add these manually to the output and create their won relationship. We've also got some methods in here to generate some convenient style names.
"flowchart TD".Dump();
foreach (var key in colorDictionary.Keys)
{
$"classDef {key} fill:{colorDictionary[key].Fill},color:{colorDictionary[key].Color},stroke:none;".Dump();
}
"published(Published):::persiangreen;".Dump();
"archived(Archived):::gray;".Dump();
"published --> archived".Dump();
The final thing to do is to loop through the steps in the workflow. For each step we create a node with text and a style. For the node name, we just use the workflow step's codename
.
As the order of nodes in Mermaid does not matter, we also go ahead and list out all of the Transition To steps, building links using the codename
from the dictionary.
foreach (var step in workflow.Steps)
{
$"{step.Codename}(\"{step.Name}\"):::{step.Color.ToString().ToLowerInvariant()};".Dump();
foreach (var transition in step.TransitionsTo)
{
string targetCodename = transition.Step.Id == workflow.PublishedStep.Id ? "published"
: transition.Step.Id == workflow.ArchivedStep.Id ? "archived"
: stepDictionary.TryGetValue(transition.Step.Id??Guid.Empty, out var nextStepCodename) ? nextStepCodename
: "unknown"; // Fallback if step ID not found
$"{step.Codename} --> {targetCodename}".Dump();
}
}
The output from this I then copy and paste into a viewer for Mermaid diagrams. For example https://mermaid.js.org/ or Notion.
The output of this particular script is a simple flow diagram in Mermaid as follows:
flowchart TD classDef style_gray fill:#6F6F6F,color:#FFFFFF,stroke:none; classDef style_red fill:#DB0000,color:#FFFFFF,stroke:none; classDef style_rose fill:#D9006F,color:#FFFFFF,stroke:none; classDef style_lightpurple fill:#DAB8FF,color:#000000,stroke:none; classDef style_darkpurple fill:#AD00FB,color:#FFFFFF,stroke:none; classDef style_darkblue fill:#006CDC,color:#FFFFFF,stroke:none; classDef style_lightblue fill:#B1C5FF,color:#000000,stroke:none; classDef style_skyblue fill:#72D1FF,color:#000000,stroke:none; classDef style_mintgreen fill:#00E293,color:#000000,stroke:none; classDef style_persiangreen fill:#007F51,color:#FFFFFF,stroke:none; classDef style_darkgreen fill:#4B7C00,color:#FFFFFF,stroke:none; classDef style_lightgreen fill:#89DC00,color:#000000,stroke:none; classDef style_yellow fill:#FFB865,color:#000000,stroke:none; classDef style_pink fill:#FE9A9A,color:#000000,stroke:none; classDef style_orange fill:#C54300,color:#FFFFFF,stroke:none; classDef style_brown fill:#996400,color:#FFFFFF,stroke:none; published(Published):::style_persiangreen; archived(Archived):::style_gray; published --> archived classDef c_draft fill:red draft("Draft"):::style_red; draft --> translation_automation draft --> proof_reading draft --> proof_reading__admin_ classDef c_translation_automation fill:gray translation_automation("Translation Automation"):::style_gray; translation_automation --> ready_to_publish translation_automation --> published classDef c_proof_reading fill:darkpurple proof_reading("Proof-reading"):::style_darkpurple; proof_reading --> ready_to_translate classDef c_proof_reading__admin_ fill:yellow proof_reading__admin_("Proof Reading (Admin)"):::style_yellow; proof_reading__admin_ --> ready_to_publish classDef c_ready_to_translate fill:darkblue ready_to_translate("Ready to Translate"):::style_darkblue; ready_to_translate --> translation_submitted classDef c_translation_submitted fill:gray translation_submitted("Translation Submitted"):::style_gray; translation_submitted --> translation_cancelled translation_submitted --> translation_received classDef c_translation_cancelled fill:gray translation_cancelled("Translation Cancelled"):::style_gray; translation_cancelled --> archived classDef c_translation_received fill:gray translation_received("Translation Received"):::style_gray; translation_received --> translation_in_progress translation_received --> translation_cancelled classDef c_translation_in_progress fill:gray translation_in_progress("Translation In Progress"):::style_gray; translation_in_progress --> translation_complete translation_in_progress --> translation_cancelled classDef c_translation_complete fill:gray translation_complete("Translation Complete"):::style_gray; translation_complete --> ready_to_publish translation_complete --> translation_cancelled classDef c_ready_to_publish fill:mintgreen ready_to_publish("Ready to publish"):::style_mintgreen; ready_to_publish --> published
This is 10-15 minutes of effort, but it makes sharing a workflow for documentation much simpler, and makes the workflow easier to digest.