Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1c7411c597
commit
e40f19ef83
|
@ -1,8 +1,16 @@
|
|||
<script>
|
||||
import { GlAlert, GlFormGroup, GlFormInputGroup, GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
|
||||
import {
|
||||
GlAlert,
|
||||
GlFormGroup,
|
||||
GlFormInputGroup,
|
||||
GlSkeletonLoader,
|
||||
GlSprintf,
|
||||
GlEmptyState,
|
||||
} from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
|
||||
import {
|
||||
DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
|
||||
DEPENDENCY_PROXY_DOCS_PATH,
|
||||
|
@ -13,15 +21,17 @@ import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency
|
|||
|
||||
export default {
|
||||
components: {
|
||||
GlFormGroup,
|
||||
GlAlert,
|
||||
GlEmptyState,
|
||||
GlFormGroup,
|
||||
GlFormInputGroup,
|
||||
GlSkeletonLoader,
|
||||
GlSprintf,
|
||||
ClipboardButton,
|
||||
TitleArea,
|
||||
GlSkeletonLoader,
|
||||
ManifestsList,
|
||||
},
|
||||
inject: ['groupPath', 'dependencyProxyAvailable'],
|
||||
inject: ['groupPath', 'dependencyProxyAvailable', 'noManifestsIllustration'],
|
||||
i18n: {
|
||||
proxyNotAvailableText: s__(
|
||||
'DependencyProxy|Dependency Proxy feature is limited to public groups for now.',
|
||||
|
@ -33,6 +43,7 @@ export default {
|
|||
copyImagePrefixText: s__('DependencyProxy|Copy prefix'),
|
||||
blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'),
|
||||
pageTitle: s__('DependencyProxy|Dependency Proxy'),
|
||||
noManifestTitle: s__('DependencyProxy|There are no images in the cache'),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -46,7 +57,7 @@ export default {
|
|||
return !this.dependencyProxyAvailable;
|
||||
},
|
||||
variables() {
|
||||
return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
|
||||
return this.queryVariables;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -62,6 +73,38 @@ export default {
|
|||
dependencyProxyEnabled() {
|
||||
return this.group?.dependencyProxySetting?.enabled;
|
||||
},
|
||||
queryVariables() {
|
||||
return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
|
||||
},
|
||||
pageInfo() {
|
||||
return this.group.dependencyProxyManifests.pageInfo;
|
||||
},
|
||||
manifests() {
|
||||
return this.group.dependencyProxyManifests.nodes;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchNextPage() {
|
||||
this.fetchMore({
|
||||
first: GRAPHQL_PAGE_SIZE,
|
||||
after: this.pageInfo?.endCursor,
|
||||
});
|
||||
},
|
||||
fetchPreviousPage() {
|
||||
this.fetchMore({
|
||||
first: null,
|
||||
last: GRAPHQL_PAGE_SIZE,
|
||||
before: this.pageInfo?.startCursor,
|
||||
});
|
||||
},
|
||||
fetchMore(variables) {
|
||||
this.$apollo.queries.group.fetchMore({
|
||||
variables: { ...this.queryVariables, ...variables },
|
||||
updateQuery(_, { fetchMoreResult }) {
|
||||
return fetchMoreResult;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -103,6 +146,20 @@ export default {
|
|||
</span>
|
||||
</template>
|
||||
</gl-form-group>
|
||||
|
||||
<manifests-list
|
||||
v-if="manifests && manifests.length"
|
||||
:manifests="manifests"
|
||||
:pagination="pageInfo"
|
||||
@prev-page="fetchPreviousPage"
|
||||
@next-page="fetchNextPage"
|
||||
/>
|
||||
|
||||
<gl-empty-state
|
||||
v-else
|
||||
:svg-path="noManifestsIllustration"
|
||||
:title="$options.i18n.noManifestTitle"
|
||||
/>
|
||||
</div>
|
||||
<gl-alert v-else :dismissible="false" data-testid="proxy-disabled">
|
||||
{{ $options.i18n.proxyDisabledText }}
|
||||
|
|
|
@ -21,13 +21,18 @@ export default {
|
|||
},
|
||||
},
|
||||
i18n: {
|
||||
listTitle: s__('DependencyProxy|Manifest list'),
|
||||
listTitle: s__('DependencyProxy|Image list'),
|
||||
},
|
||||
computed: {
|
||||
showPagination() {
|
||||
return this.pagination.hasNextPage || this.pagination.hasPreviousPage;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-mt-5">
|
||||
<div class="gl-mt-6">
|
||||
<h3 class="gl-font-base">{{ $options.i18n.listTitle }}</h3>
|
||||
<div
|
||||
class="gl-border-t-1 gl-border-gray-100 gl-border-t-solid gl-display-flex gl-flex-direction-column"
|
||||
|
@ -36,6 +41,7 @@ export default {
|
|||
</div>
|
||||
<div class="gl-display-flex gl-justify-content-center">
|
||||
<gl-keyset-pagination
|
||||
v-if="showPagination"
|
||||
v-bind="pagination"
|
||||
class="gl-mt-3"
|
||||
@prev="$emit('prev-page')"
|
||||
|
|
|
@ -43,6 +43,8 @@ const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingPr
|
|||
};
|
||||
|
||||
const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
|
||||
const specialRepo = document.querySelector('.js-user-readme-repo');
|
||||
|
||||
// eslint-disable-next-line @gitlab/no-global-event-off
|
||||
$projectNameInput.off('keyup change').on('keyup change', () => {
|
||||
onProjectNameChange($projectNameInput, $projectPathInput);
|
||||
|
@ -54,6 +56,11 @@ const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
|
|||
$projectPathInput.off('keyup change').on('keyup change', () => {
|
||||
onProjectPathChange($projectNameInput, $projectPathInput, hasUserDefinedProjectName);
|
||||
hasUserDefinedProjectPath = $projectPathInput.val().trim().length > 0;
|
||||
|
||||
specialRepo.classList.toggle(
|
||||
'gl-display-none',
|
||||
$projectPathInput.val() !== $projectPathInput.data('username'),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -37,10 +37,13 @@ export const SAST_IAC_SHORT_NAME = s__('ciReport|IaC Scanning');
|
|||
export const SAST_IAC_DESCRIPTION = __(
|
||||
'Analyze your infrastructure as code configuration files for known vulnerabilities.',
|
||||
);
|
||||
export const SAST_IAC_HELP_PATH = helpPagePath('user/application_security/sast/index');
|
||||
export const SAST_IAC_CONFIG_HELP_PATH = helpPagePath('user/application_security/sast/index', {
|
||||
anchor: 'configuration',
|
||||
});
|
||||
export const SAST_IAC_HELP_PATH = helpPagePath('user/application_security/iac_scanning/index');
|
||||
export const SAST_IAC_CONFIG_HELP_PATH = helpPagePath(
|
||||
'user/application_security/iac_scanning/index',
|
||||
{
|
||||
anchor: 'configuration',
|
||||
},
|
||||
);
|
||||
|
||||
export const DAST_NAME = __('Dynamic Application Security Testing (DAST)');
|
||||
export const DAST_SHORT_NAME = s__('ciReport|DAST');
|
||||
|
|
|
@ -98,7 +98,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
|
||||
# Specs are in spec/requests/self_monitoring_project_spec.rb
|
||||
def create_self_monitoring_project
|
||||
job_id = SelfMonitoringProjectCreateWorker.perform_async # rubocop:disable CodeReuse/Worker
|
||||
job_id = SelfMonitoringProjectCreateWorker.with_status.perform_async # rubocop:disable CodeReuse/Worker
|
||||
|
||||
render status: :accepted, json: {
|
||||
job_id: job_id,
|
||||
|
@ -137,7 +137,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
|
||||
# Specs are in spec/requests/self_monitoring_project_spec.rb
|
||||
def delete_self_monitoring_project
|
||||
job_id = SelfMonitoringProjectDeleteWorker.perform_async # rubocop:disable CodeReuse/Worker
|
||||
job_id = SelfMonitoringProjectDeleteWorker.with_status.perform_async # rubocop:disable CodeReuse/Worker
|
||||
|
||||
render status: :accepted, json: {
|
||||
job_id: job_id,
|
||||
|
|
|
@ -33,7 +33,6 @@ module TabHelper
|
|||
# :item_active - Overrides the default state focing the "active" css classes (optional).
|
||||
#
|
||||
def gl_tab_link_to(name = nil, options = {}, html_options = {}, &block)
|
||||
tab_class = 'nav-item'
|
||||
link_classes = %w[nav-link gl-tab-nav-item]
|
||||
active_link_classes = %w[active gl-tab-nav-item-active gl-tab-nav-item-active-indigo]
|
||||
|
||||
|
@ -52,6 +51,8 @@ module TabHelper
|
|||
end
|
||||
|
||||
html_options = html_options.except(:item_active)
|
||||
extra_tab_classes = html_options.delete(:tab_class)
|
||||
tab_class = %w[nav-item].push(*extra_tab_classes)
|
||||
|
||||
content_tag(:li, class: tab_class, role: 'presentation') do
|
||||
if block_given?
|
||||
|
@ -215,6 +216,7 @@ def gl_tab_counter_badge(count, html_options = {})
|
|||
badge_classes = %w[badge badge-muted badge-pill gl-badge sm gl-tab-counter-badge]
|
||||
content_tag(:span,
|
||||
count,
|
||||
class: [*html_options[:class], badge_classes].join(' ')
|
||||
class: [*html_options[:class], badge_classes].join(' '),
|
||||
data: html_options[:data]
|
||||
)
|
||||
end
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
# == Strip Attribute module
|
||||
#
|
||||
# Contains functionality to clean attributes before validation
|
||||
# Contains functionality to remove leading and trailing
|
||||
# whitespace from the attribute before validation
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
|
|
|
@ -662,7 +662,7 @@ class MergeRequest < ApplicationRecord
|
|||
# updates `merge_jid` with the MergeWorker#jid.
|
||||
# This helps tracking enqueued and ongoing merge jobs.
|
||||
def merge_async(user_id, params)
|
||||
jid = MergeWorker.perform_async(id, user_id, params.to_h)
|
||||
jid = MergeWorker.with_status.perform_async(id, user_id, params.to_h)
|
||||
update_column(:merge_jid, jid)
|
||||
|
||||
# merge_ongoing? depends on merge_jid
|
||||
|
@ -681,7 +681,7 @@ class MergeRequest < ApplicationRecord
|
|||
# attribute is set *and* that the sidekiq job is still running. So a JID
|
||||
# for a completed RebaseWorker is equivalent to a nil JID.
|
||||
jid = Sidekiq::Worker.skipping_transaction_check do
|
||||
RebaseWorker.perform_async(id, user_id, skip_ci)
|
||||
RebaseWorker.with_status.perform_async(id, user_id, skip_ci)
|
||||
end
|
||||
|
||||
update_column(:rebase_jid, jid)
|
||||
|
|
|
@ -1992,6 +1992,18 @@ class User < ApplicationRecord
|
|||
saved
|
||||
end
|
||||
|
||||
def user_project
|
||||
strong_memoize(:user_project) do
|
||||
personal_projects.find_by(path: username, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
end
|
||||
end
|
||||
|
||||
def user_readme
|
||||
strong_memoize(:user_readme) do
|
||||
user_project&.repository&.readme
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# override, from Devise::Validatable
|
||||
|
|
|
@ -3,12 +3,6 @@
|
|||
class UsersStatistics < ApplicationRecord
|
||||
scope :order_created_at_desc, -> { order(created_at: :desc) }
|
||||
|
||||
class << self
|
||||
def latest
|
||||
order_created_at_desc.first
|
||||
end
|
||||
end
|
||||
|
||||
def active
|
||||
[
|
||||
without_groups_and_projects,
|
||||
|
@ -26,30 +20,26 @@ class UsersStatistics < ApplicationRecord
|
|||
end
|
||||
|
||||
class << self
|
||||
def create_current_stats!
|
||||
stats_by_role = highest_role_stats
|
||||
def latest
|
||||
order_created_at_desc.first
|
||||
end
|
||||
|
||||
create!(
|
||||
without_groups_and_projects: without_groups_and_projects_stats,
|
||||
with_highest_role_guest: stats_by_role[:guest],
|
||||
with_highest_role_reporter: stats_by_role[:reporter],
|
||||
with_highest_role_developer: stats_by_role[:developer],
|
||||
with_highest_role_maintainer: stats_by_role[:maintainer],
|
||||
with_highest_role_owner: stats_by_role[:owner],
|
||||
bots: bot_stats,
|
||||
blocked: blocked_stats
|
||||
)
|
||||
def create_current_stats!
|
||||
create!(highest_role_stats)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def highest_role_stats
|
||||
{
|
||||
owner: batch_count_for_access_level(Gitlab::Access::OWNER),
|
||||
maintainer: batch_count_for_access_level(Gitlab::Access::MAINTAINER),
|
||||
developer: batch_count_for_access_level(Gitlab::Access::DEVELOPER),
|
||||
reporter: batch_count_for_access_level(Gitlab::Access::REPORTER),
|
||||
guest: batch_count_for_access_level(Gitlab::Access::GUEST)
|
||||
without_groups_and_projects: without_groups_and_projects_stats,
|
||||
with_highest_role_guest: batch_count_for_access_level(Gitlab::Access::GUEST),
|
||||
with_highest_role_reporter: batch_count_for_access_level(Gitlab::Access::REPORTER),
|
||||
with_highest_role_developer: batch_count_for_access_level(Gitlab::Access::DEVELOPER),
|
||||
with_highest_role_maintainer: batch_count_for_access_level(Gitlab::Access::MAINTAINER),
|
||||
with_highest_role_owner: batch_count_for_access_level(Gitlab::Access::OWNER),
|
||||
bots: bot_stats,
|
||||
blocked: blocked_stats
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module Groups
|
|||
|
||||
def async_execute
|
||||
group_import_state = GroupImportState.safe_find_or_create_by!(group: group, user: current_user)
|
||||
jid = GroupImportWorker.perform_async(current_user.id, group.id)
|
||||
jid = GroupImportWorker.with_status.perform_async(current_user.id, group.id)
|
||||
|
||||
if jid.present?
|
||||
group_import_state.update!(jid: jid)
|
||||
|
|
|
@ -1,74 +1,75 @@
|
|||
- page_title s_('AdminArea|Users statistics')
|
||||
|
||||
%h3.my-4
|
||||
%h3.gl-my-6
|
||||
= s_('AdminArea|Users statistics')
|
||||
%table.table.gl-text-gray-500
|
||||
%tr
|
||||
%td.p-3
|
||||
%td.gl-p-5!
|
||||
= s_('AdminArea|Users without a Group and Project')
|
||||
= render_if_exists 'admin/dashboard/included_free_in_license_tooltip'
|
||||
%td.p-3.text-right
|
||||
= @users_statistics&.without_groups_and_projects.to_i
|
||||
%td.gl-text-right{ class: 'gl-p-5!' }
|
||||
= @users_statistics&.without_groups_and_projects
|
||||
= render_if_exists 'admin/dashboard/minimal_access_stats_row', users_statistics: @users_statistics
|
||||
%tr
|
||||
%td.p-3
|
||||
%td.gl-p-5!
|
||||
= s_('AdminArea|Users with highest role')
|
||||
%strong
|
||||
= s_('AdminArea|Guest')
|
||||
= render_if_exists 'admin/dashboard/included_free_in_license_tooltip'
|
||||
%td.p-3.text-right
|
||||
= @users_statistics&.with_highest_role_guest.to_i
|
||||
%td.gl-text-right{ class: 'gl-p-5!' }
|
||||
= @users_statistics&.with_highest_role_guest
|
||||
%tr
|
||||
%td.p-3
|
||||
%td.gl-p-5!
|
||||
= s_('AdminArea|Users with highest role')
|
||||
%strong
|
||||
= s_('AdminArea|Reporter')
|
||||
%td.p-3.text-right
|
||||
= @users_statistics&.with_highest_role_reporter.to_i
|
||||
%td.gl-text-right{ class: 'gl-p-5!' }
|
||||
= @users_statistics&.with_highest_role_reporter
|
||||
%tr
|
||||
%td.p-3
|
||||
%td.gl-p-5!
|
||||
= s_('AdminArea|Users with highest role')
|
||||
%strong
|
||||
= s_('AdminArea|Developer')
|
||||
%td.p-3.text-right
|
||||
= @users_statistics&.with_highest_role_developer.to_i
|
||||
%td.gl-text-right{ class: 'gl-p-5!' }
|
||||
= @users_statistics&.with_highest_role_developer
|
||||
%tr
|
||||
%td.p-3
|
||||
%td.gl-p-5!
|
||||
= s_('AdminArea|Users with highest role')
|
||||
%strong
|
||||
= s_('AdminArea|Maintainer')
|
||||
%td.p-3.text-right
|
||||
= @users_statistics&.with_highest_role_maintainer.to_i
|
||||
%td.gl-text-right{ class: 'gl-p-5!' }
|
||||
= @users_statistics&.with_highest_role_maintainer
|
||||
%tr
|
||||
%td.p-3
|
||||
%td.gl-p-5!
|
||||
= s_('AdminArea|Users with highest role')
|
||||
%strong
|
||||
= s_('AdminArea|Owner')
|
||||
%td.p-3.text-right
|
||||
= @users_statistics&.with_highest_role_owner.to_i
|
||||
%td.gl-text-right{ class: 'gl-p-5!' }
|
||||
= @users_statistics&.with_highest_role_owner
|
||||
%tr
|
||||
%td.p-3
|
||||
%td.gl-p-5!
|
||||
= s_('AdminArea|Bots')
|
||||
%td.p-3.text-right
|
||||
= @users_statistics&.bots.to_i
|
||||
%td.gl-text-right{ class: 'gl-p-5!' }
|
||||
= @users_statistics&.bots
|
||||
= render_if_exists 'admin/dashboard/billable_users_row'
|
||||
%tr.bg-gray-light.gl-text-gray-900
|
||||
%td.p-3
|
||||
%td.gl-p-5!
|
||||
%strong
|
||||
= s_('AdminArea|Active users')
|
||||
%td.p-3.text-right
|
||||
%td.gl-text-right{ class: 'gl-p-5!' }
|
||||
%strong
|
||||
= @users_statistics&.active.to_i
|
||||
= @users_statistics&.active
|
||||
%tr.bg-gray-light.gl-text-gray-900
|
||||
%td.p-3
|
||||
%td.gl-p-5!
|
||||
%strong
|
||||
= s_('AdminArea|Blocked users')
|
||||
%td.p-3.text-right
|
||||
%td.gl-text-right{ class: 'gl-p-5!' }
|
||||
%strong
|
||||
= @users_statistics&.blocked.to_i
|
||||
= @users_statistics&.blocked
|
||||
%tr.bg-gray-light.gl-text-gray-900
|
||||
%td.p-3
|
||||
%td.gl-p-5!
|
||||
%strong
|
||||
= s_('AdminArea|Total users')
|
||||
%td.p-3.text-right
|
||||
%td.gl-text-right{ class: 'gl-p-5!' }
|
||||
%strong
|
||||
= @users_statistics&.total.to_i
|
||||
= @users_statistics&.total
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
- dependency_proxy_available = Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: true) || @group.public?
|
||||
|
||||
#js-dependency-proxy{ data: { group_path: @group.full_path,
|
||||
dependency_proxy_available: dependency_proxy_available.to_s } }
|
||||
dependency_proxy_available: dependency_proxy_available.to_s,
|
||||
no_manifests_illustration: image_path('illustrations/docker-empty-state.svg') } }
|
||||
|
|
|
@ -40,12 +40,17 @@
|
|||
.form-group.project-path.col-sm-6
|
||||
= f.label :path, class: 'label-bold' do
|
||||
%span= _("Project slug")
|
||||
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control gl-form-input", required: true, aria: { required: true }
|
||||
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control gl-form-input", required: true, aria: { required: true }, data: { username: current_user.username }
|
||||
- if current_user.can_create_group?
|
||||
.form-text.text-muted
|
||||
- link_start_group_path = '<a href="%{path}">' % { path: new_group_path }
|
||||
- project_tip = s_('ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}') % { link_start: link_start_group_path, link_end: '</a>' }
|
||||
= project_tip.html_safe
|
||||
.gl-alert.gl-alert-success.gl-mb-4.gl-display-none.js-user-readme-repo
|
||||
= sprite_icon('check-circle', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
.gl-alert-body
|
||||
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/profile/index', anchor: 'user-profile-readme') }
|
||||
= html_escape(_('%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more.%{help_link_end}')) % { project_path: "<strong>#{current_user.username} / #{current_user.username}</strong>".html_safe, help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
|
||||
|
||||
.form-group
|
||||
= f.label :description, class: 'label-bold' do
|
||||
|
|
|
@ -9,6 +9,22 @@
|
|||
%a.js-retry-load{ href: '#' }
|
||||
= s_('UserProfile|Retry')
|
||||
.user-calendar-activities
|
||||
- if @user.user_readme
|
||||
.row
|
||||
.col-12.gl-my-6
|
||||
.gl-border-gray-100.gl-border-1.gl-border-solid.gl-rounded-small.gl-py-4.gl-px-6
|
||||
.gl-display-flex
|
||||
%ol.breadcrumb.gl-breadcrumb-list.gl-mb-4
|
||||
%li.breadcrumb-item.gl-breadcrumb-item
|
||||
= link_to @user.username, project_path(@user.user_project)
|
||||
%span.gl-breadcrumb-separator
|
||||
= sprite_icon("chevron-right", size: 16)
|
||||
%li.breadcrumb-item.gl-breadcrumb-item
|
||||
= link_to @user.user_readme.path, @user.user_project.readme_url
|
||||
- if current_user == @user
|
||||
.gl-ml-auto
|
||||
= link_to _('Edit'), edit_blob_path(@user.user_project, @user.user_project.default_branch, @user.user_readme.path)
|
||||
= render 'projects/blob/viewer', viewer: @user.user_readme.rich_viewer, load_async: false
|
||||
.row
|
||||
%div{ class: activity_pane_class }
|
||||
- if can?(current_user, :read_cross_project)
|
||||
|
|
|
@ -55,6 +55,12 @@ module ApplicationWorker
|
|||
subclass.after_set_class_attribute { subclass.set_queue }
|
||||
end
|
||||
|
||||
def with_status
|
||||
status_from_class = self.sidekiq_options_hash['status_expiration']
|
||||
|
||||
set(status_expiration: status_from_class || Gitlab::SidekiqStatus::DEFAULT_EXPIRATION)
|
||||
end
|
||||
|
||||
def generated_queue_name
|
||||
Gitlab::SidekiqConfig::WorkerRouter.queue_name_from_worker_name(self)
|
||||
end
|
||||
|
|
|
@ -47,7 +47,7 @@ module LimitedCapacity
|
|||
# would be occupied by a job that will be performed in the distant future.
|
||||
# We let the cron worker enqueue new jobs, this could be seen as our retry and
|
||||
# back off mechanism because the job might fail again if executed immediately.
|
||||
sidekiq_options retry: 0
|
||||
sidekiq_options retry: 0, status_expiration: Gitlab::SidekiqStatus::DEFAULT_EXPIRATION
|
||||
deduplicate :none
|
||||
end
|
||||
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343966
|
|||
milestone: '14.5'
|
||||
type: development
|
||||
group: group::static analysis
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddWithHighestRoleMinimalAccessToUsersStatistics < Gitlab::Database::Migration[1.0]
|
||||
def change
|
||||
add_column :users_statistics, :with_highest_role_minimal_access, :integer, null: false, default: 0
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
a22322122144f28306b3b38dbe50b3465ad623c389f8bfe6fa97a0f71b1c7c21
|
|
@ -20289,7 +20289,8 @@ CREATE TABLE users_statistics (
|
|||
with_highest_role_maintainer integer DEFAULT 0 NOT NULL,
|
||||
with_highest_role_owner integer DEFAULT 0 NOT NULL,
|
||||
bots integer DEFAULT 0 NOT NULL,
|
||||
blocked integer DEFAULT 0 NOT NULL
|
||||
blocked integer DEFAULT 0 NOT NULL,
|
||||
with_highest_role_minimal_access integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE users_statistics_id_seq
|
||||
|
|
|
@ -34,7 +34,7 @@ GET /projects/:id/dependencies?package_manager=yarn,bundler
|
|||
| Attribute | Type | Required | Description |
|
||||
| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding). |
|
||||
| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `conan`, `go`, `maven`, `npm`, `nuget`, `pip`, `yarn`, or `sbt`. |
|
||||
| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `conan`, `go`, `gradle`, `maven`, `npm`, `nuget`, `pip`, `pipenv`, `yarn`, `sbt`, or `setuptools`. |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/dependencies"
|
||||
|
|
|
@ -269,10 +269,11 @@ see this [CI/CD variable demo](https://youtu.be/4XR8gw3Pkos).
|
|||
> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) GitLab 14.3.
|
||||
> - [Feature flag `ci_include_rules` removed](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.4.
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.4.
|
||||
> - [Support for `exists` keyword added](https://gitlab.com/gitlab-org/gitlab/-/issues/341511) in GitLab 14.5.
|
||||
|
||||
You can use [`rules`](index.md#rules) with `include` to conditionally include other configuration files.
|
||||
You can only use [`if` rules](index.md#rulesif) in `include`, and only with [certain variables](#use-variables-with-include).
|
||||
`rules` keywords such as `changes` and `exists` are not supported.
|
||||
You can only use [`if` rules](index.md#rulesif) and [`exists` rules](index.md#rulesexists) in `include`, and only with
|
||||
[certain variables](#use-variables-with-include). `rules` keyword `changes` is not supported.
|
||||
|
||||
```yaml
|
||||
include:
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
---
|
||||
stage: Secure
|
||||
group: Static Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Infrastructure as Code (IaC) Scanning
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6655) in GitLab 14.5.
|
||||
|
||||
Infrastructure as Code (IaC) Scanning scans your IaC configuration files for known vulnerabilities.
|
||||
|
||||
Currently, IaC scanning supports configuration files for Terraform, Ansible, AWS CloudFormation, and Kubernetes.
|
||||
|
||||
## Requirements
|
||||
|
||||
To run IaC scanning jobs, by default, you need GitLab Runner with the
|
||||
[`docker`](https://docs.gitlab.com/runner/executors/docker.html) or
|
||||
[`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor.
|
||||
If you're using the shared runners on GitLab.com, this is enabled by default.
|
||||
|
||||
WARNING:
|
||||
Our IaC scanning jobs require a Linux container type. Windows containers are not yet supported.
|
||||
|
||||
WARNING:
|
||||
If you use your own runners, make sure the Docker version installed
|
||||
is **not** `19.03.0`. See [troubleshooting information](../sast/index.md#error-response-from-daemon-error-processing-tar-file-docker-tar-relocation-error) for details.
|
||||
|
||||
## Supported languages and frameworks
|
||||
|
||||
GitLab IaC scanning supports a variety of IaC configuration files. Our IaC security scanners also feature automatic language detection which works even for mixed-language projects. If any supported configuration files are detected in project source code we automatically run the appropriate IaC analyzers.
|
||||
|
||||
| Configuration File Type | Scan tool | Introduced in GitLab Version |
|
||||
|------------------------------------------|----------------------------------|-------------------------------|
|
||||
| Ansible | [kics](https://kics.io/) | 14.5 |
|
||||
| AWS CloudFormation | [kics](https://kics.io/) | 14.5 |
|
||||
| Kubernetes | [kics](https://kics.io/) | 14.5 |
|
||||
| Terraform | [kics](https://kics.io/) | 14.5 |
|
||||
|
||||
### Making IaC analyzers available to all GitLab tiers
|
||||
|
||||
All open source (OSS) analyzers are availibile with the GitLab Free tier. Future propietary analyzers may be restricted to higher tiers.
|
||||
|
||||
#### Summary of features per tier
|
||||
|
||||
Different features are available in different [GitLab tiers](https://about.gitlab.com/pricing/),
|
||||
as shown in the following table:
|
||||
|
||||
| Capability | In Free | In Ultimate |
|
||||
|:---------------------------------------------------------------------------------------|:--------------------|:-------------------|
|
||||
| [Configure IaC Scanners](#configuration) v | **{check-circle}** | **{check-circle}** |
|
||||
| View [JSON Report](#reports-json-format) | **{check-circle}** | **{check-circle}** |
|
||||
| Presentation of JSON Report in Merge Request | **{dotted-circle}** | **{check-circle}** |
|
||||
| [Address vulnerabilities](../../application_security/vulnerabilities/index.md) | **{dotted-circle}** | **{check-circle}** |
|
||||
| [Access to Security Dashboard](../../application_security/security_dashboard/index.md) | **{dotted-circle}** | **{check-circle}** |
|
||||
|
||||
## Contribute your scanner
|
||||
|
||||
The [Security Scanner Integration](../../../development/integrations/secure.md) documentation explains how to integrate other security scanners into GitLab.
|
||||
|
||||
## Configuration
|
||||
|
||||
To configure IaC Scanning for a project you can:
|
||||
|
||||
- [Configure IaC Scanning manually](#configure-iac-scanning-manually)
|
||||
- [Enable IaC Scanning via an automatic merge request](#enable-iac-scanning-via-an-automatic-merge-request)
|
||||
|
||||
### Configure IaC Scanning manually
|
||||
|
||||
To enable IaC Scanning you must [include](../../../ci/yaml/index.md#includetemplate) the
|
||||
[`SAST-IaC.latest.gitlab-ci.yml template`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST-IaC.latest.gitlab-ci.yml) provided as part of your GitLab installation.
|
||||
|
||||
The included template creates IaC scanning jobs in your CI/CD pipeline and scans
|
||||
your project's configuration files for possible vulnerabilities.
|
||||
|
||||
The results are saved as a
|
||||
[SAST report artifact](../../../ci/yaml/index.md#artifactsreportssast)
|
||||
that you can download and analyze.
|
||||
|
||||
### Enable IaC Scanning via an automatic merge request
|
||||
|
||||
To enable IaC Scanning in a project, you can create a merge request
|
||||
from the Security Configuration page:
|
||||
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Security & Compliance > Configuration**.
|
||||
1. In the **Infrastructure as Code (IaC) Scanning** row, select **Configure via Merge Request**.
|
||||
|
||||
This automatically creates a merge request with the changes necessary to enable IaC Scanning
|
||||
that you can review and merge to complete the configuration.
|
||||
|
||||
## Reports JSON format
|
||||
|
||||
The IaC tool emits a JSON report file in the existing SAST report format. For more information, see the
|
||||
[schema for this report](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/sast-report-format.json).
|
||||
|
||||
The JSON report file can be downloaded from the CI pipelines page, or the
|
||||
pipelines tab on merge requests by [setting `artifacts: paths`](../../../ci/yaml/index.md#artifactspaths) to `gl-sast-report.json`. For more information see [Downloading artifacts](../../../ci/pipelines/job_artifacts.md).
|
|
@ -31,19 +31,20 @@ For an overview of GitLab application security, see [Shifting Security Left](htt
|
|||
|
||||
GitLab uses the following tools to scan and report known vulnerabilities found in your project.
|
||||
|
||||
| Secure scanning tool | Description |
|
||||
|:-----------------------------------------------------------------------------|:-----------------------------------------------------------------------|
|
||||
| [Container Scanning](container_scanning/index.md) | Scan Docker containers for known vulnerabilities. |
|
||||
| [Dependency List](dependency_list/index.md) | View your project's dependencies and their known vulnerabilities. |
|
||||
| [Dependency Scanning](dependency_scanning/index.md) | Analyze your dependencies for known vulnerabilities. |
|
||||
| [Dynamic Application Security Testing (DAST)](dast/index.md) | Analyze running web applications for known vulnerabilities. |
|
||||
| [DAST API](dast_api/index.md) | Analyze running web APIs for known vulnerabilities. |
|
||||
| [API fuzzing](api_fuzzing/index.md) | Find unknown bugs and vulnerabilities in web APIs with fuzzing. |
|
||||
| [Secret Detection](secret_detection/index.md) | Analyze Git history for leaked secrets. |
|
||||
| [Security Dashboard](security_dashboard/index.md) | View vulnerabilities in all your projects and groups. |
|
||||
| [Static Application Security Testing (SAST)](sast/index.md) | Analyze source code for known vulnerabilities. |
|
||||
| [Coverage fuzzing](coverage_fuzzing/index.md) | Find unknown bugs and vulnerabilities with coverage-guided fuzzing. |
|
||||
| [Cluster Image Scanning](cluster_image_scanning/index.md) | Scan Kubernetes clusters for known vulnerabilities. |
|
||||
| Secure scanning tool | Description |
|
||||
| :------------------------------------------------------------- | :------------------------------------------------------------------ |
|
||||
| [Container Scanning](container_scanning/index.md) | Scan Docker containers for known vulnerabilities. |
|
||||
| [Dependency List](dependency_list/index.md) | View your project's dependencies and their known vulnerabilities. |
|
||||
| [Dependency Scanning](dependency_scanning/index.md) | Analyze your dependencies for known vulnerabilities. |
|
||||
| [Dynamic Application Security Testing (DAST)](dast/index.md) | Analyze running web applications for known vulnerabilities. |
|
||||
| [DAST API](dast_api/index.md) | Analyze running web APIs for known vulnerabilities. |
|
||||
| [API fuzzing](api_fuzzing/index.md) | Find unknown bugs and vulnerabilities in web APIs with fuzzing. |
|
||||
| [Secret Detection](secret_detection/index.md) | Analyze Git history for leaked secrets. |
|
||||
| [Security Dashboard](security_dashboard/index.md) | View vulnerabilities in all your projects and groups. |
|
||||
| [Static Application Security Testing (SAST)](sast/index.md) | Analyze source code for known vulnerabilities. |
|
||||
| [Infrastructure as Code (IaC) Scanning](iac_scanning/index.md) | Analyze your IaC coniguration files for known vulnerabilities. |
|
||||
| [Coverage fuzzing](coverage_fuzzing/index.md) | Find unknown bugs and vulnerabilities with coverage-guided fuzzing. |
|
||||
| [Cluster Image Scanning](cluster_image_scanning/index.md) | Scan Kubernetes clusters for known vulnerabilities. |
|
||||
|
||||
## Security scanning with Auto DevOps
|
||||
|
||||
|
|
|
@ -100,6 +100,18 @@ When visiting the public page of a user, you can only see the projects which you
|
|||
If the [public level is restricted](../admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels),
|
||||
user profiles are only visible to signed-in users.
|
||||
|
||||
## User profile README
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232157) in GitLab 14.5.
|
||||
|
||||
You can add a README section to your profile that can include more information and formatting than
|
||||
your profile's bio.
|
||||
|
||||
To add a README to your profile:
|
||||
|
||||
1. Create a new public project with the same name as your GitLab username.
|
||||
1. Create a README file inside this project. The file can be any valid [README or index file](../project/repository/index.md#readme-and-index-files).
|
||||
|
||||
## Add external accounts to your user profile page
|
||||
|
||||
You can add links to certain other external accounts you might have, like Skype and Twitter.
|
||||
|
|
|
@ -177,7 +177,9 @@ audit trail:
|
|||
include: # Execute individual project's configuration (if project contains .gitlab-ci.yml)
|
||||
project: '$CI_PROJECT_PATH'
|
||||
file: '$CI_CONFIG_PATH'
|
||||
ref: '$CI_COMMIT_REF_NAME' # Must be defined or MR pipelines always use the use default branch.
|
||||
ref: '$CI_COMMIT_REF_NAME' # Must be defined or MR pipelines always use the use default branch
|
||||
rules:
|
||||
- exists: '$CI_CONFIG_PATH'
|
||||
```
|
||||
|
||||
##### Ensure compliance jobs are always run
|
||||
|
|
|
@ -46,7 +46,8 @@ module API
|
|||
def finder_params
|
||||
{
|
||||
package_type: :terraform_module,
|
||||
package_name: "#{params[:module_name]}/#{params[:module_system]}"
|
||||
package_name: "#{params[:module_name]}/#{params[:module_system]}",
|
||||
exact_name: true
|
||||
}.tap do |finder_params|
|
||||
finder_params[:package_version] = params[:module_version] if params.has_key?(:module_version)
|
||||
end
|
||||
|
|
|
@ -5,6 +5,8 @@ module Gitlab
|
|||
module Build
|
||||
module Context
|
||||
class Base
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
attr_reader :pipeline
|
||||
|
||||
def initialize(pipeline)
|
||||
|
@ -15,6 +17,26 @@ module Gitlab
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def project
|
||||
pipeline.project
|
||||
end
|
||||
|
||||
def sha
|
||||
pipeline.sha
|
||||
end
|
||||
|
||||
def top_level_worktree_paths
|
||||
strong_memoize(:top_level_worktree_paths) do
|
||||
project.repository.tree(sha).blobs.map(&:path)
|
||||
end
|
||||
end
|
||||
|
||||
def all_worktree_paths
|
||||
strong_memoize(:all_worktree_paths) do
|
||||
project.repository.ls_files(sha)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def pipeline_attributes
|
||||
|
|
|
@ -15,19 +15,21 @@ module Gitlab
|
|||
@exact_globs, @pattern_globs = globs.partition(&method(:exact_glob?))
|
||||
end
|
||||
|
||||
def satisfied_by?(pipeline, context)
|
||||
paths = worktree_paths(pipeline)
|
||||
def satisfied_by?(_pipeline, context)
|
||||
paths = worktree_paths(context)
|
||||
|
||||
exact_matches?(paths) || pattern_matches?(paths)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def worktree_paths(pipeline)
|
||||
def worktree_paths(context)
|
||||
return unless context.project
|
||||
|
||||
if @top_level_only
|
||||
pipeline.top_level_worktree_paths
|
||||
context.top_level_worktree_paths
|
||||
else
|
||||
pipeline.all_worktree_paths
|
||||
context.all_worktree_paths
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@ module Gitlab
|
|||
include ::Gitlab::Config::Entry::Validatable
|
||||
include ::Gitlab::Config::Entry::Attributable
|
||||
|
||||
ALLOWED_KEYS = %i[if].freeze
|
||||
ALLOWED_KEYS = %i[if exists].freeze
|
||||
|
||||
attributes :if
|
||||
attributes :if, :exists
|
||||
|
||||
validations do
|
||||
validates :config, presence: true
|
||||
|
|
|
@ -5,6 +5,8 @@ module Gitlab
|
|||
class Config
|
||||
module External
|
||||
class Context
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
TimeoutError = Class.new(StandardError)
|
||||
|
||||
attr_reader :project, :sha, :user, :parent_pipeline, :variables
|
||||
|
@ -22,6 +24,18 @@ module Gitlab
|
|||
yield self if block_given?
|
||||
end
|
||||
|
||||
def top_level_worktree_paths
|
||||
strong_memoize(:top_level_worktree_paths) do
|
||||
project.repository.tree(sha).blobs.map(&:path)
|
||||
end
|
||||
end
|
||||
|
||||
def all_worktree_paths
|
||||
strong_memoize(:all_worktree_paths) do
|
||||
project.repository.ls_files(sha)
|
||||
end
|
||||
end
|
||||
|
||||
def mutate(attrs = {})
|
||||
self.class.new(**attrs) do |ctx|
|
||||
ctx.expandset = expandset
|
||||
|
|
|
@ -74,6 +74,9 @@ gemnasium-maven-dependency_scanning:
|
|||
# override the analyzer image with a custom value. This may be subject to change or
|
||||
# breakage across GitLab releases.
|
||||
DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gemnasium-maven:$DS_MAJOR_VERSION"
|
||||
# Stop reporting Gradle as "maven".
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/338252
|
||||
DS_REPORT_PACKAGE_MANAGER_MAVEN_WHEN_JAVA: "false"
|
||||
rules:
|
||||
- if: $DEPENDENCY_SCANNING_DISABLED
|
||||
when: never
|
||||
|
@ -97,6 +100,9 @@ gemnasium-python-dependency_scanning:
|
|||
# override the analyzer image with a custom value. This may be subject to change or
|
||||
# breakage across GitLab releases.
|
||||
DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gemnasium-python:$DS_MAJOR_VERSION"
|
||||
# Stop reporting Pipenv and Setuptools as "pip".
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/338252
|
||||
DS_REPORT_PACKAGE_MANAGER_PIP_WHEN_PYTHON: "false"
|
||||
rules:
|
||||
- if: $DEPENDENCY_SCANNING_DISABLED
|
||||
when: never
|
||||
|
|
|
@ -7,12 +7,16 @@ module Gitlab
|
|||
# To check if a job has been completed, simply pass the job ID to the
|
||||
# `completed?` method:
|
||||
#
|
||||
# job_id = SomeWorker.perform_async(...)
|
||||
# job_id = SomeWorker.with_status.perform_async(...)
|
||||
#
|
||||
# if Gitlab::SidekiqStatus.completed?(job_id)
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# If you do not use `with_status`, and the worker class does not declare
|
||||
# `status_expiration` in its `sidekiq_options`, then this status will not be
|
||||
# stored.
|
||||
#
|
||||
# For each job ID registered a separate key is stored in Redis, making lookups
|
||||
# much faster than using Sidekiq's built-in job finding/status API. These keys
|
||||
# expire after a certain period of time to prevent storing too many keys in
|
||||
|
|
|
@ -815,6 +815,9 @@ msgstr ""
|
|||
msgid "%{primary} (%{secondary})"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more.%{help_link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{ref} cannot be added: %{error}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2313,6 +2316,9 @@ msgstr ""
|
|||
msgid "AdminArea|Maintainer"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminArea|Minimal access"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminArea|New group"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11337,7 +11343,10 @@ msgstr ""
|
|||
msgid "DependencyProxy|Enable Proxy"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Manifest list"
|
||||
msgid "DependencyProxy|Image list"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|There are no images in the cache"
|
||||
msgstr ""
|
||||
|
||||
msgid "Depends on %d merge request being merged"
|
||||
|
@ -18547,12 +18556,18 @@ msgstr ""
|
|||
msgid "Integrations|Default settings are inherited from the instance level."
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Edit project alias"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Enable GitLab.com slash commands in a Slack workspace."
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Enable comments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Enter your alias"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Failed to link namespace. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
@ -18670,6 +18685,9 @@ msgstr ""
|
|||
msgid "Integrations|You can now close this window and return to the GitLab for Jira application."
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|You can use this alias in your Slack commands"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|You haven't activated any integrations yet."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ RSpec.describe Projects::MergeRequestsController do
|
|||
let_it_be_with_reload(:project_public_with_private_builds) { create(:project, :repository, :public, :builds_private) }
|
||||
|
||||
let(:user) { project.owner }
|
||||
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: merge_request_source_project, allow_maintainer_to_push: false) }
|
||||
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: merge_request_source_project, allow_collaboration: false) }
|
||||
let(:merge_request_source_project) { project }
|
||||
|
||||
before do
|
||||
|
@ -507,6 +507,7 @@ RSpec.describe Projects::MergeRequestsController do
|
|||
end
|
||||
|
||||
it 'starts the merge immediately with permitted params' do
|
||||
allow(MergeWorker).to receive(:with_status).and_return(MergeWorker)
|
||||
expect(MergeWorker).to receive(:perform_async).with(merge_request.id, anything, { 'sha' => merge_request.diff_head_sha })
|
||||
|
||||
merge_with_sha
|
||||
|
@ -2078,6 +2079,10 @@ RSpec.describe Projects::MergeRequestsController do
|
|||
post :rebase, params: { namespace_id: project.namespace, project_id: project, id: merge_request }
|
||||
end
|
||||
|
||||
before do
|
||||
allow(RebaseWorker).to receive(:with_status).and_return(RebaseWorker)
|
||||
end
|
||||
|
||||
def expect_rebase_worker_for(user)
|
||||
expect(RebaseWorker).to receive(:perform_async).with(merge_request.id, user.id, false)
|
||||
end
|
||||
|
|
|
@ -5,10 +5,10 @@ FactoryBot.define do
|
|||
highest_access_level { nil }
|
||||
user
|
||||
|
||||
trait(:guest) { highest_access_level { GroupMember::GUEST } }
|
||||
trait(:reporter) { highest_access_level { GroupMember::REPORTER } }
|
||||
trait(:developer) { highest_access_level { GroupMember::DEVELOPER } }
|
||||
trait(:maintainer) { highest_access_level { GroupMember::MAINTAINER } }
|
||||
trait(:owner) { highest_access_level { GroupMember::OWNER } }
|
||||
trait(:guest) { highest_access_level { GroupMember::GUEST } }
|
||||
trait(:reporter) { highest_access_level { GroupMember::REPORTER } }
|
||||
trait(:developer) { highest_access_level { GroupMember::DEVELOPER } }
|
||||
trait(:maintainer) { highest_access_level { GroupMember::MAINTAINER } }
|
||||
trait(:owner) { highest_access_level { GroupMember::OWNER } }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,14 @@ RSpec.describe 'User visits their profile' do
|
|||
expect(page).to have_content "This information will appear on your profile"
|
||||
end
|
||||
|
||||
it 'shows user readme' do
|
||||
create(:project, :repository, :public, path: user.username, namespace: user.namespace)
|
||||
|
||||
visit(user_path(user))
|
||||
|
||||
expect(find('.file-content')).to have_content('testme')
|
||||
end
|
||||
|
||||
context 'when user has groups' do
|
||||
let(:group) do
|
||||
create :group do |group|
|
||||
|
|
|
@ -39,7 +39,7 @@ export const member = {
|
|||
Developer: 30,
|
||||
Maintainer: 40,
|
||||
Owner: 50,
|
||||
'Minimal Access': 5,
|
||||
'Minimal access': 5,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1,32 +1,40 @@
|
|||
import { GlFormInputGroup, GlFormGroup, GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
|
||||
import {
|
||||
GlFormInputGroup,
|
||||
GlFormGroup,
|
||||
GlSkeletonLoader,
|
||||
GlSprintf,
|
||||
GlEmptyState,
|
||||
} from '@gitlab/ui';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { stripTypenames } from 'helpers/graphql_helpers';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
|
||||
|
||||
import DependencyProxyApp from '~/packages_and_registries/dependency_proxy/app.vue';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
|
||||
|
||||
import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
|
||||
|
||||
import { proxyDetailsQuery, proxyData } from './mock_data';
|
||||
import { proxyDetailsQuery, proxyData, pagination, proxyManifests } from './mock_data';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
describe('DependencyProxyApp', () => {
|
||||
let wrapper;
|
||||
let apolloProvider;
|
||||
let resolver;
|
||||
|
||||
const provideDefaults = {
|
||||
groupPath: 'gitlab-org',
|
||||
dependencyProxyAvailable: true,
|
||||
noManifestsIllustration: 'noManifestsIllustration',
|
||||
};
|
||||
|
||||
function createComponent({
|
||||
provide = provideDefaults,
|
||||
resolver = jest.fn().mockResolvedValue(proxyDetailsQuery()),
|
||||
} = {}) {
|
||||
function createComponent({ provide = provideDefaults } = {}) {
|
||||
localVue.use(VueApollo);
|
||||
|
||||
const requestHandlers = [[getDependencyProxyDetailsQuery, resolver]];
|
||||
|
@ -53,6 +61,12 @@ describe('DependencyProxyApp', () => {
|
|||
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
||||
const findMainArea = () => wrapper.findByTestId('main-area');
|
||||
const findProxyCountText = () => wrapper.findByTestId('proxy-count');
|
||||
const findManifestList = () => wrapper.findComponent(ManifestsList);
|
||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||
|
||||
beforeEach(() => {
|
||||
resolver = jest.fn().mockResolvedValue(proxyDetailsQuery());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
|
@ -78,8 +92,8 @@ describe('DependencyProxyApp', () => {
|
|||
});
|
||||
|
||||
it('does not call the graphql endpoint', async () => {
|
||||
const resolver = jest.fn().mockResolvedValue(proxyDetailsQuery());
|
||||
createComponent({ ...createComponentArguments, resolver });
|
||||
resolver = jest.fn().mockResolvedValue(proxyDetailsQuery());
|
||||
createComponent({ ...createComponentArguments });
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
|
@ -145,14 +159,73 @@ describe('DependencyProxyApp', () => {
|
|||
it('from group has a description with proxy count', () => {
|
||||
expect(findProxyCountText().text()).toBe('Contains 2 blobs of images (1024 Bytes)');
|
||||
});
|
||||
|
||||
describe('manifest lists', () => {
|
||||
describe('when there are no manifests', () => {
|
||||
beforeEach(() => {
|
||||
resolver = jest.fn().mockResolvedValue(
|
||||
proxyDetailsQuery({
|
||||
extend: { dependencyProxyManifests: { nodes: [], pageInfo: pagination() } },
|
||||
}),
|
||||
);
|
||||
createComponent();
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
it('shows the empty state message', () => {
|
||||
expect(findEmptyState().props()).toMatchObject({
|
||||
svgPath: provideDefaults.noManifestsIllustration,
|
||||
title: DependencyProxyApp.i18n.noManifestTitle,
|
||||
});
|
||||
});
|
||||
|
||||
it('hides the list', () => {
|
||||
expect(findManifestList().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are manifests', () => {
|
||||
it('hides the empty state message', () => {
|
||||
expect(findEmptyState().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows list', () => {
|
||||
expect(findManifestList().props()).toMatchObject({
|
||||
manifests: proxyManifests(),
|
||||
pagination: stripTypenames(pagination()),
|
||||
});
|
||||
});
|
||||
|
||||
it('prev-page event on list fetches the previous page', () => {
|
||||
findManifestList().vm.$emit('prev-page');
|
||||
|
||||
expect(resolver).toHaveBeenCalledWith({
|
||||
before: pagination().startCursor,
|
||||
first: null,
|
||||
fullPath: provideDefaults.groupPath,
|
||||
last: GRAPHQL_PAGE_SIZE,
|
||||
});
|
||||
});
|
||||
|
||||
it('next-page event on list fetches the next page', () => {
|
||||
findManifestList().vm.$emit('next-page');
|
||||
|
||||
expect(resolver).toHaveBeenCalledWith({
|
||||
after: pagination().endCursor,
|
||||
first: GRAPHQL_PAGE_SIZE,
|
||||
fullPath: provideDefaults.groupPath,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the dependency proxy is disabled', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
resolver: jest
|
||||
.fn()
|
||||
.mockResolvedValue(proxyDetailsQuery({ extendSettings: { enabled: false } })),
|
||||
});
|
||||
resolver = jest
|
||||
.fn()
|
||||
.mockResolvedValue(proxyDetailsQuery({ extendSettings: { enabled: false } }));
|
||||
createComponent();
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
|
|
|
@ -26,42 +26,56 @@ describe('Manifests List', () => {
|
|||
const findRows = () => wrapper.findAllComponents(ManifestRow);
|
||||
const findPagination = () => wrapper.findComponent(GlKeysetPagination);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('has the correct title', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.text()).toContain(Component.i18n.listTitle);
|
||||
});
|
||||
|
||||
it('shows a row for every manifest', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findRows().length).toBe(defaultProps.manifests.length);
|
||||
});
|
||||
|
||||
it('binds a manifest to each row', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findRows().at(0).props()).toMatchObject({
|
||||
manifest: defaultProps.manifests[0],
|
||||
});
|
||||
});
|
||||
|
||||
describe('pagination', () => {
|
||||
it('is hidden when there is no next or prev pages', () => {
|
||||
createComponent({ ...defaultProps, pagination: {} });
|
||||
|
||||
expect(findPagination().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('has the correct props', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findPagination().props()).toMatchObject({
|
||||
...defaultProps.pagination,
|
||||
});
|
||||
});
|
||||
|
||||
it('emits the next-page event', () => {
|
||||
createComponent();
|
||||
|
||||
findPagination().vm.$emit('next');
|
||||
|
||||
expect(wrapper.emitted('next-page')).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('emits the prev-page event', () => {
|
||||
createComponent();
|
||||
|
||||
findPagination().vm.$emit('prev');
|
||||
|
||||
expect(wrapper.emitted('prev-page')).toEqual([[]]);
|
||||
|
|
|
@ -21,7 +21,7 @@ export const pagination = (extend) => ({
|
|||
...extend,
|
||||
});
|
||||
|
||||
export const proxyDetailsQuery = ({ extendSettings = {} } = {}) => ({
|
||||
export const proxyDetailsQuery = ({ extendSettings = {}, extend } = {}) => ({
|
||||
data: {
|
||||
group: {
|
||||
...proxyData(),
|
||||
|
@ -34,6 +34,7 @@ export const proxyDetailsQuery = ({ extendSettings = {} } = {}) => ({
|
|||
nodes: proxyManifests(),
|
||||
pageInfo: pagination(),
|
||||
},
|
||||
...extend,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -36,7 +36,15 @@ RSpec.describe TabHelper do
|
|||
expect(gl_tab_link_to('/url') { 'block content' }).to match(/block content/)
|
||||
end
|
||||
|
||||
it 'creates a tab with custom classes' do
|
||||
it 'creates a tab with custom classes for enclosing list item without content block provided' do
|
||||
expect(gl_tab_link_to('Link', '/url', { tab_class: 'my-class' })).to match(/<li class=".*my-class.*"/)
|
||||
end
|
||||
|
||||
it 'creates a tab with custom classes for enclosing list item with content block provided' do
|
||||
expect(gl_tab_link_to('/url', { tab_class: 'my-class' }) { 'Link' }).to match(/<li class=".*my-class.*"/)
|
||||
end
|
||||
|
||||
it 'creates a tab with custom classes for anchor element' do
|
||||
expect(gl_tab_link_to('Link', '/url', { class: 'my-class' })).to match(/<a class=".*my-class.*"/)
|
||||
end
|
||||
|
||||
|
@ -161,5 +169,11 @@ RSpec.describe TabHelper do
|
|||
expect(gl_tab_counter_badge(1, { class: 'js-test' })).to eq('<span class="js-test badge badge-muted badge-pill gl-badge sm gl-tab-counter-badge">1</span>')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with data attributes' do
|
||||
it 'creates a tab counter badge with the data attributes' do
|
||||
expect(gl_tab_counter_badge(1, { data: { some_attribute: 'foo' } })).to eq('<span class="badge badge-muted badge-pill gl-badge sm gl-tab-counter-badge" data-some-attribute="foo">1</span>')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
|
||||
describe '#satisfied_by?' do
|
||||
let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.head_commit.sha) }
|
||||
|
||||
subject { described_class.new(globs).satisfied_by?(pipeline, nil) }
|
||||
shared_examples 'an exists rule with a context' do
|
||||
subject { described_class.new(globs).satisfied_by?(pipeline, context) }
|
||||
|
||||
it_behaves_like 'a glob matching rule' do
|
||||
let(:project) { create(:project, :custom_repo, files: files) }
|
||||
|
@ -24,4 +22,26 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
|
|||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#satisfied_by?' do
|
||||
let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.head_commit.sha) }
|
||||
|
||||
context 'when context is Build::Context::Build' do
|
||||
it_behaves_like 'an exists rule with a context' do
|
||||
let(:context) { Gitlab::Ci::Build::Context::Build.new(pipeline, sha: 'abc1234') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when context is Build::Context::Global' do
|
||||
it_behaves_like 'an exists rule with a context' do
|
||||
let(:context) { Gitlab::Ci::Build::Context::Global.new(pipeline, yaml_variables: {}) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when context is Config::External::Context' do
|
||||
it_behaves_like 'an exists rule with a context' do
|
||||
let(:context) { Gitlab::Ci::Config::External::Context.new(project: project, sha: project.repository.tree.sha) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ require 'fast_spec_helper'
|
|||
RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do
|
||||
let(:factory) do
|
||||
Gitlab::Config::Entry::Factory.new(described_class)
|
||||
.value(config)
|
||||
.value(config)
|
||||
end
|
||||
|
||||
subject(:entry) { factory.create! }
|
||||
|
@ -25,6 +25,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do
|
|||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'when specifying an exists: clause' do
|
||||
let(:config) { { exists: './this.md' } }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'using a list of multiple expressions' do
|
||||
let(:config) { { if: ['$MY_VAR == "this"', '$YOUR_VAR == "that"'] } }
|
||||
|
||||
|
@ -86,5 +92,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do
|
|||
expect(subject).to eq(if: '$THIS || $THAT')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when specifying an exists: clause' do
|
||||
let(:config) { { exists: './test.md' } }
|
||||
|
||||
it 'returns the config' do
|
||||
expect(subject).to eq(exists: './test.md')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -406,7 +406,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
|
|||
context 'when rules defined' do
|
||||
context 'when a rule is invalid' do
|
||||
let(:values) do
|
||||
{ include: [{ local: 'builds.yml', rules: [{ exists: ['$MY_VAR'] }] }] }
|
||||
{ include: [{ local: 'builds.yml', rules: [{ changes: ['$MY_VAR'] }] }] }
|
||||
end
|
||||
|
||||
it 'raises IncludeError' do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Config::External::Rules do
|
||||
let(:rule_hashes) {}
|
||||
|
@ -32,6 +32,26 @@ RSpec.describe Gitlab::Ci::Config::External::Rules do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when there is a rule with exists' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['test.md']) }
|
||||
let(:rule_hashes) { [{ exists: 'Dockerfile' }] }
|
||||
|
||||
context 'when the file does not exist' do
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
|
||||
context 'when the file exists' do
|
||||
let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['Dockerfile']) }
|
||||
|
||||
before do
|
||||
project.repository.create_file(project.owner, 'Dockerfile', "commit", message: 'test', branch_name: "master")
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a rule with if and when' do
|
||||
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] }
|
||||
|
||||
|
@ -41,12 +61,12 @@ RSpec.describe Gitlab::Ci::Config::External::Rules do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when there is a rule with exists' do
|
||||
let(:rule_hashes) { [{ exists: ['$MY_VAR'] }] }
|
||||
context 'when there is a rule with changes' do
|
||||
let(:rule_hashes) { [{ changes: ['$MY_VAR'] }] }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
|
||||
'invalid include rule: {:exists=>["$MY_VAR"]}')
|
||||
'invalid include rule: {:changes=>["$MY_VAR"]}')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -597,7 +597,7 @@ RSpec.describe Gitlab::Ci::Config do
|
|||
job1: {
|
||||
script: ["echo 'hello from main file'"],
|
||||
variables: {
|
||||
VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
|
||||
VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -727,30 +727,70 @@ RSpec.describe Gitlab::Ci::Config do
|
|||
end
|
||||
end
|
||||
|
||||
context "when an 'include' has rules with a project variable" do
|
||||
let(:gitlab_ci_yml) do
|
||||
<<~HEREDOC
|
||||
include:
|
||||
- local: #{local_location}
|
||||
rules:
|
||||
- if: $CI_PROJECT_ID == "#{project_id}"
|
||||
image: ruby:2.7
|
||||
HEREDOC
|
||||
end
|
||||
context "when an 'include' has rules" do
|
||||
context "when the rule is an if" do
|
||||
let(:gitlab_ci_yml) do
|
||||
<<~HEREDOC
|
||||
include:
|
||||
- local: #{local_location}
|
||||
rules:
|
||||
- if: $CI_PROJECT_ID == "#{project_id}"
|
||||
image: ruby:2.7
|
||||
HEREDOC
|
||||
end
|
||||
|
||||
context 'when the rules condition is satisfied' do
|
||||
let(:project_id) { project.id }
|
||||
context 'when the rules condition is satisfied' do
|
||||
let(:project_id) { project.id }
|
||||
|
||||
it 'includes the file' do
|
||||
expect(config.to_hash).to include(local_location_hash)
|
||||
it 'includes the file' do
|
||||
expect(config.to_hash).to include(local_location_hash)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the rules condition is satisfied' do
|
||||
let(:project_id) { non_existing_record_id }
|
||||
|
||||
it 'does not include the file' do
|
||||
expect(config.to_hash).not_to include(local_location_hash)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the rules condition is satisfied' do
|
||||
let(:project_id) { non_existing_record_id }
|
||||
context "when the rule is an exists" do
|
||||
let(:gitlab_ci_yml) do
|
||||
<<~HEREDOC
|
||||
include:
|
||||
- local: #{local_location}
|
||||
rules:
|
||||
- exists: "#{filename}"
|
||||
image: ruby:2.7
|
||||
HEREDOC
|
||||
end
|
||||
|
||||
it 'does not include the file' do
|
||||
expect(config.to_hash).not_to include(local_location_hash)
|
||||
before do
|
||||
project.repository.create_file(
|
||||
project.creator,
|
||||
'my_builds.yml',
|
||||
local_file_content,
|
||||
message: 'Add my_builds.yml',
|
||||
branch_name: '12345'
|
||||
)
|
||||
end
|
||||
|
||||
context 'when the exists file does not exist' do
|
||||
let(:filename) { 'not_a_real_file.md' }
|
||||
|
||||
it 'does not include the file' do
|
||||
expect(config.to_hash).not_to include(local_location_hash)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the exists file does exist' do
|
||||
let(:filename) { 'my_builds.yml' }
|
||||
|
||||
it 'does include the file' do
|
||||
expect(config.to_hash).to include(local_location_hash)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2920,6 +2920,8 @@ RSpec.describe MergeRequest, factory_default: :keep do
|
|||
params = {}
|
||||
merge_jid = 'hash-123'
|
||||
|
||||
allow(MergeWorker).to receive(:with_status).and_return(MergeWorker)
|
||||
|
||||
expect(merge_request).to receive(:expire_etag_cache)
|
||||
expect(MergeWorker).to receive(:perform_async).with(merge_request.id, user_id, params) do
|
||||
merge_jid
|
||||
|
@ -2938,6 +2940,10 @@ RSpec.describe MergeRequest, factory_default: :keep do
|
|||
|
||||
subject(:execute) { merge_request.rebase_async(user_id) }
|
||||
|
||||
before do
|
||||
allow(RebaseWorker).to receive(:with_status).and_return(RebaseWorker)
|
||||
end
|
||||
|
||||
it 'atomically enqueues a RebaseWorker job and updates rebase_jid' do
|
||||
expect(RebaseWorker)
|
||||
.to receive(:perform_async)
|
||||
|
|
|
@ -6225,4 +6225,31 @@ RSpec.describe User do
|
|||
expect(described_class.get_ids_by_username([user_name])).to match_array([user_id])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'user_project' do
|
||||
it 'returns users project matched by username and public visibility' do
|
||||
user = create(:user)
|
||||
public_project = create(:project, :public, path: user.username, namespace: user.namespace)
|
||||
create(:project, namespace: user.namespace)
|
||||
|
||||
expect(user.user_project).to eq(public_project)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'user_readme' do
|
||||
it 'returns readme from user project' do
|
||||
user = create(:user)
|
||||
create(:project, :repository, :public, path: user.username, namespace: user.namespace)
|
||||
|
||||
expect(user.user_readme.name).to eq('README.md')
|
||||
expect(user.user_readme.data).to include('testme')
|
||||
end
|
||||
|
||||
it 'returns nil if project is private' do
|
||||
user = create(:user)
|
||||
create(:project, :repository, :private, path: user.username, namespace: user.namespace)
|
||||
|
||||
expect(user.user_readme).to be(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,11 +34,11 @@ RSpec.describe UsersStatistics do
|
|||
|
||||
describe '.create_current_stats!' do
|
||||
before do
|
||||
create_list(:user_highest_role, 4)
|
||||
create_list(:user_highest_role, 1)
|
||||
create_list(:user_highest_role, 2, :guest)
|
||||
create_list(:user_highest_role, 3, :reporter)
|
||||
create_list(:user_highest_role, 4, :developer)
|
||||
create_list(:user_highest_role, 3, :maintainer)
|
||||
create_list(:user_highest_role, 2, :reporter)
|
||||
create_list(:user_highest_role, 2, :developer)
|
||||
create_list(:user_highest_role, 2, :maintainer)
|
||||
create_list(:user_highest_role, 2, :owner)
|
||||
create_list(:user, 2, :bot)
|
||||
create_list(:user, 1, :blocked)
|
||||
|
@ -49,11 +49,11 @@ RSpec.describe UsersStatistics do
|
|||
context 'when successful' do
|
||||
it 'creates an entry with the current statistics values' do
|
||||
expect(described_class.create_current_stats!).to have_attributes(
|
||||
without_groups_and_projects: 4,
|
||||
without_groups_and_projects: 1,
|
||||
with_highest_role_guest: 2,
|
||||
with_highest_role_reporter: 3,
|
||||
with_highest_role_developer: 4,
|
||||
with_highest_role_maintainer: 3,
|
||||
with_highest_role_reporter: 2,
|
||||
with_highest_role_developer: 2,
|
||||
with_highest_role_maintainer: 2,
|
||||
with_highest_role_owner: 2,
|
||||
bots: 2,
|
||||
blocked: 1
|
||||
|
|
|
@ -3278,6 +3278,8 @@ RSpec.describe API::MergeRequests do
|
|||
|
||||
context 'when skip_ci parameter is set' do
|
||||
it 'enqueues a rebase of the merge request with skip_ci flag set' do
|
||||
allow(RebaseWorker).to receive(:with_status).and_return(RebaseWorker)
|
||||
|
||||
expect(RebaseWorker).to receive(:perform_async).with(merge_request.id, user.id, true).and_call_original
|
||||
|
||||
Sidekiq::Testing.fake! do
|
||||
|
|
|
@ -28,10 +28,25 @@ RSpec.describe API::Terraform::Modules::V1::Packages do
|
|||
|
||||
describe 'GET /api/v4/packages/terraform/modules/v1/:module_namespace/:module_name/:module_system/versions' do
|
||||
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/versions") }
|
||||
let(:headers) { {} }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{tokens[:job_token]}" } }
|
||||
|
||||
subject { get(url, headers: headers) }
|
||||
|
||||
context 'with a conflicting package name' do
|
||||
let!(:conflicting_package) { create(:terraform_module_package, project: project, name: "conflict-#{package.name}", version: '2.0.0') }
|
||||
|
||||
before do
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns only one version' do
|
||||
subject
|
||||
|
||||
expect(json_response['modules'][0]['versions'].size).to eq(1)
|
||||
expect(json_response['modules'][0]['versions'][0]['version']).to eq('1.0.0')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid namespace' do
|
||||
where(:visibility, :user_role, :member, :token_type, :valid_token, :shared_examples_name, :expected_status) do
|
||||
:public | :developer | true | :personal_access_token | true | 'returns terraform module packages' | :success
|
||||
|
|
|
@ -60,6 +60,7 @@ RSpec.describe Import::GitlabGroupsController do
|
|||
end
|
||||
|
||||
it 'imports the group data', :sidekiq_inline do
|
||||
allow(GroupImportWorker).to receive(:with_status).and_return(GroupImportWorker)
|
||||
allow(GroupImportWorker).to receive(:perform_async).and_call_original
|
||||
|
||||
import_request
|
||||
|
@ -67,7 +68,6 @@ RSpec.describe Import::GitlabGroupsController do
|
|||
group = Group.find_by(name: 'test-group-import')
|
||||
|
||||
expect(GroupImportWorker).to have_received(:perform_async).with(user.id, group.id)
|
||||
|
||||
expect(group.description).to eq 'A voluptate non sequi temporibus quam at.'
|
||||
expect(group.visibility_level).to eq Gitlab::VisibilityLevel::PRIVATE
|
||||
end
|
||||
|
|
|
@ -24,6 +24,10 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do
|
|||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(MergeWorker).to receive(:with_status).and_return(MergeWorker)
|
||||
end
|
||||
|
||||
describe "#available_for?" do
|
||||
subject { service.available_for?(mr_merge_if_green_enabled) }
|
||||
|
||||
|
|
|
@ -7,6 +7,10 @@ RSpec.describe Groups::ImportExport::ImportService do
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
before do
|
||||
allow(GroupImportWorker).to receive(:with_status).and_return(GroupImportWorker)
|
||||
end
|
||||
|
||||
context 'when the job can be successfully scheduled' do
|
||||
subject(:import_service) { described_class.new(group: group, user: user) }
|
||||
|
||||
|
@ -20,6 +24,8 @@ RSpec.describe Groups::ImportExport::ImportService do
|
|||
end
|
||||
|
||||
it 'enqueues an import job' do
|
||||
allow(GroupImportWorker).to receive(:with_status).and_return(GroupImportWorker)
|
||||
|
||||
expect(GroupImportWorker).to receive(:perform_async).with(user.id, group.id)
|
||||
|
||||
import_service.async_execute
|
||||
|
|
|
@ -39,6 +39,10 @@ end
|
|||
# let(:status_api) { status_create_self_monitoring_project_admin_application_settings_path }
|
||||
# subject { post create_self_monitoring_project_admin_application_settings_path }
|
||||
RSpec.shared_examples 'triggers async worker, returns sidekiq job_id with response accepted' do
|
||||
before do
|
||||
allow(worker_class).to receive(:with_status).and_return(worker_class)
|
||||
end
|
||||
|
||||
it 'returns sidekiq job_id of expected length' do
|
||||
subject
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ end
|
|||
|
||||
RSpec.shared_examples 'returns in_progress based on Sidekiq::Status' do
|
||||
it 'returns true when job is enqueued' do
|
||||
jid = described_class.perform_async
|
||||
jid = described_class.with_status.perform_async
|
||||
|
||||
expect(described_class.in_progress?(jid)).to eq(true)
|
||||
end
|
||||
|
|
|
@ -598,4 +598,48 @@ RSpec.describe ApplicationWorker do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_status' do
|
||||
around do |example|
|
||||
Sidekiq::Testing.fake!(&example)
|
||||
end
|
||||
|
||||
context 'when the worker does have status_expiration set' do
|
||||
let(:status_expiration_worker) do
|
||||
Class.new(worker) do
|
||||
sidekiq_options status_expiration: 3
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses status_expiration from the worker' do
|
||||
status_expiration_worker.with_status.perform_async
|
||||
|
||||
expect(Sidekiq::Queues[status_expiration_worker.queue].first).to include('status_expiration' => 3)
|
||||
expect(Sidekiq::Queues[status_expiration_worker.queue].length).to eq(1)
|
||||
end
|
||||
|
||||
it 'uses status_expiration from the worker without with_status' do
|
||||
status_expiration_worker.perform_async
|
||||
|
||||
expect(Sidekiq::Queues[status_expiration_worker.queue].first).to include('status_expiration' => 3)
|
||||
expect(Sidekiq::Queues[status_expiration_worker.queue].length).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the worker does not have status_expiration set' do
|
||||
it 'uses the default status_expiration' do
|
||||
worker.with_status.perform_async
|
||||
|
||||
expect(Sidekiq::Queues[worker.queue].first).to include('status_expiration' => Gitlab::SidekiqStatus::DEFAULT_EXPIRATION)
|
||||
expect(Sidekiq::Queues[worker.queue].length).to eq(1)
|
||||
end
|
||||
|
||||
it 'does not set status_expiration without with_status' do
|
||||
worker.perform_async
|
||||
|
||||
expect(Sidekiq::Queues[worker.queue].first).not_to include('status_expiration')
|
||||
expect(Sidekiq::Queues[worker.queue].length).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue