Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e5731d5194
commit
927df95cc4
|
|
@ -208,17 +208,6 @@ Naming/RescuedExceptionsVariableName:
|
|||
RSpec/ContextWording:
|
||||
Enabled: false
|
||||
|
||||
RSpec/EmptyLineAfterSharedExample:
|
||||
Exclude:
|
||||
- 'ee/spec/mailers/notify_spec.rb'
|
||||
- 'ee/spec/services/quick_actions/interpret_service_spec.rb'
|
||||
- 'spec/controllers/repositories/git_http_controller_spec.rb'
|
||||
- 'spec/finders/projects/serverless/functions_finder_spec.rb'
|
||||
- 'spec/lib/gitlab/hook_data/issuable_builder_spec.rb'
|
||||
- 'spec/lib/gitlab/legacy_github_import/importer_spec.rb'
|
||||
- 'spec/models/event_spec.rb'
|
||||
- 'spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb'
|
||||
|
||||
# Offense count: 879
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
filterALertsByStatus(tabIndex) {
|
||||
filterAlertsByStatus(tabIndex) {
|
||||
this.statusFilter = this.$options.statusTabs[tabIndex].filters;
|
||||
},
|
||||
capitalizeFirstCharacter,
|
||||
|
|
@ -184,7 +184,7 @@ export default {
|
|||
{{ $options.i18n.errorMsg }}
|
||||
</gl-alert>
|
||||
|
||||
<gl-tabs v-if="glFeatures.alertListStatusFilteringEnabled" @input="filterALertsByStatus">
|
||||
<gl-tabs v-if="glFeatures.alertListStatusFilteringEnabled" @input="filterAlertsByStatus">
|
||||
<gl-tab v-for="tab in $options.statusTabs" :key="tab.status">
|
||||
<template slot="title">
|
||||
<span>{{ tab.title }}</span>
|
||||
|
|
|
|||
|
|
@ -74,20 +74,27 @@ function initStatusTriggers() {
|
|||
}
|
||||
}
|
||||
|
||||
function trackShowUserDropdownLink(trackEvent, elToTrack, el) {
|
||||
const { trackLabel, trackProperty } = elToTrack.dataset;
|
||||
|
||||
$(el).on('shown.bs.dropdown', () => {
|
||||
Tracking.event(document.body.dataset.page, trackEvent, {
|
||||
label: trackLabel,
|
||||
property: trackProperty,
|
||||
});
|
||||
});
|
||||
}
|
||||
export function initNavUserDropdownTracking() {
|
||||
const el = document.querySelector('.js-nav-user-dropdown');
|
||||
const buyEl = document.querySelector('.js-buy-ci-minutes-link');
|
||||
const upgradeEl = document.querySelector('.js-upgrade-plan-link');
|
||||
|
||||
if (el && buyEl) {
|
||||
const { trackLabel, trackProperty } = buyEl.dataset;
|
||||
const trackEvent = 'show_buy_ci_minutes';
|
||||
trackShowUserDropdownLink('show_buy_ci_minutes', buyEl, el);
|
||||
}
|
||||
|
||||
$(el).on('shown.bs.dropdown', () => {
|
||||
Tracking.event(undefined, trackEvent, {
|
||||
label: trackLabel,
|
||||
property: trackProperty,
|
||||
});
|
||||
});
|
||||
if (el && upgradeEl) {
|
||||
trackShowUserDropdownLink('show_upgrade_link', upgradeEl, el);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -228,13 +228,11 @@ export default {
|
|||
'promVariables',
|
||||
'isUpdatingStarredValue',
|
||||
]),
|
||||
...mapGetters('monitoringDashboard', ['getMetricStates', 'filteredEnvironments']),
|
||||
firstDashboard() {
|
||||
return this.allDashboards.length > 0 ? this.allDashboards[0] : {};
|
||||
},
|
||||
selectedDashboard() {
|
||||
return this.allDashboards.find(d => d.path === this.currentDashboard) || this.firstDashboard;
|
||||
},
|
||||
...mapGetters('monitoringDashboard', [
|
||||
'selectedDashboard',
|
||||
'getMetricStates',
|
||||
'filteredEnvironments',
|
||||
]),
|
||||
showRearrangePanelsBtn() {
|
||||
return !this.showEmptyState && this.rearrangePanelsAvailable;
|
||||
},
|
||||
|
|
@ -242,7 +240,10 @@ export default {
|
|||
return (
|
||||
this.customMetricsAvailable &&
|
||||
!this.showEmptyState &&
|
||||
this.firstDashboard === this.selectedDashboard
|
||||
// Custom metrics only avaialble on system dashboards because
|
||||
// they are stored in the database. This can be improved. See:
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/issues/28241
|
||||
this.selectedDashboard?.system_dashboard
|
||||
);
|
||||
},
|
||||
shouldShowEnvironmentsDropdownNoMatchedMsg() {
|
||||
|
|
@ -269,7 +270,7 @@ export default {
|
|||
},
|
||||
expandedPanel: {
|
||||
handler({ group, panel }) {
|
||||
const dashboardPath = this.currentDashboard || this.firstDashboard.path;
|
||||
const dashboardPath = this.currentDashboard || this.selectedDashboard?.path;
|
||||
updateHistory({
|
||||
url: panelToUrl(dashboardPath, group, panel),
|
||||
title: document.title,
|
||||
|
|
@ -341,7 +342,7 @@ export default {
|
|||
this.selectedTimeRange = defaultTimeRange;
|
||||
},
|
||||
generatePanelUrl(groupKey, panel) {
|
||||
const dashboardPath = this.currentDashboard || this.firstDashboard.path;
|
||||
const dashboardPath = this.currentDashboard || this.selectedDashboard?.path;
|
||||
return panelToUrl(dashboardPath, groupKey, panel);
|
||||
},
|
||||
hideAddMetricModal() {
|
||||
|
|
@ -597,7 +598,10 @@ export default {
|
|||
</gl-modal>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedDashboard.can_edit" class="mb-2 mr-2 d-flex d-sm-block">
|
||||
<div
|
||||
v-if="selectedDashboard && selectedDashboard.can_edit"
|
||||
class="mb-2 mr-2 d-flex d-sm-block"
|
||||
>
|
||||
<gl-deprecated-button
|
||||
class="flex-grow-1 js-edit-link"
|
||||
:href="selectedDashboard.project_blob_path"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ const metricsIdsInPanel = panel =>
|
|||
export const selectedDashboard = state => {
|
||||
const { allDashboards } = state;
|
||||
return (
|
||||
allDashboards.find(({ path }) => path === state.currentDashboard) || allDashboards[0] || null
|
||||
allDashboards.find(d => d.path === state.currentDashboard) ||
|
||||
allDashboards.find(d => d.default) ||
|
||||
null
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import GitlabTeamMemberBadge from '~/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
timeAgoTooltip,
|
||||
GitlabTeamMemberBadge,
|
||||
GitlabTeamMemberBadge: () =>
|
||||
import('ee_component/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'),
|
||||
},
|
||||
props: {
|
||||
author: {
|
||||
|
|
@ -62,9 +62,6 @@ export default {
|
|||
hasAuthor() {
|
||||
return this.author && Object.keys(this.author).length;
|
||||
},
|
||||
showGitlabTeamMemberBadge() {
|
||||
return this.author?.is_gitlab_employee;
|
||||
},
|
||||
authorLinkClasses() {
|
||||
return {
|
||||
hover: this.isUsernameLinkHovered,
|
||||
|
|
@ -156,7 +153,7 @@ export default {
|
|||
@mouseleave="handleUsernameMouseLeave"
|
||||
><span class="note-headline-light">@{{ author.username }}</span>
|
||||
</a>
|
||||
<gitlab-team-member-badge v-if="showGitlabTeamMemberBadge" />
|
||||
<gitlab-team-member-badge v-if="author && author.is_gitlab_employee" />
|
||||
</span>
|
||||
</template>
|
||||
<span v-else>{{ __('A deleted user') }}</span>
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
const GITLAB_TEAM_MEMBER_LABEL = __('GitLab Team Member');
|
||||
|
||||
export default {
|
||||
name: 'GitlabTeamMemberBadge',
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
components: { GlIcon },
|
||||
gitlabTeamMemberLabel: GITLAB_TEAM_MEMBER_LABEL,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
v-gl-tooltip.hover
|
||||
:title="$options.gitlabTeamMemberLabel"
|
||||
role="img"
|
||||
:aria-label="$options.gitlabTeamMemberLabel"
|
||||
class="d-inline-block align-middle"
|
||||
>
|
||||
<gl-icon name="tanuki-verified" class="gl-text-purple d-block" />
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -553,6 +553,7 @@
|
|||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
a.upgrade-plan-link gl-emoji,
|
||||
a.ci-minutes-emoji gl-emoji,
|
||||
a.trial-link gl-emoji {
|
||||
font-size: $gl-font-size;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FreezePeriodsFinder
|
||||
def initialize(project, current_user = nil)
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def execute
|
||||
return Ci::FreezePeriod.none unless Ability.allowed?(@current_user, :read_freeze_period, @project)
|
||||
|
||||
@project.freeze_periods
|
||||
end
|
||||
end
|
||||
|
|
@ -5,6 +5,8 @@ module Ci
|
|||
include StripAttribute
|
||||
self.table_name = 'ci_freeze_periods'
|
||||
|
||||
default_scope { order(created_at: :asc) }
|
||||
|
||||
belongs_to :project, inverse_of: :freeze_periods
|
||||
|
||||
strip_attributes :freeze_start, :freeze_end
|
||||
|
|
|
|||
|
|
@ -305,9 +305,10 @@ class Group < Namespace
|
|||
# rubocop: enable CodeReuse/ServiceClass
|
||||
|
||||
# rubocop: disable CodeReuse/ServiceClass
|
||||
def refresh_members_authorized_projects(blocking: true)
|
||||
UserProjectAccessChangedService.new(user_ids_for_project_authorizations)
|
||||
.execute(blocking: blocking)
|
||||
def refresh_members_authorized_projects(blocking: true, priority: UserProjectAccessChangedService::HIGH_PRIORITY)
|
||||
UserProjectAccessChangedService
|
||||
.new(user_ids_for_project_authorizations)
|
||||
.execute(blocking: blocking, priority: priority)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ServiceClass
|
||||
|
||||
|
|
|
|||
|
|
@ -171,6 +171,14 @@ class Milestone < ApplicationRecord
|
|||
alias_method :group_milestone?, :group_timebox?
|
||||
alias_method :project_milestone?, :project_timebox?
|
||||
|
||||
def parent
|
||||
if group_milestone?
|
||||
group
|
||||
else
|
||||
project
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def milestone_format_reference(format = :iid)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ci
|
||||
class FreezePeriodPolicy < BasePolicy
|
||||
delegate { @subject.resource_parent }
|
||||
end
|
||||
end
|
||||
|
|
@ -362,6 +362,10 @@ class ProjectPolicy < BasePolicy
|
|||
enable :destroy_deploy_token
|
||||
enable :read_prometheus_alerts
|
||||
enable :admin_terraform_state
|
||||
enable :create_freeze_period
|
||||
enable :read_freeze_period
|
||||
enable :update_freeze_period
|
||||
enable :destroy_freeze_period
|
||||
end
|
||||
|
||||
rule { public_project & metrics_dashboard_allowed }.policy do
|
||||
|
|
|
|||
|
|
@ -15,11 +15,8 @@ module Clusters
|
|||
def execute
|
||||
return unless management_project_required?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
project = create_management_project!
|
||||
|
||||
update_cluster!(project)
|
||||
end
|
||||
project = create_management_project!
|
||||
update_cluster!(project)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ module MergeRequests
|
|||
|
||||
# Don't try to print expensive instance variables.
|
||||
def inspect
|
||||
return "#<#{self.class}>" unless respond_to?(:merge_request)
|
||||
|
||||
"#<#{self.class} #{merge_request.to_reference(full: true)}>"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -108,8 +108,22 @@ module Projects
|
|||
# users in the background
|
||||
def setup_authorizations
|
||||
if @project.group
|
||||
@project.group.refresh_members_authorized_projects(blocking: false)
|
||||
current_user.refresh_authorized_projects
|
||||
|
||||
if Feature.enabled?(:specialized_project_authorization_workers)
|
||||
AuthorizedProjectUpdate::ProjectCreateWorker.perform_async(@project.id)
|
||||
# AuthorizedProjectsWorker uses an exclusive lease per user but
|
||||
# specialized workers might have synchronization issues. Until we
|
||||
# compare the inconsistency rates of both approaches, we still run
|
||||
# AuthorizedProjectsWorker but with some delay and lower urgency as a
|
||||
# safety net.
|
||||
@project.group.refresh_members_authorized_projects(
|
||||
blocking: false,
|
||||
priority: UserProjectAccessChangedService::LOW_PRIORITY
|
||||
)
|
||||
else
|
||||
@project.group.refresh_members_authorized_projects(blocking: false)
|
||||
end
|
||||
else
|
||||
@project.add_maintainer(@project.namespace.owner, current_user: current_user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,17 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserProjectAccessChangedService
|
||||
DELAY = 1.hour
|
||||
|
||||
HIGH_PRIORITY = :high
|
||||
LOW_PRIORITY = :low
|
||||
|
||||
def initialize(user_ids)
|
||||
@user_ids = Array.wrap(user_ids)
|
||||
end
|
||||
|
||||
def execute(blocking: true)
|
||||
def execute(blocking: true, priority: HIGH_PRIORITY)
|
||||
bulk_args = @user_ids.map { |id| [id] }
|
||||
|
||||
if blocking
|
||||
AuthorizedProjectsWorker.bulk_perform_and_wait(bulk_args)
|
||||
else
|
||||
AuthorizedProjectsWorker.bulk_perform_async(bulk_args) # rubocop:disable Scalability/BulkPerformWithContext
|
||||
if priority == HIGH_PRIORITY
|
||||
AuthorizedProjectsWorker.bulk_perform_async(bulk_args) # rubocop:disable Scalability/BulkPerformWithContext
|
||||
else
|
||||
AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker.bulk_perform_in(DELAY, bulk_args) # rubocop:disable Scalability/BulkPerformWithContext
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
%li
|
||||
= link_to s_("CurrentUser|Settings"), profile_path, data: { qa_selector: 'settings_link' }
|
||||
= render_if_exists 'layouts/header/buy_ci_minutes', project: @project, namespace: @group
|
||||
= render_if_exists 'layouts/header/upgrade'
|
||||
|
||||
- if current_user_menu?(:help)
|
||||
%li.divider.d-md-none
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@
|
|||
= link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'), class: 'btn btn-md new-gl-button js-close-callout'
|
||||
|
||||
%button.gl-banner-close.close.js-close-callout{ type: 'button',
|
||||
'aria-label' => 'Dismiss Auto DevOps box' }
|
||||
'aria-label' => s_('AutoDevOps|Dismiss Auto DevOps box') }
|
||||
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
= http_clone_button(project)
|
||||
= render_if_exists 'shared/kerberos_clone_button', project: project
|
||||
|
||||
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' }
|
||||
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: _('Project clone URL') }
|
||||
.input-group-append
|
||||
= clipboard_button(target: '#project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard")
|
||||
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
%li.js-builds-dropdown-loading.hidden
|
||||
.loading-container.text-center
|
||||
%span.spinner{ 'aria-label': 'Loading' }
|
||||
%span.spinner{ 'aria-label': _('Loading') }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- if show_no_ssh_key_message?
|
||||
%div{ class: 'no-ssh-key-message gl-alert gl-alert-warning', role: 'alert' }
|
||||
= sprite_icon('warning', size: 16, css_class: 'gl-icon s16 gl-alert-icon gl-alert-icon-no-title')
|
||||
%button{ class: 'gl-alert-dismiss hide-no-ssh-message', type: 'button', 'aria-label': 'Dismiss' }
|
||||
%button{ class: 'gl-alert-dismiss hide-no-ssh-message', type: 'button', 'aria-label': _('Dismiss') }
|
||||
= sprite_icon('close', size: 16, css_class: 'gl-icon s16')
|
||||
.gl-alert-body
|
||||
= s_("MissingSSHKeyWarningLink|You won't be able to pull or push project code via SSH until you add an SSH key to your profile").html_safe
|
||||
|
|
|
|||
|
|
@ -4,20 +4,20 @@
|
|||
|
||||
%ul.nav-links.issues-state-filters.mobile-separator.nav.nav-tabs
|
||||
%li{ class: active_when(params[:state] == 'opened') }>
|
||||
= link_to page_filter_path(state: 'opened'), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do
|
||||
= link_to page_filter_path(state: 'opened'), id: 'state-opened', title: _("Filter by %{page_context_word} that are currently opened.") % { page_context_word: page_context_word }, data: { state: 'opened' } do
|
||||
#{issuables_state_counter_text(type, :opened, display_count)}
|
||||
|
||||
- if type == :merge_requests
|
||||
%li{ class: active_when(params[:state] == 'merged') }>
|
||||
= link_to page_filter_path(state: 'merged'), id: 'state-merged', title: 'Filter by merge requests that are currently merged.', data: { state: 'merged' } do
|
||||
= link_to page_filter_path(state: 'merged'), id: 'state-merged', title: _('Filter by merge requests that are currently merged.'), data: { state: 'merged' } do
|
||||
#{issuables_state_counter_text(type, :merged, display_count)}
|
||||
|
||||
%li{ class: active_when(params[:state] == 'closed') }>
|
||||
= link_to page_filter_path(state: 'closed'), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.', data: { state: 'closed' } do
|
||||
= link_to page_filter_path(state: 'closed'), id: 'state-closed', title: _('Filter by merge requests that are currently closed and unmerged.'), data: { state: 'closed' } do
|
||||
#{issuables_state_counter_text(type, :closed, display_count)}
|
||||
- else
|
||||
%li{ class: active_when(params[:state] == 'closed') }>
|
||||
= link_to page_filter_path(state: 'closed'), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed', qa_selector: 'closed_issues_link' } do
|
||||
= link_to page_filter_path(state: 'closed'), id: 'state-closed', title: _('Filter by issues that are currently closed.'), data: { state: 'closed', qa_selector: 'closed_issues_link' } do
|
||||
#{issuables_state_counter_text(type, :closed, display_count)}
|
||||
|
||||
= render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all, display_count)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Skip mergeability check when listing MRs in the API
|
||||
merge_request: 31890
|
||||
author:
|
||||
type: performance
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Fix bug in Groups API when statistics are requested in an unauthenticated
|
||||
API call
|
||||
merge_request: 32057
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Externalize i18n strings from ./app/views/shared/issuable/_nav.html.haml
|
||||
merge_request: 32165
|
||||
author: Gilang Gumilar
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Externalize i18n aria-label strings from ./app/views/shared/*
|
||||
merge_request: 32142
|
||||
author: Gilang Gumilar
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Expose Freeze Periods in REST API
|
||||
merge_request: 29382
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix database schema inconsistency with not-null checks
|
||||
merge_request: 31930
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChangeVerificationChecksumFieldTypeInPackageFile < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
# The use of this column is behind a feature flag that never got enabled,
|
||||
# so it's safe to remove it in a normal migration
|
||||
remove_column :packages_package_files, :verification_checksum, :string # rubocop:disable Migration/RemoveColumn
|
||||
add_column :packages_package_files, :verification_checksum, :binary
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :packages_package_files, :verification_checksum, :binary
|
||||
add_column :packages_package_files, :verification_checksum, :string
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddChecksumIndexToPackageFile < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :packages_package_files, :verification_checksum, where: "(verification_checksum IS NOT NULL)", name: "packages_packages_verification_checksum_partial"
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index :packages_package_files, :verification_checksum
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FixNotNullCheckConstraintInconsistency < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
table = :application_settings
|
||||
|
||||
%i(container_registry_vendor container_registry_version).each do |column|
|
||||
change_column_null table, column, false
|
||||
remove_not_null_constraint(table, column) if check_not_null_constraint_exists?(table, column)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
# No-op: for regular systems without the inconsistency, #up is a no-op, too
|
||||
end
|
||||
end
|
||||
|
|
@ -4668,9 +4668,9 @@ CREATE TABLE public.packages_package_files (
|
|||
file_sha256 bytea,
|
||||
verification_retry_at timestamp with time zone,
|
||||
verified_at timestamp with time zone,
|
||||
verification_checksum character varying(255),
|
||||
verification_failure character varying(255),
|
||||
verification_retry_count integer
|
||||
verification_retry_count integer,
|
||||
verification_checksum bytea
|
||||
);
|
||||
|
||||
CREATE SEQUENCE public.packages_package_files_id_seq
|
||||
|
|
@ -13738,6 +13738,8 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200408175424
|
||||
20200408212219
|
||||
20200409085956
|
||||
20200409105455
|
||||
20200409105456
|
||||
20200409211607
|
||||
20200410104828
|
||||
20200410232012
|
||||
|
|
@ -13817,6 +13819,7 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200511162115
|
||||
20200512085150
|
||||
20200512164334
|
||||
20200513160930
|
||||
20200513234502
|
||||
20200513235347
|
||||
20200513235532
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ This article expands on [How to Configure LDAP with GitLab CE](../how_to_configu
|
|||
|
||||
## GitLab Enterprise Edition - LDAP features
|
||||
|
||||
[GitLab Enterprise Edition (EE)](https://about.gitlab.com/pricing/) has a number of advantages when it comes to integrating with Active Directory (LDAP):
|
||||
[GitLab Enterprise Edition (EE)](https://about.gitlab.com/pricing/) has several advantages when it comes to integrating with Active Directory (LDAP):
|
||||
|
||||
- [Administrator Sync](../ldap-ee.md#administrator-sync): As an extension of group sync, you can automatically manage your global GitLab administrators. Specify a group CN for `admin_group` and all members of the LDAP group will be given administrator privileges.
|
||||
- [Group Sync](#group-sync): This allows GitLab group membership to be automatically updated based on LDAP group members.
|
||||
|
|
@ -16,7 +16,7 @@ This article expands on [How to Configure LDAP with GitLab CE](../how_to_configu
|
|||
|
||||
- Daily user synchronization: Once a day, GitLab will run a synchronization to check and update GitLab users against LDAP. This process updates all user details automatically.
|
||||
|
||||
On the following section, you'll find a description for each of these features. Read through [LDAP GitLab EE docs](../ldap-ee.md) for complementary information.
|
||||
In the following section, you'll find a description of each of these features. Read through [LDAP GitLab EE docs](../ldap-ee.md) for complementary information.
|
||||
|
||||

|
||||
|
||||
|
|
@ -28,7 +28,7 @@ Group syncing allows AD (LDAP) groups to be mapped to GitLab groups. This provid
|
|||
|
||||
#### Creating group links - example
|
||||
|
||||
As an example, let's suppose we have a "UKGov" GitLab group, which deals with confidential government information. Therefore, it is important that users of this group are given the correct permissions to projects contained within the group. Granular group permissions can be applied based on the AD group.
|
||||
As an example, let's suppose we have a "UKGov" GitLab group, which deals with confidential government information. Therefore, users of this group must be given the correct permissions to projects contained within the group. Granular group permissions can be applied based on the AD group.
|
||||
|
||||
**UK Developers** of our "UKGov" group are given **"developer"** permissions.
|
||||
|
||||
|
|
|
|||
|
|
@ -150,9 +150,9 @@ Project.update_all(visibility_level: 0)
|
|||
#
|
||||
projects = Project.where(pending_delete: true)
|
||||
projects.each do |p|
|
||||
puts "Project name: #{p.id}"
|
||||
puts "Project ID: #{p.id}"
|
||||
puts "Project name: #{p.name}"
|
||||
puts "Repository path: #{p.repository.storage_path}"
|
||||
puts "Repository path: #{p.repository.full_path}"
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ Parameters:
|
|||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
|
||||
| `labels` | string | no | Return merge requests matching a comma separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. `No+Label` (Deprecated) lists all merge requests with no labels. Predefined names are case-insensitive. |
|
||||
| `with_labels_details` | boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) |
|
||||
| `with_merge_status_recheck` | boolean | no | If `true`, this projection requests (but does not guarantee) that the `merge_status` field be recalculated asynchronously. Default is `false`. Introduced in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890) |
|
||||
| `created_after` | datetime | no | Return merge requests created on or after the given time |
|
||||
| `created_before` | datetime | no | Return merge requests created on or before the given time |
|
||||
| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
|
||||
|
|
@ -64,6 +65,13 @@ Parameters:
|
|||
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
|
||||
| `wip` | string | no | Filter merge requests against their `wip` status. `yes` to return *only* WIP merge requests, `no` to return *non* WIP merge requests |
|
||||
|
||||
NOTE: **Note:**
|
||||
[Starting in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890),
|
||||
listing merge requests may not proactively update the `merge_status` field
|
||||
(which also affects the `has_conflicts` field), as this can be an expensive
|
||||
operation. If you are interested in the value of these fields from this
|
||||
endpoint, set the `with_merge_status_recheck` parameter to `true` in the query.
|
||||
|
||||
NOTE: **Note:**
|
||||
[Starting in GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/issues/29984),
|
||||
when `async_merge_request_check_mergeability` feature flag is enabled, the
|
||||
|
|
@ -227,6 +235,7 @@ Parameters:
|
|||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
|
||||
| `labels` | string | no | Return merge requests matching a comma separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. `No+Label` (Deprecated) lists all merge requests with no labels. Predefined names are case-insensitive. |
|
||||
| `with_labels_details` | boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) |
|
||||
| `with_merge_status_recheck` | boolean | no | If `true`, this projection requests (but does not guarantee) that the `merge_status` field be recalculated asynchronously. Default is `false`. Introduced in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890) |
|
||||
| `created_after` | datetime | no | Return merge requests created on or after the given time |
|
||||
| `created_before` | datetime | no | Return merge requests created on or before the given time |
|
||||
| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
|
||||
|
|
@ -390,6 +399,7 @@ Parameters:
|
|||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
|
||||
| `labels` | string | no | Return merge requests matching a comma separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. `No+Label` (Deprecated) lists all merge requests with no labels. Predefined names are case-insensitive. |
|
||||
| `with_labels_details` | boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413)|
|
||||
| `with_merge_status_recheck` | boolean | no | If `true`, this projection requests (but does not guarantee) that the `merge_status` field be recalculated asynchronously. Default is `false`. Introduced in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890) |
|
||||
| `created_after` | datetime | no | Return merge requests created on or after the given time |
|
||||
| `created_before` | datetime | no | Return merge requests created on or before the given time |
|
||||
| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
|
||||
|
|
|
|||
|
|
@ -96,11 +96,16 @@ with [domain expertise](#domain-experts).
|
|||
1. If your merge request includes documentation changes, it must be **approved
|
||||
by a [Technical writer](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers)**, based on
|
||||
the appropriate [product category](https://about.gitlab.com/handbook/product/categories/).
|
||||
1. If your merge request includes Quality and non-Quality-related changes (*3*), it must be **approved
|
||||
by a [Software Engineer in Test](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors)**.
|
||||
1. If your merge request includes _only_ Quality-related changes (*3*), it must be **approved
|
||||
by a [Quality maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_qa)**.
|
||||
|
||||
- (*1*): Please note that specs other than JavaScript specs are considered backend code.
|
||||
- (*2*): We encourage you to seek guidance from a database maintainer if your merge
|
||||
request is potentially introducing expensive queries. It is most efficient to comment
|
||||
on the line of code in question with the SQL queries so they can give their advice.
|
||||
- (*3*): Quality-related changes include all files within the `qa` directory.
|
||||
|
||||
#### Security requirements
|
||||
|
||||
|
|
@ -320,6 +325,7 @@ Before taking the decision to merge:
|
|||
- Consider warnings and errors from danger bot, code quality, and other reports.
|
||||
Unless a strong case can be made for the violation, these should be resolved
|
||||
before merging. A comment must to be posted if the MR is merged with any failed job.
|
||||
- If the MR contains both Quality and non-Quality-related changes, the MR should be merged by the relevant maintainer for user-facing changes (backend, frontend, or database) after the Quality related changes are approved by a Software Engineer in Test.
|
||||
|
||||
When ready to merge:
|
||||
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ Widgets should now be replicated by Geo!
|
|||
def change
|
||||
add_column :widgets, :verification_retry_at, :datetime_with_timezone
|
||||
add_column :widgets, :verified_at, :datetime_with_timezone
|
||||
add_column :widgets, :verification_checksum, :string
|
||||
add_column :widgets, :verification_checksum, :binary, using: 'verification_checksum::bytea'
|
||||
add_column :widgets, :verification_failure, :string
|
||||
add_column :widgets, :verification_retry_count, :integer
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ and effective _as well as_ fast.
|
|||
|
||||
Here are some things to keep in mind regarding test performance:
|
||||
|
||||
- `double` and `spy` are faster than `FactoryBot.build(...)`
|
||||
- `instance_double` and `spy` are faster than `FactoryBot.build(...)`
|
||||
- `FactoryBot.build(...)` and `.build_stubbed` are faster than `.create`.
|
||||
- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`,
|
||||
`spy`, or `double` will do. Database persistence is slow!
|
||||
`spy`, or `instance_double` will do. Database persistence is slow!
|
||||
- Don't mark a feature as requiring JavaScript (through `:js` in RSpec) unless it's _actually_ required for the test
|
||||
to be valid. Headless browser testing is slow!
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ as the hardware requirements that are needed to install and use GitLab.
|
|||
- Scientific Linux (please use the CentOS packages and instructions)
|
||||
- Oracle Linux (please use the CentOS packages and instructions)
|
||||
|
||||
For the installations options, see [the main installation page](README.md).
|
||||
For the installation options, see [the main installation page](README.md).
|
||||
|
||||
### Unsupported Linux distributions and Unix-like operating systems
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ GitLab uses [webpack](https://webpack.js.org/) to compile frontend assets, which
|
|||
version of Node.js 10.13.0.
|
||||
|
||||
You can check which version you are running with `node -v`. If you are running
|
||||
a version older than `v10.13.0`, you need to update to a newer version. You
|
||||
a version older than `v10.13.0`, you need to update it to a newer version. You
|
||||
can find instructions to install from community maintained packages or compile
|
||||
from source at the [Node.js website](https://nodejs.org/en/download/).
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ available when needed.
|
|||
|
||||
Our [Memory Team](https://about.gitlab.com/handbook/engineering/development/enablement/memory/) is actively working to reduce the memory requirement.
|
||||
|
||||
NOTE: **Note:** The 25 workers of Sidekiq will show up as separate processes in your process overview (such as `top` or `htop`) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about how many you need of those.
|
||||
NOTE: **Note:** The 25 workers of Sidekiq will show up as separate processes in your process overview (such as `top` or `htop`) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about how many you need for those.
|
||||
|
||||
## Database
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 54 KiB |
|
|
@ -8,6 +8,12 @@ description: "The static site editor enables users to edit content on static web
|
|||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28758) in GitLab 12.10.
|
||||
> - WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214559) in GitLab 13.0.
|
||||
|
||||
DANGER: **Danger:**
|
||||
In GitLab 13.0, we [introduced breaking changes](https://gitlab.com/gitlab-org/gitlab/-/issues/213282)
|
||||
to the URL structure of the Static Site Editor. Follow the instructions in this
|
||||
[snippet](https://gitlab.com/gitlab-org/project-templates/static-site-editor-middleman/snippets/1976539)
|
||||
to update your project with the latest changes.
|
||||
|
||||
Static Site Editor enables users to edit content on static websites without
|
||||
prior knowledge of the underlying templating language, site architecture, or
|
||||
Git commands. A contributor to your project can quickly edit a Markdown page
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ snippet was created using the GitLab web interface the original line ending is W
|
|||
> Introduced in GitLab 10.8.
|
||||
|
||||
Public snippets can not only be shared, but also embedded on any website. This
|
||||
allows to reuse a GitLab snippet in multiple places and any change to the source
|
||||
allows us to reuse a GitLab snippet in multiple places and any change to the source
|
||||
is automatically reflected in the embedded snippet.
|
||||
|
||||
To embed a snippet, first make sure that:
|
||||
|
|
@ -172,6 +172,6 @@ Here's how an embedded snippet looks like:
|
|||
|
||||
<script src="https://gitlab.com/gitlab-org/gitlab-foss/snippets/1717978.js"></script>
|
||||
|
||||
Embedded snippets are displayed with a header that shows the file name if defined,
|
||||
Embedded snippets are displayed with a header that shows the file name is defined,
|
||||
the snippet size, a link to GitLab, and the actual snippet content. Actions in
|
||||
the header allow users to see the snippet in raw format and download it.
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ module API
|
|||
mount ::API::Events
|
||||
mount ::API::Features
|
||||
mount ::API::Files
|
||||
mount ::API::FreezePeriods
|
||||
mount ::API::GroupBoards
|
||||
mount ::API::GroupClusters
|
||||
mount ::API::GroupExport
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Entities
|
||||
class FreezePeriod < Grape::Entity
|
||||
expose :id
|
||||
expose :freeze_start, :freeze_end, :cron_timezone
|
||||
expose :created_at, :updated_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -50,8 +50,10 @@ module API
|
|||
# use `MergeRequest#mergeable?` instead (boolean).
|
||||
# See https://gitlab.com/gitlab-org/gitlab-foss/issues/42344 for more
|
||||
# information.
|
||||
expose :merge_status do |merge_request|
|
||||
merge_request.check_mergeability(async: true)
|
||||
#
|
||||
# For list endpoints, we skip the recheck by default, since it's expensive
|
||||
expose :merge_status do |merge_request, options|
|
||||
merge_request.check_mergeability(async: true) unless options[:skip_merge_status_recheck]
|
||||
merge_request.public_merge_status
|
||||
end
|
||||
expose :diff_head_sha, as: :sha
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
class FreezePeriods < Grape::API
|
||||
include PaginationParams
|
||||
|
||||
before { authenticate! }
|
||||
|
||||
params do
|
||||
requires :id, type: String, desc: 'The ID of a project'
|
||||
end
|
||||
|
||||
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
|
||||
desc 'Get project freeze periods' do
|
||||
detail 'This feature was introduced in GitLab 13.0.'
|
||||
success Entities::FreezePeriod
|
||||
end
|
||||
params do
|
||||
use :pagination
|
||||
end
|
||||
|
||||
get ":id/freeze_periods" do
|
||||
authorize! :read_freeze_period, user_project
|
||||
|
||||
freeze_periods = ::FreezePeriodsFinder.new(user_project, current_user).execute
|
||||
|
||||
present paginate(freeze_periods), with: Entities::FreezePeriod, current_user: current_user
|
||||
end
|
||||
|
||||
desc 'Get a single freeze period' do
|
||||
detail 'This feature was introduced in GitLab 13.0.'
|
||||
success Entities::FreezePeriod
|
||||
end
|
||||
params do
|
||||
requires :freeze_period_id, type: Integer, desc: 'The ID of a project freeze period'
|
||||
end
|
||||
get ":id/freeze_periods/:freeze_period_id" do
|
||||
authorize! :read_freeze_period, user_project
|
||||
|
||||
present freeze_period, with: Entities::FreezePeriod, current_user: current_user
|
||||
end
|
||||
|
||||
desc 'Create a new freeze period' do
|
||||
detail 'This feature was introduced in GitLab 13.0.'
|
||||
success Entities::FreezePeriod
|
||||
end
|
||||
params do
|
||||
requires :freeze_start, type: String, desc: 'Freeze Period start'
|
||||
requires :freeze_end, type: String, desc: 'Freeze Period end'
|
||||
optional :cron_timezone, type: String, desc: 'Timezone'
|
||||
end
|
||||
post ':id/freeze_periods' do
|
||||
authorize! :create_freeze_period, user_project
|
||||
|
||||
freeze_period_params = declared(params, include_parent_namespaces: false)
|
||||
|
||||
freeze_period = user_project.freeze_periods.create(freeze_period_params)
|
||||
|
||||
if freeze_period.persisted?
|
||||
present freeze_period, with: Entities::FreezePeriod
|
||||
else
|
||||
render_validation_error!(freeze_period)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update a freeze period' do
|
||||
detail 'This feature was introduced in GitLab 13.0.'
|
||||
success Entities::FreezePeriod
|
||||
end
|
||||
params do
|
||||
optional :freeze_start, type: String, desc: 'Freeze Period start'
|
||||
optional :freeze_end, type: String, desc: 'Freeze Period end'
|
||||
optional :cron_timezone, type: String, desc: 'Freeze Period Timezone'
|
||||
end
|
||||
put ':id/freeze_periods/:freeze_period_id' do
|
||||
authorize! :update_freeze_period, user_project
|
||||
|
||||
freeze_period_params = declared(params, include_parent_namespaces: false, include_missing: false)
|
||||
|
||||
if freeze_period.update(freeze_period_params)
|
||||
present freeze_period, with: Entities::FreezePeriod
|
||||
else
|
||||
render_validation_error!(freeze_period)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Delete a freeze period' do
|
||||
detail 'This feature was introduced in GitLab 13.0.'
|
||||
success Entities::FreezePeriod
|
||||
end
|
||||
params do
|
||||
requires :freeze_period_id, type: Integer, desc: 'Freeze Period ID'
|
||||
end
|
||||
delete ':id/freeze_periods/:freeze_period_id' do
|
||||
authorize! :destroy_freeze_period, user_project
|
||||
|
||||
destroy_conditionally!(freeze_period)
|
||||
end
|
||||
end
|
||||
|
||||
helpers do
|
||||
def freeze_period
|
||||
@freeze_period ||= user_project.freeze_periods.find(params[:freeze_period_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -91,7 +91,7 @@ module API
|
|||
options = {
|
||||
with: Entities::Group,
|
||||
current_user: current_user,
|
||||
statistics: params[:statistics] && current_user.admin?
|
||||
statistics: params[:statistics] && current_user&.admin?
|
||||
}
|
||||
|
||||
groups = groups.with_statistics if options[:statistics]
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ module API
|
|||
coerce_with: Validations::Types::LabelsList.coerce,
|
||||
desc: 'Comma-separated list of label names'
|
||||
optional :with_labels_details, type: Boolean, desc: 'Return titles of labels and other details', default: false
|
||||
optional :with_merge_status_recheck, type: Boolean, desc: 'Request that stale merge statuses be rechecked asynchronously', default: false
|
||||
optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
|
||||
optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
|
||||
optional :updated_after, type: DateTime, desc: 'Return merge requests updated after the specified time'
|
||||
|
|
|
|||
|
|
@ -93,6 +93,9 @@ module API
|
|||
options[:with] = Entities::MergeRequestSimple
|
||||
else
|
||||
options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest', current_user)
|
||||
if Feature.enabled?(:mr_list_api_skip_merge_status_recheck, default_enabled: true)
|
||||
options[:skip_merge_status_recheck] = !declared_params[:with_merge_status_recheck]
|
||||
end
|
||||
end
|
||||
|
||||
options
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ module ContainerRegistry
|
|||
DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE = 'application/vnd.docker.distribution.manifest.v2+json'
|
||||
OCI_MANIFEST_V1_TYPE = 'application/vnd.oci.image.manifest.v1+json'
|
||||
CONTAINER_IMAGE_V1_TYPE = 'application/vnd.docker.container.image.v1+json'
|
||||
REGISTRY_VERSION_HEADER = 'gitlab-container-registry-version'
|
||||
REGISTRY_FEATURES_HEADER = 'gitlab-container-registry-features'
|
||||
|
||||
ACCEPTED_TYPES = [DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE].freeze
|
||||
|
||||
|
|
@ -24,6 +26,21 @@ module ContainerRegistry
|
|||
@options = options
|
||||
end
|
||||
|
||||
def registry_info
|
||||
response = faraday.get("/v2/")
|
||||
|
||||
return {} unless response&.success?
|
||||
|
||||
version = response.headers[REGISTRY_VERSION_HEADER]
|
||||
features = response.headers.fetch(REGISTRY_FEATURES_HEADER, '')
|
||||
|
||||
{
|
||||
version: version,
|
||||
features: features.split(',').map(&:strip),
|
||||
vendor: version ? 'gitlab' : 'other'
|
||||
}
|
||||
end
|
||||
|
||||
def repository_tags(name)
|
||||
response_body faraday.get("/v2/#{name}/tags/list")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ module Gitlab
|
|||
},
|
||||
buy_ci_minutes_version_a: {
|
||||
tracking_category: 'Growth::Expansion::Experiment::BuyCiMinutesVersionA'
|
||||
},
|
||||
upgrade_link_in_user_menu_a: {
|
||||
tracking_category: 'Growth::Expansion::Experiment::UpgradeLinkInUserMenuA'
|
||||
}
|
||||
}.freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -57,11 +57,8 @@ module Gitlab
|
|||
# Already a commit?
|
||||
return commit_id if commit_id.is_a?(Gitlab::Git::Commit)
|
||||
|
||||
# Some weird thing?
|
||||
return unless commit_id.is_a?(String)
|
||||
|
||||
# This saves us an RPC round trip.
|
||||
return if commit_id.include?(':')
|
||||
return unless valid?(commit_id)
|
||||
|
||||
commit = find_commit(repo, commit_id)
|
||||
|
||||
|
|
@ -431,6 +428,15 @@ module Gitlab
|
|||
def fetch_body_from_gitaly
|
||||
self.class.get_message(@repository, id)
|
||||
end
|
||||
|
||||
def self.valid?(commit_id)
|
||||
commit_id.is_a?(String) && !(
|
||||
commit_id.start_with?('-') ||
|
||||
commit_id.include?(':') ||
|
||||
commit_id.include?("\x00") ||
|
||||
commit_id.match?(/\s/)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -145,7 +145,8 @@ module Gitlab
|
|||
services_usage,
|
||||
usage_counters,
|
||||
user_preferences_usage,
|
||||
ingress_modsecurity_usage
|
||||
ingress_modsecurity_usage,
|
||||
container_expiration_policies_usage
|
||||
)
|
||||
}
|
||||
end
|
||||
|
|
@ -185,32 +186,9 @@ module Gitlab
|
|||
web_ide_clientside_preview_enabled: alt_usage_data { Gitlab::CurrentSettings.web_ide_clientside_preview_enabled? },
|
||||
ingress_modsecurity_enabled: Feature.enabled?(:ingress_modsecurity),
|
||||
grafana_link_enabled: alt_usage_data { Gitlab::CurrentSettings.grafana_enabled? }
|
||||
}.merge(features_usage_data_container_expiration_policies)
|
||||
}
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def features_usage_data_container_expiration_policies
|
||||
results = {}
|
||||
start = ::Project.minimum(:id)
|
||||
finish = ::Project.maximum(:id)
|
||||
|
||||
results[:projects_with_expiration_policy_disabled] = distinct_count(::ContainerExpirationPolicy.where(enabled: false), :project_id, start: start, finish: finish)
|
||||
base = ::ContainerExpirationPolicy.active
|
||||
results[:projects_with_expiration_policy_enabled] = distinct_count(base, :project_id, start: start, finish: finish)
|
||||
|
||||
%i[keep_n cadence older_than].each do |option|
|
||||
::ContainerExpirationPolicy.public_send("#{option}_options").keys.each do |value| # rubocop: disable GitlabSecurity/PublicSend
|
||||
results["projects_with_expiration_policy_enabled_with_#{option}_set_to_#{value}".to_sym] = distinct_count(base.where(option => value), :project_id, start: start, finish: finish)
|
||||
end
|
||||
end
|
||||
|
||||
results[:projects_with_expiration_policy_enabled_with_keep_n_unset] = distinct_count(base.where(keep_n: nil), :project_id, start: start, finish: finish)
|
||||
results[:projects_with_expiration_policy_enabled_with_older_than_unset] = distinct_count(base.where(older_than: nil), :project_id, start: start, finish: finish)
|
||||
|
||||
results
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
# @return [Hash<Symbol, Integer>]
|
||||
def usage_counters
|
||||
usage_data_counters.map { |counter| redis_usage_data(counter) }.reduce({}, :merge)
|
||||
|
|
@ -315,6 +293,29 @@ module Gitlab
|
|||
}
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def container_expiration_policies_usage
|
||||
results = {}
|
||||
start = ::Project.minimum(:id)
|
||||
finish = ::Project.maximum(:id)
|
||||
|
||||
results[:projects_with_expiration_policy_disabled] = distinct_count(::ContainerExpirationPolicy.where(enabled: false), :project_id, start: start, finish: finish)
|
||||
base = ::ContainerExpirationPolicy.active
|
||||
results[:projects_with_expiration_policy_enabled] = distinct_count(base, :project_id, start: start, finish: finish)
|
||||
|
||||
%i[keep_n cadence older_than].each do |option|
|
||||
::ContainerExpirationPolicy.public_send("#{option}_options").keys.each do |value| # rubocop: disable GitlabSecurity/PublicSend
|
||||
results["projects_with_expiration_policy_enabled_with_#{option}_set_to_#{value}".to_sym] = distinct_count(base.where(option => value), :project_id, start: start, finish: finish)
|
||||
end
|
||||
end
|
||||
|
||||
results[:projects_with_expiration_policy_enabled_with_keep_n_unset] = distinct_count(base.where(keep_n: nil), :project_id, start: start, finish: finish)
|
||||
results[:projects_with_expiration_policy_enabled_with_older_than_unset] = distinct_count(base.where(older_than: nil), :project_id, start: start, finish: finish)
|
||||
|
||||
results
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def services_usage
|
||||
results = Service.available_services_names.without('jira').each_with_object({}) do |service_name, response|
|
||||
|
|
|
|||
|
|
@ -2978,6 +2978,9 @@ msgstr ""
|
|||
msgid "AutoDevOps|Auto DevOps documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "AutoDevOps|Dismiss Auto DevOps box"
|
||||
msgstr ""
|
||||
|
||||
msgid "AutoDevOps|Enable in settings"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -3506,6 +3509,9 @@ msgstr ""
|
|||
msgid "BurndownChartLabel|Open issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "Burnup chart"
|
||||
msgstr ""
|
||||
|
||||
msgid "Business"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -6471,6 +6477,9 @@ msgstr ""
|
|||
msgid "CurrentUser|Start a Gold trial"
|
||||
msgstr ""
|
||||
|
||||
msgid "CurrentUser|Upgrade"
|
||||
msgstr ""
|
||||
|
||||
msgid "Custom CI configuration path"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -9443,9 +9452,21 @@ msgstr ""
|
|||
msgid "Filter by %{issuable_type} that are currently opened."
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter by %{page_context_word} that are currently opened."
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter by commit message"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter by issues that are currently closed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter by merge requests that are currently closed and unmerged."
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter by merge requests that are currently merged."
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter by milestone name"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -16276,6 +16297,9 @@ msgstr ""
|
|||
msgid "Project cannot be shared with the group it is in or one of its ancestors."
|
||||
msgstr ""
|
||||
|
||||
msgid "Project clone URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Project configuration, including services"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -24603,6 +24627,9 @@ msgstr ""
|
|||
msgid "You didn't renew your %{strong}%{plan_name}%{strong_close} subscription so it was downgraded to the GitLab Core Plan."
|
||||
msgstr ""
|
||||
|
||||
msgid "You do not have an active license"
|
||||
msgstr ""
|
||||
|
||||
msgid "You do not have any subscriptions yet"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -24651,6 +24678,9 @@ msgstr ""
|
|||
msgid "You don’t have access to Value Stream Analytics for this group"
|
||||
msgstr ""
|
||||
|
||||
msgid "You have a license(s) that activates at a future date. Please see the License History table below."
|
||||
msgstr ""
|
||||
|
||||
msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
"@babel/preset-env": "^7.8.4",
|
||||
"@gitlab/at.js": "1.5.5",
|
||||
"@gitlab/svgs": "1.127.0",
|
||||
"@gitlab/ui": "14.5.0",
|
||||
"@gitlab/ui": "14.10.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.2-2",
|
||||
"@sentry/browser": "^5.10.2",
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ describe Repositories::GitHttpController do
|
|||
it_behaves_like 'info_refs behavior' do
|
||||
let(:user) { project.owner }
|
||||
end
|
||||
|
||||
it_behaves_like 'git_upload_pack behavior', true
|
||||
it_behaves_like 'access checker class' do
|
||||
let(:expected_class) { Gitlab::GitAccess }
|
||||
|
|
@ -183,6 +184,7 @@ describe Repositories::GitHttpController do
|
|||
it_behaves_like 'info_refs behavior' do
|
||||
let(:user) { personal_snippet.author }
|
||||
end
|
||||
|
||||
it_behaves_like 'git_upload_pack behavior', false
|
||||
it_behaves_like 'access checker class' do
|
||||
let(:expected_class) { Gitlab::GitAccessSnippet }
|
||||
|
|
@ -197,6 +199,7 @@ describe Repositories::GitHttpController do
|
|||
it_behaves_like 'info_refs behavior' do
|
||||
let(:user) { project_snippet.author }
|
||||
end
|
||||
|
||||
it_behaves_like 'git_upload_pack behavior', false
|
||||
it_behaves_like 'access checker class' do
|
||||
let(:expected_class) { Gitlab::GitAccessSnippet }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe FreezePeriodsFinder do
|
||||
subject(:finder) { described_class.new(project, user).execute }
|
||||
|
||||
let(:project) { create(:project, :private) }
|
||||
let(:user) { create(:user) }
|
||||
let!(:freeze_period_1) { create(:ci_freeze_period, project: project, created_at: 2.days.ago) }
|
||||
let!(:freeze_period_2) { create(:ci_freeze_period, project: project, created_at: 1.day.ago) }
|
||||
|
||||
shared_examples_for 'returns nothing' do
|
||||
specify do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'returns freeze_periods ordered by created_at asc' do
|
||||
it 'returns freeze_periods ordered by created_at' do
|
||||
expect(subject.count).to eq(2)
|
||||
expect(subject.pluck('id')).to eq([freeze_period_1.id, freeze_period_2.id])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns freeze_periods ordered by created_at asc'
|
||||
end
|
||||
|
||||
context 'when user is a guest' do
|
||||
before do
|
||||
project.add_guest(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns nothing'
|
||||
end
|
||||
|
||||
context 'when user is a developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns nothing'
|
||||
end
|
||||
|
||||
context 'when user is not a project member' do
|
||||
it_behaves_like 'returns nothing'
|
||||
|
||||
context 'when project is public' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
it_behaves_like 'returns nothing'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -48,6 +48,7 @@ describe Projects::Serverless::FunctionsFinder do
|
|||
expect(function_finder.knative_installed).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project level cluster is present and enabled' do
|
||||
it_behaves_like 'before first deployment' do
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp, enabled: true) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"freeze_start",
|
||||
"freeze_end",
|
||||
"cron_timezone",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"freeze_start": { "type": "string" },
|
||||
"freeze_end": { "type": "string"},
|
||||
"cron_timezone": { "type": "string" },
|
||||
"created_at": { "type": "string" },
|
||||
"updated_at": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "array",
|
||||
"items": { "$ref": "freeze_period.json" }
|
||||
}
|
||||
|
||||
|
|
@ -60,8 +60,8 @@ describe('Header', () => {
|
|||
beforeEach(() => {
|
||||
setFixtures(`
|
||||
<li class="js-nav-user-dropdown">
|
||||
<a class="js-buy-ci-minutes-link" data-track-event="click_buy_ci_minutes" data-track-label="free" data-track-property="user_dropdown">Buy CI minutes
|
||||
</a>
|
||||
<a class="js-buy-ci-minutes-link" data-track-event="click_buy_ci_minutes" data-track-label="free" data-track-property="user_dropdown">Buy CI minutes</a>
|
||||
<a class="js-upgrade-plan-link" data-track-event="click_upgrade_link" data-track-label="free" data-track-property="user_dropdown">Upgrade</a>
|
||||
</li>`);
|
||||
|
||||
trackingSpy = mockTracking('_category_', $('.js-nav-user-dropdown').element, jest.spyOn);
|
||||
|
|
@ -77,8 +77,16 @@ describe('Header', () => {
|
|||
it('sends a tracking event when the dropdown is opened and contains Buy CI minutes link', () => {
|
||||
$('.js-nav-user-dropdown').trigger('shown.bs.dropdown');
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledTimes(1);
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'show_buy_ci_minutes', {
|
||||
expect(trackingSpy).toHaveBeenCalledWith('some:page', 'show_buy_ci_minutes', {
|
||||
label: 'free',
|
||||
property: 'user_dropdown',
|
||||
});
|
||||
});
|
||||
|
||||
it('sends a tracking event when the dropdown is opened and contains Upgrade link', () => {
|
||||
$('.js-nav-user-dropdown').trigger('shown.bs.dropdown');
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith('some:page', 'show_upgrade_link', {
|
||||
label: 'free',
|
||||
property: 'user_dropdown',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
|
|||
import { createStore } from '~/monitoring/stores';
|
||||
import * as types from '~/monitoring/stores/mutation_types';
|
||||
import {
|
||||
setupAllDashboards,
|
||||
setupStoreWithDashboard,
|
||||
setMetricResult,
|
||||
setupStoreWithData,
|
||||
|
|
@ -279,7 +280,7 @@ describe('Dashboard', () => {
|
|||
expect(window.history.pushState).toHaveBeenCalledWith(
|
||||
expect.anything(), // state
|
||||
expect.any(String), // document title
|
||||
expect.stringContaining(`?${expectedSearch}`),
|
||||
expect.stringContaining(`${expectedSearch}`),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -302,7 +303,7 @@ describe('Dashboard', () => {
|
|||
expect(window.history.pushState).toHaveBeenCalledWith(
|
||||
expect.anything(), // state
|
||||
expect.any(String), // document title
|
||||
expect.stringContaining(`?${expectedSearch}`),
|
||||
expect.stringContaining(`${expectedSearch}`),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -317,7 +318,7 @@ describe('Dashboard', () => {
|
|||
expect(window.history.pushState).toHaveBeenCalledWith(
|
||||
expect.anything(), // state
|
||||
expect.any(String), // document title
|
||||
expect.not.stringContaining('?'), // no params
|
||||
expect.not.stringMatching(/group|title|y_label/), // no panel params
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -359,6 +360,7 @@ describe('Dashboard', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
createShallowWrapper();
|
||||
setupAllDashboards(store);
|
||||
});
|
||||
|
||||
it('toggle star button is shown', () => {
|
||||
|
|
@ -380,10 +382,7 @@ describe('Dashboard', () => {
|
|||
const getToggleTooltip = () => findToggleStar().element.parentElement.getAttribute('title');
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper.vm.$store.commit(
|
||||
`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
|
||||
dashboardGitResponse,
|
||||
);
|
||||
setupAllDashboards(store);
|
||||
jest.spyOn(store, 'dispatch');
|
||||
});
|
||||
|
||||
|
|
@ -400,7 +399,9 @@ describe('Dashboard', () => {
|
|||
|
||||
describe('when dashboard is not starred', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.setProps({ currentDashboard: dashboardGitResponse[0].path });
|
||||
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
|
||||
currentDashboard: dashboardGitResponse[0].path,
|
||||
});
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
|
|
@ -415,7 +416,9 @@ describe('Dashboard', () => {
|
|||
|
||||
describe('when dashboard is starred', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.setProps({ currentDashboard: dashboardGitResponse[1].path });
|
||||
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
|
||||
currentDashboard: dashboardGitResponse[1].path,
|
||||
});
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
|
|
@ -551,7 +554,7 @@ describe('Dashboard', () => {
|
|||
|
||||
it('sets a link to the expanded panel', () => {
|
||||
const searchQuery =
|
||||
'?group=System%20metrics%20(Kubernetes)&title=Memory%20Usage%20(Total)&y_label=Total%20Memory%20Used%20(GB)';
|
||||
'?dashboard=config%2Fprometheus%2Fcommon_metrics.yml&group=System%20metrics%20(Kubernetes)&title=Memory%20Usage%20(Total)&y_label=Total%20Memory%20Used%20(GB)';
|
||||
|
||||
expect(findExpandedPanel().attributes('clipboard-text')).toEqual(
|
||||
expect.stringContaining(searchQuery),
|
||||
|
|
@ -808,10 +811,7 @@ describe('Dashboard', () => {
|
|||
beforeEach(() => {
|
||||
createShallowWrapper({ hasMetrics: true });
|
||||
|
||||
wrapper.vm.$store.commit(
|
||||
`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
|
||||
dashboardGitResponse,
|
||||
);
|
||||
setupAllDashboards(store);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
|
|
@ -820,10 +820,11 @@ describe('Dashboard', () => {
|
|||
});
|
||||
|
||||
it('is present for a custom dashboard, and links to its edit_path', () => {
|
||||
const dashboard = dashboardGitResponse[1]; // non-default dashboard
|
||||
const currentDashboard = dashboard.path;
|
||||
const dashboard = dashboardGitResponse[1];
|
||||
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
|
||||
currentDashboard: dashboard.path,
|
||||
});
|
||||
|
||||
wrapper.setProps({ currentDashboard });
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findEditLink().exists()).toBe(true);
|
||||
expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path);
|
||||
|
|
@ -834,12 +835,7 @@ describe('Dashboard', () => {
|
|||
describe('Dashboard dropdown', () => {
|
||||
beforeEach(() => {
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
|
||||
wrapper.vm.$store.commit(
|
||||
`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
|
||||
dashboardGitResponse,
|
||||
);
|
||||
|
||||
setupAllDashboards(store);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
|
|
@ -872,7 +868,7 @@ describe('Dashboard', () => {
|
|||
});
|
||||
|
||||
describe('Clipboard text in panels', () => {
|
||||
const currentDashboard = 'TEST_DASHBOARD';
|
||||
const currentDashboard = dashboardGitResponse[1].path;
|
||||
const panelIndex = 1; // skip expanded panel
|
||||
|
||||
const getClipboardTextFirstPanel = () =>
|
||||
|
|
@ -882,37 +878,20 @@ describe('Dashboard', () => {
|
|||
.props('clipboardText');
|
||||
|
||||
beforeEach(() => {
|
||||
setupStoreWithData(store);
|
||||
createShallowWrapper({ hasMetrics: true, currentDashboard });
|
||||
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('contains a link to the dashboard', () => {
|
||||
expect(getClipboardTextFirstPanel()).toContain(`dashboard=${currentDashboard}`);
|
||||
const dashboardParam = `dashboard=${encodeURIComponent(currentDashboard)}`;
|
||||
|
||||
expect(getClipboardTextFirstPanel()).toContain(dashboardParam);
|
||||
expect(getClipboardTextFirstPanel()).toContain(`group=`);
|
||||
expect(getClipboardTextFirstPanel()).toContain(`title=`);
|
||||
expect(getClipboardTextFirstPanel()).toContain(`y_label=`);
|
||||
});
|
||||
|
||||
it('strips the undefined parameter', () => {
|
||||
wrapper.setProps({ currentDashboard: undefined });
|
||||
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(getClipboardTextFirstPanel()).not.toContain(`dashboard=`);
|
||||
expect(getClipboardTextFirstPanel()).toContain(`y_label=`);
|
||||
});
|
||||
});
|
||||
|
||||
it('null parameter is stripped', () => {
|
||||
wrapper.setProps({ currentDashboard: null });
|
||||
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(getClipboardTextFirstPanel()).not.toContain(`dashboard=`);
|
||||
expect(getClipboardTextFirstPanel()).toContain(`y_label=`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('add custom metrics', () => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import Dashboard from '~/monitoring/components/dashboard.vue';
|
||||
import { createStore } from '~/monitoring/stores';
|
||||
import { setupAllDashboards } from '../store_utils';
|
||||
import { propsData } from '../mock_data';
|
||||
|
||||
jest.mock('~/lib/utils/url_utility');
|
||||
|
|
@ -15,6 +16,8 @@ describe('Dashboard template', () => {
|
|||
beforeEach(() => {
|
||||
store = createStore();
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
setupAllDashboards(store);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as types from '~/monitoring/stores/mutation_types';
|
||||
import { metricsResult, environmentData } from './mock_data';
|
||||
import { metricsResult, environmentData, dashboardGitResponse } from './mock_data';
|
||||
import { metricsDashboardPayload } from './fixture_data';
|
||||
|
||||
export const setMetricResult = ({ $store, result, group = 0, panel = 0, metric = 0 }) => {
|
||||
|
|
@ -16,11 +16,19 @@ const setEnvironmentData = $store => {
|
|||
$store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, environmentData);
|
||||
};
|
||||
|
||||
export const setupAllDashboards = $store => {
|
||||
$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, dashboardGitResponse);
|
||||
};
|
||||
|
||||
export const setupStoreWithDashboard = $store => {
|
||||
$store.commit(
|
||||
`monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
|
||||
metricsDashboardPayload,
|
||||
);
|
||||
$store.commit(
|
||||
`monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
|
||||
metricsDashboardPayload,
|
||||
);
|
||||
};
|
||||
|
||||
export const setupStoreWithVariable = $store => {
|
||||
|
|
@ -30,6 +38,7 @@ export const setupStoreWithVariable = $store => {
|
|||
};
|
||||
|
||||
export const setupStoreWithData = $store => {
|
||||
setupAllDashboards($store);
|
||||
setupStoreWithDashboard($store);
|
||||
|
||||
setMetricResult({ $store, result: [], panel: 0 });
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
|
|||
import { nextTick } from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import NoteHeader from '~/notes/components/note_header.vue';
|
||||
import GitlabTeamMemberBadge from '~/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
|
@ -141,20 +140,6 @@ describe('NoteHeader component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test.each`
|
||||
props | expected | message1 | message2
|
||||
${{ author: { ...author, is_gitlab_employee: true } }} | ${true} | ${'renders'} | ${'true'}
|
||||
${{ author: { ...author, is_gitlab_employee: false } }} | ${false} | ${"doesn't render"} | ${'false'}
|
||||
${{ author }} | ${false} | ${"doesn't render"} | ${'undefined'}
|
||||
`(
|
||||
'$message1 GitLab team member badge when `is_gitlab_employee` is $message2',
|
||||
({ props, expected }) => {
|
||||
createComponent(props);
|
||||
|
||||
expect(wrapper.find(GitlabTeamMemberBadge).exists()).toBe(expected);
|
||||
},
|
||||
);
|
||||
|
||||
describe('loading spinner', () => {
|
||||
it('shows spinner when showSpinner is true', () => {
|
||||
createComponent();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ describe('Release detail mutations', () => {
|
|||
release = convertObjectPropsToCamelCase(originalRelease);
|
||||
});
|
||||
|
||||
describe(types.REQUEST_RELEASE, () => {
|
||||
describe(`${types.REQUEST_RELEASE}`, () => {
|
||||
it('set state.isFetchingRelease to true', () => {
|
||||
mutations[types.REQUEST_RELEASE](state);
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.RECEIVE_RELEASE_SUCCESS, () => {
|
||||
describe(`${types.RECEIVE_RELEASE_SUCCESS}`, () => {
|
||||
it('handles a successful response from the server', () => {
|
||||
mutations[types.RECEIVE_RELEASE_SUCCESS](state, release);
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.RECEIVE_RELEASE_ERROR, () => {
|
||||
describe(`${types.RECEIVE_RELEASE_ERROR}`, () => {
|
||||
it('handles an unsuccessful response from the server', () => {
|
||||
const error = { message: 'An error occurred!' };
|
||||
mutations[types.RECEIVE_RELEASE_ERROR](state, error);
|
||||
|
|
@ -55,7 +55,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.UPDATE_RELEASE_TITLE, () => {
|
||||
describe(`${types.UPDATE_RELEASE_TITLE}`, () => {
|
||||
it("updates the release's title", () => {
|
||||
state.release = release;
|
||||
const newTitle = 'The new release title';
|
||||
|
|
@ -65,7 +65,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.UPDATE_RELEASE_NOTES, () => {
|
||||
describe(`${types.UPDATE_RELEASE_NOTES}`, () => {
|
||||
it("updates the release's notes", () => {
|
||||
state.release = release;
|
||||
const newNotes = 'The new release notes';
|
||||
|
|
@ -75,7 +75,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.REQUEST_UPDATE_RELEASE, () => {
|
||||
describe(`${types.REQUEST_UPDATE_RELEASE}`, () => {
|
||||
it('set state.isUpdatingRelease to true', () => {
|
||||
mutations[types.REQUEST_UPDATE_RELEASE](state);
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.RECEIVE_UPDATE_RELEASE_SUCCESS, () => {
|
||||
describe(`${types.RECEIVE_UPDATE_RELEASE_SUCCESS}`, () => {
|
||||
it('handles a successful response from the server', () => {
|
||||
mutations[types.RECEIVE_UPDATE_RELEASE_SUCCESS](state, release);
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.RECEIVE_UPDATE_RELEASE_ERROR, () => {
|
||||
describe(`${types.RECEIVE_UPDATE_RELEASE_ERROR}`, () => {
|
||||
it('handles an unsuccessful response from the server', () => {
|
||||
const error = { message: 'An error occurred!' };
|
||||
mutations[types.RECEIVE_UPDATE_RELEASE_ERROR](state, error);
|
||||
|
|
@ -104,7 +104,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.ADD_EMPTY_ASSET_LINK, () => {
|
||||
describe(`${types.ADD_EMPTY_ASSET_LINK}`, () => {
|
||||
it('adds a new, empty link object to the release', () => {
|
||||
state.release = release;
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.UPDATE_ASSET_LINK_URL, () => {
|
||||
describe(`${types.UPDATE_ASSET_LINK_URL}`, () => {
|
||||
it('updates an asset link with a new URL', () => {
|
||||
state.release = release;
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.UPDATE_ASSET_LINK_NAME, () => {
|
||||
describe(`${types.UPDATE_ASSET_LINK_NAME}`, () => {
|
||||
it('updates an asset link with a new name', () => {
|
||||
state.release = release;
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(types.REMOVE_ASSET_LINK, () => {
|
||||
describe(`${types.REMOVE_ASSET_LINK}`, () => {
|
||||
it('removes an asset link from the release', () => {
|
||||
state.release = release;
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ describe ContainerRegistry::Client do
|
|||
it 'follows 307 redirect for GET /v2/:name/blobs/:digest' do
|
||||
stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345")
|
||||
.with(headers: blob_headers)
|
||||
.to_return(status: 307, body: "", headers: { Location: 'http://redirected' })
|
||||
.to_return(status: 307, body: '', headers: { Location: 'http://redirected' })
|
||||
# We should probably use hash_excluding here, but that requires an update to WebMock:
|
||||
# https://github.com/bblimke/webmock/blob/master/lib/webmock/matchers/hash_excluding_matcher.rb
|
||||
stub_request(:get, "http://redirected/")
|
||||
|
|
@ -238,4 +238,54 @@ describe ContainerRegistry::Client do
|
|||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
def stub_registry_info(headers: {}, status: 200)
|
||||
stub_request(:get, 'http://container-registry/v2/')
|
||||
.to_return(status: status, body: "", headers: headers)
|
||||
end
|
||||
|
||||
describe '#registry_info' do
|
||||
subject { client.registry_info }
|
||||
|
||||
context 'when the check is successful' do
|
||||
context 'when using the GitLab container registry' do
|
||||
before do
|
||||
stub_registry_info(headers: {
|
||||
'GitLab-Container-Registry-Version' => '2.9.1-gitlab',
|
||||
'GitLab-Container-Registry-Features' => 'a,b,c'
|
||||
})
|
||||
end
|
||||
|
||||
it 'identifies the vendor as "gitlab"' do
|
||||
expect(subject).to include(vendor: 'gitlab')
|
||||
end
|
||||
|
||||
it 'identifies version and features' do
|
||||
expect(subject).to include(version: '2.9.1-gitlab', features: %w[a b c])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using a third-party container registry' do
|
||||
before do
|
||||
stub_registry_info
|
||||
end
|
||||
|
||||
it 'identifies the vendor as "other"' do
|
||||
expect(subject).to include(vendor: 'other')
|
||||
end
|
||||
|
||||
it 'does not identify version or features' do
|
||||
expect(subject).to include(version: nil, features: [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the check is not successful' do
|
||||
it 'does not identify vendor, version or features' do
|
||||
stub_registry_info(status: 500)
|
||||
|
||||
expect(subject).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -161,6 +161,26 @@ describe Gitlab::Git::Commit, :seed_helper do
|
|||
expect(described_class.find(repository, "+123_4532530XYZ")).to be_nil
|
||||
end
|
||||
|
||||
it "returns nil for id started with dash" do
|
||||
expect(described_class.find(repository, "-HEAD")).to be_nil
|
||||
end
|
||||
|
||||
it "returns nil for id containing colon" do
|
||||
expect(described_class.find(repository, "HEAD:")).to be_nil
|
||||
end
|
||||
|
||||
it "returns nil for id containing space" do
|
||||
expect(described_class.find(repository, "HE AD")).to be_nil
|
||||
end
|
||||
|
||||
it "returns nil for id containing tab" do
|
||||
expect(described_class.find(repository, "HE\tAD")).to be_nil
|
||||
end
|
||||
|
||||
it "returns nil for id containing NULL" do
|
||||
expect(described_class.find(repository, "HE\x00AD")).to be_nil
|
||||
end
|
||||
|
||||
context 'with broken repo' do
|
||||
let(:repository) { Gitlab::Git::Repository.new('default', TEST_BROKEN_REPO_PATH, '', 'group/project') }
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ describe Gitlab::HookData::IssuableBuilder do
|
|||
include_examples 'project hook data' do
|
||||
let(:project) { builder.issuable.project }
|
||||
end
|
||||
|
||||
include_examples 'deprecated repository hook data'
|
||||
|
||||
context "with a #{kind}" do
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ describe Gitlab::LegacyGithubImport::Importer do
|
|||
it_behaves_like 'Gitlab::LegacyGithubImport::Importer#execute' do
|
||||
let(:expected_not_called) { [:import_releases, [:import_comments, :pull_requests]] }
|
||||
end
|
||||
|
||||
it_behaves_like 'Gitlab::LegacyGithubImport::Importer#execute an error occurs'
|
||||
it_behaves_like 'Gitlab::LegacyGithubImport unit-testing'
|
||||
|
||||
|
|
|
|||
|
|
@ -99,6 +99,46 @@ describe Gitlab::UsageData, :aggregate_failures do
|
|||
)
|
||||
end
|
||||
|
||||
context 'with existing container expiration policies' do
|
||||
let_it_be(:disabled) { create(:container_expiration_policy, enabled: false) }
|
||||
let_it_be(:enabled) { create(:container_expiration_policy, enabled: true) }
|
||||
|
||||
%i[keep_n cadence older_than].each do |attribute|
|
||||
ContainerExpirationPolicy.send("#{attribute}_options").keys.each do |value|
|
||||
let_it_be("container_expiration_policy_with_#{attribute}_set_to_#{value}") { create(:container_expiration_policy, attribute => value) }
|
||||
end
|
||||
end
|
||||
|
||||
let(:inactive_policies) { ::ContainerExpirationPolicy.where(enabled: false) }
|
||||
let(:active_policies) { ::ContainerExpirationPolicy.active }
|
||||
|
||||
subject { described_class.data[:counts] }
|
||||
|
||||
it 'gathers usage data' do
|
||||
expect(subject[:projects_with_expiration_policy_enabled]).to eq 20
|
||||
expect(subject[:projects_with_expiration_policy_disabled]).to eq 1
|
||||
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_unset]).to eq 14
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_1]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_5]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_10]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_25]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_50]).to eq 1
|
||||
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_unset]).to eq 16
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_7d]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_14d]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_30d]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_90d]).to eq 1
|
||||
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1d]).to eq 12
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_7d]).to eq 5
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_14d]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1month]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_3month]).to eq 1
|
||||
end
|
||||
end
|
||||
|
||||
it 'works when queries time out' do
|
||||
allow_any_instance_of(ActiveRecord::Relation)
|
||||
.to receive(:count).and_raise(ActiveRecord::StatementInvalid.new(''))
|
||||
|
|
@ -192,43 +232,6 @@ describe Gitlab::UsageData, :aggregate_failures do
|
|||
expect(subject[:grafana_link_enabled]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with existing container expiration policies' do
|
||||
let_it_be(:disabled) { create(:container_expiration_policy, enabled: false) }
|
||||
let_it_be(:enabled) { create(:container_expiration_policy, enabled: true) }
|
||||
%i[keep_n cadence older_than].each do |attribute|
|
||||
ContainerExpirationPolicy.send("#{attribute}_options").keys.each do |value|
|
||||
let_it_be("container_expiration_policy_with_#{attribute}_set_to_#{value}") { create(:container_expiration_policy, attribute => value) }
|
||||
end
|
||||
end
|
||||
|
||||
let(:inactive_policies) { ::ContainerExpirationPolicy.where(enabled: false) }
|
||||
let(:active_policies) { ::ContainerExpirationPolicy.active }
|
||||
|
||||
it 'gathers usage data' do
|
||||
expect(subject[:projects_with_expiration_policy_enabled]).to eq 16
|
||||
expect(subject[:projects_with_expiration_policy_disabled]).to eq 1
|
||||
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_unset]).to eq 10
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_1]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_5]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_10]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_25]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_50]).to eq 1
|
||||
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_unset]).to eq 12
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_7d]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_14d]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_30d]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_90d]).to eq 1
|
||||
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1d]).to eq 12
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_7d]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_14d]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1month]).to eq 1
|
||||
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_3month]).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#components_usage_data' do
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ require 'spec_helper'
|
|||
RSpec.describe Ci::FreezePeriod, type: :model do
|
||||
subject { build(:ci_freeze_period) }
|
||||
|
||||
let(:invalid_cron) { '0 0 0 * *' }
|
||||
|
||||
it { is_expected.to belong_to(:project) }
|
||||
|
||||
it { is_expected.to respond_to(:freeze_start) }
|
||||
|
|
@ -13,13 +15,19 @@ RSpec.describe Ci::FreezePeriod, type: :model do
|
|||
|
||||
describe 'cron validations' do
|
||||
it 'allows valid cron patterns' do
|
||||
freeze_period = build(:ci_freeze_period, freeze_start: '0 23 * * 5')
|
||||
freeze_period = build(:ci_freeze_period)
|
||||
|
||||
expect(freeze_period).to be_valid
|
||||
end
|
||||
|
||||
it 'does not allow invalid cron patterns' do
|
||||
freeze_period = build(:ci_freeze_period, freeze_start: '0 0 0 * *')
|
||||
it 'does not allow invalid cron patterns on freeze_start' do
|
||||
freeze_period = build(:ci_freeze_period, freeze_start: invalid_cron)
|
||||
|
||||
expect(freeze_period).not_to be_valid
|
||||
end
|
||||
|
||||
it 'does not allow invalid cron patterns on freeze_end' do
|
||||
freeze_period = build(:ci_freeze_period, freeze_end: invalid_cron)
|
||||
|
||||
expect(freeze_period).not_to be_valid
|
||||
end
|
||||
|
|
|
|||
|
|
@ -300,6 +300,7 @@ describe Event do
|
|||
include_examples 'visibility examples' do
|
||||
let(:visibility) { visible_to_all }
|
||||
end
|
||||
|
||||
include_examples 'visible to assignee and author', true
|
||||
end
|
||||
|
||||
|
|
@ -309,6 +310,7 @@ describe Event do
|
|||
include_examples 'visibility examples' do
|
||||
let(:visibility) { visible_to_none_except(:member, :admin) }
|
||||
end
|
||||
|
||||
include_examples 'visible to assignee and author', true
|
||||
end
|
||||
end
|
||||
|
|
@ -320,6 +322,7 @@ describe Event do
|
|||
include_examples 'visibility examples' do
|
||||
let(:visibility) { visible_to_all }
|
||||
end
|
||||
|
||||
include_examples 'visible to assignee and author', true
|
||||
end
|
||||
|
||||
|
|
@ -329,6 +332,7 @@ describe Event do
|
|||
include_examples 'visibility examples' do
|
||||
let(:visibility) { visible_to_none_except(:member, :admin) }
|
||||
end
|
||||
|
||||
include_examples 'visible to assignee and author', true
|
||||
end
|
||||
|
||||
|
|
@ -429,6 +433,7 @@ describe Event do
|
|||
end
|
||||
# Normally, we'd expect the author of a comment to be able to view it.
|
||||
# However, this doesn't seem to be the case for comments on snippets.
|
||||
|
||||
include_examples 'visible to author', false
|
||||
end
|
||||
|
||||
|
|
@ -440,6 +445,7 @@ describe Event do
|
|||
end
|
||||
# Normally, we'd expect the author of a comment to be able to view it.
|
||||
# However, this doesn't seem to be the case for comments on snippets.
|
||||
|
||||
include_examples 'visible to author', false
|
||||
end
|
||||
end
|
||||
|
|
@ -450,6 +456,7 @@ describe Event do
|
|||
include_examples 'visibility examples' do
|
||||
let(:visibility) { visible_to_all }
|
||||
end
|
||||
|
||||
include_examples 'visible to author', true
|
||||
|
||||
context 'on internal snippet' do
|
||||
|
|
@ -466,6 +473,7 @@ describe Event do
|
|||
include_examples 'visibility examples' do
|
||||
let(:visibility) { visible_to_none_except(:admin) }
|
||||
end
|
||||
|
||||
include_examples 'visible to author', true
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -480,4 +480,22 @@ describe Milestone do
|
|||
it { is_expected.not_to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-foss/issues/123") }
|
||||
it { is_expected.not_to match("gitlab-org/gitlab-ce/milestones/123") }
|
||||
end
|
||||
|
||||
describe '#parent' do
|
||||
context 'with group' do
|
||||
it 'returns the expected parent' do
|
||||
group = create(:group)
|
||||
|
||||
expect(build(:milestone, group: group).parent).to eq(group)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with project' do
|
||||
it 'returns the expected parent' do
|
||||
project = create(:project)
|
||||
|
||||
expect(build(:milestone, project: project).parent).to eq(project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,475 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe API::FreezePeriods do
|
||||
let_it_be(:project) { create(:project, :repository, :private) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:admin) { create(:admin) }
|
||||
let(:api_user) { user }
|
||||
let(:invalid_cron) { '0 0 0 * *' }
|
||||
let(:last_freeze_period) { project.freeze_periods.last }
|
||||
|
||||
describe 'GET /projects/:id/freeze_periods' do
|
||||
context 'when the user is the admin' do
|
||||
let!(:freeze_period) { create(:ci_freeze_period, project: project, created_at: 2.days.ago) }
|
||||
|
||||
it 'returns 200 HTTP status' do
|
||||
get api("/projects/#{project.id}/freeze_periods", admin)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is the maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context 'when there are two freeze_periods' do
|
||||
let!(:freeze_period_1) { create(:ci_freeze_period, project: project, created_at: 2.days.ago) }
|
||||
let!(:freeze_period_2) { create(:ci_freeze_period, project: project, created_at: 1.day.ago) }
|
||||
|
||||
it 'returns 200 HTTP status' do
|
||||
get api("/projects/#{project.id}/freeze_periods", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'returns freeze_periods ordered by created_at ascending' do
|
||||
get api("/projects/#{project.id}/freeze_periods", user)
|
||||
|
||||
expect(json_response.count).to eq(2)
|
||||
expect(freeze_period_ids).to eq([freeze_period_1.id, freeze_period_2.id])
|
||||
end
|
||||
|
||||
it 'matches response schema' do
|
||||
get api("/projects/#{project.id}/freeze_periods", user)
|
||||
|
||||
expect(response).to match_response_schema('public_api/v4/freeze_periods')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no freeze_periods' do
|
||||
it 'returns 200 HTTP status' do
|
||||
get api("/projects/#{project.id}/freeze_periods", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'returns an empty response' do
|
||||
get api("/projects/#{project.id}/freeze_periods", user)
|
||||
|
||||
expect(json_response).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a guest' do
|
||||
before do
|
||||
project.add_guest(user)
|
||||
end
|
||||
|
||||
let!(:freeze_period) do
|
||||
create(:ci_freeze_period, project: project)
|
||||
end
|
||||
|
||||
it 'responds 403 Forbidden' do
|
||||
get api("/projects/#{project.id}/freeze_periods", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not a project member' do
|
||||
it 'responds 404 Not Found' do
|
||||
get api("/projects/#{project.id}/freeze_periods", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
context 'when project is public' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
it 'responds 403 Forbidden' do
|
||||
get api("/projects/#{project.id}/freeze_periods", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/freeze_periods/:freeze_period_id' do
|
||||
context 'when there is a freeze period' do
|
||||
let!(:freeze_period) do
|
||||
create(:ci_freeze_period, project: project)
|
||||
end
|
||||
|
||||
context 'when the user is the admin' do
|
||||
let!(:freeze_period) { create(:ci_freeze_period, project: project, created_at: 2.days.ago) }
|
||||
|
||||
it 'responds 200 OK' do
|
||||
get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", admin)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is the maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'responds 200 OK' do
|
||||
get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'returns a freeze period' do
|
||||
get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", user)
|
||||
|
||||
expect(json_response).to include(
|
||||
'id' => freeze_period.id,
|
||||
'freeze_start' => freeze_period.freeze_start,
|
||||
'freeze_end' => freeze_period.freeze_end,
|
||||
'cron_timezone' => freeze_period.cron_timezone)
|
||||
end
|
||||
|
||||
it 'matches response schema' do
|
||||
get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", user)
|
||||
|
||||
expect(response).to match_response_schema('public_api/v4/freeze_period')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a guest' do
|
||||
before do
|
||||
project.add_guest(user)
|
||||
end
|
||||
|
||||
it 'responds 403 Forbidden' do
|
||||
get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
|
||||
context 'when project is public' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
context 'when freeze_period exists' do
|
||||
it 'responds 403 Forbidden' do
|
||||
get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when freeze_period does not exist' do
|
||||
it 'responds 403 Forbidden' do
|
||||
get api("/projects/#{project.id}/freeze_periods/0", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /projects/:id/freeze_periods' do
|
||||
let(:params) do
|
||||
{
|
||||
freeze_start: '0 23 * * 5',
|
||||
freeze_end: '0 7 * * 1',
|
||||
cron_timezone: 'UTC'
|
||||
}
|
||||
end
|
||||
|
||||
subject { post api("/projects/#{project.id}/freeze_periods", api_user), params: params }
|
||||
|
||||
context 'when the user is the admin' do
|
||||
let(:api_user) { admin }
|
||||
|
||||
it 'accepts the request' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is the maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context 'with valid params' do
|
||||
it 'accepts the request' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
end
|
||||
|
||||
it 'creates a new freeze period' do
|
||||
expect do
|
||||
subject
|
||||
end.to change { Ci::FreezePeriod.count }.by(1)
|
||||
|
||||
expect(last_freeze_period.freeze_start).to eq('0 23 * * 5')
|
||||
expect(last_freeze_period.freeze_end).to eq('0 7 * * 1')
|
||||
expect(last_freeze_period.cron_timezone).to eq('UTC')
|
||||
end
|
||||
|
||||
it 'matches response schema' do
|
||||
subject
|
||||
|
||||
expect(response).to match_response_schema('public_api/v4/freeze_period')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with incomplete params' do
|
||||
let(:params) do
|
||||
{
|
||||
freeze_start: '0 23 * * 5',
|
||||
cron_timezone: 'UTC'
|
||||
}
|
||||
end
|
||||
|
||||
it 'responds 400 Bad Request' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq("freeze_end is missing")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:params) do
|
||||
{
|
||||
freeze_start: '0 23 * * 5',
|
||||
freeze_end: invalid_cron,
|
||||
cron_timezone: 'UTC'
|
||||
}
|
||||
end
|
||||
|
||||
it 'responds 400 Bad Request' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['message']['freeze_end']).to eq([" is invalid syntax"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'responds 403 Forbidden' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a reporter' do
|
||||
before do
|
||||
project.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'responds 403 Forbidden' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not a project member' do
|
||||
it 'responds 403 Forbidden' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
context 'when project is public' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
it 'responds 403 Forbidden' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /projects/:id/freeze_periods/:freeze_period_id' do
|
||||
let(:params) { { freeze_start: '0 22 * * 5', freeze_end: '5 4 * * sun' } }
|
||||
let!(:freeze_period) { create :ci_freeze_period, project: project }
|
||||
|
||||
subject { put api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", api_user), params: params }
|
||||
|
||||
context 'when user is the admin' do
|
||||
let(:api_user) { admin }
|
||||
|
||||
it 'accepts the request' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is the maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context 'with valid params' do
|
||||
it 'accepts the request' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'performs the update' do
|
||||
subject
|
||||
|
||||
freeze_period.reload
|
||||
|
||||
expect(freeze_period.freeze_start).to eq(params[:freeze_start])
|
||||
expect(freeze_period.freeze_end).to eq(params[:freeze_end])
|
||||
end
|
||||
|
||||
it 'matches response schema' do
|
||||
subject
|
||||
|
||||
expect(response).to match_response_schema('public_api/v4/freeze_period')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:params) { { freeze_start: invalid_cron } }
|
||||
|
||||
it 'responds 400 Bad Request' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['message']['freeze_start']).to eq([" is invalid syntax"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a reporter' do
|
||||
before do
|
||||
project.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'responds 403 Forbidden' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not a project member' do
|
||||
it 'responds 404 Not Found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
context 'when project is public' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
it 'responds 403 Forbidden' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /projects/:id/freeze_periods/:freeze_period_id' do
|
||||
let!(:freeze_period) { create :ci_freeze_period, project: project }
|
||||
let(:freeze_period_id) { freeze_period.id }
|
||||
|
||||
subject { delete api("/projects/#{project.id}/freeze_periods/#{freeze_period_id}", api_user) }
|
||||
|
||||
context 'when user is the admin' do
|
||||
let(:api_user) { admin }
|
||||
|
||||
it 'accepts the request' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is the maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'accepts the request' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
|
||||
it 'destroys the freeze period' do
|
||||
expect do
|
||||
subject
|
||||
end.to change { Ci::FreezePeriod.count }.by(-1)
|
||||
end
|
||||
|
||||
context 'when it is a non-existing freeze period id' do
|
||||
let(:freeze_period_id) { 0 }
|
||||
|
||||
it '404' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a reporter' do
|
||||
before do
|
||||
project.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'responds 403 Forbidden' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not a project member' do
|
||||
it 'responds 404 Not Found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
context 'when project is public' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
it 'responds 403 Forbidden' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def freeze_period_ids
|
||||
json_response.map do |freeze_period_hash|
|
||||
freeze_period_hash.fetch('id')&.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,15 +6,15 @@ describe API::Groups do
|
|||
include GroupAPIHelpers
|
||||
include UploadHelpers
|
||||
|
||||
let(:user1) { create(:user, can_create_group: false) }
|
||||
let(:user2) { create(:user) }
|
||||
let(:user3) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) }
|
||||
let!(:group2) { create(:group, :private) }
|
||||
let!(:project1) { create(:project, namespace: group1) }
|
||||
let!(:project2) { create(:project, namespace: group2) }
|
||||
let!(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
|
||||
let_it_be(:user1) { create(:user, can_create_group: false) }
|
||||
let_it_be(:user2) { create(:user) }
|
||||
let_it_be(:user3) { create(:user) }
|
||||
let_it_be(:admin) { create(:admin) }
|
||||
let_it_be(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) }
|
||||
let_it_be(:group2) { create(:group, :private) }
|
||||
let_it_be(:project1) { create(:project, namespace: group1) }
|
||||
let_it_be(:project2) { create(:project, namespace: group2) }
|
||||
let_it_be(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
|
||||
|
||||
before do
|
||||
group1.add_owner(user1)
|
||||
|
|
@ -90,6 +90,17 @@ describe API::Groups do
|
|||
get api("/groups", admin)
|
||||
end.not_to exceed_query_limit(control)
|
||||
end
|
||||
|
||||
context 'when statistics are requested' do
|
||||
it 'does not include statistics' do
|
||||
get api("/groups"), params: { statistics: true }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.first).not_to include 'statistics'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when authenticated as user" do
|
||||
|
|
@ -1113,6 +1124,17 @@ describe API::Groups do
|
|||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
context 'when statistics are requested' do
|
||||
it 'does not include statistics' do
|
||||
get api("/groups/#{group1.id}/subgroups"), params: { statistics: true }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.first).not_to include 'statistics'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as user' do
|
||||
|
|
|
|||
|
|
@ -66,17 +66,36 @@ describe API::MergeRequests do
|
|||
end
|
||||
|
||||
context 'when merge request is unchecked' do
|
||||
let(:check_service_class) { MergeRequests::MergeabilityCheckService }
|
||||
let(:mr_entity) { json_response.find { |mr| mr['id'] == merge_request.id } }
|
||||
|
||||
before do
|
||||
merge_request.mark_as_unchecked!
|
||||
end
|
||||
|
||||
it 'checks mergeability asynchronously' do
|
||||
expect_next_instance_of(MergeRequests::MergeabilityCheckService) do |service|
|
||||
expect(service).not_to receive(:execute)
|
||||
expect(service).to receive(:async_execute)
|
||||
end
|
||||
context 'with merge status recheck projection' do
|
||||
it 'checks mergeability asynchronously' do
|
||||
expect_next_instance_of(check_service_class) do |service|
|
||||
expect(service).not_to receive(:execute)
|
||||
expect(service).to receive(:async_execute).and_call_original
|
||||
end
|
||||
|
||||
get api(endpoint_path, user)
|
||||
get(api(endpoint_path, user), params: { with_merge_status_recheck: true })
|
||||
|
||||
expect_successful_response_with_paginated_array
|
||||
expect(mr_entity['merge_status']).to eq('checking')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without merge status recheck projection' do
|
||||
it 'does not enqueue a merge status recheck' do
|
||||
expect(check_service_class).not_to receive(:new)
|
||||
|
||||
get api(endpoint_path, user)
|
||||
|
||||
expect_successful_response_with_paginated_array
|
||||
expect(mr_entity['merge_status']).to eq('unchecked')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -510,6 +510,83 @@ describe Projects::CreateService, '#execute' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with specialized_project_authorization_workers' do
|
||||
let_it_be(:other_user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
let(:opts) do
|
||||
{
|
||||
name: 'GitLab',
|
||||
namespace_id: group.id
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
group.add_maintainer(user)
|
||||
group.add_developer(other_user)
|
||||
end
|
||||
|
||||
it 'updates authorization for current_user' do
|
||||
expect(Users::RefreshAuthorizedProjectsService).to(
|
||||
receive(:new).with(user).and_call_original
|
||||
)
|
||||
|
||||
project = create_project(user, opts)
|
||||
|
||||
expect(
|
||||
Ability.allowed?(user, :read_project, project)
|
||||
).to be_truthy
|
||||
end
|
||||
|
||||
it 'schedules authorization update for users with access to group' do
|
||||
expect(AuthorizedProjectsWorker).not_to(
|
||||
receive(:bulk_perform_async)
|
||||
)
|
||||
expect(AuthorizedProjectUpdate::ProjectCreateWorker).to(
|
||||
receive(:perform_async).and_call_original
|
||||
)
|
||||
expect(AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker).to(
|
||||
receive(:bulk_perform_in)
|
||||
.with(1.hour, array_including([user.id], [other_user.id]))
|
||||
.and_call_original
|
||||
)
|
||||
|
||||
create_project(user, opts)
|
||||
end
|
||||
|
||||
context 'when feature is disabled' do
|
||||
before do
|
||||
stub_feature_flags(specialized_project_authorization_workers: false)
|
||||
end
|
||||
|
||||
it 'updates authorization for current_user' do
|
||||
expect(Users::RefreshAuthorizedProjectsService).to(
|
||||
receive(:new).with(user).and_call_original
|
||||
)
|
||||
|
||||
project = create_project(user, opts)
|
||||
|
||||
expect(
|
||||
Ability.allowed?(user, :read_project, project)
|
||||
).to be_truthy
|
||||
end
|
||||
|
||||
it 'uses AuthorizedProjectsWorker' do
|
||||
expect(AuthorizedProjectsWorker).to(
|
||||
receive(:bulk_perform_async).with(array_including([user.id], [other_user.id])).and_call_original
|
||||
)
|
||||
expect(AuthorizedProjectUpdate::ProjectCreateWorker).not_to(
|
||||
receive(:perform_async)
|
||||
)
|
||||
expect(AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker).not_to(
|
||||
receive(:bulk_perform_in)
|
||||
)
|
||||
|
||||
create_project(user, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_project(user, opts)
|
||||
Projects::CreateService.new(user, opts).execute
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,5 +17,14 @@ describe UserProjectAccessChangedService do
|
|||
|
||||
described_class.new([1, 2]).execute(blocking: false)
|
||||
end
|
||||
|
||||
it 'permits low-priority operation' do
|
||||
expect(AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker).to(
|
||||
receive(:bulk_perform_in).with(described_class::DELAY, [[1], [2]])
|
||||
)
|
||||
|
||||
described_class.new([1, 2]).execute(blocking: false,
|
||||
priority: described_class::LOW_PRIORITY)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -99,6 +99,24 @@ module UsageDataHelpers
|
|||
projects_with_error_tracking_enabled
|
||||
projects_with_alerts_service_enabled
|
||||
projects_with_prometheus_alerts
|
||||
projects_with_expiration_policy_enabled
|
||||
projects_with_expiration_policy_disabled
|
||||
projects_with_expiration_policy_enabled_with_keep_n_unset
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_1
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_5
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_10
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_25
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_50
|
||||
projects_with_expiration_policy_enabled_with_older_than_unset
|
||||
projects_with_expiration_policy_enabled_with_older_than_set_to_7d
|
||||
projects_with_expiration_policy_enabled_with_older_than_set_to_14d
|
||||
projects_with_expiration_policy_enabled_with_older_than_set_to_30d
|
||||
projects_with_expiration_policy_enabled_with_older_than_set_to_90d
|
||||
projects_with_expiration_policy_enabled_with_cadence_set_to_1d
|
||||
projects_with_expiration_policy_enabled_with_cadence_set_to_7d
|
||||
projects_with_expiration_policy_enabled_with_cadence_set_to_14d
|
||||
projects_with_expiration_policy_enabled_with_cadence_set_to_1month
|
||||
projects_with_expiration_policy_enabled_with_cadence_set_to_3month
|
||||
pages_domains
|
||||
protected_branches
|
||||
releases
|
||||
|
|
@ -136,25 +154,6 @@ module UsageDataHelpers
|
|||
prometheus_metrics_enabled
|
||||
web_ide_clientside_preview_enabled
|
||||
ingress_modsecurity_enabled
|
||||
projects_with_expiration_policy_disabled
|
||||
projects_with_expiration_policy_enabled
|
||||
projects_with_expiration_policy_enabled_with_keep_n_unset
|
||||
projects_with_expiration_policy_enabled_with_older_than_unset
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_1
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_5
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_10
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_25
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_50
|
||||
projects_with_expiration_policy_enabled_with_keep_n_set_to_100
|
||||
projects_with_expiration_policy_enabled_with_cadence_set_to_1d
|
||||
projects_with_expiration_policy_enabled_with_cadence_set_to_7d
|
||||
projects_with_expiration_policy_enabled_with_cadence_set_to_14d
|
||||
projects_with_expiration_policy_enabled_with_cadence_set_to_1month
|
||||
projects_with_expiration_policy_enabled_with_cadence_set_to_3month
|
||||
projects_with_expiration_policy_enabled_with_older_than_set_to_7d
|
||||
projects_with_expiration_policy_enabled_with_older_than_set_to_14d
|
||||
projects_with_expiration_policy_enabled_with_older_than_set_to_30d
|
||||
projects_with_expiration_policy_enabled_with_older_than_set_to_90d
|
||||
object_store
|
||||
).freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
shared_examples 'error tracking index page' do
|
||||
it 'renders the error index page' do
|
||||
it 'renders the error index page', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
|
||||
within('div.js-title-container') do
|
||||
expect(page).to have_content(project.namespace.name)
|
||||
expect(page).to have_content(project.name)
|
||||
|
|
@ -15,7 +15,7 @@ shared_examples 'error tracking index page' do
|
|||
end
|
||||
end
|
||||
|
||||
it 'loads the error show page on click' do
|
||||
it 'loads the error show page on click', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
|
||||
click_on issues_response[0]['title']
|
||||
|
||||
wait_for_requests
|
||||
|
|
@ -23,7 +23,7 @@ shared_examples 'error tracking index page' do
|
|||
expect(page).to have_content('Error Details')
|
||||
end
|
||||
|
||||
it 'renders the error index data' do
|
||||
it 'renders the error index data', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
|
||||
within('div.error-list') do
|
||||
expect(page).to have_content(issues_response[0]['title'])
|
||||
expect(page).to have_content(issues_response[0]['count'].to_s)
|
||||
|
|
@ -34,7 +34,7 @@ shared_examples 'error tracking index page' do
|
|||
end
|
||||
|
||||
shared_examples 'expanded stack trace context' do |selected_line: nil, expected_line: 1|
|
||||
it 'expands the stack trace context' do
|
||||
it 'expands the stack trace context', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
|
||||
within('div.stacktrace') do
|
||||
find("div.file-holder:nth-child(#{selected_line}) svg.ic-chevron-right").click if selected_line
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ shared_examples 'expanded stack trace context' do |selected_line: nil, expected_
|
|||
end
|
||||
|
||||
shared_examples 'error tracking show page' do
|
||||
it 'renders the error details' do
|
||||
it 'renders the error details', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
|
||||
content = page.find(".content")
|
||||
nav = page.find("nav.breadcrumbs")
|
||||
header = page.find(".error-details-header")
|
||||
|
|
@ -67,11 +67,11 @@ shared_examples 'error tracking show page' do
|
|||
expect(content).to have_content('Users: 0')
|
||||
end
|
||||
|
||||
it 'renders the stack trace heading' do
|
||||
it 'renders the stack trace heading', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
|
||||
expect(page).to have_content('Stack trace')
|
||||
end
|
||||
|
||||
it 'renders the stack trace', :quarantine do
|
||||
it 'renders the stack trace', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
|
||||
event_response['entries'][0]['data']['values'][0]['stacktrace']['frames'].each do |frame|
|
||||
expect(frame['filename']).not_to be_nil
|
||||
expect(page).to have_content(frame['filename'])
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ RSpec.shared_examples 'issuable hook data' do |kind|
|
|||
include_examples 'project hook data' do
|
||||
let(:project) { builder.issuable.project }
|
||||
end
|
||||
|
||||
include_examples 'deprecated repository hook data'
|
||||
|
||||
context "with a #{kind}" do
|
||||
|
|
|
|||
|
|
@ -787,10 +787,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.127.0.tgz#1f7ffdffe44d6a82b372535f93d78f3a895d1960"
|
||||
integrity sha512-Uv52DqkG2KwCB0VRlXUEHFZxJ/7Ql0t1YTdzICpXmDjltuUBrysFcdmWPVO6PgXQxk2ahryNsUjSOziMYTeSiw==
|
||||
|
||||
"@gitlab/ui@14.5.0":
|
||||
version "14.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-14.5.0.tgz#6fed9a5b435884fe69e499578469c8b144726c90"
|
||||
integrity sha512-7OarJzyyeRpFRmShN7c2GBPpahBDbmOSk10ATisannbX/h9i+Z83MQ8ZDqYbM8qeRIfG/BVsnLjC8M7aSsBlPQ==
|
||||
"@gitlab/ui@14.10.0":
|
||||
version "14.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-14.10.0.tgz#39c04d62c914fcefe96c7ec32fdf31b1f98f1119"
|
||||
integrity sha512-k9w6z3/QBeUas++cH5BaozjxY4fVu+AggjGoh9QMKN5hpiGTiTPx5aQJIlOv8UX/kpUmgc4pHSWAbw30YVGGFw==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
"@gitlab/vue-toasted" "^1.3.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue