Project

General

Profile

Actions

Feature #44425

closed

Render Mermaid code blocks as diagrams

Added by Mizuki ISHIKAWA about 1 month ago. Updated 10 days ago.

Status:
Closed
Priority:
Normal
Assignee:
Category:
Text formatting
Target version:
Resolution:
Fixed

Description

This patch adds support for rendering code blocks written in Mermaid syntax as diagrams using Mermaid.js.

For example, with CommonMark:

```mermaid
flowchart LR
    A[Start] --> B[Process]
    B --> C[End]
```

Or with Textile:

<pre><code class="mermaid">
flowchart LR
    A[Start] --> B[Process]
    B --> C[End]
</code></pre>

These are rendered as diagrams instead of code blocks:

This allows flowcharts, sequence diagrams, class diagrams, and other diagrams to be described as text in wikis, issue descriptions, and other formatted text fields.

Mermaid.js is widely used, and supporting it in Redmine would make it possible to preserve information in a more visual form using diagrams.

Implementation

  • Adds mermaid.min.js 11.17.2
  • Detects mermaid code blocks and renders them as diagrams
  • Loads Mermaid.js lazily only when a Mermaid code block is present, since the library is several MB in size
  • Handles invalid Mermaid syntax without affecting the rest of the page
  • Uses securityLevel: 'strict' to prevent scripts and other unsafe content from being executed from Mermaid diagrams
  • Supports both Textile and CommonMark
  • Adds license information for Mermaid.js and its bundled dependencies
  • Adds tests for the above behavior

Patch

