Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
333f76ab66
commit
65f5f75e3a
|
@ -13,11 +13,35 @@ import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
|
|||
import { isInViewport, scrollToElement, isInMRPage } from '../../lib/utils/common_utils';
|
||||
import { mergeUrlParams } from '../../lib/utils/url_utility';
|
||||
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
|
||||
import updateIssueConfidentialMutation from '~/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import Api from '~/api';
|
||||
|
||||
let eTagPoll;
|
||||
|
||||
export const updateConfidentialityOnIssue = ({ commit, getters }, { confidential, fullPath }) => {
|
||||
const { iid } = getters.getNoteableData;
|
||||
|
||||
return utils.gqClient
|
||||
.mutate({
|
||||
mutation: updateIssueConfidentialMutation,
|
||||
variables: {
|
||||
input: {
|
||||
projectPath: fullPath,
|
||||
iid: String(iid),
|
||||
confidential,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
const {
|
||||
issueSetConfidential: { issue },
|
||||
} = data;
|
||||
|
||||
commit(types.SET_ISSUE_CONFIDENTIAL, issue.confidential);
|
||||
});
|
||||
};
|
||||
|
||||
export const expandDiscussion = ({ commit, dispatch }, data) => {
|
||||
if (data.discussionId) {
|
||||
dispatch('diffs/renderFileForDiscussionId', data.discussionId, { root: true });
|
||||
|
@ -32,6 +56,8 @@ export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, d
|
|||
|
||||
export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
|
||||
|
||||
export const setConfidentiality = ({ commit }, data) => commit(types.SET_ISSUE_CONFIDENTIAL, data);
|
||||
|
||||
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
|
||||
|
||||
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
|
||||
|
|
|
@ -39,6 +39,7 @@ export const CLOSE_ISSUE = 'CLOSE_ISSUE';
|
|||
export const REOPEN_ISSUE = 'REOPEN_ISSUE';
|
||||
export const TOGGLE_STATE_BUTTON_LOADING = 'TOGGLE_STATE_BUTTON_LOADING';
|
||||
export const TOGGLE_BLOCKED_ISSUE_WARNING = 'TOGGLE_BLOCKED_ISSUE_WARNING';
|
||||
export const SET_ISSUE_CONFIDENTIAL = 'SET_ISSUE_CONFIDENTIAL';
|
||||
|
||||
// Description version
|
||||
export const REQUEST_DESCRIPTION_VERSION = 'REQUEST_DESCRIPTION_VERSION';
|
||||
|
|
|
@ -95,6 +95,10 @@ export default {
|
|||
Object.assign(state, { noteableData: data });
|
||||
},
|
||||
|
||||
[types.SET_ISSUE_CONFIDENTIAL](state, data) {
|
||||
state.noteableData.confidential = data;
|
||||
},
|
||||
|
||||
[types.SET_USER_DATA](state, data) {
|
||||
Object.assign(state, { userData: data });
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import AjaxCache from '~/lib/utils/ajax_cache';
|
||||
import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
|
||||
import { sprintf, __ } from '~/locale';
|
||||
import createGqClient, { fetchPolicies } from '~/lib/graphql';
|
||||
|
||||
// factory function because global flag makes RegExp stateful
|
||||
const createQuickActionsRegex = () => /^\/\w+.*$/gm;
|
||||
|
@ -34,3 +35,10 @@ export const stripQuickActions = note => note.replace(createQuickActionsRegex(),
|
|||
|
||||
export const prepareDiffLines = diffLines =>
|
||||
diffLines.map(line => ({ ...trimFirstCharOfLineContent(line) }));
|
||||
|
||||
export const gqClient = createGqClient(
|
||||
{},
|
||||
{
|
||||
fetchPolicy: fetchPolicies.NO_CACHE,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -33,7 +33,7 @@ export default {
|
|||
errorTexts: {
|
||||
[LOAD_FAILURE]: __('We are currently unable to fetch data for this graph.'),
|
||||
[PARSE_FAILURE]: __('There was an error parsing the data for this graph.'),
|
||||
[UNSUPPORTED_DATA]: __('A DAG must have two dependent jobs to be visualized on this tab.'),
|
||||
[UNSUPPORTED_DATA]: __('DAG visualization requires at least 3 dependent jobs.'),
|
||||
[DEFAULT]: __('An unknown error occurred while loading this graph.'),
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { __ } from '~/locale';
|
||||
import Flash from '~/flash';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
|
@ -18,6 +18,10 @@ export default {
|
|||
},
|
||||
mixins: [recaptchaModalImplementor],
|
||||
props: {
|
||||
fullPath: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
isEditable: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
|
@ -42,16 +46,24 @@ export default {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
eventHub.$on('updateConfidentialAttribute', this.updateConfidentialAttribute);
|
||||
eventHub.$on('closeConfidentialityForm', this.toggleForm);
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('updateConfidentialAttribute', this.updateConfidentialAttribute);
|
||||
eventHub.$off('closeConfidentialityForm', this.toggleForm);
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setConfidentiality']),
|
||||
toggleForm() {
|
||||
this.edit = !this.edit;
|
||||
},
|
||||
updateConfidentialAttribute(confidential) {
|
||||
closeForm() {
|
||||
this.edit = false;
|
||||
},
|
||||
updateConfidentialAttribute() {
|
||||
// TODO: rm when FF is defaulted to on.
|
||||
const confidential = !this.confidential;
|
||||
this.service
|
||||
.update('issue', { confidential })
|
||||
.then(({ data }) => this.checkForSpam(data))
|
||||
|
@ -97,12 +109,8 @@ export default {
|
|||
>
|
||||
</div>
|
||||
<div class="value sidebar-item-value hide-collapsed">
|
||||
<edit-form
|
||||
v-if="edit"
|
||||
:is-confidential="confidential"
|
||||
:update-confidential-attribute="updateConfidentialAttribute"
|
||||
/>
|
||||
<div v-if="!confidential" class="no-value sidebar-item-value">
|
||||
<edit-form v-if="edit" :is-confidential="confidential" :full-path="fullPath" />
|
||||
<div v-if="!confidential" class="no-value sidebar-item-value" data-testid="not-confidential">
|
||||
<icon :size="16" name="eye" aria-hidden="true" class="sidebar-item-icon inline" />
|
||||
{{ __('Not confidential') }}
|
||||
</div>
|
||||
|
|
|
@ -11,9 +11,9 @@ export default {
|
|||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
updateConfidentialAttribute: {
|
||||
fullPath: {
|
||||
required: true,
|
||||
type: Function,
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
@ -37,10 +37,7 @@ export default {
|
|||
<div>
|
||||
<p v-if="!isConfidential" v-html="confidentialityOnWarning"></p>
|
||||
<p v-else v-html="confidentialityOffWarning"></p>
|
||||
<edit-form-buttons
|
||||
:is-confidential="isConfidential"
|
||||
:update-confidential-attribute="updateConfidentialAttribute"
|
||||
/>
|
||||
<edit-form-buttons :full-path="fullPath" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,35 +1,60 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import eventHub from '../../event_hub';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { __ } from '~/locale';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import Flash from '~/flash';
|
||||
import eventHub from '../../event_hub';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
isConfidential: {
|
||||
fullPath: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
updateConfidentialAttribute: {
|
||||
required: true,
|
||||
type: Function,
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ confidential: ({ noteableData }) => noteableData.confidential }),
|
||||
toggleButtonText() {
|
||||
return this.isConfidential ? __('Turn Off') : __('Turn On');
|
||||
},
|
||||
updateConfidentialBool() {
|
||||
return !this.isConfidential;
|
||||
if (this.isLoading) {
|
||||
return __('Applying');
|
||||
}
|
||||
|
||||
return this.confidential ? __('Turn Off') : __('Turn On');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateConfidentialityOnIssue']),
|
||||
closeForm() {
|
||||
eventHub.$emit('closeConfidentialityForm');
|
||||
$(this.$el).trigger('hidden.gl.dropdown');
|
||||
},
|
||||
submitForm() {
|
||||
this.closeForm();
|
||||
this.updateConfidentialAttribute(this.updateConfidentialBool);
|
||||
this.isLoading = true;
|
||||
const confidential = !this.confidential;
|
||||
|
||||
if (this.glFeatures.confidentialApolloSidebar) {
|
||||
this.updateConfidentialityOnIssue({ confidential, fullPath: this.fullPath })
|
||||
.catch(() => {
|
||||
Flash(__('Something went wrong trying to change the confidentiality of this issue'));
|
||||
})
|
||||
.finally(() => {
|
||||
this.closeForm();
|
||||
this.isLoading = false;
|
||||
});
|
||||
} else {
|
||||
eventHub.$emit('updateConfidentialAttribute');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -44,8 +69,10 @@ export default {
|
|||
type="button"
|
||||
class="btn btn-close"
|
||||
data-testid="confidential-toggle"
|
||||
:disabled="isLoading"
|
||||
@click.prevent="submitForm"
|
||||
>
|
||||
<gl-loading-icon v-if="isLoading" inline />
|
||||
{{ toggleButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
mutation updateIssueConfidential($input: IssueSetConfidentialInput!) {
|
||||
issueSetConfidential(input: $input) {
|
||||
issue {
|
||||
confidential
|
||||
}
|
||||
}
|
||||
}
|
|
@ -52,20 +52,30 @@ function mountAssigneesComponent(mediator) {
|
|||
function mountConfidentialComponent(mediator) {
|
||||
const el = document.getElementById('js-confidential-entry-point');
|
||||
|
||||
const { fullPath, iid } = getSidebarOptions();
|
||||
|
||||
if (!el) return;
|
||||
|
||||
const dataNode = document.getElementById('js-confidential-issue-data');
|
||||
const initialData = JSON.parse(dataNode.innerHTML);
|
||||
|
||||
const ConfidentialComp = Vue.extend(ConfidentialIssueSidebar);
|
||||
|
||||
new ConfidentialComp({
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
store,
|
||||
propsData: {
|
||||
isEditable: initialData.is_editable,
|
||||
service: mediator.service,
|
||||
components: {
|
||||
ConfidentialIssueSidebar,
|
||||
},
|
||||
}).$mount(el);
|
||||
render: createElement =>
|
||||
createElement('confidential-issue-sidebar', {
|
||||
props: {
|
||||
iid: String(iid),
|
||||
fullPath,
|
||||
isEditable: initialData.is_editable,
|
||||
service: mediator.service,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function mountLockComponent(mediator) {
|
||||
|
|
|
@ -415,7 +415,6 @@ img.emoji {
|
|||
.append-right-default { margin-right: $gl-padding; }
|
||||
.append-right-20 { margin-right: 20px; }
|
||||
.append-right-48 { margin-right: 48px; }
|
||||
.prepend-right-32 { margin-right: 32px; }
|
||||
.append-bottom-5 { margin-bottom: 5px; }
|
||||
.append-bottom-10 { margin-bottom: 10px; }
|
||||
.append-bottom-15 { margin-bottom: 15px; }
|
||||
|
|
|
@ -51,6 +51,8 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
before_action only: :show do
|
||||
push_frontend_feature_flag(:real_time_issue_sidebar, @project)
|
||||
push_frontend_feature_flag(:confidential_notes, @project)
|
||||
push_frontend_feature_flag(:confidential_apollo_sidebar, @project)
|
||||
end
|
||||
|
||||
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
|
||||
|
|
|
@ -320,7 +320,9 @@ module Ci
|
|||
# ref - The ref to scope the data to (e.g. "master"). If the ref is not
|
||||
# given we simply get the latest pipelines for the commits, regardless
|
||||
# of what refs the pipelines belong to.
|
||||
def self.latest_pipeline_per_commit(commits, ref = nil)
|
||||
# project_key - Support `commits` from different projects, returns results
|
||||
# keyed by `hash[project_id][commit_id]`
|
||||
def self.latest_pipeline_per_commit(commits, ref = nil, project_key: false)
|
||||
p1 = arel_table
|
||||
p2 = arel_table.alias
|
||||
|
||||
|
@ -341,7 +343,13 @@ module Ci
|
|||
relation = relation.where(ref: ref) if ref
|
||||
|
||||
relation.each_with_object({}) do |pipeline, hash|
|
||||
hash[pipeline.sha] = pipeline
|
||||
commits = if project_key
|
||||
hash[pipeline.project_id] ||= {}
|
||||
else
|
||||
hash
|
||||
end
|
||||
|
||||
commits[pipeline.sha] = pipeline
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -349,6 +357,10 @@ module Ci
|
|||
success.group(:project_id).select('max(id) as id')
|
||||
end
|
||||
|
||||
def self.last_finished_for_ref_id(ci_ref_id)
|
||||
where(ci_ref_id: ci_ref_id).ci_sources.finished.order(id: :desc).select(:id).take
|
||||
end
|
||||
|
||||
def self.truncate_sha(sha)
|
||||
sha[0...8]
|
||||
end
|
||||
|
|
|
@ -45,7 +45,8 @@ module Ci
|
|||
webide_source: 3,
|
||||
remote_source: 4,
|
||||
external_project_source: 5,
|
||||
bridge_source: 6
|
||||
bridge_source: 6,
|
||||
parameter_source: 7
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ module Ci
|
|||
end
|
||||
|
||||
def last_finished_pipeline_id
|
||||
Ci::Pipeline.where(ci_ref_id: self.id).finished.order(id: :desc).select(:id).take&.id
|
||||
Ci::Pipeline.last_finished_for_ref_id(self.id)&.id
|
||||
end
|
||||
|
||||
def update_status_by!(pipeline)
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# A collection of Commit instances for a specific container and Git reference.
|
||||
# A collection of Commit instances for a specific Git reference.
|
||||
class CommitCollection
|
||||
include Enumerable
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
attr_reader :container, :ref, :commits
|
||||
|
||||
delegate :repository, to: :container, allow_nil: true
|
||||
delegate :project, to: :repository, allow_nil: true
|
||||
|
||||
# container - The object the commits belong to.
|
||||
# container - The object the commits belong to (each commit project will be used if not provided).
|
||||
# commits - The Commit instances to store.
|
||||
# ref - The name of the ref (e.g. "master").
|
||||
def initialize(container, commits, ref = nil)
|
||||
|
@ -42,12 +39,13 @@ class CommitCollection
|
|||
# Setting the pipeline for each commit ahead of time removes the need for running
|
||||
# a query for every commit we're displaying.
|
||||
def with_latest_pipeline(ref = nil)
|
||||
return self unless project
|
||||
|
||||
pipelines = project.ci_pipelines.latest_pipeline_per_commit(map(&:id), ref)
|
||||
# since commit ids are not unique across all projects, use project_key = true to get commits by project
|
||||
pipelines = ::Ci::Pipeline.ci_sources.latest_pipeline_per_commit(map(&:id), ref, project_key: true)
|
||||
|
||||
# set the pipeline for each commit by project_id and commit for the latest pipeline for ref
|
||||
each do |commit|
|
||||
commit.set_latest_pipeline_for_ref(ref, pipelines[commit.id])
|
||||
project_id = container&.id || commit.project_id
|
||||
commit.set_latest_pipeline_for_ref(ref, pipelines.dig(project_id, commit.id))
|
||||
end
|
||||
|
||||
self
|
||||
|
@ -64,16 +62,19 @@ class CommitCollection
|
|||
# Batch load any commits that are not backed by full gitaly data, and
|
||||
# replace them in the collection.
|
||||
def enrich!
|
||||
return self if fully_enriched?
|
||||
|
||||
# Batch load full Commits from the repository
|
||||
# and map to a Hash of id => Commit
|
||||
|
||||
# A container is needed in order to fetch data from gitaly. Containers
|
||||
# can be absent from commits in certain rare situations (like when
|
||||
# viewing a MR of a deleted fork). In these cases, assume that the
|
||||
# enriched data is not needed.
|
||||
return self if container.blank? || fully_enriched?
|
||||
|
||||
# Batch load full Commits from the repository
|
||||
# and map to a Hash of id => Commit
|
||||
replacements = Hash[unenriched.map do |c|
|
||||
[c.id, Commit.lazy(container, c.id)]
|
||||
commits_to_enrich = unenriched.select { |c| container.present? || c.container.present? }
|
||||
replacements = Hash[commits_to_enrich.map do |c|
|
||||
commit_container = container || c.container
|
||||
[c.id, Commit.lazy(commit_container, c.id)]
|
||||
end.compact]
|
||||
|
||||
# Replace the commits, keeping the same order
|
||||
|
|
|
@ -520,6 +520,10 @@ class Project < ApplicationRecord
|
|||
group: :ip_restrictions, namespace: [:route, :owner])
|
||||
}
|
||||
|
||||
scope :with_api_commit_entity_associations, -> {
|
||||
preload(:project_feature, :route, namespace: [:route, :owner])
|
||||
}
|
||||
|
||||
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
|
||||
|
||||
chronic_duration_attr :build_timeout_human_readable, :build_timeout,
|
||||
|
@ -1699,7 +1703,7 @@ class Project < ApplicationRecord
|
|||
|
||||
def pages_url
|
||||
url = pages_group_url
|
||||
url_path = full_path.partition('/').last
|
||||
url_path = full_path.partition('/').last.downcase
|
||||
|
||||
# If the project path is the same as host, we serve it as group page
|
||||
return url if url == "#{Settings.pages.protocol}://#{url_path}"
|
||||
|
|
|
@ -23,6 +23,24 @@ module Ci
|
|||
Gitlab::Ci::Pipeline::Chain::Limit::Activity,
|
||||
Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze
|
||||
|
||||
# Create a new pipeline in the specified project.
|
||||
#
|
||||
# @param [Symbol] source What event (Ci::Pipeline.sources) triggers the pipeline
|
||||
# creation.
|
||||
# @param [Boolean] ignore_skip_ci Whether skipping a pipeline creation when `[skip ci]` comment
|
||||
# is present in the commit body
|
||||
# @param [Boolean] save_on_errors Whether persisting an invalid pipeline when it encounters an
|
||||
# error during creation (e.g. invalid yaml)
|
||||
# @param [Ci::TriggerRequest] trigger_request The pipeline trigger triggers the pipeline creation.
|
||||
# @param [Ci::PipelineSchedule] schedule The pipeline schedule triggers the pipeline creation.
|
||||
# @param [MergeRequest] merge_request The merge request triggers the pipeline creation.
|
||||
# @param [ExternalPullRequest] external_pull_request The external pull request triggers the pipeline creation.
|
||||
# @param [Ci::Bridge] bridge The bridge job that triggers the downstream pipeline creation.
|
||||
# @param [String] content The content of .gitlab-ci.yml to override the default config
|
||||
# contents (e.g. .gitlab-ci.yml in repostiry). Mainly used for
|
||||
# generating a dangling pipeline.
|
||||
#
|
||||
# @return [Ci::Pipeline] The created Ci::Pipeline object.
|
||||
# rubocop: disable Metrics/ParameterLists
|
||||
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, bridge: nil, **options, &block)
|
||||
@pipeline = Ci::Pipeline.new
|
||||
|
@ -122,13 +140,8 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def extra_options(options = {})
|
||||
# In Ruby 2.4, even when options is empty, f(**options) doesn't work when f
|
||||
# doesn't have any parameters. We reproduce the Ruby 2.5 behavior by
|
||||
# checking explicitly that no arguments are given.
|
||||
raise ArgumentError if options.any?
|
||||
|
||||
{} # overridden in EE
|
||||
def extra_options(content: nil)
|
||||
{ content: content }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -76,7 +76,7 @@ module Projects
|
|||
end
|
||||
|
||||
def parsed_payload
|
||||
Gitlab::Alerting::NotificationPayloadParser.call(params.to_h)
|
||||
Gitlab::Alerting::NotificationPayloadParser.call(params.to_h, project)
|
||||
end
|
||||
|
||||
def valid_token?(token)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Edit copy of DAG unsupported data alert
|
||||
merge_request: 35170
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix pages_url for projects with mixed case path
|
||||
merge_request: 35300
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Ability to use an arbitrary YAML blob to create CI pipelines
|
||||
merge_request: 34706
|
||||
author:
|
||||
type: added
|
|
@ -1346,6 +1346,51 @@ enum CommitEncoding {
|
|||
TEXT
|
||||
}
|
||||
|
||||
"""
|
||||
Represents a ComplianceFramework associated with a Project
|
||||
"""
|
||||
type ComplianceFramework {
|
||||
"""
|
||||
Name of the compliance framework
|
||||
"""
|
||||
name: ProjectSettingEnum!
|
||||
}
|
||||
|
||||
"""
|
||||
The connection type for ComplianceFramework.
|
||||
"""
|
||||
type ComplianceFrameworkConnection {
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [ComplianceFrameworkEdge]
|
||||
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [ComplianceFramework]
|
||||
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type ComplianceFrameworkEdge {
|
||||
"""
|
||||
A cursor for use in pagination.
|
||||
"""
|
||||
cursor: String!
|
||||
|
||||
"""
|
||||
The item at the end of the edge.
|
||||
"""
|
||||
node: ComplianceFramework
|
||||
}
|
||||
|
||||
"""
|
||||
A tag expiration policy designed to keep only the images that matter most
|
||||
"""
|
||||
|
@ -8750,6 +8795,31 @@ type Project {
|
|||
last: Int
|
||||
): BoardConnection
|
||||
|
||||
"""
|
||||
Compliance frameworks associated with the project
|
||||
"""
|
||||
complianceFrameworks(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): ComplianceFrameworkConnection
|
||||
|
||||
"""
|
||||
The container expiration policy of the project
|
||||
"""
|
||||
|
@ -10026,6 +10096,17 @@ type ProjectPermissions {
|
|||
uploadFile: Boolean!
|
||||
}
|
||||
|
||||
"""
|
||||
Names of compliance frameworks that can be assigned to a Project
|
||||
"""
|
||||
enum ProjectSettingEnum {
|
||||
gdpr
|
||||
hipaa
|
||||
pci_dss
|
||||
soc_2
|
||||
sox
|
||||
}
|
||||
|
||||
type ProjectStatistics {
|
||||
"""
|
||||
Build artifacts size of the project
|
||||
|
|
|
@ -3607,6 +3607,149 @@
|
|||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "ComplianceFramework",
|
||||
"description": "Represents a ComplianceFramework associated with a Project",
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the compliance framework",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "ProjectSettingEnum",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "ComplianceFrameworkConnection",
|
||||
"description": "The connection type for ComplianceFramework.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "edges",
|
||||
"description": "A list of edges.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "ComplianceFrameworkEdge",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "nodes",
|
||||
"description": "A list of nodes.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "ComplianceFramework",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pageInfo",
|
||||
"description": "Information to aid in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "PageInfo",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "ComplianceFrameworkEdge",
|
||||
"description": "An edge in a connection.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "cursor",
|
||||
"description": "A cursor for use in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "node",
|
||||
"description": "The item at the end of the edge.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "ComplianceFramework",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "ContainerExpirationPolicy",
|
||||
|
@ -26105,6 +26248,59 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "complianceFrameworks",
|
||||
"description": "Compliance frameworks associated with the project",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "ComplianceFrameworkConnection",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "containerExpirationPolicy",
|
||||
"description": "The container expiration policy of the project",
|
||||
|
@ -29593,6 +29789,47 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "ProjectSettingEnum",
|
||||
"description": "Names of compliance frameworks that can be assigned to a Project",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "gdpr",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "hipaa",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pci_dss",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "soc_2",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "sox",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "ProjectStatistics",
|
||||
|
|
|
@ -238,6 +238,14 @@ Autogenerated return type of CommitCreate
|
|||
| `commit` | Commit | The commit after mutation |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
|
||||
## ComplianceFramework
|
||||
|
||||
Represents a ComplianceFramework associated with a Project
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `name` | ProjectSettingEnum! | Name of the compliance framework |
|
||||
|
||||
## ContainerExpirationPolicy
|
||||
|
||||
A tag expiration policy designed to keep only the images that matter most
|
||||
|
|
|
@ -225,6 +225,47 @@ work between two different workers, one with `urgency :high` code that
|
|||
executes quickly, and the other with `urgency :low`, which has no
|
||||
execution latency requirements (but also has lower scheduling targets).
|
||||
|
||||
### Changing a queue's urgency
|
||||
|
||||
On GitLab.com, we run Sidekiq in several
|
||||
[shards](https://dashboards.gitlab.net/d/sidekiq-shard-detail/sidekiq-shard-detail),
|
||||
each of which represents a particular type of workload.
|
||||
|
||||
When changing a queue's urgency, or adding a new queue, we need to take
|
||||
into account the expected workload on the new shard. Note that, if we're
|
||||
changing an existing queue, there is also an effect on the old shard,
|
||||
but that will always be a reduction in work.
|
||||
|
||||
To do this, we want to calculate the expected increase in total execution time
|
||||
and RPS (throughput) for the new shard. We can get these values from:
|
||||
|
||||
- The [Queue Detail
|
||||
dashboard](https://dashboards.gitlab.net/d/sidekiq-queue-detail/sidekiq-queue-detail)
|
||||
has values for the queue itself. For a new queue, we can look for
|
||||
queues that have similar patterns or are scheduled in similar
|
||||
circumstances.
|
||||
- The [Shard Detail
|
||||
dashboard](https://dashboards.gitlab.net/d/sidekiq-shard-detail/sidekiq-shard-detail)
|
||||
has Total Execution Time and Throughput (RPS). The Shard Utilization
|
||||
panel will show if there is currently any excess capacity for this
|
||||
shard.
|
||||
|
||||
We can then calculate the RPS * average runtime (estimated for new jobs)
|
||||
for the queue we're changing to see what the relative increase in RPS and
|
||||
execution time we expect for the new shard:
|
||||
|
||||
```ruby
|
||||
new_queue_consumption = queue_rps * queue_duration_avg
|
||||
shard_consumption = shard_rps * shard_duration_avg
|
||||
|
||||
(new_queue_consumption / shard_consumption) * 100
|
||||
```
|
||||
|
||||
If we expect an increase of **less than 5%**, then no further action is needed.
|
||||
|
||||
Otherwise, please ping `@gitlab-org/scalability` on the merge request and ask
|
||||
for a review.
|
||||
|
||||
## Jobs with External Dependencies
|
||||
|
||||
Most background jobs in the GitLab application communicate with other GitLab
|
||||
|
|
|
@ -8,14 +8,15 @@ module API
|
|||
expose :project_id
|
||||
|
||||
expose :last_pipeline do |commit, options|
|
||||
pipeline = commit.last_pipeline if can_read_pipeline?
|
||||
pipeline = commit.latest_pipeline if can_read_pipeline?
|
||||
|
||||
::API::Entities::PipelineBasic.represent(pipeline, options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can_read_pipeline?
|
||||
Ability.allowed?(options[:current_user], :read_pipeline, object.last_pipeline)
|
||||
Ability.allowed?(options[:current_user], :read_pipeline, object.latest_pipeline)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,7 +24,8 @@ module API
|
|||
merge_requests: :with_api_entity_associations,
|
||||
projects: :with_api_entity_associations,
|
||||
issues: :with_api_entity_associations,
|
||||
milestones: :with_api_entity_associations
|
||||
milestones: :with_api_entity_associations,
|
||||
commits: :with_api_commit_entity_associations
|
||||
}.freeze
|
||||
|
||||
def search(additional_params = {})
|
||||
|
@ -38,6 +39,9 @@ module API
|
|||
|
||||
results = SearchService.new(current_user, search_params).search_objects(preload_method)
|
||||
|
||||
# preload commit data
|
||||
results = CommitCollection.new(nil, results).with_latest_pipeline if params[:scope].to_sym == :commits
|
||||
|
||||
Gitlab::UsageDataCounters::SearchCounter.count(:all_searches)
|
||||
|
||||
paginate(results)
|
||||
|
|
|
@ -8,7 +8,7 @@ module Gitlab
|
|||
}.freeze
|
||||
|
||||
def self.from_generic_alert(project:, payload:)
|
||||
parsed_payload = Gitlab::Alerting::NotificationPayloadParser.call(payload).with_indifferent_access
|
||||
parsed_payload = Gitlab::Alerting::NotificationPayloadParser.call(payload, project).with_indifferent_access
|
||||
annotations = parsed_payload[:annotations]
|
||||
|
||||
{
|
||||
|
|
|
@ -10,11 +10,14 @@ module Gitlab
|
|||
def generate(data)
|
||||
return unless data.present?
|
||||
|
||||
if data.is_a?(Array)
|
||||
data = flatten_array(data)
|
||||
end
|
||||
string = case data
|
||||
when Array then flatten_array(data)
|
||||
when Hash then flatten_hash(data)
|
||||
else
|
||||
data.to_s
|
||||
end
|
||||
|
||||
Digest::SHA1.hexdigest(data.to_s)
|
||||
Digest::SHA1.hexdigest(string)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -22,6 +25,11 @@ module Gitlab
|
|||
def flatten_array(array)
|
||||
array.flatten.map!(&:to_s).join
|
||||
end
|
||||
|
||||
def flatten_hash(hash)
|
||||
# Sort hash so SHA generated is the same
|
||||
Gitlab::Utils::SafeInlineHash.merge_keys!(hash).sort.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,12 +8,13 @@ module Gitlab
|
|||
DEFAULT_TITLE = 'New: Incident'
|
||||
DEFAULT_SEVERITY = 'critical'
|
||||
|
||||
def initialize(payload)
|
||||
def initialize(payload, project)
|
||||
@payload = payload.to_h.with_indifferent_access
|
||||
@project = project
|
||||
end
|
||||
|
||||
def self.call(payload)
|
||||
new(payload).call
|
||||
def self.call(payload, project)
|
||||
new(payload, project).call
|
||||
end
|
||||
|
||||
def call
|
||||
|
@ -25,7 +26,7 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
attr_reader :payload
|
||||
attr_reader :payload, :project
|
||||
|
||||
def title
|
||||
payload[:title].presence || DEFAULT_TITLE
|
||||
|
@ -84,3 +85,5 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Alerting::NotificationPayloadParser.prepend_if_ee('EE::Gitlab::Alerting::NotificationPayloadParser')
|
||||
|
|
|
@ -10,7 +10,7 @@ module Gitlab
|
|||
:trigger_request, :schedule, :merge_request, :external_pull_request,
|
||||
:ignore_skip_ci, :save_incompleted,
|
||||
:seeds_block, :variables_attributes, :push_options,
|
||||
:chat_data, :allow_mirror_update, :bridge,
|
||||
:chat_data, :allow_mirror_update, :bridge, :content,
|
||||
# These attributes are set by Chains during processing:
|
||||
:config_content, :config_processor, :stage_seeds
|
||||
) do
|
||||
|
|
|
@ -9,6 +9,7 @@ module Gitlab
|
|||
include Chain::Helpers
|
||||
|
||||
SOURCES = [
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Content::Parameter,
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Content::Bridge,
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Content::Repository,
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Content::ExternalProject,
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
module Pipeline
|
||||
module Chain
|
||||
module Config
|
||||
class Content
|
||||
class Parameter < Source
|
||||
def content
|
||||
strong_memoize(:content) do
|
||||
next unless command.content.present?
|
||||
|
||||
command.content
|
||||
end
|
||||
end
|
||||
|
||||
def source
|
||||
:parameter_source
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,6 +11,8 @@ module Gitlab
|
|||
|
||||
DEFAULT_YAML_FILE = '.gitlab-ci.yml'
|
||||
|
||||
attr_reader :command
|
||||
|
||||
def initialize(pipeline, command)
|
||||
@pipeline = pipeline
|
||||
@command = command
|
||||
|
|
|
@ -962,9 +962,6 @@ msgstr ""
|
|||
msgid "A .NET Core console application template, customizable for any .NET Core project"
|
||||
msgstr ""
|
||||
|
||||
msgid "A DAG must have two dependent jobs to be visualized on this tab."
|
||||
msgstr ""
|
||||
|
||||
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
|
||||
msgstr ""
|
||||
|
||||
|
@ -2685,6 +2682,9 @@ msgstr ""
|
|||
msgid "Apply this approval rule to any branch or a specific protected branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "Applying"
|
||||
msgstr ""
|
||||
|
||||
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
|
||||
msgstr ""
|
||||
|
||||
|
@ -7105,6 +7105,9 @@ msgstr ""
|
|||
msgid "DAG"
|
||||
msgstr ""
|
||||
|
||||
msgid "DAG visualization requires at least 3 dependent jobs."
|
||||
msgstr ""
|
||||
|
||||
msgid "DNS"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -40,8 +40,8 @@
|
|||
"@babel/plugin-syntax-import-meta": "^7.10.1",
|
||||
"@babel/preset-env": "^7.10.1",
|
||||
"@gitlab/at.js": "1.5.5",
|
||||
"@gitlab/svgs": "1.141.0",
|
||||
"@gitlab/ui": "17.2.0",
|
||||
"@gitlab/svgs": "1.143.0",
|
||||
"@gitlab/ui": "17.6.1",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-1",
|
||||
"@sentry/browser": "^5.10.2",
|
||||
|
|
|
@ -18,6 +18,8 @@ import {
|
|||
batchSuggestionsInfoMock,
|
||||
} from '../mock_data';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import * as utils from '~/notes/stores/utils';
|
||||
import updateIssueConfidentialMutation from '~/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql';
|
||||
|
||||
const TEST_ERROR_MESSAGE = 'Test error message';
|
||||
jest.mock('~/flash');
|
||||
|
@ -1142,6 +1144,14 @@ describe('Actions Notes Store', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('setConfidentiality', () => {
|
||||
it('calls the correct mutation with the correct args', () => {
|
||||
testAction(actions.setConfidentiality, true, { noteableData: { confidential: false } }, [
|
||||
{ type: mutationTypes.SET_ISSUE_CONFIDENTIAL, payload: true },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAssignees', () => {
|
||||
it('update the assignees state', done => {
|
||||
testAction(
|
||||
|
@ -1154,4 +1164,49 @@ describe('Actions Notes Store', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateConfidentialityOnIssue', () => {
|
||||
state = { noteableData: { confidential: false } };
|
||||
const iid = '1';
|
||||
const projectPath = 'full/path';
|
||||
const getters = { getNoteableData: { iid } };
|
||||
const actionArgs = { fullPath: projectPath, confidential: true };
|
||||
const confidential = true;
|
||||
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(utils.gqClient, 'mutate')
|
||||
.mockResolvedValue({ data: { issueSetConfidential: { issue: { confidential } } } });
|
||||
});
|
||||
|
||||
it('calls gqClient mutation one time', () => {
|
||||
actions.updateConfidentialityOnIssue({ commit: () => {}, state, getters }, actionArgs);
|
||||
|
||||
expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls gqClient mutation with the correct values', () => {
|
||||
actions.updateConfidentialityOnIssue({ commit: () => {}, state, getters }, actionArgs);
|
||||
|
||||
expect(utils.gqClient.mutate).toHaveBeenCalledWith({
|
||||
mutation: updateIssueConfidentialMutation,
|
||||
variables: { input: { iid, projectPath, confidential } },
|
||||
});
|
||||
});
|
||||
|
||||
describe('on success of mutation', () => {
|
||||
it('calls commit with the correct values', () => {
|
||||
const commitSpy = jest.fn();
|
||||
|
||||
return actions
|
||||
.updateConfidentialityOnIssue({ commit: commitSpy, state, getters }, actionArgs)
|
||||
.then(() => {
|
||||
expect(commitSpy).toHaveBeenCalledWith(
|
||||
mutationTypes.SET_ISSUE_CONFIDENTIAL,
|
||||
confidential,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -806,6 +806,20 @@ describe('Notes Store mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('SET_ISSUE_CONFIDENTIAL', () => {
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
state = { noteableData: { confidential: false } };
|
||||
});
|
||||
|
||||
it('sets sort order', () => {
|
||||
mutations.SET_ISSUE_CONFIDENTIAL(state, true);
|
||||
|
||||
expect(state.noteableData.confidential).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UPDATE_ASSIGNEES', () => {
|
||||
it('should update assignees', () => {
|
||||
const state = {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
exports[`Confidential Issue Sidebar Block renders for confidential = false and isEditable = false 1`] = `
|
||||
<div
|
||||
class="block issuable-sidebar-item confidentiality"
|
||||
iid=""
|
||||
>
|
||||
<div
|
||||
class="sidebar-collapsed-icon"
|
||||
|
@ -35,6 +36,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
|
|||
|
||||
<div
|
||||
class="no-value sidebar-item-value"
|
||||
data-testid="not-confidential"
|
||||
>
|
||||
<icon-stub
|
||||
aria-hidden="true"
|
||||
|
@ -55,6 +57,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
|
|||
exports[`Confidential Issue Sidebar Block renders for confidential = false and isEditable = true 1`] = `
|
||||
<div
|
||||
class="block issuable-sidebar-item confidentiality"
|
||||
iid=""
|
||||
>
|
||||
<div
|
||||
class="sidebar-collapsed-icon"
|
||||
|
@ -95,6 +98,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
|
|||
|
||||
<div
|
||||
class="no-value sidebar-item-value"
|
||||
data-testid="not-confidential"
|
||||
>
|
||||
<icon-stub
|
||||
aria-hidden="true"
|
||||
|
@ -115,6 +119,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
|
|||
exports[`Confidential Issue Sidebar Block renders for confidential = true and isEditable = false 1`] = `
|
||||
<div
|
||||
class="block issuable-sidebar-item confidentiality"
|
||||
iid=""
|
||||
>
|
||||
<div
|
||||
class="sidebar-collapsed-icon"
|
||||
|
@ -167,6 +172,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = true and is
|
|||
exports[`Confidential Issue Sidebar Block renders for confidential = true and isEditable = true 1`] = `
|
||||
<div
|
||||
class="block issuable-sidebar-item confidentiality"
|
||||
iid=""
|
||||
>
|
||||
<div
|
||||
class="sidebar-collapsed-icon"
|
||||
|
|
|
@ -1,16 +1,49 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import EditFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue';
|
||||
import eventHub from '~/sidebar/event_hub';
|
||||
import createStore from '~/notes/stores';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import flash from '~/flash';
|
||||
|
||||
jest.mock('~/sidebar/event_hub', () => ({ $emit: jest.fn() }));
|
||||
jest.mock('~/flash');
|
||||
|
||||
describe('Edit Form Buttons', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
const findConfidentialToggle = () => wrapper.find('[data-testid="confidential-toggle"]');
|
||||
|
||||
const createComponent = props => {
|
||||
const createComponent = ({
|
||||
props = {},
|
||||
data = {},
|
||||
confidentialApolloSidebar = false,
|
||||
resolved = true,
|
||||
}) => {
|
||||
store = createStore();
|
||||
if (resolved) {
|
||||
jest.spyOn(store, 'dispatch').mockResolvedValue();
|
||||
} else {
|
||||
jest.spyOn(store, 'dispatch').mockRejectedValue();
|
||||
}
|
||||
|
||||
wrapper = shallowMount(EditFormButtons, {
|
||||
propsData: {
|
||||
updateConfidentialAttribute: () => {},
|
||||
fullPath: '',
|
||||
...props,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
...data,
|
||||
};
|
||||
},
|
||||
provide: {
|
||||
glFeatures: {
|
||||
confidentialApolloSidebar,
|
||||
},
|
||||
},
|
||||
store,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -19,10 +52,32 @@ describe('Edit Form Buttons', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('when isLoading', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({});
|
||||
|
||||
wrapper.vm.$store.state.noteableData.confidential = false;
|
||||
});
|
||||
|
||||
it('renders "Applying" in the toggle button', () => {
|
||||
expect(findConfidentialToggle().text()).toBe('Applying');
|
||||
});
|
||||
|
||||
it('disables the toggle button', () => {
|
||||
expect(findConfidentialToggle().attributes('disabled')).toBe('disabled');
|
||||
});
|
||||
|
||||
it('finds the GlLoadingIcon', () => {
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when not confidential', () => {
|
||||
it('renders Turn On in the ', () => {
|
||||
it('renders Turn On in the toggle button', () => {
|
||||
createComponent({
|
||||
isConfidential: false,
|
||||
data: {
|
||||
isLoading: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findConfidentialToggle().text()).toBe('Turn On');
|
||||
|
@ -30,12 +85,75 @@ describe('Edit Form Buttons', () => {
|
|||
});
|
||||
|
||||
describe('when confidential', () => {
|
||||
it('renders on or off text based on confidentiality', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
isConfidential: true,
|
||||
data: {
|
||||
isLoading: false,
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.vm.$store.state.noteableData.confidential = true;
|
||||
});
|
||||
|
||||
it('renders on or off text based on confidentiality', () => {
|
||||
expect(findConfidentialToggle().text()).toBe('Turn Off');
|
||||
});
|
||||
|
||||
describe('when clicking on the confidential toggle', () => {
|
||||
it('emits updateConfidentialAttribute', () => {
|
||||
findConfidentialToggle().trigger('click');
|
||||
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('updateConfidentialAttribute');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when confidentialApolloSidebar is turned on', () => {
|
||||
const isConfidential = true;
|
||||
|
||||
describe('when succeeds', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ data: { isLoading: false }, confidentialApolloSidebar: true });
|
||||
wrapper.vm.$store.state.noteableData.confidential = isConfidential;
|
||||
findConfidentialToggle().trigger('click');
|
||||
});
|
||||
|
||||
it('dispatches the correct action', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith('updateConfidentialityOnIssue', {
|
||||
confidential: !isConfidential,
|
||||
fullPath: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('resets loading', () => {
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('emits close form', () => {
|
||||
return waitForPromises().then(() => {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('closeConfidentialityForm');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when fails', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
data: { isLoading: false },
|
||||
confidentialApolloSidebar: true,
|
||||
resolved: false,
|
||||
});
|
||||
wrapper.vm.$store.state.noteableData.confidential = isConfidential;
|
||||
findConfidentialToggle().trigger('click');
|
||||
});
|
||||
|
||||
it('calls flash with the correct message', () => {
|
||||
expect(flash).toHaveBeenCalledWith(
|
||||
'Something went wrong trying to change the confidentiality of this issue',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,8 @@ describe('Edit Form Dropdown', () => {
|
|||
wrapper = shallowMount(EditForm, {
|
||||
propsData: {
|
||||
...props,
|
||||
isLoading: false,
|
||||
fullPath: '',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ import createFlash from '~/flash';
|
|||
import RecaptchaModal from '~/vue_shared/components/recaptcha_modal.vue';
|
||||
import createStore from '~/notes/stores';
|
||||
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
|
||||
import eventHub from '~/sidebar/event_hub';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/sidebar/services/sidebar_service');
|
||||
|
@ -15,6 +16,9 @@ describe('Confidential Issue Sidebar Block', () => {
|
|||
useMockLocationHelper();
|
||||
|
||||
let wrapper;
|
||||
const mutate = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { issueSetConfidential: { issue: { confidential: true } } } });
|
||||
|
||||
const findRecaptchaModal = () => wrapper.find(RecaptchaModal);
|
||||
|
||||
|
@ -25,24 +29,32 @@ describe('Confidential Issue Sidebar Block', () => {
|
|||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
const editForm = wrapper.find(EditForm);
|
||||
const { updateConfidentialAttribute } = editForm.props();
|
||||
updateConfidentialAttribute();
|
||||
eventHub.$emit('updateConfidentialAttribute');
|
||||
})
|
||||
// wait for reCAPTCHA modal to render
|
||||
.then(() => wrapper.vm.$nextTick())
|
||||
);
|
||||
};
|
||||
|
||||
const createComponent = propsData => {
|
||||
const createComponent = ({ propsData, data = {} }) => {
|
||||
const store = createStore();
|
||||
const service = new SidebarService();
|
||||
wrapper = shallowMount(ConfidentialIssueSidebar, {
|
||||
store,
|
||||
data() {
|
||||
return data;
|
||||
},
|
||||
propsData: {
|
||||
service,
|
||||
iid: '',
|
||||
fullPath: '',
|
||||
...propsData,
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
mutate,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -60,7 +72,9 @@ describe('Confidential Issue Sidebar Block', () => {
|
|||
'renders for confidential = $confidential and isEditable = $isEditable',
|
||||
({ confidential, isEditable }) => {
|
||||
createComponent({
|
||||
isEditable,
|
||||
propsData: {
|
||||
isEditable,
|
||||
},
|
||||
});
|
||||
wrapper.vm.$store.state.noteableData.confidential = confidential;
|
||||
|
||||
|
@ -73,7 +87,9 @@ describe('Confidential Issue Sidebar Block', () => {
|
|||
describe('if editable', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
isEditable: true,
|
||||
propsData: {
|
||||
isEditable: true,
|
||||
},
|
||||
});
|
||||
wrapper.vm.$store.state.noteableData.confidential = true;
|
||||
});
|
||||
|
|
|
@ -13,34 +13,62 @@ RSpec.describe Gitlab::AlertManagement::Fingerprint do
|
|||
context 'when data is an array' do
|
||||
let(:data) { [1, 'fingerprint', 'given'] }
|
||||
|
||||
it 'flattens the array' do
|
||||
expect_next_instance_of(described_class) do |obj|
|
||||
expect(obj).to receive(:flatten_array)
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it 'returns the hashed fingerprint' do
|
||||
expected_fingerprint = Digest::SHA1.hexdigest(data.flatten.map!(&:to_s).join)
|
||||
expect(subject).to eq(expected_fingerprint)
|
||||
end
|
||||
|
||||
context 'with a variety of data' do
|
||||
where(:data) do
|
||||
[
|
||||
111,
|
||||
'fingerprint',
|
||||
:fingerprint,
|
||||
true
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'performs like a hashed fingerprint' do
|
||||
expect(subject).to eq(Digest::SHA1.hexdigest(data.to_s))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when data is a non-array type' do
|
||||
where(:data) do
|
||||
[
|
||||
111,
|
||||
'fingerprint',
|
||||
:fingerprint,
|
||||
true,
|
||||
{ test: true }
|
||||
]
|
||||
context 'when data is a hash' do
|
||||
let(:data) { { test: true } }
|
||||
|
||||
shared_examples 'fingerprinted Hash' do
|
||||
it 'performs like a hashed fingerprint' do
|
||||
flattened_hash = Gitlab::Utils::SafeInlineHash.merge_keys!(data).sort.to_s
|
||||
expect(subject).to eq(Digest::SHA1.hexdigest(flattened_hash))
|
||||
end
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'performs like a hashed fingerprint' do
|
||||
expect(subject).to eq(Digest::SHA1.hexdigest(data.to_s))
|
||||
it_behaves_like 'fingerprinted Hash'
|
||||
|
||||
context 'hashes with different order' do
|
||||
it 'calculates the same result' do
|
||||
data = { test: true, another_test: 1 }
|
||||
data_hash = described_class.generate(data)
|
||||
|
||||
reverse_data = { another_test: 1, test: true }
|
||||
reverse_data_hash = described_class.generate(reverse_data)
|
||||
|
||||
expect(data_hash).to eq(reverse_data_hash)
|
||||
end
|
||||
end
|
||||
|
||||
context 'hash is too large' do
|
||||
before do
|
||||
expect_next_instance_of(Gitlab::Utils::SafeInlineHash) do |obj|
|
||||
expect(obj).to receive(:valid?).and_return(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Alerting::NotificationPayloadParser do
|
||||
let_it_be(:project) { build(:project) }
|
||||
|
||||
describe '.call' do
|
||||
let(:starts_at) { Time.current.change(usec: 0) }
|
||||
let(:payload) do
|
||||
|
@ -17,7 +19,7 @@ RSpec.describe Gitlab::Alerting::NotificationPayloadParser do
|
|||
}
|
||||
end
|
||||
|
||||
subject { described_class.call(payload) }
|
||||
subject { described_class.call(payload, project) }
|
||||
|
||||
it 'returns Prometheus-like payload' do
|
||||
is_expected.to eq(
|
||||
|
|
|
@ -5,7 +5,8 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content do
|
||||
let(:project) { create(:project, ci_config_path: ci_config_path) }
|
||||
let(:pipeline) { build(:ci_pipeline, project: project) }
|
||||
let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project) }
|
||||
let(:content) { nil }
|
||||
let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project, content: content) }
|
||||
|
||||
subject { described_class.new(pipeline, command) }
|
||||
|
||||
|
@ -141,6 +142,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when config is passed as a parameter' do
|
||||
let(:ci_config_path) { nil }
|
||||
let(:content) do
|
||||
<<~EOY
|
||||
---
|
||||
stages:
|
||||
- dast
|
||||
EOY
|
||||
end
|
||||
|
||||
it 'uses the parameter content' do
|
||||
subject.perform!
|
||||
|
||||
expect(pipeline.config_source).to eq 'parameter_source'
|
||||
expect(pipeline.pipeline_config.content).to eq(content)
|
||||
expect(command.config_content).to eq(content)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when config is not defined anywhere' do
|
||||
let(:ci_config_path) { nil }
|
||||
|
||||
|
|
|
@ -1923,6 +1923,37 @@ RSpec.describe Ci::Pipeline, :mailer do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when there are two pipelines for a ref, sha across multiple projects' do
|
||||
let(:project_2) { create(:project) }
|
||||
|
||||
let!(:commit_456_project_2_ref_test) do
|
||||
create(
|
||||
:ci_empty_pipeline,
|
||||
status: 'success',
|
||||
ref: 'test',
|
||||
sha: '456',
|
||||
project: project_2
|
||||
)
|
||||
end
|
||||
|
||||
context 'when project_key is false' do
|
||||
it 'returns the latest pipeline' do
|
||||
result = described_class.latest_pipeline_per_commit(%w[456])
|
||||
|
||||
expect(result).to match('456' => commit_456_project_2_ref_test)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project_key is true' do
|
||||
it 'returns the latest pipeline per project' do
|
||||
result = described_class.latest_pipeline_per_commit(%w[456], project_key: true)
|
||||
|
||||
expect(result[project.id]).to match('456' => commit_456_ref_test)
|
||||
expect(result[project_2.id]).to match('456' => commit_456_project_2_ref_test)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a ref' do
|
||||
it 'only includes the pipelines for the given ref' do
|
||||
result = described_class.latest_pipeline_per_commit(%w[123 456], 'master')
|
||||
|
@ -1947,6 +1978,23 @@ RSpec.describe Ci::Pipeline, :mailer do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.last_finished_for_ref_id' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:branch) { project.default_branch }
|
||||
let(:ref) { project.ci_refs.take }
|
||||
let(:config_source) { Ci::PipelineEnums.config_sources[:parameter_source] }
|
||||
let!(:pipeline1) { create(:ci_pipeline, :success, project: project, ref: branch) }
|
||||
let!(:pipeline2) { create(:ci_pipeline, :success, project: project, ref: branch) }
|
||||
let!(:pipeline3) { create(:ci_pipeline, :failed, project: project, ref: branch) }
|
||||
let!(:pipeline4) { create(:ci_pipeline, :success, project: project, ref: branch) }
|
||||
let!(:pipeline5) { create(:ci_pipeline, :success, project: project, ref: branch, config_source: config_source) }
|
||||
|
||||
it 'returns the expected pipeline' do
|
||||
result = described_class.last_finished_for_ref_id(ref.id)
|
||||
expect(result).to eq(pipeline4)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.internal_sources' do
|
||||
subject { described_class.internal_sources }
|
||||
|
||||
|
|
|
@ -62,6 +62,35 @@ RSpec.describe Ci::Ref do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#last_finished_pipeline_id' do
|
||||
let(:pipeline_status) { :running }
|
||||
let(:config_source) { Ci::PipelineEnums.config_sources[:repository_source] }
|
||||
let(:pipeline) { create(:ci_pipeline, pipeline_status, config_source: config_source) }
|
||||
let(:ci_ref) { pipeline.ci_ref }
|
||||
|
||||
context 'when there are no finished pipelines' do
|
||||
it 'returns nil' do
|
||||
expect(ci_ref.last_finished_pipeline_id).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are finished pipelines' do
|
||||
let(:pipeline_status) { :success }
|
||||
|
||||
it 'returns the pipeline id' do
|
||||
expect(ci_ref.last_finished_pipeline_id).to eq(pipeline.id)
|
||||
end
|
||||
|
||||
context 'when the pipeline is not a ci_source' do
|
||||
let(:config_source) { Ci::PipelineEnums.config_sources[:parameter_source] }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(ci_ref.last_finished_pipeline_id).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_status_by!' do
|
||||
subject { ci_ref.update_status_by!(pipeline) }
|
||||
|
||||
|
|
|
@ -1651,6 +1651,14 @@ RSpec.describe Project do
|
|||
let(:project_name) { 'group.example.com' }
|
||||
|
||||
it { is_expected.to eq("http://group.example.com") }
|
||||
|
||||
context 'mixed case path' do
|
||||
before do
|
||||
project.update!(path: 'Group.example.com')
|
||||
end
|
||||
|
||||
it { is_expected.to eq("http://group.example.com") }
|
||||
end
|
||||
end
|
||||
|
||||
context 'project page' do
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ci::CreatePipelineService do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:user) { create(:admin) }
|
||||
let(:service) { described_class.new(project, user, { ref: 'refs/heads/master' }) }
|
||||
let(:content) do
|
||||
<<~EOY
|
||||
---
|
||||
stages:
|
||||
- dast
|
||||
|
||||
variables:
|
||||
DAST_VERSION: 1
|
||||
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
|
||||
|
||||
dast:
|
||||
stage: dast
|
||||
image:
|
||||
name: "$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION"
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
script:
|
||||
- /analyze
|
||||
EOY
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject { service.execute(:web, content: content) }
|
||||
|
||||
context 'parameter config content' do
|
||||
it 'creates a pipeline' do
|
||||
expect(subject).to be_persisted
|
||||
end
|
||||
|
||||
it 'creates builds with the correct names' do
|
||||
expect(subject.builds.pluck(:name)).to match_array %w[dast]
|
||||
end
|
||||
|
||||
it 'creates stages with the correct names' do
|
||||
expect(subject.stages.pluck(:name)).to match_array %w[dast]
|
||||
end
|
||||
|
||||
it 'sets the correct config source' do
|
||||
expect(subject.config_source).to eq 'parameter_source'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
yarn.lock
16
yarn.lock
|
@ -843,15 +843,15 @@
|
|||
eslint-plugin-vue "^6.2.1"
|
||||
vue-eslint-parser "^7.0.0"
|
||||
|
||||
"@gitlab/svgs@1.141.0":
|
||||
version "1.141.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.141.0.tgz#0d6c03511180669538be5c63a96b2ae28840bbf4"
|
||||
integrity sha512-6k4HA0jVGMb/47bqcflSdpLGpo0rN2yd5K2X39LVQxukrg56PdZQvFPxT2UDOgChLstEtmN/iJTZuXqpeVOg+g==
|
||||
"@gitlab/svgs@1.143.0":
|
||||
version "1.143.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.143.0.tgz#94bb9c09dd328ea7c125b21349f7237e9d6786b8"
|
||||
integrity sha512-KZSMfR2DWT4EeEMGopbFOpqK1F2X9BXdUpUaLp8ovOmFJ1mtiMxNk9QYkejhx91IQrc8wwYp/XPPSeGOZjjCnA==
|
||||
|
||||
"@gitlab/ui@17.2.0":
|
||||
version "17.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.2.0.tgz#c4bca963c987f21131be0a650bca47b3708714f2"
|
||||
integrity sha512-pCzHoA41ggaPjN7612I5MxXq370Utlml9joUuo92BAXQk5XslAJQOFkdmyF/E89qaT7vgWurVAfPylqnSc5LrA==
|
||||
"@gitlab/ui@17.6.1":
|
||||
version "17.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.6.1.tgz#3c7cd6463a0d38c7c5037b5ca861af99680df9ae"
|
||||
integrity sha512-iCgcKn8hxn2k/763t77CRf07Dnw7wycl+rCUN8UIzBKe4yNswaNVrvjcdfM/TXe46EUVkQ2LfrwPuBShXAeHCA==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
"@gitlab/vue-toasted" "^1.3.0"
|
||||
|
|
Loading…
Reference in New Issue