diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-28 00:10:27 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-28 00:10:27 +0300 |
commit | 29471900730296fbd71ba8a24d3d5bc810cd2529 (patch) | |
tree | 7ba4881377bb7fe98901c2e27bf275ba1ad79b9b | |
parent | da50206243972a4cafcaea7539aed7c6986c775e (diff) |
Add latest changes from gitlab-org/gitlab@master
25 files changed, 288 insertions, 200 deletions
diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue index a1351faeee2..9c8afdbc266 100644 --- a/app/assets/javascripts/issue_show/components/title.vue +++ b/app/assets/javascripts/issue_show/components/title.vue @@ -1,13 +1,15 @@ <script> /* eslint-disable vue/no-v-html */ +import { GlButton, GlTooltipDirective } from '@gitlab/ui'; import animateMixin from '../mixins/animate'; import eventHub from '../event_hub'; -import tooltip from '../../vue_shared/directives/tooltip'; -import { spriteIcon } from '../../lib/utils/common_utils'; export default { + components: { + GlButton, + }, directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, mixins: [animateMixin], props: { @@ -41,11 +43,6 @@ export default { titleEl: document.querySelector('title'), }; }, - computed: { - pencilIcon() { - return spriteIcon('pencil', 'link-highlight'); - }, - }, watch: { titleHtml() { this.setPageTitle(); @@ -76,17 +73,13 @@ export default { dir="auto" v-html="titleHtml" ></h2> - <button + <gl-button v-if="showInlineEditButton && canUpdate" - v-tooltip - type="button" - class="btn btn-default btn-edit btn-svg js-issuable-edit - qa-edit-button" + v-gl-tooltip.bottom + icon="pencil" + class="btn-edit js-issuable-edit qa-edit-button" title="Edit title and description" - data-placement="bottom" - data-container="body" @click="edit" - v-html="pencilIcon" - ></button> + /> </div> </template> diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue index e3f4500d404..ee69da47ed8 100644 --- a/app/assets/javascripts/projects/components/shared/delete_button.vue +++ b/app/assets/javascripts/projects/components/shared/delete_button.vue @@ -86,7 +86,7 @@ export default { <slot name="modal-body"></slot> <p class="gl-mb-1">{{ $options.strings.confirmText }}</p> <p> - <code>{{ confirmPhrase }}</code> + <code class="ws-pre-wrap">{{ confirmPhrase }}</code> </p> <gl-form-input id="confirm_name_input" diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 1c7a6535c12..6226685d93d 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -50,6 +50,7 @@ .title-container { display: flex; + align-items: flex-start; } .title { @@ -65,7 +66,6 @@ .btn-edit { margin-left: auto; - height: $gl-padding * 2; } .emoji-block { diff --git a/app/models/concerns/milestoneable.rb b/app/models/concerns/milestoneable.rb index ccb334343ff..b1698bc2ee3 100644 --- a/app/models/concerns/milestoneable.rb +++ b/app/models/concerns/milestoneable.rb @@ -51,7 +51,7 @@ module Milestoneable # Overridden on EE module # def supports_milestone? - respond_to?(:milestone_id) + respond_to?(:milestone_id) && !incident? end end diff --git a/app/serializers/issuable_sidebar_basic_entity.rb b/app/serializers/issuable_sidebar_basic_entity.rb index 7e4164fecbc..11df60ade70 100644 --- a/app/serializers/issuable_sidebar_basic_entity.rb +++ b/app/serializers/issuable_sidebar_basic_entity.rb @@ -104,6 +104,7 @@ class IssuableSidebarBasicEntity < Grape::Entity end expose :supports_time_tracking?, as: :supports_time_tracking + expose :supports_milestone?, as: :supports_milestone private diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index c2fe16be257..c4e8a2d51ee 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -29,33 +29,34 @@ = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar - - milestone = issuable_sidebar[:milestone] || {} - .block.milestone{ data: { qa_selector: 'milestone_block' } } - .sidebar-collapsed-icon.has-tooltip{ title: sidebar_milestone_tooltip_label(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } - = sprite_icon('clock') - %span.milestone-title.collapse-truncated-title + - if issuable_sidebar[:supports_milestone] + - milestone = issuable_sidebar[:milestone] || {} + .block.milestone{ data: { qa_selector: 'milestone_block' } } + .sidebar-collapsed-icon.has-tooltip{ title: sidebar_milestone_tooltip_label(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } + = sprite_icon('clock') + %span.milestone-title.collapse-truncated-title + - if milestone.present? + = milestone[:title] + - else + = _('None') + .title.hide-collapsed + = _('Milestone') + = loading_icon(css_class: 'gl-vertical-align-text-bottom hidden block-loading') + - if can_edit_issuable + = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { qa_selector: "edit_milestone_link", track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" } + .value.hide-collapsed - if milestone.present? - = milestone[:title] + - milestone_title = milestone[:expired] ? _("%{milestone_name} (Past due)").html_safe % { milestone_name: milestone[:title] } : milestone[:title] + = link_to milestone_title, milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link', qa_title: milestone[:title] } - else - = _('None') - .title.hide-collapsed - = _('Milestone') - = loading_icon(css_class: 'gl-vertical-align-text-bottom hidden block-loading') - - if can_edit_issuable - = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { qa_selector: "edit_milestone_link", track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" } - .value.hide-collapsed - - if milestone.present? - - milestone_title = milestone[:expired] ? _("%{milestone_name} (Past due)").html_safe % { milestone_name: milestone[:title] } : milestone[:title] - = link_to milestone_title, milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link', qa_title: milestone[:title] } - - else - %span.no-value - = _('None') - - .selectbox.hide-collapsed - = f.hidden_field 'milestone_id', value: milestone[:id], id: nil - = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }}) - - if @project.group.present? - = render_if_exists 'shared/issuable/iteration_select', { can_edit: can_edit_issuable, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type } + %span.no-value + = _('None') + + .selectbox.hide-collapsed + = f.hidden_field 'milestone_id', value: milestone[:id], id: nil + = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }}) + - if @project.group.present? + = render_if_exists 'shared/issuable/iteration_select', { can_edit: can_edit_issuable, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type } - if issuable_sidebar[:supports_time_tracking] #issuable-time-tracker.block diff --git a/changelogs/unreleased/229313-update-issue-edit-button.yml b/changelogs/unreleased/229313-update-issue-edit-button.yml new file mode 100644 index 00000000000..63c2be55ff0 --- /dev/null +++ b/changelogs/unreleased/229313-update-issue-edit-button.yml @@ -0,0 +1,5 @@ +--- +title: Update issue edit button to gl-button +merge_request: 40438 +author: +type: changed diff --git a/changelogs/unreleased/229968-remove-milesone-incidents.yml b/changelogs/unreleased/229968-remove-milesone-incidents.yml new file mode 100644 index 00000000000..7496f8b8336 --- /dev/null +++ b/changelogs/unreleased/229968-remove-milesone-incidents.yml @@ -0,0 +1,5 @@ +--- +title: "Remove milestone and iteration feature from Incidents sidebar" +merge_request: 40283 +author: +type: other diff --git a/changelogs/unreleased/241531-user-unable-to-delete-project.yml b/changelogs/unreleased/241531-user-unable-to-delete-project.yml new file mode 100644 index 00000000000..b05a30d56c9 --- /dev/null +++ b/changelogs/unreleased/241531-user-unable-to-delete-project.yml @@ -0,0 +1,5 @@ +--- +title: Fix delete confirm message not displaying trailing spaces +merge_request: 40549 +author: +type: fixed diff --git a/changelogs/unreleased/bw-large-description-timeout.yml b/changelogs/unreleased/bw-large-description-timeout.yml new file mode 100644 index 00000000000..1975f4dae63 --- /dev/null +++ b/changelogs/unreleased/bw-large-description-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Increase performance of rendering large amounts of markdown data +merge_request: 40448 +author: +type: performance diff --git a/doc/administration/auth/ldap/index.md b/doc/administration/auth/ldap/index.md index 548e734c931..6c21d3707af 100644 --- a/doc/administration/auth/ldap/index.md +++ b/doc/administration/auth/ldap/index.md @@ -16,6 +16,8 @@ This integration works with most LDAP-compliant directory servers, including: - Open LDAP - 389 Server +Users added through LDAP take a [licensed seat](../../../subscriptions/index.md#choosing-the-number-of-users). + GitLab Enterprise Editions (EE) include enhanced integration, including group membership syncing as well as multiple LDAP servers support. diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 6bd27670f96..5f6289a1bdc 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -955,3 +955,38 @@ For Omnibus GitLab installations, GitLab Monitor logs reside in `/var/log/gitlab ## GitLab Exporter For Omnibus GitLab installations, GitLab Exporter logs reside in `/var/log/gitlab/gitlab-exporter/`. + +## Gathering logs + +When [troubleshooting](troubleshooting/index.md) issues that aren't localized to one of the +previously listed components, it's helpful to simultaneously gather multiple logs and statistics +from a GitLab instance. + +### GitLabSOS + +If performance degradations or cascading errors occur that can't readily be attributed to one +of the previously listed GitLab components, [GitLabSOS](https://gitlab.com/gitlab-com/support/toolbox/gitlabsos/) +can provide a perspective spanning all of Omnibus GitLab. For more details and instructions +to run it, see [the GitLabSOS documentation](https://gitlab.com/gitlab-com/support/toolbox/gitlabsos/#gitlabsos). + +NOTE: **Note:** +GitLab Support likes to use this custom-made tool. + +### Briefly tail the main logs + +If the bug or error is readily reproducible bug or error, save the main GitLab logs +[to a file](troubleshooting/linux_cheat_sheet.md#files--dirs) while reproducing the +problem once or more times: + +```shell +sudo gitlab-ctl tail | tee /tmp/<case-ID-and-keywords>.log +``` + +Conclude the log gathering with <kbd>Ctrl</kbd> + <kbd>C</kbd>. + +### Fast-stats + +[Fast-stats](https://gitlab.com/gitlab-com/support/toolbox/fast-stats) is a tool +for creating and comparing performance statistics from GitLab logs. +For more details and instructions to run it, see +[read the documentation for fast-stats](https://gitlab.com/gitlab-com/support/toolbox/fast-stats#usage). diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index bff689c0c0c..5339017ecbf 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -157,25 +157,34 @@ configuration option in `gitlab.yml`. These metrics are served from the | `geo_lfs_objects_synced_missing_on_primary` | Gauge | 10.7 | Number of LFS objects marked as synced due to the file missing on the primary | `url` | | `geo_job_artifacts_synced_missing_on_primary` | Gauge | 10.7 | Number of job artifacts marked as synced due to the file missing on the primary | `url` | | `geo_attachments_synced_missing_on_primary` | Gauge | 10.7 | Number of attachments marked as synced due to the file missing on the primary | `url` | -| `geo_repositories_checksummed_count` | Gauge | 10.7 | Number of repositories checksummed on primary | `url` | -| `geo_repositories_checksum_failed_count` | Gauge | 10.7 | Number of repositories failed to calculate the checksum on primary | `url` | -| `geo_wikis_checksummed_count` | Gauge | 10.7 | Number of wikis checksummed on primary | `url` | -| `geo_wikis_checksum_failed_count` | Gauge | 10.7 | Number of wikis failed to calculate the checksum on primary | `url` | -| `geo_repositories_verified_count` | Gauge | 10.7 | Number of repositories verified on secondary | `url` | -| `geo_repositories_verification_failed_count` | Gauge | 10.7 | Number of repositories failed to verify on secondary | `url` | -| `geo_repositories_checksum_mismatch_count` | Gauge | 10.7 | Number of repositories that checksum mismatch on secondary | `url` | -| `geo_wikis_verified_count` | Gauge | 10.7 | Number of wikis verified on secondary | `url` | -| `geo_wikis_verification_failed_count` | Gauge | 10.7 | Number of wikis failed to verify on secondary | `url` | -| `geo_wikis_checksum_mismatch_count` | Gauge | 10.7 | Number of wikis that checksum mismatch on secondary | `url` | -| `geo_repositories_checked_count` | Gauge | 11.1 | Number of repositories that have been checked via `git fsck` | `url` | -| `geo_repositories_checked_failed_count` | Gauge | 11.1 | Number of repositories that have a failure from `git fsck` | `url` | -| `geo_repositories_retrying_verification_count` | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | `url` | -| `geo_wikis_retrying_verification_count` | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | `url` | +| `geo_repositories_checksummed` | Gauge | 10.7 | Number of repositories checksummed on primary | `url` | +| `geo_repositories_checksum_failed` | Gauge | 10.7 | Number of repositories failed to calculate the checksum on primary | `url` | +| `geo_wikis_checksummed` | Gauge | 10.7 | Number of wikis checksummed on primary | `url` | +| `geo_wikis_checksum_failed` | Gauge | 10.7 | Number of wikis failed to calculate the checksum on primary | `url` | +| `geo_repositories_verified` | Gauge | 10.7 | Number of repositories verified on secondary | `url` | +| `geo_repositories_verification_failed` | Gauge | 10.7 | Number of repositories failed to verify on secondary | `url` | +| `geo_repositories_checksum_mismatch` | Gauge | 10.7 | Number of repositories that checksum mismatch on secondary | `url` | +| `geo_wikis_verified` | Gauge | 10.7 | Number of wikis verified on secondary | `url` | +| `geo_wikis_verification_failed` | Gauge | 10.7 | Number of wikis failed to verify on secondary | `url` | +| `geo_wikis_checksum_mismatch` | Gauge | 10.7 | Number of wikis that checksum mismatch on secondary | `url` | +| `geo_repositories_checked` | Gauge | 11.1 | Number of repositories that have been checked via `git fsck` | `url` | +| `geo_repositories_checked_failed` | Gauge | 11.1 | Number of repositories that have a failure from `git fsck` | `url` | +| `geo_repositories_retrying_verification` | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | `url` | +| `geo_wikis_retrying_verification` | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | `url` | +| `geo_package_files` | Gauge | 13.0 | Number of package files on primary | `url` | +| `geo_package_files_checksummed` | Gauge | 13.0 | Number of package files checksummed on primary | `url` | +| `geo_package_files_checksum_failed` | Gauge | 13.0 | Number of package files failed to calculate the checksum on primary | `url` | +| `geo_package_files_synced` | Gauge | 13.3 | Number of syncable package files synced on secondary | `url` | +| `geo_package_files_failed` | Gauge | 13.3 | Number of syncable package files failed to sync on secondary | `url` | +| `geo_package_files_registry` | Gauge | 13.3 | Number of package files in the registry | `url` | +| `geo_terraform_states` | Gauge | 13.3 | Number of terraform states on primary | `url` | +| `geo_terraform_states_checksummed` | Gauge | 13.3 | Number of terraform states checksummed on primary | `url` | +| `geo_terraform_states_checksum_failed` | Gauge | 13.3 | Number of terraform states failed to calculate the checksum on primary | `url` | +| `geo_terraform_states_synced` | Gauge | 13.3 | Number of syncable terraform states synced on secondary | `url` | +| `geo_terraform_states_failed` | Gauge | 13.3 | Number of syncable terraform states failed to sync on secondary | `url` | +| `geo_terraform_states_registry` | Gauge | 13.3 | Number of terraform states in the registry | `url` | | `global_search_bulk_cron_queue_size` | Gauge | 12.10 | Number of database records waiting to be synchronized to Elasticsearch | | | `global_search_awaiting_indexing_queue_size` | Gauge | 13.2 | Number of database updates waiting to be synchronized to Elasticsearch while indexing is paused | | -| `package_files_count` | Gauge | 13.0 | Number of package files on primary | `url` | -| `package_files_checksummed_count` | Gauge | 13.0 | Number of package files checksummed on primary | `url` | -| `package_files_checksum_failed_count` | Gauge | 13.0 | Number of package files failed to calculate the checksum on primary ## Database load balancing metrics **(PREMIUM ONLY)** diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md index ba970d2cdb1..e52bcbcdaa0 100644 --- a/doc/api/geo_nodes.md +++ b/doc/api/geo_nodes.md @@ -447,7 +447,13 @@ Example response: "package_files_checksum_failed_count": 0, "package_files_registry_count": 10, "package_files_synced_count": 6, - "package_files_failed_count": 3 + "package_files_failed_count": 3, + "terraform_states_count": 10, + "terraform_states_checksummed_count": 10, + "terraform_states_checksum_failed_count": 0, + "terraform_states_registry_count": 10, + "terraform_states_synced_count": 6, + "terraform_states_failed_count": 3 } ] ``` diff --git a/doc/development/geo/framework.md b/doc/development/geo/framework.md index fc8a1c56d67..75f938cbc59 100644 --- a/doc/development/geo/framework.md +++ b/doc/development/geo/framework.md @@ -520,44 +520,37 @@ Widgets should now be verified by Geo! Metrics are gathered by `Geo::MetricsUpdateWorker`, persisted in `GeoNodeStatus` for display in the UI, and sent to Prometheus. -1. Add fields `widget_count`, `widget_checksummed_count`, - `widget_checksum_failed_count`, `widget_synced_count`, - `widget_failed_count`, and `widget_registry_count` to - `GeoNodeStatus#RESOURCE_STATUS_FIELDS` array in - `ee/app/models/geo_node_status.rb`. -1. Add the same fields to `GeoNodeStatus#PROMETHEUS_METRICS` hash in - `ee/app/models/geo_node_status.rb`. -1. Add the same fields to `Sidekiq metrics` table in - `doc/administration/monitoring/prometheus/gitlab_metrics.md`. +1. Add fields `widgets_count`, `widgets_checksummed_count`, + `widgets_checksum_failed_count`, `widgets_synced_count`, + `widgets_failed_count`, and `widgets_registry_count` to + `GET /geo_nodes/status` example response in + `doc/api/geo_nodes.md`. 1. Add the same fields to `GET /geo_nodes/status` example response in `doc/api/geo_nodes.md`. -1. Add the same fields to `ee/spec/models/geo_node_status_spec.rb` and - `ee/spec/factories/geo_node_statuses.rb`. -1. Set `widget_count` in `GeoNodeStatus#load_data_from_current_node`: +1. Add fields `geo_widgets`, `geo_widgets_checksummed`, + `geo_widgets_checksum_failed`, `geo_widgets_synced`, + `geo_widgets_failed`, and `geo_widgets_registry` to + `Sidekiq metrics` table in + `doc/administration/monitoring/prometheus/gitlab_metrics.md`. +1. Add the following to the parameterized table in + `ee/spec/models/geo_node_status_spec.rb`: ```ruby - self.widget_count = Geo::WidgetReplicator.primary_total_count + Geo::WidgetReplicator | :widget | :geo_widget_registry ``` -1. Add `GeoNodeStatus#load_widgets_data` to set `widget_synced_count`, - `widget_failed_count`, and `widget_registry_count`: +1. Add the following to `spec/factories/widgets.rb`: ```ruby - def load_widget_data - self.widget_synced_count = Geo::WidgetReplicator.synced_count - self.widget_failed_count = Geo::WidgetReplicator.failed_count - self.widget_registry_count = Geo::WidgetReplicator.registry_count + trait(:checksummed) do + with_file + verification_checksum { 'abc' } end - ``` -1. Call `GeoNodeStatus#load_widgets_data` in - `GeoNodeStatus#load_secondary_data`. - -1. Set `widget_checksummed_count` and `widget_checksum_failed_count` in - `GeoNodeStatus#load_verification_data`: - - ```ruby - self.widget_checksummed_count = Geo::WidgetReplicator.checksummed_count self.widget_checksum_failed_count = Geo::WidgetReplicator.checksum_failed_count + trait(:checksum_failure) do + with_file + verification_failure { 'Could not calculate the checksum' } + end ``` Widget replication and verification metrics should now be available in the API, diff --git a/lib/gitlab/quick_actions/extractor.rb b/lib/gitlab/quick_actions/extractor.rb index cd07122ffd9..dd7a27ead01 100644 --- a/lib/gitlab/quick_actions/extractor.rb +++ b/lib/gitlab/quick_actions/extractor.rb @@ -9,6 +9,62 @@ module Gitlab # extractor = Gitlab::QuickActions::Extractor.new([:open, :assign, :labels]) # ``` class Extractor + CODE_REGEX = %r{ + (?<code> + # Code blocks: + # ``` + # Anything, including `/cmd arg` which are ignored by this filter + # ``` + + ^``` + .+? + \n```$ + ) + }mix.freeze + + INLINE_CODE_REGEX = %r{ + (?<inline_code> + # Inline code on separate rows: + # ` + # Anything, including `/cmd arg` which are ignored by this filter + # ` + + ^.*`\n* + .+? + \n*`$ + ) + }mix.freeze + + HTML_BLOCK_REGEX = %r{ + (?<html> + # HTML block: + # <tag> + # Anything, including `/cmd arg` which are ignored by this filter + # </tag> + + ^<[^>]+?>\n + .+? + \n<\/[^>]+?>$ + ) + }mix.freeze + + QUOTE_BLOCK_REGEX = %r{ + (?<html> + # Quote block: + # >>> + # Anything, including `/cmd arg` which are ignored by this filter + # >>> + + ^>>> + .+? + \n>>>$ + ) + }mix.freeze + + EXCLUSION_REGEX = %r{ + #{CODE_REGEX} | #{INLINE_CODE_REGEX} | #{HTML_BLOCK_REGEX} | #{QUOTE_BLOCK_REGEX} + }mix.freeze + attr_reader :command_definitions def initialize(command_definitions) @@ -35,9 +91,7 @@ module Gitlab def extract_commands(content, only: nil) return [content, []] unless content - content, commands = perform_regex(content, only: only) - - perform_substitutions(content, commands) + perform_regex(content, only: only) end # Encloses quick action commands into code span markdown @@ -55,13 +109,19 @@ module Gitlab private def perform_regex(content, only: nil, redact: false) - commands = [] - content = content.dup + names = command_names(limit_to_commands: only).map(&:to_s) + sub_names = substitution_names.map(&:to_s) + commands = [] + content = content.dup content.delete!("\r") - names = command_names(limit_to_commands: only).map(&:to_s) - content.gsub!(commands_regex(names: names)) do - command, output = process_commands($~, redact) + content.gsub!(commands_regex(names: names, sub_names: sub_names)) do + command, output = if $~[:substitution] + process_substitutions($~) + else + process_commands($~, redact) + end + commands << command output end @@ -86,6 +146,21 @@ module Gitlab [command, output] end + def process_substitutions(matched_text) + output = matched_text[0] + command = [] + + if matched_text[:substitution] + cmd = matched_text[:substitution].downcase + command = [cmd, matched_text[:arg]].reject(&:blank?) + + substitution = substitution_definitions.find { |definition| definition.all_names.include?(cmd.to_sym) } + output = substitution.perform_substitution(self, output) if substitution + end + + [command, output] + end + # Builds a regular expression to match known commands. # First match group captures the command name and # second match group captures its arguments. @@ -93,51 +168,9 @@ module Gitlab # It looks something like: # # /^\/(?<cmd>close|reopen|...)(?:( |$))(?<arg>[^\/\n]*)(?:\n|$)/ - def commands_regex(names:) + def commands_regex(names:, sub_names:) @commands_regex[names] ||= %r{ - (?<code> - # Code blocks: - # ``` - # Anything, including `/cmd arg` which are ignored by this filter - # ``` - - ^``` - .+? - \n```$ - ) - | - (?<inline_code> - # Inline code on separate rows: - # ` - # Anything, including `/cmd arg` which are ignored by this filter - # ` - - ^.*`\n* - .+? - \n*`$ - ) - | - (?<html> - # HTML block: - # <tag> - # Anything, including `/cmd arg` which are ignored by this filter - # </tag> - - ^<[^>]+?>\n - .+? - \n<\/[^>]+?>$ - ) - | - (?<html> - # Quote block: - # >>> - # Anything, including `/cmd arg` which are ignored by this filter - # >>> - - ^>>> - .+? - \n>>>$ - ) + #{EXCLUSION_REGEX} | (?: # Command not in a blockquote, blockcode, or HTML tag: @@ -151,32 +184,19 @@ module Gitlab )? (?:\s*\n|$) ) - }mix - end - - def perform_substitutions(content, commands) - return unless content - - substitution_definitions = self.command_definitions.select do |definition| - definition.is_a?(Gitlab::QuickActions::SubstitutionDefinition) - end - - substitution_definitions.each do |substitution| - regex = commands_regex(names: substitution.all_names) - content = content.gsub(regex) do |text| - if $~[:cmd] - command = [substitution.name.to_s] - command << $~[:arg] if $~[:arg].present? - commands << command - - substitution.perform_substitution(self, text) - else - text - end - end - end + | + (?: + # Substitution not in a blockquote, blockcode, or HTML tag: - [content, commands] + ^\/ + (?<substitution>#{Regexp.new(Regexp.union(sub_names).source, Regexp::IGNORECASE)}) + (?: + [ ] + (?<arg>[^\n]*) + )? + (?:\s*\n|$) + ) + }mix end def command_names(limit_to_commands:) @@ -190,6 +210,17 @@ module Gitlab command.all_names end.compact end + + def substitution_names + substitution_definitions.flat_map { |command| command.all_names } + .compact + end + + def substitution_definitions + @substition_definitions ||= command_definitions.select do |command| + command.is_a?(Gitlab::QuickActions::SubstitutionDefinition) + end + end end end end diff --git a/lib/gitlab/quick_actions/substitution_definition.rb b/lib/gitlab/quick_actions/substitution_definition.rb index cd4d202e8d0..24b4e3c62b3 100644 --- a/lib/gitlab/quick_actions/substitution_definition.rb +++ b/lib/gitlab/quick_actions/substitution_definition.rb @@ -9,10 +9,6 @@ module Gitlab true end - def match(content) - content.match %r{^/#{all_names.join('|')}(?![\S]) ?(.*)$} - end - def perform_substitution(context, content) return unless content diff --git a/spec/factories/terraform/state.rb b/spec/factories/terraform/state.rb index 46784581180..94769290ac5 100644 --- a/spec/factories/terraform/state.rb +++ b/spec/factories/terraform/state.rb @@ -15,5 +15,15 @@ FactoryBot.define do locked_at { Time.current } locked_by_user { create(:user) } end + + trait(:checksummed) do + with_file + verification_checksum { 'abc' } + end + + trait(:checksum_failure) do + with_file + verification_failure { 'Could not calculate the checksum' } + end end end diff --git a/spec/fixtures/api/schemas/entities/issue_sidebar.json b/spec/fixtures/api/schemas/entities/issue_sidebar.json index 9161c992a97..b2c8244ce69 100644 --- a/spec/fixtures/api/schemas/entities/issue_sidebar.json +++ b/spec/fixtures/api/schemas/entities/issue_sidebar.json @@ -43,6 +43,7 @@ "toggle_subscription_path": { "type": "string" }, "move_issue_path": { "type": "string" }, "projects_autocomplete_path": { "type": "string" }, - "supports_time_tracking": { "type": "boolean" } + "supports_time_tracking": { "type": "boolean" }, + "supports_milestone": { "type": "boolean" } } } diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json index c20d07e99f7..633203aed28 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json +++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json @@ -52,6 +52,7 @@ "toggle_subscription_path": { "type": "string" }, "move_issue_path": { "type": "string" }, "projects_autocomplete_path": { "type": "string" }, - "supports_time_tracking": { "type": "boolean" } + "supports_time_tracking": { "type": "boolean" }, + "supports_milestone": { "type": "boolean" } } } diff --git a/spec/frontend/alert_management/components/alert_management_table_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js index 0bef4e6bd03..dc5a38bbf26 100644 --- a/spec/frontend/alert_management/components/alert_management_table_spec.js +++ b/spec/frontend/alert_management/components/alert_management_table_spec.js @@ -95,13 +95,10 @@ describe('AlertManagementTable', () => { }); } - beforeEach(() => { - mountComponent({ data: { alerts: mockAlerts, alertsCount } }); - }); - afterEach(() => { if (wrapper) { wrapper.destroy(); + wrapper = null; } }); diff --git a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap index 44220bdef64..a24069ac47f 100644 --- a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap +++ b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap @@ -66,7 +66,9 @@ exports[`Project remove modal initialized matches the snapshot 1`] = ` </p> <p> - <code> + <code + class="ws-pre-wrap" + > foo </code> </p> diff --git a/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap b/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap index a43acc8c002..0ed48e7c311 100644 --- a/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap +++ b/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap @@ -55,7 +55,9 @@ exports[`Project remove modal intialized matches the snapshot 1`] = ` </p> <p> - <code> + <code + class="ws-pre-wrap" + > foo </code> </p> diff --git a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb index b28ac49b4ea..8a4e9ab8bb7 100644 --- a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb +++ b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb @@ -46,24 +46,4 @@ EOF end end end - - describe '#match' do - it 'checks the content for the command' do - expect(subject.match(content)).to be_truthy - end - - it 'returns the match data' do - data = subject.match(content) - expect(data).to be_a(MatchData) - expect(data[1]).to eq('I like this stuff') - end - - it 'is nil if content does not have the command' do - expect(subject.match('blah')).to be_falsey - end - - it 'is nil if content contains the command as prefix' do - expect(subject.match('/sub_namex')).to be_falsey - end - end end diff --git a/spec/models/concerns/milestoneable_spec.rb b/spec/models/concerns/milestoneable_spec.rb index 3dd6f1450c7..f5b82e42ad4 100644 --- a/spec/models/concerns/milestoneable_spec.rb +++ b/spec/models/concerns/milestoneable_spec.rb @@ -100,6 +100,14 @@ RSpec.describe Milestoneable do expect(merge_request.supports_milestone?).to be_truthy end end + + context "for incidents" do + let(:incident) { build(:incident) } + + it 'returns false' do + expect(incident.supports_milestone?).to be_falsy + end + end end describe 'release scopes' do |