Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
352ce61b77
commit
1561d0c1f5
|
|
@ -194,7 +194,7 @@ workflow:
|
|||
|
||||
variables:
|
||||
PG_VERSION: "14"
|
||||
DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-rust-${RUST_VERSION}-node-${NODE_VERSION}-postgresql-${PG_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-${GIT_VERSION}-lfs-${LFS_VERSION}-chrome-${CHROME_VERSION}-yarn-${YARN_VERSION}-graphicsmagick-${GRAPHICSMAGICK_VERSION}"
|
||||
DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/ci/${BUILD_OS}-${OS_VERSION}-slim-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-node-${NODE_VERSION}-postgresql-${PG_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-${GIT_VERSION}-lfs-${LFS_VERSION}-chrome-${CHROME_VERSION}-yarn-${YARN_VERSION}-graphicsmagick-${GRAPHICSMAGICK_VERSION}"
|
||||
DEFAULT_JOB_TAG: "gitlab-org"
|
||||
GITLAB_LARGE_RUNNER_OPTIONAL: "gitlab-org" # overridden just in gitlab-org/gitlab
|
||||
GLCI_PRODUCTION_ASSETS_RUNNER_OPTIONAL: "gitlab-org-docker" # this is set to GITLAB_LARGE_RUNNER_OPTIONAL on gitlab-org/gitlab and default to docker runners for dind to work correctly
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ update-ruby-gems-coverage-cache-push:
|
|||
extends:
|
||||
- .default-retry
|
||||
- .ruby-gems-coverage-cache
|
||||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-slim-ruby-${RUBY_VERSION}-rust-${RUST_VERSION}
|
||||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/ci/${BUILD_OS}-${OS_VERSION}-slim-ruby-${RUBY_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-${GIT_VERSION}
|
||||
variables:
|
||||
BUNDLE_WITHOUT: "" # This is to override the variable defined in .gitlab-ci.yml
|
||||
BUNDLE_ONLY: "coverage"
|
||||
|
|
@ -651,6 +651,11 @@ rspec:merge-auto-explain-logs:
|
|||
needs: !reference ["rspec:coverage", "needs"]
|
||||
script:
|
||||
- scripts/merge-auto-explain-logs
|
||||
- |
|
||||
if [[ -f "$RSPEC_AUTO_EXPLAIN_LOG_PATH" ]] && [[ "$CI_PROJECT_PATH" == "gitlab-org/gitlab" ]] && [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
|
||||
source scripts/gitlab_component_helpers.sh
|
||||
extract_and_upload_fingerprints
|
||||
fi
|
||||
artifacts:
|
||||
name: auto-explain-logs
|
||||
expire_in: 31d
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
<script>
|
||||
import { GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import {
|
||||
I18N_DELETION_PROTECTION,
|
||||
DEL_ADJ_PERIOD_MAX_LIMIT_ERROR,
|
||||
DEL_ADJ_PERIOD_MIN_LIMIT_ERROR,
|
||||
DEL_ADJ_PERIOD_MAX_LIMIT,
|
||||
DEL_ADJ_PERIOD_MIN_LIMIT,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlFormGroup,
|
||||
GlFormInput,
|
||||
GlLink,
|
||||
},
|
||||
props: {
|
||||
deletionAdjournedPeriod: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
deletionAdjournedPeriod: this.deletionAdjournedPeriod,
|
||||
},
|
||||
invalidFeedback: '',
|
||||
};
|
||||
},
|
||||
i18n: I18N_DELETION_PROTECTION,
|
||||
helpPath: helpPagePath('administration/settings/visibility_and_access_controls', {
|
||||
anchor: 'delayed-project-deletion',
|
||||
}),
|
||||
inputId: 'application_setting_deletion_adjourned_period',
|
||||
computed: {
|
||||
state() {
|
||||
return this.invalidFeedback !== '' ? false : null;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
validate() {
|
||||
if (this.formData.deletionAdjournedPeriod > DEL_ADJ_PERIOD_MAX_LIMIT) {
|
||||
this.invalidFeedback = DEL_ADJ_PERIOD_MAX_LIMIT_ERROR;
|
||||
return;
|
||||
}
|
||||
if (this.formData.deletionAdjournedPeriod < DEL_ADJ_PERIOD_MIN_LIMIT) {
|
||||
this.invalidFeedback = DEL_ADJ_PERIOD_MIN_LIMIT_ERROR;
|
||||
return;
|
||||
}
|
||||
this.invalidFeedback = '';
|
||||
},
|
||||
onBlur() {
|
||||
this.validate();
|
||||
},
|
||||
onInvalid() {
|
||||
this.validate();
|
||||
this.$refs.formInput.$el.focus();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-form-group
|
||||
:label="$options.i18n.label"
|
||||
:label-for="$options.inputId"
|
||||
:state="state"
|
||||
:invalid-feedback="invalidFeedback"
|
||||
>
|
||||
<template #label-description>
|
||||
<span>{{ $options.i18n.helpText }}</span>
|
||||
<gl-link :href="$options.helpPath" target="_blank">{{ $options.i18n.learnMore }}</gl-link>
|
||||
</template>
|
||||
<div data-testid="deletion_adjourned_period_group" class="gl-flex gl-items-center">
|
||||
<gl-form-input
|
||||
:id="$options.inputId"
|
||||
ref="formInput"
|
||||
v-model="formData.deletionAdjournedPeriod"
|
||||
name="application_setting[deletion_adjourned_period]"
|
||||
data-testid="deletion_adjourned_period"
|
||||
width="xs"
|
||||
type="number"
|
||||
:min="1"
|
||||
:max="90"
|
||||
:state="state"
|
||||
@blur="onBlur"
|
||||
@invalid.prevent="onInvalid"
|
||||
/>
|
||||
<span class="gl-ml-3">{{ $options.i18n.days }}</span>
|
||||
</div>
|
||||
</gl-form-group>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { __, s__ } from '~/locale';
|
||||
|
||||
export const I18N_DELETION_PROTECTION = {
|
||||
label: s__('DeletionSettings|Deletion protection'),
|
||||
helpText: s__(
|
||||
'DeletionSettings|Period that deleted groups and projects will remain restorable for. Personal projects are always deleted immediately.',
|
||||
),
|
||||
learnMore: __('Learn more.'),
|
||||
days: __('days'),
|
||||
};
|
||||
|
||||
export const DEL_ADJ_PERIOD_MAX_LIMIT = 90;
|
||||
export const DEL_ADJ_PERIOD_MIN_LIMIT = 1;
|
||||
|
||||
export const DEL_ADJ_PERIOD_MAX_LIMIT_ERROR = s__(
|
||||
'DeletionSettings|Maximum deletion protection duration is 90 days.',
|
||||
);
|
||||
|
||||
export const DEL_ADJ_PERIOD_MIN_LIMIT_ERROR = s__(
|
||||
'DeletionSettings|Minimum deletion protection duration is 1 day.',
|
||||
);
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import Vue from 'vue';
|
||||
import { parseFormProps } from './utils';
|
||||
import FormGroup from './components/form_group.vue';
|
||||
|
||||
export const initAdminDeletionProtectionSettings = () => {
|
||||
const el = document.querySelector('#js-admin-deletion-protection-settings');
|
||||
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { deletionAdjournedPeriod } = parseFormProps(el.dataset);
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'AdminDeletionProtectionSettings',
|
||||
render(createElement) {
|
||||
return createElement(FormGroup, {
|
||||
props: {
|
||||
deletionAdjournedPeriod,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
|
||||
export const parseFormProps = ({
|
||||
deletionAdjournedPeriod,
|
||||
delayedGroupDeletion,
|
||||
delayedProjectDeletion,
|
||||
}) => ({
|
||||
deletionAdjournedPeriod:
|
||||
deletionAdjournedPeriod !== undefined ? parseInt(deletionAdjournedPeriod, 10) : undefined,
|
||||
delayedGroupDeletion: parseBoolean(delayedGroupDeletion),
|
||||
delayedProjectDeletion: parseBoolean(delayedProjectDeletion),
|
||||
});
|
||||
|
|
@ -23,10 +23,10 @@ export default {
|
|||
<div>
|
||||
<gl-card
|
||||
class="ci-card gl-rounded-lg gl-bg-section"
|
||||
header-class="gl-rounded-lg gl-px-0 gl-py-0 gl-bg-section gl-border-b-0"
|
||||
body-class="gl-pt-2 gl-pb-0 gl-px-2"
|
||||
header-class="gl-rounded-t-lg gl-px-0 gl-pt-0 gl-pb-2 gl-bg-section gl-border-b-section"
|
||||
body-class="gl-pt-2 gl-pb-0 gl-px-0"
|
||||
>
|
||||
<template #header>
|
||||
<template v-if="$scopedSlots.stages" #header>
|
||||
<slot name="stages"></slot>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { reportToSentry } from '~/ci/utils';
|
|||
import { __, s__, sprintf } from '~/locale';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import { FAILED_STATUS } from '~/ci/constants';
|
||||
import RootGraphLayout from './root_graph_layout.vue';
|
||||
import JobGroupDropdown from './job_group_dropdown.vue';
|
||||
import JobItem from './job_item.vue';
|
||||
|
|
@ -114,6 +115,12 @@ export default {
|
|||
stageName: this.name,
|
||||
});
|
||||
},
|
||||
nonFailedJobs() {
|
||||
return this.groups.filter((group) => group.status?.group !== FAILED_STATUS);
|
||||
},
|
||||
failedJobs() {
|
||||
return this.groups.filter((group) => group.status?.group === FAILED_STATUS);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('updateMeasurements');
|
||||
|
|
@ -176,7 +183,7 @@ export default {
|
|||
class="stage-column gl-relative gl-basis-full"
|
||||
data-testid="stage-column"
|
||||
>
|
||||
<template #stages>
|
||||
<template v-if="name" #stages>
|
||||
<div
|
||||
data-testid="stage-column-title"
|
||||
class="stage-column-title gl-pipeline-job-width gl-relative -gl-mb-2 gl-flex gl-justify-between gl-truncate gl-pl-4 gl-font-bold gl-leading-36"
|
||||
|
|
@ -198,37 +205,87 @@ export default {
|
|||
</div>
|
||||
</template>
|
||||
<template #jobs>
|
||||
<div v-if="failedJobs.length > 0">
|
||||
<div
|
||||
class="gl-my-2 gl-pl-4 gl-text-sm gl-font-bold gl-text-strong"
|
||||
data-testid="failed-jobs-title"
|
||||
>
|
||||
{{ s__('Pipelines|Failed jobs') }}
|
||||
</div>
|
||||
|
||||
<div class="gl-px-2">
|
||||
<div
|
||||
v-for="group in failedJobs"
|
||||
:id="groupId(group)"
|
||||
:key="getGroupId(group)"
|
||||
data-testid="stage-column-group-failed"
|
||||
class="gl-pipeline-job-width gl-relative gl-mb-2 gl-whitespace-normal"
|
||||
@mouseenter="$emit('jobHover', group.name)"
|
||||
@mouseleave="$emit('jobHover', '')"
|
||||
>
|
||||
<div v-if="isParallel(group)" :class="{ 'gl-opacity-3': isFadedOut(group.name) }">
|
||||
<job-group-dropdown
|
||||
:group="group"
|
||||
:stage-name="showStageName ? group.stageName : ''"
|
||||
:pipeline-id="pipelineId"
|
||||
:css-class-job-name="$options.jobClasses"
|
||||
/>
|
||||
</div>
|
||||
<job-item
|
||||
v-else-if="singleJobExists(group)"
|
||||
:job="group.jobs[0]"
|
||||
:job-hovered="jobHovered"
|
||||
:skip-retry-modal="skipRetryModal"
|
||||
:source-job-hovered="sourceJobHovered"
|
||||
:pipeline-expanded="pipelineExpanded"
|
||||
:pipeline-id="pipelineId"
|
||||
:stage-name="showStageName ? group.stageName : ''"
|
||||
:css-class-job-name="$options.jobClasses"
|
||||
:class="[{ 'gl-opacity-3': isFadedOut(group.name) }, 'gl-duration-slow gl-ease-ease']"
|
||||
@pipelineActionRequestComplete="$emit('refreshPipelineGraph')"
|
||||
@setSkipRetryModal="$emit('setSkipRetryModal')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="group in groups"
|
||||
:id="groupId(group)"
|
||||
:key="getGroupId(group)"
|
||||
data-testid="stage-column-group"
|
||||
class="gl-pipeline-job-width gl-relative gl-mb-2 gl-whitespace-normal"
|
||||
@mouseenter="$emit('jobHover', group.name)"
|
||||
@mouseleave="$emit('jobHover', '')"
|
||||
v-if="nonFailedJobs.length > 0"
|
||||
class="gl-px-2"
|
||||
:class="{ 'gl-border-t': failedJobs.length > 0 }"
|
||||
>
|
||||
<div v-if="isParallel(group)" :class="{ 'gl-opacity-3': isFadedOut(group.name) }">
|
||||
<job-group-dropdown
|
||||
:group="group"
|
||||
:stage-name="showStageName ? group.stageName : ''"
|
||||
<div
|
||||
v-for="group in nonFailedJobs"
|
||||
:id="groupId(group)"
|
||||
:key="getGroupId(group)"
|
||||
data-testid="stage-column-group"
|
||||
class="gl-pipeline-job-width gl-relative gl-mb-2 gl-whitespace-normal"
|
||||
@mouseenter="$emit('jobHover', group.name)"
|
||||
@mouseleave="$emit('jobHover', '')"
|
||||
>
|
||||
<div v-if="isParallel(group)" :class="{ 'gl-opacity-3': isFadedOut(group.name) }">
|
||||
<job-group-dropdown
|
||||
:group="group"
|
||||
:stage-name="showStageName ? group.stageName : ''"
|
||||
:pipeline-id="pipelineId"
|
||||
:css-class-job-name="$options.jobClasses"
|
||||
/>
|
||||
</div>
|
||||
<job-item
|
||||
v-else-if="singleJobExists(group)"
|
||||
:job="group.jobs[0]"
|
||||
:job-hovered="jobHovered"
|
||||
:skip-retry-modal="skipRetryModal"
|
||||
:source-job-hovered="sourceJobHovered"
|
||||
:pipeline-expanded="pipelineExpanded"
|
||||
:pipeline-id="pipelineId"
|
||||
:stage-name="showStageName ? group.stageName : ''"
|
||||
:css-class-job-name="$options.jobClasses"
|
||||
:class="[{ 'gl-opacity-3': isFadedOut(group.name) }, 'gl-duration-slow gl-ease-ease']"
|
||||
@pipelineActionRequestComplete="$emit('refreshPipelineGraph')"
|
||||
@setSkipRetryModal="$emit('setSkipRetryModal')"
|
||||
/>
|
||||
</div>
|
||||
<job-item
|
||||
v-else-if="singleJobExists(group)"
|
||||
:job="group.jobs[0]"
|
||||
:job-hovered="jobHovered"
|
||||
:skip-retry-modal="skipRetryModal"
|
||||
:source-job-hovered="sourceJobHovered"
|
||||
:pipeline-expanded="pipelineExpanded"
|
||||
:pipeline-id="pipelineId"
|
||||
:stage-name="showStageName ? group.stageName : ''"
|
||||
:css-class-job-name="$options.jobClasses"
|
||||
:class="[{ 'gl-opacity-3': isFadedOut(group.name) }, 'gl-duration-slow gl-ease-ease']"
|
||||
@pipelineActionRequestComplete="$emit('refreshPipelineGraph')"
|
||||
@setSkipRetryModal="$emit('setSkipRetryModal')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</root-graph-layout>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export default {
|
|||
'currentDefinitionPosition',
|
||||
'currentBlobPath',
|
||||
'definitionPathPrefix',
|
||||
'data',
|
||||
]),
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -50,17 +51,28 @@ export default {
|
|||
|
||||
this.body = document.body;
|
||||
|
||||
eventHub.$on('showBlobInteractionZones', this.showBlobInteractionZones);
|
||||
eventHub.$on('showBlobInteractionZones', this.showCodeNavigation);
|
||||
|
||||
this.addGlobalEventListeners();
|
||||
this.fetchData();
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('showBlobInteractionZones', this.showBlobInteractionZones);
|
||||
eventHub.$off('showBlobInteractionZones', this.showCodeNavigation);
|
||||
this.removeGlobalEventListeners();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchData', 'showDefinition', 'showBlobInteractionZones', 'setInitialData']),
|
||||
showCodeNavigation(path) {
|
||||
if (this.data?.[path]) {
|
||||
this.showBlobInteractionZones(path);
|
||||
} else {
|
||||
const unwatchData = this.$watch('data', () => {
|
||||
unwatchData();
|
||||
|
||||
this.showBlobInteractionZones(path);
|
||||
});
|
||||
}
|
||||
},
|
||||
addGlobalEventListeners() {
|
||||
if (this.body) {
|
||||
this.body.addEventListener('click', this.showDefinition);
|
||||
|
|
|
|||
|
|
@ -65,10 +65,18 @@ export default {
|
|||
|
||||
if (el.closest('.js-code-navigation') && !isCurrentElementPopoverOpen) {
|
||||
const { lineIndex, charIndex } = el.dataset;
|
||||
const blobViewer = el.closest('.file-holder')?.querySelector('.blob-viewer');
|
||||
|
||||
let offsetLeft = 0;
|
||||
const blobContent = document.querySelector('.line-numbers')?.nextElementSibling;
|
||||
|
||||
if (blobContent?.classList.contains('blob-content')) {
|
||||
offsetLeft = blobContent.offsetLeft;
|
||||
}
|
||||
|
||||
position = {
|
||||
x: el.offsetLeft || 0,
|
||||
y: el.offsetTop + (el.closest('pre.code')?.offsetTop || 0) || 0,
|
||||
x: (el.offsetLeft || 0) + offsetLeft,
|
||||
y: el.offsetTop + (blobViewer?.offsetTop || 0) || 0,
|
||||
height: el.offsetHeight,
|
||||
lineIndex: parseInt(lineIndex, 10),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -85,6 +85,15 @@ export const typePolicies = {
|
|||
OrganizationUserConnection: {
|
||||
merge: true,
|
||||
},
|
||||
RepositoryBlob: {
|
||||
keyFields: ({ id, path }) => {
|
||||
if (path) {
|
||||
return `${id}${encodeURIComponent(path)}`;
|
||||
}
|
||||
|
||||
return id;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const stripWhitespaceFromQuery = (url, path) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { initSilentModeSettings } from '~/silent_mode_settings';
|
||||
import { initSimpleApp } from '~/helpers/init_simple_app_helper';
|
||||
import VscodeExtensionMarketplaceSettings from '~/vscode_extension_marketplace/components/settings_app.vue';
|
||||
import { initAdminDeletionProtectionSettings } from '~/admin/application_settings/deletion_protection';
|
||||
import initAccountAndLimitsSection from '../account_and_limits';
|
||||
import initGitpod from '../gitpod';
|
||||
import initSignupRestrictions from '../signup_restrictions';
|
||||
|
|
@ -10,6 +11,7 @@ import initSignupRestrictions from '../signup_restrictions';
|
|||
initGitpod();
|
||||
initSignupRestrictions();
|
||||
initSilentModeSettings();
|
||||
initAdminDeletionProtectionSettings();
|
||||
|
||||
initSimpleApp('#js-extension-marketplace-settings-app', VscodeExtensionMarketplaceSettings);
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import LineHighlighter from '~/blob/line_highlighter';
|
|||
import blobInfoQuery from 'shared_queries/repository/blob_info.query.graphql';
|
||||
import highlightMixin from '~/repository/mixins/highlight_mixin';
|
||||
import projectInfoQuery from 'ee_else_ce/repository/queries/project_info.query.graphql';
|
||||
import eventHub from '~/notes/event_hub';
|
||||
import getRefMixin from '../mixins/get_ref';
|
||||
import { getRefType } from '../utils/ref_type';
|
||||
import {
|
||||
|
|
@ -292,6 +293,7 @@ export default {
|
|||
|
||||
await this.$nextTick();
|
||||
handleLocationHash(); // Ensures that we scroll to the hash when async content is loaded
|
||||
eventHub.$emit('showBlobInteractionZones', this.blobInfo.path);
|
||||
})
|
||||
.catch(() => this.displayError());
|
||||
},
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ export default {
|
|||
hide-button
|
||||
is-group
|
||||
data-testid="new-group-work-item-modal"
|
||||
:work-item-type-name="$options.WORK_ITEM_TYPE_ENUM_EPIC"
|
||||
:preselected-work-item-type="$options.WORK_ITEM_TYPE_ENUM_EPIC"
|
||||
@hideModal="showCreateGroupWorkItemModal = false"
|
||||
/>
|
||||
<create-work-item-modal
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
workItemTypeName: {
|
||||
preselectedWorkItemType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
|
|
@ -94,7 +94,7 @@ export default {
|
|||
return {
|
||||
isCreateModalVisible: false,
|
||||
isConfirmationModalVisible: false,
|
||||
selectedWorkItemTypeName: this.workItemTypeName,
|
||||
selectedWorkItemTypeName: this.preselectedWorkItemType,
|
||||
shouldDiscardDraft: false,
|
||||
};
|
||||
},
|
||||
|
|
@ -126,7 +126,7 @@ export default {
|
|||
return newWorkItemPath({
|
||||
fullPath: this.fullPath,
|
||||
isGroup: this.isGroup,
|
||||
workItemTypeName: this.workItemTypeName,
|
||||
workItemTypeName: this.selectedWorkItemTypeName,
|
||||
query: this.newWorkItemPathQuery,
|
||||
});
|
||||
},
|
||||
|
|
@ -136,7 +136,7 @@ export default {
|
|||
];
|
||||
},
|
||||
newWorkItemButtonText() {
|
||||
return this.alwaysShowWorkItemTypeSelect && this.workItemTypeName
|
||||
return this.alwaysShowWorkItemTypeSelect && this.selectedWorkItemTypeName
|
||||
? sprintfWorkItem(s__('WorkItem|New %{workItemType}'), '')
|
||||
: this.newWorkItemText;
|
||||
},
|
||||
|
|
@ -165,7 +165,7 @@ export default {
|
|||
hideCreateModal() {
|
||||
this.$emit('hideModal');
|
||||
this.isCreateModalVisible = false;
|
||||
this.selectedWorkItemTypeName = this.workItemTypeName;
|
||||
this.selectedWorkItemTypeName = this.preselectedWorkItemType;
|
||||
},
|
||||
showCreateModal(event) {
|
||||
if (Boolean(event) && isMetaClick(event)) {
|
||||
|
|
@ -198,7 +198,7 @@ export default {
|
|||
this.hideConfirmationModal();
|
||||
},
|
||||
handleDiscardDraft(modal) {
|
||||
this.selectedWorkItemTypeName = this.workItemTypeName;
|
||||
this.selectedWorkItemTypeName = this.preselectedWorkItemType;
|
||||
|
||||
if (modal === 'createModal') {
|
||||
// This is triggered on the create modal when the user didn't update the form,
|
||||
|
|
@ -317,7 +317,7 @@ export default {
|
|||
:parent-id="parentId"
|
||||
:show-project-selector="showProjectSelector"
|
||||
:title="title"
|
||||
:work-item-type-name="workItemTypeName"
|
||||
:work-item-type-name="selectedWorkItemTypeName"
|
||||
:related-item="relatedItem"
|
||||
:should-discard-draft="shouldDiscardDraft"
|
||||
:is-modal="true"
|
||||
|
|
|
|||
|
|
@ -693,7 +693,7 @@ export default {
|
|||
@action="handleToggleReportAbuseModal"
|
||||
>
|
||||
<template #list-item>
|
||||
<gl-icon name="review-warning" class="gl-mr-2" variant="subtle" />
|
||||
<gl-icon name="abuse" class="gl-mr-2" variant="subtle" />
|
||||
{{ $options.i18n.reportAbuse }}
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
|
@ -791,7 +791,7 @@ export default {
|
|||
:always-show-work-item-type-select="!isGroup"
|
||||
:visible="isCreateWorkItemModalVisible"
|
||||
:related-item="relatedItemData"
|
||||
:work-item-type-name="workItemTypeNameEnum"
|
||||
:preselected-work-item-type="workItemTypeNameEnum"
|
||||
:show-project-selector="!isEpic"
|
||||
:is-group="isGroup"
|
||||
hide-button
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ export default {
|
|||
:show-project-selector="isGroup"
|
||||
:title="childTitle"
|
||||
:visible="visible"
|
||||
:work-item-type-name="childItemType"
|
||||
:preselected-work-item-type="childItemType"
|
||||
@hideModal="visible = false"
|
||||
@workItemCreated="handleWorkItemCreated"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -917,7 +917,7 @@ export default {
|
|||
<template #new-issue-button>
|
||||
<create-work-item-modal
|
||||
:is-group="isGroup"
|
||||
:work-item-type-name="workItemTypeName"
|
||||
:preselected-work-item-type="workItemTypeName"
|
||||
@workItemCreated="handleWorkItemCreated"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@ module Admin
|
|||
:lets_encrypt_notification_email,
|
||||
:lets_encrypt_terms_of_service_accepted,
|
||||
:domain_denylist_file,
|
||||
:deletion_adjourned_period,
|
||||
:raw_blob_request_limit,
|
||||
:issues_create_limit,
|
||||
:notes_create_limit,
|
||||
|
|
|
|||
|
|
@ -713,6 +713,12 @@ module ApplicationSettingsHelper
|
|||
# NOTE: description is overridden in EE
|
||||
_('Enable VS Code Extension Marketplace and configure the extensions registry for Web IDE.')
|
||||
end
|
||||
|
||||
def deletion_protection_data
|
||||
{
|
||||
deletion_adjourned_period: @application_setting[:deletion_adjourned_period]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
ApplicationSettingsHelper.prepend_mod_with('ApplicationSettingsHelper')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
- return unless Feature.enabled?(:downtier_delayed_deletion, :instance, type: :wip)
|
||||
|
||||
#js-admin-deletion-protection-settings{ data: deletion_protection_data }
|
||||
|
|
@ -30,6 +30,9 @@
|
|||
= _("Time (in hours) that users are allowed to skip forced configuration of two-factor authentication.")
|
||||
|
||||
- if @group.namespace_settings.present?
|
||||
- if @group.licensed_feature_available?(:security_orchestration_policies) && @group.root?
|
||||
= render_if_exists 'groups/admin_security_policies', form: f
|
||||
|
||||
.form-group.gl-form-group
|
||||
%legend.col-form-label.col-form-label
|
||||
= s_('Runners|Runner Registration')
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/522987
|
|||
milestone: '17.10'
|
||||
group: group::code review
|
||||
type: beta
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/397730
|
|||
milestone: '15.11'
|
||||
type: development
|
||||
group: group::authentication
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -555,6 +555,10 @@ sbom_components:
|
|||
- table: organizations
|
||||
column: organization_id
|
||||
on_delete: async_delete
|
||||
sbom_graph_paths:
|
||||
- table: projects
|
||||
column: project_id
|
||||
on_delete: async_delete
|
||||
sbom_occurrences:
|
||||
- table: p_ci_pipelines
|
||||
column: pipeline_id
|
||||
|
|
|
|||
|
|
@ -8,14 +8,6 @@ description: Persists links between timeline event tags and timeline events.
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100271
|
||||
milestone: '15.6'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
desired_sharding_key:
|
||||
project_id:
|
||||
references: projects
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: timeline_event_tag_id
|
||||
table: incident_management_timeline_event_tags
|
||||
sharding_key: project_id
|
||||
belongs_to: timeline_event_tag
|
||||
desired_sharding_key_migration_job_name: BackfillIncidentManagementTimelineEventTagLinksProjectId
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
table_size: small
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
table_name: sbom_graph_paths
|
||||
classes:
|
||||
- Sbom::GraphPath
|
||||
feature_categories:
|
||||
- dependency_management
|
||||
description: Stores dependency graph paths
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/184240
|
||||
milestone: '17.11'
|
||||
gitlab_schema: gitlab_sec
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
table_size: small
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateSbomGraphPaths < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.11'
|
||||
|
||||
def change
|
||||
create_table :sbom_graph_paths do |t|
|
||||
t.bigint :ancestor_id, null: false
|
||||
t.bigint :descendant_id, null: false
|
||||
t.bigint :project_id, null: false
|
||||
t.integer :path_length, null: false
|
||||
|
||||
t.index :ancestor_id
|
||||
t.index :descendant_id
|
||||
t.index [:project_id, :descendant_id]
|
||||
t.timestamps_with_timezone null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSbomOccurrencesFksToSbomGraphPaths < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
milestone '17.11'
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :sbom_graph_paths, :sbom_occurrences, column: :ancestor_id,
|
||||
on_delete: :cascade
|
||||
add_concurrent_foreign_key :sbom_graph_paths, :sbom_occurrences, column: :descendant_id,
|
||||
on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :sbom_graph_paths, column: :ancestor_id
|
||||
remove_foreign_key :sbom_graph_paths, column: :descendant_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSecurityPoliciesNamespaceSetting < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.11'
|
||||
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
add_column :namespace_settings, :security_policies, :jsonb, default: {}, null: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSecurityPoliciesHashConstraintToNamespaceSettings < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
milestone '17.11'
|
||||
|
||||
CONSTRAINT_NAME = 'check_namespace_settings_security_policies_is_hash'
|
||||
|
||||
def up
|
||||
add_check_constraint(
|
||||
:namespace_settings,
|
||||
"(jsonb_typeof(security_policies) = 'object')",
|
||||
CONSTRAINT_NAME
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_check_constraint :namespace_settings, CONSTRAINT_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIncidentManagementTimelineEventTagLinksProjectIdNotNull < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
milestone '17.11'
|
||||
|
||||
def up
|
||||
add_not_null_constraint :incident_management_timeline_event_tag_links, :project_id
|
||||
end
|
||||
|
||||
def down
|
||||
remove_not_null_constraint :incident_management_timeline_event_tag_links, :project_id
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
9c832ec50a8fc33fe133c3c8a9357fa665ead6272cb2cebdb2906b392b032026
|
||||
|
|
@ -0,0 +1 @@
|
|||
35eafea460167f1b1359c7adf5e432086a521aa5104b487260bb762e9e45aabe
|
||||
|
|
@ -0,0 +1 @@
|
|||
a60a8786eb1c9752739186163fdc325490b587493d6b8ed915aa772902d77326
|
||||
|
|
@ -0,0 +1 @@
|
|||
cff376d79d1a029588c473c0f500501b7e24be69a2a9ec7f1131fcaf3a4d2f3e
|
||||
|
|
@ -0,0 +1 @@
|
|||
368940784a31e8bba5c12d849fba003a87673cbc03f9a96100344c5876855600
|
||||
|
|
@ -15732,7 +15732,8 @@ CREATE TABLE incident_management_timeline_event_tag_links (
|
|||
timeline_event_id bigint NOT NULL,
|
||||
timeline_event_tag_id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
project_id bigint
|
||||
project_id bigint,
|
||||
CONSTRAINT check_e693cb4516 CHECK ((project_id IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE incident_management_timeline_event_tag_links_id_seq
|
||||
|
|
@ -18026,7 +18027,9 @@ CREATE TABLE namespace_settings (
|
|||
jwt_ci_cd_job_token_opted_out boolean DEFAULT false NOT NULL,
|
||||
require_dpop_for_manage_api_endpoints boolean DEFAULT true NOT NULL,
|
||||
job_token_policies_enabled boolean DEFAULT false NOT NULL,
|
||||
security_policies jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)),
|
||||
CONSTRAINT check_namespace_settings_security_policies_is_hash CHECK ((jsonb_typeof(security_policies) = 'object'::text)),
|
||||
CONSTRAINT namespace_settings_unique_project_download_limit_alertlist_size CHECK ((cardinality(unique_project_download_limit_alertlist) <= 100)),
|
||||
CONSTRAINT namespace_settings_unique_project_download_limit_allowlist_size CHECK ((cardinality(unique_project_download_limit_allowlist) <= 100))
|
||||
);
|
||||
|
|
@ -22314,6 +22317,25 @@ CREATE SEQUENCE sbom_components_id_seq
|
|||
|
||||
ALTER SEQUENCE sbom_components_id_seq OWNED BY sbom_components.id;
|
||||
|
||||
CREATE TABLE sbom_graph_paths (
|
||||
id bigint NOT NULL,
|
||||
ancestor_id bigint NOT NULL,
|
||||
descendant_id bigint NOT NULL,
|
||||
project_id bigint NOT NULL,
|
||||
path_length integer NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE sbom_graph_paths_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE sbom_graph_paths_id_seq OWNED BY sbom_graph_paths.id;
|
||||
|
||||
CREATE TABLE sbom_occurrences (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
|
|
@ -27642,6 +27664,8 @@ ALTER TABLE ONLY sbom_component_versions ALTER COLUMN id SET DEFAULT nextval('sb
|
|||
|
||||
ALTER TABLE ONLY sbom_components ALTER COLUMN id SET DEFAULT nextval('sbom_components_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY sbom_graph_paths ALTER COLUMN id SET DEFAULT nextval('sbom_graph_paths_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY sbom_occurrences ALTER COLUMN id SET DEFAULT nextval('sbom_occurrences_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY sbom_occurrences_vulnerabilities ALTER COLUMN id SET DEFAULT nextval('sbom_occurrences_vulnerabilities_id_seq'::regclass);
|
||||
|
|
@ -30617,6 +30641,9 @@ ALTER TABLE ONLY sbom_component_versions
|
|||
ALTER TABLE ONLY sbom_components
|
||||
ADD CONSTRAINT sbom_components_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY sbom_graph_paths
|
||||
ADD CONSTRAINT sbom_graph_paths_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY sbom_occurrences
|
||||
ADD CONSTRAINT sbom_occurrences_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -36959,6 +36986,12 @@ CREATE INDEX index_sbom_component_versions_on_organization_id ON sbom_component_
|
|||
|
||||
CREATE INDEX index_sbom_components_on_organization_id ON sbom_components USING btree (organization_id);
|
||||
|
||||
CREATE INDEX index_sbom_graph_paths_on_ancestor_id ON sbom_graph_paths USING btree (ancestor_id);
|
||||
|
||||
CREATE INDEX index_sbom_graph_paths_on_descendant_id ON sbom_graph_paths USING btree (descendant_id);
|
||||
|
||||
CREATE INDEX index_sbom_graph_paths_on_project_id_and_descendant_id ON sbom_graph_paths USING btree (project_id, descendant_id);
|
||||
|
||||
CREATE INDEX index_sbom_occurr_on_project_id_and_component_version_id_and_id ON sbom_occurrences USING btree (project_id, component_version_id, id);
|
||||
|
||||
CREATE INDEX index_sbom_occurrences_on_component_id_and_id ON sbom_occurrences USING btree (component_id, id);
|
||||
|
|
@ -43170,6 +43203,9 @@ ALTER TABLE ONLY targeted_message_dismissals
|
|||
ALTER TABLE ONLY timelogs
|
||||
ADD CONSTRAINT fk_c49c83dd77 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY sbom_graph_paths
|
||||
ADD CONSTRAINT fk_c4c7d16f3e FOREIGN KEY (ancestor_id) REFERENCES sbom_occurrences(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY wiki_repository_states
|
||||
ADD CONSTRAINT fk_c558ca51b8 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -43449,6 +43485,9 @@ ALTER TABLE ONLY fork_networks
|
|||
ALTER TABLE ONLY packages_conan_package_references
|
||||
ADD CONSTRAINT fk_e7b5f3afc7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY sbom_graph_paths
|
||||
ADD CONSTRAINT fk_e83002e9da FOREIGN KEY (descendant_id) REFERENCES sbom_occurrences(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY error_tracking_error_events
|
||||
ADD CONSTRAINT fk_e84882273e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ Requests over the rate limit are logged into the `auth.log` file.
|
|||
For example, if you set a limit of 400 for `POST /organizations`, requests to the API endpoint that
|
||||
exceed a rate of 400 within one minute are blocked. Access to the endpoint is restored after one minute.
|
||||
|
||||
You can configure the per minute rate limit per user for requests to the [POST /organizations API](../../api/organizations.md#create-organization). The default is 10.
|
||||
You can configure the per minute rate limit per user for requests to the [POST /organizations API](../../api/organizations.md#create-an-organization). The default is 10.
|
||||
|
||||
## Change the rate limit
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ title: Group badges API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
Use this API to interact with group badges. For more information, see [group badges](../user/project/badges.md#group-badges).
|
||||
|
||||
## Placeholder tokens
|
||||
|
||||
[Badges](../user/project/badges.md) support placeholders that are replaced in real time in both the link and image URL. The allowed placeholders are:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ title: LDAP group links
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
List, add, and delete [LDAP group links](../user/group/access_and_permissions.md#manage-group-memberships-with-ldap).
|
||||
Use this API to manage LDAP group links. For more information, see [manage group memberships with LDAP](../user/group/access_and_permissions.md#manage-group-memberships-with-ldap).
|
||||
|
||||
## List LDAP group links
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ title: Groups API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
Use the Groups API to list and manage GitLab groups through REST API calls. For more information, see [groups](../user/group/_index.md).
|
||||
Use this API to view and manage GitLab groups. For more information, see [groups](../user/group/_index.md).
|
||||
|
||||
Endpoint responses might vary based on the [permissions](../user/permissions.md) of the authenticated user in the group.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ title: Invitations API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
Use the Invitations API to invite or add users to a group or project, and to list pending
|
||||
invitations.
|
||||
Use this API to manage invitations and add users to a [group](../user/group/_index.md#add-users-to-a-group) or [project](../user/project/members/_index.md).
|
||||
|
||||
## Valid access levels
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" \
|
|||
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1256k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
|
||||
"created_at": "2015-09-03T07:24:44.627Z",
|
||||
"expires_at": "2020-05-05T00:00:00.000Z",
|
||||
"last_used_at": "2020-04-07T00:00:00.000Z",
|
||||
"usage_type": "auth",
|
||||
"user": {
|
||||
"name": "John Smith",
|
||||
|
|
@ -103,6 +104,7 @@ Example response:
|
|||
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1016k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
|
||||
"created_at": "2019-11-14T15:11:13.222Z",
|
||||
"expires_at": "2020-05-05T00:00:00.000Z",
|
||||
"last_used_at": "2020-04-07T00:00:00.000Z",
|
||||
"usage_type": "auth",
|
||||
"user": {
|
||||
"id": 1,
|
||||
|
|
@ -168,6 +170,8 @@ Example response:
|
|||
"title": "Sample key 1",
|
||||
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1016k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
|
||||
"created_at": "2019-11-14T15:11:13.222Z",
|
||||
"expires_at": "2020-05-05T00:00:00.000Z",
|
||||
"last_used_at": "2020-04-07T00:00:00.000Z",
|
||||
"usage_type": "auth",
|
||||
"user": {
|
||||
"id": 1,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ title: Namespaces API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
Use this API to interact with namespaces, a special resource category used to organize users and groups. For more information, see [Namespaces](../user/namespace/_index.md).
|
||||
Use this API to interact with namespaces, a special resource category used to organize users and groups. For more information, see [namespaces](../user/namespace/_index.md).
|
||||
|
||||
This API uses [Pagination](rest/_index.md#pagination) to filter results.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ title: Notification settings API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
Change [notification settings](../user/profile/notifications.md) using the REST API.
|
||||
Use this API to manage settings for GitLab notifications. For more information, see [notification emails](../user/profile/notifications.md).
|
||||
|
||||
## Valid notification levels
|
||||
|
||||
|
|
|
|||
|
|
@ -53451,6 +53451,10 @@ definitions:
|
|||
type: string
|
||||
format: date-time
|
||||
example: '2020-09-03T07:24:44.627Z'
|
||||
last_used_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2020-09-03T07:24:44.627Z'
|
||||
key:
|
||||
type: string
|
||||
example: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNJAkI3Wdf0r13c8a5pEExB2YowPWCSVzfZV22pNBc1CuEbyYLHpUyaD0GwpGvFdx2aP7lMEk35k6Rz3ccBF6jRaVJyhsn5VNnW92PMpBJ/P1UebhXwsFHdQf5rTt082cSxWuk61kGWRQtk4ozt/J2DF/dIUVaLvc+z4HomT41fQ==
|
||||
|
|
@ -53518,6 +53522,10 @@ definitions:
|
|||
type: string
|
||||
format: date-time
|
||||
example: '2020-09-03T07:24:44.627Z'
|
||||
last_used_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2020-09-03T07:24:44.627Z'
|
||||
key:
|
||||
type: string
|
||||
example: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNJAkI3Wdf0r13c8a5pEExB2YowPWCSVzfZV22pNBc1CuEbyYLHpUyaD0GwpGvFdx2aP7lMEk35k6Rz3ccBF6jRaVJyhsn5VNnW92PMpBJ/P1UebhXwsFHdQf5rTt082cSxWuk61kGWRQtk4ozt/J2DF/dIUVaLvc+z4HomT41fQ==
|
||||
|
|
@ -65772,6 +65780,10 @@ definitions:
|
|||
type: string
|
||||
format: date-time
|
||||
example: '2020-09-03T07:24:44.627Z'
|
||||
last_used_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2020-09-03T07:24:44.627Z'
|
||||
key:
|
||||
type: string
|
||||
example: |-
|
||||
|
|
@ -67329,6 +67341,10 @@ definitions:
|
|||
type: string
|
||||
format: date-time
|
||||
example: '2020-09-03T07:24:44.627Z'
|
||||
last_used_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2020-09-03T07:24:44.627Z'
|
||||
key:
|
||||
type: string
|
||||
example: |-
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ title: Organizations API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
## Create organization
|
||||
Use this API to interact with GitLab organizations. For more information, see [organization](../user/organization/_index.md).
|
||||
|
||||
## Create an organization
|
||||
|
||||
{{< history >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ title: Project forks API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
You can manage [project forks](../user/project/repository/forking_workflow.md) by using the REST API.
|
||||
Use this API to manage forks of GitLab projects. For more information, see [forks](../user/project/repository/forking_workflow.md).
|
||||
|
||||
## Fork a project
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
title: Project starring API
|
||||
---
|
||||
|
||||
You can get information about [projects and stars](../user/project/working_with_projects.md) by using the REST API.
|
||||
Use this API to interact with starred projects. For more information, see [projects and stars](../user/project/working_with_projects.md).
|
||||
|
||||
## List projects starred by a user
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ title: Projects API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
The Projects API provides programmatic access to manage GitLab projects and configure their key settings. A project is a central hub for collaboration where you store code, track issues, and organize team activities.
|
||||
Use this API to manage GitLab projects and their associated settings. A project is a central hub for
|
||||
collaboration where you store code, track issues, and organize team activities.
|
||||
For more information, see [create a project](../user/project/_index.md).
|
||||
|
||||
The Projects API contains endpoints that:
|
||||
|
||||
|
|
@ -24,8 +26,6 @@ The Projects API contains endpoints that:
|
|||
- Transfer projects between namespaces
|
||||
- Manage deployment and container registry settings
|
||||
|
||||
This page explains how to use the Projects REST API endpoints to interact with [GitLab projects](../user/project/_index.md).
|
||||
|
||||
## Permissions
|
||||
|
||||
Users with:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ title: GitLab To-Do List API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
Interact with [to-do items](../user/todos.md) using the REST API.
|
||||
Use this API to interact with [to-do items](../user/todos.md).
|
||||
|
||||
## Get a list of to-do items
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ title: Topics API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
Interact with project topics using the REST API.
|
||||
Use this API to interact with project topics. For more information, see [project topics](../user/project/project_topics.md).
|
||||
|
||||
## List topics
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
stage: Security Risk Management
|
||||
group: Security Insights
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
title: SBoM dependency graph ingestion overview
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The process starts *after* all `SBoM::Occurence` models have been ingested because we ingest them in slices and it would be tricky to process that in slices as well.
|
||||
|
||||
All work happens in a background worker which will be added in a subsequent MR so that we do not increase the time it takes to ingest an SBoM report. This means that there will be a delay between when the SBoM report is ingested and before the dependency graph is updated.
|
||||
|
||||
All record pertaining to dependency graphs are stored in `sbom_graph_paths` database table and has foreign keys to `sbom_occurrences` as well as `projects` for easier filtering.
|
||||
|
||||
## Details
|
||||
|
||||
1. The database table is designed as a [closure table](https://www.slideshare.net/slideshow/models-for-hierarchical-data/4179181)
|
||||
1. When a dependency is transitive then the corresponding `Sbom::Occurrence#ancestors` will contain entries.
|
||||
1. When a dependency is a direct dependency then the corresponding `Sbom::Occurrence#ancestors` will contain an `{}`.
|
||||
1. Dependencies can be both direct and transitive.
|
||||
1. There can be more than one version of a given dependency in a project (for example Node allows that).
|
||||
1. There can be more than one `Sbom::Occurrence` for a given dependency version, for example in monorepos. These `Sbom::Occurrence` rows should have a different `input_file_path` and `source_id` (however we will not use `source_id` when building the dependency tree to avoid SQL JOIN).
|
||||
|
|
@ -212,6 +212,55 @@ To customize policy enforcement, you can define a policy's scope to either inclu
|
|||
specified projects, groups, or compliance framework labels. For more details, see
|
||||
[Scope](_index.md#scope).
|
||||
|
||||
## Pipeline execution policy limits
|
||||
|
||||
For performance reasons, GitLab limits the number of pipeline execution policies that can run as part of a security policy project or pipeline. By default, these are the maximum number of pipelines execution policies:
|
||||
|
||||
- Five per security policy project
|
||||
- Five per pipeline
|
||||
|
||||
### Adjust policy limits
|
||||
|
||||
{{< history >}}
|
||||
|
||||
- [Configurable limits introduced](https://gitlab.com/groups/gitlab-org/-/epics/16929) in GitLab 17.11.
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
The *Maximum 5 pipeline execution policies per security policy project* limit can be adjusted at different levels:
|
||||
|
||||
#### Adjust the limit for an instance
|
||||
|
||||
{{< details >}}
|
||||
|
||||
- Offering: GitLab Self-Managed
|
||||
|
||||
{{< /details >}}
|
||||
|
||||
On GitLab Self-Managed instances, administrators can adjust the limits for the entire instance, up to a maximum of 20 pipeline execution policies:
|
||||
|
||||
1. Go to **Admin Area** > **Settings** > **Security and compliance**.
|
||||
1. Expand the **Security policies** section.
|
||||
1. Set a new value for **Maximum number of pipeline execution policies allowed per security policy configuration**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
#### Adjust the limit for a top-level group
|
||||
|
||||
GitLab instance administrators can modify the limits for top-level groups. These group limits can exceed the configured or default instance limits.
|
||||
|
||||
{{< alert type="note" >}}
|
||||
Increasing these limits can affect system performance, especially for complex policies or when applying many policies simultaneously.
|
||||
{{< /alert >}}
|
||||
|
||||
To adjust the limit for a top-level group:
|
||||
|
||||
1. Go to **Admin Area** > **Overview** > **Groups**.
|
||||
1. In the row of the top-level group you want to modify, select **Edit**.
|
||||
1. Set a new value for **Maximum number of pipeline execution policies allowed per security policy configuration**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
When the limit for an individual group is set to zero, the system will fall back to using the instance-wide default value. This ensures that groups with a zero limit can still create pipeline execution policies according to the instance default configuration.
|
||||
|
||||
## Manage access to the CI/CD configuration
|
||||
|
||||
When you enforce pipeline execution policies on a project, users that trigger pipelines must have at least read-only access to the project that contains the policy CI/CD configuration. You can grant access to the project manually or automatically.
|
||||
|
|
|
|||
|
|
@ -673,7 +673,8 @@ Audit event types belong to the following product categories.
|
|||
| [`user_auditor_status_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136456) | A user is either made an auditor or removed as an auditor | {{< icon name="check-circle" >}} Yes | GitLab [16.6](https://gitlab.com/gitlab-org/gitlab/-/issues/430235) | User |
|
||||
| [`user_email_address_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/2103) | A user updates their email address | {{< icon name="check-circle" >}} Yes | GitLab [10.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/1370) | User |
|
||||
| [`user_name_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167484) | A user's name is updated | {{< icon name="check-circle" >}} Yes | GitLab [17.5](https://gitlab.com/gitlab-org/gitlab/-/issues/486532) | User |
|
||||
| [`user_profile_visiblity_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129149) | User toggles private profile user setting | {{< icon name="dotted-circle" >}} No | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129149) | User |
|
||||
| [`user_profile_visibility_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/187207) | User toggles private profile user setting | {{< icon name="dotted-circle" >}} No | GitLab [17.11](https://gitlab.com/gitlab-org/gitlab/-/issues/474386) | User |
|
||||
| [`user_profile_visiblity_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129149) | User toggles private profile user setting (DEPRECATED). Use `user_profile_visibility_updated` instead. | {{< icon name="dotted-circle" >}} No | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129149) | User |
|
||||
| [`user_username_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106086) | A user's username is updated | {{< icon name="check-circle" >}} Yes | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/369329) | User |
|
||||
|
||||
### Value stream management
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ You can also provide it with additional context.
|
|||
| Epics | Either:<br>• The URL of the group or epic. <br>• The epic ID and the name of the group the epic is in. | Examples:<br>• List all epics in `https://gitlab.com/groups/namespace/group`<br>• Summarize the epic: `https://gitlab.com/groups/namespace/group/-/epics/42`<br>• `Summarize epic 42 in group namespace/group` |
|
||||
| Issues | Either:<br>• The URL of the project or issue. <br>• The issue ID in the current or another project. | Examples:<br>• List all issues in the project at `https://gitlab.com/namespace/project`<br>• Summarize the issue at `https://gitlab.com/namespace/project/-/issues/103`<br>• Review the comment with ID `42` in `https://gitlab.com/namespace/project/-/issues/103`<br>• List all comments on the issue at `https://gitlab.com/namespace/project/-/issues/103`<br>• Summarize issue `103` in this project |
|
||||
| Merge requests | Either:<br>• The URL of the merge request. <br>• The merge request ID in the current or another project. |• Summarize `https://gitlab.com/namespace/project/-/merge_requests/103`<br>• Review the diffs in `https://gitlab.com/namespace/project/-/merge_requests/103`<br>• Summarize the comments on `https://gitlab.com/namespace/project/-/merge_requests/103`<br>• Summarize merge request `103` in this project |
|
||||
| Merge request pipelines | The merge request ID in the current or another project. |• Review the failures in merge request `12345`<br>• Can you identify the cause of the error in the merge request `54321` in project `gitlab-org/gitlab-qa` |
|
||||
| Merge request pipelines | The merge request ID in the current or another project. |• Review the failures in merge request `12345`<br>• Can you identify the cause of the error in the merge request `54321` in project `gitlab-org/gitlab-qa` <br>• Suggest a solution to the pipeline failure in `https://gitlab.com/namespace/project/-/merge_requests/54321` |
|
||||
|
||||
Workflow also has access to the GitLab [Search API](../../api/search.md) to find related issues or merge requests.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module API
|
|||
expose :title, documentation: { type: 'string', example: 'Sample key 25' }
|
||||
expose :created_at, documentation: { type: 'dateTime', example: '2015-09-03T07:24:44.627Z' }
|
||||
expose :expires_at, documentation: { type: 'dateTime', example: '2020-09-03T07:24:44.627Z' }
|
||||
expose :last_used_at, documentation: { type: 'dateTime', example: '2020-09-03T07:24:44.627Z' }
|
||||
expose :publishable_key, as: :key, documentation:
|
||||
{ type: 'string',
|
||||
example: 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1256k6Yjz\
|
||||
|
|
|
|||
|
|
@ -230,29 +230,26 @@ module Gitlab
|
|||
|
||||
# If the parent doesn't exist, or the current user can't access it
|
||||
unless parent && current_user.can?(:read_work_item, parent)
|
||||
return _("This parent does not exist or you don't have sufficient permission.")
|
||||
return _("This parent item does not exist or you don't have sufficient permission.")
|
||||
end
|
||||
|
||||
# If the child has already been added to the parent
|
||||
if child_work_item && child_work_item.work_item_parent == parent
|
||||
return format(_('%{child_type} %{child_reference} has already been added to parent %{parent_reference}.'),
|
||||
return format(_('%{child_reference} has already been added to parent %{parent_reference}.'),
|
||||
child_reference: child_work_item.to_reference,
|
||||
parent_reference: parent.to_reference,
|
||||
child_type: child_work_item.work_item_type.name)
|
||||
parent_reference: parent.to_reference)
|
||||
end
|
||||
|
||||
# If the parent is confidential, but the child is not
|
||||
if parent.confidential? && !child.confidential?
|
||||
return format(_("Cannot assign a confidential parent to a non-confidential %{child_type}. Make the " \
|
||||
"%{child_type} confidential and try again"), child_type: child.work_item_type&.name || 'Issue')
|
||||
return _("Cannot assign a confidential parent item to a non-confidential child item. Make the child item " \
|
||||
"confidential and try again.")
|
||||
end
|
||||
|
||||
# Check hierarchy restriction
|
||||
return unless child_work_item && !hierarchy_relationship_allowed?(parent, child_work_item)
|
||||
|
||||
format(_("Cannot assign a child %{child_type} to a %{parent_type}"),
|
||||
child_type: child_work_item.work_item_type.name,
|
||||
parent_type: parent.work_item_type.name)
|
||||
_("Cannot assign this child type to parent type.")
|
||||
end
|
||||
|
||||
def fetch_child_work_item(child)
|
||||
|
|
|
|||
|
|
@ -687,7 +687,7 @@ msgstr[1] ""
|
|||
msgid "%{chartTitle} no data series"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{child_type} %{child_reference} has already been added to parent %{parent_reference}."
|
||||
msgid "%{child_reference} has already been added to parent %{parent_reference}."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{codeStart}$%{codeEnd} will be treated as the start of a reference to another variable."
|
||||
|
|
@ -6364,6 +6364,9 @@ msgstr ""
|
|||
msgid "Amazon Q"
|
||||
msgstr ""
|
||||
|
||||
msgid "Amazon Q connectivity check failed: %{message}"
|
||||
msgstr ""
|
||||
|
||||
msgid "AmazonQ|Active cloud connector token not found."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11909,13 +11912,13 @@ msgstr ""
|
|||
msgid "Cancelling Preview"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cannot assign a child %{child_type} to a %{parent_type}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cannot assign a confidential epic to a non-confidential issue. Make the issue confidential and try again"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cannot assign a confidential parent to a non-confidential %{child_type}. Make the %{child_type} confidential and try again"
|
||||
msgid "Cannot assign a confidential parent item to a non-confidential child item. Make the child item confidential and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Cannot assign this child type to parent type."
|
||||
msgstr ""
|
||||
|
||||
msgid "Cannot be merged automatically"
|
||||
|
|
@ -12350,6 +12353,9 @@ msgstr ""
|
|||
msgid "Checking group path availability…"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checking if %{message} failed: %{error}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checking if merge request can be merged…"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -27393,6 +27399,9 @@ msgstr ""
|
|||
msgid "GitLab container registry API not supported"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab credentials used by Amazon Q are valid"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab detected an attempt to sign in to your %{host} account using an incorrect verification code"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -27417,6 +27426,9 @@ msgstr ""
|
|||
msgid "GitLab informs you if a new version is available. %{link_start}What information does GitLab Inc. collect?%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab instance is reachable by Amazon Q"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab is a complete DevOps platform, delivered as a single application, fundamentally changing the way Development, Security, and Ops teams collaborate"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -35872,6 +35884,9 @@ msgstr ""
|
|||
msgid "Manage secret detection behavior for all projects in your GitLab instance"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manage security policy settings in your GitLab instance"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manage templates..."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36292,6 +36307,9 @@ msgstr ""
|
|||
msgid "Maximum number of Helm packages that can be listed per channel. Must be at least 1."
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum number of actions per scan execution policy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum number of changes (branches or tags) in a single push above which a bulk push event is created (default is 3). Setting to 0 does not disable throttling."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36304,6 +36322,9 @@ msgstr ""
|
|||
msgid "Maximum number of mirrors that can be synchronizing at the same time."
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum number of pipeline execution policies allowed per security policy configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum number of possible retries for Elasticsearch search requests."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -53215,6 +53236,9 @@ msgstr ""
|
|||
msgid "Security inventory"
|
||||
msgstr ""
|
||||
|
||||
msgid "Security policies"
|
||||
msgstr ""
|
||||
|
||||
msgid "Security policy"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -59986,6 +60010,9 @@ msgstr ""
|
|||
msgid "The maximum file size is %{size}."
|
||||
msgstr ""
|
||||
|
||||
msgid "The maximum number of pipeline execution policies allowed per security policy configuration can exceed the limit for the instance. Set to 0 to use instance limit."
|
||||
msgstr ""
|
||||
|
||||
msgid "The maximum number of tags that a single worker accepts for cleanup. If the number of tags goes above this limit, the list of tags to delete is truncated to this number. To remove this limit, set it to 0."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -61167,7 +61194,7 @@ msgstr ""
|
|||
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
|
||||
msgstr ""
|
||||
|
||||
msgid "This parent does not exist or you don't have sufficient permission."
|
||||
msgid "This parent item does not exist or you don't have sufficient permission."
|
||||
msgstr ""
|
||||
|
||||
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
|
||||
|
|
@ -63497,6 +63524,9 @@ msgstr ""
|
|||
msgid "Unknown Error"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unknown format"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Script to extract SQL query fingerprints from auto-explain logs
|
||||
require 'json'
|
||||
require 'zlib'
|
||||
|
||||
if ARGV.size < 2
|
||||
puts "Usage: #{$PROGRAM_NAME} <input_file> <output_file>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
input_file = ARGV[0]
|
||||
output_file = ARGV[1]
|
||||
|
||||
unless File.exist?(input_file)
|
||||
puts "Error: Input file not found - #{input_file}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
fingerprints = Set.new
|
||||
|
||||
begin
|
||||
# Handle both compressed and uncompressed files
|
||||
if input_file.end_with?('.gz')
|
||||
Zlib::GzipReader.open(input_file) do |gz|
|
||||
gz.each_line do |line|
|
||||
data = JSON.parse(line)
|
||||
fingerprints.add(data['fingerprint']) if data['fingerprint']
|
||||
rescue JSON::ParserError
|
||||
# empty
|
||||
end
|
||||
end
|
||||
else
|
||||
File.foreach(input_file) do |line|
|
||||
data = JSON.parse(line)
|
||||
fingerprints.add(data['fingerprint']) if data['fingerprint']
|
||||
rescue JSON::ParserError
|
||||
# empty
|
||||
end
|
||||
end
|
||||
|
||||
File.open(output_file, 'w') { |f| fingerprints.each { |fp| f.puts(fp) } }
|
||||
rescue StandardError => e
|
||||
puts "Error: #{e.message}"
|
||||
exit 1
|
||||
end
|
||||
|
|
@ -224,3 +224,15 @@ function fixtures_directory_exists() {
|
|||
function upload_fixtures_package() {
|
||||
upload_package "${FIXTURES_PACKAGE}" "${FIXTURES_PACKAGE_URL}"
|
||||
}
|
||||
|
||||
# Dump auto-explain logs fingerprints
|
||||
export FINGERPRINTS_PACKAGE="query-fingerprints.tar.gz"
|
||||
export FINGERPRINTS_FILE="query_fingerprints.txt"
|
||||
export FINGERPRINTS_PACKAGE_URL="${API_PACKAGES_BASE_URL}/auto-explain-logs/master/${FINGERPRINTS_PACKAGE}"
|
||||
|
||||
function extract_and_upload_fingerprints() {
|
||||
echo "Extracting SQL query fingerprints from ${RSPEC_AUTO_EXPLAIN_LOG_PATH}"
|
||||
ruby scripts/extract_fingerprints "${RSPEC_AUTO_EXPLAIN_LOG_PATH}" "${FINGERPRINTS_FILE}"
|
||||
create_package "${FINGERPRINTS_PACKAGE}" "${FINGERPRINTS_FILE}"
|
||||
upload_package "${FINGERPRINTS_PACKAGE}" "${FINGERPRINTS_PACKAGE_URL}"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -461,6 +461,17 @@ RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_set
|
|||
expect(application_settings.reload.ci_max_includes).to eq(200)
|
||||
end
|
||||
end
|
||||
|
||||
context 'deletion adjourned period' do
|
||||
let(:application_settings) { ApplicationSetting.current }
|
||||
|
||||
it 'updates deletion_adjourned_period setting' do
|
||||
put :update, params: { application_setting: { deletion_adjourned_period: 6 } }
|
||||
|
||||
expect(response).to redirect_to(general_admin_application_settings_path)
|
||||
expect(application_settings.reload.deletion_adjourned_period).to eq(6)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #reset_registration_token', feature_category: :user_management do
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@
|
|||
],
|
||||
"format": "date-time"
|
||||
},
|
||||
"last_used_at": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"format": "date-time"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -59,4 +66,4 @@
|
|||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
import { GlLink } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import FormGroup from '~/admin/application_settings/deletion_protection/components/form_group.vue';
|
||||
import {
|
||||
I18N_DELETION_PROTECTION,
|
||||
DEL_ADJ_PERIOD_MIN_LIMIT_ERROR,
|
||||
DEL_ADJ_PERIOD_MAX_LIMIT_ERROR,
|
||||
} from '~/admin/application_settings/deletion_protection/constants';
|
||||
|
||||
describe('Form group component', () => {
|
||||
let wrapper;
|
||||
|
||||
const findGlLink = () => wrapper.findComponent(GlLink);
|
||||
const findDeletionAdjournedPeriodInput = () => wrapper.findByTestId('deletion_adjourned_period');
|
||||
|
||||
const createComponent = ({ props = {}, provide = {} } = {}) => {
|
||||
wrapper = mountExtended(FormGroup, {
|
||||
propsData: {
|
||||
deletionAdjournedPeriod: 7,
|
||||
...props,
|
||||
},
|
||||
provide,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders an input for setting the deletion adjourned period', () => {
|
||||
expect(
|
||||
wrapper.findByLabelText(I18N_DELETION_PROTECTION.label, { exact: false }).attributes(),
|
||||
).toMatchObject({
|
||||
name: 'application_setting[deletion_adjourned_period]',
|
||||
type: 'number',
|
||||
min: '1',
|
||||
max: '90',
|
||||
});
|
||||
});
|
||||
|
||||
it('displays the help text', () => {
|
||||
expect(wrapper.findByText(I18N_DELETION_PROTECTION.helpText).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays the help link', () => {
|
||||
expect(findGlLink().text()).toContain(I18N_DELETION_PROTECTION.learnMore);
|
||||
expect(findGlLink().attributes('href')).toBe(
|
||||
helpPagePath('administration/settings/visibility_and_access_controls', {
|
||||
anchor: 'delayed-project-deletion',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe.each`
|
||||
value | errorMessage
|
||||
${''} | ${DEL_ADJ_PERIOD_MIN_LIMIT_ERROR}
|
||||
${'91'} | ${DEL_ADJ_PERIOD_MAX_LIMIT_ERROR}
|
||||
${'-1'} | ${DEL_ADJ_PERIOD_MIN_LIMIT_ERROR}
|
||||
`('when the input has a value of $value', ({ value, errorMessage }) => {
|
||||
describe('when input is blured', () => {
|
||||
it('displays error message', async () => {
|
||||
findDeletionAdjournedPeriodInput().vm.$emit('input', value);
|
||||
findDeletionAdjournedPeriodInput().vm.$emit('blur');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findDeletionAdjournedPeriodInput().attributes('aria-invalid')).toBe('true');
|
||||
expect(wrapper.findByText(errorMessage).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when input emits invalid event', () => {
|
||||
it('displays error message, prevents default and focuses on input', async () => {
|
||||
findDeletionAdjournedPeriodInput().vm.$emit('input', value);
|
||||
const event = {
|
||||
preventDefault: jest.fn(),
|
||||
};
|
||||
const focusSpy = jest.spyOn(findDeletionAdjournedPeriodInput().element, 'focus');
|
||||
|
||||
findDeletionAdjournedPeriodInput().vm.$emit('invalid', event);
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findDeletionAdjournedPeriodInput().attributes('aria-invalid')).toBe('true');
|
||||
expect(wrapper.findByText(errorMessage).exists()).toBe(true);
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
expect(focusSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when input has valid value', () => {
|
||||
describe('when input is blured', () => {
|
||||
it('does not display error message', async () => {
|
||||
findDeletionAdjournedPeriodInput().vm.$emit('input', '50');
|
||||
findDeletionAdjournedPeriodInput().vm.$emit('blur');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findDeletionAdjournedPeriodInput().attributes('aria-invalid')).toBe(undefined);
|
||||
expect(wrapper.findByText(DEL_ADJ_PERIOD_MIN_LIMIT_ERROR).exists()).toBe(false);
|
||||
expect(wrapper.findByText(DEL_ADJ_PERIOD_MAX_LIMIT_ERROR).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when input emits invalid event', () => {
|
||||
it('does not display error message', async () => {
|
||||
findDeletionAdjournedPeriodInput().vm.$emit('input', '50');
|
||||
const event = {
|
||||
preventDefault: jest.fn(),
|
||||
};
|
||||
findDeletionAdjournedPeriodInput().vm.$emit('invalid', event);
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findDeletionAdjournedPeriodInput().attributes('aria-invalid')).toBe(undefined);
|
||||
expect(wrapper.findByText(DEL_ADJ_PERIOD_MIN_LIMIT_ERROR).exists()).toBe(false);
|
||||
expect(wrapper.findByText(DEL_ADJ_PERIOD_MAX_LIMIT_ERROR).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import { createWrapper } from '@vue/test-utils';
|
||||
import { initAdminDeletionProtectionSettings } from '~/admin/application_settings/deletion_protection';
|
||||
import { parseFormProps } from '~/admin/application_settings/deletion_protection/utils';
|
||||
import FormGroup from '~/admin/application_settings/deletion_protection/components/form_group.vue';
|
||||
|
||||
jest.mock('~/admin/application_settings/deletion_protection/utils', () => ({
|
||||
parseFormProps: jest.fn().mockReturnValue({
|
||||
deletionAdjournedPeriod: 7,
|
||||
delayedGroupDeletion: false,
|
||||
delayedProjectDeletion: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('initAdminDeletionProtectionSettings', () => {
|
||||
let appRoot;
|
||||
let wrapper;
|
||||
|
||||
const createAppRoot = () => {
|
||||
appRoot = document.createElement('div');
|
||||
appRoot.setAttribute('id', 'js-admin-deletion-protection-settings');
|
||||
appRoot.dataset.deletionAdjournedPeriod = 7;
|
||||
appRoot.dataset.delayedGroupDeletion = false;
|
||||
appRoot.dataset.delayedProjectDeletion = false;
|
||||
document.body.appendChild(appRoot);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
if (appRoot) {
|
||||
appRoot.remove();
|
||||
appRoot = null;
|
||||
}
|
||||
});
|
||||
|
||||
const findFormGroup = () => wrapper.findComponent(FormGroup);
|
||||
|
||||
describe('when there is no app root', () => {
|
||||
it('returns false', () => {
|
||||
expect(initAdminDeletionProtectionSettings()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is an app root', () => {
|
||||
beforeEach(() => {
|
||||
createAppRoot();
|
||||
wrapper = createWrapper(initAdminDeletionProtectionSettings());
|
||||
});
|
||||
|
||||
it('renders FormGroup', () => {
|
||||
expect(findFormGroup().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('parses the form props from the dataset', () => {
|
||||
expect(parseFormProps).toHaveBeenCalledWith(appRoot.dataset);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { parseFormProps } from '~/admin/application_settings/deletion_protection/utils';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
|
||||
describe('deletion protection utils', () => {
|
||||
describe('parseFormProps', () => {
|
||||
const input = {
|
||||
deletionAdjournedPeriod: '7',
|
||||
delayedGroupDeletion: 'true',
|
||||
delayedProjectDeletion: 'false',
|
||||
};
|
||||
|
||||
it('returns the expected result', () => {
|
||||
expect(parseFormProps(input)).toStrictEqual({
|
||||
deletionAdjournedPeriod: parseInt(input.deletionAdjournedPeriod, 10),
|
||||
delayedGroupDeletion: parseBoolean(input.delayedGroupDeletion),
|
||||
delayedProjectDeletion: parseBoolean(input.delayedProjectDeletion),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not attempt to parse an undefined adjourned period', () => {
|
||||
expect(parseFormProps({ deletionAdjournedPeriod: undefined })).toMatchObject({
|
||||
deletionAdjournedPeriod: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -28,7 +28,7 @@ describe('MetricPopover', () => {
|
|||
};
|
||||
|
||||
const findMetricLabel = () => wrapper.findByTestId('metric-label');
|
||||
const findMetricLink = () => wrapper.find('[data-testid="metric-link"]');
|
||||
const findMetricLink = () => wrapper.findByTestId('metric-link');
|
||||
const findMetricDescription = () => wrapper.findByTestId('metric-description');
|
||||
const findMetricDocsLink = () => wrapper.findByTestId('metric-docs-link');
|
||||
const findMetricDocsLinkIcon = () => findMetricDocsLink().findComponent(GlIcon);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { GlLoadingIcon, GlTable, GlButton } from '@gitlab/ui';
|
||||
import { getAllByRole } from '@testing-library/dom';
|
||||
import { shallowMount, mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import Papa from 'papaparse';
|
||||
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import CsvViewer from '~/blob/csv/csv_viewer.vue';
|
||||
import PapaParseAlert from '~/blob/components/papa_parse_alert.vue';
|
||||
import { MAX_ROWS_TO_RENDER } from '~/blob/csv/constants';
|
||||
|
|
@ -16,7 +16,7 @@ describe('app/assets/javascripts/blob/csv/csv_viewer.vue', () => {
|
|||
const createComponent = ({
|
||||
csv = validCsv,
|
||||
remoteFile = false,
|
||||
mountFunction = shallowMount,
|
||||
mountFunction = shallowMountExtended,
|
||||
} = {}) => {
|
||||
wrapper = mountFunction(CsvViewer, {
|
||||
propsData: {
|
||||
|
|
@ -30,7 +30,7 @@ describe('app/assets/javascripts/blob/csv/csv_viewer.vue', () => {
|
|||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findAlert = () => wrapper.findComponent(PapaParseAlert);
|
||||
const findSwitchToRawViewBtn = () => wrapper.findComponent(GlButton);
|
||||
const findLargeCsvText = () => wrapper.find('[data-testid="large-csv-text"]');
|
||||
const findLargeCsvText = () => wrapper.findByTestId('large-csv-text');
|
||||
|
||||
it('should render loading spinner', () => {
|
||||
createComponent();
|
||||
|
|
@ -70,7 +70,7 @@ describe('app/assets/javascripts/blob/csv/csv_viewer.vue', () => {
|
|||
});
|
||||
|
||||
it('renders the CSV table with the correct content', async () => {
|
||||
createComponent({ mountFunction: mount });
|
||||
createComponent({ mountFunction: mountExtended });
|
||||
await nextTick();
|
||||
|
||||
expect(getAllByRole(wrapper.element, 'row', { name: /One/i })).toHaveLength(1);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ describe('stage column component', () => {
|
|||
const findStageColumnTitle = () => wrapper.find('[data-testid="stage-column-title"]');
|
||||
const findStageColumnGroup = () => wrapper.find('[data-testid="stage-column-group"]');
|
||||
const findAllStageColumnGroups = () => wrapper.findAll('[data-testid="stage-column-group"]');
|
||||
const findAllStageColumnFailedTitle = () => wrapper.find('[data-testid="failed-jobs-title"]');
|
||||
const findAllStageColumnFailedGroups = () =>
|
||||
wrapper.findAll('[data-testid="stage-column-group-failed"]');
|
||||
const findJobItem = () => wrapper.findComponent(JobItem);
|
||||
const findActionComponent = () => wrapper.findComponent(ActionComponent);
|
||||
|
||||
|
|
@ -106,6 +109,39 @@ describe('stage column component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when has failed jobs', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
method: shallowMount,
|
||||
props: {
|
||||
groups: [
|
||||
{
|
||||
jobs: [mockJob],
|
||||
name: 'test2',
|
||||
size: 1,
|
||||
title: 'Bird',
|
||||
status: {
|
||||
group: 'failed',
|
||||
},
|
||||
},
|
||||
{
|
||||
jobs: [mockJob],
|
||||
name: 'test',
|
||||
size: 1,
|
||||
title: 'Fish',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('shows failed jobs grouped', () => {
|
||||
expect(findAllStageColumnFailedGroups().length).toBe(1);
|
||||
expect(findAllStageColumnFailedTitle().text()).toEqual('Failed jobs');
|
||||
expect(findAllStageColumnGroups().length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('job', () => {
|
||||
describe('text handling', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ describe('Pipeline graph wrapper', () => {
|
|||
|
||||
await findViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
|
||||
|
||||
expect(findStageColumnTitle().text()).toBe('');
|
||||
expect(findStageColumnTitle().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('saves the view type to local storage', async () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { GlModal, GlSprintf } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import RemoveClusterConfirmation from '~/clusters/components/remove_cluster_confirmation.vue';
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ describe('Remove cluster confirmation modal', () => {
|
|||
const showMock = jest.fn();
|
||||
|
||||
const createComponent = ({ props = {}, stubs = {} } = {}) => {
|
||||
wrapper = mount(RemoveClusterConfirmation, {
|
||||
wrapper = mountExtended(RemoveClusterConfirmation, {
|
||||
propsData: {
|
||||
clusterPath: 'clusterPath',
|
||||
clusterName: 'clusterName',
|
||||
|
|
@ -26,10 +26,9 @@ describe('Remove cluster confirmation modal', () => {
|
|||
|
||||
describe('two buttons', () => {
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findRemoveIntegrationButton = () =>
|
||||
wrapper.find('[data-testid="remove-integration-button"]');
|
||||
const findRemoveIntegrationButton = () => wrapper.findByTestId('remove-integration-button');
|
||||
const findRemoveIntegrationAndResourcesButton = () =>
|
||||
wrapper.find('[data-testid="remove-integration-and-resources-button"]');
|
||||
wrapper.findByTestId('remove-integration-and-resources-button');
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { GlLoadingIcon, GlTable, GlAvatar, GlEmptyState } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
|
||||
import IncidentsList from '~/incidents/components/incidents_list.vue';
|
||||
import PaginatedTableWithSearchAndTabs from '~/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue';
|
||||
|
|
@ -57,50 +56,48 @@ describe('Incidents List', () => {
|
|||
const findIncidentLink = () => wrapper.findByTestId('incident-link');
|
||||
|
||||
function mountComponent({ data = {}, loading = false, provide = {} } = {}) {
|
||||
wrapper = extendedWrapper(
|
||||
mount(IncidentsList, {
|
||||
data() {
|
||||
return {
|
||||
incidents: [],
|
||||
incidentsCount: {},
|
||||
...data,
|
||||
};
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
queries: {
|
||||
incidents: {
|
||||
loading,
|
||||
},
|
||||
wrapper = mountExtended(IncidentsList, {
|
||||
data() {
|
||||
return {
|
||||
incidents: [],
|
||||
incidentsCount: {},
|
||||
...data,
|
||||
};
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
queries: {
|
||||
incidents: {
|
||||
loading,
|
||||
},
|
||||
},
|
||||
},
|
||||
provide: {
|
||||
projectPath: '/project/path',
|
||||
newIssuePath,
|
||||
incidentTemplateName,
|
||||
incidentType,
|
||||
issuePath: '/project/issues',
|
||||
publishedAvailable: true,
|
||||
emptyListSvgPath,
|
||||
textQuery: '',
|
||||
authorUsernameQuery: '',
|
||||
assigneeUsernameQuery: '',
|
||||
slaFeatureAvailable: true,
|
||||
canCreateIncident: true,
|
||||
...provide,
|
||||
},
|
||||
stubs: {
|
||||
GlButton: true,
|
||||
GlAvatar: true,
|
||||
GlEmptyState: true,
|
||||
ServiceLevelAgreementCell: true,
|
||||
PaginatedTableWithSearchAndTabs: stubComponent(PaginatedTableWithSearchAndTabs, {
|
||||
template: RENDER_ALL_SLOTS_TEMPLATE,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
provide: {
|
||||
projectPath: '/project/path',
|
||||
newIssuePath,
|
||||
incidentTemplateName,
|
||||
incidentType,
|
||||
issuePath: '/project/issues',
|
||||
publishedAvailable: true,
|
||||
emptyListSvgPath,
|
||||
textQuery: '',
|
||||
authorUsernameQuery: '',
|
||||
assigneeUsernameQuery: '',
|
||||
slaFeatureAvailable: true,
|
||||
canCreateIncident: true,
|
||||
...provide,
|
||||
},
|
||||
stubs: {
|
||||
GlButton: true,
|
||||
GlAvatar: true,
|
||||
GlEmptyState: true,
|
||||
ServiceLevelAgreementCell: true,
|
||||
PaginatedTableWithSearchAndTabs: stubComponent(PaginatedTableWithSearchAndTabs, {
|
||||
template: RENDER_ALL_SLOTS_TEMPLATE,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
it('shows the loading state', () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { GlIcon } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { useFakeDate } from 'helpers/fake_date';
|
||||
import { STATUS_CLOSED } from '~/issues/constants';
|
||||
import IssueCardTimeInfo from '~/issues/list/components/issue_card_time_info.vue';
|
||||
|
|
@ -52,9 +52,10 @@ describe('CE IssueCardTimeInfo component', () => {
|
|||
|
||||
const findMilestone = () => wrapper.findComponent(IssuableMilestone);
|
||||
const findWorkItemAttribute = () => wrapper.findComponent(WorkItemAttribute);
|
||||
const findDueDateIcon = () => wrapper.findByTestId('issuable-due-date').findComponent(GlIcon);
|
||||
|
||||
const mountComponent = ({ issue = issueObject() } = {}) =>
|
||||
shallowMount(IssueCardTimeInfo, {
|
||||
shallowMountExtended(IssueCardTimeInfo, {
|
||||
propsData: { issue },
|
||||
stubs: {
|
||||
WorkItemAttribute,
|
||||
|
|
@ -82,8 +83,7 @@ describe('CE IssueCardTimeInfo component', () => {
|
|||
wrapper = mountComponent({ issue: object({ dueDate: '2020-12-12' }) });
|
||||
expect(findWorkItemAttribute().props('title')).toBe('Dec 12, 2020');
|
||||
expect(findWorkItemAttribute().props('tooltipText')).toBe('Due date');
|
||||
const datesEl = wrapper.find('[data-testid="issuable-due-date"]');
|
||||
expect(datesEl.findComponent(GlIcon).props()).toMatchObject({
|
||||
expect(findDueDateIcon().props()).toMatchObject({
|
||||
variant: 'current',
|
||||
name: 'calendar',
|
||||
});
|
||||
|
|
@ -94,8 +94,7 @@ describe('CE IssueCardTimeInfo component', () => {
|
|||
describe('when issue is open', () => {
|
||||
it('renders in red with overdue icon', () => {
|
||||
wrapper = mountComponent({ issue: object({ dueDate: '2020-10-10' }) });
|
||||
const datesEl = wrapper.find('[data-testid="issuable-due-date"]');
|
||||
expect(datesEl.findComponent(GlIcon).props()).toMatchObject({
|
||||
expect(findDueDateIcon().props()).toMatchObject({
|
||||
variant: 'danger',
|
||||
name: 'calendar-overdue',
|
||||
});
|
||||
|
|
@ -108,8 +107,7 @@ describe('CE IssueCardTimeInfo component', () => {
|
|||
issue: object({ dueDate: '2020-10-10', state: STATUS_CLOSED }),
|
||||
});
|
||||
|
||||
const datesEl = wrapper.find('[data-testid="issuable-due-date"]');
|
||||
expect(datesEl.findComponent(GlIcon).props()).toMatchObject({
|
||||
expect(findDueDateIcon().props()).toMatchObject({
|
||||
variant: 'current',
|
||||
name: 'calendar',
|
||||
});
|
||||
|
|
@ -143,7 +141,7 @@ describe('CE IssueCardTimeInfo component', () => {
|
|||
|
||||
it('renders time estimate', () => {
|
||||
wrapper = mountComponent();
|
||||
const timeEstimate = wrapper.find('[data-testid="time-estimate"]');
|
||||
const timeEstimate = wrapper.findByTestId('time-estimate');
|
||||
|
||||
expect(findWorkItemAttribute().props('title')).toBe('1w');
|
||||
expect(findWorkItemAttribute().props('tooltipText')).toBe('Estimate');
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
GlFormRadio,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import axios from 'axios';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import { kebabCase, merge } from 'lodash';
|
||||
|
|
@ -16,6 +15,7 @@ import { createAlert } from '~/alert';
|
|||
import * as urlUtility from '~/lib/utils/url_utility';
|
||||
import ForkForm from '~/pages/projects/forks/new/components/fork_form.vue';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import searchQuery from '~/pages/projects/forks/new/queries/search_forkable_namespaces.query.graphql';
|
||||
import ProjectNamespace from '~/pages/projects/forks/new/components/project_namespace.vue';
|
||||
import { START_RULE, CONTAINS_RULE } from '~/projects/project_name_rules';
|
||||
|
|
@ -119,8 +119,8 @@ describe('ForkForm component', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const createComponent = createComponentFactory(shallowMount);
|
||||
const createFullComponent = createComponentFactory(mount);
|
||||
const createComponent = createComponentFactory(shallowMountExtended);
|
||||
const createFullComponent = createComponentFactory(mountExtended);
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new AxiosMockAdapter(axios);
|
||||
|
|
@ -133,23 +133,21 @@ describe('ForkForm component', () => {
|
|||
axiosMock.restore();
|
||||
});
|
||||
|
||||
const findPrivateRadio = () => wrapper.find('[data-testid="radio-private"]');
|
||||
const findInternalRadio = () => wrapper.find('[data-testid="radio-internal"]');
|
||||
const findPublicRadio = () => wrapper.find('[data-testid="radio-public"]');
|
||||
const findForkNameInput = () => wrapper.find('[data-testid="fork-name-input"]');
|
||||
const findPrivateRadio = () => wrapper.findByTestId('radio-private');
|
||||
const findInternalRadio = () => wrapper.findByTestId('radio-internal');
|
||||
const findPublicRadio = () => wrapper.findByTestId('radio-public');
|
||||
const findForkNameInput = () => wrapper.findByTestId('fork-name-input');
|
||||
const findForkUrlInput = () => wrapper.findComponent(ProjectNamespace);
|
||||
const findForkSlugInput = () => wrapper.find('[data-testid="fork-slug-input"]');
|
||||
const findForkDescriptionTextarea = () =>
|
||||
wrapper.find('[data-testid="fork-description-textarea"]');
|
||||
const findVisibilityRadioGroup = () =>
|
||||
wrapper.find('[data-testid="fork-visibility-radio-group"]');
|
||||
const findBranchesRadioGroup = () => wrapper.find('[data-testid="fork-branches-radio-group"]');
|
||||
const findForkSlugInput = () => wrapper.findByTestId('fork-slug-input');
|
||||
const findForkDescriptionTextarea = () => wrapper.findByTestId('fork-description-textarea');
|
||||
const findVisibilityRadioGroup = () => wrapper.findByTestId('fork-visibility-radio-group');
|
||||
const findBranchesRadioGroup = () => wrapper.findByTestId('fork-branches-radio-group');
|
||||
|
||||
it('will go to cancelPath when click cancel button', () => {
|
||||
createComponent();
|
||||
|
||||
const { cancelPath } = DEFAULT_PROVIDE;
|
||||
const cancelButton = wrapper.find('[data-testid="cancel-button"]');
|
||||
const cancelButton = wrapper.findByTestId('cancel-button');
|
||||
|
||||
expect(cancelButton.attributes('href')).toBe(cancelPath);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { GlAlert, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
|
||||
import { GlAreaChart } from '@gitlab/ui/dist/charts';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
||||
import { nextTick } from 'vue';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
|
|
@ -26,10 +26,10 @@ describe('Code Coverage', () => {
|
|||
const findListBoxItems = () => wrapper.findAllComponents(GlListboxItem);
|
||||
const findFirstListBoxItem = () => findListBoxItems().at(0);
|
||||
const findSecondListBoxItem = () => findListBoxItems().at(1);
|
||||
const findDownloadButton = () => wrapper.find('[data-testid="download-button"]');
|
||||
const findDownloadButton = () => wrapper.findByTestId('download-button');
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(CodeCoverage, {
|
||||
wrapper = shallowMountExtended(CodeCoverage, {
|
||||
propsData: {
|
||||
graphEndpoint,
|
||||
graphStartDate,
|
||||
|
|
|
|||
|
|
@ -131,8 +131,7 @@ describe('Settings Panel', () => {
|
|||
findContainerRegistrySettings().findComponent(GlSprintf);
|
||||
const findContainerRegistryAccessLevelInput = () =>
|
||||
wrapper.find('[name="project[project_feature_attributes][container_registry_access_level]"]');
|
||||
const findPackageAccessLevel = () =>
|
||||
wrapper.find('[data-testid="package-registry-access-level"]');
|
||||
const findPackageAccessLevel = () => wrapper.findByTestId('package-registry-access-level');
|
||||
const findPackageRegistryEnabledInput = () => wrapper.find('[name="package_registry_enabled"]');
|
||||
const findPackageRegistryAccessLevelHiddenInput = () =>
|
||||
wrapper.find(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { GlSkeletonLoader, GlAlert } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import WikiContent from '~/pages/shared/wikis/components/wiki_content.vue';
|
||||
import { renderGFM } from '~/behaviors/markdown/render_gfm';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
|
@ -18,7 +18,7 @@ describe('pages/shared/wikis/components/wiki_content', () => {
|
|||
let mock;
|
||||
|
||||
function buildWrapper(propsData = {}) {
|
||||
wrapper = shallowMount(WikiContent, {
|
||||
wrapper = shallowMountExtended(WikiContent, {
|
||||
provide: {
|
||||
contentApi: PATH,
|
||||
},
|
||||
|
|
@ -36,7 +36,7 @@ describe('pages/shared/wikis/components/wiki_content', () => {
|
|||
|
||||
const findGlAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findGlSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
||||
const findContent = () => wrapper.find('[data-testid="wiki-page-content"]');
|
||||
const findContent = () => wrapper.findByTestId('wiki-page-content');
|
||||
|
||||
describe('when loading content', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { GlIcon, GlLink } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import component from '~/vue_shared/components/registry/metadata_item.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
|
|
@ -11,7 +11,7 @@ describe('Metadata Item', () => {
|
|||
};
|
||||
|
||||
const mountComponent = (propsData = defaultProps) => {
|
||||
wrapper = shallowMount(component, {
|
||||
wrapper = shallowMountExtended(component, {
|
||||
propsData,
|
||||
directives: {
|
||||
GlTooltip: createMockDirective('gl-tooltip'),
|
||||
|
|
@ -21,9 +21,9 @@ describe('Metadata Item', () => {
|
|||
|
||||
const findIcon = () => wrapper.findComponent(GlIcon);
|
||||
const findLink = (w = wrapper) => w.findComponent(GlLink);
|
||||
const findText = () => wrapper.find('[data-testid="metadata-item-text"]');
|
||||
const findText = () => wrapper.findByTestId('metadata-item-text');
|
||||
const findTooltipOnTruncate = (w = wrapper) => w.findComponent(TooltipOnTruncate);
|
||||
const findTextTooltip = () => wrapper.find('[data-testid="text-tooltip-container"]');
|
||||
const findTextTooltip = () => wrapper.findByTestId('text-tooltip-container');
|
||||
|
||||
const SIZE_TO_TAILWIND_UTILITY_MAPPING = {
|
||||
s: 'gl-max-w-20',
|
||||
|
|
|
|||
|
|
@ -40,13 +40,13 @@ describe('CreateWorkItemModal', () => {
|
|||
const createComponent = ({
|
||||
asDropdownItem = false,
|
||||
hideButton = false,
|
||||
workItemTypeName = 'EPIC',
|
||||
preselectedWorkItemType = 'EPIC',
|
||||
relatedItem = null,
|
||||
alwaysShowWorkItemTypeSelect = false,
|
||||
} = {}) => {
|
||||
wrapper = shallowMount(CreateWorkItemModal, {
|
||||
propsData: {
|
||||
workItemTypeName,
|
||||
preselectedWorkItemType,
|
||||
asDropdownItem,
|
||||
hideButton,
|
||||
relatedItem,
|
||||
|
|
@ -111,8 +111,8 @@ describe('CreateWorkItemModal', () => {
|
|||
expect(findTrigger().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('has text of "New item" when the `alwaysShowWorkItemTypeSelect` prop is `true` and we also have a `workItemTypeName`', () => {
|
||||
createComponent({ alwaysShowWorkItemTypeSelect: true, workItemTypeName: 'ISSUE' });
|
||||
it('has text of "New item" when the `alwaysShowWorkItemTypeSelect` prop is `true` and we also have a `preselectedWorkItemType`', () => {
|
||||
createComponent({ alwaysShowWorkItemTypeSelect: true, preselectedWorkItemType: 'ISSUE' });
|
||||
|
||||
expect(findTrigger().text()).toBe('New item');
|
||||
});
|
||||
|
|
@ -148,9 +148,9 @@ describe('CreateWorkItemModal', () => {
|
|||
expect(findCreateModal().props('visible')).toBe(false);
|
||||
});
|
||||
|
||||
for (const [workItemTypeName, vals] of Object.entries(WORK_ITEMS_TYPE_MAP)) {
|
||||
it(`has link to new work item page in modal header for ${workItemTypeName}`, async () => {
|
||||
createComponent({ workItemTypeName });
|
||||
for (const [preselectedWorkItemType, vals] of Object.entries(WORK_ITEMS_TYPE_MAP)) {
|
||||
it(`has link to new work item page in modal header for ${preselectedWorkItemType}`, async () => {
|
||||
createComponent({ preselectedWorkItemType });
|
||||
|
||||
const routeParamName = vals.routeParamName || WORK_ITEM_TYPE_ROUTE_WORK_ITEM;
|
||||
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ and even more`,
|
|||
title:
|
||||
'item 2 with a really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really rea',
|
||||
visible: true,
|
||||
workItemTypeName: 'TASK',
|
||||
preselectedWorkItemType: 'TASK',
|
||||
});
|
||||
|
||||
findCreateWorkItemModal().vm.$emit('workItemCreated');
|
||||
|
|
@ -295,7 +295,7 @@ and even more`,
|
|||
showProjectSelector: true,
|
||||
title: 'item 1',
|
||||
visible: true,
|
||||
workItemTypeName: 'ISSUE',
|
||||
preselectedWorkItemType: 'ISSUE',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -321,7 +321,7 @@ and even more`,
|
|||
showProjectSelector: false,
|
||||
title: 'item 1',
|
||||
visible: true,
|
||||
workItemTypeName: 'TASK',
|
||||
preselectedWorkItemType: 'TASK',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -449,6 +449,20 @@ RSpec.describe ApplicationSettingsHelper, feature_category: :shared do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.deletion_protection_data' do
|
||||
let_it_be(:application_setting) { build(:application_setting) }
|
||||
|
||||
before do
|
||||
application_setting.deletion_adjourned_period = 1
|
||||
|
||||
helper.instance_variable_set(:@application_setting, application_setting)
|
||||
end
|
||||
|
||||
subject { helper.deletion_protection_data }
|
||||
|
||||
it { is_expected.to eq({ deletion_adjourned_period: 1 }) }
|
||||
end
|
||||
|
||||
describe '#vscode_extension_marketplace_settings_view' do
|
||||
let(:feature_flag) { true }
|
||||
let(:application_setting) { build(:application_setting) }
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ RSpec.describe API::Entities::SSHKey, feature_category: :system_access do
|
|||
title: key.title,
|
||||
created_at: key.created_at,
|
||||
expires_at: key.expires_at,
|
||||
last_used_at: key.last_used_at,
|
||||
key: key.publishable_key,
|
||||
usage_type: 'auth_and_signing'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3353,7 +3353,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :text_editors d
|
|||
_, updates, message = service.execute(content, task_work_item)
|
||||
|
||||
expect(updates).to be_empty
|
||||
expect(message).to eq("This parent does not exist or you don't have sufficient permission.")
|
||||
expect(message).to eq("This parent item does not exist or you don't have sufficient permission.")
|
||||
expect(task_work_item.reload.work_item_parent).to be_nil
|
||||
end
|
||||
end
|
||||
|
|
@ -3367,7 +3367,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :text_editors d
|
|||
_, updates, message = service.execute(content, task_work_item_with_parent)
|
||||
|
||||
expect(updates).to be_empty
|
||||
expect(message).to eq("Task #{task_work_item_with_parent.to_reference} has already been added to " \
|
||||
expect(message).to eq("#{task_work_item_with_parent.to_reference} has already been added to " \
|
||||
"parent #{parent.to_reference}.")
|
||||
expect(task_work_item_with_parent.reload.work_item_parent).to eq parent
|
||||
end
|
||||
|
|
@ -3381,8 +3381,8 @@ RSpec.describe QuickActions::InterpretService, feature_category: :text_editors d
|
|||
_, updates, message = service.execute(content, task_work_item)
|
||||
|
||||
expect(updates).to be_empty
|
||||
expect(message).to eq("Cannot assign a confidential parent to a non-confidential Task. Make the " \
|
||||
"Task confidential and try again")
|
||||
expect(message).to eq("Cannot assign a confidential parent item to a non-confidential child item. Make " \
|
||||
"the child item confidential and try again.")
|
||||
expect(task_work_item.reload.work_item_parent).to be_nil
|
||||
end
|
||||
end
|
||||
|
|
@ -3395,7 +3395,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :text_editors d
|
|||
_, updates, message = service.execute(content, task_work_item)
|
||||
|
||||
expect(updates).to be_empty
|
||||
expect(message).to eq("Cannot assign a child Task to a Task")
|
||||
expect(message).to eq("Cannot assign this child type to parent type.")
|
||||
expect(task_work_item.reload.work_item_parent).to be_nil
|
||||
end
|
||||
end
|
||||
|
|
@ -3408,7 +3408,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :text_editors d
|
|||
_, updates, message = service.execute(content, task_work_item)
|
||||
|
||||
expect(updates).to be_empty
|
||||
expect(message).to eq("This parent does not exist or you don't have sufficient permission.")
|
||||
expect(message).to eq("This parent item does not exist or you don't have sufficient permission.")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'admin/application_settings/_deletion_protection_settings', feature_category: :system_access do
|
||||
let_it_be(:application_setting) do
|
||||
build(
|
||||
:application_setting,
|
||||
deletion_adjourned_period: 1
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
assign(:application_setting, application_setting)
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(downtier_delayed_deletion: true)
|
||||
end
|
||||
|
||||
it 'renders the deletion protection settings app root' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_selector('#js-admin-deletion-protection-settings')
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue