diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 830c6784a5f..89e9d3bc8b6 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -463,7 +463,7 @@ db:backup_and_restore: script: - . scripts/prepare_build.sh - bundle exec rake db:drop db:create db:structure:load db:seed_fu - - mkdir -p tmp/tests/public/uploads tmp/tests/{artifacts,pages,lfs-objects,terraform_state,registry} + - mkdir -p tmp/tests/public/uploads tmp/tests/{artifacts,pages,lfs-objects,terraform_state,registry,packages} - bundle exec rake gitlab:backup:create - date - bundle exec rake gitlab:backup:restore diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js index 4698fcd4d42..c4e09efe263 100644 --- a/app/assets/javascripts/behaviors/markdown/render_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js @@ -4,6 +4,7 @@ import initUserPopovers from '../../user_popovers'; import highlightCurrentUser from './highlight_current_user'; import renderMath from './render_math'; import renderMermaid from './render_mermaid'; +import renderSandboxedMermaid from './render_sandboxed_mermaid'; import renderMetrics from './render_metrics'; // Render GitLab flavoured Markdown @@ -13,7 +14,11 @@ import renderMetrics from './render_metrics'; $.fn.renderGFM = function renderGFM() { syntaxHighlight(this.find('.js-syntax-highlight').get()); renderMath(this.find('.js-render-math')); - renderMermaid(this.find('.js-render-mermaid')); + if (gon.features?.sandboxedMermaid) { + renderSandboxedMermaid(this.find('.js-render-mermaid')); + } else { + renderMermaid(this.find('.js-render-mermaid')); + } highlightCurrentUser(this.find('.gfm-project_member').get()); initUserPopovers(this.find('.js-user-link').get()); diff --git a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js new file mode 100644 index 00000000000..92cdd1c600f --- /dev/null +++ b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js @@ -0,0 +1,233 @@ +import $ from 'jquery'; +import { once, countBy } from 'lodash'; +import { __ } from '~/locale'; +import { + getBaseURL, + relativePathToAbsolute, + setUrlParams, + joinPaths, +} from '~/lib/utils/url_utility'; +import { darkModeEnabled } from '~/lib/utils/color_utils'; +import { setAttributes } from '~/lib/utils/dom_utils'; + +// Renders diagrams and flowcharts from text using Mermaid in any element with the +// `js-render-mermaid` class. +// +// Example markup: +// +//
+// graph TD; +// A-- > B; +// A-- > C; +// B-- > D; +// C-- > D; +//+// + +const SANDBOX_FRAME_PATH = '/-/sandbox/mermaid'; +// This is an arbitrary number; Can be iterated upon when suitable. +const MAX_CHAR_LIMIT = 2000; +// Max # of mermaid blocks that can be rendered in a page. +const MAX_MERMAID_BLOCK_LIMIT = 50; +// Max # of `&` allowed in Chaining of links syntax +const MAX_CHAINING_OF_LINKS_LIMIT = 30; +// Keep a map of mermaid blocks we've already rendered. +const elsProcessingMap = new WeakMap(); +let renderedMermaidBlocks = 0; + +// Pages without any restrictions on mermaid rendering +const PAGES_WITHOUT_RESTRICTIONS = [ + // Group wiki + 'groups:wikis:show', + 'groups:wikis:edit', + 'groups:wikis:create', + + // Project wiki + 'projects:wikis:show', + 'projects:wikis:edit', + 'projects:wikis:create', + + // Project files + 'projects:show', + 'projects:blob:show', +]; + +function shouldLazyLoadMermaidBlock(source) { + /** + * If source contains `&`, which means that it might + * contain Chaining of links a new syntax in Mermaid. + */ + if (countBy(source)['&'] > MAX_CHAINING_OF_LINKS_LIMIT) { + return true; + } + + return false; +} + +function fixElementSource(el) { + // Mermaid doesn't like `