Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
da50206243
commit
2947190073
25 changed files with 287 additions and 199 deletions
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -51,7 +51,7 @@ module Milestoneable
|
|||
# Overridden on EE module
|
||||
#
|
||||
def supports_milestone?
|
||||
respond_to?(:milestone_id)
|
||||
respond_to?(:milestone_id) && !incident?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@ class IssuableSidebarBasicEntity < Grape::Entity
|
|||
end
|
||||
|
||||
expose :supports_time_tracking?, as: :supports_time_tracking
|
||||
expose :supports_milestone?, as: :supports_milestone
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -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')
|
||||
%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 }
|
||||
.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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update issue edit button to gl-button
|
||||
merge_request: 40438
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: "Remove milestone and iteration feature from Incidents sidebar"
|
||||
merge_request: 40283
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix delete confirm message not displaying trailing spaces
|
||||
merge_request: 40549
|
||||
author:
|
||||
type: fixed
|
5
changelogs/unreleased/bw-large-description-timeout.yml
Normal file
5
changelogs/unreleased/bw-large-description-timeout.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Increase performance of rendering large amounts of markdown data
|
||||
merge_request: 40448
|
||||
author:
|
||||
type: performance
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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)**
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,34 +184,21 @@ module Gitlab
|
|||
)?
|
||||
(?:\s*\n|$)
|
||||
)
|
||||
|
|
||||
(?:
|
||||
# Substitution not in a blockquote, blockcode, or HTML tag:
|
||||
|
||||
^\/
|
||||
(?<substitution>#{Regexp.new(Regexp.union(sub_names).source, Regexp::IGNORECASE)})
|
||||
(?:
|
||||
[ ]
|
||||
(?<arg>[^\n]*)
|
||||
)?
|
||||
(?:\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
|
||||
|
||||
[content, commands]
|
||||
end
|
||||
|
||||
def command_names(limit_to_commands:)
|
||||
command_definitions.flat_map do |command|
|
||||
next if command.noop?
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,13 +95,10 @@ describe('AlertManagementTable', () => {
|
|||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mountComponent({ data: { alerts: mockAlerts, alertsCount } });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -66,7 +66,9 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
|
|||
</p>
|
||||
|
||||
<p>
|
||||
<code>
|
||||
<code
|
||||
class="ws-pre-wrap"
|
||||
>
|
||||
foo
|
||||
</code>
|
||||
</p>
|
||||
|
|
|
@ -55,7 +55,9 @@ exports[`Project remove modal intialized matches the snapshot 1`] = `
|
|||
</p>
|
||||
|
||||
<p>
|
||||
<code>
|
||||
<code
|
||||
class="ws-pre-wrap"
|
||||
>
|
||||
foo
|
||||
</code>
|
||||
</p>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue