Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-06 18:08:29 +00:00
parent 526f1e9859
commit 8280fa786e
92 changed files with 850 additions and 1028 deletions

View File

@ -1 +1 @@
ac72695adc90343b7255869818376f505bde8315
6944c95243e2b6ed8f783ae903cb9b03ad50e0f9

View File

@ -1,118 +0,0 @@
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, sprintf } from '~/locale';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import getPipelineQuery from './graphql/queries/pipeline.query.graphql';
import BridgeEmptyState from './components/empty_state.vue';
import BridgeSidebar from './components/sidebar.vue';
import { SIDEBAR_COLLAPSE_BREAKPOINTS } from './components/constants';
export default {
name: 'BridgePageApp',
components: {
BridgeEmptyState,
BridgeSidebar,
CiHeader,
GlLoadingIcon,
},
inject: ['buildId', 'projectFullPath', 'pipelineIid'],
apollo: {
pipeline: {
query: getPipelineQuery,
variables() {
return {
fullPath: this.projectFullPath,
iid: this.pipelineIid,
};
},
update(data) {
if (!data?.project?.pipeline) {
return null;
}
const { pipeline } = data.project;
const stages = pipeline?.stages.edges.map((edge) => edge.node) || [];
const jobs = stages.map((stage) => stage.jobs.nodes).flat();
return {
...pipeline,
commit: {
...pipeline.commit,
commit_path: pipeline.commit.webPath,
short_id: pipeline.commit.shortId,
},
id: getIdFromGraphQLId(pipeline.id),
jobs,
stages,
};
},
},
},
data() {
return {
isSidebarExpanded: true,
pipeline: {},
};
},
computed: {
bridgeJob() {
return (
this.pipeline.jobs?.filter(
(job) => getIdFromGraphQLId(job.id) === Number(this.buildId),
)[0] || {}
);
},
bridgeName() {
return sprintf(__('Job %{jobName}'), { jobName: this.bridgeJob.name });
},
isPipelineLoading() {
return this.$apollo.queries.pipeline.loading;
},
},
created() {
window.addEventListener('resize', this.onResize);
},
mounted() {
this.onResize();
},
methods: {
toggleSidebar() {
this.isSidebarExpanded = !this.isSidebarExpanded;
},
onResize() {
const breakpoint = bp.getBreakpointSize();
if (SIDEBAR_COLLAPSE_BREAKPOINTS.includes(breakpoint)) {
this.isSidebarExpanded = false;
} else if (!this.isSidebarExpanded) {
this.isSidebarExpanded = true;
}
},
},
};
</script>
<template>
<div>
<gl-loading-icon v-if="isPipelineLoading" size="lg" class="gl-mt-4" />
<div v-else>
<ci-header
class="gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
:status="bridgeJob.detailedStatus"
:time="bridgeJob.createdAt"
:user="pipeline.user"
:has-sidebar-button="true"
:item-name="bridgeName"
@clickedSidebarButton="toggleSidebar"
/>
<bridge-empty-state :downstream-pipeline-path="bridgeJob.downstreamPipeline.path" />
<bridge-sidebar
v-if="isSidebarExpanded"
:bridge-job="bridgeJob"
:commit="pipeline.commit"
:is-sidebar-expanded="isSidebarExpanded"
@toggleSidebar="toggleSidebar"
/>
</div>
</div>
</template>

View File

@ -1 +0,0 @@
export const SIDEBAR_COLLAPSE_BREAKPOINTS = ['xs', 'sm'];

View File

@ -1,45 +0,0 @@
<script>
import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
name: 'BridgeEmptyState',
i18n: {
title: __('This job triggers a downstream pipeline'),
linkBtnText: __('View downstream pipeline'),
},
components: {
GlButton,
},
inject: {
emptyStateIllustrationPath: {
type: String,
require: true,
},
},
props: {
downstreamPipelinePath: {
type: String,
required: false,
default: undefined,
},
},
};
</script>
<template>
<div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11">
<img :src="emptyStateIllustrationPath" />
<h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
<gl-button
v-if="downstreamPipelinePath"
class="gl-mt-3"
category="secondary"
variant="confirm"
size="medium"
:href="downstreamPipelinePath"
>
{{ $options.i18n.linkBtnText }}
</gl-button>
</div>
</template>

View File

@ -1,105 +0,0 @@
<script>
import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { JOB_SIDEBAR } from '../../constants';
import CommitBlock from '../../components/commit_block.vue';
export default {
styles: {
width: '290px',
},
name: 'BridgeSidebar',
i18n: {
...JOB_SIDEBAR,
retryButton: __('Retry'),
retryTriggerJob: __('Retry the trigger job'),
retryDownstreamPipeline: __('Retry the downstream pipeline'),
},
sectionClass: ['gl-border-t-solid', 'gl-border-t-1', 'gl-border-t-gray-100', 'gl-py-5'],
components: {
CommitBlock,
GlButton,
GlDropdown,
GlDropdownItem,
TooltipOnTruncate,
},
mixins: [glFeatureFlagsMixin()],
props: {
bridgeJob: {
type: Object,
required: true,
},
commit: {
type: Object,
required: true,
},
},
data() {
return {
topPosition: 0,
};
},
computed: {
rootStyle() {
return { ...this.$options.styles, top: `${this.topPosition}px` };
},
},
mounted() {
this.setTopPosition();
},
methods: {
onSidebarButtonClick() {
this.$emit('toggleSidebar');
},
setTopPosition() {
const navbarEl = document.querySelector('.js-navbar');
if (navbarEl) {
this.topPosition = navbarEl.getBoundingClientRect().bottom;
}
},
},
};
</script>
<template>
<aside
class="gl-fixed gl-right-0 gl-px-5 gl-bg-gray-10 gl-h-full gl-border-l-solid gl-border-1 gl-border-gray-100 gl-z-index-200 gl-overflow-hidden"
:style="rootStyle"
>
<div class="gl-py-5 gl-display-flex gl-align-items-center">
<tooltip-on-truncate :title="bridgeJob.name" truncate-target="child"
><h4 class="gl-mb-0 gl-mr-2 gl-text-truncate">
{{ bridgeJob.name }}
</h4>
</tooltip-on-truncate>
<!-- TODO: implement retry actions -->
<div
v-if="glFeatures.triggerJobRetryAction"
class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right"
>
<gl-dropdown
:text="$options.i18n.retryButton"
category="primary"
variant="confirm"
right
size="medium"
>
<gl-dropdown-item>{{ $options.i18n.retryTriggerJob }}</gl-dropdown-item>
<gl-dropdown-item>{{ $options.i18n.retryDownstreamPipeline }}</gl-dropdown-item>
</gl-dropdown>
</div>
<gl-button
:aria-label="$options.i18n.toggleSidebar"
data-testid="sidebar-expansion-toggle"
category="tertiary"
class="gl-md-display-none gl-ml-2"
icon="chevron-double-lg-right"
@click="onSidebarButtonClick"
/>
</div>
<commit-block :commit="commit" :class="$options.sectionClass" />
<!-- TODO: show stage dropdown, jobs list -->
</aside>
</template>

View File

@ -1,70 +0,0 @@
query getPipelineData($fullPath: ID!, $iid: ID!) {
project(fullPath: $fullPath) {
id
pipeline(iid: $iid) {
id
iid
path
sha
ref
refPath
commit {
id
shortId
title
webPath
}
detailedStatus {
id
icon
group
}
stages {
edges {
node {
id
name
jobs {
nodes {
id
createdAt
name
scheduledAt
startedAt
status
triggered
detailedStatus {
id
detailsPath
icon
group
text
tooltip
}
downstreamPipeline {
id
path
}
stage {
id
name
}
}
}
}
}
}
user {
id
avatarUrl
name
username
webPath
webUrl
status {
message
}
}
}
}
}

View File

@ -1,7 +1,4 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import BridgeApp from './bridge/app.vue';
import JobApp from './components/job_app.vue';
import createStore from './store';
@ -51,43 +48,7 @@ const initializeJobPage = (element) => {
});
};
const initializeBridgePage = (el) => {
const {
buildId,
downstreamPipelinePath,
emptyStateIllustrationPath,
pipelineIid,
projectFullPath,
} = el.dataset;
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
return new Vue({
el,
apolloProvider,
provide: {
buildId,
downstreamPipelinePath,
emptyStateIllustrationPath,
pipelineIid,
projectFullPath,
},
render(h) {
return h(BridgeApp);
},
});
};
export default () => {
const jobElement = document.getElementById('js-job-page');
const bridgeElement = document.getElementById('js-bridge-page');
if (jobElement) {
initializeJobPage(jobElement);
} else {
initializeBridgePage(bridgeElement);
}
initializeJobPage(jobElement);
};

View File

@ -14,6 +14,8 @@ export default {
LeadTimeCharts: () => import('ee_component/dora/components/lead_time_charts.vue'),
TimeToRestoreServiceCharts: () =>
import('ee_component/dora/components/time_to_restore_service_charts.vue'),
ChangeFailureRateCharts: () =>
import('ee_component/dora/components/change_failure_rate_charts.vue'),
ProjectQualitySummary: () => import('ee_component/project_quality_summary/app.vue'),
},
piplelinesTabEvent: 'p_analytics_ci_cd_pipelines',
@ -40,7 +42,12 @@ export default {
const chartsToShow = ['pipelines'];
if (this.shouldRenderDoraCharts) {
chartsToShow.push('deployment-frequency', 'lead-time', 'time-to-restore-service');
chartsToShow.push(
'deployment-frequency',
'lead-time',
'time-to-restore-service',
'change-failure-rate',
);
}
if (this.shouldRenderQualitySummary) {
@ -105,6 +112,12 @@ export default {
>
<time-to-restore-service-charts />
</gl-tab>
<gl-tab
:title="s__('DORA4Metrics|Change failure rate')"
data-testid="change-failure-rate-tab"
>
<change-failure-rate-charts />
</gl-tab>
</template>
<gl-tab v-if="shouldRenderQualitySummary" :title="s__('QualitySummary|Project quality')">
<project-quality-summary />

View File

@ -1,5 +1,5 @@
<script>
import { GlButton, GlSprintf, GlSafeHtmlDirective } from '@gitlab/ui';
import { GlButton, GlSprintf, GlSafeHtmlDirective, GlTooltipDirective } from '@gitlab/ui';
import gitlabLogo from '@gitlab/svgs/dist/illustrations/gitlab_logo.svg';
import { s__, __ } from '~/locale';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
@ -29,6 +29,7 @@ export default {
},
directives: {
safeHtml: GlSafeHtmlDirective,
tooltip: GlTooltipDirective,
},
mixins: [Tracking.mixin()],
i18n: {
@ -92,7 +93,7 @@ export default {
>
<template #default="{ dismiss }">
<aside
class="gl-fixed gl-bottom-0 gl-right-0 gl-z-index-9999 gl-p-5"
class="mr-experience-survey-wrapper gl-fixed gl-bottom-0 gl-right-0 gl-p-5"
:aria-label="$options.i18n.survey"
>
<transition name="survey-slide-up">
@ -101,6 +102,7 @@ export default {
class="mr-experience-survey-body gl-relative gl-display-flex gl-flex-direction-column gl-bg-white gl-p-5 gl-border gl-rounded-base"
>
<gl-button
v-tooltip="$options.i18n.close"
:aria-label="$options.i18n.close"
variant="default"
category="tertiary"

View File

@ -350,6 +350,13 @@ $comparison-empty-state-height: 62px;
}
}
.mr-experience-survey-wrapper {
// setting this explicitly because:
// diff-files-holder has z-index 203
// z-index 9999 utility class breaks tooltips
z-index: 210;
}
.mr-experience-survey-body {
width: 300px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);

View File

@ -22,10 +22,6 @@ class Projects::JobsController < Projects::ApplicationController
before_action :push_jobs_table_vue_search, only: [:index]
before_action :reject_if_build_artifacts_size_refreshing!, only: [:erase]
before_action do
push_frontend_feature_flag(:trigger_job_retry_action, @project)
end
layout 'project'
feature_category :continuous_integration

View File

@ -436,7 +436,7 @@ class IssuableFinder
elsif not_params.filter_by_started_milestone?
items.joins(:milestone).merge(Milestone.not_started)
else
items.without_particular_milestone(not_params[:milestone_title])
items.without_particular_milestones(not_params[:milestone_title])
end
end
# rubocop: enable CodeReuse/ActiveRecord

View File

@ -116,19 +116,16 @@ module EmailsHelper
end
end
# "You are receiving this email because #{reason} on #{gitlab_host}."
def notification_reason_text(reason)
gitlab_host = Gitlab.config.gitlab.host
case reason
when NotificationReason::OWN_ACTIVITY
_("You're receiving this email because of your activity on %{host}.") % { host: gitlab_host }
when NotificationReason::ASSIGNED
_("You're receiving this email because you have been assigned an item on %{host}.") % { host: gitlab_host }
when NotificationReason::MENTIONED
_("You're receiving this email because you have been mentioned on %{host}.") % { host: gitlab_host }
# "You are receiving this email because ... on #{host}. ..."
def notification_reason_text(reason: nil, show_manage_notifications_link: false, show_help_link: false, manage_label_subscriptions_url: nil, unsubscribe_url: nil, format: :text)
if unsubscribe_url && show_manage_notifications_link && show_help_link
notification_reason_text_with_unsubscribe_and_manage_notifications_and_help_links(reason: reason, unsubscribe_url: unsubscribe_url, format: format)
elsif !reason && manage_label_subscriptions_url && show_help_link
notification_reason_text_with_manage_label_subscriptions_and_help_links(manage_label_subscriptions_url: manage_label_subscriptions_url, format: format)
elsif show_manage_notifications_link && show_help_link
notification_reason_text_with_manage_notifications_and_help_links(reason: reason, format: format)
else
_("You're receiving this email because of your account on %{host}.") % { host: gitlab_host }
notification_reason_text_without_links(reason: reason, format: format)
end
end
@ -259,9 +256,7 @@ module EmailsHelper
end
def instance_access_request_text(user, format: nil)
gitlab_host = Gitlab.config.gitlab.host
_('%{username} has asked for a GitLab account on your instance %{host}:') % { username: sanitize_name(user.name), host: gitlab_host }
_('%{username} has asked for a GitLab account on your instance %{host}:').html_safe % { username: sanitize_name(user.name), host: gitlab_host_link(format) }
end
def instance_access_request_link(user, format: nil)
@ -325,6 +320,75 @@ module EmailsHelper
def email_header_and_footer_enabled?
current_appearance&.email_header_and_footer_enabled?
end
def gitlab_host_link(format)
case format
when :html
generate_link(Gitlab.config.gitlab.host, Gitlab.config.gitlab.url)
when :text
Gitlab.config.gitlab.host
end
end
def notification_reason_text_with_unsubscribe_and_manage_notifications_and_help_links(reason:, unsubscribe_url:, format:)
unsubscribe_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: unsubscribe_url }
unsubscribe_link_end = '</a>'.html_safe
manage_notifications_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="mng-notif-link">'.html_safe % { url: profile_notifications_url }
manage_notifications_link_end = '</a>'.html_safe
help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="help-link">'.html_safe % { url: help_url }
help_link_end = '</a>'.html_safe
case reason
when NotificationReason::OWN_ACTIVITY
_("You're receiving this email because of your activity on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), unsubscribe_link_start: unsubscribe_link_start, unsubscribe_link_end: unsubscribe_link_end, manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
when NotificationReason::ASSIGNED
_("You're receiving this email because you have been assigned an item on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), unsubscribe_link_start: unsubscribe_link_start, unsubscribe_link_end: unsubscribe_link_end, manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
when NotificationReason::MENTIONED
_("You're receiving this email because you have been mentioned on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), unsubscribe_link_start: unsubscribe_link_start, unsubscribe_link_end: unsubscribe_link_end, manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
else
_("You're receiving this email because of your account on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), unsubscribe_link_start: unsubscribe_link_start, unsubscribe_link_end: unsubscribe_link_end, manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
end
end
def notification_reason_text_with_manage_label_subscriptions_and_help_links(manage_label_subscriptions_url:, format:)
manage_label_subscriptions_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="mng-notif-link">'.html_safe % { url: manage_label_subscriptions_url }
manage_label_subscriptions_link_end = '</a>'.html_safe
help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="help-link">'.html_safe % { url: help_url }
help_link_end = '</a>'.html_safe
_("You're receiving this email because of your account on %{host}. %{manage_label_subscriptions_link_start}Manage label subscriptions%{manage_label_subscriptions_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), manage_label_subscriptions_link_start: manage_label_subscriptions_link_start, manage_label_subscriptions_link_end: manage_label_subscriptions_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
end
def notification_reason_text_with_manage_notifications_and_help_links(reason:, format:)
manage_notifications_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="mng-notif-link">'.html_safe % { url: profile_notifications_url }
manage_notifications_link_end = '</a>'.html_safe
help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="help-link">'.html_safe % { url: help_url }
help_link_end = '</a>'.html_safe
case reason
when NotificationReason::MENTIONED
_("You're receiving this email because you have been mentioned on %{host}. %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
else
_("You're receiving this email because of your account on %{host}. %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
end
end
def notification_reason_text_without_links(reason:, format:)
case reason
when NotificationReason::OWN_ACTIVITY
_("You're receiving this email because of your activity on %{host}.").html_safe % { host: gitlab_host_link(format) }
when NotificationReason::ASSIGNED
_("You're receiving this email because you have been assigned an item on %{host}.").html_safe % { host: gitlab_host_link(format) }
when NotificationReason::MENTIONED
_("You're receiving this email because you have been mentioned on %{host}.").html_safe % { host: gitlab_host_link(format) }
else
_("You're receiving this email because of your account on %{host}.").html_safe % { host: gitlab_host_link(format) }
end
end
end
EmailsHelper.prepend_mod_with('EmailsHelper')

View File

@ -16,7 +16,7 @@ module Milestoneable
scope :any_milestone, -> { where.not(milestone_id: nil) }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
scope :without_particular_milestone, ->(title) { left_outer_joins(:milestone).where("milestones.title != ? OR milestone_id IS NULL", title) }
scope :without_particular_milestones, ->(titles) { left_outer_joins(:milestone).where("milestones.title NOT IN (?) OR milestone_id IS NULL", titles) }
scope :any_release, -> { joins_milestone_releases }
scope :with_release, -> (tag, project_id) { joins_milestone_releases.where( milestones: { releases: { tag: tag, project_id: project_id } } ) }
scope :without_particular_release, -> (tag, project_id) { joins_milestone_releases.where.not(milestones: { releases: { tag: tag, project_id: project_id } }) }

View File

@ -15,7 +15,12 @@ class MergeRequestDiffFile < ApplicationRecord
end
def utf8_diff
fetched_diff = diff
fetched_diff = if Feature.enabled?(:externally_stored_diffs_caching_export) &&
merge_request_diff&.stored_externally?
diff_export
else
diff
end
return '' if fetched_diff.blank?
@ -46,14 +51,13 @@ class MergeRequestDiffFile < ApplicationRecord
end
end
private
# This method is meant to be used during Project Export.
# It is identical to the behaviour in #diff & #utf8_diff with the only
# It is identical to the behaviour in #diff with the only
# difference of caching externally stored diffs on local disk in
# temp storage location in order to improve diff export performance.
def diff_export
return utf8_diff unless Feature.enabled?(:externally_stored_diffs_caching_export)
return utf8_diff unless merge_request_diff&.stored_externally?
content = merge_request_diff.cached_external_diff do |file|
file.seek(external_diff_offset)
@ -69,14 +73,17 @@ class MergeRequestDiffFile < ApplicationRecord
end
end
if content.respond_to?(:encoding)
content = encode_utf8(content)
end
return '' if content.blank?
content
rescue StandardError
utf8_diff
rescue StandardError => e
log_payload = {
message: 'Cached external diff export failed',
merge_request_diff_file_id: id,
merge_request_diff_id: merge_request_diff&.id
}
Gitlab::ExceptionLogFormatter.format!(e, log_payload)
Gitlab::AppLogger.warn(log_payload)
diff
end
end

View File

@ -0,0 +1,50 @@
# frozen_string_literal: true
module Terraform
class ModuleVersionPresenter < Gitlab::View::Presenter::Simple
attr_accessor :package, :system
def initialize(package, system)
@package = package
@system = system
end
def name
package.name
end
def provider
system
end
def providers
[
provider
]
end
def root
{
'dependencies' => []
}
end
def source
package&.project&.web_url
end
def submodules
[]
end
def version
package.version
end
def versions
[
version
]
end
end
end

View File

@ -1,7 +1,7 @@
- expanded = integration_expanded?('eks_')
%section.settings.as-eks.no-animate#js-eks-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Amazon EKS')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -1,6 +1,6 @@
%section.settings.as-external-auth.no-animate#js-external-auth-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= s_('ExternalAuthorization|External authorization')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -2,7 +2,7 @@
%section.settings.no-animate#js-floc-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= s_('FloC|Federated Learning of Cohorts')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -2,7 +2,7 @@
%section.settings.no-animate#js-gitpod-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Gitpod')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -2,7 +2,7 @@
%section.settings.no-animate#js-jira_connect-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= s_('JiraConnect|GitLab for Jira App')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -1,7 +1,7 @@
- expanded = integration_expanded?('kroki_')
%section.settings.as-kroki.no-animate#js-kroki-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Kroki')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -1,7 +1,7 @@
- expanded = integration_expanded?('mailgun_')
%section.settings.as-mailgun.no-animate#js-mailgun-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Mailgun')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -1,7 +1,7 @@
- expanded = integration_expanded?('plantuml_')
%section.settings.as-plantuml.no-animate#js-plantuml-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('PlantUML')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -1,7 +1,7 @@
- expanded = integration_expanded?('snowplow_')
%section.settings.as-snowplow.no-animate#js-snowplow-settings{ class: ('expanded' if expanded), data: { qa_selector: 'snowplow_settings_content' } }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Snowplow')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -3,7 +3,7 @@
%section.settings.as-sourcegraph.no-animate#js-sourcegraph-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Sourcegraph')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -1,7 +1,7 @@
- expanded = integration_expanded?('hide_third_party_')
%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Customer experience improvement and third-party offers')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')

View File

@ -4,7 +4,7 @@
%section.settings.as-visibility-access.no-animate#js-visibility-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Visibility and access controls')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@ -15,7 +15,7 @@
%section.settings.as-account-limit.no-animate#js-account-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'account_and_limit_settings_content' } }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Account and limit')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@ -26,7 +26,7 @@
%section.settings.as-diff-limits.no-animate#js-merge-request-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Diff limits')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@ -37,7 +37,7 @@
%section.settings.as-signup.no-animate#js-signup-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'sign_up_restrictions_settings_content' } }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Sign-up restrictions')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@ -48,7 +48,7 @@
%section.settings.as-signin.no-animate#js-signin-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Sign-in restrictions')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@ -60,7 +60,7 @@
%section.settings.as-terms.no-animate#js-terms-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Terms of Service and Privacy Policy')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@ -74,7 +74,7 @@
%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Web terminal')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@ -86,7 +86,7 @@
%section.settings.no-animate#js-web-ide-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Web IDE')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')

View File

@ -3,8 +3,6 @@
%td
%img.footer-logo{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png') }
%div
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, class: 'mng-notif-link')
- help_link = link_to(_("Help"), help_url, class: 'help-link')
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
= notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)
= render 'layouts/mailer'

View File

@ -3,7 +3,7 @@
<%= yield -%>
-- <%# signature marker %>
<%= _("You're receiving this email because of your account on %{host}.") % { host: Gitlab.config.gitlab.host } %>
<%= notification_reason_text %>
<%= render_if_exists 'layouts/mailer/additional_text' %>
<%= text_footer_message %>

View File

@ -26,16 +26,7 @@
- else
#{link_to _("View it on GitLab"), @target_url}.
%br
-# Don't link the host in the line below, one link in the email is easier to quickly click than two.
= notification_reason_text(@reason)
If you'd like to receive fewer emails, you can
- if @labels_url
adjust your #{link_to 'label subscriptions', @labels_url}.
- else
- if @unsubscribe_url
= link_to "unsubscribe", @unsubscribe_url
from this thread or
adjust your notification settings.
= notification_reason_text(reason: @reason, show_manage_notifications_link: !@labels_url, show_help_link: true, manage_label_subscriptions_url: @labels_url, unsubscribe_url: @unsubscribe_url, format: :html)
= email_action @target_url

View File

@ -11,7 +11,7 @@
<% end -%>
<% end -%>
<%= notification_reason_text(@reason) %>
<%= notification_reason_text(reason: @reason) %>
<%= render_if_exists 'layouts/mailer/additional_text' %>
<%= text_footer_message -%>

View File

@ -152,6 +152,4 @@
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png'), style: "display:block;margin:0 auto 1em;", width: "90" }/
%div
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
- help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
= notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)

View File

@ -148,6 +148,4 @@
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png'), style: "display:block;margin:0 auto 1em;", width: "90" }
%div
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
- help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
= notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)

View File

@ -151,6 +151,4 @@
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png'), style: "display:block;margin:0 auto 1em;", width: "90" }/
%div
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
- help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
= notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)

View File

@ -7,7 +7,4 @@
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
- if @build.is_a? ::Ci::Build
#js-job-page{ data: jobs_data }
- else
#js-bridge-page{ data: bridge_data(@build, @project) }
#js-job-page{ data: jobs_data }

View File

@ -1,5 +1,5 @@
= link_to root_path do
= render 'shared/logo.svg'
= render partial: 'shared/logo', formats: :svg
%h1= _('Offline')
.container
%h3= _('You are currently offline, or the GitLab instance is not reachable.')

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363186
milestone: '15.1'
type: development
group: group::pipeline authoring
default_enabled: false
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: trigger_job_retry_action
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77951
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349966
milestone: '14.7'
type: development
group: group::pipeline authoring
default_enabled: false

View File

@ -0,0 +1,9 @@
---
table_name: namespace_bans
classes:
- NamespaceBan
feature_categories:
- instance_resiliency
description: Contains users banned from namespaces
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91271
milestone: "15.2"

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class CreateNamespaceBans < Gitlab::Database::Migration[2.0]
UNIQUE_INDEX_NAME = 'index_namespace_bans_on_namespace_id_and_user_id'
def change
create_table :namespace_bans do |t|
t.bigint :namespace_id, null: false
t.bigint :user_id, null: false, index: true
t.timestamps_with_timezone
t.index [:namespace_id, :user_id], unique: true, name: UNIQUE_INDEX_NAME
end
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddNamespaceBansNamespaceIdForeignKey < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
add_concurrent_foreign_key :namespace_bans, :namespaces, column: :namespace_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :namespace_bans, column: :namespace_id
end
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddNamespaceBansUserIdForeignKey < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
add_concurrent_foreign_key :namespace_bans, :users, column: :user_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :namespace_bans, column: :user_id
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class IncreaseWebauthnXidLength < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
new_constraint_name = check_constraint_name(:webauthn_registrations, :credential_xid, 'max_length_v3')
add_text_limit :webauthn_registrations, :credential_xid, 1364, constraint_name: new_constraint_name
prev_constraint_name = check_constraint_name(:webauthn_registrations, :credential_xid, 'max_length_v2')
remove_text_limit :webauthn_registrations, :credential_xid, constraint_name: prev_constraint_name
end
def down
# no-op: Danger of failling if there are records with length(credential_xid) > 1364
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class AddIndicesOnSecurityScansInfoColumn < Gitlab::Database::Migration[2.0]
INDEX_NAME_ON_ERRORS = :index_security_scans_on_length_of_errors
INDEX_NAME_ON_WARNINGS = :index_security_scans_on_length_of_warnings
disable_ddl_transaction!
def up
add_concurrent_index(
:security_scans,
"pipeline_id, jsonb_array_length(COALESCE((security_scans.info -> 'errors'::text), '[]'::jsonb))",
name: INDEX_NAME_ON_ERRORS
)
add_concurrent_index(
:security_scans,
"pipeline_id, jsonb_array_length(COALESCE((security_scans.info -> 'warnings'::text), '[]'::jsonb))",
name: INDEX_NAME_ON_WARNINGS
)
end
def down
remove_concurrent_index_by_name :security_scans, INDEX_NAME_ON_ERRORS
remove_concurrent_index_by_name :security_scans, INDEX_NAME_ON_WARNINGS
end
end

View File

@ -0,0 +1 @@
75027a5b09491b156837707af20406b2672d5ee3ce2272ecf1496e98da2861bf

View File

@ -0,0 +1 @@
90b9b47ef3671b73117205264589f895a083b0d00db00e684b25e60673d2e840

View File

@ -0,0 +1 @@
d64a9c41376bbb3bc2c9df846668b1a67b0bed1b1410d97dba17c19a2f322b38

View File

@ -0,0 +1 @@
2f5e08212b2f733ce5812d7154879768532e31e642b647648d1c03fd4ddf8b13

View File

@ -0,0 +1 @@
ea387b35bfb7f15a036aca9413b8fd15ede6b16048fa9e9be5a62b9e21ca362d

View File

@ -17493,6 +17493,23 @@ CREATE TABLE namespace_aggregation_schedules (
namespace_id integer NOT NULL
);
CREATE TABLE namespace_bans (
id bigint NOT NULL,
namespace_id bigint NOT NULL,
user_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE namespace_bans_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE namespace_bans_id_seq OWNED BY namespace_bans.id;
CREATE TABLE namespace_ci_cd_settings (
namespace_id bigint NOT NULL,
allow_stale_runner_pruning boolean DEFAULT false NOT NULL
@ -22447,7 +22464,7 @@ CREATE TABLE webauthn_registrations (
public_key text NOT NULL,
u2f_registration_id integer,
CONSTRAINT check_2f02e74321 CHECK ((char_length(name) <= 255)),
CONSTRAINT check_e54008d9ce CHECK ((char_length(credential_xid) <= 340))
CONSTRAINT check_f5ab2b551a CHECK ((char_length(credential_xid) <= 1364))
);
CREATE SEQUENCE webauthn_registrations_id_seq
@ -23223,6 +23240,8 @@ ALTER TABLE ONLY milestones ALTER COLUMN id SET DEFAULT nextval('milestones_id_s
ALTER TABLE ONLY namespace_admin_notes ALTER COLUMN id SET DEFAULT nextval('namespace_admin_notes_id_seq'::regclass);
ALTER TABLE ONLY namespace_bans ALTER COLUMN id SET DEFAULT nextval('namespace_bans_id_seq'::regclass);
ALTER TABLE ONLY namespace_statistics ALTER COLUMN id SET DEFAULT nextval('namespace_statistics_id_seq'::regclass);
ALTER TABLE ONLY namespaces ALTER COLUMN id SET DEFAULT nextval('namespaces_id_seq'::regclass);
@ -25234,6 +25253,9 @@ ALTER TABLE ONLY namespace_admin_notes
ALTER TABLE ONLY namespace_aggregation_schedules
ADD CONSTRAINT namespace_aggregation_schedules_pkey PRIMARY KEY (namespace_id);
ALTER TABLE ONLY namespace_bans
ADD CONSTRAINT namespace_bans_pkey PRIMARY KEY (id);
ALTER TABLE ONLY namespace_ci_cd_settings
ADD CONSTRAINT namespace_ci_cd_settings_pkey PRIMARY KEY (namespace_id);
@ -28780,6 +28802,10 @@ CREATE INDEX index_namespace_admin_notes_on_namespace_id ON namespace_admin_note
CREATE UNIQUE INDEX index_namespace_aggregation_schedules_on_namespace_id ON namespace_aggregation_schedules USING btree (namespace_id);
CREATE UNIQUE INDEX index_namespace_bans_on_namespace_id_and_user_id ON namespace_bans USING btree (namespace_id, user_id);
CREATE INDEX index_namespace_bans_on_user_id ON namespace_bans USING btree (user_id);
CREATE UNIQUE INDEX index_namespace_root_storage_statistics_on_namespace_id ON namespace_root_storage_statistics USING btree (namespace_id);
CREATE UNIQUE INDEX index_namespace_statistics_on_namespace_id ON namespace_statistics USING btree (namespace_id);
@ -29540,6 +29566,10 @@ CREATE INDEX index_security_scans_on_created_at ON security_scans USING btree (c
CREATE INDEX index_security_scans_on_date_created_at_and_id ON security_scans USING btree (date(timezone('UTC'::text, created_at)), id);
CREATE INDEX index_security_scans_on_length_of_errors ON security_scans USING btree (pipeline_id, jsonb_array_length(COALESCE((info -> 'errors'::text), '[]'::jsonb)));
CREATE INDEX index_security_scans_on_length_of_warnings ON security_scans USING btree (pipeline_id, jsonb_array_length(COALESCE((info -> 'warnings'::text), '[]'::jsonb)));
CREATE INDEX index_security_scans_on_pipeline_id ON security_scans USING btree (pipeline_id);
CREATE INDEX index_security_scans_on_project_id ON security_scans USING btree (project_id);
@ -31777,6 +31807,9 @@ ALTER TABLE ONLY protected_environment_approval_rules
ALTER TABLE ONLY ci_pipeline_schedule_variables
ADD CONSTRAINT fk_41c35fda51 FOREIGN KEY (pipeline_schedule_id) REFERENCES ci_pipeline_schedules(id) ON DELETE CASCADE;
ALTER TABLE ONLY namespace_bans
ADD CONSTRAINT fk_4275fbb1d7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY geo_event_log
ADD CONSTRAINT fk_42c3b54bed FOREIGN KEY (cache_invalidation_event_id) REFERENCES geo_cache_invalidation_events(id) ON DELETE CASCADE;
@ -32164,6 +32197,9 @@ ALTER TABLE ONLY deployments
ALTER TABLE ONLY routes
ADD CONSTRAINT fk_bb2e5b8968 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY namespace_bans
ADD CONSTRAINT fk_bcc024eef2 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY gitlab_subscriptions
ADD CONSTRAINT fk_bd0c4019c3 FOREIGN KEY (hosted_plan_id) REFERENCES plans(id) ON DELETE CASCADE;

View File

@ -347,6 +347,7 @@ Some basic Ruby runtime metrics are available:
|:---------------------------------------- |:--------- |:----- |:----------- |
| `ruby_gc_duration_seconds` | Counter | 11.1 | Time spent by Ruby in GC |
| `ruby_gc_stat_...` | Gauge | 11.1 | Various metrics from [GC.stat](https://ruby-doc.org/core-2.6.5/GC.html#method-c-stat) |
| `ruby_gc_stat_ext_heap_fragmentation` | Gauge | 15.2 | Degree of Ruby heap fragmentation as live objects versus eden slots (range 0 to 1) |
| `ruby_file_descriptors` | Gauge | 11.1 | File descriptors per process |
| `ruby_sampler_duration_seconds` | Counter | 11.1 | Time spent collecting stats |
| `ruby_process_cpu_seconds_total` | Gauge | 12.0 | Total amount of CPU time per process |

View File

@ -3530,12 +3530,12 @@ in that container.
#### `service:pull_policy`
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21619) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `ci_docker_image_pull_policy`. Disabled by default.
> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/363186) in GitLab 15.2.
> - Requires GitLab Runner 15.1 or later.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available,
ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `ci_docker_image_pull_policy`.
The feature is not ready for production use.
On self-managed GitLab, by default this feature is available. To hide the feature,
ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `ci_docker_image_pull_policy`.
The pull policy that the runner uses to fetch the Docker image.

View File

@ -360,6 +360,41 @@ Ongoing improvements to report validation are tracked [in this epic](https://git
In the meantime, you can see which versions are supported in the
[source code](https://gitlab.com/gitlab-org/gitlab/-/blob/08dd756429731a0cca1e27ca9d59eea226398a7d/lib/gitlab/ci/parsers/security/validators/schema_validator.rb#L9-27).
#### Validate locally
Before running your analyzer in GitLab, you should validate the report produced by your analyzer to
ensure it complies with the declared schema version.
Use the script below to validate JSON files against a given schema.
```ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'json_schemer'
end
require 'json'
require 'pathname'
raise 'Usage: ruby script.rb <security schema file name> <report file name>' unless ARGV.size == 2
schema = JSONSchemer.schema(Pathname.new(ARGV[0]))
report = JSON.parse(File.open(ARGV[1]).read)
schema_validation_errors = schema.validate(report).map { |error| JSONSchemer::Errors.pretty(error) }
puts(schema_validation_errors)
```
1. Download the appropriate schema that matches your report type and declared version. For
example, you can find version `14.0.6` of the `container_scanning` report schema at
`https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/v14.0.6/dist/container-scanning-report-format.json?inline=false`.
1. Save the Ruby script above in a file, for example, `validate.rb`.
1. Run the script, passing the schema and report file names as arguments in order. For example:
1. Using your local Ruby interpreter: `ruby validate.rb container-scanning-format_14-0-6.json gl-container-scanning-report.json`.
1. Using Docker: `docker run -it --rm -v $(pwd):/ci ruby:3-slim ruby /ci/validate.rb /ci/container-scanning-format_14-0-6.json /ci/gl-container-scanning-report.json`
1. Validation errors are shown on the screen. You must resolve these errors before GitLab can ingest your report.
### Report Fields
#### Version

View File

@ -78,7 +78,7 @@ To view the lead time for changes chart:
![Lead time](img/lead_time_chart_v13_11.png)
## View time to restore service chart **(PREMIUM)**
## View time to restore service chart **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/356959) in GitLab 15.1
@ -93,3 +93,17 @@ To view the time to restore service chart:
1. Select the **Time to restore service** tab.
![Lead time](img/time_to_restore_service_charts_v15_1.png)
## View change failure rate chart **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357072) in GitLab 15.2
The change failure rate chart shows information about the percentage of deployments that cause an incident in a production environment. This chart is available for groups and projects.
Change failure rate is one of the four [DORA metrics](index.md#devops-research-and-assessment-dora-key-metrics) that DevOps teams use for measuring excellence in software delivery.
To view the change failure rate chart:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Analytics > CI/CD Analytics**.
1. Select the **Change failure rate** tab.

View File

@ -873,6 +873,7 @@ Payload example:
},
"object_attributes": {
"id": 99,
"iid": 1,
"target_branch": "master",
"source_branch": "ms-viewport",
"source_project_id": 14,
@ -884,10 +885,12 @@ Payload example:
"milestone_id": null,
"state": "opened",
"blocking_discussions_resolved": true,
"work_in_progress": false,
"first_contribution": true,
"merge_status": "unchecked",
"target_project_id": 14,
"iid": 1,
"description": "",
"url": "http://example.com/diaspora/merge_requests/1",
"source": {
"name":"Awesome Project",
"description":"Aut reprehenderit ut est.",
@ -930,8 +933,18 @@ Payload example:
"email": "gitlabdev@dv6700.(none)"
}
},
"work_in_progress": false,
"url": "http://example.com/diaspora/merge_requests/1",
"labels": [{
"id": 206,
"title": "API",
"color": "#ffffff",
"project_id": 14,
"created_at": "2013-12-03T17:15:43Z",
"updated_at": "2013-12-03T17:15:43Z",
"template": false,
"description": "API related issues",
"type": "ProjectLabel",
"group_id": 41
}],
"action": "open",
"assignee": {
"name": "User1",
@ -990,6 +1003,9 @@ Payload example:
}
```
NOTE:
The fields `assignee_id`, and `state` are deprecated.
## Wiki page events
Wiki page events are triggered when a wiki page is created, updated, or deleted.

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
module API
module Entities
module Terraform
class ModuleVersion < Grape::Entity
expose :name
expose :provider
expose :providers
expose :root
expose :source
expose :submodules
expose :version
expose :versions
end
end
end
end

View File

@ -151,6 +151,16 @@ module API
present_carrierwave_file!(package_file.file)
end
end
# This endpoint has to be the last within namespace '*module_version' block
# due to how the route matching works in grape
# format: false is required, otherwise grape splits the semver version into 2 params:
# params[:module_version] and params[:format],
# thus leading to an invalid/not found module version
get format: false do
presenter = ::Terraform::ModuleVersionPresenter.new(package, params[:module_system])
present presenter, with: ::API::Entities::Terraform::ModuleVersion
end
end
end

View File

@ -29,7 +29,7 @@ module API
params do
requires :event, type: String, desc: 'The event name that should be tracked'
end
post 'increment_unique_users' do
post 'increment_unique_users', urgency: :low do
event_name = params[:event]
increment_unique_values(event_name, current_user.id)

View File

@ -18,7 +18,7 @@ module Gitlab
user: user.hook_attrs,
project: issuable.project.hook_attrs,
object_attributes: issuable_builder.new(issuable).build,
labels: issuable.labels.map(&:hook_attrs),
labels: issuable.labels_hook_attrs,
changes: final_changes(changes.slice(*safe_keys)),
# DEPRECATED
repository: issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)

View File

@ -327,6 +327,7 @@ milestone_releases: :gitlab_main
milestones: :gitlab_main
namespace_admin_notes: :gitlab_main
namespace_aggregation_schedules: :gitlab_main
namespace_bans: :gitlab_main
namespace_limits: :gitlab_main
namespace_package_settings: :gitlab_main
namespace_root_storage_statistics: :gitlab_main

View File

@ -62,7 +62,8 @@ module Gitlab
assignee_id: merge_request.assignee_ids.first, # This key is deprecated
labels: merge_request.labels_hook_attrs,
state: merge_request.state, # This key is deprecated
blocking_discussions_resolved: merge_request.mergeable_discussions_state?
blocking_discussions_resolved: merge_request.mergeable_discussions_state?,
first_contribution: merge_request.first_contribution?
}
merge_request.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)

View File

@ -977,7 +977,7 @@ methods:
statuses:
- :type
merge_request_diff_files:
- :diff_export
- :utf8_diff
merge_requests:
- :diff_head_sha
- :source_branch_sha

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Gitlab
module Metrics
module Memory
extend self
HEAP_SLOTS_PER_PAGE = GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
def gc_heap_fragmentation(gc_stat = GC.stat)
1 - (gc_stat[:heap_live_slots] / (HEAP_SLOTS_PER_PAGE * gc_stat[:heap_eden_pages].to_f))
end
end
end
end

View File

@ -39,7 +39,8 @@ module Gitlab
process_proportional_memory_bytes: ::Gitlab::Metrics.gauge(metric_name(:process, :proportional_memory_bytes), 'Memory used (PSS)', labels),
process_start_time_seconds: ::Gitlab::Metrics.gauge(metric_name(:process, :start_time_seconds), 'Process start time seconds'),
sampler_duration: ::Gitlab::Metrics.counter(metric_name(:sampler, :duration_seconds_total), 'Sampler time', labels),
gc_duration_seconds: ::Gitlab::Metrics.histogram(metric_name(:gc, :duration_seconds), 'GC time', labels, GC_REPORT_BUCKETS)
gc_duration_seconds: ::Gitlab::Metrics.histogram(metric_name(:gc, :duration_seconds), 'GC time', labels, GC_REPORT_BUCKETS),
heap_fragmentation: ::Gitlab::Metrics.gauge(metric_name(:gc_stat_ext, :heap_fragmentation), 'Ruby heap fragmentation', labels)
}
GC.stat.keys.each do |key|
@ -76,8 +77,13 @@ module Gitlab
end
# Collect generic GC stats
GC.stat.each do |key, value|
metrics[key].set(labels, value)
GC.stat.then do |gc_stat|
gc_stat.each do |key, value|
metrics[key].set(labels, value)
end
# Collect custom GC stats
metrics[:heap_fragmentation].set(labels, Memory.gc_heap_fragmentation(gc_stat))
end
end

View File

@ -105,6 +105,8 @@ module UnnestedInFilters
# LIMIT 20
#
def rewrite
log_rewrite
model.from(from)
.limit(limit_value)
.order(order_values)
@ -125,6 +127,10 @@ module UnnestedInFilters
delegate :model, :order_values, :limit_value, :where_values_hash, to: :relation, private: true
def log_rewrite
::Gitlab::AppLogger.info(message: 'Query is being rewritten by `UnnestedInFilters`', model: model.name)
end
def from
[value_tables.map(&:to_sql) + [lateral]].join(', ')
end

View File

@ -6969,6 +6969,9 @@ msgstr ""
msgid "CICDAnalytics|All time"
msgstr ""
msgid "CICDAnalytics|Change failure rate"
msgstr ""
msgid "CICDAnalytics|Deployment frequency"
msgstr ""
@ -23625,9 +23628,6 @@ msgstr ""
msgid "Manage access"
msgstr ""
msgid "Manage all notifications"
msgstr ""
msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
msgstr ""
@ -32982,12 +32982,6 @@ msgstr ""
msgid "Retry migration"
msgstr ""
msgid "Retry the downstream pipeline"
msgstr ""
msgid "Retry the trigger job"
msgstr ""
msgid "Retry this job"
msgstr ""
@ -39678,9 +39672,6 @@ msgstr ""
msgid "This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes."
msgstr ""
msgid "This job triggers a downstream pipeline"
msgstr ""
msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
msgstr ""
@ -42467,9 +42458,6 @@ msgstr ""
msgid "View documentation"
msgstr ""
msgid "View downstream pipeline"
msgstr ""
msgid "View eligible approvers"
msgstr ""
@ -44489,18 +44477,36 @@ msgstr ""
msgid "You're receiving this email because of your account on %{host}."
msgstr ""
msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
msgid "You're receiving this email because of your account on %{host}. %{manage_label_subscriptions_link_start}Manage label subscriptions%{manage_label_subscriptions_link_end} &middot; %{help_link_start}Help%{help_link_end}"
msgstr ""
msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
msgstr ""
msgid "You're receiving this email because of your account on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
msgstr ""
msgid "You're receiving this email because of your activity on %{host}."
msgstr ""
msgid "You're receiving this email because of your activity on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
msgstr ""
msgid "You're receiving this email because you have been assigned an item on %{host}."
msgstr ""
msgid "You're receiving this email because you have been assigned an item on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
msgstr ""
msgid "You're receiving this email because you have been mentioned on %{host}."
msgstr ""
msgid "You're receiving this email because you have been mentioned on %{host}. %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
msgstr ""
msgid "You're receiving this email because you have been mentioned on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
msgstr ""
msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
msgstr ""
@ -44930,6 +44936,9 @@ msgstr ""
msgid "allowed to fail"
msgstr ""
msgid "already banned from namespace"
msgstr ""
msgid "already being used for another group or project %{timebox_name}."
msgstr ""

View File

@ -20,6 +20,7 @@ require_relative '../lib/prometheus/cleanup_multiproc_dir_service'
require_relative '../lib/gitlab/metrics/prometheus'
require_relative '../lib/gitlab/metrics'
require_relative '../lib/gitlab/metrics/system'
require_relative '../lib/gitlab/metrics/memory'
require_relative '../lib/gitlab/metrics/samplers/base_sampler'
require_relative '../lib/gitlab/metrics/samplers/ruby_sampler'
require_relative '../lib/gitlab/metrics/exporter/base_exporter'

View File

@ -18,13 +18,13 @@ module QA
end
def set_allow_duplicates_disabled
expand_content :package_registry_settings_content do
within_element :package_registry_settings_content do
click_on_allow_duplicates_button if duplicates_enabled?
end
end
def set_allow_duplicates_enabled
expand_content :package_registry_settings_content do
within_element :package_registry_settings_content do
click_on_allow_duplicates_button unless duplicates_enabled?
end
end
@ -49,7 +49,7 @@ module QA
end
def has_dependency_proxy_enabled?
expand_content :dependency_proxy_settings_content do
within_element :dependency_proxy_settings_content do
within_element :dependency_proxy_setting_toggle do
toggle = find('button.gl-toggle')
toggle[:class].include?('is-checked')

View File

@ -14,7 +14,7 @@ RSpec.describe 'Unsubscribe links', :sidekiq_might_not_need_inline do
let(:mail) { ActionMailer::Base.deliveries.last }
let(:body) { Capybara::Node::Simple.new(mail.default_part_body.to_s) }
let(:header_link) { mail.header['List-Unsubscribe'].to_s[1..-2] } # Strip angle brackets
let(:body_link) { body.find_link('unsubscribe')['href'] }
let(:body_link) { body.find_link('Unsubscribe')['href'] }
before do
perform_enqueued_jobs { issue }

View File

@ -0,0 +1,55 @@
{
"type": "object",
"required": [
"name",
"provider",
"providers",
"root",
"source",
"submodules",
"version",
"versions"
],
"properties": {
"name": {
"type": "string"
},
"provider": {
"type": "string"
},
"providers": {
"type": "array",
"items": {
"type": "string"
}
},
"root": {
"type": "object",
"required": [
"dependencies"
],
"properties": {
"dependencies": {
"type": "array",
"maxItems": 0
}
}
},
"source": {
"type": "string"
},
"submodules": {
"type": "array",
"maxItems": 0
},
"version": {
"type": "string"
},
"versions": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -1,146 +0,0 @@
import Vue, { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { GlLoadingIcon } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import getPipelineQuery from '~/jobs/bridge/graphql/queries/pipeline.query.graphql';
import waitForPromises from 'helpers/wait_for_promises';
import BridgeApp from '~/jobs/bridge/app.vue';
import BridgeEmptyState from '~/jobs/bridge/components/empty_state.vue';
import BridgeSidebar from '~/jobs/bridge/components/sidebar.vue';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import {
MOCK_BUILD_ID,
MOCK_PIPELINE_IID,
MOCK_PROJECT_FULL_PATH,
mockPipelineQueryResponse,
} from './mock_data';
describe('Bridge Show Page', () => {
let wrapper;
let mockApollo;
let mockPipelineQuery;
const createComponent = (options) => {
wrapper = shallowMount(BridgeApp, {
provide: {
buildId: MOCK_BUILD_ID,
projectFullPath: MOCK_PROJECT_FULL_PATH,
pipelineIid: MOCK_PIPELINE_IID,
},
mocks: {
$apollo: {
queries: {
pipeline: {
loading: true,
},
},
},
},
...options,
});
};
const createComponentWithApollo = () => {
const handlers = [[getPipelineQuery, mockPipelineQuery]];
Vue.use(VueApollo);
mockApollo = createMockApollo(handlers);
createComponent({
apolloProvider: mockApollo,
mocks: {},
});
};
const findCiHeader = () => wrapper.findComponent(CiHeader);
const findEmptyState = () => wrapper.findComponent(BridgeEmptyState);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findSidebar = () => wrapper.findComponent(BridgeSidebar);
beforeEach(() => {
mockPipelineQuery = jest.fn();
});
afterEach(() => {
mockPipelineQuery.mockReset();
wrapper.destroy();
});
describe('while pipeline query is loading', () => {
beforeEach(() => {
createComponent();
});
it('renders loading icon', () => {
expect(findLoadingIcon().exists()).toBe(true);
});
});
describe('after pipeline query is loaded', () => {
beforeEach(async () => {
mockPipelineQuery.mockResolvedValue(mockPipelineQueryResponse);
createComponentWithApollo();
await waitForPromises();
});
it('query is called with correct variables', async () => {
expect(mockPipelineQuery).toHaveBeenCalledTimes(1);
expect(mockPipelineQuery).toHaveBeenCalledWith({
fullPath: MOCK_PROJECT_FULL_PATH,
iid: MOCK_PIPELINE_IID,
});
});
it('renders CI header state', () => {
expect(findCiHeader().exists()).toBe(true);
});
it('renders empty state', () => {
expect(findEmptyState().exists()).toBe(true);
});
it('renders sidebar', () => {
expect(findSidebar().exists()).toBe(true);
});
});
describe('sidebar expansion', () => {
beforeEach(async () => {
mockPipelineQuery.mockResolvedValue(mockPipelineQueryResponse);
createComponentWithApollo();
await waitForPromises();
});
describe('on resize', () => {
it.each`
breakpoint | isSidebarExpanded
${'xs'} | ${false}
${'sm'} | ${false}
${'md'} | ${true}
${'lg'} | ${true}
${'xl'} | ${true}
`(
'sets isSidebarExpanded to `$isSidebarExpanded` when the breakpoint is "$breakpoint"',
async ({ breakpoint, isSidebarExpanded }) => {
jest.spyOn(GlBreakpointInstance, 'getBreakpointSize').mockReturnValue(breakpoint);
window.dispatchEvent(new Event('resize'));
await nextTick();
expect(findSidebar().exists()).toBe(isSidebarExpanded);
},
);
});
it('toggles expansion on button click', async () => {
expect(findSidebar().exists()).toBe(true);
wrapper.vm.toggleSidebar();
await nextTick();
expect(findSidebar().exists()).toBe(false);
});
});
});

View File

@ -1,58 +0,0 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import BridgeEmptyState from '~/jobs/bridge/components/empty_state.vue';
import { MOCK_EMPTY_ILLUSTRATION_PATH, MOCK_PATH_TO_DOWNSTREAM } from '../mock_data';
describe('Bridge Empty State', () => {
let wrapper;
const createComponent = ({ downstreamPipelinePath }) => {
wrapper = shallowMount(BridgeEmptyState, {
provide: {
emptyStateIllustrationPath: MOCK_EMPTY_ILLUSTRATION_PATH,
},
propsData: {
downstreamPipelinePath,
},
});
};
const findSvg = () => wrapper.find('img');
const findTitle = () => wrapper.find('h1');
const findLinkBtn = () => wrapper.findComponent(GlButton);
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
beforeEach(() => {
createComponent({ downstreamPipelinePath: MOCK_PATH_TO_DOWNSTREAM });
});
it('renders illustration', () => {
expect(findSvg().exists()).toBe(true);
});
it('renders title', () => {
expect(findTitle().exists()).toBe(true);
expect(findTitle().text()).toBe(wrapper.vm.$options.i18n.title);
});
it('renders CTA button', () => {
expect(findLinkBtn().exists()).toBe(true);
expect(findLinkBtn().text()).toBe(wrapper.vm.$options.i18n.linkBtnText);
expect(findLinkBtn().attributes('href')).toBe(MOCK_PATH_TO_DOWNSTREAM);
});
});
describe('without downstream pipeline', () => {
beforeEach(() => {
createComponent({ downstreamPipelinePath: undefined });
});
it('does not render CTA button', () => {
expect(findLinkBtn().exists()).toBe(false);
});
});
});

View File

@ -1,99 +0,0 @@
import { GlButton, GlDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import BridgeSidebar from '~/jobs/bridge/components/sidebar.vue';
import CommitBlock from '~/jobs/components/commit_block.vue';
import { mockCommit, mockJob } from '../mock_data';
describe('Bridge Sidebar', () => {
let wrapper;
const MockHeaderEl = {
getBoundingClientRect() {
return {
bottom: '40',
};
},
};
const createComponent = ({ featureFlag } = {}) => {
wrapper = shallowMount(BridgeSidebar, {
provide: {
glFeatures: {
triggerJobRetryAction: featureFlag,
},
},
propsData: {
bridgeJob: mockJob,
commit: mockCommit,
},
});
};
const findJobTitle = () => wrapper.find('h4');
const findCommitBlock = () => wrapper.findComponent(CommitBlock);
const findRetryDropdown = () => wrapper.find(GlDropdown);
const findToggleBtn = () => wrapper.findComponent(GlButton);
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
beforeEach(() => {
createComponent();
});
it('renders job name', () => {
expect(findJobTitle().text()).toBe(mockJob.name);
});
it('renders commit information', () => {
expect(findCommitBlock().exists()).toBe(true);
});
});
describe('styles', () => {
beforeEach(async () => {
jest.spyOn(document, 'querySelector').mockReturnValue(MockHeaderEl);
createComponent();
});
it('calculates root styles correctly', () => {
expect(wrapper.attributes('style')).toBe('width: 290px; top: 40px;');
});
});
describe('sidebar expansion', () => {
beforeEach(() => {
createComponent();
});
it('emits toggle sidebar event on button click', async () => {
expect(wrapper.emitted('toggleSidebar')).toBe(undefined);
findToggleBtn().vm.$emit('click');
expect(wrapper.emitted('toggleSidebar')).toHaveLength(1);
});
});
describe('retry action', () => {
describe('when feature flag is ON', () => {
beforeEach(() => {
createComponent({ featureFlag: true });
});
it('renders retry dropdown', () => {
expect(findRetryDropdown().exists()).toBe(true);
});
});
describe('when feature flag is OFF', () => {
it('does not render retry dropdown', () => {
createComponent({ featureFlag: false });
expect(findRetryDropdown().exists()).toBe(false);
});
});
});
});

View File

@ -1,102 +0,0 @@
export const MOCK_EMPTY_ILLUSTRATION_PATH = '/path/to/svg';
export const MOCK_PATH_TO_DOWNSTREAM = '/path/to/downstream/pipeline';
export const MOCK_BUILD_ID = '1331';
export const MOCK_PIPELINE_IID = '174';
export const MOCK_PROJECT_FULL_PATH = '/root/project/';
export const MOCK_SHA = '38f3d89147765427a7ce58be28cd76d14efa682a';
export const mockCommit = {
id: `gid://gitlab/CommitPresenter/${MOCK_SHA}`,
shortId: '38f3d891',
title: 'Update .gitlab-ci.yml file',
webPath: `/root/project/-/commit/${MOCK_SHA}`,
__typename: 'Commit',
};
export const mockJob = {
createdAt: '2021-12-10T09:05:45Z',
id: 'gid://gitlab/Ci::Build/1331',
name: 'triggerJobName',
scheduledAt: null,
startedAt: '2021-12-10T09:13:43Z',
status: 'SUCCESS',
triggered: null,
detailedStatus: {
id: '1',
detailsPath: '/root/project/-/jobs/1331',
icon: 'status_success',
group: 'success',
text: 'passed',
tooltip: 'passed',
__typename: 'DetailedStatus',
},
downstreamPipeline: {
id: '1',
path: '/root/project/-/pipelines/175',
},
stage: {
id: '1',
name: 'build',
__typename: 'CiStage',
},
__typename: 'CiJob',
};
export const mockUser = {
id: 'gid://gitlab/User/1',
avatarUrl: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
name: 'Administrator',
username: 'root',
webPath: '/root',
webUrl: 'http://gdk.test:3000/root',
status: {
message: 'making great things',
__typename: 'UserStatus',
},
__typename: 'UserCore',
};
export const mockStage = {
id: '1',
name: 'build',
jobs: {
nodes: [mockJob],
__typename: 'CiJobConnection',
},
__typename: 'CiStage',
};
export const mockPipelineQueryResponse = {
data: {
project: {
id: '1',
pipeline: {
commit: mockCommit,
id: 'gid://gitlab/Ci::Pipeline/174',
iid: '88',
path: '/root/project/-/pipelines/174',
sha: MOCK_SHA,
ref: 'main',
refPath: 'path/to/ref',
user: mockUser,
detailedStatus: {
id: '1',
icon: 'status_failed',
group: 'failed',
__typename: 'DetailedStatus',
},
stages: {
edges: [
{
node: mockStage,
__typename: 'CiStageEdge',
},
],
__typename: 'CiStageConnection',
},
__typename: 'Pipeline',
},
__typename: 'Project',
},
},
};

View File

@ -14,6 +14,7 @@ jest.mock('~/lib/utils/url_utility');
const DeploymentFrequencyChartsStub = { name: 'DeploymentFrequencyCharts', render: () => {} };
const LeadTimeChartsStub = { name: 'LeadTimeCharts', render: () => {} };
const TimeToRestoreServiceChartsStub = { name: 'TimeToRestoreServiceCharts', render: () => {} };
const ChangeFailureRateChartsStub = { name: 'ChangeFailureRateCharts', render: () => {} };
const ProjectQualitySummaryStub = { name: 'ProjectQualitySummary', render: () => {} };
describe('ProjectsPipelinesChartsApp', () => {
@ -33,6 +34,7 @@ describe('ProjectsPipelinesChartsApp', () => {
DeploymentFrequencyCharts: DeploymentFrequencyChartsStub,
LeadTimeCharts: LeadTimeChartsStub,
TimeToRestoreServiceCharts: TimeToRestoreServiceChartsStub,
ChangeFailureRateCharts: ChangeFailureRateChartsStub,
ProjectQualitySummary: ProjectQualitySummaryStub,
},
},
@ -50,6 +52,7 @@ describe('ProjectsPipelinesChartsApp', () => {
const findGlTabAtIndex = (index) => findAllGlTabs().at(index);
const findLeadTimeCharts = () => wrapper.find(LeadTimeChartsStub);
const findTimeToRestoreServiceCharts = () => wrapper.find(TimeToRestoreServiceChartsStub);
const findChangeFailureRateCharts = () => wrapper.find(ChangeFailureRateChartsStub);
const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub);
const findPipelineCharts = () => wrapper.find(PipelineCharts);
const findProjectQualitySummary = () => wrapper.find(ProjectQualitySummaryStub);
@ -59,58 +62,49 @@ describe('ProjectsPipelinesChartsApp', () => {
createComponent();
});
it('renders tabs', () => {
expect(findGlTabs().exists()).toBe(true);
expect(findGlTabAtIndex(0).attributes('title')).toBe('Pipelines');
expect(findGlTabAtIndex(1).attributes('title')).toBe('Deployment frequency');
expect(findGlTabAtIndex(2).attributes('title')).toBe('Lead time');
expect(findGlTabAtIndex(3).attributes('title')).toBe('Time to restore service');
});
it('renders the pipeline charts', () => {
expect(findPipelineCharts().exists()).toBe(true);
});
it('renders the deployment frequency charts', () => {
expect(findDeploymentFrequencyCharts().exists()).toBe(true);
});
it('renders the lead time charts', () => {
expect(findLeadTimeCharts().exists()).toBe(true);
});
it('renders the time to restore service charts', () => {
expect(findTimeToRestoreServiceCharts().exists()).toBe(true);
});
it('renders the project quality summary', () => {
expect(findProjectQualitySummary().exists()).toBe(true);
});
it('sets the tab and url when a tab is clicked', async () => {
let chartsPath;
setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`);
mergeUrlParams.mockImplementation(({ chart }, path) => {
expect(chart).toBe('deployment-frequency');
expect(path).toBe(window.location.pathname);
chartsPath = `${path}?chart=${chart}`;
return chartsPath;
describe.each`
title | finderFn | index
${'Pipelines'} | ${findPipelineCharts} | ${0}
${'Deployment frequency'} | ${findDeploymentFrequencyCharts} | ${1}
${'Lead time'} | ${findLeadTimeCharts} | ${2}
${'Time to restore service'} | ${findTimeToRestoreServiceCharts} | ${3}
${'Change failure rate'} | ${findChangeFailureRateCharts} | ${4}
${'Project quality'} | ${findProjectQualitySummary} | ${5}
`('Tabs', ({ title, finderFn, index }) => {
it(`renders tab with a title ${title} at index ${index}`, () => {
expect(findGlTabAtIndex(index).attributes('title')).toBe(title);
});
updateHistory.mockImplementation(({ url }) => {
expect(url).toBe(chartsPath);
it(`renders the ${title} chart`, () => {
expect(finderFn().exists()).toBe(true);
});
const tabs = findGlTabs();
expect(tabs.attributes('value')).toBe('0');
it(`updates the current tab and url when the ${title} tab is clicked`, async () => {
let chartsPath;
const tabName = title.toLowerCase().replace(/\s/g, '-');
tabs.vm.$emit('input', 1);
setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`);
await nextTick();
mergeUrlParams.mockImplementation(({ chart }, path) => {
expect(chart).toBe(tabName);
expect(path).toBe(window.location.pathname);
chartsPath = `${path}?chart=${chart}`;
return chartsPath;
});
expect(tabs.attributes('value')).toBe('1');
updateHistory.mockImplementation(({ url }) => {
expect(url).toBe(chartsPath);
});
const tabs = findGlTabs();
expect(tabs.attributes('value')).toBe('0');
tabs.vm.$emit('input', index);
await nextTick();
expect(tabs.attributes('value')).toBe(index.toString());
});
});
it('should not try to push history if the tab does not change', async () => {
@ -151,6 +145,7 @@ describe('ProjectsPipelinesChartsApp', () => {
describe('when provided with a query param', () => {
it.each`
chart | tab
${'change-failure-rate'} | ${'4'}
${'time-to-restore-service'} | ${'3'}
${'lead-time'} | ${'2'}
${'deployment-frequency'} | ${'1'}

View File

@ -77,7 +77,7 @@ RSpec.describe EmailsHelper do
end
describe 'notification_reason_text' do
subject { helper.notification_reason_text(reason_code) }
subject { helper.notification_reason_text(reason: reason_code) }
using RSpec::Parameterized::TableSyntax

View File

@ -7,13 +7,12 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
let(:builder) { described_class.new(merge_request) }
describe '#build' do
let(:data) { builder.build }
describe '.safe_hook_attributes' do
let(:safe_attribute_keys) { described_class.safe_hook_attributes }
it 'includes safe attribute' do
%w[
expected_safe_attribute_keys = %i[
assignee_id
assignee_ids
author_id
blocking_discussions_resolved
created_at
@ -32,17 +31,21 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
milestone_id
source_branch
source_project_id
state
state_id
target_branch
target_project_id
time_estimate
title
updated_at
updated_by_id
].each do |key|
expect(data).to include(key)
end
].freeze
expect(safe_attribute_keys).to match_array(expected_safe_attribute_keys)
end
end
describe '#build' do
let(:data) { builder.build }
%i[source target].each do |key|
describe "#{key} key" do
@ -52,17 +55,30 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
end
end
it 'includes safe attributes' do
expect(data).to include(*described_class.safe_hook_attributes)
end
it 'includes additional attrs' do
expect(data).to include(:source)
expect(data).to include(:target)
expect(data).to include(:last_commit)
expect(data).to include(:work_in_progress)
expect(data).to include(:total_time_spent)
expect(data).to include(:time_change)
expect(data).to include(:human_time_estimate)
expect(data).to include(:human_total_time_spent)
expect(data).to include(:human_time_change)
expect(data).to include(:labels)
expected_additional_attributes = %w[
description
url
last_commit
work_in_progress
total_time_spent
time_change
human_total_time_spent
human_time_change
human_time_estimate
assignee_ids
assignee_id
labels
state
blocking_discussions_resolved
first_contribution
].freeze
expect(data).to include(*expected_additional_attributes)
end
context 'when the MR has an image in the description' do

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Gitlab::Metrics::Memory do
describe '.gc_heap_fragmentation' do
subject(:call) do
described_class.gc_heap_fragmentation(
heap_live_slots: gc_stat_heap_live_slots,
heap_eden_pages: gc_stat_heap_eden_pages
)
end
context 'when the Ruby heap is perfectly utilized' do
# All objects are located in a single heap page.
let(:gc_stat_heap_live_slots) { described_class::HEAP_SLOTS_PER_PAGE }
let(:gc_stat_heap_eden_pages) { 1 }
it { is_expected.to eq(0) }
end
context 'when the Ruby heap is greatly fragmented' do
# There is one object per heap page.
let(:gc_stat_heap_live_slots) { described_class::HEAP_SLOTS_PER_PAGE }
let(:gc_stat_heap_eden_pages) { described_class::HEAP_SLOTS_PER_PAGE }
# The heap can never be "perfectly fragmented" because that would require
# zero objects per page.
it { is_expected.to be > 0.99 }
end
context 'when the Ruby heap is semi-fragmented' do
# All objects are spread over two pages i.e. each page is 50% utilized.
let(:gc_stat_heap_live_slots) { described_class::HEAP_SLOTS_PER_PAGE }
let(:gc_stat_heap_eden_pages) { 2 }
it { is_expected.to eq(0.5) }
end
end
end

View File

@ -125,5 +125,11 @@ RSpec.describe Gitlab::Metrics::Samplers::RubySampler do
sampler.sample
end
it 'adds a heap fragmentation metric' do
expect(sampler.metrics[:heap_fragmentation]).to receive(:set).with({}, anything)
sampler.sample
end
end
end

View File

@ -138,5 +138,20 @@ RSpec.describe UnnestedInFilters::Rewriter do
end
end
end
describe 'logging' do
subject(:load_reload) { rewriter.rewrite }
before do
allow(::Gitlab::AppLogger).to receive(:info)
end
it 'logs the call' do
load_reload
expect(::Gitlab::AppLogger)
.to have_received(:info).with(message: 'Query is being rewritten by `UnnestedInFilters`', model: 'User')
end
end
end
end

View File

@ -64,7 +64,7 @@ RSpec.describe Emails::AdminNotification do
end
it 'includes the email reason' do
is_expected.to have_body_text "You're receiving this email because of your account on localhost"
is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
end
context 'when scoped to a group' do

View File

@ -151,7 +151,7 @@ RSpec.describe Emails::Profile do
end
it 'includes the email reason' do
is_expected.to have_body_text /You're receiving this email because of your account on localhost/
is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
end
end
end
@ -187,7 +187,7 @@ RSpec.describe Emails::Profile do
end
it 'includes the email reason' do
is_expected.to have_body_text /You're receiving this email because of your account on localhost/
is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
end
context 'with User does not exist' do
@ -222,7 +222,7 @@ RSpec.describe Emails::Profile do
end
it 'includes the email reason' do
is_expected.to have_body_text /You're receiving this email because of your account on localhost/
is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
end
end
@ -266,7 +266,7 @@ RSpec.describe Emails::Profile do
end
shared_examples 'includes the email reason' do
it { is_expected.to have_body_text /You're receiving this email because of your account on localhost/ }
it { is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>} }
end
shared_examples 'valid use case' do

View File

@ -7,6 +7,7 @@ RSpec.describe Notify do
include EmailSpec::Helpers
include EmailSpec::Matchers
include EmailHelpers
include EmailsHelper
include RepoHelpers
include MembersHelper
@ -396,7 +397,7 @@ RSpec.describe Notify do
end
end
context 'when sent with a reason' do
context 'when sent with a reason', type: :helper do
subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, NotificationReason::ASSIGNED) }
it_behaves_like 'appearance header and footer enabled'
@ -407,15 +408,15 @@ RSpec.describe Notify do
end
it 'includes the reason in the footer' do
text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::ASSIGNED)
text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(reason: NotificationReason::ASSIGNED, format: :html)
is_expected.to have_body_text(text)
new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, NotificationReason::MENTIONED)
text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::MENTIONED)
text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(reason: NotificationReason::MENTIONED, format: :html)
expect(new_subject).to have_body_text(text)
new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, nil)
text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(nil)
text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(format: :html)
expect(new_subject).to have_body_text(text)
end
end

View File

@ -97,10 +97,8 @@ RSpec.describe MergeRequestDiffFile do
file.utf8_diff
end
end
describe '#diff_export' do
context 'when diff is externally stored' do
context 'externally stored diff caching' do
let(:test_dir) { 'tmp/tests/external-diffs' }
around do |example|
@ -121,7 +119,7 @@ RSpec.describe MergeRequestDiffFile do
it 'caches external diffs' do
expect(file.merge_request_diff).to receive(:cache_external_diff).and_call_original
expect(file.diff_export).to eq(file.utf8_diff)
expect(file.utf8_diff).to eq(file.diff)
end
end
@ -133,7 +131,7 @@ RSpec.describe MergeRequestDiffFile do
expect(file_stub).to receive(:seek).with(file.external_diff_offset)
expect(file_stub).to receive(:read).with(file.external_diff_size)
file.diff_export
file.utf8_diff
end
end
@ -151,28 +149,28 @@ RSpec.describe MergeRequestDiffFile do
end
it 'unpacks from base 64' do
expect(file.diff_export).to eq(unpacked)
expect(file.utf8_diff).to eq(unpacked)
end
context 'invalid base64' do
let(:packed) { '---/dev/null' }
it 'returns the raw diff' do
expect(file.diff_export).to eq(packed)
expect(file.utf8_diff).to eq(packed)
end
end
end
context 'when the diff is not marked as binary' do
it 'returns the raw diff' do
expect(file.diff_export).to eq(packed)
expect(file.utf8_diff).to eq(packed)
end
end
end
context 'when content responds to #encoding' do
it 'encodes content to utf8 encoding' do
expect(file.diff_export.encoding).to eq(Encoding::UTF_8)
expect(file.utf8_diff.encoding).to eq(Encoding::UTF_8)
end
end
@ -180,35 +178,46 @@ RSpec.describe MergeRequestDiffFile do
it 'returns an empty string' do
allow(file.merge_request_diff).to receive(:cached_external_diff).and_return(nil)
expect(file.diff_export).to eq('')
expect(file.utf8_diff).to eq('')
end
end
context 'when exception is raised' do
it 'falls back to #utf8_diff' do
allow(file).to receive(:binary?).and_raise(StandardError)
expect(file).to receive(:utf8_diff)
it 'falls back to #diff' do
allow(file).to receive(:binary?).and_raise(StandardError, 'Error!')
expect(file).to receive(:diff)
expect(Gitlab::AppLogger)
.to receive(:warn)
.with(
a_hash_including(
:message => 'Cached external diff export failed',
:merge_request_diff_file_id => file.id,
:merge_request_diff_id => file.merge_request_diff.id,
'exception.class' => 'StandardError',
'exception.message' => 'Error!'
)
)
file.diff_export
file.utf8_diff
end
end
end
context 'when externally_stored_diffs_caching_export feature flag is disabled' do
it 'calls #utf8_diff' do
it 'calls #diff' do
stub_feature_flags(externally_stored_diffs_caching_export: false)
expect(file).to receive(:utf8_diff)
expect(file).to receive(:diff)
file.diff_export
file.utf8_diff
end
end
context 'when diff is not stored externally' do
it 'calls #utf8_diff' do
expect(file).to receive(:utf8_diff)
it 'calls #diff' do
expect(file).to receive(:diff)
file.diff_export
file.utf8_diff
end
end
end

View File

@ -171,6 +171,75 @@ RSpec.describe API::Terraform::Modules::V1::Packages do
end
end
describe 'GET /api/v4/packages/terraform/modules/v1/:module_namespace/:module_name/:module_system/:module_version' do
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/#{package.version}") }
let(:headers) { {} }
subject { get(url, headers: headers) }
context 'not found' do
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/2.0.0") }
let(:headers) { { 'Authorization' => "Bearer #{tokens[:job_token]}" } }
subject { get(url, headers: headers) }
it 'returns not found when the specified version is not present in the registry' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'with valid namespace' do
where(:visibility, :user_role, :member, :token_type, :shared_examples_name, :expected_status) do
:public | :developer | true | :personal_access_token | 'returns terraform module version' | :success
:public | :guest | true | :personal_access_token | 'returns terraform module version' | :success
:public | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :developer | false | :personal_access_token | 'returns terraform module version' | :success
:public | :guest | false | :personal_access_token | 'returns terraform module version' | :success
:public | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :anonymous | false | nil | 'returns terraform module version' | :success
:private | :developer | true | :personal_access_token | 'returns terraform module version' | :success
:private | :guest | true | :personal_access_token | 'rejects terraform module packages access' | :forbidden
:private | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :developer | false | :personal_access_token | 'rejects terraform module packages access' | :forbidden
:private | :guest | false | :personal_access_token | 'rejects terraform module packages access' | :forbidden
:private | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :anonymous | false | nil | 'rejects terraform module packages access' | :unauthorized
:public | :developer | true | :job_token | 'returns terraform module version' | :success
:public | :guest | true | :job_token | 'returns terraform module version' | :success
:public | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :developer | false | :job_token | 'returns terraform module version' | :success
:public | :guest | false | :job_token | 'returns terraform module version' | :success
:public | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :developer | true | :job_token | 'returns terraform module version' | :success
:private | :guest | true | :job_token | 'rejects terraform module packages access' | :forbidden
:private | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :developer | false | :job_token | 'rejects terraform module packages access' | :forbidden
:private | :guest | false | :job_token | 'rejects terraform module packages access' | :forbidden
:private | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
end
with_them do
let(:headers) { user_role == :anonymous ? {} : { 'Authorization' => "Bearer #{token}" } }
before do
group.update!(visibility: visibility.to_s)
project.update!(visibility: visibility.to_s)
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
end
describe 'GET /api/v4/packages/terraform/modules/v1/:module_namespace/:module_name/:module_system/:module_version/download' do
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/#{package.version}/download") }
let(:headers) { {} }

View File

@ -269,6 +269,17 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns items not assigned to that milestone' do
expect(items).to contain_exactly(item2, item3, item4, item5)
end
context 'with multiple milestones' do
let(:milestone2) { create(:milestone, project: project2) }
let(:params) { { not: { milestone_title: [milestone.title, milestone2.title] } } }
it 'returns items not assigned to both milestones' do
item2.update!(milestone: milestone2)
expect(items).to contain_exactly(item3, item4, item5)
end
end
end
context 'filtering by group milestone' do

View File

@ -102,6 +102,22 @@ RSpec.shared_examples 'returns terraform module packages' do |user_type, status,
end
end
RSpec.shared_examples 'returns terraform module version' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
it 'returning a valid response' do
subject
expect(json_response).to match_schema('public_api/v4/packages/terraform/modules/v1/single_version')
end
end
end
RSpec.shared_examples 'returns no terraform module packages' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'projects/commits/_commit.html.haml' do
let(:template) { 'projects/commits/commit.html.haml' }
let(:template) { 'projects/commits/commit' }
let(:project) { create(:project, :repository) }
let(:commit) { project.repository.commit(ref) }

View File

@ -27,7 +27,6 @@ RSpec.describe 'projects/jobs/show' do
it 'shows job vue app' do
expect(rendered).to have_css('#js-job-page')
expect(rendered).not_to have_css('#js-bridge-page')
end
context 'when job is running' do
@ -42,18 +41,4 @@ RSpec.describe 'projects/jobs/show' do
end
end
end
context 'when showing a bridge job' do
let(:bridge) { create(:ci_bridge, status: :pending) }
before do
assign(:build, bridge)
render
end
it 'shows bridge vue app' do
expect(rendered).to have_css('#js-bridge-page')
expect(rendered).not_to have_css('#js-job-page')
end
end
end

View File

@ -25,7 +25,7 @@ require (
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
github.com/sirupsen/logrus v1.8.1
github.com/smartystreets/goconvey v1.7.2
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.8.0
gitlab.com/gitlab-org/gitaly/v15 v15.1.0
gitlab.com/gitlab-org/golang-archive-zip v0.1.1
gitlab.com/gitlab-org/labkit v1.15.0
@ -113,5 +113,5 @@ require (
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de // indirect
gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -1044,16 +1044,19 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
@ -1815,8 +1818,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=