Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-03-23 12:11:54 +00:00
parent 1f8c5a116b
commit e10ea43772
108 changed files with 1057 additions and 256 deletions

View File

@ -235,7 +235,7 @@ _super-sidebar-nav:
SKIP_REPORT_IN_ISSUES: "true"
allow_failure: true
rules:
- if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
- if: $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
- !reference [.rules:test:manual, rules]
# ------------------------------------------

View File

@ -57,17 +57,10 @@
.rules:update-cache:
rules:
- <<: *not-canonical-project
when: never
- when: always
- if: '$UPDATE_QA_CACHE == "true"'
# This job requires project access token with api permissions to detect parallel jobs,
# it is problematic to set for every project that would include this template
# Because parallel jobs themselves can download knapsack report, skip for non canonical runs
.rules:download-knapsack:
rules:
- <<: *not-canonical-project
when: never
- when: always
# ------------------------------------------

View File

@ -1225,6 +1225,11 @@
- <<: *if-dot-com-gitlab-org-and-security-merge-request-and-qa-tests-specified
changes: *code-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes:
- qa/Gemfile.lock
variables:
UPDATE_QA_CACHE: "true"
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *code-patterns
when: manual
@ -1242,6 +1247,7 @@
SKIP_REPORT_IN_ISSUES: "false"
PROCESS_TEST_RESULTS: "true"
KNAPSACK_GENERATE_REPORT: "true"
UPDATE_QA_CACHE: "true"
QA_SAVE_TEST_METRICS: "true"
QA_EXPORT_TEST_METRICS: "false" # on main runs, metrics are exported to separate bucket via rake task for better consistency

View File

@ -1,5 +1,6 @@
---
Search/NamespacedClass:
Details: grace period
Exclude:
- 'app/controllers/concerns/search_rate_limitable.rb'
- 'app/controllers/search_controller.rb'

View File