The patch could not be attached directly to this issue because it exceeds the attachment size limit, so it is available from the following GitHub pull request instead: https://patch-diff.githubusercontent.com/raw/ishikawa999/redmine/pull/19.patch
(Source: https://github.com/ishikawa999/redmine/pull/19 )

To download and apply the patch:

curl -o mermaid.patch https://patch-diff.githubusercontent.com/raw/ishikawa999/redmine/pull/19.patch
git am mermaid.patch


Files


Related issues

Related to Redmine - Feature #44605: Add a link to download a rendered Mermaid diagram as a PNG imageClosedGo MAEDAActions
Related to Redmine - Defect #44584: Mermaid error diagram may be drawn into another valid diagram on the same pageClosedActions
Related to Redmine - Patch #44621: Hide Mermaid code blocks until the diagrams are renderedClosedGo MAEDAActions
Related to Redmine - Feature #44636: Add Mermaid diagrams to the wiki syntax help pagesNewActions
Actions #1

Updated by Go MAEDA about 1 month ago

Mermaid has become widely adopted for creating diagrams in Markdown-based tools, and is supported by major platforms such as GitHub, GitLab, Azure DevOps, and Obsidian. I think Mermaid support would be a valuable addition to Redmine.

As a side benefit, having Mermaid.js available in Redmine could also support future UI improvements beyond Mermaid markup in formatted text. For example, it could be used for workflow visualization (#559) or issue dependency graphs (#2448).

Actions #2

Updated by Marius BĂLTEANU about 1 month ago

  • Target version set to 7.1.0
Actions #3

Updated by Florian Walchshofer about 1 month ago

Thanks for the patch. Overall, I like the implementation. The amount of core changes is relatively small, Mermaid is loaded only when needed, and features such as lazy loading and one-time initialization are implemented cleanly.

I also agree with Go MAEDA that having Mermaid support available directly in Redmine could enable additional use cases beyond wiki content.

One concern is that Mermaid is a rather actively developed project with frequent releases. Bundling it in Redmine core means tracking Mermaid updates, security fixes and related third-party dependencies over time. An alternative approach could be an optional Mermaid CLI/CGI integration with server-side SVG/PNG generation, allowing each Redmine installation to decide whether to enable it and keeping Mermaid outside the core dependency chain.

However, this would also make the integration and deployment more complex. Therefore, I still support the proposed JavaScript integration, as it is simple, widely adopted and probably the most practical solution for most Redmine installations.

Regarding the current patch itself, I only have one question:

mermaid.run({ nodes: [container], suppressErrors: true })

From my testing, with suppressErrors: false Mermaid parser and validation errors are propagated to the Promise rejection handler, for example invalid diagram types or syntax errors. This causes the .catch() block to run, while the original code block remains visible.

With suppressErrors: true, these errors no longer seem to reach the .catch() block, so no error is logged and the Mermaid-generated error rendering is shown instead.

Would it make sense to use suppressErrors: false here, hide the Mermaid error output, and log or display the actual parser error object?
The error details often include the exact line and syntax problem, which can be very helpful for users debugging Mermaid diagrams.

Actions #4

Updated by Mizuki ISHIKAWA about 1 month ago

Florian Walchshofer wrote in #note-3:

One concern is that Mermaid is a rather actively developed project with frequent releases. Bundling it in Redmine core means tracking Mermaid updates, security fixes and related third-party dependencies over time. An alternative approach could be an optional Mermaid CLI/CGI integration with server-side SVG/PNG generation, allowing each Redmine installation to decide whether to enable it and keeping Mermaid outside the core dependency chain.

Thank you for the feedback.

I have not tested this approach yet, but I think it should be possible to enable Mermaid conversion only when Mermaid CLI is available, similar to other features that depend on external commands such as Ghostscript or ImageMagick.
Personally, I prefer the Mermaid.js approach because the implementation is simpler. However, if the Mermaid CLI approach is considered preferable given the maintenance burden of keeping Mermaid.js and its dependencies up to date, I would respect that decision.

With suppressErrors: true, these errors no longer seem to reach the .catch() block, so no error is logged and the Mermaid-generated error rendering is shown instead.

You're right. I've updated the pull request. https://github.com/ishikawa999/redmine/pull/19/changes/71ca601f747d8cccb67816ddb9ec87f1ebebb5fb

Actions #5

Updated by Marius BĂLTEANU about 1 month ago

Florian Walchshofer wrote in #note-3:

[..]
One concern is that Mermaid is a rather actively developed project with frequent releases. Bundling it in Redmine core means tracking Mermaid updates, security fixes and related third-party dependencies over time. An alternative approach could be an optional Mermaid CLI/CGI integration with server-side SVG/PNG generation, allowing each Redmine installation to decide whether to enable it and keeping Mermaid outside the core dependency chain.

That also means the integration can break after a server-side Mermaid CLI/CGI update. Additionally, server-side rendering requires Puppeteer, which makes installation and maintenance much more complex.

Overall, I am in favor of bundling the JS library inside Redmine. We can cut new releases for important upstream security fixes, just as we already do for other dependencies.

Actions #6

Updated by Florian Walchshofer about 1 month ago

Thanks for the clarification.

I tried an alternative approach based on suppressErrors: false

With the attached patch, Mermaid rendering errors are handled in the Promise rejection handler. Instead of rendering Mermaid's built-in error diagram, a standard Redmine flash message is shown and the original source block remains visible for inspection and copying.

This also brings the behaviour closer to GitHub's Mermaid integration, which displays an error message while keeping the original diagram source visible.

The corresponding system tests were updated to cover both successful rendering and invalid Mermaid diagrams.

This patch is intended as an additional change on top of the current pull request.
Without a rejection handler, using suppressErrors: false results in unhandled Promise rejections whenever Mermaid parsing or rendering fails.

Actions #7

Updated by Mizuki ISHIKAWA about 1 month ago

Florian Walchshofer wrote in #note-6:

I tried an alternative approach based on suppressErrors: false

I applied the patch and tested it locally.
I think the error messages are much easier to read now.

Since the error messages are generated by Mermaid.js, they cannot be localized, but I think this is acceptable.

Actions #8

Updated by Helder Costa about 1 month ago

It will be greate that PDF export also render mermaid blocks correctly!
Thanks

Actions #9

Updated by Go MAEDA about 1 month ago

Helder Costa wrote in #note-8:

It will be greate that PDF export also render mermaid blocks correctly!

Thanks for the suggestion. I agree that rendering Mermaid diagrams in PDF exports would be a great addition. However, supporting PDF export would significantly increase the scope of this feature.

In my opinion, we should first focus on making Mermaid available on Redmine's web pages, and consider PDF export as a future improvement after the web support is complete.

Actions #10

Updated by Mizuki ISHIKAWA about 1 month ago

I updated the implementation to support Mermaid.js 12.0.0 and incorporated 0003-parse-error-to-flash-error-message.patch by Florian Walchshofer, along with some other changes, and pushed the updates to https://github.com/ishikawa999/redmine/pull/19 .

Actions #11

Updated by Go MAEDA about 1 month ago

I have tested the latest patch, and I have not found any issues that would block merging it.

Since there is still enough time before Redmine 7.1.0, I am planning to commit this to trunk in the next few days. This will allow more people to try Mermaid support in real-world use and give us time to address any issues that may be found before the release.

Please let me know if you have any concerns.

Actions #12

Updated by Marius BĂLTEANU about 1 month ago

Go MAEDA wrote in #note-11:

I have tested the latest patch, and I have not found any issues that would block merging it.

Since there is still enough time before Redmine 7.1.0, I am planning to commit this to trunk in the next few days. This will allow more people to try Mermaid support in real-world use and give us time to address any issues that may be found before the release.

Please let me know if you have any concerns.

I'm in favour of adding this to the core as soon as possible.

Actions #13

Updated by Go MAEDA 21 days ago

I have updated the patch so that Redmine no longer bundles mermaid.js. The two attached patches replace the previous ones.

Bundling mermaid.js raises licensing problems. mermaid 12 embeds elkjs into mermaid.min.js, and elkjs is licensed under the EPL-2.0, which is not compatible with the GPLv2, so we cannot ship this file in the Redmine distribution. Even before version 12, mermaid.min.js embedded several third-party libraries such as lodash-es, DOMPurify and cytoscape.js, and Redmine had to reproduce their license notices under doc/licenses. Since the bundled file does not even carry the elkjs license notice, keeping such a list accurate is error-prone.

mermaid.js therefore has to be installed by the administrator rather than shipped with Redmine, in the same way that ImageMagick and Pandoc are optional external components. The usual tool for this would be importmap-rails, but it cannot be used for mermaid.js: mermaid ships no build that an import map can load, since the ESM build consists of about 100 chunk files and the single-file build only works as a classic script, loaded with a plain <script> tag.

For this purpose, the updated patch adds a rake task, bin/rails redmine:mermaid:install. It downloads the mermaid package of the supported version (currently 12.0.0) from registry.npmjs.org, verifies its SHA-256 checksum and places dist/mermaid.min.js into vendor/javascript, which is already one of Propshaft's load paths. FILE=... installs from a local copy on servers without internet access, and redmine:mermaid:uninstall removes the file. When mermaid.js is not installed, mermaid code blocks are displayed as ordinary code blocks.

Only the version supported by the Redmine release is enabled, so the task has to be run again after upgrading Redmine. The supported version is defined by the VERSION and SHA256 constants in lib/redmine/mermaid.rb, and updating mermaid.js in a future release only requires changing these two lines.

The rendering part is otherwise unchanged from the previous patch, including the error message from Florian's patch. In addition, whether mermaid.js is installed is shown in Administration -> Information, doc/INSTALL and doc/UPGRADING describe the optional step, and the CI workflow installs it before running the system tests. Two small fixes are also included: an inline <code class="mermaid"> in Textile is no longer rendered as a diagram, and the Stimulus controller cleans up after itself when its element is removed from the page.

Actions #14

Updated by Mizuki ISHIKAWA 21 days ago

Go MAEDA wrote in #note-13:

I have updated the patch so that Redmine no longer bundles mermaid.js. The two attached patches replace the previous ones.

Thank you for pointing this out and for the updated patch.
As you mentioned, now that mermaid 12 embeds elkjs, which is licensed under the EPL-2.0, into mermaid.min.js, it cannot be distributed with Redmine, which is licensed under the GPLv2. mermaid.min.js bundled more libraries than I had expected, and I failed to notice this when updating it.

I think having administrators install mermaid.js instead of bundling it is the best approach to deliver this useful feature to users without violating the licenses.

Actions #15

Updated by Go MAEDA 19 days ago

I have updated the first patch. It changes when the SHA-256 checksum is verified.

Before: The checksum was verified when Mermaid.js was installed and again at runtime. If the installed file did not match, diagram rendering was disabled.

Now: The checksum is verified only when the redmine:mermaid:install task installs the file. At runtime, Redmine only checks that the file exists.

The runtime check had the following problems:

  • It added no real protection. Redmine does not verify any other JavaScript file at runtime, and anyone who can replace mermaid.min.js on the server can also replace those files or the application code.
  • Administrators could not replace Mermaid.js with another version, for example to get a security fix or a new diagram type, without waiting for a new Redmine release.
  • Diagrams stopped rendering after a Redmine upgrade that changed the required Mermaid.js version, until the redmine:mermaid:install task was run again.

To keep administrators informed, Redmine now reads the version string embedded in mermaid.min.js. A warning is written to the log when that version differs from the one installed by redmine:mermaid:install. The redmine:mermaid:status task also shows the installed version.

doc/UPGRADING still asks administrators to run redmine:mermaid:install after each upgrade, so installations that follow the documented steps keep using the version installed by the task.

Actions #16

Updated by Go MAEDA 17 days ago

  • Status changed from New to Resolved
  • Assignee set to Go MAEDA
  • Resolution set to Fixed

Committed the patches in the following revisions. Thank you for your contribution.

  • r25202: Add a rake task that installs Mermaid.js
  • r25203: Render code blocks highlighted as mermaid into diagrams
  • r25204: Show Mermaid.js rendering errors in a flash message and keep the code block
  • r25205: Document the installation of Mermaid.js in doc/INSTALL and doc/UPGRADING
  • r25206: Run redmine:mermaid:install in the test environment in the CI workflow

Redmine can now render Mermaid code blocks as diagrams once Mermaid.js is installed.

Mermaid.js is not bundled with Redmine because its standard build includes dependencies, such as ELK, whose licenses are not compatible with GPLv2. To install it, run the following rake task and restart Redmine:

bin/rails redmine:mermaid:install RAILS_ENV=production

You can check whether Mermaid.js is installed by running bin/rails redmine:mermaid:status, or on the "Administration > Information" page. If it is not installed, Mermaid code blocks are displayed as ordinary code blocks, as before.

To write a diagram, use the syntax for code highlighting and specify mermaid as the language. For example, in CommonMark Markdown:

```mermaid
flowchart LR
  A --> B
```

See doc/INSTALL for details.

Actions #17

Updated by Go MAEDA 10 days ago

  • Status changed from Resolved to Closed
Actions #18

Updated by Go MAEDA 7 days ago

  • Related to Feature #44605: Add a link to download a rendered Mermaid diagram as a PNG image added
Actions #19

Updated by Go MAEDA 7 days ago

  • Related to Defect #44584: Mermaid error diagram may be drawn into another valid diagram on the same page added
Actions #20

Updated by Go MAEDA 5 days ago

  • Related to Patch #44621: Hide Mermaid code blocks until the diagrams are rendered added
Actions #21

Updated by Go MAEDA 3 days ago

  • Related to Feature #44636: Add Mermaid diagrams to the wiki syntax help pages added
Actions

Also available in: Atom PDF