Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
526f1e9859
commit
8280fa786e
|
@ -1 +1 @@
|
||||||
ac72695adc90343b7255869818376f505bde8315
|
6944c95243e2b6ed8f783ae903cb9b03ad50e0f9
|
||||||
|
|
|
@ -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>
|
|
|
@ -1 +0,0 @@
|
||||||
export const SIDEBAR_COLLAPSE_BREAKPOINTS = ['xs', 'sm'];
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,4 @@
|
||||||
import Vue from 'vue';
|
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 JobApp from './components/job_app.vue';
|
||||||
import createStore from './store';
|
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 () => {
|
export default () => {
|
||||||
const jobElement = document.getElementById('js-job-page');
|
const jobElement = document.getElementById('js-job-page');
|
||||||
const bridgeElement = document.getElementById('js-bridge-page');
|
initializeJobPage(jobElement);
|
||||||
|
|
||||||
if (jobElement) {
|
|
||||||
initializeJobPage(jobElement);
|
|
||||||
} else {
|
|
||||||
initializeBridgePage(bridgeElement);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,8 @@ export default {
|
||||||
LeadTimeCharts: () => import('ee_component/dora/components/lead_time_charts.vue'),
|
LeadTimeCharts: () => import('ee_component/dora/components/lead_time_charts.vue'),
|
||||||
TimeToRestoreServiceCharts: () =>
|
TimeToRestoreServiceCharts: () =>
|
||||||
import('ee_component/dora/components/time_to_restore_service_charts.vue'),
|
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'),
|
ProjectQualitySummary: () => import('ee_component/project_quality_summary/app.vue'),
|
||||||
},
|
},
|
||||||
piplelinesTabEvent: 'p_analytics_ci_cd_pipelines',
|
piplelinesTabEvent: 'p_analytics_ci_cd_pipelines',
|
||||||
|
@ -40,7 +42,12 @@ export default {
|
||||||
const chartsToShow = ['pipelines'];
|
const chartsToShow = ['pipelines'];
|
||||||
|
|
||||||
if (this.shouldRenderDoraCharts) {
|
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) {
|
if (this.shouldRenderQualitySummary) {
|
||||||
|
@ -105,6 +112,12 @@ export default {
|
||||||
>
|
>
|
||||||
<time-to-restore-service-charts />
|
<time-to-restore-service-charts />
|
||||||
</gl-tab>
|
</gl-tab>
|
||||||
|
<gl-tab
|
||||||
|
:title="s__('DORA4Metrics|Change failure rate')"
|
||||||
|
data-testid="change-failure-rate-tab"
|
||||||
|
>
|
||||||
|
<change-failure-rate-charts />
|
||||||
|
</gl-tab>
|
||||||
</template>
|
</template>
|
||||||
<gl-tab v-if="shouldRenderQualitySummary" :title="s__('QualitySummary|Project quality')">
|
<gl-tab v-if="shouldRenderQualitySummary" :title="s__('QualitySummary|Project quality')">
|
||||||
<project-quality-summary />
|
<project-quality-summary />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<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 gitlabLogo from '@gitlab/svgs/dist/illustrations/gitlab_logo.svg';
|
||||||
import { s__, __ } from '~/locale';
|
import { s__, __ } from '~/locale';
|
||||||
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
|
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
|
||||||
|
@ -29,6 +29,7 @@ export default {
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
safeHtml: GlSafeHtmlDirective,
|
safeHtml: GlSafeHtmlDirective,
|
||||||
|
tooltip: GlTooltipDirective,
|
||||||
},
|
},
|
||||||
mixins: [Tracking.mixin()],
|
mixins: [Tracking.mixin()],
|
||||||
i18n: {
|
i18n: {
|
||||||
|
@ -92,7 +93,7 @@ export default {
|
||||||
>
|
>
|
||||||
<template #default="{ dismiss }">
|
<template #default="{ dismiss }">
|
||||||
<aside
|
<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"
|
:aria-label="$options.i18n.survey"
|
||||||
>
|
>
|
||||||
<transition name="survey-slide-up">
|
<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"
|
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
|
<gl-button
|
||||||
|
v-tooltip="$options.i18n.close"
|
||||||
:aria-label="$options.i18n.close"
|
:aria-label="$options.i18n.close"
|
||||||
variant="default"
|
variant="default"
|
||||||
category="tertiary"
|
category="tertiary"
|
||||||
|
|
|
@ -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 {
|
.mr-experience-survey-body {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
|
|
@ -22,10 +22,6 @@ class Projects::JobsController < Projects::ApplicationController
|
||||||
before_action :push_jobs_table_vue_search, only: [:index]
|
before_action :push_jobs_table_vue_search, only: [:index]
|
||||||
before_action :reject_if_build_artifacts_size_refreshing!, only: [:erase]
|
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'
|
layout 'project'
|
||||||
|
|
||||||
feature_category :continuous_integration
|
feature_category :continuous_integration
|
||||||
|
|
|
@ -436,7 +436,7 @@ class IssuableFinder
|
||||||
elsif not_params.filter_by_started_milestone?
|
elsif not_params.filter_by_started_milestone?
|
||||||
items.joins(:milestone).merge(Milestone.not_started)
|
items.joins(:milestone).merge(Milestone.not_started)
|
||||||
else
|
else
|
||||||
items.without_particular_milestone(not_params[:milestone_title])
|
items.without_particular_milestones(not_params[:milestone_title])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
# rubocop: enable CodeReuse/ActiveRecord
|
||||||
|
|
|
@ -116,19 +116,16 @@ module EmailsHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# "You are receiving this email because #{reason} on #{gitlab_host}."
|
# "You are receiving this email because ... on #{host}. ..."
|
||||||
def notification_reason_text(reason)
|
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)
|
||||||
gitlab_host = Gitlab.config.gitlab.host
|
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)
|
||||||
case reason
|
elsif !reason && manage_label_subscriptions_url && show_help_link
|
||||||
when NotificationReason::OWN_ACTIVITY
|
notification_reason_text_with_manage_label_subscriptions_and_help_links(manage_label_subscriptions_url: manage_label_subscriptions_url, format: format)
|
||||||
_("You're receiving this email because of your activity on %{host}.") % { host: gitlab_host }
|
elsif show_manage_notifications_link && show_help_link
|
||||||
when NotificationReason::ASSIGNED
|
notification_reason_text_with_manage_notifications_and_help_links(reason: reason, format: format)
|
||||||
_("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 }
|
|
||||||
else
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -259,9 +256,7 @@ module EmailsHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def instance_access_request_text(user, format: nil)
|
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}:').html_safe % { username: sanitize_name(user.name), host: gitlab_host_link(format) }
|
||||||
|
|
||||||
_('%{username} has asked for a GitLab account on your instance %{host}:') % { username: sanitize_name(user.name), host: gitlab_host }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def instance_access_request_link(user, format: nil)
|
def instance_access_request_link(user, format: nil)
|
||||||
|
@ -325,6 +320,75 @@ module EmailsHelper
|
||||||
def email_header_and_footer_enabled?
|
def email_header_and_footer_enabled?
|
||||||
current_appearance&.email_header_and_footer_enabled?
|
current_appearance&.email_header_and_footer_enabled?
|
||||||
end
|
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 · %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} · %{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 · %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} · %{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 · %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} · %{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 · %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} · %{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} · %{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} · %{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} · %{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
|
end
|
||||||
|
|
||||||
EmailsHelper.prepend_mod_with('EmailsHelper')
|
EmailsHelper.prepend_mod_with('EmailsHelper')
|
||||||
|
|
|
@ -16,7 +16,7 @@ module Milestoneable
|
||||||
|
|
||||||
scope :any_milestone, -> { where.not(milestone_id: nil) }
|
scope :any_milestone, -> { where.not(milestone_id: nil) }
|
||||||
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
|
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 :any_release, -> { joins_milestone_releases }
|
||||||
scope :with_release, -> (tag, project_id) { joins_milestone_releases.where( milestones: { releases: { tag: tag, project_id: project_id } } ) }
|
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 } }) }
|
scope :without_particular_release, -> (tag, project_id) { joins_milestone_releases.where.not(milestones: { releases: { tag: tag, project_id: project_id } }) }
|
||||||
|
|
|
@ -15,7 +15,12 @@ class MergeRequestDiffFile < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def utf8_diff
|
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?
|
return '' if fetched_diff.blank?
|
||||||
|
|
||||||
|
@ -46,14 +51,13 @@ class MergeRequestDiffFile < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
# This method is meant to be used during Project Export.
|
# 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
|
# difference of caching externally stored diffs on local disk in
|
||||||
# temp storage location in order to improve diff export performance.
|
# temp storage location in order to improve diff export performance.
|
||||||
def diff_export
|
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|
|
content = merge_request_diff.cached_external_diff do |file|
|
||||||
file.seek(external_diff_offset)
|
file.seek(external_diff_offset)
|
||||||
|
|
||||||
|
@ -69,14 +73,17 @@ class MergeRequestDiffFile < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if content.respond_to?(:encoding)
|
|
||||||
content = encode_utf8(content)
|
|
||||||
end
|
|
||||||
|
|
||||||
return '' if content.blank?
|
|
||||||
|
|
||||||
content
|
content
|
||||||
rescue StandardError
|
rescue StandardError => e
|
||||||
utf8_diff
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -1,7 +1,7 @@
|
||||||
- expanded = integration_expanded?('eks_')
|
- expanded = integration_expanded?('eks_')
|
||||||
%section.settings.as-eks.no-animate#js-eks-settings{ class: ('expanded' if expanded) }
|
%section.settings.as-eks.no-animate#js-eks-settings{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Amazon EKS')
|
= _('Amazon EKS')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
%section.settings.as-external-auth.no-animate#js-external-auth-settings{ class: ('expanded' if expanded) }
|
%section.settings.as-external-auth.no-animate#js-external-auth-settings{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= s_('ExternalAuthorization|External authorization')
|
= s_('ExternalAuthorization|External authorization')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
%section.settings.no-animate#js-floc-settings{ class: ('expanded' if expanded) }
|
%section.settings.no-animate#js-floc-settings{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= s_('FloC|Federated Learning of Cohorts')
|
= s_('FloC|Federated Learning of Cohorts')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
%section.settings.no-animate#js-gitpod-settings{ class: ('expanded' if expanded) }
|
%section.settings.no-animate#js-gitpod-settings{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Gitpod')
|
= _('Gitpod')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
%section.settings.no-animate#js-jira_connect-settings{ class: ('expanded' if expanded) }
|
%section.settings.no-animate#js-jira_connect-settings{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= s_('JiraConnect|GitLab for Jira App')
|
= s_('JiraConnect|GitLab for Jira App')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- expanded = integration_expanded?('kroki_')
|
- expanded = integration_expanded?('kroki_')
|
||||||
%section.settings.as-kroki.no-animate#js-kroki-settings{ class: ('expanded' if expanded) }
|
%section.settings.as-kroki.no-animate#js-kroki-settings{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Kroki')
|
= _('Kroki')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- expanded = integration_expanded?('mailgun_')
|
- expanded = integration_expanded?('mailgun_')
|
||||||
%section.settings.as-mailgun.no-animate#js-mailgun-settings{ class: ('expanded' if expanded) }
|
%section.settings.as-mailgun.no-animate#js-mailgun-settings{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Mailgun')
|
= _('Mailgun')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- expanded = integration_expanded?('plantuml_')
|
- expanded = integration_expanded?('plantuml_')
|
||||||
%section.settings.as-plantuml.no-animate#js-plantuml-settings{ class: ('expanded' if expanded) }
|
%section.settings.as-plantuml.no-animate#js-plantuml-settings{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('PlantUML')
|
= _('PlantUML')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- expanded = integration_expanded?('snowplow_')
|
- expanded = integration_expanded?('snowplow_')
|
||||||
%section.settings.as-snowplow.no-animate#js-snowplow-settings{ class: ('expanded' if expanded), data: { qa_selector: 'snowplow_settings_content' } }
|
%section.settings.as-snowplow.no-animate#js-snowplow-settings{ class: ('expanded' if expanded), data: { qa_selector: 'snowplow_settings_content' } }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Snowplow')
|
= _('Snowplow')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
%section.settings.as-sourcegraph.no-animate#js-sourcegraph-settings{ class: ('expanded' if expanded) }
|
%section.settings.as-sourcegraph.no-animate#js-sourcegraph-settings{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Sourcegraph')
|
= _('Sourcegraph')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- expanded = integration_expanded?('hide_third_party_')
|
- expanded = integration_expanded?('hide_third_party_')
|
||||||
%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
|
%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Customer experience improvement and third-party offers')
|
= _('Customer experience improvement and third-party offers')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
%section.settings.as-visibility-access.no-animate#js-visibility-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-visibility-access.no-animate#js-visibility-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Visibility and access controls')
|
= _('Visibility and access controls')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= 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' } }
|
%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
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Account and limit')
|
= _('Account and limit')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= 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?) }
|
%section.settings.as-diff-limits.no-animate#js-merge-request-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Diff limits')
|
= _('Diff limits')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= 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' } }
|
%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
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Sign-up restrictions')
|
= _('Sign-up restrictions')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
%section.settings.as-signin.no-animate#js-signin-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-signin.no-animate#js-signin-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Sign-in restrictions')
|
= _('Sign-in restrictions')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
|
|
||||||
%section.settings.as-terms.no-animate#js-terms-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-terms.no-animate#js-terms-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Terms of Service and Privacy Policy')
|
= _('Terms of Service and Privacy Policy')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
|
|
||||||
%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Web terminal')
|
= _('Web terminal')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
|
|
||||||
%section.settings.no-animate#js-web-ide-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.no-animate#js-web-ide-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Web IDE')
|
= _('Web IDE')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
%td
|
%td
|
||||||
%img.footer-logo{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png') }
|
%img.footer-logo{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png') }
|
||||||
%div
|
%div
|
||||||
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, class: 'mng-notif-link')
|
= notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)
|
||||||
- help_link = link_to(_("Help"), help_url, class: 'help-link')
|
|
||||||
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} · %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
|
|
||||||
|
|
||||||
= render 'layouts/mailer'
|
= render 'layouts/mailer'
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<%= yield -%>
|
<%= yield -%>
|
||||||
|
|
||||||
-- <%# signature marker %>
|
-- <%# 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' %>
|
<%= render_if_exists 'layouts/mailer/additional_text' %>
|
||||||
|
|
||||||
<%= text_footer_message %>
|
<%= text_footer_message %>
|
||||||
|
|
|
@ -26,16 +26,7 @@
|
||||||
- else
|
- else
|
||||||
#{link_to _("View it on GitLab"), @target_url}.
|
#{link_to _("View it on GitLab"), @target_url}.
|
||||||
%br
|
%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: @reason, show_manage_notifications_link: !@labels_url, show_help_link: true, manage_label_subscriptions_url: @labels_url, unsubscribe_url: @unsubscribe_url, format: :html)
|
||||||
= 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.
|
|
||||||
|
|
||||||
= email_action @target_url
|
= email_action @target_url
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|
||||||
<%= notification_reason_text(@reason) %>
|
<%= notification_reason_text(reason: @reason) %>
|
||||||
<%= render_if_exists 'layouts/mailer/additional_text' %>
|
<%= render_if_exists 'layouts/mailer/additional_text' %>
|
||||||
|
|
||||||
<%= text_footer_message -%>
|
<%= text_footer_message -%>
|
||||||
|
|
|
@ -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;" }
|
%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" }/
|
%img{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png'), style: "display:block;margin:0 auto 1em;", width: "90" }/
|
||||||
%div
|
%div
|
||||||
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
|
= notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)
|
||||||
- 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} · %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
|
|
||||||
|
|
|
@ -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;" }
|
%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" }
|
%img{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png'), style: "display:block;margin:0 auto 1em;", width: "90" }
|
||||||
%div
|
%div
|
||||||
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
|
= notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)
|
||||||
- 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} · %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
|
|
||||||
|
|
|
@ -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;" }
|
%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" }/
|
%img{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png'), style: "display:block;margin:0 auto 1em;", width: "90" }/
|
||||||
%div
|
%div
|
||||||
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
|
= notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)
|
||||||
- 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} · %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
|
|
||||||
|
|
|
@ -7,7 +7,4 @@
|
||||||
|
|
||||||
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
|
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
|
||||||
|
|
||||||
- if @build.is_a? ::Ci::Build
|
#js-job-page{ data: jobs_data }
|
||||||
#js-job-page{ data: jobs_data }
|
|
||||||
- else
|
|
||||||
#js-bridge-page{ data: bridge_data(@build, @project) }
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
= link_to root_path do
|
= link_to root_path do
|
||||||
= render 'shared/logo.svg'
|
= render partial: 'shared/logo', formats: :svg
|
||||||
%h1= _('Offline')
|
%h1= _('Offline')
|
||||||
.container
|
.container
|
||||||
%h3= _('You are currently offline, or the GitLab instance is not reachable.')
|
%h3= _('You are currently offline, or the GitLab instance is not reachable.')
|
||||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363186
|
||||||
milestone: '15.1'
|
milestone: '15.1'
|
||||||
type: development
|
type: development
|
||||||
group: group::pipeline authoring
|
group: group::pipeline authoring
|
||||||
default_enabled: false
|
default_enabled: true
|
||||||
|
|
|
@ -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
|
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
75027a5b09491b156837707af20406b2672d5ee3ce2272ecf1496e98da2861bf
|
|
@ -0,0 +1 @@
|
||||||
|
90b9b47ef3671b73117205264589f895a083b0d00db00e684b25e60673d2e840
|
|
@ -0,0 +1 @@
|
||||||
|
d64a9c41376bbb3bc2c9df846668b1a67b0bed1b1410d97dba17c19a2f322b38
|
|
@ -0,0 +1 @@
|
||||||
|
2f5e08212b2f733ce5812d7154879768532e31e642b647648d1c03fd4ddf8b13
|
|
@ -0,0 +1 @@
|
||||||
|
ea387b35bfb7f15a036aca9413b8fd15ede6b16048fa9e9be5a62b9e21ca362d
|
|
@ -17493,6 +17493,23 @@ CREATE TABLE namespace_aggregation_schedules (
|
||||||
namespace_id integer NOT NULL
|
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 (
|
CREATE TABLE namespace_ci_cd_settings (
|
||||||
namespace_id bigint NOT NULL,
|
namespace_id bigint NOT NULL,
|
||||||
allow_stale_runner_pruning boolean DEFAULT false NOT NULL
|
allow_stale_runner_pruning boolean DEFAULT false NOT NULL
|
||||||
|
@ -22447,7 +22464,7 @@ CREATE TABLE webauthn_registrations (
|
||||||
public_key text NOT NULL,
|
public_key text NOT NULL,
|
||||||
u2f_registration_id integer,
|
u2f_registration_id integer,
|
||||||
CONSTRAINT check_2f02e74321 CHECK ((char_length(name) <= 255)),
|
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
|
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_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 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);
|
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
|
ALTER TABLE ONLY namespace_aggregation_schedules
|
||||||
ADD CONSTRAINT namespace_aggregation_schedules_pkey PRIMARY KEY (namespace_id);
|
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
|
ALTER TABLE ONLY namespace_ci_cd_settings
|
||||||
ADD CONSTRAINT namespace_ci_cd_settings_pkey PRIMARY KEY (namespace_id);
|
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_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_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);
|
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_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_pipeline_id ON security_scans USING btree (pipeline_id);
|
||||||
|
|
||||||
CREATE INDEX index_security_scans_on_project_id ON security_scans USING btree (project_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
|
ALTER TABLE ONLY ci_pipeline_schedule_variables
|
||||||
ADD CONSTRAINT fk_41c35fda51 FOREIGN KEY (pipeline_schedule_id) REFERENCES ci_pipeline_schedules(id) ON DELETE CASCADE;
|
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
|
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;
|
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
|
ALTER TABLE ONLY routes
|
||||||
ADD CONSTRAINT fk_bb2e5b8968 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
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
|
ALTER TABLE ONLY gitlab_subscriptions
|
||||||
ADD CONSTRAINT fk_bd0c4019c3 FOREIGN KEY (hosted_plan_id) REFERENCES plans(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_bd0c4019c3 FOREIGN KEY (hosted_plan_id) REFERENCES plans(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
|
@ -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_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_...` | 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_file_descriptors` | Gauge | 11.1 | File descriptors per process |
|
||||||
| `ruby_sampler_duration_seconds` | Counter | 11.1 | Time spent collecting stats |
|
| `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 |
|
| `ruby_process_cpu_seconds_total` | Gauge | 12.0 | Total amount of CPU time per process |
|
||||||
|
|
|
@ -3530,12 +3530,12 @@ in that container.
|
||||||
#### `service:pull_policy`
|
#### `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.
|
> - [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.
|
> - Requires GitLab Runner 15.1 or later.
|
||||||
|
|
||||||
FLAG:
|
FLAG:
|
||||||
On self-managed GitLab, by default this feature is not available. To make it available,
|
On self-managed GitLab, by default this feature is available. To hide the feature,
|
||||||
ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `ci_docker_image_pull_policy`.
|
ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `ci_docker_image_pull_policy`.
|
||||||
The feature is not ready for production use.
|
|
||||||
|
|
||||||
The pull policy that the runner uses to fetch the Docker image.
|
The pull policy that the runner uses to fetch the Docker image.
|
||||||
|
|
||||||
|
|
|
@ -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
|
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).
|
[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
|
### Report Fields
|
||||||
|
|
||||||
#### Version
|
#### Version
|
||||||
|
|
|
@ -78,7 +78,7 @@ To view the lead time for changes chart:
|
||||||
|
|
||||||
![Lead time](img/lead_time_chart_v13_11.png)
|
![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
|
> [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.
|
1. Select the **Time to restore service** tab.
|
||||||
|
|
||||||
![Lead time](img/time_to_restore_service_charts_v15_1.png)
|
![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.
|
||||||
|
|
|
@ -873,6 +873,7 @@ Payload example:
|
||||||
},
|
},
|
||||||
"object_attributes": {
|
"object_attributes": {
|
||||||
"id": 99,
|
"id": 99,
|
||||||
|
"iid": 1,
|
||||||
"target_branch": "master",
|
"target_branch": "master",
|
||||||
"source_branch": "ms-viewport",
|
"source_branch": "ms-viewport",
|
||||||
"source_project_id": 14,
|
"source_project_id": 14,
|
||||||
|
@ -884,10 +885,12 @@ Payload example:
|
||||||
"milestone_id": null,
|
"milestone_id": null,
|
||||||
"state": "opened",
|
"state": "opened",
|
||||||
"blocking_discussions_resolved": true,
|
"blocking_discussions_resolved": true,
|
||||||
|
"work_in_progress": false,
|
||||||
|
"first_contribution": true,
|
||||||
"merge_status": "unchecked",
|
"merge_status": "unchecked",
|
||||||
"target_project_id": 14,
|
"target_project_id": 14,
|
||||||
"iid": 1,
|
|
||||||
"description": "",
|
"description": "",
|
||||||
|
"url": "http://example.com/diaspora/merge_requests/1",
|
||||||
"source": {
|
"source": {
|
||||||
"name":"Awesome Project",
|
"name":"Awesome Project",
|
||||||
"description":"Aut reprehenderit ut est.",
|
"description":"Aut reprehenderit ut est.",
|
||||||
|
@ -930,8 +933,18 @@ Payload example:
|
||||||
"email": "gitlabdev@dv6700.(none)"
|
"email": "gitlabdev@dv6700.(none)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"work_in_progress": false,
|
"labels": [{
|
||||||
"url": "http://example.com/diaspora/merge_requests/1",
|
"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",
|
"action": "open",
|
||||||
"assignee": {
|
"assignee": {
|
||||||
"name": "User1",
|
"name": "User1",
|
||||||
|
@ -990,6 +1003,9 @@ Payload example:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
The fields `assignee_id`, and `state` are deprecated.
|
||||||
|
|
||||||
## Wiki page events
|
## Wiki page events
|
||||||
|
|
||||||
Wiki page events are triggered when a wiki page is created, updated, or deleted.
|
Wiki page events are triggered when a wiki page is created, updated, or deleted.
|
||||||
|
|
|
@ -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
|
|
@ -151,6 +151,16 @@ module API
|
||||||
present_carrierwave_file!(package_file.file)
|
present_carrierwave_file!(package_file.file)
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ module API
|
||||||
params do
|
params do
|
||||||
requires :event, type: String, desc: 'The event name that should be tracked'
|
requires :event, type: String, desc: 'The event name that should be tracked'
|
||||||
end
|
end
|
||||||
post 'increment_unique_users' do
|
post 'increment_unique_users', urgency: :low do
|
||||||
event_name = params[:event]
|
event_name = params[:event]
|
||||||
|
|
||||||
increment_unique_values(event_name, current_user.id)
|
increment_unique_values(event_name, current_user.id)
|
||||||
|
|
|
@ -18,7 +18,7 @@ module Gitlab
|
||||||
user: user.hook_attrs,
|
user: user.hook_attrs,
|
||||||
project: issuable.project.hook_attrs,
|
project: issuable.project.hook_attrs,
|
||||||
object_attributes: issuable_builder.new(issuable).build,
|
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)),
|
changes: final_changes(changes.slice(*safe_keys)),
|
||||||
# DEPRECATED
|
# DEPRECATED
|
||||||
repository: issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)
|
repository: issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)
|
||||||
|
|
|
@ -327,6 +327,7 @@ milestone_releases: :gitlab_main
|
||||||
milestones: :gitlab_main
|
milestones: :gitlab_main
|
||||||
namespace_admin_notes: :gitlab_main
|
namespace_admin_notes: :gitlab_main
|
||||||
namespace_aggregation_schedules: :gitlab_main
|
namespace_aggregation_schedules: :gitlab_main
|
||||||
|
namespace_bans: :gitlab_main
|
||||||
namespace_limits: :gitlab_main
|
namespace_limits: :gitlab_main
|
||||||
namespace_package_settings: :gitlab_main
|
namespace_package_settings: :gitlab_main
|
||||||
namespace_root_storage_statistics: :gitlab_main
|
namespace_root_storage_statistics: :gitlab_main
|
||||||
|
|
|
@ -62,7 +62,8 @@ module Gitlab
|
||||||
assignee_id: merge_request.assignee_ids.first, # This key is deprecated
|
assignee_id: merge_request.assignee_ids.first, # This key is deprecated
|
||||||
labels: merge_request.labels_hook_attrs,
|
labels: merge_request.labels_hook_attrs,
|
||||||
state: merge_request.state, # This key is deprecated
|
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)
|
merge_request.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
|
||||||
|
|
|
@ -977,7 +977,7 @@ methods:
|
||||||
statuses:
|
statuses:
|
||||||
- :type
|
- :type
|
||||||
merge_request_diff_files:
|
merge_request_diff_files:
|
||||||
- :diff_export
|
- :utf8_diff
|
||||||
merge_requests:
|
merge_requests:
|
||||||
- :diff_head_sha
|
- :diff_head_sha
|
||||||
- :source_branch_sha
|
- :source_branch_sha
|
||||||
|
|
|
@ -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
|
|
@ -39,7 +39,8 @@ module Gitlab
|
||||||
process_proportional_memory_bytes: ::Gitlab::Metrics.gauge(metric_name(:process, :proportional_memory_bytes), 'Memory used (PSS)', labels),
|
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'),
|
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),
|
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|
|
GC.stat.keys.each do |key|
|
||||||
|
@ -76,8 +77,13 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
# Collect generic GC stats
|
# Collect generic GC stats
|
||||||
GC.stat.each do |key, value|
|
GC.stat.then do |gc_stat|
|
||||||
metrics[key].set(labels, value)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,8 @@ module UnnestedInFilters
|
||||||
# LIMIT 20
|
# LIMIT 20
|
||||||
#
|
#
|
||||||
def rewrite
|
def rewrite
|
||||||
|
log_rewrite
|
||||||
|
|
||||||
model.from(from)
|
model.from(from)
|
||||||
.limit(limit_value)
|
.limit(limit_value)
|
||||||
.order(order_values)
|
.order(order_values)
|
||||||
|
@ -125,6 +127,10 @@ module UnnestedInFilters
|
||||||
|
|
||||||
delegate :model, :order_values, :limit_value, :where_values_hash, to: :relation, private: true
|
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
|
def from
|
||||||
[value_tables.map(&:to_sql) + [lateral]].join(', ')
|
[value_tables.map(&:to_sql) + [lateral]].join(', ')
|
||||||
end
|
end
|
||||||
|
|
|
@ -6969,6 +6969,9 @@ msgstr ""
|
||||||
msgid "CICDAnalytics|All time"
|
msgid "CICDAnalytics|All time"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "CICDAnalytics|Change failure rate"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "CICDAnalytics|Deployment frequency"
|
msgid "CICDAnalytics|Deployment frequency"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -23625,9 +23628,6 @@ msgstr ""
|
||||||
msgid "Manage access"
|
msgid "Manage access"
|
||||||
msgstr ""
|
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."
|
msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -32982,12 +32982,6 @@ msgstr ""
|
||||||
msgid "Retry migration"
|
msgid "Retry migration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Retry the downstream pipeline"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Retry the trigger job"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Retry this job"
|
msgid "Retry this job"
|
||||||
msgstr ""
|
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."
|
msgid "This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes."
|
||||||
msgstr ""
|
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."
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -42467,9 +42458,6 @@ msgstr ""
|
||||||
msgid "View documentation"
|
msgid "View documentation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "View downstream pipeline"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "View eligible approvers"
|
msgid "View eligible approvers"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -44489,18 +44477,36 @@ msgstr ""
|
||||||
msgid "You're receiving this email because of your account on %{host}."
|
msgid "You're receiving this email because of your account on %{host}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} · %{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} · %{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} · %{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 · %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} · %{help_link_start}Help%{help_link_end}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "You're receiving this email because of your activity on %{host}."
|
msgid "You're receiving this email because of your activity on %{host}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You're receiving this email because of your activity on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread · %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} · %{help_link_start}Help%{help_link_end}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "You're receiving this email because you have been assigned an item on %{host}."
|
msgid "You're receiving this email because you have been assigned an item on %{host}."
|
||||||
msgstr ""
|
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 · %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} · %{help_link_start}Help%{help_link_end}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "You're receiving this email because you have been mentioned on %{host}."
|
msgid "You're receiving this email because you have been mentioned on %{host}."
|
||||||
msgstr ""
|
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} · %{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 · %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} · %{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."
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -44930,6 +44936,9 @@ msgstr ""
|
||||||
msgid "allowed to fail"
|
msgid "allowed to fail"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "already banned from namespace"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "already being used for another group or project %{timebox_name}."
|
msgid "already being used for another group or project %{timebox_name}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ require_relative '../lib/prometheus/cleanup_multiproc_dir_service'
|
||||||
require_relative '../lib/gitlab/metrics/prometheus'
|
require_relative '../lib/gitlab/metrics/prometheus'
|
||||||
require_relative '../lib/gitlab/metrics'
|
require_relative '../lib/gitlab/metrics'
|
||||||
require_relative '../lib/gitlab/metrics/system'
|
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/base_sampler'
|
||||||
require_relative '../lib/gitlab/metrics/samplers/ruby_sampler'
|
require_relative '../lib/gitlab/metrics/samplers/ruby_sampler'
|
||||||
require_relative '../lib/gitlab/metrics/exporter/base_exporter'
|
require_relative '../lib/gitlab/metrics/exporter/base_exporter'
|
||||||
|
|
|
@ -18,13 +18,13 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_allow_duplicates_disabled
|
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?
|
click_on_allow_duplicates_button if duplicates_enabled?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_allow_duplicates_enabled
|
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?
|
click_on_allow_duplicates_button unless duplicates_enabled?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -49,7 +49,7 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_dependency_proxy_enabled?
|
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
|
within_element :dependency_proxy_setting_toggle do
|
||||||
toggle = find('button.gl-toggle')
|
toggle = find('button.gl-toggle')
|
||||||
toggle[:class].include?('is-checked')
|
toggle[:class].include?('is-checked')
|
||||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe 'Unsubscribe links', :sidekiq_might_not_need_inline do
|
||||||
let(:mail) { ActionMailer::Base.deliveries.last }
|
let(:mail) { ActionMailer::Base.deliveries.last }
|
||||||
let(:body) { Capybara::Node::Simple.new(mail.default_part_body.to_s) }
|
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(: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
|
before do
|
||||||
perform_enqueued_jobs { issue }
|
perform_enqueued_jobs { issue }
|
||||||
|
|
55
spec/fixtures/api/schemas/public_api/v4/packages/terraform/modules/v1/single_version.json
vendored
Normal file
55
spec/fixtures/api/schemas/public_api/v4/packages/terraform/modules/v1/single_version.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -14,6 +14,7 @@ jest.mock('~/lib/utils/url_utility');
|
||||||
const DeploymentFrequencyChartsStub = { name: 'DeploymentFrequencyCharts', render: () => {} };
|
const DeploymentFrequencyChartsStub = { name: 'DeploymentFrequencyCharts', render: () => {} };
|
||||||
const LeadTimeChartsStub = { name: 'LeadTimeCharts', render: () => {} };
|
const LeadTimeChartsStub = { name: 'LeadTimeCharts', render: () => {} };
|
||||||
const TimeToRestoreServiceChartsStub = { name: 'TimeToRestoreServiceCharts', render: () => {} };
|
const TimeToRestoreServiceChartsStub = { name: 'TimeToRestoreServiceCharts', render: () => {} };
|
||||||
|
const ChangeFailureRateChartsStub = { name: 'ChangeFailureRateCharts', render: () => {} };
|
||||||
const ProjectQualitySummaryStub = { name: 'ProjectQualitySummary', render: () => {} };
|
const ProjectQualitySummaryStub = { name: 'ProjectQualitySummary', render: () => {} };
|
||||||
|
|
||||||
describe('ProjectsPipelinesChartsApp', () => {
|
describe('ProjectsPipelinesChartsApp', () => {
|
||||||
|
@ -33,6 +34,7 @@ describe('ProjectsPipelinesChartsApp', () => {
|
||||||
DeploymentFrequencyCharts: DeploymentFrequencyChartsStub,
|
DeploymentFrequencyCharts: DeploymentFrequencyChartsStub,
|
||||||
LeadTimeCharts: LeadTimeChartsStub,
|
LeadTimeCharts: LeadTimeChartsStub,
|
||||||
TimeToRestoreServiceCharts: TimeToRestoreServiceChartsStub,
|
TimeToRestoreServiceCharts: TimeToRestoreServiceChartsStub,
|
||||||
|
ChangeFailureRateCharts: ChangeFailureRateChartsStub,
|
||||||
ProjectQualitySummary: ProjectQualitySummaryStub,
|
ProjectQualitySummary: ProjectQualitySummaryStub,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -50,6 +52,7 @@ describe('ProjectsPipelinesChartsApp', () => {
|
||||||
const findGlTabAtIndex = (index) => findAllGlTabs().at(index);
|
const findGlTabAtIndex = (index) => findAllGlTabs().at(index);
|
||||||
const findLeadTimeCharts = () => wrapper.find(LeadTimeChartsStub);
|
const findLeadTimeCharts = () => wrapper.find(LeadTimeChartsStub);
|
||||||
const findTimeToRestoreServiceCharts = () => wrapper.find(TimeToRestoreServiceChartsStub);
|
const findTimeToRestoreServiceCharts = () => wrapper.find(TimeToRestoreServiceChartsStub);
|
||||||
|
const findChangeFailureRateCharts = () => wrapper.find(ChangeFailureRateChartsStub);
|
||||||
const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub);
|
const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub);
|
||||||
const findPipelineCharts = () => wrapper.find(PipelineCharts);
|
const findPipelineCharts = () => wrapper.find(PipelineCharts);
|
||||||
const findProjectQualitySummary = () => wrapper.find(ProjectQualitySummaryStub);
|
const findProjectQualitySummary = () => wrapper.find(ProjectQualitySummaryStub);
|
||||||
|
@ -59,58 +62,49 @@ describe('ProjectsPipelinesChartsApp', () => {
|
||||||
createComponent();
|
createComponent();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders tabs', () => {
|
describe.each`
|
||||||
expect(findGlTabs().exists()).toBe(true);
|
title | finderFn | index
|
||||||
|
${'Pipelines'} | ${findPipelineCharts} | ${0}
|
||||||
expect(findGlTabAtIndex(0).attributes('title')).toBe('Pipelines');
|
${'Deployment frequency'} | ${findDeploymentFrequencyCharts} | ${1}
|
||||||
expect(findGlTabAtIndex(1).attributes('title')).toBe('Deployment frequency');
|
${'Lead time'} | ${findLeadTimeCharts} | ${2}
|
||||||
expect(findGlTabAtIndex(2).attributes('title')).toBe('Lead time');
|
${'Time to restore service'} | ${findTimeToRestoreServiceCharts} | ${3}
|
||||||
expect(findGlTabAtIndex(3).attributes('title')).toBe('Time to restore service');
|
${'Change failure rate'} | ${findChangeFailureRateCharts} | ${4}
|
||||||
});
|
${'Project quality'} | ${findProjectQualitySummary} | ${5}
|
||||||
|
`('Tabs', ({ title, finderFn, index }) => {
|
||||||
it('renders the pipeline charts', () => {
|
it(`renders tab with a title ${title} at index ${index}`, () => {
|
||||||
expect(findPipelineCharts().exists()).toBe(true);
|
expect(findGlTabAtIndex(index).attributes('title')).toBe(title);
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
updateHistory.mockImplementation(({ url }) => {
|
it(`renders the ${title} chart`, () => {
|
||||||
expect(url).toBe(chartsPath);
|
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 () => {
|
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', () => {
|
describe('when provided with a query param', () => {
|
||||||
it.each`
|
it.each`
|
||||||
chart | tab
|
chart | tab
|
||||||
|
${'change-failure-rate'} | ${'4'}
|
||||||
${'time-to-restore-service'} | ${'3'}
|
${'time-to-restore-service'} | ${'3'}
|
||||||
${'lead-time'} | ${'2'}
|
${'lead-time'} | ${'2'}
|
||||||
${'deployment-frequency'} | ${'1'}
|
${'deployment-frequency'} | ${'1'}
|
||||||
|
|
|
@ -77,7 +77,7 @@ RSpec.describe EmailsHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'notification_reason_text' do
|
describe 'notification_reason_text' do
|
||||||
subject { helper.notification_reason_text(reason_code) }
|
subject { helper.notification_reason_text(reason: reason_code) }
|
||||||
|
|
||||||
using RSpec::Parameterized::TableSyntax
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,12 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
|
||||||
|
|
||||||
let(:builder) { described_class.new(merge_request) }
|
let(:builder) { described_class.new(merge_request) }
|
||||||
|
|
||||||
describe '#build' do
|
describe '.safe_hook_attributes' do
|
||||||
let(:data) { builder.build }
|
let(:safe_attribute_keys) { described_class.safe_hook_attributes }
|
||||||
|
|
||||||
it 'includes safe attribute' do
|
it 'includes safe attribute' do
|
||||||
%w[
|
expected_safe_attribute_keys = %i[
|
||||||
assignee_id
|
assignee_id
|
||||||
assignee_ids
|
|
||||||
author_id
|
author_id
|
||||||
blocking_discussions_resolved
|
blocking_discussions_resolved
|
||||||
created_at
|
created_at
|
||||||
|
@ -32,17 +31,21 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
|
||||||
milestone_id
|
milestone_id
|
||||||
source_branch
|
source_branch
|
||||||
source_project_id
|
source_project_id
|
||||||
state
|
state_id
|
||||||
target_branch
|
target_branch
|
||||||
target_project_id
|
target_project_id
|
||||||
time_estimate
|
time_estimate
|
||||||
title
|
title
|
||||||
updated_at
|
updated_at
|
||||||
updated_by_id
|
updated_by_id
|
||||||
].each do |key|
|
].freeze
|
||||||
expect(data).to include(key)
|
|
||||||
end
|
expect(safe_attribute_keys).to match_array(expected_safe_attribute_keys)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#build' do
|
||||||
|
let(:data) { builder.build }
|
||||||
|
|
||||||
%i[source target].each do |key|
|
%i[source target].each do |key|
|
||||||
describe "#{key} key" do
|
describe "#{key} key" do
|
||||||
|
@ -52,17 +55,30 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'includes safe attributes' do
|
||||||
|
expect(data).to include(*described_class.safe_hook_attributes)
|
||||||
|
end
|
||||||
|
|
||||||
it 'includes additional attrs' do
|
it 'includes additional attrs' do
|
||||||
expect(data).to include(:source)
|
expected_additional_attributes = %w[
|
||||||
expect(data).to include(:target)
|
description
|
||||||
expect(data).to include(:last_commit)
|
url
|
||||||
expect(data).to include(:work_in_progress)
|
last_commit
|
||||||
expect(data).to include(:total_time_spent)
|
work_in_progress
|
||||||
expect(data).to include(:time_change)
|
total_time_spent
|
||||||
expect(data).to include(:human_time_estimate)
|
time_change
|
||||||
expect(data).to include(:human_total_time_spent)
|
human_total_time_spent
|
||||||
expect(data).to include(:human_time_change)
|
human_time_change
|
||||||
expect(data).to include(:labels)
|
human_time_estimate
|
||||||
|
assignee_ids
|
||||||
|
assignee_id
|
||||||
|
labels
|
||||||
|
state
|
||||||
|
blocking_discussions_resolved
|
||||||
|
first_contribution
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
expect(data).to include(*expected_additional_attributes)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the MR has an image in the description' do
|
context 'when the MR has an image in the description' do
|
||||||
|
|
|
@ -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
|
|
@ -125,5 +125,11 @@ RSpec.describe Gitlab::Metrics::Samplers::RubySampler do
|
||||||
|
|
||||||
sampler.sample
|
sampler.sample
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'adds a heap fragmentation metric' do
|
||||||
|
expect(sampler.metrics[:heap_fragmentation]).to receive(:set).with({}, anything)
|
||||||
|
|
||||||
|
sampler.sample
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -138,5 +138,20 @@ RSpec.describe UnnestedInFilters::Rewriter do
|
||||||
end
|
end
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -64,7 +64,7 @@ RSpec.describe Emails::AdminNotification do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes the email reason' do
|
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
|
||||||
|
|
||||||
context 'when scoped to a group' do
|
context 'when scoped to a group' do
|
||||||
|
|
|
@ -151,7 +151,7 @@ RSpec.describe Emails::Profile do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes the email reason' do
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -187,7 +187,7 @@ RSpec.describe Emails::Profile do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes the email reason' do
|
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
|
||||||
|
|
||||||
context 'with User does not exist' do
|
context 'with User does not exist' do
|
||||||
|
@ -222,7 +222,7 @@ RSpec.describe Emails::Profile do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes the email reason' do
|
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
|
end
|
||||||
|
|
||||||
|
@ -266,7 +266,7 @@ RSpec.describe Emails::Profile do
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'includes the email reason' do
|
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
|
end
|
||||||
|
|
||||||
shared_examples 'valid use case' do
|
shared_examples 'valid use case' do
|
||||||
|
|
|
@ -7,6 +7,7 @@ RSpec.describe Notify do
|
||||||
include EmailSpec::Helpers
|
include EmailSpec::Helpers
|
||||||
include EmailSpec::Matchers
|
include EmailSpec::Matchers
|
||||||
include EmailHelpers
|
include EmailHelpers
|
||||||
|
include EmailsHelper
|
||||||
include RepoHelpers
|
include RepoHelpers
|
||||||
include MembersHelper
|
include MembersHelper
|
||||||
|
|
||||||
|
@ -396,7 +397,7 @@ RSpec.describe Notify do
|
||||||
end
|
end
|
||||||
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) }
|
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'
|
it_behaves_like 'appearance header and footer enabled'
|
||||||
|
@ -407,15 +408,15 @@ RSpec.describe Notify do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes the reason in the footer' do
|
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)
|
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)
|
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)
|
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)
|
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)
|
expect(new_subject).to have_body_text(text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -97,10 +97,8 @@ RSpec.describe MergeRequestDiffFile do
|
||||||
|
|
||||||
file.utf8_diff
|
file.utf8_diff
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
describe '#diff_export' do
|
context 'externally stored diff caching' do
|
||||||
context 'when diff is externally stored' do
|
|
||||||
let(:test_dir) { 'tmp/tests/external-diffs' }
|
let(:test_dir) { 'tmp/tests/external-diffs' }
|
||||||
|
|
||||||
around do |example|
|
around do |example|
|
||||||
|
@ -121,7 +119,7 @@ RSpec.describe MergeRequestDiffFile do
|
||||||
it 'caches external diffs' do
|
it 'caches external diffs' do
|
||||||
expect(file.merge_request_diff).to receive(:cache_external_diff).and_call_original
|
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
|
||||||
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(:seek).with(file.external_diff_offset)
|
||||||
expect(file_stub).to receive(:read).with(file.external_diff_size)
|
expect(file_stub).to receive(:read).with(file.external_diff_size)
|
||||||
|
|
||||||
file.diff_export
|
file.utf8_diff
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -151,28 +149,28 @@ RSpec.describe MergeRequestDiffFile do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'unpacks from base 64' do
|
it 'unpacks from base 64' do
|
||||||
expect(file.diff_export).to eq(unpacked)
|
expect(file.utf8_diff).to eq(unpacked)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'invalid base64' do
|
context 'invalid base64' do
|
||||||
let(:packed) { '---/dev/null' }
|
let(:packed) { '---/dev/null' }
|
||||||
|
|
||||||
it 'returns the raw diff' do
|
it 'returns the raw diff' do
|
||||||
expect(file.diff_export).to eq(packed)
|
expect(file.utf8_diff).to eq(packed)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the diff is not marked as binary' do
|
context 'when the diff is not marked as binary' do
|
||||||
it 'returns the raw diff' do
|
it 'returns the raw diff' do
|
||||||
expect(file.diff_export).to eq(packed)
|
expect(file.utf8_diff).to eq(packed)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when content responds to #encoding' do
|
context 'when content responds to #encoding' do
|
||||||
it 'encodes content to utf8 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -180,35 +178,46 @@ RSpec.describe MergeRequestDiffFile do
|
||||||
it 'returns an empty string' do
|
it 'returns an empty string' do
|
||||||
allow(file.merge_request_diff).to receive(:cached_external_diff).and_return(nil)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when exception is raised' do
|
context 'when exception is raised' do
|
||||||
it 'falls back to #utf8_diff' do
|
it 'falls back to #diff' do
|
||||||
allow(file).to receive(:binary?).and_raise(StandardError)
|
allow(file).to receive(:binary?).and_raise(StandardError, 'Error!')
|
||||||
expect(file).to receive(:utf8_diff)
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when externally_stored_diffs_caching_export feature flag is disabled' do
|
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)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when diff is not stored externally' do
|
context 'when diff is not stored externally' do
|
||||||
it 'calls #utf8_diff' do
|
it 'calls #diff' do
|
||||||
expect(file).to receive(:utf8_diff)
|
expect(file).to receive(:diff)
|
||||||
|
|
||||||
file.diff_export
|
file.utf8_diff
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -171,6 +171,75 @@ RSpec.describe API::Terraform::Modules::V1::Packages do
|
||||||
end
|
end
|
||||||
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
|
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(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/#{package.version}/download") }
|
||||||
let(:headers) { {} }
|
let(:headers) { {} }
|
||||||
|
|
|
@ -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
|
it 'returns items not assigned to that milestone' do
|
||||||
expect(items).to contain_exactly(item2, item3, item4, item5)
|
expect(items).to contain_exactly(item2, item3, item4, item5)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
context 'filtering by group milestone' do
|
context 'filtering by group milestone' do
|
||||||
|
|
|
@ -102,6 +102,22 @@ RSpec.shared_examples 'returns terraform module packages' do |user_type, status,
|
||||||
end
|
end
|
||||||
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|
|
RSpec.shared_examples 'returns no terraform module packages' do |user_type, status, add_member = true|
|
||||||
context "for user type #{user_type}" do
|
context "for user type #{user_type}" do
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe 'projects/commits/_commit.html.haml' do
|
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(:project) { create(:project, :repository) }
|
||||||
let(:commit) { project.repository.commit(ref) }
|
let(:commit) { project.repository.commit(ref) }
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ RSpec.describe 'projects/jobs/show' do
|
||||||
|
|
||||||
it 'shows job vue app' do
|
it 'shows job vue app' do
|
||||||
expect(rendered).to have_css('#js-job-page')
|
expect(rendered).to have_css('#js-job-page')
|
||||||
expect(rendered).not_to have_css('#js-bridge-page')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when job is running' do
|
context 'when job is running' do
|
||||||
|
@ -42,18 +41,4 @@ RSpec.describe 'projects/jobs/show' do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -25,7 +25,7 @@ require (
|
||||||
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
|
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/smartystreets/goconvey v1.7.2
|
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/gitaly/v15 v15.1.0
|
||||||
gitlab.com/gitlab-org/golang-archive-zip v0.1.1
|
gitlab.com/gitlab-org/golang-archive-zip v0.1.1
|
||||||
gitlab.com/gitlab-org/labkit v1.15.0
|
gitlab.com/gitlab-org/labkit v1.15.0
|
||||||
|
@ -113,5 +113,5 @@ require (
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de // indirect
|
google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de // indirect
|
||||||
gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // 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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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/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.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.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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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.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.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.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.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.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.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.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
|
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.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-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-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.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-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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
Loading…
Reference in New Issue