@ -1,4 +1,5 @@
<script>
import { GlSprintf, GlLink } from '@gitlab/ui';
import { getTimeago } from '~/lib/utils/datetime_utility';
import { __, sprintf } from '~/locale';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
@ -6,6 +7,8 @@ import ListItem from '~/vue_shared/components/registry/list_item.vue';
export default {
name: 'AbuseReportRow',
components: {
GlLink,
GlSprintf,
ListItem,
},
props: {
@ -19,10 +22,22 @@ export default {
const template = __('Updated %{timeAgo}');
return sprintf(template, { timeAgo: getTimeago().format(this.report.updatedAt) });
},
reported() {
const { reportedUser } = this.report;
return sprintf('%{userLinkStart}%{reported}%{userLinkEnd}', {
reported: reportedUser.name,
});
},
reporter() {
const { reporter } = this.report;
return sprintf('%{reporterLinkStart}%{reporter}%{reporterLinkEnd}', {
reporter: reporter.name,
});
},
title() {
const { reportedUser, reporter, category } = this.report;
const { category } = this.report;
const template = __('%{reported} reported for %{category} by %{reporter}');
return sprintf(template, { reported: reportedUser.name, reporter: reporter.name, category });
return sprintf(template, { reported: this.reported, reporter: this.reporter, category });
},
},
};
@ -31,7 +46,16 @@ export default {
<template>
<list-item data-testid="abuse-report-row">
<template #left-primary>
<div class="gl-font-weight-normal" data-testid="title">{{ title }}</div>
<div class="gl-font-weight-normal" data-testid="title">
<gl-sprintf :message="title">
<template #userLink="{ content }">
<gl-link :href="report.reportedUserPath">{{ content }}</gl-link>
</template>
<template #reporterLink="{ content }">
<gl-link :href="report.reporterPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</div>
</template>
<template #right-secondary>

View File

@ -12,6 +12,7 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import getAlertsQuery from '~/graphql_shared/queries/get_alerts.query.graphql';
import { STATUS_CLOSED } from '~/issues/constants';
import { sortObjectToString } from '~/lib/utils/table_utility';
import { fetchPolicies } from '~/lib/graphql';
import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
@ -229,7 +230,7 @@ export default {
},
getIssueMeta({ issue: { iid, state } }) {
return {
state: state === 'closed' ? `(${this.$options.i18n.closed})` : '',
state: state === STATUS_CLOSED ? `(${this.$options.i18n.closed})` : '',
link: joinPaths('/', this.projectPath, '-', 'issues/incident', iid),
};
},

View File

@ -2,6 +2,7 @@
import { GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui';
import Draggable from 'vuedraggable';
import { mapActions, mapState } from 'vuex';
import { STATUS_CLOSED } from '~/issues/constants';
import { sprintf, __ } from '~/locale';
import { defaultSortableOptions } from '~/sortable/constants';
import { sortableStart, sortableEnd } from '~/sortable/utils';
@ -158,10 +159,10 @@ export default {
return this.isApolloBoard ? this.isLoadingMore : this.listsFlags[this.list.id]?.isLoadingMore;
},
epicCreateFormVisible() {
return this.isEpicBoard && this.list.listType !== 'closed' && this.showEpicForm;
return this.isEpicBoard && this.list.listType !== STATUS_CLOSED && this.showEpicForm;
},
issueCreateFormVisible() {
return !this.isEpicBoard && this.list.listType !== 'closed' && this.showIssueForm;
return !this.isEpicBoard && this.list.listType !== STATUS_CLOSED && this.showIssueForm;
},
listRef() {
// When list is draggable, the reference to the list needs to be accessed differently

View File

@ -2,7 +2,13 @@ import { last } from 'lodash';
import recentSearchesStorageKeys from 'ee_else_ce/filtered_search/recent_searches_storage_keys';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { createAlert } from '~/alert';
import { WORKSPACE_PROJECT } from '~/issues/constants';
import {
STATUS_ALL,
STATUS_CLOSED,
STATUS_MERGED,
STATUS_OPEN,
WORKSPACE_PROJECT,
} from '~/issues/constants';
import {
ENTER_KEY_CODE,
BACKSPACE_KEY_CODE,
@ -43,7 +49,7 @@ export default class FilteredSearchManager {
this.isGroupAncestor = isGroupAncestor;
this.isGroupDecendent = isGroupDecendent;
this.useDefaultState = useDefaultState;
this.states = ['opened', 'closed', 'merged', 'all'];
this.states = [STATUS_OPEN, STATUS_CLOSED, STATUS_MERGED, STATUS_ALL];
this.page = page;
this.container = FilteredSearchContainer.container;
@ -743,7 +749,7 @@ export default class FilteredSearchManager {
const { tokens, searchToken } = this.getSearchTokens();
let currentState = state || getParameterByName('state');
if (!currentState && this.useDefaultState) {
currentState = 'opened';
currentState = STATUS_OPEN;
}
if (this.states.includes(currentState)) {
paths.push(`state=${currentState}`);

View File

@ -1,4 +1,5 @@
import { createAlert } from '~/alert';
import { STATUS_OPEN } from '~/issues/constants';
import { __ } from '~/locale';
import { leftSidebarViews, PERMISSION_READ_MR, MAX_MR_FILES_AUTO_OPEN } from '../../constants';
import service from '../../services';
@ -16,7 +17,7 @@ export const getMergeRequestsForBranch = (
.getProjectMergeRequests(`${projectId}`, {
source_branch: branchId,
source_project_id: state.projects[projectId].id,
state: 'opened',
state: STATUS_OPEN,
order_by: 'created_at',
per_page: 1,
})

View File

@ -12,6 +12,7 @@ import {
GlEmptyState,
} from '@gitlab/ui';
import { isValidSlaDueAt } from 'ee_else_ce/vue_shared/components/incidents/utils';
import { STATUS_CLOSED } from '~/issues/constants';
import { visitUrl, mergeUrlParams, joinPaths } from '~/lib/utils/url_utility';
import { s__, n__ } from '~/locale';
import { INCIDENT_SEVERITY } from '~/sidebar/constants';
@ -301,6 +302,9 @@ export default {
getEscalationStatus(escalationStatus) {
return ESCALATION_STATUSES[escalationStatus] || this.$options.i18n.noEscalationStatus;
},
isClosed(item) {
return item.state === STATUS_CLOSED;
},
showIncidentLink({ iid }) {
return joinPaths(this.issuePath, INCIDENT_DETAILS_PATH, iid);
},
@ -397,7 +401,7 @@ export default {
<template #cell(title)="{ item }">
<div
:class="{
'gl-display-flex gl-align-items-center gl-max-w-full': item.state === 'closed',
'gl-display-flex gl-align-items-center gl-max-w-full': isClosed(item),
}"
>
<gl-link
@ -411,7 +415,7 @@ export default {
</tooltip-on-truncate>
</gl-link>
<gl-icon
v-if="item.state === 'closed'"
v-if="isClosed(item)"
name="issue-close"
class="gl-ml-2 gl-fill-blue-500 gl-flex-shrink-0"
:size="16"

View File

@ -1,4 +1,5 @@
import { isEmpty } from 'lodash';
import { STATUS_CLOSED, STATUS_MERGED, STATUS_OPEN, STATUS_REOPENED } from '~/issues/constants';
import { formatDate } from '~/lib/utils/datetime_utility';
import { sprintf, __ } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
@ -107,13 +108,13 @@ const mixins = {
return this.isMergeRequest && this.pipelineStatus && Object.keys(this.pipelineStatus).length;
},
isOpen() {
return this.state === 'opened' || this.state === 'reopened';
return this.state === STATUS_OPEN || this.state === STATUS_REOPENED;
},
isClosed() {
return this.state === 'closed';
return this.state === STATUS_CLOSED;
},
isMerged() {
return this.state === 'merged';
return this.state === STATUS_MERGED;
},
hasTitle() {
return this.title.length > 0;

View File

@ -1,6 +1,7 @@
<script>
import { GlLink, GlTooltip, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { STATUS_CLOSED } from '~/issues/constants';
import { __ } from '~/locale';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
@ -42,7 +43,7 @@ export default {
].filter(({ count }) => count);
},
isClosed() {
return this.suggestion.state === 'closed';
return this.suggestion.state === STATUS_CLOSED;
},
stateIconClass() {
return this.isClosed ? 'gl-text-blue-500' : 'gl-text-green-500';

View File

@ -0,0 +1,38 @@
import Vue from 'vue';
import {
createStore,
mapState,
mapGetters,
mapActions,
mapMutations,
createNamespacedHelpers,
} from 'vuex-vue3';
export { mapState, mapGetters, mapActions, mapMutations, createNamespacedHelpers };
const installedStores = new WeakMap();
export default {
Store: class VuexCompatStore {
constructor(...args) {
// eslint-disable-next-line no-constructor-return
return createStore(...args);
}
},
install() {
Vue.mixin({
beforeCreate() {
const { app } = this.$.appContext;
const { store } = this.$options;
if (store && !installedStores.get(app)?.has(store)) {
if (!installedStores.has(app)) {
installedStores.set(app, new WeakSet());
}
installedStores.get(app).add(store);
this.$.appContext.app.use(this.$options.store);
}
},
});
},
};

View File

@ -1,5 +1,6 @@
<script>
import { mapState } from 'vuex';
import { __ } from '~/locale';
import {
getParameterByName,
setUrlParams,
@ -46,6 +47,10 @@ export default {
return false;
}
if (token.type === 'user_type' && !gon.features?.serviceAccountsCrud) {
return false;
}
return this.filteredSearchBar.tokens?.includes(token.type);
});
},
@ -94,6 +99,14 @@ export default {
};
}
} else {
// Remove this block after this issue is closed: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2159
if (value.data === __('Service account')) {
return {
...accumulator,
[type]: 'service_account',
};
}
return {
...accumulator,
[type]: value.data,

View File

@ -23,7 +23,7 @@ const APP_OPTIONS = {
requestFormatter: groupMemberRequestFormatter,
filteredSearchBar: {
show: true,
tokens: ['two_factor', 'with_inherited_permissions', 'enterprise'],
tokens: ['two_factor', 'with_inherited_permissions', 'enterprise', 'user_type'],
searchParam: 'search',
placeholder: s__('Members|Filter members'),
recentSearchesStorageKey: 'group_members',

View File

@ -9,3 +9,9 @@ export const LEVEL_TYPES = {
GROUP: 'group',
DEPLOY_KEY: 'deploy_key',
};
export const BRANCH_RULES_ANCHOR = '#branch-rules';
export const IS_PROTECTED_BRANCH_CREATED = 'is_protected_branch_created';
export const PROTECTED_BRANCHES_ANCHOR = '#js-protected-branches-settings';

View File

@ -1,17 +1,24 @@
import $ from 'jquery';
import CreateItemDropdown from '~/create_item_dropdown';
import { createAlert } from '~/alert';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import AccessorUtilities from '~/lib/utils/accessor';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import { __, s__ } from '~/locale';
import AccessDropdown from '~/projects/settings/access_dropdown';
import { initToggle } from '~/toggles';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import { expandSection } from '~/settings_panels';
import { scrollToElement } from '~/lib/utils/common_utils';
import {
BRANCH_RULES_ANCHOR,
PROTECTED_BRANCHES_ANCHOR,
IS_PROTECTED_BRANCH_CREATED,
ACCESS_LEVELS,
LEVEL_TYPES,
} from './constants';
export default class ProtectedBranchCreate {
constructor(options) {
this.hasLicense = options.hasLicense;
this.$form = $('.js-new-protected-branch');
this.isLocalStorageAvailable = AccessorUtilities.canUseLocalStorage();
this.currentProjectUserDefaults = {};
@ -22,7 +29,7 @@ export default class ProtectedBranchCreate {
if (this.hasLicense) {
this.codeOwnerToggle = initToggle(document.querySelector('.js-code-owner-toggle'));
}
this.showSuccessAlertIfNeeded();
this.bindEvents();
}
@ -81,6 +88,49 @@ export default class ProtectedBranchCreate {
callback(gon.open_branches);
}
// eslint-disable-next-line class-methods-use-this
expandAndScroll(anchor) {
expandSection(anchor);
scrollToElement(anchor);
}
hasProtectedBranchSuccessAlert() {
return (
window.gon?.features?.branchRules &&
this.isLocalStorageAvailable &&
localStorage.getItem(IS_PROTECTED_BRANCH_CREATED)
);
}
createSuccessAlert() {
this.alert = createAlert({
variant: VARIANT_SUCCESS,
containerSelector: '.js-alert-protected-branch-created-container',
title: s__('ProtectedBranch|View protected branches as branch rules'),
message: s__('ProtectedBranch|Manage branch related settings in one area with branch rules.'),
primaryButton: {
text: s__('ProtectedBranch|View branch rule'),
clickHandler: () => {
this.expandAndScroll(BRANCH_RULES_ANCHOR);
},
},
secondaryButton: {
text: __('Dismiss'),
clickHandler: () => this.alert.dismiss(),
},
});
}
showSuccessAlertIfNeeded() {
if (!this.hasProtectedBranchSuccessAlert()) {
return;
}
this.expandAndScroll(PROTECTED_BRANCHES_ANCHOR);
this.createSuccessAlert();
localStorage.removeItem(IS_PROTECTED_BRANCH_CREATED);
}
getFormData() {
const formData = {
authenticity_token: this.$form.find('input[name="authenticity_token"]').val(),
@ -127,6 +177,9 @@ export default class ProtectedBranchCreate {
axios[this.$form.attr('method')](this.$form.attr('action'), this.getFormData())
.then(() => {
if (this.isLocalStorageAvailable) {
localStorage.setItem(IS_PROTECTED_BRANCH_CREATED, 'true');
}
window.location.reload();
})
.catch(() =>

View File

@ -31,6 +31,7 @@ export default {
i18n: {
collapseSidebar: __('Collapse sidebar'),
createNew: __('Create new...'),
homepage: __('Homepage'),
issues: __('Issues'),
mergeRequests: __('Merge requests'),
search: __('Search'),
@ -64,7 +65,11 @@ export default {
<template>
<div class="user-bar">
<div class="gl-display-flex gl-align-items-center gl-px-3 gl-py-2 gl-gap-2">
<a :href="rootPath">
<a
v-gl-tooltip:super-sidebar.hover.bottom="$options.i18n.homepage"
:href="rootPath"
:title="$options.i18n.homepage"
>
<img
v-if="sidebarData.logo_url"
data-testid="brand-header-custom-logo"

View File

@ -1,6 +1,7 @@
<script>
import { GlSprintf, GlLink } from '@gitlab/ui';
import { escape } from 'lodash';
import { STATUS_CLOSED, STATUS_MERGED } from '~/issues/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { n__, s__, sprintf } from '~/locale';
@ -49,7 +50,7 @@ export default {
},
computed: {
isMerged() {
return this.state === 'merged';
return this.state === STATUS_MERGED;
},
targetBranchEscaped() {
return escape(this.targetBranch);
@ -67,7 +68,7 @@ export default {
);
},
message() {
if (this.state === 'closed') {
if (this.state === STATUS_CLOSED) {
return s__('mrWidgetCommitsAdded|The changes were not merged into %{targetBranch}.');
} else if (this.isMerged) {
return s__(

View File

@ -1,6 +1,7 @@
<script>
import { GlButton, GlSprintf } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { STATUS_MERGED } from '~/issues/constants';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { HTTP_STATUS_UNAUTHORIZED } from '~/lib/utils/http_status';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@ -99,7 +100,7 @@ export default {
return !this.userHasApproved && this.userCanApprove && this.mr.isOpen;
},
showUnapprove() {
return this.userHasApproved && !this.userCanApprove && this.mr.state !== 'merged';
return this.userHasApproved && !this.userCanApprove && this.mr.state !== STATUS_MERGED;
},
approvalText() {
return this.isApproved && this.approvedBy.length > 0

View File

@ -1,5 +1,6 @@
<script>
import { GlLink } from '@gitlab/ui';
import { STATUS_CLOSED, STATUS_MERGED } from '~/issues/constants';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { s__, n__ } from '~/locale';
@ -30,10 +31,10 @@ export default {
},
computed: {
closesText() {
if (this.state === 'merged') {
if (this.state === STATUS_MERGED) {
return s__('mrWidget|Closed');
}
if (this.state === 'closed') {
if (this.state === STATUS_CLOSED) {
return s__('mrWidget|Did not close');
}

View File

@ -1,5 +1,6 @@
<script>
import { GlIcon } from '@gitlab/ui';
import { STATUS_CLOSED, STATUS_MERGED } from '~/issues/constants';
import StatusIcon from './extensions/status_icon.vue';
export default {
@ -14,22 +15,23 @@ export default {
},
},
computed: {
isClosed() {
return this.status === STATUS_CLOSED;
},
isLoading() {
return this.status === 'loading';
},
isMerged() {
return this.status === STATUS_MERGED;
},
},
};
</script>
<template>
<div class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3">
<div class="gl-display-flex gl-m-auto">
<gl-icon v-if="status === 'merged'" name="merge" :size="16" class="gl-text-blue-500" />
<gl-icon
v-else-if="status === 'closed'"
name="merge-request-close"
:size="16"
class="gl-text-red-500"
/>
<gl-icon v-if="isMerged" name="merge" :size="16" class="gl-text-blue-500" />
<gl-icon v-else-if="isClosed" name="merge-request-close" :size="16" class="gl-text-red-500" />
<status-icon v-else :is-loading="isLoading" :icon-name="status" :level="1" class="gl-m-0!" />
</div>
</div>

View File

@ -1,5 +1,6 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { STATUS_CLOSED, STATUS_MERGED } from '~/issues/constants';
import { __ } from '~/locale';
import StatusIcon from './mr_widget_status_icon.vue';
import Actions from './action_buttons.vue';
@ -41,8 +42,8 @@ export default {
},
computed: {
wrapperClasses() {
if (this.status === 'merged') return 'gl-bg-blue-50';
if (this.status === 'closed') return 'gl-bg-red-50';
if (this.status === STATUS_MERGED) return 'gl-bg-blue-50';
if (this.status === STATUS_CLOSED) return 'gl-bg-red-50';
return null;
},
hasActionsSlot() {

View File

@ -1,5 +1,6 @@
<script>
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import { STATUS_MERGED } from '~/issues/constants';
import simplePoll from '~/lib/utils/simple_poll';
import MergeRequest from '~/merge_request';
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
@ -50,7 +51,7 @@ export default {
.poll()
.then((res) => res.data)
.then((data) => {
if (data.state === 'merged') {
if (data.state === STATUS_MERGED) {
// If state is merged we should update the widget and stop the polling
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('FetchActionsContent');

View File

@ -16,6 +16,7 @@ import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_
import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql';
import { createAlert } from '~/alert';
import { TYPENAME_MERGE_REQUEST } from '~/graphql_shared/constants';
import { STATUS_CLOSED, STATUS_MERGED } from '~/issues/constants';
import { secondsToMilliseconds } from '~/lib/utils/datetime_utility';
import simplePoll from '~/lib/utils/simple_poll';
import { __, s__, n__ } from '~/locale';
@ -274,6 +275,12 @@ export default {
hasPipelineMustSucceedConflict() {
return !this.hasCI && this.stateData.onlyAllowMergeIfPipelineSucceeds;
},
isNotClosed() {
return this.mr.state !== STATUS_CLOSED;
},
isNeitherClosedNorMerged() {
return this.mr.state !== STATUS_CLOSED && this.mr.state !== STATUS_MERGED;
},
isRemoveSourceBranchButtonDisabled() {
return this.isMergeButtonDisabled;
},
@ -307,7 +314,7 @@ export default {
);
},
sourceBranchDeletedText() {
const isPreMerge = this.mr.state !== 'merged';
const isPreMerge = this.mr.state !== STATUS_MERGED;
if (isPreMerge) {
return this.mr.shouldRemoveSourceBranch
@ -495,7 +502,7 @@ export default {
<template>
<div
:class="{ 'gl-bg-gray-10': mr.state !== 'closed' && mr.state !== 'merged' }"
:class="{ 'gl-bg-gray-10': isNeitherClosedNorMerged }"
data-testid="ready_to_merge_state"
class="gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-pl-7"
>
@ -702,7 +709,7 @@ export default {
/>
</li>
<li
v-if="mr.state !== 'closed'"
v-if="isNotClosed"
class="gl-line-height-normal"
data-testid="source-branch-deleted-text"
>

View File

@ -1,4 +1,5 @@
/* eslint-disable */
import { STATUS_CLOSED } from '~/issues/constants';
import { EXTENSION_ICONS } from '../constants';
import issuesCollapsedQuery from './issues_collapsed.query.graphql';
import issuesQuery from './issues.query.graphql';
@ -82,7 +83,7 @@ export default {
// Icon to get rendered on the side of each row
icon: {
// Required: Name maps to an icon in GitLabs SVG
name: issue.state === 'closed' ? EXTENSION_ICONS.error : EXTENSION_ICONS.success,
name: issue.state === STATUS_CLOSED ? EXTENSION_ICONS.error : EXTENSION_ICONS.success,
},
// Badges get rendered next to the text on each row
// badge: issue.state === 'closed' && {

View File

@ -10,6 +10,7 @@ import MRWidgetService from 'ee_else_ce/vue_merge_request_widget/services/mr_wid
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
import { stateToComponentMap as classState } from 'ee_else_ce/vue_merge_request_widget/stores/state_maps';
import { createAlert } from '~/alert';
import { STATUS_CLOSED, STATUS_MERGED } from '~/issues/constants';
import notify from '~/lib/utils/notify';
import { sprintf, s__, __ } from '~/locale';
import Project from '~/pages/projects/project';
@ -226,7 +227,7 @@ export default {
return this.mr.allowCollaboration && this.mr.isOpen;
},
shouldRenderMergedPipeline() {
return this.mr.state === 'merged' && !isEmpty(this.mr.mergePipeline);
return this.mr.state === STATUS_MERGED && !isEmpty(this.mr.mergePipeline);
},
showMergePipelineForkWarning() {
return Boolean(
@ -264,7 +265,7 @@ export default {
return (this.mr.humanAccess || '').toLowerCase();
},
hasMergeError() {
return this.mr.mergeError && this.state !== 'closed';
return this.mr.mergeError && this.state !== STATUS_CLOSED;
},
hasAlerts() {
return this.hasMergeError || this.showMergePipelineForkWarning;

View File

@ -1,5 +1,6 @@
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
import { badgeState } from '~/issuable/components/status_box.vue';
import { STATUS_CLOSED, STATUS_MERGED, STATUS_OPEN } from '~/issues/constants';
import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
import { machine } from '~/lib/utils/finite_state_machine';
import {
@ -121,7 +122,7 @@ export default class MergeRequestStore {
this.ffOnlyEnabled = data.ff_only_enabled;
this.isRemovingSourceBranch = this.isRemovingSourceBranch || false;
this.mergeRequestState = data.state;
this.isOpen = this.mergeRequestState === 'opened';
this.isOpen = this.mergeRequestState === STATUS_OPEN;
this.latestSHA = data.diff_head_sha;
this.isMergeAllowed = data.mergeable || false;
this.mergeOngoing = data.merge_ongoing;
@ -236,11 +237,11 @@ export default class MergeRequestStore {
this.state = getStateKey.call(this);
} else {
switch (this.mergeRequestState) {
case 'merged':
this.state = 'merged';
case STATUS_MERGED:
this.state = STATUS_MERGED;
break;
case 'closed':
this.state = 'closed';
case STATUS_CLOSED:
this.state = STATUS_CLOSED;
break;
default:
this.state = null;

View File

@ -130,7 +130,7 @@ export default {
>
<div
:class="iconBgClass"
class="gl-float-left gl--flex-center gl-rounded-full gl-mt-n1 gl-ml-2 gl-w-6 gl-h-6"
class="gl-float-left gl--flex-center gl-rounded-full gl-mt-n1 gl-ml-2 gl-w-6 gl-h-6 timeline-icon"
>
<gl-icon
v-if="note.system_note_icon_name"

View File

@ -5,7 +5,9 @@ export default {
// We can't use this.vuexModule due to bug in vue-apollo when
// provide is called in beforeCreate
// See https://github.com/vuejs/vue-apollo/pull/1153 for details
vuexModule: this.$options.propsData.vuexModule,
// @vue-compat does not care to normalize propsData fields
vuexModule: this.$options.propsData.vuexModule ?? this.$options.propsData['vuex-module'],
};
},
props: {

View File

@ -2,6 +2,7 @@
import { GlLink, GlIcon, GlLabel, GlFormCheckbox, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { STATUS_CLOSED } from '~/issues/constants';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { getTimeago } from '~/lib/utils/datetime_utility';
import { isExternal, setUrlFragment } from '~/lib/utils/url_utility';
@ -93,13 +94,13 @@ export default {
return getTimeago().format(this.issuable.createdAt);
},
timestamp() {
if (this.issuable.state === 'closed' && this.issuable.closedAt) {
if (this.issuable.state === STATUS_CLOSED && this.issuable.closedAt) {
return this.issuable.closedAt;
}
return this.issuable.updatedAt;
},
formattedTimestamp() {
if (this.issuable.state === 'closed' && this.issuable.closedAt) {
if (this.issuable.state === STATUS_CLOSED && this.issuable.closedAt) {
return sprintf(__('closed %{timeago}'), {
timeago: getTimeago().format(this.issuable.closedAt),
});

View File

@ -139,7 +139,7 @@ export default {
<template>
<div class="issue-details issuable-details">
<div class="detail-page-description js-detail-page-description content-block gl-pt-2">
<div class="detail-page-description js-detail-page-description content-block gl-pt-4">
<issuable-edit-form
v-if="editFormVisible"
:issuable="issuable"

View File

@ -2,14 +2,6 @@
* File content holder
*
*/
.container-fluid.container-limited.limit-container-width {
.file-holder.readme-holder.limited-width-container .file-content {
max-width: $limited-layout-width;
margin-left: auto;
margin-right: auto;
}
}
.file-holder {
border: 1px solid $border-color;
border-top: 0;

View File

@ -324,6 +324,7 @@ $search-input-field-x-min-width: 200px;
padding-top: $gl-padding / 2;
padding-bottom: $gl-padding / 2;
align-items: center;
border-bottom: 1px solid $border-color;
}
.breadcrumbs-links {

View File

@ -1,6 +1,6 @@
.page-title-holder {
.page-title {
margin: $gl-padding 0;
margin: $gl-spacing-scale-4 0;
color: $gl-text-color;
}

View File

@ -632,7 +632,7 @@ body {
}
.page-title {
margin: 0 0 #{2 * $grid-size};
margin: $gl-spacing-scale-4 0;
line-height: 1.3;
&.with-button {

View File

@ -66,7 +66,7 @@
.title {
padding: 0;
margin-bottom: $gl-padding;
margin-bottom: $gl-spacing-scale-4;
border-bottom: 0;
word-wrap: break-word;
overflow-wrap: break-word;

View File

@ -1,4 +1,5 @@
.detail-page-header {
padding-top: $gl-spacing-scale-4;
color: $gl-text-color;
line-height: 34px;
display: flex;
@ -59,7 +60,7 @@
.detail-page-description {
.title {
margin: 0 0 16px;
margin: 0 0 $gl-spacing-scale-4;
color: $gl-text-color;
padding: 0 0 0.3em;
border-bottom: 1px solid $white-dark;

View File

@ -7,7 +7,7 @@ $system-note-icon-size: 1.5rem;
$system-note-svg-size: 1rem;
$icon-size-diff: $avatar-icon-size - $system-note-icon-size;
$system-note-icon-m-top: $avatar-m-top + $icon-size-diff - 0.1rem;
$system-note-icon-m-top: $avatar-m-top + $icon-size-diff - 0.3rem;
$system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
@mixin vertical-line($left) {
@ -343,6 +343,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.note-header-info {
padding-bottom: 0;
padding-top: 0;
}
&.timeline-entry::after {
@ -459,7 +460,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.timeline-icon {
margin: 8px 0 0 14px;
margin: 12px 0 0 20px;
}
}

View File

@ -16,6 +16,10 @@ class Groups::GroupMembersController < Groups::ApplicationController
before_action :authorize_admin_group_member!, except: admin_not_required_endpoints
before_action :authorize_read_group_member!, only: :index
before_action only: [:index] do
push_frontend_feature_flag(:service_accounts_crud, @group)
end
skip_before_action :check_two_factor_requirement, only: :leave
skip_cross_project_access_check :index, :update, :destroy, :request_access,
:approve_access_request, :leave, :resend_invite, :override

View File

@ -2,6 +2,8 @@
module Admin
class AbuseReportEntity < Grape::Entity
include RequestAwareEntity
expose :category
expose :updated_at
@ -12,5 +14,13 @@ module Admin
expose :reporter do |report|
UserEntity.represent(report.reporter, only: [:name])
end
expose :reported_user_path do |report|
user_path(report.user)
end
expose :reporter_path do |report|
user_path(report.reporter)
end
end
end

View File

@ -5,7 +5,7 @@
.gl-relative
.breadcrumbs{ class: [container, @content_class] }
.breadcrumbs-container{ class: ("border-bottom-0" if @no_breadcrumb_border) }
.breadcrumbs-container
- if show_super_sidebar?
= render Pajamas::ButtonComponent.new(icon: 'sidebar', category: :tertiary, button_options: { class: 'js-super-sidebar-toggle super-sidebar-toggle gl-ml-n3 gl-mr-2', title: _('Expand sidebar'), aria: { label: _('Expand sidebar') }, data: {toggle: 'tooltip', placement: 'right' } })
- elsif defined?(@left_sidebar)

View File

@ -1,4 +1,3 @@
- @no_breadcrumb_border = true
- show_auto_devops_callout = show_auto_devops_callout?(@project)
- is_project_overview = local_assigns.fetch(:is_project_overview, false)
- ref = local_assigns.fetch(:ref) { current_ref }
@ -8,7 +7,7 @@
- add_page_startup_api_call project_blob_path(@project, tree_join(@ref, readme_path), viewer: "rich", format: "json")
#tree-holder.tree-holder.clearfix.js-per-page{ data: { blame_per_page: Projects::BlameService::PER_PAGE } }
.info-well.gl-display-none.gl-sm-display-flex.project-last-commit.gl-flex-direction-column.gl-mt-2
.info-well.gl-display-none.gl-sm-display-flex.project-last-commit.gl-flex-direction-column.gl-mt-5
#js-last-commit.gl-m-auto
= gl_loading_icon(size: 'md')
#js-code-owners

View File

@ -3,7 +3,7 @@
- emails_disabled = @project.emails_disabled?
- cache_enabled = Feature.enabled?(:cache_home_panel, @project, type: :development)
.project-home-panel.js-show-on-project-root.gl-mt-2.gl-mb-5{ class: [("empty-project" if empty_repo)] }
.project-home-panel.js-show-on-project-root.gl-mt-4.gl-mb-5{ class: [("empty-project" if empty_repo)] }
.gl-display-flex.gl-justify-content-space-between.gl-flex-wrap.gl-sm-flex-direction-column.gl-mb-3.gl-gap-5
.home-panel-title-row.gl-display-flex.gl-align-items-center
%div{ class: 'avatar-container rect-avatar s64 home-panel-avatar gl-flex-shrink-0 gl-w-11 gl-h-11 gl-mr-3! float-none' }

View File

@ -3,13 +3,11 @@
- add_to_breadcrumbs _('Commits'), project_commits_path(@project)
- breadcrumb_title @commit.short_id
- container_class = !fluid_layout && diff_view == :inline ? 'container-limited' : ''
- limited_container_width = fluid_layout ? '' : 'limit-container-width'
- @content_class = limited_container_width
- page_title "#{@commit.title} (#{@commit.short_id})", _('Commits')
- page_description @commit.description
- add_page_specific_style 'page_bundles/pipelines'
.container-fluid{ class: [limited_container_width, container_class] }
.container-fluid{ class: [container_class] }
= render "commit_box"
= render "ci_menu"
= render "projects/diffs/diffs",

View File

@ -2,7 +2,7 @@
- breadcrumb_title _("New")
- page_title _("New Issue")
.top-area.gl-lg-flex-direction-row.gl-border-bottom-0
.page-title-holder
%h1.page-title.gl-font-size-h-display= _("New Issue")
= render "form"

View File

@ -1,3 +1,4 @@
- @content_class = "limit-container-width" unless fluid_layout
- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
- breadcrumb_title @issue.to_reference
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues")

View File

@ -1,4 +1,3 @@
- @no_breadcrumb_border = true
- can_update_merge_request = can?(current_user, :update_merge_request, @merge_request)
- can_reopen_merge_request = can?(current_user, :reopen_merge_request, @merge_request)
- are_close_and_open_buttons_hidden = merge_request_button_hidden?(@merge_request, true) && merge_request_button_hidden?(@merge_request, false)
@ -13,7 +12,7 @@
= c.body do
= _('The source project of this merge request has been removed.')
.detail-page-header.border-bottom-0.pt-0.pb-0.gl-display-block{ class: "gl-md-display-flex! #{'is-merge-request' if moved_mr_sidebar_enabled? && !fluid_layout}" }
.detail-page-header.border-bottom-0.pb-0.gl-display-block{ class: "gl-md-display-flex! #{'is-merge-request' if moved_mr_sidebar_enabled? && !fluid_layout}" }
.detail-page-header-body
.issuable-meta.gl-display-flex
#js-issuable-header-warnings{ data: { hidden: @merge_request.hidden?.to_s } }

View File

@ -4,7 +4,6 @@
- add_page_startup_graphql_call('repository/permissions', { projectPath: @project.full_path })
- add_page_startup_graphql_call('repository/files', { nextPageCursor: "", pageSize: 100, projectPath: @project.full_path, ref: current_ref, path: current_route_path || "/"})
- breadcrumb_title _("Repository")
- @content_class = "limit-container-width" unless fluid_layout
- page_title @path.presence || _("Files"), @ref
= content_for :meta_tags do

View File

@ -40,3 +40,5 @@
= render_if_exists 'protected_branches/ee/code_owner_approval_form', f: f, protected_branch_entity: protected_branch_entity
- c.footer do
= f.submit s_('ProtectedBranch|Protect'), disabled: true, data: { qa_selector: 'protect_button' }, pajamas_button: true
.js-alert-protected-branch-created-container.gl-mb-5

View File

@ -2,7 +2,7 @@
- api_awards_path = local_assigns.fetch(:api_awards_path, nil)
.issue-details.issuable-details.js-issue-details
.detail-page-description.content-block.js-detail-page-description.gl-pt-2.gl-pb-0.gl-border-none
.detail-page-description.content-block.js-detail-page-description.gl-pt-4.gl-pb-0.gl-border-none
#js-issuable-app{ data: { initial: issuable_initial_data(issuable).to_json,
issuable_id: issuable.id,
full_path: @project.full_path,

View File

@ -1,4 +1,4 @@
.detail-page-description.milestone-detail.gl-py-5
.detail-page-description.milestone-detail.gl-py-4
%h2.gl-m-0{ data: { qa_selector: "milestone_title_content" } }
= markdown_field(milestone, :title)
.gl-font-sm.gl-text-secondary.gl-font-base.gl-font-weight-normal.gl-line-height-normal{ data: { qa_selector: 'milestone_id_content' }, itemprop: 'identifier' }

View File

@ -0,0 +1,8 @@
---
name: service_accounts_crud
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113884
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/397730
milestone: '15.11'
type: development
group: group::authentication and authorization
default_enabled: false

View File

@ -561,6 +561,8 @@
- 1
- - wikis_git_garbage_collect
- 1
- - work_items_update_parent_objectives_progress
- 1
- - x509_certificate_revoke
- 1
- - zoekt_indexer

View File

@ -323,6 +323,7 @@ if (EXPLICIT_VUE_VERSION) {
if (USE_VUE3) {
Object.assign(alias, {
vue: '@vue/compat',
vuex: path.join(ROOT_PATH, 'app/assets/javascripts/lib/utils/vue3compat/vuex.js'),
});
vueLoaderOptions.compiler = require.resolve('./vue3migration/compiler');

View File

@ -0,0 +1,11 @@
---
table_name: search_indices
classes:
- Search::Index
- Search::NoteIndex
feature_categories:
- global_search
description: Represents an Advanced Search index
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113612/
milestone: '15.11'
gitlab_schema: gitlab_main

View File

@ -0,0 +1,10 @@
---
table_name: search_namespace_index_assignments
classes:
- Search::NamespaceIndexAssignment
feature_categories:
- global_search
description: Joins a Namespace to a Search::Index
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113612
milestone: '15.11'
gitlab_schema: gitlab_main

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class CreateSearchIndices < Gitlab::Database::Migration[2.1]
enable_lock_retries!
def change
create_table :search_indices do |t|
t.timestamps_with_timezone null: false
t.integer :bucket_number # We allow null bucket numbers to support custom index assignments
t.text :path, null: false, limit: 255
t.text :type, null: false, limit: 255
end
add_index :search_indices, [:id, :type], unique: true
add_index :search_indices, [:type, :path], unique: true
add_index :search_indices, [:type, :bucket_number], unique: true
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
class CreateSearchNamespaceIndexAssignments < Gitlab::Database::Migration[2.1]
enable_lock_retries!
def change
create_table :search_namespace_index_assignments do |t|
t.references :namespace, foreign_key: true, null: true, on_delete: :nullify
t.bigint :search_index_id, index: true, null: false
t.bigint :namespace_id_non_nullable, null: false
t.timestamps_with_timezone null: false
t.integer :namespace_id_hashed, null: false
t.text :index_type, null: false, limit: 255
end
add_index :search_namespace_index_assignments,
[:namespace_id, :index_type],
unique: true,
name: 'index_search_namespace_index_assignments_uniqueness_index_type'
add_index :search_namespace_index_assignments,
[:namespace_id, :search_index_id],
unique: true,
name: 'index_search_namespace_index_assignments_uniqueness_on_index_id'
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class AddSearchIndexForeignKeyToSearchNamespaceIndexAssignments < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
FK_NAME = 'fk_search_index_id_and_type'
def up
add_concurrent_foreign_key :search_namespace_index_assignments, :search_indices,
name: FK_NAME, column: [:search_index_id, :index_type], target_column: [:id, :type], on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :search_namespace_index_assignments, name: FK_NAME
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class AddIndexToVulnerabilityFindingsOnUuid < Gitlab::Database::Migration[2.1]
INDEX_NAME = 'index_vuln_findings_on_uuid_including_vuln_id'
disable_ddl_transaction!
# TODO: Index to be created synchronously in https://gitlab.com/gitlab-org/gitlab/-/issues/397740
def up
index_sql = <<-SQL
CREATE UNIQUE INDEX CONCURRENTLY #{INDEX_NAME} ON vulnerability_occurrences (uuid) include (vulnerability_id);
SQL
prepare_async_index_from_sql(:vulnerability_occurrences, INDEX_NAME, index_sql)
end
def down
unprepare_async_index_by_name(:vulnerability_occurrences, INDEX_NAME)
end
end

View File

@ -0,0 +1 @@
7c9f554950c0b2b2adc6d31d6cc42335dfd00965c61b2b24489e0099ad227a5c

View File

@ -0,0 +1 @@
c3098250c8ade855d84fec852dac81bab891e6e844404814ddff99711136d9eb

View File

@ -0,0 +1 @@
6dde6a29aefd3811f9c5bd144b24f33046e1762e13f18ad069d6d53a2448df49

View File

@ -0,0 +1 @@
b45db7a4404bbab731138f5db6031241945969a210f1c3b6fce323938ec8980d

View File

@ -22033,6 +22033,47 @@ CREATE SEQUENCE scim_oauth_access_tokens_id_seq
ALTER SEQUENCE scim_oauth_access_tokens_id_seq OWNED BY scim_oauth_access_tokens.id;
CREATE TABLE search_indices (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
bucket_number integer,
path text NOT NULL,
type text NOT NULL,
CONSTRAINT check_75c11e6d37 CHECK ((char_length(type) <= 255)),
CONSTRAINT check_ab47e7ff85 CHECK ((char_length(path) <= 255))
);
CREATE SEQUENCE search_indices_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE search_indices_id_seq OWNED BY search_indices.id;
CREATE TABLE search_namespace_index_assignments (
id bigint NOT NULL,
namespace_id bigint,
search_index_id bigint NOT NULL,
namespace_id_non_nullable bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
namespace_id_hashed integer NOT NULL,
index_type text NOT NULL,
CONSTRAINT check_64cf4e670a CHECK ((char_length(index_type) <= 255))
);
CREATE SEQUENCE search_namespace_index_assignments_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE search_namespace_index_assignments_id_seq OWNED BY search_namespace_index_assignments.id;
CREATE SEQUENCE security_findings_id_seq
START WITH 1
INCREMENT BY 1
@ -25271,6 +25312,10 @@ ALTER TABLE ONLY scim_identities ALTER COLUMN id SET DEFAULT nextval('scim_ident
ALTER TABLE ONLY scim_oauth_access_tokens ALTER COLUMN id SET DEFAULT nextval('scim_oauth_access_tokens_id_seq'::regclass);
ALTER TABLE ONLY search_indices ALTER COLUMN id SET DEFAULT nextval('search_indices_id_seq'::regclass);
ALTER TABLE ONLY search_namespace_index_assignments ALTER COLUMN id SET DEFAULT nextval('search_namespace_index_assignments_id_seq'::regclass);
ALTER TABLE ONLY security_findings ALTER COLUMN id SET DEFAULT nextval('security_findings_id_seq'::regclass);
ALTER TABLE ONLY security_orchestration_policy_configurations ALTER COLUMN id SET DEFAULT nextval('security_orchestration_policy_configurations_id_seq'::regclass);
@ -27649,6 +27694,12 @@ ALTER TABLE ONLY scim_identities
ALTER TABLE ONLY scim_oauth_access_tokens
ADD CONSTRAINT scim_oauth_access_tokens_pkey PRIMARY KEY (id);
ALTER TABLE ONLY search_indices
ADD CONSTRAINT search_indices_pkey PRIMARY KEY (id);
ALTER TABLE ONLY search_namespace_index_assignments
ADD CONSTRAINT search_namespace_index_assignments_pkey PRIMARY KEY (id);
ALTER TABLE ONLY security_findings
ADD CONSTRAINT security_findings_pkey PRIMARY KEY (id, partition_number);
@ -31883,6 +31934,20 @@ CREATE UNIQUE INDEX index_scim_identities_on_user_id_and_group_id ON scim_identi
CREATE UNIQUE INDEX index_scim_oauth_access_tokens_on_group_id_and_token_encrypted ON scim_oauth_access_tokens USING btree (group_id, token_encrypted);
CREATE UNIQUE INDEX index_search_indices_on_id_and_type ON search_indices USING btree (id, type);
CREATE UNIQUE INDEX index_search_indices_on_type_and_bucket_number ON search_indices USING btree (type, bucket_number);
CREATE UNIQUE INDEX index_search_indices_on_type_and_path ON search_indices USING btree (type, path);
CREATE INDEX index_search_namespace_index_assignments_on_namespace_id ON search_namespace_index_assignments USING btree (namespace_id);
CREATE INDEX index_search_namespace_index_assignments_on_search_index_id ON search_namespace_index_assignments USING btree (search_index_id);
CREATE UNIQUE INDEX index_search_namespace_index_assignments_uniqueness_index_type ON search_namespace_index_assignments USING btree (namespace_id, index_type);
CREATE UNIQUE INDEX index_search_namespace_index_assignments_uniqueness_on_index_id ON search_namespace_index_assignments USING btree (namespace_id, search_index_id);
CREATE INDEX index_secure_ci_builds_on_user_id_name_created_at ON ci_builds USING btree (user_id, name, created_at) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('apifuzzer_fuzz'::character varying)::text, ('apifuzzer_fuzz_dnd'::character varying)::text, ('secret_detection'::character varying)::text])));
CREATE INDEX index_security_ci_builds_on_name_and_id_parser_features ON ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text, ('apifuzzer_fuzz'::character varying)::text, ('apifuzzer_fuzz_dnd'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
@ -35113,6 +35178,9 @@ ALTER TABLE ONLY ip_restrictions
ALTER TABLE ONLY terraform_state_versions
ADD CONSTRAINT fk_rails_04f176e239 FOREIGN KEY (terraform_state_id) REFERENCES terraform_states(id) ON DELETE CASCADE;
ALTER TABLE ONLY search_namespace_index_assignments
ADD CONSTRAINT fk_rails_06f9b905d3 FOREIGN KEY (namespace_id) REFERENCES namespaces(id);
ALTER TABLE ONLY work_item_hierarchy_restrictions
ADD CONSTRAINT fk_rails_08cd7fef58 FOREIGN KEY (child_type_id) REFERENCES work_item_types(id) ON DELETE CASCADE;
@ -36796,6 +36864,9 @@ ALTER TABLE ONLY resource_label_events
ALTER TABLE ONLY pages_deployment_states
ADD CONSTRAINT fk_rails_ff6ca551a4 FOREIGN KEY (pages_deployment_id) REFERENCES pages_deployments(id) ON DELETE CASCADE;
ALTER TABLE ONLY search_namespace_index_assignments
ADD CONSTRAINT fk_search_index_id_and_type FOREIGN KEY (search_index_id, index_type) REFERENCES search_indices(id, type) ON DELETE CASCADE;
ALTER TABLE ONLY security_orchestration_policy_configurations
ADD CONSTRAINT fk_security_policy_configurations_management_project_id FOREIGN KEY (security_policy_management_project_id) REFERENCES projects(id) ON DELETE CASCADE;

View File

@ -18,6 +18,8 @@ The steps below cover:
- Configuring the Secure LDAP Client in the Google administrator console.
- Required GitLab configuration.
Secure LDAP is only available on specific Google Workspace editions. For more information, see the [Google Secure LDAP service documentation](https://support.google.com/a/answer/9048516).
## Configuring Google LDAP client
1. Go to <https://admin.google.com/Dashboard> and sign in as a Google Workspace domain administrator.

View File

@ -138,7 +138,7 @@ Example response:
## Group Audit Events
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34078) in GitLab 12.5.
> - [Support for keyset pagination added](https://gitlab.com/gitlab-org/gitlab/-/issues/333968) in GitLab 15.2.
> - Support for keyset pagination [added](https://gitlab.com/gitlab-org/gitlab/-/issues/333968) in GitLab 15.2.
The Group Audit Events API allows you to retrieve [group audit events](../administration/audit_events.md#group-events).
This API cannot retrieve project audit events.
@ -255,7 +255,7 @@ Example response:
## Project Audit Events
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/219238) in GitLab 13.1.
> - [Support for keyset pagination added](https://gitlab.com/gitlab-org/gitlab/-/issues/367528) in GitLab 15.10.
> - Support for keyset pagination [added](https://gitlab.com/gitlab-org/gitlab/-/issues/367528) in GitLab 15.10.
The Project Audit Events API allows you to retrieve [project audit events](../administration/audit_events.md#project-events).

View File

@ -521,11 +521,6 @@ To get the details of all projects within a group, use either the [list a group'
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/4"
```
NOTE:
There is [a known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/345200) that can
prevent `runners_token` from being returned when the call has the `with_projects=false`
parameter.
This endpoint returns:
- All projects and shared projects in GitLab 12.5 and earlier.

View File

@ -12,7 +12,7 @@ developers who are more comfortable with traditional API architecture.
## Compatibility guidelines
The HTTP API is versioned with a single number, which is currently `4`. This number
The HTTP API is versioned with a single number, which is `4`. This number
symbolizes the major version number, as described by [SemVer](https://semver.org/).
Because of this, backward-incompatible changes require this version number to
change.
@ -83,9 +83,9 @@ authentication isn't provided. When authentication is not required, the document
for each endpoint specifies this. For example, the
[`/projects/:id` endpoint](../projects.md#get-single-project) does not require authentication.
There are several ways you can authenticate with the GitLab API:
You can authenticate with the GitLab API in several ways:
- [OAuth2 tokens](#oauth2-tokens)
- [OAuth 2.0 tokens](#oauth-20-tokens)
- [Personal access tokens](../../user/profile/personal_access_tokens.md)
- [Project access tokens](../../user/project/settings/project_access_tokens.md)
- [Group access tokens](../../user/group/settings/group_access_tokens.md)
@ -116,30 +116,30 @@ NOTE:
Deploy tokens can't be used with the GitLab public API. For details, see
[Deploy Tokens](../../user/project/deploy_tokens/index.md).
### OAuth2 tokens
### OAuth 2.0 tokens
You can use an [OAuth2 token](../oauth2.md) to authenticate with the API by passing
You can use an [OAuth 2.0 token](../oauth2.md) to authenticate with the API by passing
it in either the `access_token` parameter or the `Authorization` header.
Example of using the OAuth2 token in a parameter:
Example of using the OAuth 2.0 token in a parameter:
```shell
curl "https://gitlab.example.com/api/v4/projects?access_token=OAUTH-TOKEN"
```
Example of using the OAuth2 token in a header:
Example of using the OAuth 2.0 token in a header:
```shell
curl --header "Authorization: Bearer OAUTH-TOKEN" "https://gitlab.example.com/api/v4/projects"
```
Read more about [GitLab as an OAuth2 provider](../oauth2.md).
Read more about [GitLab as an OAuth 2.0 provider](../oauth2.md).
NOTE:
We recommend OAuth access tokens have an expiration. You can use the `refresh_token` parameter
You should give OAuth access tokens an expiration. You can use the `refresh_token` parameter
to refresh tokens. Integrations may need to be updated to use refresh tokens prior to
expiration, which is based on the [`expires_in`](https://datatracker.ietf.org/doc/html/rfc6749#appendix-A.14)
property in the token endpoint response. See [OAuth2 token](../oauth2.md) documentation
property in the token endpoint response. See [OAuth 2.0 token](../oauth2.md) documentation
for examples requesting a new access token using a refresh token.
A default refresh setting of two hours is tracked in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/336598).
@ -299,52 +299,52 @@ insight into what went wrong.
The following table gives an overview of how the API functions generally behave.
| Request type | Description |
|---------------|-------------|
| `GET` | Access one or more resources and return the result as JSON. |
| `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. |
| `GET` / `PUT` | Return `200 OK` if the resource is accessed or modified successfully. The (modified) result is returned as JSON. |
| Request type | Description |
|:--------------|:--------------------------------------------------------------------------------------------------------------------------------|
| `GET` | Access one or more resources and return the result as JSON. |
| `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. |
| `GET` / `PUT` | Return `200 OK` if the resource is accessed or modified successfully. The (modified) result is returned as JSON. |
| `DELETE` | Returns `204 No Content` if the resource was deleted successfully or `202 Accepted` if the resource is scheduled to be deleted. |
The following table shows the possible return codes for API requests.
| Return values | Description |
|--------------------------|-------------|
| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, and the resource itself is returned as JSON. |
| `201 Created` | The `POST` request was successful, and the resource is returned as JSON. |
| `202 Accepted` | The `GET`, `PUT` or `DELETE` request was successful, and the resource is scheduled for processing. |
| `204 No Content` | The server has successfully fulfilled the request, and there is no additional content to send in the response payload body. |
| `304 Not Modified` | The resource hasn't been modified since the last request. |
| `400 Bad Request` | A required attribute of the API request is missing. For example, the title of an issue is not given. |
| `401 Unauthorized` | The user isn't authenticated. A valid [user token](#authentication) is necessary. |
| `403 Forbidden` | The request isn't allowed. For example, the user isn't allowed to delete a project. |
| `404 Not Found` | A resource couldn't be accessed. For example, an ID for a resource couldn't be found, or the user isn't authorized to access the resource. |
| `405 Method Not Allowed` | The request isn't supported. |
| `409 Conflict` | A conflicting resource already exists. For example, creating a project with a name that already exists. |
| `412 Precondition Failed`| The request was denied. This can happen if the `If-Unmodified-Since` header is provided when trying to delete a resource, which was modified in between. |
| `422 Unprocessable` | The entity couldn't be processed. |
| `429 Too Many Requests` | The user exceeded the [application rate limits](../../administration/instance_limits.md#rate-limits). |
| `500 Server Error` | While handling the request, something went wrong on the server. |
| Return values | Description |
|:--------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------|
| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, and the resource itself is returned as JSON. |
| `201 Created` | The `POST` request was successful, and the resource is returned as JSON. |
| `202 Accepted` | The `GET`, `PUT` or `DELETE` request was successful, and the resource is scheduled for processing. |
| `204 No Content` | The server has successfully fulfilled the request, and there is no additional content to send in the response payload body. |
| `304 Not Modified` | The resource hasn't been modified since the last request. |
| `400 Bad Request` | A required attribute of the API request is missing. For example, the title of an issue is not given. |
| `401 Unauthorized` | The user isn't authenticated. A valid [user token](#authentication) is necessary. |
| `403 Forbidden` | The request isn't allowed. For example, the user isn't allowed to delete a project. |
| `404 Not Found` | A resource couldn't be accessed. For example, an ID for a resource couldn't be found, or the user isn't authorized to access the resource. |
| `405 Method Not Allowed` | The request isn't supported. |
| `409 Conflict` | A conflicting resource already exists. For example, creating a project with a name that already exists. |
| `412 Precondition Failed` | The request was denied. This can happen if the `If-Unmodified-Since` header is provided when trying to delete a resource, which was modified in between. |
| `422 Unprocessable` | The entity couldn't be processed. |
| `429 Too Many Requests` | The user exceeded the [application rate limits](../../administration/instance_limits.md#rate-limits). |
| `500 Server Error` | While handling the request, something went wrong on the server. |
## Pagination
GitLab supports the following pagination methods:
- Offset-based pagination. This is the default method and is available on all endpoints.
- Offset-based pagination. The default method and available on all endpoints.
- Keyset-based pagination. Added to selected endpoints but being
[progressively rolled out](https://gitlab.com/groups/gitlab-org/-/epics/2039).
For large collections, for performance reasons we recommend keyset pagination
(when available) instead of offset pagination.
For large collections, you should use keyset pagination
(when available) instead of offset pagination, for performance reasons.
### Offset-based pagination
Sometimes, the returned result spans many pages. When listing resources, you can
pass the following parameters:
| Parameter | Description |
|------------|-------------|
| `page` | Page number (default: `1`). |
| Parameter | Description |
|:-----------|:--------------------------------------------------------------|
| `page` | Page number (default: `1`). |
| `per_page` | Number of items to list per page (default: `20`, max: `100`). |
In the following example, we list 50 [namespaces](../namespaces.md) per page:
@ -397,14 +397,14 @@ x-total-pages: 3
GitLab also returns the following additional pagination headers:
| Header | Description |
|-----------------|-------------|
| `x-next-page` | The index of the next page. |
| Header | Description |
|:----------------|:-----------------------------------------------|
| `x-next-page` | The index of the next page. |
| `x-page` | The index of the current page (starting at 1). |
| `x-per-page` | The number of items per page. |
| `x-prev-page` | The index of the previous page. |
| `x-total` | The total number of items. |
| `x-total-pages` | The total number of pages. |
| `x-per-page` | The number of items per page. |
| `x-prev-page` | The index of the previous page. |
| `x-total` | The total number of items. |
| `x-total-pages` | The total number of pages. |
For GitLab.com users, [some pagination headers may not be returned](../../user/gitlab_com/index.md#pagination-response-headers).
@ -476,21 +476,23 @@ When the end of the collection is reached and there are no additional
records to retrieve, the `Link` header is absent and the resulting array is
empty.
We recommend using only the given link to retrieve the next page instead of
You should use only the given link to retrieve the next page instead of
building your own URL. Apart from the headers shown, we don't expose additional
pagination headers.
#### Supported resources
Keyset-based pagination is supported only for selected resources and ordering
options:
| Resource | Options | Availability |
|:------------------------------------------------------------------|:---------------------------------|:--------------------------------------------------------------------------------------------------------------|
| [Projects](../projects.md) | `order_by=id` only | Authenticated and unauthenticated users |
| [Group audit events](../audit_events.md#group-audit-events) | `order_by=id`, `sort=desc` only | Authenticated users only ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/333968) in GitLab 15.2) |
| [Groups](../groups.md) | `order_by=name`, `sort=asc` only | Unauthenticated users only |
| [Instance audit events](../audit_events.md#instance-audit-events) | `order_by=id`, `sort=desc` only | Authenticated users only ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367528) in GitLab 15.11) |
| [Group audit events](../audit_events.md#group-audit-events) | `order_by=id`, `sort=desc` only | Authenticated users only ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/333968) in GitLab 15.2) |
| [Project audit events](../audit_events.md#project-audit-events) | `order_by=id`, `sort=desc` only | Authenticated users only ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367528) in GitLab 15.10) |
| [Jobs](../jobs.md) | `order_by=id`, `sort=desc` only | Authenticated users only ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/362172) in GitLab 15.9) |
| [Project audit events](../audit_events.md#project-audit-events) | `order_by=id`, `sort=desc` only | Authenticated users only ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367528) in GitLab 15.10) |
| [Projects](../projects.md) | `order_by=id` only | Authenticated and unauthenticated users |
### Pagination response headers

View File

@ -112,24 +112,14 @@ attempts to build the full path by:
#### Path correction example
As an example, a project with:
As an example, a C# project with:
- A full path of `test-org/test-project`.
- A full path of `test-org/test-cs-project`.
- The following files relative to the project root:
```shell
Auth/User.cs
Lib/Utils/User.cs
src/main/java
```
In the:
- Cobertura XML, the `filename` attribute in the `class` element assumes the value is a relative
path to the project's root:
```xml
<class name="packet.name" filename="src/main/java" line-rate="0.0" branch-rate="0.0" complexity="5">
```
- `sources` from Cobertura XML, the following paths in the format
@ -137,8 +127,8 @@ In the:
```xml
<sources>
<source>/builds/test-org/test-project/Auth</source>
<source>/builds/test-org/test-project/Lib/Utils</source>
<source>/builds/test-org/test-cs-project/Auth</source>
<source>/builds/test-org/test-cs-project/Lib/Utils</source>
</sources>
```
@ -153,6 +143,29 @@ The parser:
100 iterations. If it reaches this limit without finding a matching path in the file tree, the
class is not included in the final coverage report.
Automatic class path correction also works for a Java project with:
- A full path of `test-org/test-java-project`.
- The following files relative to the project root:
```shell
src/main/java/com/gitlab/security_products/tests/App.java
```
- `sources` from Cobertura XML:
```xml
<sources>
<source>/builds/test-org/test-java-project/src/main/java/</source>
</sources>
```
- `class` element with the `filename` value of `com/gitlab/security_products/tests/App.java`:
```xml
<class name="com.gitlab.security_products.tests.App" filename="com/gitlab/security_products/tests/App.java" line-rate="0.0" branch-rate="0.0" complexity="6.0">
```
NOTE:
Automatic class path correction only works on `source` paths in the format `<CI_BUILDS_DIR>/<PROJECT_FULL_PATH>/...`.
The `source` is ignored if the path does not follow this pattern. The parser assumes that the

View File

@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Authentication **(FREE)**
This page gathers all the resources for the topic **Authentication** within GitLab.
This page gathers all the resources for the topic **Authentication** in GitLab.
## GitLab users
@ -17,7 +17,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Support for Universal 2nd Factor Authentication - YubiKeys](https://about.gitlab.com/blog/2016/06/22/gitlab-adds-support-for-u2f/)
- [Security Webcast with Yubico](https://about.gitlab.com/blog/2016/08/31/gitlab-and-yubico-security-webcast/)
- **Integrations:**
- [GitLab as OAuth2 authentication service provider](../../integration/oauth_provider.md)
- [GitLab as OAuth 2.0 authentication service provider](../../integration/oauth_provider.md)
- [GitLab as OpenID Connect identity provider](../../integration/openid_connect_provider.md)
## GitLab administrators
@ -38,7 +38,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
## API
- [OAuth 2 Tokens](../../api/rest/index.md#oauth2-tokens)
- [OAuth 2.0 tokens](../../api/rest/index.md#oauth-20-tokens)
- [Personal access tokens](../../api/rest/index.md#personalprojectgroup-access-tokens)
- [Project access tokens](../../api/rest/index.md#personalprojectgroup-access-tokens)
- [Group access tokens](../../api/rest/index.md#personalprojectgroup-access-tokens)

View File

@ -0,0 +1,177 @@
---
stage: Govern
group: Compliance
info: For assistance with this tutorial, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
---
# Tutorial: Create a compliance pipeline **(ULTIMATE)**
You can use [compliance pipelines](../user/group/compliance_frameworks.md#compliance-pipelines) to ensure specific
compliance-related jobs are run on pipelines for all projects in a group. Compliance pipelines are applied
to projects through [compliance frameworks](../user/group/compliance_frameworks.md).
In this tutorial, you:
1. Create a [new group](#create-a-new-group).
1. Create a [new project for the compliance pipeline configuration](#create-a-new-compliance-pipeline-project).
1. Configure a [compliance framework](#configure-compliance-framework) to apply to other projects.
1. Create a [new project and apply the compliance framework](#create-a-new-project-and-apply-the-compliance-framework) to it.
1. Combine [compliance pipeline configuration and regular pipeline configuration](#combine-pipeline-configurations).
Prerequisites:
- Permission to create new top-level groups.
## Create a new group
Compliance frameworks are configured in top-level groups. In this tutorial, you create a top-level group that:
- Contains two projects:
- The compliance pipeline project to store the compliance pipeline configuration.
- Another project that must run a job in its pipeline that is defined by the compliance pipeline configuration.
- Has the compliance framework to apply to projects.
To create the new group:
1. On the top bar, select **Create new... > New group**.
1. Select **Create group**.
1. In the **Group name** field, enter `Tutorial group`.
1. Select **Create group**.
## Create a new compliance pipeline project
Now you're ready to create a compliance pipeline project. This project contains the
[compliance pipeline configuration](../user/group/compliance_frameworks.md#example-configuration) to apply to all
projects with the compliance framework applied.
To create the compliance pipeline project:
1. On the top bar, select **Main menu > Groups** and find the `Tutorial group` group.
1. Select **New project**.
1. Select **Create blank project**.
1. In the **Project name** field, enter `Tutorial compliance project`.
1. Select **Create project**.
To add compliance pipeline configuration to `Tutorial compliance project`:
1. On the top bar, select **Main menu > Projects** and find the `Tutorial compliance project` project.
1. On the left sidebar, select **CI/CD > Editor**.
1. Select **Configure pipeline**.
1. In the pipeline editor, replace the default configuration with:
```yaml
---
compliance-job:
script:
- echo "Running compliance job required for every project in this group..."
```
1. Select **Commit changes**.
## Configure compliance framework
The compliance framework is configured in the [new group](#create-a-new-group).
To configure the compliance framework:
1. On the top bar, select **Main menu > Groups** and find the `Tutorial group` group.
1. On the left sidebar, select **Settings > General**.
1. Expand **Compliance frameworks**.
1. Select **Add framework**.
1. In the **Name** field, enter `Tutorial compliance framework`.
1. In the **Description** field, enter `Compliance framework for tutorial`.
1. In the **Compliance pipeline configuration (optional)** field, enter
`.gitlab-ci.yml@tutorial-group/tutorial-compliance-project`.
1. In the **Background color** field, select a color of your choice.
1. Select **Add framework**.
For convenience, make the new compliance framework the default for all new projects in the group:
1. On the top bar, select **Main menu > Groups** and find the `Tutorial group` group.
1. On the left sidebar, select **Settings > General**.
1. Expand **Compliance frameworks**.
1. In the row for `Tutorial compliance framework`, select **Options** (**{ellipsis_v}**).
1. Select **Set default**.
## Create a new project and apply the compliance framework
Your compliance framework is ready, so you can now create projects in the group and they automatically run the
compliance pipeline configuration in their pipelines.
To create a new project for running the compliance pipeline configuration:
1. On the top bar, select **Main menu > Groups** and find the `Tutorial group` group.
1. Select **New project**.
1. Select **Create blank project**.
1. In the **Project name** field, enter `Tutorial project`.
1. Select **Create project**.
On the project page, notice the `Tutorial compliance framework` label appears because that was set as the default
compliance framework for the group.
Without any other pipeline configuration, `Tutorial project` can run the jobs defined in the compliance
pipeline configuration in `Tutorial compliance project`.
To run the compliance pipeline configuration in `Tutorial project`:
1. On the top bar, select **Main menu > Projects** and find the `Tutorial project` project.
1. Select **CI/CD Pipelines**.
1. Select **Run pipeline**.
1. On the **Run pipeline** page, select **Run pipeline**.
Notice the pipeline runs a job called `compliance-job` in a **test** stage. Nice work, you've run your first compliance
job!
## Combine pipeline configurations
If you want projects to run their own jobs as well as the compliance pipeline jobs, you must combine the compliance
pipeline configuration and the regular pipeline configuration of the project.
To combine the pipeline configurations, you must define the regular pipeline configuration and then update the
compliance pipeline configuration to refer to it.
To create the regular pipeline configuration:
1. On the top bar, select **Main menu > Projects** and find the `Tutorial project` project.
1. On the left sidebar, select **CI/CD > Editor**.
1. Select **Configure pipeline**.
1. In the pipeline editor, replace the default configuration with:
```yaml
---
project-job:
script:
- echo "Running project job..."
```
1. Select **Commit changes**.
To combine the new project pipeline configuration with the compliance pipeline configuration:
1. On the top bar, select **Main menu > Projects** and find the `Tutorial compliance project` project.
1. On the left sidebar, select **CI/CD > Editor**.
1. In the existing configuration, add:
```yaml
include:
- project: 'tutorial-group/tutorial-project'
file: '.gitlab-ci.yml'
```
1. Select **Commit changes**.
To confirm the regular pipeline configuration is combined with the compliance pipeline configuration:
1. On the top bar, select **Main menu > Projects** and find the `Tutorial project` project.
1. Select **CI/CD Pipelines**.
1. Select **Run pipeline**.
1. On the **Run pipeline** page, select **Run pipeline**.
Notice the pipeline runs two jobs in a **test** stage:
- `compliance-job`.
- `project-job`.
Congratulations, you've created and configured a compliance pipeline!
See more [example compliance pipeline configurations](../user/group/compliance_frameworks.md#example-configuration).

View File

@ -264,6 +264,12 @@ NOTE:
Specific information that follow related to Ruby and Git versions do not apply to [Omnibus installations](https://docs.gitlab.com/omnibus/)
and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with appropriate Ruby and Git versions and are not using system binaries for Ruby and Git. There is no need to install Ruby or Git when utilizing these two approaches.
### 16.0.0
- Sidekiq jobs are only routed to `default` and `mailers` queues by default, and as a result,
every Sidekiq process also listens to those queues to ensure all jobs are processed across
all queues. This behavior does not apply if you have configured the [routing rules](../administration/sidekiq/processing_specific_job_classes.md#routing-rules).
### 15.10.0
- Gitaly configuration changes significantly in Omnibus GitLab 16.0. You can begin migrating to the new structure in Omnibus GitLab 15.10 while backwards compatibility is

View File

@ -51,7 +51,7 @@ You can configure the following security controls:
- [Static Application Security Testing](../sast/index.md) (SAST)
- Select **Enable SAST** to configure SAST for the current project.
For more details, read [Configure SAST in the UI](../sast/index.md#configure-sast-in-the-ui).
For more details, read [Configure SAST in the UI](../sast/index.md#configure-sast-by-using-the-ui).
- [Dynamic Application Security Testing](../dast/index.md) (DAST)
- Select **Enable DAST** to configure DAST for the current project.
- Select **Manage scans** to manage the saved DAST scans, site profiles, and scanner profiles.

View File

@ -238,7 +238,7 @@ as shown in the following table:
| See new findings in merge request widget | **{dotted-circle}** | **{check-circle}** |
| [Manage vulnerabilities](../vulnerabilities/index.md) | **{dotted-circle}** | **{check-circle}** |
| [Access the Security Dashboard](../security_dashboard/index.md) | **{dotted-circle}** | **{check-circle}** |
| [Configure SAST in the UI](#configure-sast-in-the-ui) | **{dotted-circle}** | **{check-circle}** |
| [Configure SAST by using the UI](#configure-sast-by-using-the-ui) | **{dotted-circle}** | **{check-circle}** |
| [Customize SAST rulesets](customize_rulesets.md) | **{dotted-circle}** | **{check-circle}** |
| [Detect False Positives](#false-positive-detection) | **{dotted-circle}** | **{check-circle}** |
| [Track moved vulnerabilities](#advanced-vulnerability-tracking) | **{dotted-circle}** | **{check-circle}** |
@ -257,7 +257,7 @@ To configure SAST for a project you can:
- Use [Auto SAST](../../../topics/autodevops/stages.md#auto-sast), provided by
[Auto DevOps](../../../topics/autodevops/index.md).
- [Configure SAST in your CI/CD YAML](#configure-sast-in-your-cicd-yaml).
- [Configure SAST using the UI](#configure-sast-in-the-ui) (introduced in GitLab 13.3).
- [Configure SAST by using the UI](#configure-sast-by-using-the-ui).
You can enable SAST across many projects by [enforcing scan execution](../index.md#enforce-scan-execution).
@ -282,15 +282,12 @@ The results are saved as a
that you can later download and analyze.
When downloading, you always receive the most recent SAST artifact available.
### Configure SAST in the UI
### Configure SAST by using the UI
You can enable and configure SAST in the UI, either with default settings, or with customizations.
You can enable and configure SAST by using the UI, either with the default settings or with customizations.
The method you can use depends on your GitLab license tier.
- [Configure SAST in the UI with customizations](#configure-sast-in-the-ui-with-customizations). **(ULTIMATE)**
- [Configure SAST in the UI with default settings only](#configure-sast-in-the-ui-with-default-settings-only).
### Configure SAST in the UI with customizations **(ULTIMATE)**
#### Configure SAST with customizations **(ULTIMATE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3659) in GitLab 13.3.
> - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/232862) in GitLab 13.4.
@ -319,7 +316,7 @@ To enable and configure SAST with customizations:
Pipelines now include a SAST job.
### Configure SAST in the UI with default settings only
#### Configure SAST with default settings only
> [Introduced](https://about.gitlab.com/releases/2021/02/22/gitlab-13-9-released/#security-configuration-page-for-all-users) in GitLab 13.9

View File

@ -108,8 +108,11 @@ However, the compliance pipeline configuration can reference the `.gitlab-ci.yml
- Jobs and variables defined in the compliance pipeline can't be changed by variables in the labeled project's
`.gitlab-ci.yml` file.
See [example configuration](#example-configuration) for help configuring a compliance pipeline that runs jobs from
labeled project pipeline configuration.
For more information, see:
- [Example configuration](#example-configuration) for help configuring a compliance pipeline that runs jobs from
labeled project pipeline configuration.
- The [Create a compliance pipeline](../../tutorials/create_compliance_pipeline.md) tutorial.
### Effect on labeled projects

View File

@ -40,6 +40,9 @@ module.exports = (path, options = {}) => {
Object.assign(vueModuleNameMappers, {
'^vue$': '@vue/compat',
'^@vue/test-utils$': '@vue/test-utils-vue3',
// Library wrappers
'^vuex$': '<rootDir>/app/assets/javascripts/lib/utils/vue3compat/vuex.js',
});
Object.assign(globals, {
'vue-jest': {

View File

@ -7,22 +7,13 @@ module Gitlab
class Mapper
# Matches the first file type that matches the given location
class Matcher < Base
FILE_CLASSES = [
External::File::Local,
External::File::Project,
External::File::Component,
External::File::Remote,
External::File::Template,
External::File::Artifact
].freeze
FILE_SUBKEYS = FILE_CLASSES.map { |f| f.name.demodulize.downcase }.freeze
include Gitlab::Utils::StrongMemoize
private
def process_without_instrumentation(locations)
locations.map do |location|
matching = FILE_CLASSES.map do |file_class|
matching = file_classes.map do |file_class|
file_class.new(location, context)
end.select(&:matching?)
@ -31,10 +22,10 @@ module Gitlab
elsif matching.empty?
raise Mapper::AmbigiousSpecificationError,
"`#{masked_location(location.to_json)}` does not have a valid subkey for include. " \
"Valid subkeys are: `#{FILE_SUBKEYS.join('`, `')}`"
"Valid subkeys are: `#{file_subkeys.join('`, `')}`"
else
raise Mapper::AmbigiousSpecificationError,
"Each include must use only one of: `#{FILE_SUBKEYS.join('`, `')}`"
"Each include must use only one of: `#{file_subkeys.join('`, `')}`"
end
end
end
@ -42,6 +33,26 @@ module Gitlab
def masked_location(location)
context.mask_variables_from(location)
end
def file_subkeys
file_classes.map { |f| f.name.demodulize.downcase }.freeze
end
strong_memoize_attr :file_subkeys
def file_classes
classes = [
External::File::Local,
External::File::Project,
External::File::Remote,
External::File::Template,
External::File::Artifact
]
classes << External::File::Component if Feature.enabled?(:ci_include_components, context.project)
classes
end
strong_memoize_attr :file_classes
end
end
end

View File

@ -77,6 +77,33 @@ module Gitlab
async_index
end
def prepare_async_index_from_sql(table_name, index_name, definition)
Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode!
return unless async_index_creation_available?
if index_name_exists?(table_name, index_name)
Gitlab::AppLogger.warn(
message: 'Index not prepared because it already exists',
table_name: table_name,
index_name: index_name)
return
end
async_index = Gitlab::Database::AsyncIndexes::PostgresAsyncIndex.find_or_create_by!(name: index_name) do |rec|
rec.table_name = table_name
rec.definition = definition
end
Gitlab::AppLogger.info(
message: 'Prepared index for async creation',
table_name: async_index.table_name,
index_name: async_index.name)
async_index
end
# Prepares an index for asynchronous destruction.
#
# Stores the index information in the postgres_async_indexes table to be removed later. The

View File

@ -35224,6 +35224,9 @@ msgstr ""
msgid "ProtectedBranch|Learn more."
msgstr ""
msgid "ProtectedBranch|Manage branch related settings in one area with branch rules."
msgstr ""
msgid "ProtectedBranch|New Protected Tag"
msgstr ""
@ -35275,6 +35278,12 @@ msgstr ""
msgid "ProtectedBranch|Unprotect branch"
msgstr ""
msgid "ProtectedBranch|View branch rule"
msgstr ""
msgid "ProtectedBranch|View protected branches as branch rules"
msgstr ""
msgid "ProtectedBranch|What are protected branches?"
msgstr ""
@ -40139,6 +40148,9 @@ msgstr ""
msgid "Service Ping payload not found in the application cache"
msgstr ""
msgid "Service account"
msgstr ""
msgid "Service account generated successfully"
msgstr ""

View File

@ -197,6 +197,7 @@
"vue-virtual-scroll-list": "^1.4.7",
"vuedraggable": "^2.23.0",
"vuex": "^3.6.2",
"vuex-vue3": "npm:vuex@4.0.0",
"web-streams-polyfill": "^3.2.1",
"web-vitals": "^0.2.4",
"webpack": "^4.46.0",

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
FactoryBot.define do
factory :search_index, class: 'Search::Index' do
initialize_with { type.present? ? type.new : Search::Index.new }
sequence(:path) { |n| "index-path-#{n}" }
sequence(:bucket_number) { |n| n }
type { Search::NoteIndex }
end
end

View File

@ -96,6 +96,15 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
expect(ProtectedBranch.last.name).to eq('some->branch')
end
it "shows success alert once protected branch is created" do
visit project_protected_branches_path(project)
set_defaults
set_protected_branch_name('some->branch')
click_on "Protect"
wait_for_requests
expect(page).to have_content(s_('ProtectedBranch|View protected branches as branch rules'))
end
it "displays the last commit on the matching branch if it exists" do
commit = create(:commit, project: project)
project.repository.add_branch(admin, 'some-branch', commit.id)

View File

@ -1,3 +1,4 @@
import { GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import AbuseReportRow from '~/admin/abuse_reports/components/abuse_report_row.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
@ -8,6 +9,7 @@ describe('AbuseReportRow', () => {
let wrapper;
const mockAbuseReport = mockAbuseReports[0];
const findLinks = () => wrapper.findAllComponents(GlLink);
const findListItem = () => wrapper.findComponent(ListItem);
const findTitle = () => wrapper.findByTestId('title');
const findUpdatedAt = () => wrapper.findByTestId('updated-at');
@ -17,6 +19,7 @@ describe('AbuseReportRow', () => {
propsData: {
report: mockAbuseReport,
},
stubs: { GlSprintf },
});
};
@ -29,10 +32,18 @@ describe('AbuseReportRow', () => {
});
it('displays correctly formatted title', () => {
const { reporter, reportedUser, category } = mockAbuseReport;
const { reporter, reportedUser, category, reportedUserPath, reporterPath } = mockAbuseReport;
expect(findTitle().text()).toMatchInterpolatedText(
`${reportedUser.name} reported for ${category} by ${reporter.name}`,
);
const userLink = findLinks().at(0);
expect(userLink.text()).toEqual(reportedUser.name);
expect(userLink.attributes('href')).toEqual(reportedUserPath);
const reporterLink = findLinks().at(1);
expect(reporterLink.text()).toEqual(reporter.name);
expect(reporterLink.attributes('href')).toEqual(reporterPath);
});
it('displays correctly formatted updated at', () => {

View File

@ -4,11 +4,15 @@ export const mockAbuseReports = [
updatedAt: '2022-12-07T06:45:39.977Z',
reporter: { name: 'Ms. Admin' },
reportedUser: { name: 'Mr. Abuser' },
reportedUserPath: '/mr_abuser',
reporterPath: '/admin',
},
{
category: 'phishing',
updatedAt: '2022-12-07T06:45:39.977Z',
reporter: { name: 'Ms. Reporter' },
reportedUser: { name: 'Mr. Phisher' },
reportedUserPath: '/mr_phisher',
reporterPath: '/admin',
},
];

View File

@ -1,4 +1,4 @@
import { GlDropdown, GlLink, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
import { GlLink, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { shallowMount, mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
@ -145,16 +145,11 @@ describe('SidebarDropdownWidget', () => {
stubs: {
SidebarEditableItem,
GlSearchBoxByType,
GlDropdown,
},
}),
);
wrapper.vm.$refs.dropdown.show = jest.fn();
// We need to mock out `showDropdown` which
// invokes `show` method of BDropdown used inside GlDropdown.
jest.spyOn(wrapper.vm, 'showDropdown').mockImplementation();
};
describe('when not editing', () => {
@ -166,7 +161,6 @@ describe('SidebarDropdownWidget', () => {
},
},
stubs: {
GlDropdown,
SidebarEditableItem,
},
});

View File

@ -2,6 +2,7 @@ import { nextTick } from 'vue';
import { GlDropdown } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants';
import { stubComponent } from 'helpers/stub_component';
import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue';
import DropdownContentsColorView from '~/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue';
import DropdownHeader from '~/vue_shared/components/color_select_dropdown/dropdown_header.vue';
@ -19,12 +20,13 @@ const defaultProps = {
describe('DropdownContent', () => {
let wrapper;
const createComponent = ({ propsData = {} } = {}) => {
const createComponent = ({ propsData = {}, stubs = {} } = {}) => {
wrapper = mountExtended(DropdownContents, {
propsData: {
...defaultProps,
...propsData,
},
stubs,
});
};
@ -33,13 +35,22 @@ describe('DropdownContent', () => {
const findDropdown = () => wrapper.findComponent(GlDropdown);
it('calls dropdown `show` method on `isVisible` prop change', async () => {
createComponent();
const spy = jest.spyOn(wrapper.vm.$refs.dropdown, 'show');
const showDropdown = jest.fn();
const hideDropdown = jest.fn();
const dropdownStub = {
GlDropdown: stubComponent(GlDropdown, {
methods: {
show: showDropdown,
hide: hideDropdown,
},
}),
};
createComponent({ stubs: dropdownStub });
await wrapper.setProps({
isVisible: true,
});
expect(spy).toHaveBeenCalledTimes(1);
expect(showDropdown).toHaveBeenCalledTimes(1);
});
it('does not emit `setColor` event on dropdown hide if color did not change', () => {

View File

@ -32,6 +32,13 @@ describe('~/vue_shared/components/vuex_module_provider', () => {
expect(findProvidedVuexModule()).toBe(TEST_VUEX_MODULE);
});
it('provides "vuexModel" set from "vuex-module" prop when using @vue/compat', () => {
createComponent({
propsData: { 'vuex-module': TEST_VUEX_MODULE },
});
expect(findProvidedVuexModule()).toBe(TEST_VUEX_MODULE);
});
it('does not blow up when used with vue-apollo', () => {
// See https://github.com/vuejs/vue-apollo/pull/1153 for details
Vue.use(VueApollo);

View File

@ -16,28 +16,56 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Matcher, feature_category:
subject(:matcher) { described_class.new(context) }
describe '#process' do
let(:locations) do
[
{ local: 'file.yml' },
{ file: 'file.yml', project: 'namespace/project' },
{ component: 'gitlab.com/org/component@1.0' },
{ remote: 'https://example.com/.gitlab-ci.yml' },
{ template: 'file.yml' },
{ artifact: 'generated.yml', job: 'test' }
]
end
subject(:process) { matcher.process(locations) }
it 'returns an array of file objects' do
is_expected.to contain_exactly(
an_instance_of(Gitlab::Ci::Config::External::File::Local),
an_instance_of(Gitlab::Ci::Config::External::File::Project),
an_instance_of(Gitlab::Ci::Config::External::File::Component),
an_instance_of(Gitlab::Ci::Config::External::File::Remote),
an_instance_of(Gitlab::Ci::Config::External::File::Template),
an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
)
context 'with ci_include_components FF disabled' do
before do
stub_feature_flags(ci_include_components: false)
end
let(:locations) do
[
{ local: 'file.yml' },
{ file: 'file.yml', project: 'namespace/project' },
{ remote: 'https://example.com/.gitlab-ci.yml' },
{ template: 'file.yml' },
{ artifact: 'generated.yml', job: 'test' }
]
end
it 'returns an array of file objects' do
is_expected.to contain_exactly(
an_instance_of(Gitlab::Ci::Config::External::File::Local),
an_instance_of(Gitlab::Ci::Config::External::File::Project),
an_instance_of(Gitlab::Ci::Config::External::File::Remote),
an_instance_of(Gitlab::Ci::Config::External::File::Template),
an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
)
end
end
context 'with ci_include_components FF enabled' do
let(:locations) do
[
{ local: 'file.yml' },
{ file: 'file.yml', project: 'namespace/project' },
{ component: 'gitlab.com/org/component@1.0' },
{ remote: 'https://example.com/.gitlab-ci.yml' },
{ template: 'file.yml' },
{ artifact: 'generated.yml', job: 'test' }
]
end
it 'returns an array of file objects' do
is_expected.to contain_exactly(
an_instance_of(Gitlab::Ci::Config::External::File::Local),
an_instance_of(Gitlab::Ci::Config::External::File::Project),
an_instance_of(Gitlab::Ci::Config::External::File::Component),
an_instance_of(Gitlab::Ci::Config::External::File::Remote),
an_instance_of(Gitlab::Ci::Config::External::File::Template),
an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
)
end
end
context 'when a location is not valid' do

View File

@ -143,6 +143,62 @@ RSpec.describe Gitlab::Database::AsyncIndexes::MigrationHelpers, feature_categor
end
end
describe '#prepare_async_index_from_sql' do
let(:index_definition) { "CREATE INDEX #{index_name} ON #{table_name} USING btree(id)" }
subject(:prepare_async_index_from_sql) do
migration.prepare_async_index_from_sql(table_name, index_name, index_definition)
end
before do
connection.create_table(table_name)
allow(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_ddl_mode!).and_call_original
end
it 'requires ddl mode' do
prepare_async_index_from_sql
expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to have_received(:require_ddl_mode!)
end
context 'when the async index creation is not available' do
before do
connection.drop_table(:postgres_async_indexes)
end
it 'does not raise an error' do
expect { prepare_async_index_from_sql }.not_to raise_error
end
end
context 'when the async index creation is available' do
context 'when there is already an index with the given name' do
before do
connection.add_index(table_name, 'id', name: index_name)
end
it 'does not create the async index record' do
expect { prepare_async_index_from_sql }.not_to change { index_model.where(name: index_name).count }
end
end
context 'when there is no index with the given name' do
let(:async_index) { index_model.find_by(name: index_name) }
it 'creates the async index record' do
expect { prepare_async_index_from_sql }.to change { index_model.where(name: index_name).count }.by(1)
end
it 'sets the async index attributes correctly' do
prepare_async_index_from_sql
expect(async_index).to have_attributes(table_name: table_name, definition: index_definition)
end
end
end
end
describe '#prepare_async_index_removal' do
before do
connection.create_table(table_name)

View File

@ -12,12 +12,16 @@ RSpec.describe FinaliseProjectNamespaceMembers, :migration, feature_category: :s
shared_examples 'finalizes the migration' do
it 'finalizes the migration' do
allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |runner|
expect(runner).to receive(:finalize).with('BackfillProjectMemberNamespaceId', :members, :id, [])
expect(runner).to receive(:finalize).with(migration, :members, :id, [])
end
end
end
context 'when migration is missing' do
before do
batched_migrations.where(job_class_name: migration).delete_all
end
it 'warns migration not found' do
expect(Gitlab::AppLogger)
.to receive(:warn).with(/Could not find batched background migration for the given configuration:/)
@ -29,7 +33,7 @@ RSpec.describe FinaliseProjectNamespaceMembers, :migration, feature_category: :s
context 'with migration present' do
let!(:project_member_namespace_id_backfill) do
batched_migrations.create!(
job_class_name: 'BackfillProjectMemberNamespaceId',
job_class_name: migration,
table_name: :members,
column_name: :id,
job_arguments: [],

View File

@ -12,12 +12,16 @@ RSpec.describe FinalizeGroupMemberNamespaceIdMigration, :migration, feature_cate
shared_examples 'finalizes the migration' do
it 'finalizes the migration' do
allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |runner|
expect(runner).to receive(:finalize).with('BackfillMemberNamespaceForGroupMembers', :members, :id, [])
expect(runner).to receive(:finalize).with(migration, :members, :id, [])
end
end
end
context 'when migration is missing' do
before do
batched_migrations.where(job_class_name: migration).delete_all
end
it 'warns migration not found' do
expect(Gitlab::AppLogger)
.to receive(:warn).with(/Could not find batched background migration for the given configuration:/)
@ -29,7 +33,7 @@ RSpec.describe FinalizeGroupMemberNamespaceIdMigration, :migration, feature_cate
context 'with migration present' do
let!(:group_member_namespace_id_backfill) do
batched_migrations.create!(
job_class_name: 'BackfillMemberNamespaceForGroupMembers',
job_class_name: migration,
table_name: :members,
column_name: :id,
job_arguments: [],

View File

@ -25,6 +25,10 @@ RSpec.describe EnsureTaskNoteRenamingBackgroundMigrationFinished, :migration, fe
end
context 'when migration is missing' do
before do
batched_migrations.where(job_class_name: migration).delete_all
end
it 'warns migration not found' do
expect(Gitlab::AppLogger)
.to receive(:warn).with(/Could not find batched background migration for the given configuration:/)
@ -36,7 +40,7 @@ RSpec.describe EnsureTaskNoteRenamingBackgroundMigrationFinished, :migration, fe
context 'with migration present' do
let!(:task_renaming_migration) do
batched_migrations.create!(
job_class_name: 'RenameTaskSystemNoteToChecklistItem',
job_class_name: migration,
table_name: :system_note_metadata,
column_name: :id,
job_arguments: [],

View File

@ -26,6 +26,10 @@ RSpec.describe FinalizeBackfillUserDetailsFields, :migration, feature_category:
end
context 'when migration is missing' do
before do
batched_migrations.where(job_class_name: migration).delete_all
end
it 'warns migration not found' do
expect(Gitlab::AppLogger)
.to receive(:warn).with(/Could not find batched background migration for the given configuration:/)

View File

@ -13,6 +13,10 @@ RSpec.describe EnsureWorkItemTypeBackfillMigrationFinished, :migration, feature_
describe '#up', :redis do
context 'when migration is missing' do
before do
batched_migrations.where(job_class_name: migration_class).delete_all
end
it 'warns migration not found' do
expect(Gitlab::AppLogger)
.to receive(:warn).with(/Could not find batched background migration for the given configuration:/)

View File

@ -18,6 +18,10 @@ RSpec.describe FinalizeBackfillEnvironmentTierMigration, :migration, feature_cat
end
context 'when migration is missing' do
before do
batched_migrations.where(job_class_name: migration).delete_all
end
it 'warns migration not found' do
expect(Gitlab::AppLogger)
.to receive(:warn).with(/Could not find batched background migration for the given configuration:/)
@ -29,7 +33,7 @@ RSpec.describe FinalizeBackfillEnvironmentTierMigration, :migration, feature_cat
context 'with migration present' do
let!(:group_member_namespace_id_backfill) do
batched_migrations.create!(
job_class_name: 'BackfillEnvironmentTiers',
job_class_name: migration,
table_name: :environments,
column_name: :id,
job_arguments: [],

View File

@ -26,6 +26,10 @@ RSpec.describe FinalizeNullifyCreatorIdOfOrphanedProjects, :migration, feature_c
end
context 'when migration is missing' do
before do
batched_migrations.where(job_class_name: migration).delete_all
end
it 'warns migration not found' do
expect(Gitlab::AppLogger)
.to receive(:warn).with(/Could not find batched background migration for the given configuration:/)
@ -37,7 +41,7 @@ RSpec.describe FinalizeNullifyCreatorIdOfOrphanedProjects, :migration, feature_c
context 'with migration present' do
let!(:migration_record) do
batched_migrations.create!(
job_class_name: 'NullifyCreatorIdColumnOfOrphanedProjects',
job_class_name: migration,
table_name: :projects,
column_name: :id,
job_arguments: [],

View File

@ -18,6 +18,10 @@ RSpec.describe FinalizeInvalidMemberCleanup, :migration, feature_category: :subg
end
context 'when migration is missing' do
before do
batched_migrations.where(job_class_name: migration).delete_all
end
it 'warns migration not found' do
expect(Gitlab::AppLogger)
.to receive(:warn).with(/Could not find batched background migration for the given configuration:/)

View File

@ -12,12 +12,16 @@ RSpec.describe FinalizeIssuesNamespaceIdBackfilling, :migration, feature_categor
shared_examples 'finalizes the migration' do
it 'finalizes the migration' do
allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |runner|
expect(runner).to receive(:finalize).with('BackfillProjectNamespaceOnIssues', :projects, :id, [])
expect(runner).to receive(:finalize).with(migration, :projects, :id, [])
end
end
end
context 'when routes backfilling migration is missing' do
before do
batched_migrations.where(job_class_name: migration).delete_all
end
it 'warns migration not found' do
expect(Gitlab::AppLogger)
.to receive(:warn).with(/Could not find batched background migration for the given configuration:/)
@ -29,7 +33,7 @@ RSpec.describe FinalizeIssuesNamespaceIdBackfilling, :migration, feature_categor
context 'with backfilling migration present' do
let!(:project_namespace_backfill) do
batched_migrations.create!(
job_class_name: 'BackfillProjectNamespaceOnIssues',
job_class_name: migration,
table_name: :routes,
column_name: :id,
job_arguments: [],

Some files were not shown because too many files have changed in this diff Show More