Commit f4186a75 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 02211168
<script>
import { initEditorLite } from '~/blob/utils';
export default {
props: {
value: {
type: String,
required: true,
},
fileName: {
type: String,
required: false,
default: '',
},
},
data() {
return {
content: this.value,
editor: null,
};
},
watch: {
fileName(newVal) {
this.editor.updateModelLanguage(newVal);
},
},
mounted() {
this.editor = initEditorLite({
el: this.$refs.editor,
blobPath: this.fileName,
blobContent: this.content,
});
},
methods: {
triggerFileChange() {
const val = this.editor.getValue();
this.content = val;
this.$emit('input', val);
},
},
};
</script>
<template>
<div class="file-content code">
<pre id="editor" ref="editor" data-editor-loading @focusout="triggerFileChange">{{
content
}}</pre>
</div>
</template>
/* global ace */
import Editor from '~/editor/editor_lite';
export function initEditorLite({ el, blobPath, blobContent }) {
if (!el) {
throw new Error(`"el" parameter is required to initialize Editor`);
}
let editor;
if (window?.gon?.features?.monacoSnippets) {
editor = new Editor();
editor.createInstance({
el,
blobPath,
blobContent,
});
} else {
editor = ace.edit(el);
}
return editor;
}
export default () => ({});
<script>
import _ from 'underscore';
import { mapState } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
import { GlLabel, GlTooltipDirective } from '@gitlab/ui';
import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
......@@ -10,18 +10,17 @@ import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_
import IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue';
import boardsStore from '../stores/boards_store';
import IssueCardInnerScopedLabel from './issue_card_inner_scoped_label.vue';
import { isScopedLabel } from '~/lib/utils/common_utils';
export default {
components: {
GlLabel,
Icon,
UserAvatarLink,
TooltipOnTruncate,
IssueDueDate,
IssueTimeEstimate,
IssueCardWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
IssueCardInnerScopedLabel,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -145,12 +144,6 @@ export default {
boardsStore.toggleFilter(filter);
},
labelStyle(label) {
return {
backgroundColor: label.color,
color: label.textColor,
};
},
showScopedLabel(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
},
......@@ -184,27 +177,16 @@ export default {
</div>
<div v-if="showLabelFooter" class="board-card-labels prepend-top-4 d-flex flex-wrap">
<template v-for="label in orderedLabels">
<issue-card-inner-scoped-label
v-if="showScopedLabel(label)"
<gl-label
:key="label.id"
:label="label"
:label-style="labelStyle(label)"
:background-color="label.color"
:title="label.title"
:description="label.description"
size="sm"
:scoped="showScopedLabel(label)"
:scoped-labels-documentation-link="helpLink"
@scoped-label-click="filterByLabel($event)"
/>
<button
v-else
:key="label.id"
v-gl-tooltip
:style="labelStyle(label)"
:title="label.description"
class="badge color-label append-right-4 prepend-top-4"
type="button"
@click="filterByLabel(label)"
>
{{ label.title }}
</button>
/>
</template>
</div>
<div class="board-card-footer d-flex justify-content-between align-items-end">
......
<script>
import { GlLink, GlTooltip } from '@gitlab/ui';
export default {
components: {
GlTooltip,
GlLink,
},
props: {
label: {
type: Object,
required: true,
},
labelStyle: {
type: Object,
required: true,
},
scopedLabelsDocumentationLink: {
type: String,
required: true,
},
},
};
</script>
<template>
<span
class="d-inline-block position-relative scoped-label-wrapper append-right-4 prepend-top-4 board-label"
>
<a @click="$emit('scoped-label-click', label)">
<span :ref="'labelTitleRef'" :style="labelStyle" class="badge label color-label">
{{ label.title }}
</span>
<gl-tooltip :target="() => $refs.labelTitleRef" placement="top" boundary="viewport">
<span class="font-weight-bold scoped-label-tooltip-title">{{ __('Scoped label') }}</span
><br />
{{ label.description }}
</gl-tooltip>
</a>
<gl-link :href="scopedLabelsDocumentationLink" target="_blank" class="label scoped-label"
><i class="fa fa-question-circle" :style="labelStyle"></i
></gl-link>
</span>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { GlTable, GlLoadingIcon, GlBadge } from '@gitlab/ui';
import { CLUSTER_TYPES } from '../constants';
import { __ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import { CLUSTER_TYPES, STATUSES } from '../constants';
import { __, sprintf } from '~/locale';
export default {
components: {
......@@ -10,6 +11,9 @@ export default {
GlLoadingIcon,
GlBadge,
},
directives: {
tooltip,
},
fields: [
{
key: 'name',
......@@ -38,6 +42,13 @@ export default {
},
methods: {
...mapActions(['fetchClusters']),
statusClass(status) {
return STATUSES[status].className;
},
statusTitle(status) {
const { title } = STATUSES[status];
return sprintf(__('Status: %{title}'), { title }, false);
},
},
};
</script>
......@@ -52,6 +63,25 @@ export default {
variant="light"
class="qa-clusters-table"
>
<template #cell(name)="{ item }">
<div class="d-flex flex-row-reverse flex-md-row js-status">
{{ item.name }}
<gl-loading-icon
v-if="item.status === 'deleting'"
v-tooltip
:title="statusTitle(item.status)"
size="sm"
class="mr-2 ml-md-2"
/>
<div
v-else
v-tooltip
class="cluster-status-indicator rounded-circle align-self-center gl-w-8 gl-h-8 mr-2 ml-md-2"
:class="statusClass(item.status)"
:title="statusTitle(item.status)"
></div>
</div>
</template>
<template #cell(clusterType)="{value}">
<gl-badge variant="light">
{{ value }}
......
......@@ -6,6 +6,10 @@ export const CLUSTER_TYPES = {
instance_type: __('Instance'),
};
export default {
CLUSTER_TYPES,
export const STATUSES = {
disabled: { className: 'disabled', title: __('Disabled') },
connected: { className: 'bg-success', title: __('Connected') },
unreachable: { className: 'bg-danger', title: __('Unreachable') },
authentication_failure: { className: 'bg-warning', title: __('Authentication Failure') },
deleting: { title: __('Deleting') },
};
export default (initialState = {}) => ({
endpoint: initialState.endpoint,
loading: false, // TODO - set this to true once integrated with BE
clusters: [
// TODO - remove mock data once integrated with BE
// {
// name: 'My Cluster',
// environmentScope: '*',
// size: '3',
// clusterType: 'group_type',
// },
// {
// name: 'My other cluster',
// environmentScope: 'production',
// size: '12',
// clusterType: 'project_type',
// },
],
clusters: [],
});
/* global ace */
import Editor from '~/editor/editor_lite';
import { initEditorLite } from '~/blob/utils';
import setupCollapsibleInputs from './collapsible_input';
let editor;
const initAce = () => {
editor = ace.edit('editor');
const editorEl = document.getElementById('editor');
const form = document.querySelector('.snippet-form-holder form');
const content = document.querySelector('.snippet-file-content');
editor = initEditorLite({ el: editorEl });
form.addEventListener('submit', () => {
content.value = editor.getValue();
});
......@@ -20,8 +21,7 @@ const initMonaco = () => {
const fileNameEl = document.querySelector('.js-snippet-file-name');
const form = document.querySelector('.snippet-form-holder form');
editor = new Editor();
editor.createInstance({
editor = initEditorLite({
el: editorEl,
blobPath: fileNameEl.value,
blobContent: contentEl.value,
......
<script>
import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue';
import BlobContentEdit from '~/blob/components/blob_edit_content.vue';
export default {
components: {
BlobHeaderEdit,
BlobContentEdit,
},
props: {
content: {
type: String,
required: true,
},
fileName: {
type: String,
required: true,
},
},
data() {
return {
name: this.fileName,
blobContent: this.content,
};
},
};
</script>
<template>
<div class="form-group file-editor">
<label>{{ s__('Snippets|File') }}</label>
<div class="file-holder snippet">
<blob-header-edit v-model="name" />
<blob-content-edit v-model="blobContent" :file-name="name" />
</div>
</div>
</template>
......@@ -111,4 +111,6 @@ export function initUserTracking() {
if (opts.linkClickTracking) window.snowplow('enableLinkClickTracking');
Tracking.bindDocument();
document.dispatchEvent(new Event('SnowplowInitialized'));
}
<script>
import DropdownValueScopedLabel from './dropdown_value_scoped_label.vue';
import DropdownValueRegularLabel from './dropdown_value_regular_label.vue';
import { GlLabel } from '@gitlab/ui';
import { isScopedLabel } from '~/lib/utils/common_utils';
export default {
components: {
DropdownValueScopedLabel,
DropdownValueRegularLabel,
GlLabel,
},
props: {
labels: {
......@@ -37,12 +35,6 @@ export default {
labelFilterUrl(label) {
return `${this.labelFilterBasePath}?label_name[]=${encodeURIComponent(label.title)}`;
},
labelStyle(label) {
return {
color: label.textColor,
backgroundColor: label.color,
};
},
scopedLabelsDescription({ description = '' }) {
return `<span class="font-weight-bold scoped-label-tooltip-title">Scoped label</span><br />${description}`;
},
......@@ -65,22 +57,15 @@ export default {
</span>
<template v-for="label in labels" v-else>
<dropdown-value-scoped-label
v-if="showScopedLabels(label)"
<gl-label
:key="label.id"
:label="label"
:label-filter-url="labelFilterUrl(label)"
:label-style="labelStyle(label)"
:target="labelFilterUrl(label)"
:background-color="label.color"
:title="label.title"
:description="label.description"
:scoped="showScopedLabels(label)"
:scoped-labels-documentation-link="scopedLabelsDocumentationLink"
/>
<dropdown-value-regular-label
v-else
:key="label.id"
:label="label"
:label-filter-url="labelFilterUrl(label)"
:label-style="labelStyle(label)"
/>
</template>
</div>
</template>
<script>
import { GlTooltip } from '@gitlab/ui';
export default {
components: {
GlTooltip,
},
props: {
label: {
type: Object,
required: true,
},
labelStyle: {
type: Object,
required: true,
},
labelFilterUrl: {
type: String,
required: true,
},
},
};
</script>
<template>
<a ref="regularLabelRef" :href="labelFilterUrl">
<span :style="labelStyle" class="badge color-label">
{{ label.title }}
</span>
<gl-tooltip
v-if="label.description"
:target="() => $refs.regularLabelRef"
placement="top"
boundary="viewport"
>
{{ label.description }}
</gl-tooltip>
</a>
</template>
<script>
import { GlLink, GlTooltip } from '@gitlab/ui';
export default {
components: {
GlTooltip,
GlLink,
},
props: {
label: {
type: Object,
required: true,
},
labelStyle: {
type: Object,
required: true,
},
scopedLabelsDocumentationLink: {
type: String,
required: true,
},
labelFilterUrl: {
type: String,
required: true,
},
},
};
</script>
<template>
<span class="d-inline-block position-relative scoped-label-wrapper">
<a :href="labelFilterUrl">
<span :ref="`labelTitleRef`" :style="labelStyle" class="badge color-label label">
{{ label.title }}
</span>
<gl-tooltip
v-if="label.description"
:target="() => $refs.labelTitleRef"
placement="top"
boundary="viewport"
>
<span class="font-weight-bold scoped-label-tooltip-title">{{ __('Scoped label') }}</span
><br />
{{ label.description }}
</gl-tooltip>
</a>
<gl-link :href="scopedLabelsDocumentationLink" target="_blank" class="label scoped-label"
><i class="fa fa-question-circle" :style="labelStyle"></i
></gl-link>
</span>
</template>
......@@ -266,20 +266,9 @@
background-color: $blue-50;
}
.badge {
border: 0;
outline: 0;
&:hover {
text-decoration: underline;
}
@include media-breakpoint-down(lg) {
font-size: $gl-font-size-xs;
padding-left: $gl-padding-4;
padding-right: $gl-padding-4;
font-weight: $gl-font-weight-bold;
}
.gl-label {
margin-top: 4px;
margin-right: 4px;
}
.confidential-icon {
......
......@@ -163,3 +163,9 @@
color: $black;
font-weight: $gl-font-weight-bold;
}
.cluster-status-indicator {
&.disabled {
background-color: $gray-600;
}
}
......@@ -158,6 +158,10 @@
a:not(.btn) {
color: inherit;
.gl-label-text:hover {
color: inherit;
}
&:hover {
color: $blue-800;
......
......@@ -54,8 +54,10 @@
.mh-50vh { max-height: 50vh; }
.font-size-inherit { font-size: inherit; }
.gl-w-8 { width: px-to-rem($grid-size); }
.gl-w-16 { width: px-to-rem($grid-size * 2); }
.gl-w-64 { width: px-to-rem($grid-size * 8); }
.gl-h-8 { height: px-to-rem($grid-size); }
.gl-h-32 { height: px-to-rem($grid-size * 4); }
.gl-h-64 { height: px-to-rem($grid-size * 8); }
......
# frozen_string_literal: true
module Ci
# A state object to centralize logic related to merge request pipelines
class PipelinesForMergeRequestFinder
include Gitlab::Utils::StrongMemoize
EVENT = 'merge_request_event'
def initialize(merge_request)
@merge_request = merge_request
end
attr_reader :merge_request
delegate :commit_shas, :source_project, :source_branch, to: :merge_request
def all
strong_memoize(:all_pipelines) do
next Ci::Pipeline.none unless source_project
pipelines =
if merge_request.persisted?
pipelines_using_cte
else
triggered_for_branch.for_sha(commit_shas)
end
sort(pipelines)
end
end
private
def pipelines_using_cte
cte = Gitlab::SQL::CTE.new(:shas, merge_request.all_commits.select(:sha))
source_pipelines_join = cte.table[:sha].eq(Ci::Pipeline.arel_table[:source_sha])
source_pipelines = filter_by(triggered_by_merge_request, cte, source_pipelines_join)
detached_pipelines = filter_by_sha(triggered_by_merge_request, cte)
pipelines_for_branch = filter_by_sha(triggered_for_branch, cte)
Ci::Pipeline.with(cte.to_arel) # rubocop: disable CodeReuse/ActiveRecord
.from_union([source_pipelines, detached_pipelines, pipelines_for_branch])
end
def filter_by_sha(pipelines, cte)
hex = Arel::Nodes::SqlLiteral.new("'hex'")
string_sha = Arel::Nodes::NamedFunction.new('encode', [cte.table[:sha], hex])
join_condition = string_sha.eq(Ci::Pipeline.arel_table[:sha])
filter_by(pipelines, cte, join_condition)
end
def filter_by(pipelines, cte, join_condition)
shas_table =
Ci::Pipeline.arel_table
.join(cte.table, Arel::Nodes::InnerJoin)
.on(join_condition)
.join_sources
pipelines.joins(shas_table) # rubocop: disable CodeReuse/ActiveRecord
end
# NOTE: this method returns only parent merge request pipelines.
# Child merge request pipelines have a different source.
def triggered_by_merge_request
source_project.ci_pipelines
.where(source: :merge_request_event, merge_request: merge_request) # rubocop: disable CodeReuse/ActiveRecord
end
def triggered_for_branch
source_project.ci_pipelines
.where(source: branch_pipeline_sources, ref: source_branch, tag: false) # rubocop: disable CodeReuse/ActiveRecord
end
def branch_pipeline_sources
strong_memoize(:branch_pipeline_sources) do
Ci::Pipeline.sources.reject { |source| source == EVENT }.values
end
end
def sort(pipelines)
sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC'
query = ApplicationRecord.send(:sanitize_sql_array, [sql, Ci::Pipeline.sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend
pipelines.order(Arel.sql(query)) # rubocop: disable CodeReuse/ActiveRecord
end
end
end
......@@ -1251,7 +1251,7 @@ class MergeRequest < ApplicationRecord
def all_pipelines
strong_memoize(:all_pipelines) do
MergeRequest::Pipelines.new(self).all
Ci::PipelinesForMergeRequestFinder.new(self).all
end
end
......
# frozen_string_literal: true
# A state object to centralize logic related to merge request pipelines
class MergeRequest::Pipelines
include Gitlab::Utils::StrongMemoize
EVENT = 'merge_request_event'
def initialize(merge_request)
@merge_request = merge_request
end
attr_reader :merge_request
delegate :commit_shas, :source_project, :source_branch, to: :merge_request
def all
strong_memoize(:all_pipelines) do
next Ci::Pipeline.none unless source_project
pipelines =
if merge_request.persisted?
pipelines_using_cte
else
triggered_for_branch.for_sha(commit_shas)
end
sort(pipelines)
end
end
private
def pipelines_using_cte
cte = Gitlab::SQL::CTE.new(:shas, merge_request.all_commits.select(:sha))
source_pipelines_join = cte.table[:sha].eq(Ci::Pipeline.arel_table[:source_sha])
source_pipelines = filter_by(triggered_by_merge_request, cte, source_pipelines_join)
detached_pipelines = filter_by_sha(triggered_by_merge_request, cte)
pipelines_for_branch = filter_by_sha(triggered_for_branch, cte)
Ci::Pipeline.with(cte.to_arel)
.from_union([source_pipelines, detached_pipelines, pipelines_for_branch])
end
def filter_by_sha(pipelines, cte)
hex = Arel::Nodes::SqlLiteral.new("'hex'")
string_sha = Arel::Nodes::NamedFunction.new('encode', [cte.table[:sha], hex])
join_condition = string_sha.eq(Ci::Pipeline.arel_table[:sha])
filter_by(pipelines, cte, join_condition)
end
def filter_by(pipelines, cte, join_condition)
shas_table =
Ci::Pipeline.arel_table
.join(cte.table, Arel::Nodes::InnerJoin)
.on(join_condition)
.join_sources
pipelines.joins(shas_table)
end
# NOTE: this method returns only parent merge request pipelines.
# Child merge request pipelines have a different source.
def triggered_by_merge_request
source_project.ci_pipelines
.where(source: :merge_request_event, merge_request: merge_request)
end
def triggered_for_branch
source_project.ci_pipelines
.where(source: branch_pipeline_sources, ref: source_branch, tag: false)
end
def branch_pipeline_sources
strong_memoize(:branch_pipeline_sources) do
Ci::Pipeline.sources.reject { |source| source == EVENT }.values
end
end
def sort(pipelines)
sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC'
query = ApplicationRecord.send(:sanitize_sql_array, [sql, Ci::Pipeline.sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend
pipelines.order(Arel.sql(query))
end
end
---
title: Resolve Change link-icons on security configuration page to follow design system
merge_request: 26340
author:
type: other
---
title: Support more query variables in custom dashboards per project
merge_request: 25732
author:
type: added
---
title: Update labels in Vue with GlLabel component
merge_request: 21465
author:
type: changed
---
title: Show cluster status (FE)
merge_request: 26368
author:
type: added
......@@ -1006,6 +1006,12 @@ unset http_proxy
unset https_proxy
```
### Gitaly not listening on new address after reconfiguring
When updating the `gitaly['listen_addr']` or `gitaly['prometheus_listen_addr']` values, Gitaly may continue to listen on the old address after a `sudo gitlab-ctl reconfigure`.
When this occurs, performing a `sudo gitlab-ctl restart` will resolve the issue. This will no longer be necessary after [this issue](https://gitlab.com/gitlab-org/gitaly/issues/2521) is resolved.
### Praefect
Praefect is an experimental daemon that allows for replication of the Git data.
......
......@@ -84,6 +84,8 @@ with secure tokens as you complete the setup process.
Praefect cluster directly; that could lead to data loss.
1. `PRAEFECT_SQL_PASSWORD`: this password is used by Praefect to connect to
PostgreSQL.
1. `GRAFANA_PASSWORD`: this password is used to access the `admin`
account in the Grafana dashboards.
We will note in the instructions below where these secrets are required.
......@@ -184,6 +186,10 @@ application server, or a Gitaly node.
# Make Praefect accept connections on all network interfaces.
# Use firewalls to restrict access to this address/port.
praefect['listen_addr'] = '0.0.0.0:2305'
# Enable Prometheus metrics access to Praefect. You must use firewalls
# to restrict access to this address/port.
praefect['prometheus_listen_addr'] = '0.0.0.0:9652'
```
1. Configure a strong `auth_token` for **Praefect** by editing
......@@ -354,6 +360,10 @@ documentation](index.md#3-gitaly-server-configuration).
# Make Gitaly accept connections on all network interfaces.
# Use firewalls to restrict access to this address/port.
gitaly['listen_addr'] = '0.0.0.0:8075'
# Enable Prometheus metrics access to Gitaly. You must use firewalls
# to restrict access to this address/port.
gitaly['prometheus_listen_addr'] = '0.0.0.0:9236'
```
1. Configure a strong `auth_token` for **Gitaly** by editing
......@@ -453,7 +463,7 @@ Particular attention should be shown to:
You will need to replace:
- `PRAEFECT_URL_OR_IP` with the IP/host address of the Praefect node
- `PRAEFECT_HOST` with the IP address or hostname of the Praefect node
- `PRAEFECT_EXTERNAL_TOKEN` with the real secret
```ruby
......@@ -462,7 +472,7 @@ Particular attention should be shown to:
"path" => "/var/opt/gitlab/git-data"
},
"praefect" => {
"gitaly_address" => "tcp://PRAEFECT_URL_OR_IP:2305",
"gitaly_address" => "tcp://PRAEFECT_HOST:2305",
"gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN'
}
})
......@@ -478,6 +488,38 @@ Particular attention should be shown to:
gitlab_shell['secret_token'] = 'GITLAB_SHELL_SECRET_TOKEN'
```
1. Add Prometheus monitoring settings by editing `/etc/gitlab/gitlab.rb`.
You will need to replace:
- `PRAEFECT_HOST` with the IP address or hostname of the Praefect node
- `GITALY_HOST` with the IP address or hostname of each Gitaly node
```ruby
prometheus['scrape_configs'] = [
{
'job_name' => 'praefect',
'static_configs' => [
'targets' => [
'PRAEFECT_HOST:9652' # praefect
]
]
},
{
'job_name' => 'praefect-gitaly',
'static_configs' => [
'targets' => [
'GITALY_HOST:9236', # gitaly-1
'GITALY_HOST:9236', # gitaly-2
'GITALY_HOST:9236', # gitaly-3
]
]
}
]
grafana['disable_login_form'] = false
```
1. Save the changes to `/etc/gitlab/gitlab.rb` and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure):
```shell
......@@ -490,6 +532,12 @@ Particular attention should be shown to:
sudo gitlab-rake gitlab:gitaly:check
```
1. Set the Grafana admin password. This command will prompt you to enter a new password:
```shell
sudo gitlab-ctl set-grafana-password
```
1. Update the **Repository storage** settings from **Admin Area > Settings >
Repository > Repository storage** to make the newly configured Praefect
cluster the storage location for new Git repositories.
......@@ -502,7 +550,12 @@ Particular attention should be shown to:
repository that viewed. If the project is created, and you can see the
README file, it works!
Congratulations! You have configured a highly available Praefect cluster, and
1. Inspect metrics by browsing to `/-/grafana` on your GitLab server.
Log in with `admin` / `GRAFANA_PASSWORD`. Go to 'Explore' and query
`gitlab_build_info` to verify that you are getting metrics from all your
machines.
Congratulations! You have configured a highly available Praefect cluster.
## Migrating existing repositories to Praefect
......
......@@ -58,6 +58,7 @@ Runs the following rake tasks:
- `gitlab:app:check`
It will check that each component was set up according to the installation guide and suggest fixes for issues found.
This command must be run from your app server and will not work correctly on component servers like [Gitaly](../gitaly/index.md#running-gitaly-on-its-own-server).
You may also have a look at our Troubleshooting Guides:
......
This diff is collapsed.
......@@ -11,7 +11,7 @@ type: reference, howto
The security configuration page displays the configuration state of each of the security
features and can be accessed through a project's sidebar nav.
![Screenshot of security configuration page](../img/security_configuration_page_v12_6.png)
![Screenshot of security configuration page](../img/security_configuration_page_v12_9.png)
The page uses the project's latest default branch [CI pipeline](../../../ci/pipelines.md) to determine the configuration
state of each feature. If a job with the expected security report artifact exists in the pipeline,
......
......@@ -38,14 +38,14 @@ If you follow the instructions you can publish `MyProject` by running
`npm publish` from the root directory.
Publishing `Foo` is almost exactly the same, you simply have to follow the steps
while in the `Foo` directory. `Foo` will need it's own `package.json` file,
which can be added manually or using `npm init`. And it will need it's own
while in the `Foo` directory. `Foo` will need its own `package.json` file,
which can be added manually or using `npm init`. And it will need its own
configuration settings. Since you are publishing to the same place, if you
used `npm config set` to set the registry for the parent project, then no
additional setup is necessary. If you used a `.npmrc` file, you will need an
additional `.npmrc` file in the `Foo` directory (be sure to add `.npmrc` files
to the `.gitignore` file or use environment variables in place of your access
tokens to preven them from being exposed). It can be identical to the
tokens to prevent them from being exposed). It can be identical to the
one you used in `MyProject`. You can now run `npm publish` from the `Foo`
directory and you will be able to publish `Foo` separately from `MyProject`
......
This diff is collapsed.
......@@ -155,10 +155,17 @@ Multiple metrics can be displayed on the same chart if the fields **Name**, **Ty
#### Query Variables
GitLab supports a limited set of [CI variables](../../../ci/variables/README.md) in the Prometheus query. This is particularly useful for identifying a specific environment, for example with `CI_ENVIRONMENT_SLUG`. The supported variables are:
GitLab supports a limited set of [CI variables](../../../ci/variables/README.md) in the Prometheus query. This is particularly useful for identifying a specific environment, for example with `ci_environment_slug`. The supported variables are:
- CI_ENVIRONMENT_SLUG
- KUBE_NAMESPACE
- `ci_environment_slug`
- `kube_namespace`
- `ci_project_name`
- `ci_project_namespace`
- `ci_project_path`
- `ci_environment_name`
NOTE: **Note:**
Variables for Prometheus queries must be lowercase.
There are 2 methods to specify a variable in a query or dashboard:
......
......@@ -7,7 +7,11 @@ module Gitlab
{
ci_environment_slug: environment.slug,
kube_namespace: environment.deployment_namespace || '',
environment_filter: %{container_name!="POD",environment="#{environment.slug}"}
environment_filter: %{container_name!="POD",environment="#{environment.slug}"},
ci_project_name: environment.project.name,
ci_project_namespace: environment.project.namespace.name,
ci_project_path: environment.project.full_path,
ci_environment_name: environment.name
}
end
end
......
......@@ -2026,10 +2026,10 @@ msgstr ""
msgid "Analyze a review version of your web application."
msgstr ""
msgid "Analyze your dependencies for known vulnerabilities"
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
msgid "Analyze your source code for known vulnerabilities"
msgid "Analyze your source code for known vulnerabilities."
msgstr ""
msgid "Ancestors"
......@@ -2509,6 +2509,9 @@ msgstr ""
msgid "Authenticating"
msgstr ""
msgid "Authentication Failure"
msgstr ""
msgid "Authentication Log"
msgstr ""
......@@ -3460,7 +3463,7 @@ msgstr ""
msgid "Check your .gitlab-ci.yml"
msgstr ""
msgid "Check your Docker images for known vulnerabilities"
msgid "Check your Docker images for known vulnerabilities."
msgstr ""
msgid "Checking %{text} availability…"
......@@ -5169,6 +5172,9 @@ msgstr ""
msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
msgstr ""
msgid "Connected"
msgstr ""
msgid "Connecting"
msgstr ""
......@@ -6373,6 +6379,9 @@ msgstr ""
msgid "Deleted in this version"
msgstr ""
msgid "Deleting"
msgstr ""
msgid "Deleting the license failed."
msgstr ""
......@@ -16986,9 +16995,6 @@ msgstr ""
msgid "Scoped issue boards"
msgstr ""
msgid "Scoped label"
msgstr ""
msgid "Scopes"
msgstr ""
......@@ -17085,7 +17091,7 @@ msgstr ""
msgid "Search users or groups"
msgstr ""
msgid "Search your project dependencies for their licenses and apply policies"
msgid "Search your project dependencies for their licenses and apply policies."
msgstr ""
msgid "Search your projects"
......@@ -17311,7 +17317,7 @@ msgstr ""
msgid "SecurityConfiguration|Feature"
msgstr ""
msgid "SecurityConfiguration|Feature documentation"
msgid "SecurityConfiguration|Feature documentation for %{featureName}"
msgstr ""
msgid "SecurityConfiguration|Not yet configured"
......@@ -18720,6 +18726,9 @@ msgstr ""
msgid "Status:"
msgstr ""
msgid "Status: %{title}"
msgstr ""
msgid "Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
msgstr ""
......@@ -21101,6 +21110,9 @@ msgstr ""
msgid "Unmarks this %{noun} as Work In Progress."
msgstr ""
msgid "Unreachable"
msgstr ""
msgid "Unresolve"
msgstr ""
......
......@@ -519,7 +519,7 @@ describe 'Issue Boards', :js do
page.within(find('.board:nth-child(2)')) do
expect(page).to have_selector('.board-card', count: 8)
expect(find('.board-card', match: :first)).to have_content(bug.title)
click_button(bug.title)
click_link(bug.title)
wait_for_requests
end
......@@ -536,7 +536,7 @@ describe 'Issue Boards', :js do
it 'removes label filter by clicking label button on issue' do
page.within(find('.board:nth-child(2)')) do
page.within(find('.board-card', match: :first)) do
click_button(bug.title)
click_link(bug.title)
end
wait_for_requests
......
......@@ -305,7 +305,7 @@ describe 'Issue Boards', :js do
end
# 'Development' label does not show since the card is in a 'Development' list label
expect(card).to have_selector('.badge', count: 2)
expect(card).to have_selector('.gl-label', count: 2)
expect(card).to have_content(bug.title)
end
......@@ -335,7 +335,7 @@ describe 'Issue Boards', :js do
end
# 'Development' label does not show since the card is in a 'Development' list label
expect(card).to have_selector('.badge', count: 3)
expect(card).to have_selector('.gl-label', count: 3)
expect(card).to have_content(bug.title)
expect(card).to have_content(regression.title)
end
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe MergeRequest::Pipelines do
describe Ci::PipelinesForMergeRequestFinder do
describe '#all' do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Blob Header Editing rendering matches the snapshot 1`] = `
<div
class="file-content code"
>
<pre
data-editor-loading=""
id="editor"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</pre>
</div>
`;
import { shallowMount } from '@vue/test-utils';
import BlobEditContent from '~/blob/components/blob_edit_content.vue';
import { initEditorLite } from '~/blob/utils';
import { nextTick } from 'vue';
jest.mock('~/blob/utils', () => ({
initEditorLite: jest.fn(),
}));
describe('Blob Header Editing', () => {
let wrapper;
const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
const fileName = 'lorem.txt';
function createComponent() {
wrapper = shallowMount(BlobEditContent, {
propsData: {
value,
fileName,
},
});
}
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('rendering', () => {
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('renders content', () => {
expect(wrapper.text()).toContain(value);
});
});
describe('functionality', () => {
it('initialises Editor Lite', () => {
const el = wrapper.find({ ref: 'editor' }).element;
expect(initEditorLite).toHaveBeenCalledWith({
el,
blobPath: fileName,
blobContent: value,
});
});
it('reacts to the changes in fileName', () => {
wrapper.vm.editor = {
updateModelLanguage: jest.fn(),
};
const newFileName = 'ipsum.txt';
wrapper.setProps({
fileName: newFileName,
});
return nextTick().then(() => {
expect(wrapper.vm.editor.updateModelLanguage).toHaveBeenCalledWith(newFileName);
});
});
it('emits input event when the blob content is changed', () => {
const editorEl = wrapper.find({ ref: 'editor' });
wrapper.vm.editor = {
getValue: jest.fn().mockReturnValue(value),
};
editorEl.trigger('focusout');
return nextTick().then(() => {
expect(wrapper.emitted().input[0]).toEqual([value]);
});
});
});
});
import Editor from '~/editor/editor_lite';
import * as utils from '~/blob/utils';
const mockCreateMonacoInstance = jest.fn();
jest.mock('~/editor/editor_lite', () => {
return jest.fn().mockImplementation(() => {
return { createInstance: mockCreateMonacoInstance };
});
});
const mockCreateAceInstance = jest.fn();
global.ace = {
edit: mockCreateAceInstance,
};
describe('Blob utilities', () => {
beforeEach(() => {
Editor.mockClear();
});
describe('initEditorLite', () => {
let editorEl;
const blobPath = 'foo.txt';
const blobContent = 'Foo bar';
beforeEach(() => {
setFixtures('<div id="editor"></div>');
editorEl = document.getElementById('editor');
});
describe('Monaco editor', () => {
let origProp;
beforeEach(() => {
origProp = window.gon;
window.gon = {
features: {
monacoSnippets: true,
},
};
});
afterEach(() => {
window.gon = origProp;
});
it('initializes the Editor Lite', () => {
utils.initEditorLite({ el: editorEl });
expect(Editor).toHaveBeenCalled();
});
it('creates the instance with the passed parameters', () => {
utils.initEditorLite({ el: editorEl });
expect(mockCreateMonacoInstance.mock.calls[0]).toEqual([
{
el: editorEl,
blobPath: undefined,
blobContent: undefined,
},
]);
utils.initEditorLite({ el: editorEl, blobPath, blobContent });
expect(mockCreateMonacoInstance.mock.calls[1]).toEqual([
{
el: editorEl,
blobPath,
blobContent,
},
]);
});
});
describe('ACE editor', () => {
let origProp;
beforeEach(() => {
origProp = window.gon;
window.gon = {
features: {
monacoSnippets: false,
},
};
});
afterEach(() => {
window.gon = origProp;
});
it('does not initialize the Editor Lite', () => {
utils.initEditorLite({ el: editorEl });
expect(Editor).not.toHaveBeenCalled();
expect(mockCreateAceInstance).toHaveBeenCalledWith(editorEl);
});
});
});
});
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import IssueCardInnerScopedLabel from '~/boards/components/issue_card_inner_scoped_label.vue';
describe('IssueCardInnerScopedLabel Component', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(IssueCardInnerScopedLabel, {
propsData: {
label: { title: 'Foo::Bar', description: 'Some Random Description' },
labelStyle: { background: 'white', color: 'black' },
scopedLabelsDocumentationLink: '/docs-link',
},
});
});
afterEach(() => {
wrapper.destroy();
});
it('should render label title', () => {
expect(wrapper.find('.color-label').text()).toBe('Foo::Bar');
});
it('should render question mark symbol', () => {
expect(wrapper.find('.fa-question-circle').exists()).toBe(true);
});
it('should render label style provided', () => {
const label = wrapper.find('.color-label');
expect(label.attributes('style')).toContain('background: white;');
expect(label.attributes('style')).toContain('color: black;');
});
it('should render the docs link', () => {
expect(wrapper.find(GlLink).attributes('href')).toBe('/docs-link');
});
});
......@@ -8,6 +8,7 @@ import '~/boards/models/list';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
import { listObj } from '../../javascripts/boards/mock_data';
import store from '~/boards/stores';
import { GlLabel } from '@gitlab/ui';
describe('Issue card component', () => {
const user = new ListAssignee({
......@@ -20,7 +21,7 @@ describe('Issue card component', () => {
const label1 = new ListLabel({
id: 3,
title: 'testing 123',
color: 'blue',
color: '#000CFF',
text_color: 'white',
description: 'test',
});
......@@ -50,6 +51,9 @@ describe('Issue card component', () => {
rootPath: '/',
},
store,
stubs: {
GlLabel: true,
},
});
});
......@@ -290,25 +294,11 @@ describe('Issue card component', () => {
});
it('does not render list label but renders all other labels', () => {
expect(wrapper.findAll('.badge').length).toBe(1);
});
it('renders label', () => {
const nodes = wrapper.findAll('.badge').wrappers.map(label => label.attributes('title'));
expect(nodes.includes(label1.description)).toBe(true);
});
it('sets label description as title', () => {
expect(wrapper.find('.badge').attributes('title')).toContain(label1.description);
});
it('sets background color of button', () => {
const nodes = wrapper
.findAll('.badge')
.wrappers.map(label => label.element.style.backgroundColor);
expect(nodes.includes(label1.color)).toBe(true);
expect(wrapper.findAll(GlLabel).length).toBe(1);
const label = wrapper.find(GlLabel);
expect(label.props('title')).toEqual(label1.title);
expect(label.props('description')).toEqual(label1.description);
expect(label.props('backgroundColor')).toEqual(label1.color);
});
it('does not render label if label does not have an ID', done => {
......@@ -321,7 +311,7 @@ describe('Issue card component', () => {
wrapper.vm
.$nextTick()
.then(() => {
expect(wrapper.findAll('.badge').length).toBe(1);
expect(wrapper.findAll(GlLabel).length).toBe(1);
expect(wrapper.text()).not.toContain('closed');
done();
})
......
import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import { GlTable, GlLoadingIcon } from '@gitlab/ui';
import Clusters from '~/clusters_list/components/clusters.vue';
import Vuex from 'vuex';
import mockData from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -11,9 +12,10 @@ describe('Clusters', () => {
const findTable = () => wrapper.find(GlTable);
const findLoader = () => wrapper.find(GlLoadingIcon);
const findStatuses = () => findTable().findAll('.js-status');
const mountComponent = _state => {
const state = { clusters: [], endpoint: 'some/endpoint', ..._state };
const state = { clusters: mockData, endpoint: 'some/endpoint', ..._state };
const store = new Vuex.Store({
state,
});
......@@ -52,4 +54,25 @@ describe('Clusters', () => {
expect(findTable().classes()).toContain('b-table-stacked-md');
});
});
describe('cluster status', () => {
it.each`
statusName | className | lineNumber
${'disabled'} | ${'disabled'} | ${0}
${'unreachable'} | ${'bg-danger'} | ${1}
${'authentication_failure'} | ${'bg-warning'} | ${2}
${'deleting'} | ${null} | ${3}
${'connected'} | ${'bg-success'} | ${4}
`('renders a status for each cluster', ({ statusName, className, lineNumber }) => {
const statuses = findStatuses();
const status = statuses.at(lineNumber);
if (statusName !== 'deleting') {
const statusIndicator = status.find('.cluster-status-indicator');
expect(statusIndicator.exists()).toBe(true);
expect(statusIndicator.classes()).toContain(className);
} else {
expect(status.find(GlLoadingIcon).exists()).toBe(true);
}
});
});
});
export default [
{
name: 'My Cluster 1',
environmentScope: '*',
size: '3',
clusterType: 'group_type',
status: 'disabled',
},
{
name: 'My Cluster 2',
environmentScope: 'development',
size: '12',
clusterType: 'project_type',
status: 'unreachable',
},
{
name: 'My Cluster 3',
environmentScope: 'development',
size: '12',
clusterType: 'project_type',
status: 'authentication_failure',
},
{
name: 'My Cluster 4',
environmentScope: 'production',
size: '12',
clusterType: 'project_type',
status: 'deleting',
},
{
name: 'My Cluster 5',
environmentScope: 'development',
size: '12',
clusterType: 'project_type',
status: 'connected',
},
];
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Snippet Blob Edit component rendering matches the snapshot 1`] = `
<div
class="form-group file-editor"
>
<label>
File
</label>
<div
class="file-holder snippet"
>
<blob-header-edit-stub
value="lorem.txt"
/>
<blob-content-edit-stub
filename="lorem.txt"
value="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
/>
</div>
</div>
`;
import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue';
import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue';
import BlobContentEdit from '~/blob/components/blob_edit_content.vue';
import { shallowMount } from '@vue/test-utils';
jest.mock('~/blob/utils', () => jest.fn());
describe('Snippet Blob Edit component', () => {
let wrapper;
const content = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
const fileName = 'lorem.txt';
function createComponent() {
wrapper = shallowMount(SnippetBlobEdit, {
propsData: {
content,
fileName,
},
});
}
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('rendering', () => {
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('renders required components', () => {
expect(wrapper.contains(BlobHeaderEdit)).toBe(true);
expect(wrapper.contains(BlobContentEdit)).toBe(true);
});
});
});
import { mount } from '@vue/test-utils';
import { hexToRgb } from '~/lib/utils/color_utils';
import DropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue';
import DropdownValueScopedLabel from '~/vue_shared/components/sidebar/labels_select/dropdown_value_scoped_label.vue';
import { GlLabel } from '@gitlab/ui';
import {
mockConfig,
mockLabels,
} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
const labelStyles = {
textColor: '#FFFFFF',
color: '#BADA55',
};
const createComponent = (
labels = mockLabels,
labelFilterBasePath = mockConfig.labelFilterBasePath,
) => {
labels.forEach(label => Object.assign(label, labelStyles));
return mount(DropdownValueComponent, {
) =>
mount(DropdownValueComponent, {
propsData: {
labels,
labelFilterBasePath,
enableScopedLabels: true,
},
stubs: {
GlLabel: true,
},
});
};
describe('DropdownValueComponent', () => {
let vm;
......@@ -56,24 +51,17 @@ describe('DropdownValueComponent', () => {
describe('methods', () => {
describe('labelFilterUrl', () => {
it('returns URL string starting with labelFilterBasePath and encoded label.title', () => {
expect(vm.find(DropdownValueScopedLabel).props('labelFilterUrl')).toBe(
'/gitlab-org/my-project/issues?label_name[]=Foo%3A%3ABar',
expect(vm.find(GlLabel).props('target')).toBe(
'/gitlab-org/my-project/issues?label_name[]=Foo%20Label',
);
});
});
describe('labelStyle', () => {
it('returns object with `color` & `backgroundColor` properties from label.textColor & label.color', () => {
expect(vm.find(DropdownValueScopedLabel).props('labelStyle')).toEqual({
color: labelStyles.textColor,
backgroundColor: labelStyles.color,
});
});
});
describe('showScopedLabels', () => {
it('returns true if the label is scoped label', () => {
expect(vm.findAll(DropdownValueScopedLabel).length).toEqual(1);
const labels = vm.findAll(GlLabel);
expect(labels.length).toEqual(2);
expect(labels.at(1).props('scoped')).toBe(true);
});
});
});
......@@ -95,33 +83,10 @@ describe('DropdownValueComponent', () => {
vmEmptyLabels.destroy();
});
it('renders label element with filter URL', () => {
expect(vm.find('a').attributes('href')).toBe(
'/gitlab-org/my-project/issues?label_name[]=Foo%20Label',
);
});
it('renders label element and styles based on label details', () => {
const labelEl = vm.find('a span.badge.color-label');
it('renders DropdownValueComponent element', () => {
const labelEl = vm.find(GlLabel);
expect(labelEl.exists()).toBe(true);
expect(labelEl.attributes('style')).toContain(
`background-color: rgb(${hexToRgb(labelStyles.color).join(', ')});`,
);
expect(labelEl.text().trim()).toBe(mockLabels[0].title);
});
describe('label is of scoped-label type', () => {
it('renders a scoped-label-wrapper span to incorporate 2 anchors', () => {
expect(vm.find('span.scoped-label-wrapper').exists()).toBe(true);
});
it('renders anchor tag containing question icon', () => {
const anchor = vm.find('.scoped-label-wrapper a.scoped-label');
expect(anchor.exists()).toBe(true);
expect(anchor.find('i.fa-question-circle').exists()).toBe(true);
});
});
});
});
......@@ -32,7 +32,7 @@ describe('Board card', () => {
const label1 = new ListLabel({
id: 3,
title: 'testing 123',
color: 'blue',
color: '#000cff',
text_color: 'white',
description: 'test',
});
......@@ -155,12 +155,6 @@ describe('Board card', () => {
expect(boardsStore.detail.issue).toEqual({});
});
it('does not set detail issue if button is clicked', () => {
triggerEvent('mouseup', vm.$el.querySelector('button'));
expect(boardsStore.detail.issue).toEqual({});
});
it('does not set detail issue if img is clicked', done => {
vm.issue.assignees = [
new ListAssignee({
......
......@@ -11,6 +11,10 @@ describe Gitlab::Prometheus::QueryVariables do
subject { described_class.call(environment) }
it { is_expected.to include(ci_environment_slug: slug) }
it { is_expected.to include(ci_project_name: project.name) }
it { is_expected.to include(ci_project_namespace: project.namespace.name) }
it { is_expected.to include(ci_project_path: project.full_path) }
it { is_expected.to include(ci_environment_name: environment.name) }
it do
is_expected.to include(environment_filter:
......
......@@ -796,15 +796,15 @@
dependencies:
vue-eslint-parser "^7.0.0"
"@gitlab/svgs@^1.105.0":
version "1.105.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.105.0.tgz#9686f8696594a5f22de11af2b81fdcceb715f4f2"
integrity sha512-2wzZXe2b7DnGyL7FTbPq0dSpk+gjkq4SBTNtMrqdwX2qaM+XJB50XaMm17kdY5V1bBkMgbc7JJ2vgbLxhS/CkQ==
"@gitlab/svgs@^1.110.0":
version "1.110.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.110.0.tgz#3c4f5f0e78fcf616ec63a265754158b84ed80af8"
integrity sha512-bLVUW9Hj6j7zTdeoQELO3Bls5xDKr6AoSEU8gZbEZKLK9PV81hxRl/lJPJUo1qt4E7eJGapCTlH73tTIL4OZ3A==
"@gitlab/ui@^9.21.1":
version "9.21.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.21.1.tgz#76da9b86de959c2757a0c0a9389970f8d5afcc47"
integrity sha512-nJi2lFYq3WFXDNlH5vAg1Mb3Tf/PKnaVIm5W07I+hIWj/GALnwZHO3WHJuhwWIUTZOtLz7egIr4Wyh3EqBk+cg==
"@gitlab/ui@^9.23.0":
version "9.23.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.23.0.tgz#0ad0232c529d1f8a386c8e86159e273111a55686"
integrity sha512-1VOob5tNPB3zjLHeTuMbQBMG3q6LF36iCq6XqH5eeYzpAI42zj/WhY5T47RKrfvlkflWRSUPTarGo97pQqIKzg==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
......@@ -6872,10 +6872,10 @@ js-beautify@^1.6.12, js-beautify@^1.8.8:
mkdirp "~0.5.1"
nopt "~4.0.1"
js-cookie@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.1.3.tgz#48071625217ac9ecfab8c343a13d42ec09ff0526"
integrity sha1-SAcWJSF6yez6uMNDoT1C7An/BSY=
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment