diff --git a/app/assets/javascripts/drawio/constants.js b/app/assets/javascripts/drawio/constants.js index 2e1e074db3bbd466f974b2b9f621461670690415..2f1870087f9b60b91c4389f9d42e240aa79c1149 100644 --- a/app/assets/javascripts/drawio/constants.js +++ b/app/assets/javascripts/drawio/constants.js @@ -1,9 +1,18 @@ /* * TODO: Make this URL configurable */ -export const DRAWIO_EDITOR_URL = - 'https://embed.diagrams.net/?ui=sketch&noSaveBtn=1&saveAndExit=1&keepmodified=1&spin=1&embed=1&libraries=1&configure=1&proto=json&toSvg=1'; // TODO Make it configurable - +export const DRAWIO_PARAMS = { + ui: 'sketch', + noSaveBtn: 1, + saveAndExit: 1, + keepmodified: 1, + spin: 1, + embed: 1, + libraries: 1, + configure: 1, + proto: 'json', + toSvg: 1, +}; export const DRAWIO_FRAME_ID = 'drawio-frame'; export const DARK_BACKGROUND_COLOR = '#202020'; diff --git a/app/assets/javascripts/drawio/drawio_editor.js b/app/assets/javascripts/drawio/drawio_editor.js index 38d1cadcc635a42b9224f916541a5397e1cac4c5..3c411d8093cd05f97045b94233303bad023a3673 100644 --- a/app/assets/javascripts/drawio/drawio_editor.js +++ b/app/assets/javascripts/drawio/drawio_editor.js @@ -4,8 +4,8 @@ import { darkModeEnabled } from '~/lib/utils/color_utils'; import { __ } from '~/locale'; import { setAttributes } from '~/lib/utils/dom_utils'; import { + DRAWIO_PARAMS, DARK_BACKGROUND_COLOR, - DRAWIO_EDITOR_URL, DRAWIO_FRAME_ID, DIAGRAM_BACKGROUND_COLOR, DRAWIO_IFRAME_TIMEOUT, @@ -17,7 +17,7 @@ function updateDrawioEditorState(drawIOEditorState, data) { } function postMessageToDrawioEditor(drawIOEditorState, message) { - const { origin } = new URL(DRAWIO_EDITOR_URL); + const { origin } = new URL(drawIOEditorState.drawioUrl); drawIOEditorState.iframe.contentWindow.postMessage(JSON.stringify(message), origin); } @@ -222,7 +222,7 @@ function createEditorIFrame(drawIOEditorState) { setAttributes(iframe, { id: DRAWIO_FRAME_ID, - src: DRAWIO_EDITOR_URL, + src: drawIOEditorState.drawioUrl, class: 'drawio-editor', }); @@ -256,7 +256,7 @@ function attachDrawioIFrameMessageListener(drawIOEditorState, editorFacade) { }); } -const createDrawioEditorState = ({ filename = null }) => ({ +const createDrawioEditorState = ({ filename = null, drawioUrl }) => ({ newDiagram: true, filename, diagramSvg: null, @@ -266,10 +266,17 @@ const createDrawioEditorState = ({ filename = null }) => ({ initialized: false, dark: darkModeEnabled(), disposeEventListener: null, + drawioUrl, }); -export function launchDrawioEditor({ editorFacade, filename }) { - const drawIOEditorState = createDrawioEditorState({ filename }); +export function launchDrawioEditor({ editorFacade, filename, drawioUrl = gon.diagramsnet_url }) { + const url = new URL(drawioUrl); + + for (const [key, value] of Object.entries(DRAWIO_PARAMS)) { + url.searchParams.set(key, value); + } + + const drawIOEditorState = createDrawioEditorState({ filename, drawioUrl: url.href }); // The execution order of these two functions matter attachDrawioIFrameMessageListener(drawIOEditorState, editorFacade); diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue index 3b38d715ea582df8e23afcd09a163b74ba4a1993..4f68c7984e89c43ad643ade3912c389992d05951 100644 --- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue +++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue @@ -107,7 +107,7 @@ export default { MarkdownEditor, }, mixins: [trackingMixin], - inject: ['formatOptions', 'pageInfo'], + inject: ['formatOptions', 'pageInfo', 'drawioUrl'], data() { return { editingMode: 'source', @@ -183,6 +183,9 @@ export default { disableSubmitButton() { return this.noContent || !this.title; }, + drawioEnabled() { + return typeof this.drawioUrl === 'string' && this.drawioUrl.length > 0; + }, }, mounted() { if (!this.commitMessage) this.updateCommitMessage(); @@ -356,7 +359,7 @@ export default { :autofocus="pageInfo.persisted" :enable-autocomplete="true" :autocomplete-data-sources="autocompleteDataSources" - :drawio-enabled="true" + :drawio-enabled="drawioEnabled" @contentEditor="notifyContentEditorActive" @markdownField="notifyContentEditorInactive" @keydown.ctrl.enter="submitFormShortcut" diff --git a/app/assets/javascripts/pages/shared/wikis/edit.js b/app/assets/javascripts/pages/shared/wikis/edit.js index 02878633916ede1d6db7415ac5f0e6edaaaf69ae..0044575de62a380aca70afb031960dc0519d0cb8 100644 --- a/app/assets/javascripts/pages/shared/wikis/edit.js +++ b/app/assets/javascripts/pages/shared/wikis/edit.js @@ -70,6 +70,7 @@ const createWikiFormApp = () => { provide: { formatOptions: JSON.parse(formatOptions), pageInfo: convertObjectPropsToCamelCase(JSON.parse(pageInfo)), + drawioUrl: gon.diagramsnet_url, }, render(createElement) { return createElement(wikiForm); diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index 265cf2a769822ee02a43807530ee0f0cdb307462..c606ccf4a07e7bd3306df1801d1f6a9f333c370d 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -13,9 +13,10 @@ module WikiActions included do content_security_policy do |p| next if p.directives.blank? + next unless Gitlab::CurrentSettings.diagramsnet_enabled? default_frame_src = p.directives['frame-src'] || p.directives['default-src'] - frame_src_values = Array.wrap(default_frame_src) | ['https://embed.diagrams.net'].compact + frame_src_values = Array.wrap(default_frame_src) | [Gitlab::CurrentSettings.diagramsnet_url].compact p.frame_src(*frame_src_values) end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index e163625f85fc9e1288851b41b255ba5e54443efa..1c988b9767fbec90c7d3f496cb1c1a76dd1bdd2b 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -337,6 +337,8 @@ def visible_attributes :kroki_formats, :plantuml_enabled, :plantuml_url, + :diagramsnet_enabled, + :diagramsnet_url, :polling_interval_multiplier, :project_export_enabled, :prometheus_metrics_enabled, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index aafce07dcdbef2827e5b7585543ba715e2fa9500..0e7fca6520889b00e72abbb1d4d199b4c12430a9 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -186,6 +186,11 @@ def self.kroki_formats_attributes validates :sourcegraph_url, presence: true, if: :sourcegraph_enabled + validates :diagramsnet_url, + presence: true, + addressable_url: ADDRESSABLE_URL_VALIDATION_OPTIONS.merge({ enforce_sanitization: true }), + if: :diagramsnet_enabled + validates :gitpod_url, presence: true, addressable_url: ADDRESSABLE_URL_VALIDATION_OPTIONS.merge({ enforce_sanitization: true }), diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 81ce057fa222a60026f52ce388f02e3f4364819c..ea07fe99c996434313c820e3ebe64db3c9ce8311 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -132,6 +132,8 @@ def defaults # rubocop:disable Metrics/AbcSize personal_access_token_prefix: 'glpat-', plantuml_enabled: false, plantuml_url: nil, + diagramsnet_enabled: true, + diagramsnet_url: 'https://embed.diagrams.net', polling_interval_multiplier: 1, productivity_analytics_start_date: Time.current, project_download_export_limit: 1, diff --git a/app/views/admin/application_settings/_diagramsnet.html.haml b/app/views/admin/application_settings/_diagramsnet.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..e493110a9dc97dbc24836c45ed27d2e92df3bf1c --- /dev/null +++ b/app/views/admin/application_settings/_diagramsnet.html.haml @@ -0,0 +1,25 @@ +- expanded = integration_expanded?('diagramsnet_') +%section.settings.as-diagramsnet.no-animate#js-diagramsnet-settings{ class: ('expanded' if expanded) } + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only + = _('Diagrams.net') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = expanded ? _('Collapse') : _('Expand') + %p + = _('Render diagrams in your documents using diagrams.net.') + = link_to _('Learn more.'), help_page_path('administration/integration/diagrams_net.md'), target: '_blank', rel: 'noopener noreferrer' + .settings-content + = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-diagramsnet-settings'), html: { class: 'fieldset-form', id: 'diagramsnet-settings' } do |f| + = form_errors(@application_setting) if expanded + + %fieldset + .form-group + = f.gitlab_ui_checkbox_component :diagramsnet_enabled, + _('Enable diagrams.net') + .form-group + = f.label :diagramsnet_url, _('Diagrams.net URL'), class: 'label-bold' + = f.text_field :diagramsnet_url, class: 'form-control gl-form-input', placeholder: 'https://embed.diagrams.net' + .form-text.text-muted + = _('The hostname of your diagrams.net server.') + + = f.submit _('Save changes'), pajamas_button: true diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml index e6c27c1bc84494e8a99af89204c60aa3c68d4313..3413774b3610d98a4a96f8f0b1a2e0d8688fdb3e 100644 --- a/app/views/admin/application_settings/general.html.haml +++ b/app/views/admin/application_settings/general.html.haml @@ -94,6 +94,7 @@ = render 'admin/application_settings/kroki' = render 'admin/application_settings/mailgun' = render 'admin/application_settings/plantuml' += render 'admin/application_settings/diagramsnet' = render 'admin/application_settings/sourcegraph' = render_if_exists 'admin/application_settings/slack' -# this partial is from JiHu, see details in https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417 diff --git a/db/migrate/20230329235300_add_diagramsnet_to_application_settings.rb b/db/migrate/20230329235300_add_diagramsnet_to_application_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..9e351f190f4fbed136fa62d0429c061464b5b8b1 --- /dev/null +++ b/db/migrate/20230329235300_add_diagramsnet_to_application_settings.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class AddDiagramsnetToApplicationSettings < Gitlab::Database::Migration[2.1] + enable_lock_retries! + # rubocop:disable Migration/AddLimitToTextColumns + # limit is added in 20230406115900_add_diagramsnet_text_limit.rb + def change + add_column :application_settings, :diagramsnet_enabled, :boolean, default: true, null: false + add_column :application_settings, :diagramsnet_url, :text, default: 'https://embed.diagrams.net' + end + # rubocop:enable Migration/AddLimitToTextColumns +end diff --git a/db/migrate/20230406115900_add_diagramsnet_text_limit.rb b/db/migrate/20230406115900_add_diagramsnet_text_limit.rb new file mode 100644 index 0000000000000000000000000000000000000000..27155c70c56b68bba72bef6b9bf60546284a1af8 --- /dev/null +++ b/db/migrate/20230406115900_add_diagramsnet_text_limit.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddDiagramsnetTextLimit < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_text_limit :application_settings, :diagramsnet_url, 2048 + end + + def down + remove_text_limit :application_settings, :diagramsnet_url + end +end diff --git a/db/schema_migrations/20230329235300 b/db/schema_migrations/20230329235300 new file mode 100644 index 0000000000000000000000000000000000000000..0f3d4099553cda48ec84b27389343bffd2458d66 --- /dev/null +++ b/db/schema_migrations/20230329235300 @@ -0,0 +1 @@ +44df5e98715af0cf9f8920e8fc35754901d578ae5c1dcc5fa7a3fb9ee49f995b \ No newline at end of file diff --git a/db/schema_migrations/20230406115900 b/db/schema_migrations/20230406115900 new file mode 100644 index 0000000000000000000000000000000000000000..38fa9134dacea269dd8a69936756ba5b6028de8d --- /dev/null +++ b/db/schema_migrations/20230406115900 @@ -0,0 +1 @@ +85cf98db148785c25a6ed472a300f5967aea916ef9b937d78bff90e33905886f \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 45834eff57d5384c6abc3f9a6ccda45d7e5153b7..a23d01c628e0b7c40db4997db77368af42f1fdcd 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11837,6 +11837,8 @@ CREATE TABLE application_settings ( remember_me_enabled boolean DEFAULT true NOT NULL, encrypted_anthropic_api_key bytea, encrypted_anthropic_api_key_iv bytea, + diagramsnet_enabled boolean DEFAULT true NOT NULL, + diagramsnet_url text DEFAULT 'https://embed.diagrams.net'::text, allow_account_deletion boolean DEFAULT true NOT NULL, vertex_project text, wiki_asciidoc_allow_uri_includes boolean DEFAULT false NOT NULL, @@ -11856,6 +11858,7 @@ CREATE TABLE application_settings ( CONSTRAINT app_settings_registry_repair_worker_max_concurrency_positive CHECK ((container_registry_data_repair_detail_worker_max_concurrency >= 0)), CONSTRAINT app_settings_yaml_max_depth_positive CHECK ((max_yaml_depth > 0)), CONSTRAINT app_settings_yaml_max_size_positive CHECK ((max_yaml_size_bytes > 0)), + CONSTRAINT check_0542340619 CHECK ((char_length(diagramsnet_url) <= 2048)), CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)), CONSTRAINT check_2b820eaac3 CHECK ((char_length(database_grafana_tag) <= 255)), CONSTRAINT check_2dba05b802 CHECK ((char_length(gitpod_url) <= 255)), diff --git a/doc/administration/integration/diagrams_net.md b/doc/administration/integration/diagrams_net.md new file mode 100644 index 0000000000000000000000000000000000000000..fe5e730a0643b762f5cd1eef1569361b1ff46b45 --- /dev/null +++ b/doc/administration/integration/diagrams_net.md @@ -0,0 +1,53 @@ +--- +stage: Create +group: Source Code +info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments" +type: reference, howto +--- + +# Diagrams.net **(FREE)** + +With the [diagrams.net](https://www.diagrams.net/) integration, you can create and embed SVG diagrams in wikis. +The diagram editor is available in both the Markdown editor and the content editor. + +On GitLab.com, this integration is enabled for all SaaS users and does not require any additional configuration. + +On self-managed GitLab, you can choose to integrate with the free [diagrams.net](https://www.diagrams.net/) +website, or use a self-managed diagrams.net site in offline environments. + +To set up the integration on a self-managed instance, you must: + +1. Choose to integrate with the free diagrams.net website or + [configure your diagrams.net server](#configure-your-diagramsnet-server). +1. [Enable the integration](#enable-diagramsnet-integration). + +After completing the integration, the diagrams.net editor opens with the URL you provided. + +## Configure your diagrams.net server + +You can set up your own diagrams.net server to generate the diagrams. + +This is a required step for users on offline (or "air-gapped") self-managed GitLab installations. + +For example, to run a diagrams.net container in Docker, run the following command: + +```shell +docker run -it --rm --name="draw" -p 8080:8080 -p 8443:8443 jgraph/drawio +``` + +Make note of the hostname of the server running the container, to be used as the diagrams.net URL +when you enable the integration. + +For more information, see [Run your own diagrams.net server with Docker](https://www.diagrams.net/blog/diagrams-docker-app). + +## Enable Diagrams.net integration + +1. Sign in to GitLab as an [Administrator](../../user/permissions.md) user. +1. On the top bar, select **Main menu > Admin**. +1. On the left sidebar, select **Settings > General**. +1. Expand **Diagrams.net**. +1. Select the **Enable Diagrams.net** checkbox. +1. Enter the Diagrams.net URL. To connect to: + - The free public instance: enter `https://embed.diagrams.net`. + - A self-managed diagrams.net instance: enter the URL you [configured earlier](#configure-your-diagramsnet-server). +1. Select **Save changes**. diff --git a/doc/api/settings.md b/doc/api/settings.md index 559332858b3d2835f5a4287928a82b2d19b91853..231cff418e562b93f04c9da2096094f5d4e88e20 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -67,6 +67,8 @@ Example response: "repository_storages_weighted": {"default": 100}, "plantuml_enabled": false, "plantuml_url": null, + "diagramsnet_enabled": true, + "diagramsnet_url": "https://embed.diagrams.net", "kroki_enabled": false, "kroki_url": null, "terminal_max_session_time": 0, @@ -193,6 +195,8 @@ Example response: "repository_storages": ["default"], "plantuml_enabled": false, "plantuml_url": null, + "diagramsnet_enabled": true, + "diagramsnet_url": "https://embed.diagrams.net", "terminal_max_session_time": 0, "polling_interval_multiplier": 1.0, "rsa_key_restriction": 0, @@ -325,6 +329,8 @@ listed in the descriptions of the relevant settings. | `delayed_group_deletion` **(PREMIUM SELF)** | boolean | no | Enable delayed group deletion. Default is `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/352959) in GitLab 15.0. [From GitLab 15.1](https://gitlab.com/gitlab-org/gitlab/-/issues/352960), disables and locks the group-level setting for delayed protect deletion when set to `false`. From [GitLab 15.11](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113332), with the `always_perform_delayed_deletion` feature flag enabled, this attribute has been removed. This attribute will be completely removed in GitLab 16.0. | | `default_project_deletion_protection` **(PREMIUM SELF)** | boolean | no | Enable default project deletion protection so only administrators can delete projects. Default is `false`. | | `deletion_adjourned_period` **(PREMIUM SELF)** | integer | no | The number of days to wait before deleting a project or group that is marked for deletion. Value must be between `1` and `90`. Defaults to `7`. [From GitLab 15.1](https://gitlab.com/gitlab-org/gitlab/-/issues/352960), a hook on `deletion_adjourned_period` sets the period to `1` on every update, and sets both `delayed_project_deletion` and `delayed_group_deletion` to `false` if the period is `0`. | +| `diagramsnet_enabled` | boolean | no | (If enabled, requires `diagramsnet_url`) Enable [Diagrams.net integration](../administration/integration/diagrams_net.md). Default is `true`. | +| `diagramsnet_url` | string | required by: `diagramsnet_enabled` | The Diagrams.net instance URL for integration. | | `diff_max_patch_bytes` | integer | no | Maximum [diff patch size](../user/admin_area/diff_limits.md), in bytes. | | `diff_max_files` | integer | no | Maximum [files in a diff](../user/admin_area/diff_limits.md). | | `diff_max_lines` | integer | no | Maximum [lines in a diff](../user/admin_area/diff_limits.md). | diff --git a/doc/user/markdown.md b/doc/user/markdown.md index b8ed1c0632425674e6556c2451c105130d5478a8..2026a03315044c4232b23527c366eb83212306c7 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -567,13 +567,12 @@ This example links to `<wiki_root>/miscellaneous.md`: > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/322174) in GitLab 15.10. -NOTE: -Use of the diagrams.net editor is not available on offline environments. - In wikis, you can use the [diagrams.net](https://www.diagrams.net/) editor to create diagrams. You can also edit diagrams created with the diagrams.net editor. The diagram editor is available in both the Markdown editor and the content editor. +For more information, see [Diagrams.net](../administration/integration/diagrams_net.md). + ##### Markdown editor To create a diagram in the Markdown editor: diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 78be9ee75e943d97cabc97fdaf9a57f26830532f..3b553c4a66eac9130e945b2926cd17cac38e57c1 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -125,6 +125,10 @@ def filter_attributes_using_license(attrs) given plantuml_enabled: ->(val) { val } do requires :plantuml_url, type: String, desc: 'The PlantUML server URL' end + optional :diagramsnet_enabled, type: Boolean, desc: 'Enable Diagrams.net' + given diagramsnet_enabled: ->(val) { val } do + requires :diagramsnet_url, type: String, desc: 'The Diagrams.net server URL' + end optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.' optional :project_export_enabled, type: Boolean, desc: 'Enable project export' optional :prometheus_metrics_enabled, type: Boolean, desc: 'Enable Prometheus metrics' diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 904a2ccc79bf70ac17c1a447106978fa4d165010..eef176687e7622fabb8872d65938126aea4abdb8 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -51,6 +51,8 @@ def add_gon_variables gon.dot_com = Gitlab.com? gon.uf_error_prefix = ::Gitlab::Utils::ErrorMessage::UF_ERROR_PREFIX + gon.diagramsnet_url = Gitlab::CurrentSettings.diagramsnet_url if Gitlab::CurrentSettings.diagramsnet_enabled + if current_user gon.current_user_id = current_user.id gon.current_username = current_user.username diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 46b5d39d1e5304005be4ce0e62d088e108f58a58..245f089b561f9b7d1d277fc952c3b46c6271a165 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -15829,6 +15829,12 @@ msgstr "" msgid "Diagram saved successfully." msgstr "" +msgid "Diagrams.net" +msgstr "" + +msgid "Diagrams.net URL" +msgstr "" + msgid "Did not delete the source branch." msgstr "" @@ -16797,6 +16803,9 @@ msgstr "" msgid "Enable dashboard limits on namespaces" msgstr "" +msgid "Enable diagrams.net" +msgstr "" + msgid "Enable email notification" msgstr "" @@ -38022,6 +38031,9 @@ msgstr "" msgid "Render diagrams in your documents using PlantUML." msgstr "" +msgid "Render diagrams in your documents using diagrams.net." +msgstr "" + msgid "Renew subscription" msgstr "" @@ -45458,6 +45470,9 @@ msgstr "" msgid "The hostname of your Snowplow collector." msgstr "" +msgid "The hostname of your diagrams.net server." +msgstr "" + msgid "The import cannot be canceled because it is %{project_status}" msgstr "" diff --git a/spec/features/merge_request/user_comments_on_merge_request_spec.rb b/spec/features/merge_request/user_comments_on_merge_request_spec.rb index e113e305af55ed126d9cdcd2a32684f988159b33..3aa2ce2a154f73dc15cd303b7410701d323be806 100644 --- a/spec/features/merge_request/user_comments_on_merge_request_spec.rb +++ b/spec/features/merge_request/user_comments_on_merge_request_spec.rb @@ -6,12 +6,15 @@ include RepoHelpers let(:project) { create(:project, :repository) } + let(:diagramsnet_url) { 'https://embed.diagrams.net' } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:user) { create(:user) } before do project.add_maintainer(user) sign_in(user) + allow(Gitlab::CurrentSettings).to receive(:diagramsnet_enabled).and_return(true) + allow(Gitlab::CurrentSettings).to receive(:diagramsnet_url).and_return(diagramsnet_url) visit(merge_request_path(merge_request)) end diff --git a/spec/frontend/__helpers__/test_constants.js b/spec/frontend/__helpers__/test_constants.js index 628b9b054d3349c756e996d2a270e44e274f348f..b5a585811d1940e1b44e1a177c48db1db84c13c6 100644 --- a/spec/frontend/__helpers__/test_constants.js +++ b/spec/frontend/__helpers__/test_constants.js @@ -1,5 +1,6 @@ const FIXTURES_PATH = `/fixtures`; const TEST_HOST = 'http://test.host'; +const DRAWIO_ORIGIN = 'https://embed.diagrams.net'; const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`; @@ -15,6 +16,7 @@ const DUMMY_IMAGE_BLOB_PATH = 'SpongeBlob.png'; module.exports = { FIXTURES_PATH, TEST_HOST, + DRAWIO_ORIGIN, DUMMY_IMAGE_URL, GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL, diff --git a/spec/frontend/drawio/drawio_editor_spec.js b/spec/frontend/drawio/drawio_editor_spec.js index d7d75922e1ee6393442b647f5f6ba4e1a4db87b9..4d93908b75725de52c0c4da7af18c45694aaa35c 100644 --- a/spec/frontend/drawio/drawio_editor_spec.js +++ b/spec/frontend/drawio/drawio_editor_spec.js @@ -1,6 +1,5 @@ import { launchDrawioEditor } from '~/drawio/drawio_editor'; import { - DRAWIO_EDITOR_URL, DRAWIO_FRAME_ID, DIAGRAM_BACKGROUND_COLOR, DRAWIO_IFRAME_TIMEOUT, @@ -8,6 +7,10 @@ import { } from '~/drawio/constants'; import { createAlert, VARIANT_SUCCESS } from '~/alert'; +const DRAWIO_EDITOR_URL = + 'https://embed.diagrams.net/?ui=sketch&noSaveBtn=1&saveAndExit=1&keepmodified=1&spin=1&embed=1&libraries=1&configure=1&proto=json&toSvg=1'; +const DRAWIO_EDITOR_ORIGIN = new URL(DRAWIO_EDITOR_URL).origin; + jest.mock('~/alert'); jest.useFakeTimers(); @@ -59,6 +62,7 @@ describe('drawio/drawio_editor', () => { updateDiagram: jest.fn(), }; drawioIFrameReceivedMessages = []; + gon.diagramsnet_url = DRAWIO_EDITOR_ORIGIN; }); afterEach(() => { @@ -356,7 +360,11 @@ describe('drawio/drawio_editor', () => { const TEST_FILENAME = 'diagram.drawio.svg'; beforeEach(() => { - launchDrawioEditor({ editorFacade, filename: TEST_FILENAME }); + launchDrawioEditor({ + editorFacade, + filename: TEST_FILENAME, + drawioUrl: DRAWIO_EDITOR_ORIGIN, + }); }); it('displays loading spinner in the draw.io editor', async () => { diff --git a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js index ddaa3df71e8c41abc8b374024713bcec06a36c0d..1a3eb86a00ea20c8724bfc40b2aabfe393036fb7 100644 --- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js +++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js @@ -14,6 +14,7 @@ import { WIKI_FORMAT_LABEL, WIKI_FORMAT_UPDATED_ACTION, } from '~/pages/shared/wikis/constants'; +import { DRAWIO_ORIGIN } from 'spec/test_constants'; jest.mock('~/emoji'); @@ -69,12 +70,12 @@ describe('WikiForm', () => { AsciiDoc: 'asciidoc', Org: 'org', }; - function createWrapper({ mountFn = shallowMount, persisted = false, pageInfo, glFeatures = { wikiSwitchBetweenContentEditorRawMarkdown: false }, + provide = { drawioUrl: null }, } = {}) { wrapper = extendedWrapper( mountFn(WikiForm, { @@ -85,6 +86,7 @@ describe('WikiForm', () => { ...(persisted ? pageInfoPersisted : pageInfoNew), ...pageInfo, }, + ...provide, }, stubs: { GlAlert, @@ -334,4 +336,20 @@ describe('WikiForm', () => { }); }); }); + + describe('when drawioURL is provided', () => { + it('enables drawio editor in the Markdown Editor', () => { + createWrapper({ provide: { drawioUrl: DRAWIO_ORIGIN } }); + + expect(findMarkdownEditor().props().drawioEnabled).toBe(true); + }); + }); + + describe('when drawioURL is empty', () => { + it('disables drawio editor in the Markdown Editor', () => { + createWrapper(); + + expect(findMarkdownEditor().props().drawioEnabled).toBe(false); + }); + }); }); diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 37f4bbf0f631e31c54f5a49531424e4555f7d6f6..9ecb0c6f75bceab7007f13237ad98aec129b4902 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -1127,6 +1127,26 @@ def expect_invalid end end + describe 'diagrams.net settings' do + context 'when diagrams.net is enabled' do + before do + setting.diagramsnet_enabled = true + end + + it { is_expected.not_to allow_value(nil).for(:diagramsnet_url) } + it { is_expected.to allow_value("https://embed.diagrams.net").for(:diagramsnet_url) } + it { is_expected.not_to allow_value('not a URL').for(:diagramsnet_url) } + end + + context 'when diagrams.net is not enabled' do + before do + setting.diagramsnet_enabled = false + end + + it { is_expected.to allow_value(nil).for(:diagramsnet_url) } + end + end + context 'throttle_* settings' do where(:throttle_setting) do %i[ diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 26c9399a8e5bb4b797dae3b681bbf7275f68629d..79e96d7ea3ebc80018b21c9d6a52b5de51e8112f 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -19,6 +19,8 @@ expect(json_response['password_authentication_enabled']).to be_truthy expect(json_response['plantuml_enabled']).to be_falsey expect(json_response['plantuml_url']).to be_nil + expect(json_response['diagramsnet_enabled']).to be_truthy + expect(json_response['diagramsnet_url']).to eq('https://embed.diagrams.net') expect(json_response['default_ci_config_path']).to be_nil expect(json_response['sourcegraph_enabled']).to be_falsey expect(json_response['sourcegraph_url']).to be_nil @@ -125,6 +127,8 @@ repository_storages_weighted: { 'custom' => 100 }, plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com', + diagramsnet_enabled: false, + diagramsnet_url: nil, sourcegraph_enabled: true, sourcegraph_url: 'https://sourcegraph.com', sourcegraph_public_only: false, @@ -203,6 +207,8 @@ expect(json_response['repository_storages_weighted']).to eq({ 'custom' => 100 }) expect(json_response['plantuml_enabled']).to be_truthy expect(json_response['plantuml_url']).to eq('http://plantuml.example.com') + expect(json_response['diagramsnet_enabled']).to be_falsey + expect(json_response['diagramsnet_url']).to be_nil expect(json_response['sourcegraph_enabled']).to be_truthy expect(json_response['sourcegraph_url']).to eq('https://sourcegraph.com') expect(json_response['sourcegraph_public_only']).to eq(false) @@ -553,6 +559,15 @@ end end + context "missing diagramsnet_url value when diagramsnet_enabled is true" do + it "returns a blank parameter error message" do + put api("/application/settings", admin), params: { diagramsnet_enabled: true } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq('diagramsnet_url is missing') + end + end + context 'asset_proxy settings' do it 'updates application settings' do put api('/application/settings', admin), diff --git a/spec/requests/projects/wikis_controller_spec.rb b/spec/requests/projects/wikis_controller_spec.rb index 3c434b36b2124f4b4d4bc62a6e0590a55d6a987f..9f69faf499e1aac5bfe824dffea6754e6a4b89b2 100644 --- a/spec/requests/projects/wikis_controller_spec.rb +++ b/spec/requests/projects/wikis_controller_spec.rb @@ -6,6 +6,8 @@ using RSpec::Parameterized::TableSyntax let_it_be(:user) { create(:user) } + let_it_be(:diagramsnet_is_enabled) { false } + let_it_be(:diagramsnet_url) { 'https://url.diagrams.net' } let_it_be(:project) { create(:project, :wiki_repo, namespace: user.namespace) } let_it_be(:project_wiki) { create(:project_wiki, project: project, user: user) } let_it_be(:wiki_page) do @@ -18,6 +20,12 @@ before do sign_in(user) + allow(Gitlab::CurrentSettings) + .to receive(:diagramsnet_enabled?) + .and_return(diagramsnet_is_enabled) + allow(Gitlab::CurrentSettings) + .to receive(:diagramsnet_url) + .and_return(diagramsnet_url) allow_next_instance_of(described_class) do |instance| allow(instance).to receive(:content_security_policy_nonce).and_return(csp_nonce) @@ -25,12 +33,26 @@ end shared_examples 'embed.diagrams.net frame-src directive' do - it 'adds drawio frame-src directive to the Content Security Policy header' do - frame_src = response.headers['Content-Security-Policy'].split(';') - .map(&:strip) - .find { |entry| entry.starts_with?('frame-src') } + context 'when diagrams.net disabled' do + it 'drawio frame-src directive to the Content Security Policy header' do + frame_src = response.headers['Content-Security-Policy'].split(';') + .map(&:strip) + .find { |entry| entry.starts_with?('frame-src') } - expect(frame_src).to include('https://embed.diagrams.net') + expect(frame_src).not_to include(diagramsnet_url) + end + end + + context 'when diagrams.net enabled' do + let(:diagramsnet_is_enabled) { true } + + it 'drawio frame-src directive to the Content Security Policy header' do + frame_src = response.headers['Content-Security-Policy'].split(';') + .map(&:strip) + .find { |entry| entry.starts_with?('frame-src') } + + expect(frame_src).to include(diagramsnet_url) + end end end diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb index 1877aa6490d57562e7ea63a4bab9b3c9d1d8cd89..7725366d56591b54cf4b720c3e74f5b743461114 100644 --- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb @@ -6,9 +6,12 @@ RSpec.shared_examples 'User updates wiki page' do include WikiHelpers + let(:diagramsnet_url) { 'https://embed.diagrams.net' } before do sign_in(user) + allow(Gitlab::CurrentSettings).to receive(:diagramsnet_enabled).and_return(true) + allow(Gitlab::CurrentSettings).to receive(:diagramsnet_url).and_return(diagramsnet_url) end context 'when wiki is empty', :js do