Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
81884c35b8
commit
37a492326e
|
|
@ -5,12 +5,14 @@ import mrEventHub from '../eventhub';
|
|||
|
||||
const CLASSES = {
|
||||
opened: 'status-box-open',
|
||||
locked: 'status-box-open',
|
||||
closed: 'status-box-mr-closed',
|
||||
merged: 'status-box-mr-merged',
|
||||
};
|
||||
|
||||
const STATUS = {
|
||||
opened: [__('Open'), 'issue-open-m'],
|
||||
locked: [__('Open'), 'issue-open-m'],
|
||||
closed: [__('Closed'), 'close'],
|
||||
merged: [__('Merged'), 'git-merge'],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,12 +45,18 @@ export default {
|
|||
}
|
||||
|
||||
if (this.graphData?.maxValue) {
|
||||
formatter = getFormatter(SUPPORTED_FORMATS.percent);
|
||||
return formatter(this.queryResult / Number(this.graphData.maxValue), defaultPrecision);
|
||||
formatter = getFormatter(SUPPORTED_FORMATS.number);
|
||||
return formatter(
|
||||
(this.queryResult / Number(this.graphData.maxValue)) * 100,
|
||||
defaultPrecision,
|
||||
);
|
||||
}
|
||||
|
||||
formatter = getFormatter(SUPPORTED_FORMATS.number);
|
||||
return `${formatter(this.queryResult, defaultPrecision)}${this.queryInfo.unit ?? ''}`;
|
||||
return `${formatter(this.queryResult, defaultPrecision)}`;
|
||||
},
|
||||
unit() {
|
||||
return this.graphData?.maxValue ? '%' : this.queryInfo.unit;
|
||||
},
|
||||
graphTitle() {
|
||||
return this.queryInfo.label;
|
||||
|
|
@ -60,6 +66,6 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<gl-single-stat :value="statValue" :title="graphTitle" variant="success" />
|
||||
<gl-single-stat :value="statValue" :title="graphTitle" :unit="unit" variant="success" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { initCommitBoxInfo } from '~/projects/commit_box/info';
|
||||
import initPipelines from '~/commit/pipelines/pipelines_bundle';
|
||||
import initCommitActions from '~/projects/commit';
|
||||
|
||||
initCommitBoxInfo();
|
||||
initPipelines();
|
||||
initCommitActions();
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ import flash from '~/flash';
|
|||
import { __ } from '~/locale';
|
||||
import loadAwardsHandler from '~/awards_handler';
|
||||
import { initCommitBoxInfo } from '~/projects/commit_box/info';
|
||||
import initRevertCommitTrigger from '~/projects/commit/init_revert_commit_trigger';
|
||||
import initRevertCommitModal from '~/projects/commit/init_revert_commit_modal';
|
||||
import initCommitActions from '~/projects/commit';
|
||||
|
||||
const hasPerfBar = document.querySelector('.with-performance-bar');
|
||||
const performanceHeight = hasPerfBar ? 35 : 0;
|
||||
|
|
@ -47,5 +46,4 @@ if (filesContainer.length) {
|
|||
new Diff();
|
||||
}
|
||||
loadAwardsHandler();
|
||||
initRevertCommitModal();
|
||||
initRevertCommitTrigger();
|
||||
initCommitActions();
|
||||
|
|
|
|||
|
|
@ -136,10 +136,13 @@ export default async function () {
|
|||
createTestDetails();
|
||||
createDagApp();
|
||||
|
||||
const canShowNewPipelineDetails =
|
||||
gon.features.graphqlPipelineDetails || gon.features.graphqlPipelineDetailsUsers;
|
||||
|
||||
const { dataset } = document.querySelector(SELECTORS.PIPELINE_DETAILS);
|
||||
let mediator;
|
||||
|
||||
if (!gon.features.graphqlPipelineHeader || !gon.features.graphqlPipelineDetails) {
|
||||
if (!gon.features.graphqlPipelineHeader || !canShowNewPipelineDetails) {
|
||||
try {
|
||||
const { default: PipelinesMediator } = await import(
|
||||
/* webpackChunkName: 'PipelinesMediator' */ './pipeline_details_mediator'
|
||||
|
|
@ -151,7 +154,7 @@ export default async function () {
|
|||
}
|
||||
}
|
||||
|
||||
if (gon.features.graphqlPipelineDetails) {
|
||||
if (canShowNewPipelineDetails) {
|
||||
try {
|
||||
const { createPipelinesDetailApp } = await import(
|
||||
/* webpackChunkName: 'createPipelinesDetailApp' */ './pipeline_details_graph'
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ export default {
|
|||
autocomplete="off"
|
||||
:debounce="250"
|
||||
:placeholder="$options.i18n.searchPlaceholder"
|
||||
data-testid="dropdown-search-box"
|
||||
@input="searchTermChanged"
|
||||
/>
|
||||
<gl-dropdown-item
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ export default {
|
|||
displayText: {
|
||||
default: '',
|
||||
},
|
||||
testId: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
props: {
|
||||
openModal: {
|
||||
|
|
@ -26,7 +29,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<gl-link data-is-link="true" data-testid="revert-commit-link" @click="showModal">
|
||||
<gl-link data-is-link="true" :data-testid="testId" @click="showModal">
|
||||
{{ displayText }}
|
||||
</gl-link>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ import { s__, __ } from '~/locale';
|
|||
|
||||
export const OPEN_REVERT_MODAL = 'openRevertModal';
|
||||
export const REVERT_MODAL_ID = 'revert-commit-modal';
|
||||
export const REVERT_LINK_TEST_ID = 'revert-commit-link';
|
||||
export const OPEN_CHERRY_PICK_MODAL = 'openCherryPickModal';
|
||||
export const CHERRY_PICK_MODAL_ID = 'cherry-pick-commit-modal';
|
||||
export const CHERRY_PICK_LINK_TEST_ID = 'cherry-pick-commit-link';
|
||||
|
||||
export const I18N_MODAL = {
|
||||
startMergeRequest: s__('ChangeTypeAction|Start a %{newMergeRequest} with these changes'),
|
||||
|
|
@ -20,6 +24,11 @@ export const I18N_REVERT_MODAL = {
|
|||
actionPrimaryText: s__('ChangeTypeAction|Revert'),
|
||||
};
|
||||
|
||||
export const I18N_CHERRY_PICK_MODAL = {
|
||||
branchLabel: s__('ChangeTypeAction|Pick into branch'),
|
||||
actionPrimaryText: s__('ChangeTypeAction|Cherry-pick'),
|
||||
};
|
||||
|
||||
export const PREPENDED_MODAL_TEXT = s__(
|
||||
'ChangeTypeAction|This will create a new commit in order to revert the existing changes.',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import initRevertCommitTrigger from './init_revert_commit_trigger';
|
||||
import initRevertCommitModal from './init_revert_commit_modal';
|
||||
import initCherryPickCommitTrigger from './init_cherry_pick_commit_trigger';
|
||||
import initCherryPickCommitModal from './init_cherry_pick_commit_modal';
|
||||
|
||||
export default () => {
|
||||
initRevertCommitModal();
|
||||
initRevertCommitTrigger();
|
||||
initCherryPickCommitModal();
|
||||
initCherryPickCommitTrigger();
|
||||
};
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import Vue from 'vue';
|
||||
import CommitFormModal from './components/form_modal.vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import createStore from './store';
|
||||
import {
|
||||
I18N_MODAL,
|
||||
I18N_CHERRY_PICK_MODAL,
|
||||
OPEN_CHERRY_PICK_MODAL,
|
||||
CHERRY_PICK_MODAL_ID,
|
||||
} from './constants';
|
||||
|
||||
export default function initInviteMembersModal() {
|
||||
const el = document.querySelector('.js-cherry-pick-commit-modal');
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
endpoint,
|
||||
branch,
|
||||
pushCode,
|
||||
branchCollaboration,
|
||||
existingBranch,
|
||||
branchesEndpoint,
|
||||
} = el.dataset;
|
||||
|
||||
const store = createStore({
|
||||
endpoint,
|
||||
branchesEndpoint,
|
||||
branch,
|
||||
pushCode: parseBoolean(pushCode),
|
||||
branchCollaboration: parseBoolean(branchCollaboration),
|
||||
defaultBranch: branch,
|
||||
modalTitle: title,
|
||||
existingBranch,
|
||||
});
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
store,
|
||||
render: (createElement) =>
|
||||
createElement(CommitFormModal, {
|
||||
props: {
|
||||
i18n: { ...I18N_CHERRY_PICK_MODAL, ...I18N_MODAL },
|
||||
openModal: OPEN_CHERRY_PICK_MODAL,
|
||||
modalId: CHERRY_PICK_MODAL_ID,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import Vue from 'vue';
|
||||
import CommitFormTrigger from './components/form_trigger.vue';
|
||||
import { OPEN_CHERRY_PICK_MODAL, CHERRY_PICK_LINK_TEST_ID } from './constants';
|
||||
|
||||
export default function initInviteMembersTrigger() {
|
||||
const el = document.querySelector('.js-cherry-pick-commit-trigger');
|
||||
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { displayText } = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
provide: { displayText, testId: CHERRY_PICK_LINK_TEST_ID },
|
||||
render: (createElement) =>
|
||||
createElement(CommitFormTrigger, { props: { openModal: OPEN_CHERRY_PICK_MODAL } }),
|
||||
});
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import RevertCommitTrigger from './components/form_trigger.vue';
|
||||
import { OPEN_REVERT_MODAL } from './constants';
|
||||
import CommitFormTrigger from './components/form_trigger.vue';
|
||||
import { OPEN_REVERT_MODAL, REVERT_LINK_TEST_ID } from './constants';
|
||||
|
||||
export default function initInviteMembersTrigger() {
|
||||
const el = document.querySelector('.js-revert-commit-trigger');
|
||||
|
|
@ -13,8 +13,8 @@ export default function initInviteMembersTrigger() {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
provide: { displayText },
|
||||
provide: { displayText, testId: REVERT_LINK_TEST_ID },
|
||||
render: (createElement) =>
|
||||
createElement(RevertCommitTrigger, { props: { openModal: OPEN_REVERT_MODAL } }),
|
||||
createElement(CommitFormTrigger, { props: { openModal: OPEN_REVERT_MODAL } }),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,16 @@ export default {
|
|||
type: Object,
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
changes: s__(
|
||||
'Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete',
|
||||
),
|
||||
generationErrored: s__('Terraform|Generating the report caused an error.'),
|
||||
namedReportFailed: s__('Terraform|The report %{name} failed to generate.'),
|
||||
namedReportGenerated: s__('Terraform|The report %{name} was generated in your pipelines.'),
|
||||
reportFailed: s__('Terraform|A report failed to generate.'),
|
||||
reportGenerated: s__('Terraform|A report was generated in your pipelines.'),
|
||||
},
|
||||
computed: {
|
||||
addNum() {
|
||||
return Number(this.plan.create);
|
||||
|
|
@ -30,23 +40,21 @@ export default {
|
|||
},
|
||||
reportChangeText() {
|
||||
if (this.validPlanValues) {
|
||||
return s__(
|
||||
'Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete',
|
||||
);
|
||||
return this.$options.i18n.changes;
|
||||
}
|
||||
|
||||
return s__('Terraform|Generating the report caused an error.');
|
||||
return this.$options.i18n.generationErrored;
|
||||
},
|
||||
reportHeaderText() {
|
||||
if (this.validPlanValues) {
|
||||
return this.plan.job_name
|
||||
? s__('Terraform|The Terraform report %{name} was generated in your pipelines.')
|
||||
: s__('Terraform|A Terraform report was generated in your pipelines.');
|
||||
? this.$options.i18n.namedReportGenerated
|
||||
: this.$options.i18n.reportGenerated;
|
||||
}
|
||||
|
||||
return this.plan.job_name
|
||||
? s__('Terraform|The Terraform report %{name} failed to generate.')
|
||||
: s__('Terraform|A Terraform report failed to generate.');
|
||||
? this.$options.i18n.namedReportFailed
|
||||
: this.$options.i18n.reportFailed;
|
||||
},
|
||||
validPlanValues() {
|
||||
return this.addNum + this.changeNum + this.deleteNum >= 0;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export const tributeConfig = {
|
|||
value.type === groupType ? last(value.name.split(' / ')) : `${value.name}${value.username}`,
|
||||
menuItemLimit: memberLimit,
|
||||
menuItemTemplate: ({ original }) => {
|
||||
const commonClasses = 'gl-avatar gl-avatar-s24 gl-flex-shrink-0';
|
||||
const commonClasses = 'gl-avatar gl-avatar-s32 gl-flex-shrink-0';
|
||||
const noAvatarClasses = `${commonClasses} gl-rounded-small
|
||||
gl-display-flex gl-align-items-center gl-justify-content-center`;
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ export const tributeConfig = {
|
|||
return `
|
||||
<div class="gl-display-flex gl-align-items-center">
|
||||
${avatar}
|
||||
<div class="gl-font-sm gl-line-height-normal gl-ml-3">
|
||||
<div class="gl-line-height-normal gl-ml-4">
|
||||
<div>${escape(displayName)}${count}</div>
|
||||
<div class="gl-text-gray-700">${escape(parentGroupOrUsername)}</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import ReportSection from '~/reports/components/report_section.vue';
|
||||
import { LOADING, ERROR, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '~/reports/constants';
|
||||
import { ERROR, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '~/reports/constants';
|
||||
import { s__ } from '~/locale';
|
||||
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
|
||||
import createFlash from '~/flash';
|
||||
import Api from '~/api';
|
||||
import HelpIcon from './components/help_icon.vue';
|
||||
import SecurityReportDownloadDropdown from './components/security_report_download_dropdown.vue';
|
||||
import SecuritySummary from './components/security_summary.vue';
|
||||
|
|
@ -24,8 +21,6 @@ import { extractSecurityReportArtifacts } from './utils';
|
|||
export default {
|
||||
store,
|
||||
components: {
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
ReportSection,
|
||||
HelpIcon,
|
||||
SecurityReportDownloadDropdown,
|
||||
|
|
@ -101,9 +96,6 @@ export default {
|
|||
),
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return !this.canShowDownloads;
|
||||
},
|
||||
update(data) {
|
||||
return extractSecurityReportArtifacts(this.$options.reportTypes, data);
|
||||
},
|
||||
|
|
@ -124,9 +116,6 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapGetters(['groupedSummaryText', 'summaryStatus']),
|
||||
canShowDownloads() {
|
||||
return this.glFeatures.coreSecurityMrWidgetDownloads;
|
||||
},
|
||||
hasSecurityReports() {
|
||||
return this.availableSecurityReports.length > 0;
|
||||
},
|
||||
|
|
@ -139,23 +128,6 @@ export default {
|
|||
isLoadingReportArtifacts() {
|
||||
return this.$apollo.queries.reportArtifacts.loading;
|
||||
},
|
||||
shouldShowDownloadGuidance() {
|
||||
return !this.canShowDownloads && this.summaryStatus !== LOADING;
|
||||
},
|
||||
scansHaveRunMessage() {
|
||||
return this.canShowDownloads
|
||||
? this.$options.i18n.scansHaveRun
|
||||
: this.$options.i18n.scansHaveRunWithDownloadGuidance;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (!this.canShowDownloads) {
|
||||
this.checkAvailableSecurityReports(this.$options.reportTypes)
|
||||
.then((availableSecurityReports) => {
|
||||
this.onCheckingAvailableSecurityReports(Array.from(availableSecurityReports));
|
||||
})
|
||||
.catch(this.showError);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(MODULE_SAST, {
|
||||
|
|
@ -166,36 +138,6 @@ export default {
|
|||
setSecretDetectionDiffEndpoint: 'setDiffEndpoint',
|
||||
fetchSecretDetectionDiff: 'fetchDiff',
|
||||
}),
|
||||
async checkAvailableSecurityReports(reportTypes) {
|
||||
const reportTypesSet = new Set(reportTypes);
|
||||
const availableReportTypes = new Set();
|
||||
|
||||
let page = 1;
|
||||
while (page) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data: jobs, headers } = await Api.pipelineJobs(this.projectId, this.pipelineId, {
|
||||
per_page: 100,
|
||||
page,
|
||||
});
|
||||
|
||||
jobs.forEach(({ artifacts = [] }) => {
|
||||
artifacts.forEach(({ file_type }) => {
|
||||
if (reportTypesSet.has(file_type)) {
|
||||
availableReportTypes.add(file_type);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// If we've found artifacts for all the report types, stop looking!
|
||||
if (availableReportTypes.size === reportTypesSet.size) {
|
||||
return availableReportTypes;
|
||||
}
|
||||
|
||||
page = parseIntPagination(normalizeHeaders(headers)).nextPage;
|
||||
}
|
||||
|
||||
return availableReportTypes;
|
||||
},
|
||||
fetchCounts() {
|
||||
if (!this.glFeatures.coreSecurityMrWidgetCounts) {
|
||||
return;
|
||||
|
|
@ -213,11 +155,6 @@ export default {
|
|||
this.canShowCounts = true;
|
||||
}
|
||||
},
|
||||
activatePipelinesTab() {
|
||||
if (window.mrTabs) {
|
||||
window.mrTabs.tabShown('pipelines');
|
||||
}
|
||||
},
|
||||
onCheckingAvailableSecurityReports(availableSecurityReports) {
|
||||
this.availableSecurityReports = availableSecurityReports;
|
||||
this.fetchCounts();
|
||||
|
|
@ -236,12 +173,6 @@ export default {
|
|||
'SecurityReports|Failed to get security report information. Please reload the page or try again later.',
|
||||
),
|
||||
scansHaveRun: s__('SecurityReports|Security scans have run'),
|
||||
scansHaveRunWithDownloadGuidance: s__(
|
||||
'SecurityReports|Security scans have run. Go to the %{linkStart}pipelines tab%{linkEnd} to download the security reports',
|
||||
),
|
||||
downloadFromPipelineTab: s__(
|
||||
'SecurityReports|Go to the %{linkStart}pipelines tab%{linkEnd} to download the security reports',
|
||||
),
|
||||
},
|
||||
summarySlots: [SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR],
|
||||
};
|
||||
|
|
@ -265,22 +196,7 @@ export default {
|
|||
</span>
|
||||
</template>
|
||||
|
||||
<template v-if="shouldShowDownloadGuidance" #sub-heading>
|
||||
<span class="gl-font-sm">
|
||||
<gl-sprintf :message="$options.i18n.downloadFromPipelineTab">
|
||||
<template #link="{ content }">
|
||||
<gl-link
|
||||
class="gl-font-sm"
|
||||
data-testid="show-pipelines"
|
||||
@click="activatePipelinesTab"
|
||||
>{{ content }}</gl-link
|
||||
>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template v-if="canShowDownloads" #action-buttons>
|
||||
<template #action-buttons>
|
||||
<security-report-download-dropdown
|
||||
:artifacts="reportArtifacts"
|
||||
:loading="isLoadingReportArtifacts"
|
||||
|
|
@ -298,13 +214,7 @@ export default {
|
|||
data-testid="security-mr-widget"
|
||||
>
|
||||
<template #error>
|
||||
<gl-sprintf :message="scansHaveRunMessage">
|
||||
<template #link="{ content }">
|
||||
<gl-link data-testid="show-pipelines" @click="activatePipelinesTab">{{
|
||||
content
|
||||
}}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
{{ $options.i18n.scansHaveRun }}
|
||||
|
||||
<help-icon
|
||||
:help-path="securityReportsDocsPath"
|
||||
|
|
@ -312,7 +222,7 @@ export default {
|
|||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="canShowDownloads" #action-buttons>
|
||||
<template #action-buttons>
|
||||
<security-report-download-dropdown
|
||||
:artifacts="reportArtifacts"
|
||||
:loading="isLoadingReportArtifacts"
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:default_merge_ref_for_diffs, @project)
|
||||
push_frontend_feature_flag(:core_security_mr_widget, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:core_security_mr_widget_counts, @project)
|
||||
push_frontend_feature_flag(:core_security_mr_widget_downloads, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:remove_resolve_note, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:diffs_gradual_load, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:codequality_mr_diff, @project)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
push_frontend_feature_flag(:pipelines_security_report_summary, project)
|
||||
push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: true)
|
||||
push_frontend_feature_flag(:graphql_pipeline_header, project, type: :development, default_enabled: false)
|
||||
push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: false)
|
||||
push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:graphql_pipeline_details_users, current_user, type: :development, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:new_pipeline_form_prefilled_vars, project, type: :development, default_enabled: true)
|
||||
end
|
||||
before_action :ensure_pipeline, only: [:show]
|
||||
|
|
|
|||
|
|
@ -110,20 +110,16 @@ module CommitsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def revert_commit_link(commit, continue_to_path, btn_class: nil, pajamas: false)
|
||||
def revert_commit_link
|
||||
return unless current_user
|
||||
|
||||
action = 'revert'
|
||||
|
||||
if pajamas && can_collaborate_with_project?(@project)
|
||||
tag(:div, data: { display_text: action.capitalize }, class: "js-revert-commit-trigger")
|
||||
else
|
||||
commit_action_link(action, commit, continue_to_path, btn_class: btn_class, has_tooltip: false)
|
||||
end
|
||||
tag(:div, data: { display_text: 'Revert' }, class: "js-revert-commit-trigger")
|
||||
end
|
||||
|
||||
def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true)
|
||||
commit_action_link('cherry-pick', commit, continue_to_path, btn_class: btn_class, has_tooltip: has_tooltip)
|
||||
def cherry_pick_commit_link
|
||||
return unless current_user
|
||||
|
||||
tag(:div, data: { display_text: 'Cherry-pick' }, class: "js-cherry-pick-commit-trigger")
|
||||
end
|
||||
|
||||
def commit_signature_badge_classes(additional_classes)
|
||||
|
|
@ -143,7 +139,7 @@ module CommitsHelper
|
|||
def commit_person_link(commit, options = {})
|
||||
user = commit.public_send(options[:source]) # rubocop:disable GitlabSecurity/PublicSend
|
||||
|
||||
source_name = clean(commit.public_send(:"#{options[:source]}_name")) # rubocop:disable GitlabSecurity/PublicSend
|
||||
source_name = clean(commit.public_send(:"#{options[:source]}_name")) # rubocop:disable GitlabSecurity/PublicSend
|
||||
source_email = clean(commit.public_send(:"#{options[:source]}_email")) # rubocop:disable GitlabSecurity/PublicSend
|
||||
|
||||
person_name = user.try(:name) || source_name
|
||||
|
|
@ -166,28 +162,6 @@ module CommitsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def commit_action_link(action, commit, continue_to_path, btn_class: nil, has_tooltip: true)
|
||||
return unless current_user
|
||||
|
||||
tooltip = "#{action.capitalize} this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip
|
||||
btn_class = "btn btn-#{btn_class}" unless btn_class.nil?
|
||||
|
||||
if can_collaborate_with_project?(@project)
|
||||
link_to action.capitalize, "#modal-#{action}-commit", 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
|
||||
elsif can?(current_user, :fork_project, @project)
|
||||
continue_params = {
|
||||
to: continue_to_path,
|
||||
notice: "#{edit_in_new_fork_notice} Try to #{action} this commit again.",
|
||||
notice_now: edit_in_new_fork_notice_now
|
||||
}
|
||||
fork_path = project_forks_path(@project,
|
||||
namespace_key: current_user.namespace.id,
|
||||
continue: continue_params)
|
||||
|
||||
link_to action.capitalize, fork_path, class: btn_class, method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: (tooltip if has_tooltip)
|
||||
end
|
||||
end
|
||||
|
||||
def view_file_button(commit_sha, diff_new_path, project, replaced: false)
|
||||
path = project_blob_path(project, tree_join(commit_sha, diff_new_path))
|
||||
title = replaced ? _('View replaced file @ ') : _('View file @ ')
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ module Pages
|
|||
class LookupPath
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
LegacyStorageDisabledError = Class.new(::StandardError)
|
||||
|
||||
def initialize(project, trim_prefix: nil, domain: nil)
|
||||
@project = project
|
||||
@domain = domain
|
||||
|
|
@ -24,7 +26,7 @@ module Pages
|
|||
end
|
||||
|
||||
def source
|
||||
zip_source || file_source
|
||||
zip_source || legacy_source
|
||||
end
|
||||
|
||||
def prefix
|
||||
|
|
@ -64,11 +66,17 @@ module Pages
|
|||
}
|
||||
end
|
||||
|
||||
def file_source
|
||||
def legacy_source
|
||||
raise LegacyStorageDisabledError unless Feature.enabled?(:pages_serve_from_legacy_storage, default_enabled: true)
|
||||
|
||||
{
|
||||
type: 'file',
|
||||
path: File.join(project.full_path, 'public/')
|
||||
}
|
||||
rescue LegacyStorageDisabledError => e
|
||||
Gitlab::ErrorTracking.track_exception(e)
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,9 +17,16 @@ module Pages
|
|||
end
|
||||
|
||||
def lookup_paths
|
||||
projects.map do |project|
|
||||
paths = projects.map do |project|
|
||||
project.pages_lookup_path(trim_prefix: trim_prefix, domain: domain)
|
||||
end.sort_by(&:prefix).reverse
|
||||
end
|
||||
|
||||
# TODO: remove in https://gitlab.com/gitlab-org/gitlab/-/issues/297524
|
||||
# source can only be nil if pages_serve_from_legacy_storage FF is disabled
|
||||
# we can remove this filtering once we remove legacy storage
|
||||
paths = paths.select(&:source)
|
||||
|
||||
paths.sort_by(&:prefix).reverse
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -22,4 +22,14 @@
|
|||
- label = s_('ChangeTypeAction|Cherry-pick')
|
||||
- branch_label = s_('ChangeTypeActionLabel|Pick into branch')
|
||||
- title = commit.merged_merge_request(current_user) ? _('Cherry-pick this merge request') : _('Cherry-pick this commit')
|
||||
= render "projects/commit/commit_modal", title: title, type: type, commit: commit, branch_label: branch_label, description: description, label: label
|
||||
|
||||
- if defined?(pajamas)
|
||||
.js-cherry-pick-commit-modal{ data: { title: title,
|
||||
endpoint: cherry_pick_namespace_project_commit_path(commit, namespace_id: @project.namespace.full_path, project_id: @project),
|
||||
branch: @project.default_branch,
|
||||
push_code: can?(current_user, :push_code, @project).to_s,
|
||||
branch_collaboration: @project.branch_allows_collaboration?(current_user, selected_branch).to_s,
|
||||
existing_branch: ERB::Util.html_escape(selected_branch),
|
||||
branches_endpoint: project_branches_path(@project) } }
|
||||
- else
|
||||
= render "projects/commit/commit_modal", title: title, type: type, commit: commit, branch_label: branch_label, description: description, label: label
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@
|
|||
#{ _('Browse Files') }
|
||||
- if can_collaborate && !@commit.has_been_reverted?(current_user)
|
||||
%li.clearfix
|
||||
= revert_commit_link(@commit, project_commit_path(@project, @commit.id), pajamas: true)
|
||||
= revert_commit_link
|
||||
- if can_collaborate
|
||||
%li.clearfix
|
||||
= cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
|
||||
= cherry_pick_commit_link
|
||||
- if can?(current_user, :push_code, @project)
|
||||
%li.clearfix
|
||||
= link_to s_('CreateTag|Tag'), new_project_tag_path(@project, ref: @commit)
|
||||
|
|
|
|||
|
|
@ -4,3 +4,7 @@
|
|||
= render 'commit_box'
|
||||
= render 'ci_menu'
|
||||
= render 'projects/commit/pipelines_list', endpoint: pipelines_project_commit_path(@project, @commit.id)
|
||||
|
||||
- if can_collaborate_with_project?(@project)
|
||||
= render "projects/commit/change", type: 'revert', commit: @commit, pajamas: true
|
||||
= render "projects/commit/change", type: 'cherry-pick', commit: @commit, pajamas: true
|
||||
|
|
|
|||
|
|
@ -18,4 +18,4 @@
|
|||
= render "shared/notes/notes_with_form", :autocomplete => true
|
||||
- if can_collaborate_with_project?(@project)
|
||||
= render "projects/commit/change", type: 'revert', commit: @commit, pajamas: true
|
||||
= render "projects/commit/change", type: 'cherry-pick', commit: @commit, title: @commit.title
|
||||
= render "projects/commit/change", type: 'cherry-pick', commit: @commit, pajamas: true
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
- add_page_specific_style 'page_bundles/reports'
|
||||
- add_page_specific_style 'page_bundles/ci_status'
|
||||
|
||||
- if Feature.enabled?(:graphql_pipeline_details, @project)
|
||||
- if Feature.enabled?(:graphql_pipeline_details, @project, default_enabled: :yaml) || Feature.enabled?(:graphql_pipeline_details_users, @current_user, default_enabled: :yaml)
|
||||
- add_page_startup_graphql_call('pipelines/get_pipeline_details', { projectPath: @project.full_path, iid: @pipeline.iid })
|
||||
|
||||
.js-pipeline-container{ data: { controller_action: "#{controller.action_name}" } }
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ module ContainerExpirationPolicies
|
|||
return unless container_repository
|
||||
|
||||
log_extra_metadata_on_done(:container_repository_id, container_repository.id)
|
||||
log_extra_metadata_on_done(:project_id, project.id)
|
||||
|
||||
unless allowed_to_run?(container_repository)
|
||||
container_repository.cleanup_unscheduled!
|
||||
|
|
@ -78,7 +79,7 @@ module ContainerExpirationPolicies
|
|||
end
|
||||
|
||||
def project
|
||||
container_repository&.project
|
||||
container_repository.project
|
||||
end
|
||||
|
||||
def container_repository
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: "[Commit Page] Migrate to GlModal for cherry-pick commit"
|
||||
merge_request: 51650
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add On-call Rotations destroy mutation to GraphQL
|
||||
merge_request: 51860
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove redundant text in Terraform Widget
|
||||
merge_request: 52013
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Database migration for compliance pipeline configuration location
|
||||
merge_request: 51663
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixed merge requests locked status not showing
|
||||
merge_request: 52078
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Support Project variables in `include` section of `gitlab-ci.yml`
|
||||
merge_request: 52108
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: core_security_mr_widget_downloads
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48769
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/273418
|
||||
milestone: '13.7'
|
||||
type: development
|
||||
group: group::static analysis
|
||||
default_enabled: true
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: drop_license_management_artifact
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
milestone:
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31247
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299114
|
||||
milestone: 13.0
|
||||
type: development
|
||||
group: group::composition analysis
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: graphql_pipeline_details_users
|
||||
introduced_by_url:
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299112
|
||||
milestone: '13.9'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: pages_serve_from_legacy_storage
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/297228
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/297524
|
||||
milestone: '13.8'
|
||||
type: development
|
||||
group: group::release
|
||||
default_enabled: true
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: variables_in_include_section_ci
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50188/
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/294294
|
||||
milestone: '13.8'
|
||||
type: development
|
||||
group: group::compliance
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
- title: Pipeline editor
|
||||
body: |
|
||||
GitLab CI/CD provides you with flexible options to support a variety of advanced pipeline use cases. Pipeline syntax can be verbose and sometimes complicated, especially for those who are new to GitLab CI/CD. In this release, we are proud to introduce our first iteration of the Pipeline Editor.
|
||||
|
||||
The editor makes the CI configuration authoring experience much easier for both novice and advanced users alike. The pipeline editor is a single solution that groups all the existing CI authoring features (and future ones) in a single location. The pipeline editor is the best place to go when configuring your pipeline.
|
||||
stage: Verify
|
||||
self-managed: true
|
||||
gitlab-com: true
|
||||
packages: [Core, Starter, Premium, Ultimate]
|
||||
url: https://docs.gitlab.com/ee/ci/pipeline_editor/index.html
|
||||
image_url: https://img.youtube.com/vi/MQpSyvMpsHA/hqdefault.jpg
|
||||
published_at: 2021-01-22
|
||||
release: 13.8
|
||||
- title: Visualization of pipeline configuration
|
||||
body: |
|
||||
A complex CI configuration can be difficult to understand as a developer, especially when trying to predict how your pipeline might behave (or misbehave). Without a visual aid, it is challenging to form a mental image of the relationships between all of the jobs and determine how they are interconnected. In our first iteration of a pipeline visualization, you can now see a graphic representation of your `.gitlab-ci.yml` configuration to better understand and predict how your pipelines will perform.
|
||||
stage: verify
|
||||
self-managed: true
|
||||
gitlab-com: true
|
||||
packages: [Core, Starter, Premium, Ultimate]
|
||||
url: https://docs.gitlab.com/ee/ci/yaml/visualization.html
|
||||
image_url: https://about.gitlab.com/images/13_8/pipeline_visual_builder.png
|
||||
published_at: 2021-01-22
|
||||
release: 13.8
|
||||
- title: Deployment frequency charts
|
||||
packages: [Ultimate]
|
||||
self-managed: true
|
||||
gitlab-com: true
|
||||
image_url: https://about.gitlab.com/images/13_8/deployment_graph.png
|
||||
url: https://docs.gitlab.com/ee/user/analytics/ci_cd_analytics.html#deployment-frequency-charts
|
||||
stage: Release
|
||||
body: |
|
||||
Knowing and monitoring deployment frequency is a starting point for organizations adopting DevOps. We are proud to introduce the deployment frequency charts at the project level so that you and your development teams can monitor the efficiency of deployments over time, find bottlenecks, and make improvements when necessary. This is the first of the DORA metrics that we are making available within GitLab out of the box.
|
||||
published_at: 2021-01-22
|
||||
release: 13.8
|
||||
- title: Scope a board to the current iteration
|
||||
body: |
|
||||
Many teams use boards to manage issues during an iteration. In 13.6, we added support for [filtering issues on a board to a specific Iteration](https://gitlab.com/gitlab-org/gitlab/-/issues/118742), but it is cumbersome to remember to apply that filter every time you go to your board. In this release, we've added the ability to scope your board to the currently active iteration.
|
||||
stage: plan
|
||||
self-managed: true
|
||||
gitlab-com: true
|
||||
packages: [Starter, Premium, Ultimate]
|
||||
url: https://docs.gitlab.com/ee/user/project/issue_board.html#configurable-issue-boards
|
||||
image_url: https://about.gitlab.com/images/13_8/scope_board_to_current_iteration.png
|
||||
published_at: 2021-01-22
|
||||
release: 13.8
|
||||
- title: Download artifacts directly from the merge request widget
|
||||
body: |
|
||||
We added the ability to download build artifacts directly from the MR widget. This is especially useful for mobile development. An example of this is where users want to test an Android package of a specific build created on a physical device or an emulator. You can now access these artifacts directly from the merge request widget without having to find the artifacts buried in the pipeline view.
|
||||
stage: Release
|
||||
self-managed: true
|
||||
gitlab-com: true
|
||||
packages: [Core, Starter, Premium, Ultimate]
|
||||
url: https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html#downloading-artifacts
|
||||
image_url: https://about.gitlab.com/images/13_8/artifact_mr.png
|
||||
published_at: 2021-01-22
|
||||
release: 13.8
|
||||
- title: Rebase quick action for merge requests
|
||||
body: |
|
||||
Rebase is a Git command used to reapply commits on top of a new commit. In practice, this means reapplying commits from a feature branch on top of the latest version of the target branch (e.g. `main`). In this way, it is possible to bring the feature branch up to date and resolve any conflicts without using a merge commit with the benefit of a simpler linear Git history.
|
||||
|
||||
GitLab 13.8 brings the ability to execute the rebase quick action in merge requests, allowing you to quickly invoke the rebase Git utility.
|
||||
stage: create
|
||||
self-managed: true
|
||||
gitlab-com: true
|
||||
packages: [Core, Starter, Premium, Ultimate]
|
||||
url: https://docs.gitlab.com/ee/user/project/quick_actions.html#quick-actions-for-issues-merge-requests-and-epics
|
||||
image_url: https://about.gitlab.com/images/13_8/rebase_comment.png
|
||||
published_at: 2021-01-22
|
||||
release: 13.8
|
||||
- title: GitLab Pages is now available for Kubernetes deployments of GitLab
|
||||
body: |
|
||||
GitLab Pages is a popular static-site hosting service built into GitLab, and we are excited to announce that it is now available for GitLab instances running on Kubernetes. Pages was one of the last remaining feature gaps compared to an Omnibus deployment.
|
||||
stage: enablement
|
||||
self-managed: true
|
||||
gitlab-com: false
|
||||
packages: [Core, Starter, Premium, Ultimate]
|
||||
url: https://docs.gitlab.com/charts/charts/gitlab/gitlab-pages/index.html
|
||||
image_url: https://about.gitlab.com/images/home/kubernetes.png
|
||||
published_at: 2021-01-22
|
||||
release: 13.8
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddPipelineConfigurationFullPathToCompliancePipeline < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
# rubocop:disable Migration/AddLimitToTextColumns
|
||||
# limit is added in 20210119162812_add_text_limit_to_compliance_pipeline_configuration_full_path.rb
|
||||
def up
|
||||
add_column :compliance_management_frameworks, :pipeline_configuration_full_path, :text
|
||||
end
|
||||
# rubocop:enable Migration/AddLimitToTextColumns
|
||||
|
||||
def down
|
||||
remove_column :compliance_management_frameworks, :pipeline_configuration_full_path
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTextLimitToCompliancePipelineConfigurationFullPath < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_text_limit :compliance_management_frameworks, :pipeline_configuration_full_path, 255
|
||||
end
|
||||
|
||||
def down
|
||||
remove_text_limit :compliance_management_frameworks, :pipeline_configuration_full_path
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
e6841491cd7d2cc015fd628f5c14270720d59cbb17b7efb160937963f074f5c2
|
||||
|
|
@ -0,0 +1 @@
|
|||
cd7643fc762d8b9236ef5ac7cc285ffbd29f1953178b9b6e129082efd7b9e07b
|
||||
|
|
@ -11457,9 +11457,11 @@ CREATE TABLE compliance_management_frameworks (
|
|||
color text NOT NULL,
|
||||
namespace_id integer NOT NULL,
|
||||
regulated boolean DEFAULT true NOT NULL,
|
||||
pipeline_configuration_full_path text,
|
||||
CONSTRAINT check_08cd34b2c2 CHECK ((char_length(color) <= 10)),
|
||||
CONSTRAINT check_1617e0b87e CHECK ((char_length(description) <= 255)),
|
||||
CONSTRAINT check_ab00bc2193 CHECK ((char_length(name) <= 255))
|
||||
CONSTRAINT check_ab00bc2193 CHECK ((char_length(name) <= 255)),
|
||||
CONSTRAINT check_e7a9972435 CHECK ((char_length(pipeline_configuration_full_path) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE compliance_management_frameworks_id_seq
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ successfully, you must replicate their data using some other means.
|
|||
| Feature | Replicated (added in GitLab version) | Verified (added in GitLab version) | Object Storage replication (see [Geo with Object Storage](object_storage.md)) | Notes |
|
||||
|:---------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------|:----------------------------------------------------------|:-------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [Application data in PostgreSQL](../../postgresql/index.md) | **Yes** (10.2) | **Yes** (10.2) | No | |
|
||||
| [Project repository](../../..//user/project/repository/) | **Yes** (10.2) | **Yes** (10.7) | No | |
|
||||
| [Project repository](../../../user/project/repository/) | **Yes** (10.2) | **Yes** (10.7) | No | |
|
||||
| [Project wiki repository](../../../user/project/wiki/) | **Yes** (10.2) | **Yes** (10.7) | No |
|
||||
| [Group wiki repository](../../../user/group/index.md#group-wikis) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/208147) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/208147) | No | |
|
||||
| [Uploads](../../uploads.md) | **Yes** (10.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | No | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both nodes and comparing the output between them. |
|
||||
|
|
|
|||
|
|
@ -472,7 +472,7 @@ information, see [High Availability with Omnibus GitLab](../../postgresql/replic
|
|||
## Patroni support
|
||||
|
||||
Support for Patroni is intended to replace `repmgr` as a
|
||||
[highly availabile PostgreSQL solution](../../postgresql/replication_and_failover.md)
|
||||
[highly available PostgreSQL solution](../../postgresql/replication_and_failover.md)
|
||||
on the primary node, but it can also be used for PostgreSQL HA on a secondary
|
||||
node.
|
||||
|
||||
|
|
|
|||
|
|
@ -3808,6 +3808,12 @@ type ComplianceFramework {
|
|||
Name of the compliance framework
|
||||
"""
|
||||
name: String!
|
||||
|
||||
"""
|
||||
Full path of the compliance pipeline configuration stored in a project
|
||||
repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.
|
||||
"""
|
||||
pipelineConfigurationFullPath: String
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
@ -3860,6 +3866,12 @@ input ComplianceFrameworkInput {
|
|||
New name for the compliance framework.
|
||||
"""
|
||||
name: String
|
||||
|
||||
"""
|
||||
Full path of the compliance pipeline configuration stored in a project
|
||||
repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.
|
||||
"""
|
||||
pipelineConfigurationFullPath: String
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
@ -15836,6 +15848,7 @@ type Mutation {
|
|||
mergeRequestUpdate(input: MergeRequestUpdateInput!): MergeRequestUpdatePayload
|
||||
namespaceIncreaseStorageTemporarily(input: NamespaceIncreaseStorageTemporarilyInput!): NamespaceIncreaseStorageTemporarilyPayload
|
||||
oncallRotationCreate(input: OncallRotationCreateInput!): OncallRotationCreatePayload
|
||||
oncallRotationDestroy(input: OncallRotationDestroyInput!): OncallRotationDestroyPayload
|
||||
oncallScheduleCreate(input: OncallScheduleCreateInput!): OncallScheduleCreatePayload
|
||||
oncallScheduleDestroy(input: OncallScheduleDestroyInput!): OncallScheduleDestroyPayload
|
||||
oncallScheduleUpdate(input: OncallScheduleUpdateInput!): OncallScheduleUpdatePayload
|
||||
|
|
@ -16597,6 +16610,51 @@ input OncallRotationDateInputType {
|
|||
time: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of OncallRotationDestroy
|
||||
"""
|
||||
input OncallRotationDestroyInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
The ID of the on-call rotation to remove.
|
||||
"""
|
||||
id: IncidentManagementOncallRotationID!
|
||||
|
||||
"""
|
||||
The project to remove the on-call schedule from.
|
||||
"""
|
||||
projectPath: ID!
|
||||
|
||||
"""
|
||||
The IID of the on-call schedule to the on-call rotation belongs to.
|
||||
"""
|
||||
scheduleIid: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of OncallRotationDestroy
|
||||
"""
|
||||
type OncallRotationDestroyPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Errors encountered during execution of the mutation.
|
||||
"""
|
||||
errors: [String!]!
|
||||
|
||||
"""
|
||||
The on-call rotation.
|
||||
"""
|
||||
oncallRotation: IncidentManagementOncallRotation
|
||||
}
|
||||
|
||||
"""
|
||||
The rotation length of the on-call rotation
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -10370,6 +10370,20 @@
|
|||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pipelineConfigurationFullPath",
|
||||
"description": "Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
|
|
@ -10526,6 +10540,16 @@
|
|||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "pipelineConfigurationFullPath",
|
||||
"description": "Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
|
|
@ -45793,6 +45817,33 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "oncallRotationDestroy",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "OncallRotationDestroyInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "OncallRotationDestroyPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "oncallScheduleCreate",
|
||||
"description": null,
|
||||
|
|
@ -49118,6 +49169,136 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "OncallRotationDestroyInput",
|
||||
"description": "Autogenerated input type of OncallRotationDestroy",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "projectPath",
|
||||
"description": "The project to remove the on-call schedule from.",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "scheduleIid",
|
||||
"description": "The IID of the on-call schedule to the on-call rotation belongs to.",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "The ID of the on-call rotation to remove.",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "IncidentManagementOncallRotationID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "OncallRotationDestroyPayload",
|
||||
"description": "Autogenerated return type of OncallRotationDestroy",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Errors encountered during execution of the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "oncallRotation",
|
||||
"description": "The on-call rotation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "IncidentManagementOncallRotation",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "OncallRotationLengthInputType",
|
||||
|
|
|
|||
|
|
@ -591,6 +591,7 @@ Represents a ComplianceFramework associated with a Project.
|
|||
| `description` | String! | Description of the compliance framework |
|
||||
| `id` | ID! | Compliance framework ID |
|
||||
| `name` | String! | Name of the compliance framework |
|
||||
| `pipelineConfigurationFullPath` | String | Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`. |
|
||||
|
||||
### ConfigureSastPayload
|
||||
|
||||
|
|
@ -2494,6 +2495,16 @@ Autogenerated return type of OncallRotationCreate.
|
|||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
| `oncallRotation` | IncidentManagementOncallRotation | The on-call rotation. |
|
||||
|
||||
### OncallRotationDestroyPayload
|
||||
|
||||
Autogenerated return type of OncallRotationDestroy.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
| `oncallRotation` | IncidentManagementOncallRotation | The on-call rotation. |
|
||||
|
||||
### OncallScheduleCreatePayload
|
||||
|
||||
Autogenerated return type of OncallScheduleCreate.
|
||||
|
|
|
|||
|
|
@ -1406,7 +1406,7 @@ Parameters:
|
|||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
| `merge_commit_message` | string | no | Custom merge commit message. |
|
||||
| `squash_commit_message` | string | no | Custom squash commit message. |
|
||||
| `squash` | boolean | no | If `true` the commits the commits are squashed into a single commit on merge. |
|
||||
| `squash` | boolean | no | If `true` the commits are squashed into a single commit on merge. |
|
||||
| `should_remove_source_branch` | boolean | no | If `true` removes the source branch. |
|
||||
| `merge_when_pipeline_succeeds` | boolean | no | If `true` the MR is merged when the pipeline succeeds. |
|
||||
| `sha` | string | no | If present, then this SHA must match the HEAD of the source branch, otherwise the merge fails. |
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
|
|||
|
||||
## Require code owner approvals for a single branch
|
||||
|
||||
Update the "code owner approval required" option for the given protected branch protected branch.
|
||||
Update the "code owner approval required" option for the given protected branch.
|
||||
|
||||
```plaintext
|
||||
PATCH /projects/:id/protected_branches/:name
|
||||
|
|
|
|||
|
|
@ -575,7 +575,7 @@ Example response:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "12345",
|
||||
"id": 12345,
|
||||
"token": "6337ff461c94fd3fa32ba3b1ff4125"
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ type: reference
|
|||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4540) in GitLab 13.8.
|
||||
> - It's [deployed behind a feature flag](../../user/feature_flags.md), enabled by default.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - It's not recommended for production use.
|
||||
> - It's recommended for production use.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-pipeline-editor). **(CORE ONLY)**
|
||||
|
||||
WARNING:
|
||||
|
|
@ -62,7 +62,7 @@ reflected in the CI lint. It displays the same results as the existing [CI Lint
|
|||
> - It was [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
|
||||
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/290117) in GitLab 13.8.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - It's not recommended for production use.
|
||||
> - It's recommended for production use.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-cicd-configuration-visualization). **(CORE ONLY)**
|
||||
|
||||
WARNING:
|
||||
|
|
@ -115,7 +115,7 @@ checkbox appears. Select it to start a new merge request after you commit the ch
|
|||
|
||||
## Enable or disable pipeline editor **(CORE ONLY)**
|
||||
|
||||
The pipeline editor is under development and not ready for production use. It is
|
||||
The pipeline editor is under development but ready for production use. It is
|
||||
deployed behind a feature flag that is **enabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||
can disable it.
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ Try to find which jobs don't need to run in all situations, and use pipeline con
|
|||
to stop them from running:
|
||||
|
||||
- Use the [`interruptible`](../yaml/README.md#interruptible) keyword to stop old pipelines
|
||||
when they are superceded by a newer pipeline.
|
||||
when they are superseded by a newer pipeline.
|
||||
- Use [`rules`](../yaml/README.md#rules) to skip tests that aren't needed. For example,
|
||||
skip backend tests when only the frontend code is changed.
|
||||
- Run non-essential [scheduled pipelines](schedules.md) less frequently.
|
||||
|
|
|
|||
|
|
@ -377,16 +377,10 @@ NOTE:
|
|||
Use merging to customize and override included CI/CD configurations with local
|
||||
definitions. Local definitions in `.gitlab-ci.yml` override included definitions.
|
||||
|
||||
#### Variables with `include`
|
||||
#### Variables with `include` **(CORE ONLY)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/284883) in GitLab 13.8.
|
||||
> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
|
||||
> - It's disabled on GitLab.com.
|
||||
> - It's not recommended for production use.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-includepredefined-project-variables). **(CORE ONLY)**
|
||||
|
||||
WARNING:
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/294294) in GitLab 13.9.
|
||||
|
||||
You can [use some predefined variables in `include` sections](../variables/where_variables_can_be_used.md#gitlab-ciyml-file)
|
||||
in your `.gitlab-ci.yml`:
|
||||
|
|
@ -400,25 +394,6 @@ include:
|
|||
For an example of how you can include these predefined variables, and their impact on CI jobs,
|
||||
see the following [CI variable demo](https://youtu.be/4XR8gw3Pkos).
|
||||
|
||||
##### Enable or disable include:predefined-project-variables **(CORE ONLY)**
|
||||
|
||||
Use of predefined project variables in `include` section of `.gitlab-ci.yml` is under development and not ready for production use. It is
|
||||
deployed behind a feature flag that is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||
can enable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:variables_in_include_section_ci)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:variables_in_include_section_ci)
|
||||
```
|
||||
|
||||
#### `include:local`
|
||||
|
||||
`include:local` includes a file from the same repository as `.gitlab-ci.yml`.
|
||||
|
|
@ -3097,6 +3072,32 @@ job:
|
|||
- path/*xyz/*
|
||||
```
|
||||
|
||||
#### `artifacts:public`
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49775) in GitLab 13.8
|
||||
> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - It's recommended for production use.
|
||||
|
||||
`artifacts:public` is used to determine whether the job artifacts should be
|
||||
publicly available.
|
||||
|
||||
The default for `artifacts:public` is `true` which means that the artifacts in
|
||||
public pipelines are available for download by anonymous and guest users:
|
||||
|
||||
```yaml
|
||||
artifacts:
|
||||
public: true
|
||||
```
|
||||
|
||||
To deny read access for anonymous and guest users to artifacts in public
|
||||
pipelines, set `artifacts:public` to `false`:
|
||||
|
||||
```yaml
|
||||
artifacts:
|
||||
public: false
|
||||
```
|
||||
|
||||
#### `artifacts:exclude`
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15122) in GitLab 13.1
|
||||
|
|
@ -3453,7 +3454,7 @@ job1:
|
|||
|
||||
The coverage is shown in the UI if at least one line in the job output matches the regular expression.
|
||||
If there is more than one matched line in the job output, the last line is used.
|
||||
For the matched line, the first occurence of `\d+(\.\d+)?` is the code coverage.
|
||||
For the matched line, the first occurrence of `\d+(\.\d+)?` is the code coverage.
|
||||
Leading zeros are removed.
|
||||
|
||||
Coverage output from [child pipelines](../parent_child_pipelines.md) is not recorded
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ with [domain expertise](#domain-experts).
|
|||
be **approved by a [frontend foundations member](https://about.gitlab.com/direction/create/ecosystem/frontend-ux-foundations/)**.
|
||||
- If the license used by the new library hasn't been approved for use in
|
||||
GitLab, the license must be **approved by a [legal department member](https://about.gitlab.com/handbook/legal/)**.
|
||||
More information about license compatiblity can be found in our
|
||||
More information about license compatibility can be found in our
|
||||
[GitLab Licensing and Compatibility documentation](licensing.md).
|
||||
1. If your merge request includes adding a new UI/UX paradigm (*1*), it must be
|
||||
**approved by a [UX lead](https://about.gitlab.com/company/team/)**.
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ cluster.routing.allocation.disk.watermark.low: 15gb
|
|||
cluster.routing.allocation.disk.watermark.high: 10gb
|
||||
```
|
||||
|
||||
Restart Elasticsearch, and the `read_only_allow_delete` will clear on it's own.
|
||||
Restart Elasticsearch, and the `read_only_allow_delete` will clear on its own.
|
||||
|
||||
_from "Disk-based Shard Allocation | Elasticsearch Reference" [5.6](https://www.elastic.co/guide/en/elasticsearch/reference/5.6/disk-allocator.html#disk-allocator) and [6.x](https://www.elastic.co/guide/en/elasticsearch/reference/6.7/disk-allocator.html)_
|
||||
|
||||
|
|
@ -351,7 +351,7 @@ simply reindex everything from scratch.
|
|||
|
||||
If your Elasticsearch index is incredibly large it may be too time consuming or
|
||||
cause too much downtime to reindex from scratch. There aren't any built in
|
||||
mechanisms for automatically finding discrepencies and resyncing an
|
||||
mechanisms for automatically finding discrepancies and resyncing an
|
||||
Elasticsearch index if it gets out of sync but one tool that may be useful is
|
||||
looking at the logs for all the updates that occurred in a time range you
|
||||
believe may have been missed. This information is very low level and only
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ all of the arguments that `in_batches` supports. You should always use
|
|||
|
||||
One should proceed with extra caution, and possibly avoid iterating over a column that can contain duplicate values.
|
||||
When you iterate over an attribute that is not unique, even with the applied max batch size, there is no guarantee that the resulting batches will not surpass it.
|
||||
The following snippet demonstrates this situation, whe one attempt to select `Ci::Build` entries for users with `id` between `1` and `10,s000`, database returns `1 215 178`
|
||||
The following snippet demonstrates this situation, when one attempt to select `Ci::Build` entries for users with `id` between `1` and `10,s000`, database returns `1 215 178`
|
||||
matching rows
|
||||
|
||||
```ruby
|
||||
|
|
|
|||
|
|
@ -227,5 +227,5 @@ The following table lists these variables along with their default values.
|
|||
GitLab may decide to change these settings in order to speed up application performance, lower memory requirements, or both.
|
||||
|
||||
You can see how each of these settings affect GC performance, memory use and application start-up time for an idle instance of
|
||||
GitLab by runnning the `scripts/perf/gc/collect_gc_stats.rb` script. It will output GC stats and general timing data to standard
|
||||
GitLab by running the `scripts/perf/gc/collect_gc_stats.rb` script. It will output GC stats and general timing data to standard
|
||||
out as CSV.
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ For other regular expressions, here are a few guidelines:
|
|||
|
||||
- If there's a clean non-regex solution, such as `String#start_with?`, consider using it
|
||||
- Ruby supports some advanced regex features like [atomic groups](https://www.regular-expressions.info/atomic.html)
|
||||
and [possessive quantifiers](https://www.regular-expressions.info/possessive.html) that eleminate backtracking
|
||||
and [possessive quantifiers](https://www.regular-expressions.info/possessive.html) that eliminate backtracking
|
||||
- Avoid nested quantifiers if possible (for example `(a+)+`)
|
||||
- Try to be as precise as possible in your regex and avoid the `.` if there's an alternative
|
||||
- For example, Use `_[^_]+_` instead of `_.*_` to match `_text here_`
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ Jest tests can be found in `/spec/frontend` and `/ee/spec/frontend` in EE.
|
|||
## Karma test suite
|
||||
|
||||
While GitLab has switched over to [Jest](https://jestjs.io), Karma tests still exist in our
|
||||
application because some of our specs require a browser and can't be easiliy migrated to Jest.
|
||||
application because some of our specs require a browser and can't be easily migrated to Jest.
|
||||
Those specs intend to eventually drop Karma in favor of either Jest or RSpec. You can track this migration
|
||||
in the [related epic](https://gitlab.com/groups/gitlab-org/-/epics/4900).
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ You should also reference the [OmniAuth documentation](omniauth.md) for general
|
|||
|------|-------------|
|
||||
| Identity Provider (IdP) | The service which manages your user identities such as ADFS, Okta, Onelogin, or Ping Identity. |
|
||||
| Service Provider (SP) | SAML considers GitLab to be a service provider. |
|
||||
| Assertion | A piece of information about a user's identity, such as their name or role. Also know as claims or attributes. |
|
||||
| Assertion | A piece of information about a user's identity, such as their name or role. Also known as claims or attributes. |
|
||||
| SSO | Single Sign-On. |
|
||||
| Assertion consumer service URL | The callback on GitLab where users will be redirected after successfully authenticating with the identity provider. |
|
||||
| Issuer | How GitLab identifies itself to the identity provider. Also known as a "Relying party trust identifier". |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Growth
|
||||
group: Product Analytics
|
||||
group: Product Intelligence
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ Feature.enable(:admin_new_user_signups_cap)
|
|||
## Soft email confirmation
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47003) in GitLab 12.2.
|
||||
> - It's [deployed behind a feature flag](../../..//user/feature_flags.md), disabled by default.
|
||||
> - It's [deployed behind a feature flag](../../../user/feature_flags.md), disabled by default.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - It's recommended for production use.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-soft-email-confirmation).
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@ When [configuring your identify provider](#configuring-your-identity-provider),
|
|||
### Azure setup notes
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For a demo of the Azure SAML setup including SCIM, see [SCIM Provisioning on Azure Using SAML SSO for Groups Demo](https://youtu.be/24-ZxmTeEBU). Please note that the video is outdated in regards to objectID mapping and the [SCIM documentation should be followed](scim_setup.md#azure-configuration-steps).
|
||||
For a demo of the Azure SAML setup including SCIM, see [SCIM Provisioning on Azure Using SAML SSO for Groups Demo](https://youtu.be/24-ZxmTeEBU). Please note that the video is outdated in regard to
|
||||
objectID mapping and the [SCIM documentation should be followed](scim_setup.md#azure-configuration-steps).
|
||||
|
||||
| GitLab Setting | Azure Field |
|
||||
|--------------|----------------|
|
||||
|
|
@ -343,7 +344,7 @@ access.
|
|||
|------|-------------|
|
||||
| Identity Provider | The service which manages your user identities such as ADFS, Okta, Onelogin, or Ping Identity. |
|
||||
| Service Provider | SAML considers GitLab to be a service provider. |
|
||||
| Assertion | A piece of information about a user's identity, such as their name or role. Also know as claims or attributes. |
|
||||
| Assertion | A piece of information about a user's identity, such as their name or role. Also known as claims or attributes. |
|
||||
| SSO | Single Sign On. |
|
||||
| Assertion consumer service URL | The callback on GitLab where users are redirected after successfully authenticating with the identity provider. |
|
||||
| Issuer | How GitLab identifies itself to the identity provider. Also known as a "Relying party trust identifier". |
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ You can see issues with their due dates in the [issues list](index.md#issues-lis
|
|||
Overdue issues have their icon and date colored red.
|
||||
To sort issues by their due dates, select **Due date** from the dropdown menu on the right.
|
||||
Issues are then sorted from the earliest due date to the latest.
|
||||
To display isses with the latest due dates at the top, select **Sort direction** (**{sort-lowest}**).
|
||||
To display issues with the latest due dates at the top, select **Sort direction** (**{sort-lowest}**).
|
||||
|
||||
Due dates also appear in your [to-do list](../../todos.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -99,8 +99,6 @@ module Gitlab
|
|||
end
|
||||
|
||||
def expand_variables(data)
|
||||
return data unless ::Feature.enabled?(:variables_in_include_section_ci)
|
||||
|
||||
if data.is_a?(String)
|
||||
expand(data)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -5269,6 +5269,9 @@ msgstr ""
|
|||
msgid "ChangeTypeAction|Cherry-pick"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChangeTypeAction|Pick into branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChangeTypeAction|Revert"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25171,9 +25174,6 @@ msgstr ""
|
|||
msgid "SecurityReports|Fuzzing artifacts"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|Go to the %{linkStart}pipelines tab%{linkEnd} to download the security reports"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|Hide dismissed"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25237,9 +25237,6 @@ msgstr ""
|
|||
msgid "SecurityReports|Security scans have run"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|Security scans have run. Go to the %{linkStart}pipelines tab%{linkEnd} to download the security reports"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|Select a project to add by using the project search field above."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -27752,10 +27749,10 @@ msgstr[1] ""
|
|||
msgid "Terraform|%{user} updated %{timeAgo}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|A Terraform report failed to generate."
|
||||
msgid "Terraform|A report failed to generate."
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|A Terraform report was generated in your pipelines."
|
||||
msgid "Terraform|A report was generated in your pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Actions"
|
||||
|
|
@ -27815,10 +27812,10 @@ msgstr ""
|
|||
msgid "Terraform|States"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|The Terraform report %{name} failed to generate."
|
||||
msgid "Terraform|The report %{name} failed to generate."
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
|
||||
msgid "Terraform|The report %{name} was generated in your pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|To remove the State file and its versions, type %{name} to confirm:"
|
||||
|
|
@ -32552,6 +32549,9 @@ msgstr ""
|
|||
msgid "You have insufficient permissions to create an on-call schedule for this project"
|
||||
msgstr ""
|
||||
|
||||
msgid "You have insufficient permissions to remove an on-call rotation from this project"
|
||||
msgstr ""
|
||||
|
||||
msgid "You have insufficient permissions to remove an on-call schedule from this project"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/svgs": "1.179.0",
|
||||
"@gitlab/tributejs": "1.0.0",
|
||||
"@gitlab/ui": "25.12.2",
|
||||
"@gitlab/ui": "26.0.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-4",
|
||||
"@rails/ujs": "^6.0.3-4",
|
||||
|
|
|
|||
|
|
@ -2,108 +2,126 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Cherry-pick Commits' do
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:project, :repository, namespace: group) }
|
||||
let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
|
||||
let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') }
|
||||
RSpec.describe 'Cherry-pick Commits', :js do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
|
||||
let!(:project) { create_default(:project, :repository, namespace: user.namespace) }
|
||||
let(:master_pickable_commit) { project.commit(sha) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
project.add_maintainer(user)
|
||||
visit project_commit_path(project, master_pickable_commit.id)
|
||||
end
|
||||
|
||||
context "I cherry-pick a commit" do
|
||||
it do
|
||||
find("a[href='#modal-cherry-pick-commit']").click
|
||||
expect(page).not_to have_content('v1.0.0') # Only branches, not tags
|
||||
page.within('#modal-cherry-pick-commit') do
|
||||
uncheck 'create_merge_request'
|
||||
click_button 'Cherry-pick'
|
||||
end
|
||||
expect(page).to have_content('The commit has been successfully cherry-picked into master.')
|
||||
end
|
||||
end
|
||||
|
||||
context "I cherry-pick a merge commit" do
|
||||
it do
|
||||
find("a[href='#modal-cherry-pick-commit']").click
|
||||
page.within('#modal-cherry-pick-commit') do
|
||||
uncheck 'create_merge_request'
|
||||
click_button 'Cherry-pick'
|
||||
end
|
||||
expect(page).to have_content('The commit has been successfully cherry-picked into master.')
|
||||
end
|
||||
end
|
||||
|
||||
context "I cherry-pick a commit that was previously cherry-picked" do
|
||||
it do
|
||||
find("a[href='#modal-cherry-pick-commit']").click
|
||||
page.within('#modal-cherry-pick-commit') do
|
||||
uncheck 'create_merge_request'
|
||||
click_button 'Cherry-pick'
|
||||
end
|
||||
context 'when clicking cherry-pick from the dropdown for a commit on pipelines tab' do
|
||||
it 'launches the modal form' do
|
||||
create(:ci_empty_pipeline, sha: sha)
|
||||
visit project_commit_path(project, master_pickable_commit.id)
|
||||
find("a[href='#modal-cherry-pick-commit']").click
|
||||
page.within('#modal-cherry-pick-commit') do
|
||||
uncheck 'create_merge_request'
|
||||
click_button 'Cherry-pick'
|
||||
click_link 'Pipelines'
|
||||
|
||||
open_modal
|
||||
|
||||
page.within(modal_selector) do
|
||||
expect(page).to have_content('Cherry-pick this commit')
|
||||
end
|
||||
expect(page).to have_content('Sorry, we cannot cherry-pick this commit automatically.')
|
||||
end
|
||||
end
|
||||
|
||||
context "I cherry-pick a commit in a new merge request", :js do
|
||||
it do
|
||||
find('.header-action-buttons a.dropdown-toggle').click
|
||||
find("a[href='#modal-cherry-pick-commit']").click
|
||||
page.within('#modal-cherry-pick-commit') do
|
||||
click_button 'Cherry-pick'
|
||||
context 'when starting from the commit tab' do
|
||||
before do
|
||||
visit project_commit_path(project, master_pickable_commit.id)
|
||||
end
|
||||
|
||||
context 'when cherry-picking a commit' do
|
||||
specify do
|
||||
cherry_pick_commit
|
||||
|
||||
expect(page).to have_content('The commit has been successfully cherry-picked into master.')
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
context 'when cherry-picking a merge commit' do
|
||||
specify do
|
||||
cherry_pick_commit
|
||||
|
||||
expect(page).to have_content("The commit has been successfully cherry-picked into cherry-pick-#{master_pickable_commit.short_id}. You can now submit a merge request to get this change into the original branch.")
|
||||
expect(page).to have_content("From cherry-pick-#{master_pickable_commit.short_id} into master")
|
||||
expect(page).to have_content('The commit has been successfully cherry-picked into master.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cherry-picking a commit that was previously cherry-picked' do
|
||||
specify do
|
||||
cherry_pick_commit
|
||||
|
||||
visit project_commit_path(project, master_pickable_commit.id)
|
||||
|
||||
cherry_pick_commit
|
||||
|
||||
expect(page).to have_content('Sorry, we cannot cherry-pick this commit automatically.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cherry-picking a commit in a new merge request' do
|
||||
specify do
|
||||
cherry_pick_commit(create_merge_request: true)
|
||||
|
||||
expect(page).to have_content("The commit has been successfully cherry-picked into cherry-pick-#{master_pickable_commit.short_id}. You can now submit a merge request to get this change into the original branch.")
|
||||
expect(page).to have_content("From cherry-pick-#{master_pickable_commit.short_id} into master")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when I cherry-picking a commit from a different branch' do
|
||||
specify do
|
||||
open_modal
|
||||
|
||||
page.within(modal_selector) do
|
||||
click_button 'master'
|
||||
end
|
||||
|
||||
page.within("#{modal_selector} .dropdown-menu") do
|
||||
find('[data-testid="dropdown-search-box"]').set('feature')
|
||||
wait_for_requests
|
||||
click_button 'feature'
|
||||
end
|
||||
|
||||
submit_cherry_pick
|
||||
|
||||
expect(page).to have_content('The commit has been successfully cherry-picked into feature.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project is archived' do
|
||||
let(:project) { create(:project, :repository, :archived, namespace: user.namespace) }
|
||||
|
||||
it 'does not show the cherry-pick link' do
|
||||
open_dropdown
|
||||
|
||||
expect(page).not_to have_text("Cherry-pick")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "I cherry-pick a commit from a different branch", :js do
|
||||
it do
|
||||
find('.header-action-buttons a.dropdown-toggle').click
|
||||
find(:css, "a[href='#modal-cherry-pick-commit']").click
|
||||
def cherry_pick_commit(create_merge_request: false)
|
||||
open_modal
|
||||
|
||||
page.within('#modal-cherry-pick-commit') do
|
||||
click_button 'master'
|
||||
end
|
||||
submit_cherry_pick(create_merge_request: create_merge_request)
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
def open_dropdown
|
||||
find('.header-action-buttons .dropdown').click
|
||||
end
|
||||
|
||||
page.within('#modal-cherry-pick-commit .dropdown-menu') do
|
||||
find('.dropdown-input input').set('feature')
|
||||
wait_for_requests
|
||||
click_link "feature"
|
||||
end
|
||||
def open_modal
|
||||
open_dropdown
|
||||
find('[data-testid="cherry-pick-commit-link"]').click
|
||||
end
|
||||
|
||||
page.within('#modal-cherry-pick-commit') do
|
||||
uncheck 'create_merge_request'
|
||||
click_button 'Cherry-pick'
|
||||
end
|
||||
|
||||
expect(page).to have_content('The commit has been successfully cherry-picked into feature.')
|
||||
def submit_cherry_pick(create_merge_request: false)
|
||||
page.within(modal_selector) do
|
||||
uncheck('create_merge_request') unless create_merge_request
|
||||
click_button('Cherry-pick')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project is archived' do
|
||||
let(:project) { create(:project, :repository, :archived, namespace: group) }
|
||||
|
||||
it 'does not show the cherry-pick link' do
|
||||
find('.header-action-buttons a.dropdown-toggle').click
|
||||
|
||||
expect(page).not_to have_text("Cherry-pick")
|
||||
expect(page).not_to have_css("a[href='#modal-cherry-pick-commit']")
|
||||
end
|
||||
def modal_selector
|
||||
'[data-testid="modal-commit"]'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,58 +6,89 @@ RSpec.describe 'User reverts a commit', :js do
|
|||
include RepoHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:project) { create(:project, :repository, namespace: user.namespace) }
|
||||
let!(:project) { create_default(:project, :repository, namespace: user.namespace) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
visit(project_commit_path(project, sample_commit.id))
|
||||
context 'when clicking revert from the dropdown for a commit on pipelines tab' do
|
||||
it 'launches the modal and is able to submit the revert' do
|
||||
sha = '7d3b0f7cff5f37573aea97cebfd5692ea1689924'
|
||||
create(:ci_empty_pipeline, sha: sha)
|
||||
visit project_commit_path(project, project.commit(sha).id)
|
||||
click_link 'Pipelines'
|
||||
|
||||
open_modal
|
||||
|
||||
page.within(modal_selector) do
|
||||
expect(page).to have_content('Revert this commit')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when starting from the commit tab' do
|
||||
before do
|
||||
visit project_commit_path(project, sample_commit.id)
|
||||
end
|
||||
|
||||
context 'without creating a new merge request' do
|
||||
it 'reverts a commit' do
|
||||
revert_commit
|
||||
|
||||
expect(page).to have_content('The commit has been successfully reverted.')
|
||||
end
|
||||
|
||||
it 'does not revert a previously reverted commit' do
|
||||
revert_commit
|
||||
# Visit the comment again once it was reverted.
|
||||
visit project_commit_path(project, sample_commit.id)
|
||||
|
||||
revert_commit
|
||||
|
||||
expect(page).to have_content('Sorry, we cannot revert this commit automatically.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with creating a new merge request' do
|
||||
it 'reverts a commit' do
|
||||
revert_commit(create_merge_request: true)
|
||||
|
||||
expect(page).to have_content('The commit has been successfully reverted. You can now submit a merge request to get this change into the original branch.')
|
||||
expect(page).to have_content("From revert-#{Commit.truncate_sha(sample_commit.id)} into master")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project is archived' do
|
||||
let(:project) { create(:project, :repository, :archived, namespace: user.namespace) }
|
||||
|
||||
it 'does not show the revert link' do
|
||||
open_dropdown
|
||||
|
||||
expect(page).not_to have_link('Revert')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def revert_commit(create_merge_request: false)
|
||||
find('.header-action-buttons .dropdown').click
|
||||
find('[data-testid="revert-commit-link"]').click
|
||||
open_modal
|
||||
|
||||
page.within('[data-testid="modal-commit"]') do
|
||||
page.within(modal_selector) do
|
||||
uncheck('create_merge_request') unless create_merge_request
|
||||
click_button('Revert')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without creating a new merge request' do
|
||||
it 'reverts a commit' do
|
||||
revert_commit
|
||||
|
||||
expect(page).to have_content('The commit has been successfully reverted.')
|
||||
end
|
||||
|
||||
it 'does not revert a previously reverted commit' do
|
||||
revert_commit
|
||||
# Visit the comment again once it was reverted.
|
||||
visit project_commit_path(project, sample_commit.id)
|
||||
|
||||
revert_commit
|
||||
|
||||
expect(page).to have_content('Sorry, we cannot revert this commit automatically.')
|
||||
end
|
||||
def open_dropdown
|
||||
find('.header-action-buttons .dropdown').click
|
||||
end
|
||||
|
||||
context 'with creating a new merge request' do
|
||||
it 'reverts a commit' do
|
||||
revert_commit(create_merge_request: true)
|
||||
|
||||
expect(page).to have_content('The commit has been successfully reverted. You can now submit a merge request to get this change into the original branch.')
|
||||
expect(page).to have_content("From revert-#{Commit.truncate_sha(sample_commit.id)} into master")
|
||||
end
|
||||
def open_modal
|
||||
open_dropdown
|
||||
find('[data-testid="revert-commit-link"]').click
|
||||
end
|
||||
|
||||
context 'when the project is archived' do
|
||||
let(:project) { create(:project, :repository, :archived, namespace: user.namespace) }
|
||||
|
||||
it 'does not show the revert link' do
|
||||
find('.header-action-buttons .dropdown').click
|
||||
|
||||
expect(page).not_to have_link('Revert')
|
||||
end
|
||||
def modal_selector
|
||||
'[data-testid="modal-commit"]'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ RSpec.describe 'Pipeline', :js do
|
|||
sign_in(user)
|
||||
project.add_role(user, role)
|
||||
stub_feature_flags(graphql_pipeline_details: false)
|
||||
stub_feature_flags(graphql_pipeline_details_users: false)
|
||||
end
|
||||
|
||||
shared_context 'pipeline builds' do
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ RSpec.describe 'Pipelines', :js do
|
|||
before do
|
||||
sign_in(user)
|
||||
stub_feature_flags(graphql_pipeline_details: false)
|
||||
stub_feature_flags(graphql_pipeline_details_users: false)
|
||||
project.add_developer(user)
|
||||
project.update!(auto_devops_attributes: { enabled: false })
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@ const testCases = [
|
|||
class: 'status-box-open',
|
||||
icon: 'issue-open-m',
|
||||
},
|
||||
{
|
||||
name: 'Open',
|
||||
state: 'locked',
|
||||
class: 'status-box-open',
|
||||
icon: 'issue-open-m',
|
||||
},
|
||||
{
|
||||
name: 'Closed',
|
||||
state: 'closed',
|
||||
|
|
|
|||
|
|
@ -27,8 +27,12 @@ describe('Single Stat Chart component', () => {
|
|||
|
||||
describe('computed', () => {
|
||||
describe('statValue', () => {
|
||||
it('should interpolate the value and unit props', () => {
|
||||
expect(findChart().props('value')).toBe('1.00MB');
|
||||
it('should display the correct value', () => {
|
||||
expect(findChart().props('value')).toBe('1.00');
|
||||
});
|
||||
|
||||
it('should display the correct value unit', () => {
|
||||
expect(findChart().props('unit')).toBe('MB');
|
||||
});
|
||||
|
||||
it('should change the value representation to a percentile one', () => {
|
||||
|
|
@ -36,7 +40,8 @@ describe('Single Stat Chart component', () => {
|
|||
graphData: singleStatGraphData({ max_value: 120 }, { value: 91 }),
|
||||
});
|
||||
|
||||
expect(findChart().props('value')).toContain('75.83%');
|
||||
expect(findChart().props('value')).toBe('75.83');
|
||||
expect(findChart().props('unit')).toBe('%');
|
||||
});
|
||||
|
||||
it('should display NaN for non numeric maxValue values', () => {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ describe('TerraformPlan', () => {
|
|||
|
||||
it('diplays the header text with a name', () => {
|
||||
expect(wrapper.text()).toContain(
|
||||
`The Terraform report ${validPlanWithName.job_name} was generated in your pipelines.`,
|
||||
`The report ${validPlanWithName.job_name} was generated in your pipelines.`,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ describe('TerraformPlan', () => {
|
|||
});
|
||||
|
||||
it('diplays the header text without a name', () => {
|
||||
expect(wrapper.text()).toContain('A Terraform report was generated in your pipelines.');
|
||||
expect(wrapper.text()).toContain('A report was generated in your pipelines.');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ describe('TerraformPlan', () => {
|
|||
|
||||
it('diplays the header text with a name', () => {
|
||||
expect(wrapper.text()).toContain(
|
||||
`The Terraform report ${invalidPlanWithName.job_name} failed to generate.`,
|
||||
`The report ${invalidPlanWithName.job_name} failed to generate.`,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ describe('TerraformPlan', () => {
|
|||
});
|
||||
|
||||
it('diplays the header text without a name', () => {
|
||||
expect(wrapper.text()).toContain('A Terraform report failed to generate.');
|
||||
expect(wrapper.text()).toContain('A report failed to generate.');
|
||||
});
|
||||
|
||||
it('does not render button because url is missing', () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import Api from '~/api';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { securityReportDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
|
||||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
|
|
@ -12,11 +14,14 @@ import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
|
|||
import mockData from './mock_data';
|
||||
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
|
||||
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
|
||||
import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql';
|
||||
|
||||
jest.mock('~/smart_interval');
|
||||
|
||||
jest.mock('~/lib/utils/favicon');
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('MrWidgetOptions', () => {
|
||||
let wrapper;
|
||||
let mock;
|
||||
|
|
@ -41,7 +46,7 @@ describe('MrWidgetOptions', () => {
|
|||
gon.features = {};
|
||||
});
|
||||
|
||||
const createComponent = (mrData = mockData) => {
|
||||
const createComponent = (mrData = mockData, options = {}) => {
|
||||
if (wrapper) {
|
||||
wrapper.destroy();
|
||||
}
|
||||
|
|
@ -50,6 +55,7 @@ describe('MrWidgetOptions', () => {
|
|||
propsData: {
|
||||
mrData: { ...mrData },
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
return axios.waitForAll();
|
||||
|
|
@ -815,36 +821,37 @@ describe('MrWidgetOptions', () => {
|
|||
|
||||
describe('security widget', () => {
|
||||
describe.each`
|
||||
context | hasPipeline | reportType | isFlagEnabled | shouldRender
|
||||
${'security report and flag enabled'} | ${true} | ${'sast'} | ${true} | ${true}
|
||||
${'security report and flag disabled'} | ${true} | ${'sast'} | ${false} | ${false}
|
||||
${'no security report and flag enabled'} | ${true} | ${'foo'} | ${true} | ${false}
|
||||
${'no pipeline and flag enabled'} | ${false} | ${'sast'} | ${true} | ${false}
|
||||
`('given $context', ({ hasPipeline, reportType, isFlagEnabled, shouldRender }) => {
|
||||
context | hasPipeline | isFlagEnabled | shouldRender
|
||||
${'has pipeline and flag enabled'} | ${true} | ${true} | ${true}
|
||||
${'has pipeline and flag disabled'} | ${true} | ${false} | ${false}
|
||||
${'no pipeline and flag enabled'} | ${false} | ${true} | ${false}
|
||||
`('given $context', ({ hasPipeline, isFlagEnabled, shouldRender }) => {
|
||||
beforeEach(() => {
|
||||
gon.features.coreSecurityMrWidget = isFlagEnabled;
|
||||
|
||||
if (hasPipeline) {
|
||||
jest.spyOn(Api, 'pipelineJobs').mockResolvedValue({
|
||||
data: [{ artifacts: [{ file_type: reportType }] }],
|
||||
});
|
||||
}
|
||||
|
||||
return createComponent({
|
||||
const mrData = {
|
||||
...mockData,
|
||||
...(hasPipeline ? {} : { pipeline: undefined }),
|
||||
...(hasPipeline ? {} : { pipeline: null }),
|
||||
};
|
||||
|
||||
// Override top-level mocked requests, which always use a fresh copy of
|
||||
// mockData, which always includes the full pipeline object.
|
||||
mock.onGet(mockData.merge_request_widget_path).reply(() => [200, mrData]);
|
||||
mock.onGet(mockData.merge_request_cached_widget_path).reply(() => [200, mrData]);
|
||||
|
||||
return createComponent(mrData, {
|
||||
apolloProvider: createMockApollo([
|
||||
[
|
||||
securityReportDownloadPathsQuery,
|
||||
async () => ({ data: securityReportDownloadPathsQueryResponse }),
|
||||
],
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
if (shouldRender) {
|
||||
it('renders', () => {
|
||||
expect(findSecurityMrWidget().exists()).toBe(true);
|
||||
});
|
||||
} else {
|
||||
it('does not render', () => {
|
||||
expect(findSecurityMrWidget().exists()).toBe(false);
|
||||
});
|
||||
}
|
||||
it(shouldRender ? 'renders' : 'does not render', () => {
|
||||
expect(findSecurityMrWidget().exists()).toBe(shouldRender);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ exports[`gfm_autocomplete/utils labels config shows the title in the menu item 1
|
|||
exports[`gfm_autocomplete/utils members config shows an avatar character, name, parent name, and count in the menu item for a group 1`] = `
|
||||
"
|
||||
<div class=\\"gl-display-flex gl-align-items-center\\">
|
||||
<div class=\\"gl-avatar gl-avatar-s24 gl-flex-shrink-0 gl-rounded-small
|
||||
<div class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-rounded-small
|
||||
gl-display-flex gl-align-items-center gl-justify-content-center\\" aria-hidden=\\"true\\">
|
||||
G</div>
|
||||
<div class=\\"gl-font-sm gl-line-height-normal gl-ml-3\\">
|
||||
<div class=\\"gl-line-height-normal gl-ml-4\\">
|
||||
<div>1-1s <script>alert('hi')</script> (2)</div>
|
||||
<div class=\\"gl-text-gray-700\\">GitLab Support Team</div>
|
||||
</div>
|
||||
|
|
@ -36,8 +36,8 @@ exports[`gfm_autocomplete/utils members config shows an avatar character, name,
|
|||
exports[`gfm_autocomplete/utils members config shows the avatar, name and username in the menu item for a user 1`] = `
|
||||
"
|
||||
<div class=\\"gl-display-flex gl-align-items-center\\">
|
||||
<img class=\\"gl-avatar gl-avatar-s24 gl-flex-shrink-0 gl-avatar-circle\\" src=\\"/uploads/-/system/user/avatar/123456/avatar.png\\" alt=\\"\\" />
|
||||
<div class=\\"gl-font-sm gl-line-height-normal gl-ml-3\\">
|
||||
<img class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-avatar-circle\\" src=\\"/uploads/-/system/user/avatar/123456/avatar.png\\" alt=\\"\\" />
|
||||
<div class=\\"gl-line-height-normal gl-ml-4\\">
|
||||
<div>My Name <script>alert('hi')</script></div>
|
||||
<div class=\\"gl-text-gray-700\\">@myusername</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -322,6 +322,23 @@ export const secretScanningDiffSuccessMock = {
|
|||
head_report_created_at: '2020-01-10T10:00:00.000Z',
|
||||
};
|
||||
|
||||
export const securityReportDownloadPathsQueryNoArtifactsResponse = {
|
||||
project: {
|
||||
mergeRequest: {
|
||||
headPipeline: {
|
||||
id: 'gid://gitlab/Ci::Pipeline/176',
|
||||
jobs: {
|
||||
nodes: [],
|
||||
__typename: 'CiJobConnection',
|
||||
},
|
||||
__typename: 'Pipeline',
|
||||
},
|
||||
__typename: 'MergeRequest',
|
||||
},
|
||||
__typename: 'Project',
|
||||
},
|
||||
};
|
||||
|
||||
export const securityReportDownloadPathsQueryResponse = {
|
||||
project: {
|
||||
mergeRequest: {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import { trimText } from 'helpers/text_helper';
|
|||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import {
|
||||
expectedDownloadDropdownProps,
|
||||
securityReportDownloadPathsQueryNoArtifactsResponse,
|
||||
securityReportDownloadPathsQueryResponse,
|
||||
sastDiffSuccessMock,
|
||||
secretScanningDiffSuccessMock,
|
||||
} from 'jest/vue_shared/security_reports/mock_data';
|
||||
import Api from '~/api';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import {
|
||||
|
|
@ -60,6 +60,8 @@ describe('Security reports app', () => {
|
|||
|
||||
const pendingHandler = () => new Promise(() => {});
|
||||
const successHandler = () => Promise.resolve({ data: securityReportDownloadPathsQueryResponse });
|
||||
const successEmptyHandler = () =>
|
||||
Promise.resolve({ data: securityReportDownloadPathsQueryNoArtifactsResponse });
|
||||
const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] });
|
||||
const createMockApolloProvider = (handler) => {
|
||||
localVue.use(VueApollo);
|
||||
|
|
@ -69,178 +71,85 @@ describe('Security reports app', () => {
|
|||
return createMockApollo(requestHandlers);
|
||||
};
|
||||
|
||||
const anyParams = expect.any(Object);
|
||||
|
||||
const findDownloadDropdown = () => wrapper.find(SecurityReportDownloadDropdown);
|
||||
const findPipelinesTabAnchor = () => wrapper.find('[data-testid="show-pipelines"]');
|
||||
const findHelpIconComponent = () => wrapper.find(HelpIcon);
|
||||
const setupMockJobArtifact = (reportType) => {
|
||||
jest
|
||||
.spyOn(Api, 'pipelineJobs')
|
||||
.mockResolvedValue({ data: [{ artifacts: [{ file_type: reportType }] }] });
|
||||
};
|
||||
const expectPipelinesTabAnchor = () => {
|
||||
const mrTabsMock = { tabShown: jest.fn() };
|
||||
window.mrTabs = mrTabsMock;
|
||||
findPipelinesTabAnchor().trigger('click');
|
||||
expect(mrTabsMock.tabShown.mock.calls).toEqual([['pipelines']]);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
delete window.mrTabs;
|
||||
});
|
||||
|
||||
describe.each([false, true])(
|
||||
'given the coreSecurityMrWidgetCounts feature flag is %p',
|
||||
(coreSecurityMrWidgetCounts) => {
|
||||
const createComponentWithFlag = (options) =>
|
||||
createComponent(
|
||||
merge(
|
||||
{
|
||||
provide: {
|
||||
glFeatures: {
|
||||
coreSecurityMrWidgetCounts,
|
||||
},
|
||||
},
|
||||
},
|
||||
options,
|
||||
),
|
||||
);
|
||||
|
||||
describe.each(SecurityReportsApp.reportTypes)('given a report type %p', (reportType) => {
|
||||
beforeEach(() => {
|
||||
window.mrTabs = { tabShown: jest.fn() };
|
||||
setupMockJobArtifact(reportType);
|
||||
createComponentWithFlag();
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('calls the pipelineJobs API correctly', () => {
|
||||
expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
|
||||
expect(Api.pipelineJobs).toHaveBeenCalledWith(
|
||||
props.projectId,
|
||||
props.pipelineId,
|
||||
anyParams,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders the expected message', () => {
|
||||
expect(wrapper.text()).toMatchInterpolatedText(
|
||||
SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance,
|
||||
);
|
||||
});
|
||||
|
||||
describe('clicking the anchor to the pipelines tab', () => {
|
||||
it('calls the mrTabs.tabShown global', () => {
|
||||
expectPipelinesTabAnchor();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a help link', () => {
|
||||
expect(findHelpIconComponent().props()).toEqual({
|
||||
helpPath: props.securityReportsDocsPath,
|
||||
discoverProjectSecurityPath: props.discoverProjectSecurityPath,
|
||||
});
|
||||
});
|
||||
describe('given the artifacts query is loading', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
apolloProvider: createMockApolloProvider(pendingHandler),
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a report type "foo"', () => {
|
||||
beforeEach(() => {
|
||||
setupMockJobArtifact('foo');
|
||||
createComponentWithFlag();
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
// TODO: Remove this assertion as part of
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/issues/273431
|
||||
it('initially renders nothing', () => {
|
||||
expect(wrapper.html()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
it('calls the pipelineJobs API correctly', () => {
|
||||
expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
|
||||
expect(Api.pipelineJobs).toHaveBeenCalledWith(
|
||||
props.projectId,
|
||||
props.pipelineId,
|
||||
anyParams,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders nothing', () => {
|
||||
expect(wrapper.html()).toBe('');
|
||||
});
|
||||
describe('given the artifacts query loads successfully', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
apolloProvider: createMockApolloProvider(successHandler),
|
||||
});
|
||||
});
|
||||
|
||||
describe('security artifacts on last page of multi-page response', () => {
|
||||
const numPages = 3;
|
||||
it('renders the download dropdown', () => {
|
||||
expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(Api, 'pipelineJobs')
|
||||
.mockImplementation(async (projectId, pipelineId, { page }) => {
|
||||
const requestedPage = parseInt(page, 10);
|
||||
if (requestedPage < numPages) {
|
||||
return {
|
||||
// Some jobs with no relevant artifacts
|
||||
data: [{}, {}],
|
||||
headers: { 'x-next-page': String(requestedPage + 1) },
|
||||
};
|
||||
} else if (requestedPage === numPages) {
|
||||
return {
|
||||
data: [{ artifacts: [{ file_type: SecurityReportsApp.reportTypes[0] }] }],
|
||||
};
|
||||
}
|
||||
it('renders the expected message', () => {
|
||||
expect(wrapper.text()).toContain(SecurityReportsApp.i18n.scansHaveRun);
|
||||
});
|
||||
|
||||
throw new Error('Test failed due to request of non-existent jobs page');
|
||||
});
|
||||
|
||||
createComponentWithFlag();
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('fetches all pages', () => {
|
||||
expect(Api.pipelineJobs).toHaveBeenCalledTimes(numPages);
|
||||
});
|
||||
|
||||
it('renders the expected message', () => {
|
||||
expect(wrapper.text()).toMatchInterpolatedText(
|
||||
SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance,
|
||||
);
|
||||
});
|
||||
it('renders a help link', () => {
|
||||
expect(findHelpIconComponent().props()).toEqual({
|
||||
helpPath: props.securityReportsDocsPath,
|
||||
discoverProjectSecurityPath: props.discoverProjectSecurityPath,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('given an error from the API', () => {
|
||||
let error;
|
||||
|
||||
beforeEach(() => {
|
||||
error = new Error('an error');
|
||||
jest.spyOn(Api, 'pipelineJobs').mockRejectedValue(error);
|
||||
createComponentWithFlag();
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('calls the pipelineJobs API correctly', () => {
|
||||
expect(Api.pipelineJobs).toHaveBeenCalledTimes(1);
|
||||
expect(Api.pipelineJobs).toHaveBeenCalledWith(
|
||||
props.projectId,
|
||||
props.pipelineId,
|
||||
anyParams,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders nothing', () => {
|
||||
expect(wrapper.html()).toBe('');
|
||||
});
|
||||
|
||||
it('calls createFlash correctly', () => {
|
||||
expect(createFlash.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
message: SecurityReportsApp.i18n.apiError,
|
||||
captureError: true,
|
||||
error,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
describe('given the artifacts query loads successfully with no artifacts', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
apolloProvider: createMockApolloProvider(successEmptyHandler),
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: Remove this assertion as part of
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/issues/273431
|
||||
it('initially renders nothing', () => {
|
||||
expect(wrapper.html()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('given the artifacts query fails', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
apolloProvider: createMockApolloProvider(failureHandler),
|
||||
});
|
||||
});
|
||||
|
||||
it('calls createFlash correctly', () => {
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: SecurityReportsApp.i18n.apiError,
|
||||
captureError: true,
|
||||
error: expect.any(Error),
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Remove this assertion as part of
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/issues/273431
|
||||
it('renders nothing', () => {
|
||||
expect(wrapper.html()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('given the coreSecurityMrWidgetCounts feature flag is enabled', () => {
|
||||
let mock;
|
||||
|
|
@ -253,6 +162,7 @@ describe('Security reports app', () => {
|
|||
coreSecurityMrWidgetCounts: true,
|
||||
},
|
||||
},
|
||||
apolloProvider: createMockApolloProvider(successHandler),
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -274,11 +184,7 @@ describe('Security reports app', () => {
|
|||
${REPORT_TYPE_SECRET_DETECTION} | ${'secretScanningComparisonPath'} | ${SECRET_SCANNING_COMPARISON_PATH} | ${secretScanningDiffSuccessMock} | ${SECRET_SCANNING_SUCCESS_MESSAGE}
|
||||
`(
|
||||
'given a $pathProp and $reportType artifact',
|
||||
({ reportType, pathProp, path, successResponse, successMessage }) => {
|
||||
beforeEach(() => {
|
||||
setupMockJobArtifact(reportType);
|
||||
});
|
||||
|
||||
({ pathProp, path, successResponse, successMessage }) => {
|
||||
describe('when loading', () => {
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios, { delayResponse: 1 });
|
||||
|
|
@ -294,11 +200,11 @@ describe('Security reports app', () => {
|
|||
});
|
||||
|
||||
it('should have loading message', () => {
|
||||
expect(wrapper.text()).toBe('Security scanning is loading');
|
||||
expect(wrapper.text()).toContain('Security scanning is loading');
|
||||
});
|
||||
|
||||
it('should not render the pipeline tab anchor', () => {
|
||||
expect(findPipelinesTabAnchor().exists()).toBe(false);
|
||||
it('renders the download dropdown', () => {
|
||||
expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -319,8 +225,8 @@ describe('Security reports app', () => {
|
|||
expect(trimText(wrapper.text())).toContain(successMessage);
|
||||
});
|
||||
|
||||
it('should render the pipeline tab anchor', () => {
|
||||
expectPipelinesTabAnchor();
|
||||
it('renders the download dropdown', () => {
|
||||
expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -341,125 +247,25 @@ describe('Security reports app', () => {
|
|||
expect(trimText(wrapper.text())).toContain('Loading resulted in an error');
|
||||
});
|
||||
|
||||
it('should render the pipeline tab anchor', () => {
|
||||
expectPipelinesTabAnchor();
|
||||
it('renders the download dropdown', () => {
|
||||
expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the comparison endpoint is not provided', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet(path).replyOnce(500);
|
||||
|
||||
createComponentWithFlagEnabled();
|
||||
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
it('renders the basic scansHaveRun message', () => {
|
||||
expect(wrapper.text()).toContain(SecurityReportsApp.i18n.scansHaveRun);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('given coreSecurityMrWidgetDownloads feature flag is enabled', () => {
|
||||
const createComponentWithFlagEnabled = (options) =>
|
||||
createComponent(
|
||||
merge(options, {
|
||||
provide: {
|
||||
glFeatures: {
|
||||
coreSecurityMrWidgetDownloads: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
describe('given the query is loading', () => {
|
||||
beforeEach(() => {
|
||||
createComponentWithFlagEnabled({
|
||||
apolloProvider: createMockApolloProvider(pendingHandler),
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Remove this assertion as part of
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/issues/273431
|
||||
it('initially renders nothing', () => {
|
||||
expect(wrapper.html()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('given the query loads successfully', () => {
|
||||
beforeEach(() => {
|
||||
createComponentWithFlagEnabled({
|
||||
apolloProvider: createMockApolloProvider(successHandler),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the download dropdown', () => {
|
||||
expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
|
||||
});
|
||||
|
||||
it('renders the expected message', () => {
|
||||
const text = wrapper.text();
|
||||
expect(text).not.toContain(SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance);
|
||||
expect(text).toContain(SecurityReportsApp.i18n.scansHaveRun);
|
||||
});
|
||||
|
||||
it('should not render the pipeline tab anchor', () => {
|
||||
expect(findPipelinesTabAnchor().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('given the query fails', () => {
|
||||
beforeEach(() => {
|
||||
createComponentWithFlagEnabled({
|
||||
apolloProvider: createMockApolloProvider(failureHandler),
|
||||
});
|
||||
});
|
||||
|
||||
it('calls createFlash correctly', () => {
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: SecurityReportsApp.i18n.apiError,
|
||||
captureError: true,
|
||||
error: expect.any(Error),
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Remove this assertion as part of
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/issues/273431
|
||||
it('renders nothing', () => {
|
||||
expect(wrapper.html()).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('given coreSecurityMrWidgetCounts and coreSecurityMrWidgetDownloads feature flags are enabled', () => {
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
mock.onGet(SAST_COMPARISON_PATH).replyOnce(200, sastDiffSuccessMock);
|
||||
mock.onGet(SECRET_SCANNING_COMPARISON_PATH).replyOnce(200, secretScanningDiffSuccessMock);
|
||||
createComponent({
|
||||
propsData: {
|
||||
sastComparisonPath: SAST_COMPARISON_PATH,
|
||||
secretScanningComparisonPath: SECRET_SCANNING_COMPARISON_PATH,
|
||||
},
|
||||
provide: {
|
||||
glFeatures: {
|
||||
coreSecurityMrWidgetCounts: true,
|
||||
coreSecurityMrWidgetDownloads: true,
|
||||
},
|
||||
},
|
||||
apolloProvider: createMockApolloProvider(successHandler),
|
||||
});
|
||||
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('renders the download dropdown', () => {
|
||||
expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
|
||||
});
|
||||
|
||||
it('renders the expected counts message', () => {
|
||||
expect(trimText(wrapper.text())).toContain(
|
||||
'Security scanning detected 3 potential vulnerabilities 2 Critical 1 High and 0 Others',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not render the pipeline tab anchor', () => {
|
||||
expect(findPipelinesTabAnchor().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,20 +7,13 @@ RSpec.describe CommitsHelper do
|
|||
context 'when current_user exists' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(double('User'))
|
||||
allow(helper).to receive(:can_collaborate_with_project?).and_return(true)
|
||||
end
|
||||
|
||||
it 'renders a div for Vue' do
|
||||
result = helper.revert_commit_link('_commit_', '_path_', pajamas: true)
|
||||
result = helper.revert_commit_link
|
||||
|
||||
expect(result).to include('js-revert-commit-trigger')
|
||||
end
|
||||
|
||||
it 'does not render a div for Vue' do
|
||||
result = helper.revert_commit_link('_commit_', '_path_')
|
||||
|
||||
expect(result).not_to include('js-revert-commit-trigger')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current_user does not exist' do
|
||||
|
|
@ -29,7 +22,33 @@ RSpec.describe CommitsHelper do
|
|||
end
|
||||
|
||||
it 'does not render anything' do
|
||||
result = helper.revert_commit_link(double('Commit'), '_path_')
|
||||
result = helper.revert_commit_link
|
||||
|
||||
expect(result).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cherry_pick_commit_link' do
|
||||
context 'when current_user exists' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(double('User'))
|
||||
end
|
||||
|
||||
it 'renders a div for Vue' do
|
||||
result = helper.cherry_pick_commit_link
|
||||
|
||||
expect(result).to include('js-cherry-pick-commit-trigger')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current_user does not exist' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(nil)
|
||||
end
|
||||
|
||||
it 'does not render anything' do
|
||||
result = helper.cherry_pick_commit_link
|
||||
|
||||
expect(result).to be_nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -323,20 +323,6 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
|
|||
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is turned off' do
|
||||
let(:values) do
|
||||
{ include: full_local_file_path }
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(variables_in_include_section_ci: false)
|
||||
end
|
||||
|
||||
it 'does not expand the variables' do
|
||||
expect(subject[0].location).to eq('$CI_PROJECT_PATH' + local_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,6 +56,15 @@ RSpec.describe Pages::LookupPath do
|
|||
|
||||
include_examples 'uses disk storage'
|
||||
|
||||
it 'return nil when legacy storage is disabled and there is no deployment' do
|
||||
stub_feature_flags(pages_serve_from_legacy_storage: false)
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_exception)
|
||||
.with(described_class::LegacyStorageDisabledError)
|
||||
.and_call_original
|
||||
|
||||
expect(source).to eq(nil)
|
||||
end
|
||||
|
||||
context 'when there is pages deployment' do
|
||||
let(:deployment) { create(:pages_deployment, project: project) }
|
||||
|
||||
|
|
|
|||
|
|
@ -26,31 +26,34 @@ RSpec.describe Pages::VirtualDomain do
|
|||
|
||||
describe '#lookup_paths' do
|
||||
let(:project_a) { instance_double(Project) }
|
||||
let(:project_z) { instance_double(Project) }
|
||||
let(:pages_lookup_path_a) { instance_double(Pages::LookupPath, prefix: 'aaa') }
|
||||
let(:pages_lookup_path_z) { instance_double(Pages::LookupPath, prefix: 'zzz') }
|
||||
let(:project_b) { instance_double(Project) }
|
||||
let(:project_c) { instance_double(Project) }
|
||||
let(:pages_lookup_path_a) { instance_double(Pages::LookupPath, prefix: 'aaa', source: { type: 'zip', path: 'https://example.com' }) }
|
||||
let(:pages_lookup_path_b) { instance_double(Pages::LookupPath, prefix: 'bbb', source: { type: 'zip', path: 'https://example.com' }) }
|
||||
let(:pages_lookup_path_without_source) { instance_double(Pages::LookupPath, prefix: 'ccc', source: nil) }
|
||||
|
||||
context 'when there is pages domain provided' do
|
||||
let(:domain) { instance_double(PagesDomain) }
|
||||
|
||||
subject(:virtual_domain) { described_class.new([project_a, project_z], domain: domain) }
|
||||
subject(:virtual_domain) { described_class.new([project_a, project_b, project_c], domain: domain) }
|
||||
|
||||
it 'returns collection of projects pages lookup paths sorted by prefix in reverse' do
|
||||
expect(project_a).to receive(:pages_lookup_path).with(domain: domain, trim_prefix: nil).and_return(pages_lookup_path_a)
|
||||
expect(project_z).to receive(:pages_lookup_path).with(domain: domain, trim_prefix: nil).and_return(pages_lookup_path_z)
|
||||
expect(project_b).to receive(:pages_lookup_path).with(domain: domain, trim_prefix: nil).and_return(pages_lookup_path_b)
|
||||
expect(project_c).to receive(:pages_lookup_path).with(domain: domain, trim_prefix: nil).and_return(pages_lookup_path_without_source)
|
||||
|
||||
expect(virtual_domain.lookup_paths).to eq([pages_lookup_path_z, pages_lookup_path_a])
|
||||
expect(virtual_domain.lookup_paths).to eq([pages_lookup_path_b, pages_lookup_path_a])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is trim_prefix provided' do
|
||||
subject(:virtual_domain) { described_class.new([project_a, project_z], trim_prefix: 'group/') }
|
||||
subject(:virtual_domain) { described_class.new([project_a, project_b], trim_prefix: 'group/') }
|
||||
|
||||
it 'returns collection of projects pages lookup paths sorted by prefix in reverse' do
|
||||
expect(project_a).to receive(:pages_lookup_path).with(trim_prefix: 'group/', domain: nil).and_return(pages_lookup_path_a)
|
||||
expect(project_z).to receive(:pages_lookup_path).with(trim_prefix: 'group/', domain: nil).and_return(pages_lookup_path_z)
|
||||
expect(project_b).to receive(:pages_lookup_path).with(trim_prefix: 'group/', domain: nil).and_return(pages_lookup_path_b)
|
||||
|
||||
expect(virtual_domain.lookup_paths).to eq([pages_lookup_path_z, pages_lookup_path_a])
|
||||
expect(virtual_domain.lookup_paths).to eq([pages_lookup_path_b, pages_lookup_path_a])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
|
|||
it 'skips the repository' do
|
||||
expect(ContainerExpirationPolicies::CleanupService).not_to receive(:new)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:container_repository_id, repository.id)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:project_id, repository.project.id)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_status, :skipped)
|
||||
|
||||
expect { subject }.to change { ContainerRepository.waiting_for_cleanup.count }.from(1).to(0)
|
||||
|
|
@ -213,8 +214,10 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
|
|||
end
|
||||
|
||||
def expect_log_extra_metadata(service_response:, cleanup_status: :finished, truncated: false)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_status, cleanup_status)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:container_repository_id, repository.id)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:project_id, repository.project.id)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_status, cleanup_status)
|
||||
|
||||
%i[cleanup_tags_service_original_size cleanup_tags_service_before_truncate_size cleanup_tags_service_after_truncate_size cleanup_tags_service_before_delete_size].each do |field|
|
||||
value = service_response.payload[field]
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(field, value) unless value.nil?
|
||||
|
|
|
|||
|
|
@ -876,10 +876,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
|
||||
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
|
||||
|
||||
"@gitlab/ui@25.12.2":
|
||||
version "25.12.2"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.12.2.tgz#ad47680da4b067140e8d48a04e807352660b9cca"
|
||||
integrity sha512-y+uks00z+4kivTYu+l2mrjYT3nfnBS+xKWIUQ9xrkZVCC069V+DffPK+jVRzzhQ67hOMP5LVdaUEOcUplgFvGA==
|
||||
"@gitlab/ui@26.0.0":
|
||||
version "26.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-26.0.0.tgz#7fa93726478042b1570b2bd3b909217a31177b36"
|
||||
integrity sha512-X22mc3qVBAkfAZ2DRqbPnJ6upzjWlzGLWmHR4l+3MhOBbMBi4EXIuF19nixC5u8bjCMGkK3wMIiZj3C3HsmQ7A==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
"@gitlab/vue-toasted" "^1.3.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue