From db285b625d4c81ab5cf8e95014d96404b1b2a55f Mon Sep 17 00:00:00 2001 From: Guillaume Grossetie <g.grossetie@gmail.com> Date: Wed, 17 Feb 2021 01:27:06 +0000 Subject: [PATCH] Upgrade to Asciidoctor Kroki 0.3.0 - Add excalidraw as a supported diagram https://github.com/Mogztter/asciidoctor-kroki/releases/tag/ruby-v0.3.0 --- Gemfile | 2 +- Gemfile.lock | 4 +- .../admin/application_settings_controller.rb | 1 + app/helpers/application_settings_helper.rb | 11 +++ app/models/application_setting.rb | 37 ++++++++++ .../application_setting_implementation.rb | 1 + .../application_setting_kroki_formats.json | 10 +++ .../application_settings/_kroki.html.haml | 8 +++ .../241744-additional-formats-kroki.yml | 5 ++ ...i_formats_to_application_settings_table.rb | 11 +++ db/schema_migrations/20201120092000 | 1 + db/structure.sql | 1 + lib/gitlab/kroki.rb | 18 +++-- locale/gitlab.pot | 3 + .../application_settings_controller_spec.rb | 7 ++ .../application_settings_helper_spec.rb | 29 ++++++++ spec/lib/gitlab/asciidoc_spec.rb | 67 +++++++++++++++++++ spec/models/application_setting_spec.rb | 44 ++++++++++++ 18 files changed, 251 insertions(+), 9 deletions(-) create mode 100644 app/validators/json_schemas/application_setting_kroki_formats.json create mode 100644 changelogs/unreleased/241744-additional-formats-kroki.yml create mode 100644 db/migrate/20201120092000_add_kroki_formats_to_application_settings_table.rb create mode 100644 db/schema_migrations/20201120092000 diff --git a/Gemfile b/Gemfile index 66ca052dada0d..58eea95642746 100644 --- a/Gemfile +++ b/Gemfile @@ -156,7 +156,7 @@ gem 'wikicloth', '0.8.1' gem 'asciidoctor', '~> 2.0.10' gem 'asciidoctor-include-ext', '~> 0.3.1', require: false gem 'asciidoctor-plantuml', '~> 0.0.12' -gem 'asciidoctor-kroki', '~> 0.2.2', require: false +gem 'asciidoctor-kroki', '~> 0.3.0', require: false gem 'rouge', '~> 3.26.0' gem 'truncato', '~> 0.7.11' gem 'bootstrap_form', '~> 4.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index 3962fe2707330..bea01cf000d55 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -84,7 +84,7 @@ GEM asciidoctor (2.0.12) asciidoctor-include-ext (0.3.1) asciidoctor (>= 1.5.6, < 3.0.0) - asciidoctor-kroki (0.2.2) + asciidoctor-kroki (0.3.0) asciidoctor (~> 2.0) asciidoctor-plantuml (0.0.12) asciidoctor (>= 1.5.6, < 3.0.0) @@ -1291,7 +1291,7 @@ DEPENDENCIES asana (~> 0.10.3) asciidoctor (~> 2.0.10) asciidoctor-include-ext (~> 0.3.1) - asciidoctor-kroki (~> 0.2.2) + asciidoctor-kroki (~> 0.3.0) asciidoctor-plantuml (~> 0.0.12) atlassian-jwt (~> 0.2.0) attr_encrypted (~> 3.1.0) diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index eb3de936fadf9..7f7d38a09c580 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -238,6 +238,7 @@ def visible_application_setting_attributes *::ApplicationSettingsHelper.visible_attributes, *::ApplicationSettingsHelper.external_authorization_service_attributes, *ApplicationSetting.repository_storages_weighted_attributes, + *ApplicationSetting.kroki_formats_attributes.keys.map { |key| "kroki_formats_#{key}".to_sym }, :lets_encrypt_notification_email, :lets_encrypt_terms_of_service_accepted, :domain_denylist_file, diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index b3b90c79076a6..30ae535b06fd9 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -26,6 +26,16 @@ def enabled_protocol end end + def kroki_available_formats + ApplicationSetting.kroki_formats_attributes.map do |key, value| + { + name: "kroki_formats_#{key}", + label: value[:label], + value: @application_setting.kroki_formats[key] || false + } + end + end + def storage_weights ApplicationSetting.repository_storages_weighted_attributes.map do |attribute| storage = attribute.to_s.delete_prefix('repository_storages_weighted_') @@ -259,6 +269,7 @@ def visible_attributes :personal_access_token_prefix, :kroki_enabled, :kroki_url, + :kroki_formats, :plantuml_enabled, :plantuml_url, :polling_interval_multiplier, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 6d375a19ffb12..33c058dab96b4 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -29,6 +29,21 @@ def self.repository_storages_weighted_attributes @repository_storages_weighted_atributes ||= Gitlab.config.repositories.storages.keys.map { |k| "repository_storages_weighted_#{k}".to_sym }.freeze end + def self.kroki_formats_attributes + { + blockdiag: { + label: 'BlockDiag (includes BlockDiag, SeqDiag, ActDiag, NwDiag, PacketDiag and RackDiag)' + }, + bpmn: { + label: 'BPMN' + }, + excalidraw: { + label: 'Excalidraw' + } + } + end + + store_accessor :kroki_formats, *ApplicationSetting.kroki_formats_attributes.keys, prefix: true store_accessor :repository_storages_weighted, *Gitlab.config.repositories.storages.keys, prefix: true # Include here so it can override methods from @@ -54,6 +69,7 @@ def self.repository_storages_weighted_attributes default_value_for :id, 1 default_value_for :repository_storages_weighted, {} + default_value_for :kroki_formats, {} chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds @@ -135,6 +151,8 @@ def self.repository_storages_weighted_attributes validate :validate_kroki_url, if: :kroki_enabled + validates :kroki_formats, json_schema: { filename: 'application_setting_kroki_formats' } + validates :plantuml_url, presence: true, if: :plantuml_enabled @@ -570,6 +588,25 @@ def recaptcha_or_login_protection_enabled end end + kroki_formats_attributes.keys.each do |key| + define_method :"kroki_formats_#{key}=" do |value| + super(::Gitlab::Utils.to_boolean(value)) + end + end + + def kroki_format_supported?(diagram_type) + case diagram_type + when 'excalidraw' + return kroki_formats_excalidraw + when 'bpmn' + return kroki_formats_bpmn + end + + return kroki_formats_blockdiag if ::Gitlab::Kroki::BLOCKDIAG_FORMATS.include?(diagram_type) + + ::AsciidoctorExtensions::Kroki::SUPPORTED_DIAGRAM_NAMES.include?(diagram_type) + end + private def parsed_grafana_url diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index e5284d15a4940..2911ae6b1c8f3 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -176,6 +176,7 @@ def defaults container_registry_expiration_policies_worker_capacity: 0, kroki_enabled: false, kroki_url: nil, + kroki_formats: { blockdiag: false, bpmn: false, excalidraw: false }, rate_limiting_response_text: nil } end diff --git a/app/validators/json_schemas/application_setting_kroki_formats.json b/app/validators/json_schemas/application_setting_kroki_formats.json new file mode 100644 index 0000000000000..460dc74069fa3 --- /dev/null +++ b/app/validators/json_schemas/application_setting_kroki_formats.json @@ -0,0 +1,10 @@ +{ + "description": "Kroki formats", + "type": "object", + "properties": { + "bpmn": { "type": "boolean" }, + "excalidraw": { "type": "boolean" }, + "blockdiag": { "type": "boolean" } + }, + "additionalProperties": false +} diff --git a/app/views/admin/application_settings/_kroki.html.haml b/app/views/admin/application_settings/_kroki.html.haml index 23848fb8b9b02..cd57d4cca6570 100644 --- a/app/views/admin/application_settings/_kroki.html.haml +++ b/app/views/admin/application_settings/_kroki.html.haml @@ -21,5 +21,13 @@ = f.text_field :kroki_url, class: 'form-control gl-form-input', placeholder: 'http://your-kroki-instance:8000' .form-text.text-muted = (_('When Kroki is enabled, GitLab sends diagrams to an instance of Kroki to display them as images. You can use the free public cloud instance %{kroki_public_url} or you can %{install_link} on your own infrastructure. Once you\'ve installed Kroki, make sure to update the server URL to point to your instance.') % { kroki_public_url: '<code>https://kroki.io</code>', install_link: link_to('install Kroki', 'https://docs.kroki.io/kroki/setup/install/', target: '_blank') }).html_safe + .form-group + = f.label :kroki_formats, 'Additional diagram formats', class: 'label-bold' + .form-text.text-muted + = (_('Using additional formats requires starting the companion containers. Make sure that all %{kroki_images} are running.') % { kroki_images: link_to('required containers', 'https://docs.kroki.io/kroki/setup/install/#_images', target: '_blank') }).html_safe + - kroki_available_formats.each do |format| + .form-check + = f.check_box format[:name], class: 'form-check-input' + = f.label format[:name], format[:label], class: 'form-check-label' = f.submit _('Save changes'), class: "btn gl-button btn-success" diff --git a/changelogs/unreleased/241744-additional-formats-kroki.yml b/changelogs/unreleased/241744-additional-formats-kroki.yml new file mode 100644 index 0000000000000..f305ee13553bd --- /dev/null +++ b/changelogs/unreleased/241744-additional-formats-kroki.yml @@ -0,0 +1,5 @@ +--- +title: "Enable/disable additional diagram formats on Kroki" +merge_request: 49304 +author: Guillaume Grossetie +type: added diff --git a/db/migrate/20201120092000_add_kroki_formats_to_application_settings_table.rb b/db/migrate/20201120092000_add_kroki_formats_to_application_settings_table.rb new file mode 100644 index 0000000000000..a059099dbc4f1 --- /dev/null +++ b/db/migrate/20201120092000_add_kroki_formats_to_application_settings_table.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddKrokiFormatsToApplicationSettingsTable < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + change_table :application_settings do |t| + t.jsonb :kroki_formats, null: false, default: {} + end + end +end diff --git a/db/schema_migrations/20201120092000 b/db/schema_migrations/20201120092000 new file mode 100644 index 0000000000000..eaa6c37cff27e --- /dev/null +++ b/db/schema_migrations/20201120092000 @@ -0,0 +1 @@ +c8f837a5fe7a1959af41f19f93b6dd96d8907a476626f124876ee8b10b120b71 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 429f233c8d568..f3feb2573de07 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9398,6 +9398,7 @@ CREATE TABLE application_settings ( keep_latest_artifact boolean DEFAULT true NOT NULL, notes_create_limit integer DEFAULT 300 NOT NULL, notes_create_limit_allowlist text[] DEFAULT '{}'::text[] NOT NULL, + kroki_formats jsonb DEFAULT '{}'::jsonb NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)), CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)), diff --git a/lib/gitlab/kroki.rb b/lib/gitlab/kroki.rb index 8c5652fb766e3..38090786836b4 100644 --- a/lib/gitlab/kroki.rb +++ b/lib/gitlab/kroki.rb @@ -13,9 +13,7 @@ module Kroki packetdiag rackdiag ].freeze - # Diagrams that require a companion container are disabled for now DIAGRAMS_FORMATS = ::AsciidoctorExtensions::Kroki::SUPPORTED_DIAGRAM_NAMES - .reject { |diagram_type| diagram_type == 'mermaid' || diagram_type == 'bpmn' || BLOCKDIAG_FORMATS.include?(diagram_type) } DIAGRAMS_FORMATS_WO_PLANTUML = DIAGRAMS_FORMATS .reject { |diagram_type| diagram_type == 'plantuml' } @@ -28,10 +26,18 @@ def self.formats(current_settings) # If PlantUML is enabled, PlantUML diagrams will be processed by the PlantUML server. # In other words, the PlantUML server has precedence over Kroki since both can process PlantUML diagrams. - if current_settings.plantuml_enabled - DIAGRAMS_FORMATS_WO_PLANTUML - else - DIAGRAMS_FORMATS + diagram_formats = if current_settings.plantuml_enabled + DIAGRAMS_FORMATS_WO_PLANTUML + else + DIAGRAMS_FORMATS + end + + # No additional diagram formats + return diagram_formats unless current_settings.kroki_formats.present? + + # Diagrams that require a companion container must be explicitly enabled from the settings + diagram_formats.select do |diagram_type| + current_settings.kroki_format_supported?(diagram_type) end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3847742c1b588..5076242892788 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -32256,6 +32256,9 @@ msgstr "" msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}" msgstr "" +msgid "Using additional formats requires starting the companion containers. Make sure that all %{kroki_images} are running." +msgstr "" + msgid "Using required encryption strategy when encrypted field is missing!" msgstr "" diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index f0b224484c6c2..71abf3191b89f 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -150,6 +150,13 @@ expect(ApplicationSetting.current.repository_storages_weighted_default).to eq(75) end + it 'updates kroki_formats setting' do + put :update, params: { application_setting: { kroki_formats_excalidraw: '1' } } + + expect(response).to redirect_to(general_admin_application_settings_path) + expect(ApplicationSetting.current.kroki_formats_excalidraw).to eq(true) + end + it "updates default_branch_name setting" do put :update, params: { application_setting: { default_branch_name: "example_branch_name" } } diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index 479e2d7ef9d6f..2cd01451e0d12 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -194,4 +194,33 @@ it { is_expected.to be false } end end + + describe '.kroki_available_formats' do + let(:application_setting) { build(:application_setting) } + + before do + helper.instance_variable_set(:@application_setting, application_setting) + stub_application_setting(kroki_formats: { 'blockdiag' => true, 'bpmn' => false, 'excalidraw' => false }) + end + + it 'returns available formats correctly' do + expect(helper.kroki_available_formats).to eq([ + { + name: 'kroki_formats_blockdiag', + label: 'BlockDiag (includes BlockDiag, SeqDiag, ActDiag, NwDiag, PacketDiag and RackDiag)', + value: true + }, + { + name: 'kroki_formats_bpmn', + label: 'BPMN', + value: false + }, + { + name: 'kroki_formats_excalidraw', + label: 'Excalidraw', + value: false + } + ]) + end + end end diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 36e4decdeadbe..08510d4652b10 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -510,6 +510,73 @@ module Gitlab expect(render(input, context)).to include(output.strip) end + + it 'does not convert a blockdiag diagram to image' do + input = <<~ADOC + [blockdiag] + .... + blockdiag { + Kroki -> generates -> "Block diagrams"; + Kroki -> is -> "very easy!"; + + Kroki [color = "greenyellow"]; + "Block diagrams" [color = "pink"]; + "very easy!" [color = "orange"]; + } + .... + ADOC + + output = <<~HTML + <div> + <div> + <pre>blockdiag { + Kroki -> generates -> "Block diagrams"; + Kroki -> is -> "very easy!"; + + Kroki [color = "greenyellow"]; + "Block diagrams" [color = "pink"]; + "very easy!" [color = "orange"]; + }</pre> + </div> + </div> + HTML + + expect(render(input, context)).to include(output.strip) + end + end + + context 'with Kroki and BlockDiag (additional format) enabled' do + before do + allow_any_instance_of(ApplicationSetting).to receive(:kroki_enabled).and_return(true) + allow_any_instance_of(ApplicationSetting).to receive(:kroki_url).and_return('https://kroki.io') + allow_any_instance_of(ApplicationSetting).to receive(:kroki_formats_blockdiag).and_return(true) + end + + it 'converts a blockdiag diagram to image' do + input = <<~ADOC + [blockdiag] + .... + blockdiag { + Kroki -> generates -> "Block diagrams"; + Kroki -> is -> "very easy!"; + + Kroki [color = "greenyellow"]; + "Block diagrams" [color = "pink"]; + "very easy!" [color = "orange"]; + } + .... + ADOC + + output = <<~HTML + <div> + <div> + <a class="no-attachment-icon" href="https://kroki.io/blockdiag/svg/eNpdzDEKQjEQhOHeU4zpPYFoYesRxGJ9bwghMSsbUYJ4d10UCZbDfPynolOek0Q8FsDeNCestoisNLmy-Qg7R3Blcm5hPcr0ITdaB6X15fv-_YdJixo2CNHI2lmK3sPRA__RwV5SzV80ZAegJjXSyfMFptc71w==" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Diagram" class="lazy" data-src="https://kroki.io/blockdiag/svg/eNpdzDEKQjEQhOHeU4zpPYFoYesRxGJ9bwghMSsbUYJ4d10UCZbDfPynolOek0Q8FsDeNCestoisNLmy-Qg7R3Blcm5hPcr0ITdaB6X15fv-_YdJixo2CNHI2lmK3sPRA__RwV5SzV80ZAegJjXSyfMFptc71w=="></a> + </div> + </div> + HTML + + expect(render(input, context)).to include(output.strip) + end end end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index efa9125bff994..9a4dd2c799b7f 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -964,6 +964,50 @@ def expect_invalid end end + describe 'kroki_format_supported?' do + it 'returns true when Excalidraw is enabled' do + subject.kroki_formats_excalidraw = true + expect(subject.kroki_format_supported?('excalidraw')).to eq(true) + end + + it 'returns true when BlockDiag is enabled' do + subject.kroki_formats_blockdiag = true + # format "blockdiag" aggregates multiple diagram types: actdiag, blockdiag, nwdiag... + expect(subject.kroki_format_supported?('actdiag')).to eq(true) + expect(subject.kroki_format_supported?('blockdiag')).to eq(true) + end + + it 'returns false when BlockDiag is disabled' do + subject.kroki_formats_blockdiag = false + # format "blockdiag" aggregates multiple diagram types: actdiag, blockdiag, nwdiag... + expect(subject.kroki_format_supported?('actdiag')).to eq(false) + expect(subject.kroki_format_supported?('blockdiag')).to eq(false) + end + + it 'returns false when the diagram type is optional and not enabled' do + expect(subject.kroki_format_supported?('bpmn')).to eq(false) + end + + it 'returns true when the diagram type is enabled by default' do + expect(subject.kroki_format_supported?('vegalite')).to eq(true) + expect(subject.kroki_format_supported?('nomnoml')).to eq(true) + expect(subject.kroki_format_supported?('unknown-diagram-type')).to eq(false) + end + + it 'returns false when the diagram type is unknown' do + expect(subject.kroki_format_supported?('unknown-diagram-type')).to eq(false) + end + end + + describe 'kroki_formats' do + it 'returns the value for kroki_formats' do + subject.kroki_formats = { blockdiag: true, bpmn: false, excalidraw: true } + expect(subject.kroki_formats_blockdiag).to eq(true) + expect(subject.kroki_formats_bpmn).to eq(false) + expect(subject.kroki_formats_excalidraw).to eq(true) + end + end + it 'does not allow to set weight for non existing storage' do setting.repository_storages_weighted = { invalid_storage: 100 } -- GitLab