Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-07-10 18:09:45 +00:00
parent 1782886ed2
commit 0bc6d00165
55 changed files with 688 additions and 300 deletions

View File

@ -13,6 +13,7 @@
/doc/development/ @marcia @mjang1
/doc/development/documentation/ @mikelewis
/doc/ci @marcel.amirault @sselhorn
/doc/operations @aqualls @eread
/doc/user/clusters @aqualls
/doc/user/infrastructure @aqualls
/doc/user/project/clusters @aqualls

View File

@ -244,7 +244,9 @@ gem 'slack-messenger', '~> 2.3.3'
gem 'hangouts-chat', '~> 0.0.5'
# Asana integration
gem 'asana', '~> 0.9'
# asana 0.10.1 needs faraday 1.0
# https://gitlab.com/gitlab-org/gitlab/-/issues/224296
gem 'asana', '0.10.0'
# FogBugz integration
gem 'ruby-fogbugz', '~> 0.2.1'

View File

@ -76,7 +76,7 @@ GEM
apollo_upload_server (2.0.0.beta.3)
graphql (>= 1.8)
rails (>= 4.2)
asana (0.9.3)
asana (0.10.0)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
faraday_middleware-multi_json (~> 0.0)
@ -304,7 +304,7 @@ GEM
multipart-post (>= 1.2, < 3)
faraday-http-cache (2.0.0)
faraday (~> 0.8)
faraday_middleware (0.12.2)
faraday_middleware (0.14.0)
faraday (>= 0.7.4, < 1.0)
faraday_middleware-aws-signers-v4 (0.1.7)
aws-sdk-resources (~> 2)
@ -1175,7 +1175,7 @@ DEPENDENCIES
addressable (~> 2.7)
akismet (~> 3.0)
apollo_upload_server (~> 2.0.0.beta3)
asana (~> 0.9)
asana (= 0.10.0)
asciidoctor (~> 2.0.10)
asciidoctor-include-ext (~> 0.3.1)
asciidoctor-plantuml (~> 0.0.12)

View File

@ -375,7 +375,7 @@ export const fetchDashboardValidationWarnings = ({ state, dispatch }) => {
})
.then(resp => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard)
.then(({ schemaValidationWarnings } = {}) => {
const hasWarnings = schemaValidationWarnings?.length !== 0;
const hasWarnings = schemaValidationWarnings && schemaValidationWarnings.length !== 0;
/**
* The payload of the dispatch is a boolean, because at the moment a standard
* warning message is shown instead of the warnings the BE returns

View File

@ -55,13 +55,22 @@ export default {
<template>
<div class="d-inline-block">
<button v-gl-modal="modalId" type="button" class="btn btn-danger">{{ __('Delete') }}</button>
<button
v-gl-modal="modalId"
type="button"
class="btn btn-danger"
data-qa-selector="delete_button"
>
{{ __('Delete') }}
</button>
<gl-modal
:title="title"
:ok-title="s__('WikiPageConfirmDelete|Delete page')"
:action-primary="{
text: s__('WikiPageConfirmDelete|Delete page'),
attributes: { variant: 'danger', 'data-qa-selector': 'confirm_deletion_button' },
}"
:modal-id="modalId"
title-tag="h4"
ok-variant="danger"
@ok="onSubmit"
>
{{ message }}

View File

@ -8,6 +8,7 @@ module SnippetsActions
include PaginatedCollection
include Gitlab::NoteableMetadata
include Snippets::SendBlob
include SnippetsSort
included do
skip_before_action :verify_authenticity_token,

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module SnippetsSort
extend ActiveSupport::Concern
def sort_param
params[:sort].presence || 'updated_desc'
end
end

View File

@ -3,6 +3,7 @@
class Dashboard::SnippetsController < Dashboard::ApplicationController
include PaginatedCollection
include Gitlab::NoteableMetadata
include SnippetsSort
skip_cross_project_access_check :index
@ -11,7 +12,7 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController
.new(current_user, author: current_user)
.execute
@snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope])
@snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope], sort: sort_param)
.execute
.page(params[:page])
.inc_author

View File

@ -19,7 +19,7 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController
.new(current_user, project: @project)
.execute
@snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope])
@snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope], sort: sort_param)
.execute
.page(params[:page])
.inc_author

View File

@ -21,7 +21,7 @@ class SnippetsController < Snippets::ApplicationController
if params[:username].present?
@user = UserFinder.new(params[:username]).find_by_username!
@snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope])
@snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope], sort: sort_param)
.execute
.page(params[:page])
.inc_author

View File

@ -43,7 +43,7 @@ class SnippetsFinder < UnionFinder
include Gitlab::Utils::StrongMemoize
attr_accessor :current_user, :params
delegate :explore, :only_personal, :only_project, :scope, to: :params
delegate :explore, :only_personal, :only_project, :scope, :sort, to: :params
def initialize(current_user = nil, params = {})
@current_user = current_user
@ -69,7 +69,9 @@ class SnippetsFinder < UnionFinder
items = init_collection
items = by_ids(items)
items.with_optional_visibility(visibility_from_scope).fresh
items = items.with_optional_visibility(visibility_from_scope)
items.order_by(sort_param)
end
private
@ -202,6 +204,10 @@ class SnippetsFinder < UnionFinder
params[:project].is_a?(Project) ? params[:project] : Project.find_by_id(params[:project])
end
end
def sort_param
sort.presence || 'id_desc'
end
end
SnippetsFinder.prepend_if_ee('EE::SnippetsFinder')

View File

@ -10,6 +10,10 @@ class MergeRequestPolicy < IssuablePolicy
# it would not be safe to prevent :create_note there, since
# note permissions are shared, and this would apply too broadly.
rule { ~can?(:read_merge_request) }.prevent :create_note
rule { can?(:update_merge_request) }.policy do
enable :approve_merge_request
end
end
MergeRequestPolicy.prepend_if_ee('EE::MergeRequestPolicy')

View File

@ -2,8 +2,6 @@
module Members
class DestroyService < Members::BaseService
WAIT_FOR_DELETE = 1.hour
def execute(member, skip_authorization: false, skip_subresources: false, unassign_issuables: false)
raise Gitlab::Access::AccessDeniedError unless skip_authorization || can_destroy_member?(member)
@ -72,7 +70,7 @@ module Members
source_type = member.is_a?(GroupMember) ? 'Group' : 'Project'
member.run_after_commit do
MembersDestroyer::UnassignIssuablesWorker.perform_in(WAIT_FOR_DELETE, member.user_id, member.source_id, source_type)
MembersDestroyer::UnassignIssuablesWorker.perform_async(member.user_id, member.source_id, source_type)
end
end
end

View File

@ -3,19 +3,27 @@
module MergeRequests
class ApprovalService < MergeRequests::BaseService
def execute(merge_request)
return unless can_be_approved?(merge_request)
approval = merge_request.approvals.new(user: current_user)
return unless save_approval(approval)
return success unless save_approval(approval)
reset_approvals_cache(merge_request)
create_event(merge_request)
create_approval_note(merge_request)
mark_pending_todos_as_done(merge_request)
execute_approval_hooks(merge_request, current_user)
success
end
private
def can_be_approved?(merge_request)
current_user.can?(:approve_merge_request, merge_request)
end
def reset_approvals_cache(merge_request)
merge_request.approvals.reset
end

View File

@ -4,6 +4,8 @@ module MergeRequests
class RemoveApprovalService < MergeRequests::BaseService
# rubocop: disable CodeReuse/ActiveRecord
def execute(merge_request)
return unless approved_by_user?(merge_request)
# paranoid protection against running wrong deletes
return unless merge_request.id && current_user.id
@ -15,11 +17,17 @@ module MergeRequests
reset_approvals_cache(merge_request)
create_note(merge_request)
end
success
end
# rubocop: enable CodeReuse/ActiveRecord
private
def approved_by_user?(merge_request)
merge_request.approved_by_users.include?(current_user)
end
def reset_approvals_cache(merge_request)
merge_request.approvals.reset
end

View File

@ -1,4 +1,6 @@
#{sanitize_name(@updated_by_user.name)} pushed new commits to merge request #{merge_request_reference_link(@merge_request)}
#{sanitize_name(@updated_by_user.name)} pushed new commits to merge request #{@merge_request.to_reference}
Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
\
- if @existing_commits.any?
- count = @existing_commits.size

View File

@ -0,0 +1,5 @@
---
title: Move plan stage usage activity to CE
merge_request: 36087
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Fix dashboard schema validation issue
merge_request: 36577
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Reorder snippets in lists using `updated_at` column
merge_request: 34393
author: Dibyadarshi Dash @ddash2
type: changed

View File

@ -0,0 +1,5 @@
---
title: Propagate DS_JAVA_VERSION for dependency scanning
merge_request: 36448
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Remove HTML link from plain text mail
merge_request: 36301
author:
type: fixed

View File

@ -138,7 +138,7 @@ The following documentation relates to the DevOps **Plan** stage:
Consolidate source code into a single [distributed version control system](https://en.wikipedia.org/wiki/Distributed_version_control)
thats easily managed and controlled without disrupting your workflow.
GitLabs Git repositories come complete with branching tools and access
GitLab repositories come complete with branching tools and access
controls, providing a scalable, single source of truth for collaborating
on projects and code.

View File

@ -109,6 +109,24 @@ For example, to enable the [`:junit_pipeline_view`](../ci/junit_test_reports.md#
Feature.enable(:junit_pipeline_view, Project.find(1234))
```
`Feature.enable` and `Feature.disable` always return `nil`, this is not an indication that the command failed:
```ruby
irb(main):001:0> Feature.enable(:release_evidence_collection)
=> nil
```
To check if a flag is enabled or disabled you can use `Feature.enabled?` or `Feature.disabled?`:
```ruby
Feature.enable(:release_evidence_collection)
=> nil
Feature.enabled?(:release_evidence_collection)
=> true
Feature.disabled?(:release_evidence_collection)
=> false
```
When the feature is ready, GitLab will remove the feature flag, the option for
enabling and disabling it will no longer exist, and the feature will become
available in all instances.

View File

@ -927,7 +927,7 @@ larger images, or images that take longer than 5 minutes to push, users may
encounter this error.
Administrators can increase the token duration in **Admin area > Settings >
Container Registry > Authorization token duration (minutes)**.
CI/CD > Container Registry > Authorization token duration (minutes)**.
### AWS S3 with the GitLab registry error when pushing large images

View File

@ -4,6 +4,44 @@
We use ESLint to encapsulate and enforce frontend code standards. Our configuration may be found in the [`gitlab-eslint-config`](https://gitlab.com/gitlab-org/gitlab-eslint-config) project.
### Yarn Script
This section describes yarn scripts that are available to validate and apply automatic fixes to files using ESLint.
To check all currently staged files (based on `git diff`) with ESLint, run the following script:
```shell
yarn eslint-staged
```
_A list of problems found will be logged to the console._
To apply automatic ESLint fixes to all currently staged files (based on `git diff`), run the following script:
```shell
yarn eslint-staged-fix
```
_If manual changes are required, a list of changes will be sent to the console._
To check **all** files in the repository with ESLint, run the following script:
```shell
yarn eslint
```
_A list of problems found will be logged to the console._
To apply automatic ESLint fixes to **all** files in the repository, run the following script:
```shell
yarn eslint-fix
```
_If manual changes are required, a list of changes will be sent to the console._
**Caution:** Limit use to global rule updates. Otherwise, the changes can lead to huge Merge Requests.
### Disabling ESLint in new files
Do not disable ESLint when creating new files. Existing files may have existing rules

View File

@ -681,10 +681,19 @@ appear to be associated to any of the services running, since they all appear to
| `ldap_group_sync_enabled` | `usage_activity_by_stage` | `manage` | | EE | |
| `ldap_admin_sync_enabled` | `usage_activity_by_stage` | `manage` | | EE | |
| `group_saml_enabled` | `usage_activity_by_stage` | `manage` | | EE | |
| `projects_jira_dvcs_server_active` | `usage_activity_by_stage` | `plan` | | | |
| `service_desk_enabled_projects` | `usage_activity_by_stage` | `plan` | | | |
| `service_desk_issues` | `usage_activity_by_stage` | `plan` | | | |
| `todos: 0` | `usage_activity_by_stage` | `plan` | | | |
| `issues` | `usage_activity_by_stage` | `plan` | | CE+EE | |
| `notes` | `usage_activity_by_stage` | `plan` | | CE+EE | |
| `projects` | `usage_activity_by_stage` | `plan` | | CE+EE | |
| `todos` | `usage_activity_by_stage` | `plan` | | CE+EE | |
| `assignee_lists` | `usage_activity_by_stage` | `plan` | | EE | |
| `epics` | `usage_activity_by_stage` | `plan` | | EE | |
| `label_lists` | `usage_activity_by_stage` | `plan` | | EE | |
| `milestone_lists` | `usage_activity_by_stage` | `plan` | | EE | |
| `projects_jira_active` | `usage_activity_by_stage` | `plan` | | EE | |
| `projects_jira_dvcs_server_active` | `usage_activity_by_stage` | `plan` | | EE | |
| `projects_jira_dvcs_server_active` | `usage_activity_by_stage` | `plan` | | EE | |
| `service_desk_enabled_projects` | `usage_activity_by_stage` | `plan` | | EE | |
| `service_desk_issues` | `usage_activity_by_stage` | `plan` | | EE | |
| `deployments` | `usage_activity_by_stage` | `release` | | CE+EE | Total deployments |
| `failed_deployments` | `usage_activity_by_stage` | `release` | | CE+EE | Total failed deployments |
| `projects_mirrored_with_pipelines_enabled` | `usage_activity_by_stage` | `release` | | EE | Projects with repository mirroring enabled |

View File

@ -11,6 +11,7 @@ your applications:
- Collect [Prometheus metrics](../user/project/integrations/prometheus_library/index.md).
- Deploy to different [environments](../ci/environments/index.md).
- Manage your [Alerts](../user/project/operations/alert_management.md) and [Incidents](../user/incident_management/index.md).
- Connect your project to a [Kubernetes cluster](../user/project/clusters/index.md).
- Manage your infrastructure with [Infrastructure as Code](../user/infrastructure/index.md) approaches.
- Discover and view errors generated by your applications with [Error Tracking](../user/project/operations/error_tracking.md).

View File

@ -0,0 +1,261 @@
---
stage: Monitor
group: APM
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Panel types for dashboards
The below panel types are supported in monitoring dashboards.
## Area or Line Chart
To add an area chart panel type to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group Title'
panels:
- type: area-chart # or line-chart
title: 'Area Chart Title'
y_label: "Y-Axis"
y_axis:
format: number
precision: 0
metrics:
- id: area_http_requests_total
query_range: 'http_requests_total'
label: "Instance: {{instance}}, Method: {{method}}"
unit: "count"
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| type | string | no | Type of panel to be rendered. Optional for area panel types |
| query_range | string | required | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
![area panel chart](../../../user/project/integrations/img/prometheus_dashboard_area_panel_type_v12_8.png)
Starting in [version 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/202696), the y-axis values will automatically scale according to the data. Previously, it always started from 0.
## Anomaly chart
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16530) in GitLab 12.5.
To add an anomaly chart panel type to a dashboard, add a panel with *exactly* 3 metrics.
The first metric represents the current state, and the second and third metrics represent the upper and lower limit respectively:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group Title'
panels:
- type: anomaly-chart
title: "Chart Title"
y_label: "Y-Axis"
metrics:
- id: anomaly_requests_normal
query_range: 'http_requests_total'
label: "# of Requests"
unit: "count"
metrics:
- id: anomaly_requests_upper_limit
query_range: 10000
label: "Max # of requests"
unit: "count"
metrics:
- id: anomaly_requests_lower_limit
query_range: 2000
label: "Min # of requests"
unit: "count"
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| type | string | required | Must be `anomaly-chart` for anomaly panel types |
| query_range | yes | required | For anomaly panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) in every metric. |
![anomaly panel type](../../../user/project/integrations/img/prometheus_dashboard_anomaly_panel_type.png)
## Bar chart
To add a bar chart to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group title'
panels:
- type: bar
title: "Http Handlers"
x_label: 'Response Size'
y_axis:
name: "Handlers"
metrics:
- id: prometheus_http_response_size_bytes_bucket
query_range: "sum(increase(prometheus_http_response_size_bytes_bucket[1d])) by (handler)"
unit: 'Bytes'
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| `type` | string | yes | Type of panel to be rendered. For bar chart types, set to `bar` |
| `query_range` | yes | yes | For bar chart, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries)
![bar chart panel type](../../../user/project/integrations/img/prometheus_dashboard_bar_chart_panel_type_v12.10.png)
## Column chart
To add a column panel type to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group title'
panels:
- title: "Column"
type: "column"
metrics:
- id: 1024_memory
query: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
unit: MB
label: "Memory Usage"
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| type | string | yes | Type of panel to be rendered. For column panel types, set to `column` |
| query_range | yes | yes | For column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
![anomaly panel type](../../../user/project/integrations/img/prometheus_dashboard_column_panel_type.png)
## Stacked column
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30583) in GitLab 12.8.
To add a stacked column panel type to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard title'
priority: 1
panel_groups:
- group: 'Group Title'
priority: 5
panels:
- type: 'stacked-column'
title: "Stacked column"
y_label: "y label"
x_label: 'x label'
metrics:
- id: memory_1
query_range: 'memory_query'
label: "memory query 1"
unit: "count"
series_name: 'group 1'
- id: memory_2
query_range: 'memory_query_2'
label: "memory query 2"
unit: "count"
series_name: 'group 2'
```
![stacked column panel type](../../../user/project/integrations/img/prometheus_dashboard_stacked_column_panel_type_v12_8.png)
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| `type` | string | yes | Type of panel to be rendered. For stacked column panel types, set to `stacked-column` |
| `query_range` | yes | yes | For stacked column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
## Single Stat
To add a single stat panel type to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group Title'
panels:
- title: "Single Stat"
type: "single-stat"
metrics:
- id: 10
query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
unit: MB
label: "Total"
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| type | string | yes | Type of panel to be rendered. For single stat panel types, set to `single-stat` |
| query | string | yes | For single stat panel types, you must use an [instant query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) |
![single stat panel type](../../../user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.png)
## Percentile based results
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201946) in GitLab 12.8.
Query results sometimes need to be represented as a percentage value out of 100. You can use the `max_value` property at the root of the panel definition:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group Title'
panels:
- title: "Single Stat"
type: "single-stat"
max_value: 100
metrics:
- id: 10
query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
unit: '%'
label: "Total"
```
For example, if you have a query value of `53.6`, adding `%` as the unit results in a single stat value of `53.6%`, but if the maximum expected value of the query is `120`, the value would be `44.6%`. Adding the `max_value` causes the correct percentage value to display.
## Heatmaps
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30581) in GitLab 12.5.
To add a heatmap panel type to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group Title'
panels:
- title: "Heatmap"
type: "heatmap"
metrics:
- id: 10
query: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)'
unit: req/sec
label: "Status code"
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| type | string | yes | Type of panel to be rendered. For heatmap panel types, set to `heatmap` |
| query_range | yes | yes | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
![heatmap panel type](../../../user/project/integrations/img/heatmap_panel_type.png)
CAUTION: **Warning:**
When a query returns too many data points, the heatmap data bucket dimensions tend downwards to 0, making the chart's data invisible, as shown in the image below. To fix this problem, limit the amount of data returned by changing the time range filter on the metrics dashboard UI, or adding the **step** property to your dashboard's YAML file.
![heatmap chart_too_much_data](../../../user/project/integrations/img/heatmap_chart_too_much_data_v_13_2.png)

View File

@ -59,7 +59,7 @@ Panels in a panel group are laid out in rows consisting of two panels per row. A
| `title` | string | yes | Heading for the panel. |
| `y_label` | string | no, but highly encouraged | Y-Axis label for the panel. |
| `y_axis` | string | no | Y-Axis configuration for the panel. |
| `max_value` | number | no | Denominator value used for calculating [percentile based results](../../../user/project/integrations/prometheus.md#percentile-based-results) |
| `max_value` | number | no | Denominator value used for calculating [percentile based results](panel_types.md#percentile-based-results) |
| `weight` | number | no, defaults to order in file | Order to appear within the grouping. Lower number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. |
| `metrics` | array | yes | The metrics which should be displayed in the panel. Any number of metrics can be displayed when `type` is `area-chart` or `line-chart`, whereas only 3 can be displayed when `type` is `anomaly-chart`. |
| `links` | array | no | Add links to display on the chart's [context menu](index.md#chart-context-menu). |

View File

@ -120,6 +120,7 @@ The following table depicts the various user permission levels in a project.
| Rewrite/remove Git tags | | | ✓ | ✓ | ✓ |
| Manage Feature Flags **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ |
| Run CI/CD pipeline against a protected branch | | | ✓ (*5*) | ✓ | ✓ |
| Use environment terminals | | | | ✓ | ✓ |
| Run Web IDE's Interactive Web Terminals **(ULTIMATE ONLY)** | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |

View File

@ -353,262 +353,6 @@ When **Metrics Dashboard YAML definition is invalid** at least one of the follow
Metrics Dashboard YAML definition validation information is also available as a [GraphQL API field](../../../api/graphql/reference/index.md#metricsdashboard)
#### Panel types for dashboards
The below panel types are supported in monitoring dashboards.
##### Area or Line Chart
To add an area chart panel type to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group Title'
panels:
- type: area-chart # or line-chart
title: 'Area Chart Title'
y_label: "Y-Axis"
y_axis:
format: number
precision: 0
metrics:
- id: area_http_requests_total
query_range: 'http_requests_total'
label: "Instance: {{instance}}, Method: {{method}}"
unit: "count"
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| type | string | no | Type of panel to be rendered. Optional for area panel types |
| query_range | string | required | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
![area panel chart](img/prometheus_dashboard_area_panel_type_v12_8.png)
Starting in [version 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/202696), the y-axis values will automatically scale according to the data. Previously, it always started from 0.
##### Anomaly chart
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16530) in GitLab 12.5.
To add an anomaly chart panel type to a dashboard, add a panel with *exactly* 3 metrics.
The first metric represents the current state, and the second and third metrics represent the upper and lower limit respectively:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group Title'
panels:
- type: anomaly-chart
title: "Chart Title"
y_label: "Y-Axis"
metrics:
- id: anomaly_requests_normal
query_range: 'http_requests_total'
label: "# of Requests"
unit: "count"
metrics:
- id: anomaly_requests_upper_limit
query_range: 10000
label: "Max # of requests"
unit: "count"
metrics:
- id: anomaly_requests_lower_limit
query_range: 2000
label: "Min # of requests"
unit: "count"
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| type | string | required | Must be `anomaly-chart` for anomaly panel types |
| query_range | yes | required | For anomaly panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) in every metric. |
![anomaly panel type](img/prometheus_dashboard_anomaly_panel_type.png)
##### Bar chart
To add a bar chart to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group title'
panels:
- type: bar
title: "Http Handlers"
x_label: 'Response Size'
y_axis:
name: "Handlers"
metrics:
- id: prometheus_http_response_size_bytes_bucket
query_range: "sum(increase(prometheus_http_response_size_bytes_bucket[1d])) by (handler)"
unit: 'Bytes'
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| `type` | string | yes | Type of panel to be rendered. For bar chart types, set to `bar` |
| `query_range` | yes | yes | For bar chart, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries)
![bar chart panel type](img/prometheus_dashboard_bar_chart_panel_type_v12.10.png)
##### Column chart
To add a column panel type to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group title'
panels:
- title: "Column"
type: "column"
metrics:
- id: 1024_memory
query: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
unit: MB
label: "Memory Usage"
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| type | string | yes | Type of panel to be rendered. For column panel types, set to `column` |
| query_range | yes | yes | For column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
![anomaly panel type](img/prometheus_dashboard_column_panel_type.png)
##### Stacked column
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30583) in GitLab 12.8.
To add a stacked column panel type to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard title'
priority: 1
panel_groups:
- group: 'Group Title'
priority: 5
panels:
- type: 'stacked-column'
title: "Stacked column"
y_label: "y label"
x_label: 'x label'
metrics:
- id: memory_1
query_range: 'memory_query'
label: "memory query 1"
unit: "count"
series_name: 'group 1'
- id: memory_2
query_range: 'memory_query_2'
label: "memory query 2"
unit: "count"
series_name: 'group 2'
```
![stacked column panel type](img/prometheus_dashboard_stacked_column_panel_type_v12_8.png)
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| `type` | string | yes | Type of panel to be rendered. For stacked column panel types, set to `stacked-column` |
| `query_range` | yes | yes | For stacked column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
##### Single Stat
To add a single stat panel type to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group Title'
panels:
- title: "Single Stat"
type: "single-stat"
metrics:
- id: 10
query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
unit: MB
label: "Total"
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| type | string | yes | Type of panel to be rendered. For single stat panel types, set to `single-stat` |
| query | string | yes | For single stat panel types, you must use an [instant query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) |
![single stat panel type](img/prometheus_dashboard_single_stat_panel_type.png)
###### Percentile based results
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201946) in GitLab 12.8.
Query results sometimes need to be represented as a percentage value out of 100. You can use the `max_value` property at the root of the panel definition:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group Title'
panels:
- title: "Single Stat"
type: "single-stat"
max_value: 100
metrics:
- id: 10
query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
unit: '%'
label: "Total"
```
For example, if you have a query value of `53.6`, adding `%` as the unit results in a single stat value of `53.6%`, but if the maximum expected value of the query is `120`, the value would be `44.6%`. Adding the `max_value` causes the correct percentage value to display.
##### Heatmaps
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30581) in GitLab 12.5.
To add a heatmap panel type to a dashboard, look at the following sample dashboard file:
```yaml
dashboard: 'Dashboard Title'
panel_groups:
- group: 'Group Title'
panels:
- title: "Heatmap"
type: "heatmap"
metrics:
- id: 10
query: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)'
unit: req/sec
label: "Status code"
```
Note the following properties:
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| type | string | yes | Type of panel to be rendered. For heatmap panel types, set to `heatmap` |
| query_range | yes | yes | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
![heatmap panel type](img/heatmap_panel_type.png)
CAUTION: **Warning:**
When a query returns too many data points, the heatmap data bucket dimensions tend downwards to 0, making the chart's data invisible, as shown in the image below. To fix this problem, limit the amount of data returned by changing the time range filter on the metrics dashboard UI, or adding the **step** property to your dashboard's YAML file.
![heatmap chart_too_much_data](img/heatmap_chart_too_much_data_v_13_2.png)
### Templating variables for metrics dashboards
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214539) in GitLab 13.0.

View File

@ -52,6 +52,7 @@ dependency_scanning:
DS_PYTHON_VERSION \
DS_PIP_VERSION \
DS_PIP_DEPENDENCY_PATH \
DS_JAVA_VERSION \
GEMNASIUM_DB_LOCAL_PATH \
GEMNASIUM_DB_REMOTE_URL \
GEMNASIUM_DB_REF_NAME \

View File

@ -525,9 +525,16 @@ module Gitlab
# Omitted because no user, creator or author associated: `boards`, `labels`, `milestones`, `uploads`
# Omitted because too expensive: `epics_deepest_relationship_level`
# Omitted because of encrypted properties: `projects_jira_cloud_active`, `projects_jira_server_active`
# rubocop: disable CodeReuse/ActiveRecord
def usage_activity_by_stage_plan(time_period)
{}
{
issues: distinct_count(::Issue.where(time_period), :author_id),
notes: distinct_count(::Note.where(time_period), :author_id),
projects: distinct_count(::Project.where(time_period), :creator_id),
todos: distinct_count(::Todo.where(time_period), :author_id)
}
end
# rubocop: enable CodeReuse/ActiveRecord
# Omitted because no user, creator or author associated: `environments`, `feature_flags`, `in_review_folder`, `pages_domains`
# rubocop: disable CodeReuse/ActiveRecord

View File

@ -7,6 +7,8 @@
"dev-server": "NODE_OPTIONS=\"--max-old-space-size=3584\" node scripts/frontend/webpack_dev_server.js",
"eslint": "eslint --cache --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .",
"eslint-fix": "eslint --cache --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .",
"eslint-staged": "git diff --cached --name-only | grep -E \"(.*)\\.(js|vue)$\" | xargs eslint --cache --max-warnings 0 --report-unused-disable-directives",
"eslint-staged-fix": "git diff --cached --name-only | grep -E \"(.*)\\.(js|vue)$\" | xargs eslint --cache --max-warnings 0 --report-unused-disable-directives --fix",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .",
"file-coverage": "scripts/frontend/file_test_coverage.js",
"prejest": "yarn check-dependencies",
@ -41,7 +43,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.151.0",
"@gitlab/ui": "17.19.1",
"@gitlab/ui": "17.21.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.10.2",

View File

@ -474,6 +474,10 @@ module QA
autoload :Templates, 'qa/page/component/project/templates'
end
end
module Modal
autoload :DeleteWiki, 'qa/page/modal/delete_wiki'
end
end
##

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module QA
module Page
module Modal
class DeleteWiki < Base
view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do
element :confirm_deletion_button, required: true
end
def confirm_deletion
click_element :confirm_deletion_button
end
end
end
end
end

View File

@ -15,6 +15,10 @@ module QA
element :create_page_button
end
view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do
element :delete_button
end
def set_title(title)
fill_element :wiki_title_textbox, title
end
@ -34,6 +38,11 @@ module QA
def click_create_page
click_element :create_page_button
end
def delete_page
click_element :delete_button, Page::Modal::DeleteWiki
Page::Modal::DeleteWiki.perform(&:confirm_deletion)
end
end
end
end

View File

@ -58,6 +58,10 @@ module QA
def has_content?(content)
has_element?(:wiki_page_content, content)
end
def has_no_page?
has_element? :create_first_page_link
end
end
end
end

View File

@ -22,6 +22,8 @@ module QA
Page::Project::Menu.perform(&:click_issues)
Page::Project::Issue::Index.perform do |issues_page|
expect(issues_page).to have_content("2 issues successfully imported")
issues_page.click_issue_link(jira_issue_title)
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Create' do
context 'Wiki' do
let(:initial_wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! }
before do
Flow::Login.sign_in
end
context 'Page deletion' do
it 'has removed the deleted page correctly' do
initial_wiki.visit!
Page::Project::Wiki::Show.perform(&:click_edit)
Page::Project::Wiki::Edit.perform(&:delete_page)
Page::Project::Wiki::Show.perform do |wiki|
expect(wiki).to have_no_page
end
end
end
end
end
end

View File

@ -48,7 +48,8 @@ echo
if [ ${FIND_READMES} -ne $NUMBER_READMES ]
then
echo
echo ' ✖ ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2
echo ' ✖ ERROR: The number of README.md file(s) has changed. Use index.md instead of README.md.' >&2
echo ' ✖ If removing a README.md file, update NUMBER_READMES in lint-doc.sh.' >&2
echo ' https://docs.gitlab.com/ee/development/documentation/styleguide.html#work-with-directories-and-files'
echo
((ERRORCODE++))

View File

@ -46,7 +46,7 @@ RSpec.describe RendersCommits do
it 'avoids N + 1' do
stub_const("MergeRequestDiff::COMMITS_SAFE_SIZE", 5)
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
control_count = ActiveRecord::QueryRecorder.new do
go
end.count

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Dashboard::SnippetsController do
let(:user) { create(:user) }
let_it_be(:user) { create(:user) }
before do
sign_in(user)
@ -26,5 +26,7 @@ RSpec.describe Dashboard::SnippetsController do
get :index
end
it_behaves_like 'snippets sort order'
end
end

View File

@ -15,14 +15,18 @@ RSpec.describe Projects::SnippetsController do
end
describe 'GET #index' do
let(:base_params) do
{
namespace_id: project.namespace,
project_id: project
}
end
subject { get :index, params: base_params }
it_behaves_like 'paginated collection' do
let(:collection) { project.snippets }
let(:params) do
{
namespace_id: project.namespace,
project_id: project
}
end
let(:params) { base_params }
before do
create(:project_snippet, :public, project: project, author: user)
@ -35,7 +39,11 @@ RSpec.describe Projects::SnippetsController do
.to receive(:new).with(nil, project: project)
.and_return(service)
get :index, params: { namespace_id: project.namespace, project_id: project }
subject
end
it_behaves_like 'snippets sort order' do
let(:params) { base_params }
end
context 'when the project snippet is private' do
@ -43,7 +51,7 @@ RSpec.describe Projects::SnippetsController do
context 'when anonymous' do
it 'does not include the private snippet' do
get :index, params: { namespace_id: project.namespace, project_id: project }
subject
expect(assigns(:snippets)).not_to include(project_snippet)
expect(response).to have_gitlab_http_status(:ok)
@ -56,7 +64,7 @@ RSpec.describe Projects::SnippetsController do
end
it 'renders the snippet' do
get :index, params: { namespace_id: project.namespace, project_id: project }
subject
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_gitlab_http_status(:ok)
@ -69,7 +77,7 @@ RSpec.describe Projects::SnippetsController do
end
it 'renders the snippet' do
get :index, params: { namespace_id: project.namespace, project_id: project }
subject
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_gitlab_http_status(:ok)

View File

@ -6,6 +6,8 @@ RSpec.describe SnippetsController do
let_it_be(:user) { create(:user) }
describe 'GET #index' do
let(:base_params) { { username: user.username } }
context 'when username parameter is present' do
it_behaves_like 'paginated collection' do
let(:collection) { Snippet.all }
@ -38,6 +40,10 @@ RSpec.describe SnippetsController do
expect(response).to redirect_to(dashboard_snippets_path)
end
end
it_behaves_like 'snippets sort order' do
let(:params) { base_params }
end
end
describe 'GET #new' do

View File

@ -295,6 +295,22 @@ RSpec.describe SnippetsFinder do
expect(finder.execute).to be_empty
end
end
context 'no sort param is provided' do
it 'returns snippets sorted by id' do
snippets = described_class.new(admin).execute
expect(snippets.ids).to eq(Snippet.order_id_desc.ids)
end
end
context 'sort param is provided' do
it 'returns snippets sorted by sort param' do
snippets = described_class.new(admin, sort: 'updated_desc').execute
expect(snippets.ids).to eq(Snippet.order_updated_desc.ids)
end
end
end
it_behaves_like 'snippet visibility'

View File

@ -948,6 +948,25 @@ describe('Monitoring store actions', () => {
);
});
it('dispatches receiveDashboardValidationWarningsSuccess with false payload when the response is empty ', () => {
mockMutate.mockResolvedValue({
data: {
project: null,
},
});
return testAction(
fetchDashboardValidationWarnings,
null,
state,
[],
[{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
() => {
expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
},
);
});
it('dispatches receiveDashboardValidationWarningsFailure if the warnings API call fails', () => {
mockMutate.mockRejectedValue({});

View File

@ -235,6 +235,31 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
context 'for plan' do
it 'includes accurate usage_activity_by_stage data' do
for_defined_days_back do
user = create(:user)
project = create(:project, creator: user)
issue = create(:issue, project: project, author: user)
create(:note, project: project, noteable: issue, author: user)
create(:todo, project: project, target: issue, author: user)
end
expect(described_class.uncached_data[:usage_activity_by_stage][:plan]).to include(
issues: 2,
notes: 2,
projects: 2,
todos: 2
)
expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:plan]).to include(
issues: 1,
notes: 1,
projects: 1,
todos: 1
)
end
end
context 'for release' do
it 'includes accurate usage_activity_by_stage data' do
for_defined_days_back do

View File

@ -24,6 +24,7 @@ RSpec.describe MergeRequestPolicy do
mr_perms = %i[create_merge_request_in
create_merge_request_from
read_merge_request
approve_merge_request
create_note].freeze
shared_examples_for 'a denied user' do

View File

@ -11,6 +11,10 @@ RSpec.describe MergeRequests::ApprovalService do
subject(:service) { described_class.new(project, user) }
before do
project.add_developer(user)
end
context 'with invalid approval' do
before do
allow(merge_request.approvals).to receive(:new).and_return(double(save: false))
@ -56,5 +60,15 @@ RSpec.describe MergeRequests::ApprovalService do
end
end
end
context 'user cannot update the merge request' do
before do
project.add_guest(user)
end
it 'does not update approvals' do
expect { service.execute(merge_request) }.not_to change { merge_request.approvals.size }
end
end
end
end

View File

@ -33,5 +33,14 @@ RSpec.describe MergeRequests::RemoveApprovalService do
execute!
end
end
context 'with a user who has not approved' do
it 'does not create an unapproval note and triggers web hook' do
expect(service).not_to receive(:execute_hooks)
expect(SystemNoteService).not_to receive(:unapprove_mr)
execute!
end
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
RSpec.shared_examples 'snippets sort order' do
let(:params) { {} }
let(:sort_argument) { {} }
let(:sort_params) { params.merge(sort_argument)}
before do
sign_in(user)
stub_snippet_counter
end
subject { get :index, params: sort_params }
context 'when no sort param is provided' do
it 'calls SnippetsFinder with updated_at sort option' do
expect(SnippetsFinder).to receive(:new).with(user,
hash_including(sort: 'updated_desc')).and_call_original
subject
end
end
context 'when sort param is provided' do
let(:order) { 'created_desc' }
let(:sort_argument) { { sort: order } }
it 'calls SnippetsFinder with the given sort param' do
expect(SnippetsFinder).to receive(:new).with(user,
hash_including(sort: order)).and_call_original
subject
end
end
def stub_snippet_counter
allow(Snippets::CountService)
.to receive(:new).and_return(double(:count_service, execute: {}))
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
RSpec.shared_examples 'renders plain text email correctly' do
it 'renders the email without HTML links' do
render
expect(rendered).to have_no_selector('a')
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'notify/push_to_merge_request_email.text.haml' do
let(:user) { create(:user, developer_projects: [project]) }
let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
let(:new_commits) { project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51') }
before do
assign(:updated_by_user, user)
assign(:project, project)
assign(:merge_request, merge_request)
assign(:existing_commits, [])
assign(:new_commits, new_commits)
end
it_behaves_like 'renders plain text email correctly'
end

View File

@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.151.0.tgz#099905295d33eb31033f4a48eb3652da2f686239"
integrity sha512-2PTSM8CFhUjeTFKfcq6E/YwPpOVdSVWupf3NhKO/bz/cisSBS5P7aWxaXKIaxy28ySyBKEfKaAT6b4rXTwvVgg==
"@gitlab/ui@17.19.1":
version "17.19.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.19.1.tgz#48c6d542b19fd6a420d22017e7026190aba1fd31"
integrity sha512-RA4QXzVWOjbK3gjX78Luhtmo1z6td1uOu8S01v+yu5Pc00HKIgN6pdDwPK8+WLCK2cnu368c457A901wSr82Gg==
"@gitlab/ui@17.21.0":
version "17.21.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.21.0.tgz#e881bac4540e3db29ee32e1dfd452677a445cd10"
integrity sha512-Ijh3QPlB3Y10Sk0f0eZ/rgRIKHGSzAWZLugw9sb+ppcn9OPbb+2vk0ZgCcdIrfkrX3G8tD8q0Ndl3K1nrz6a5g==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"