Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1f8c5a116b
commit
e10ea43772
|
|
@ -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]
|
||||
|
||||
# ------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
# ------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
Search/NamespacedClass:
|
||||
Details: grace period
|
||||
Exclude:
|
||||
- 'app/controllers/concerns/search_rate_limitable.rb'
|
||||
- 'app/controllers/search_controller.rb'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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(() =>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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__(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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' && {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.page-title-holder {
|
||||
.page-title {
|
||||
margin: $gl-padding 0;
|
||||
margin: $gl-spacing-scale-4 0;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -561,6 +561,8 @@
|
|||
- 1
|
||||
- - wikis_git_garbage_collect
|
||||
- 1
|
||||
- - work_items_update_parent_objectives_progress
|
||||
- 1
|
||||
- - x509_certificate_revoke
|
||||
- 1
|
||||
- - zoekt_indexer
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
7c9f554950c0b2b2adc6d31d6cc42335dfd00965c61b2b24489e0099ad227a5c
|
||||
|
|
@ -0,0 +1 @@
|
|||
c3098250c8ade855d84fec852dac81bab891e6e844404814ddff99711136d9eb
|
||||
|
|
@ -0,0 +1 @@
|
|||
6dde6a29aefd3811f9c5bd144b24f33046e1762e13f18ad069d6d53a2448df49
|
||||
|
|
@ -0,0 +1 @@
|
|||
b45db7a4404bbab731138f5db6031241945969a210f1c3b6fce323938ec8980d
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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': {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
|
|
@ -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:/)
|
||||
|
|
|
|||
|
|
@ -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:/)
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
|
|
@ -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:/)
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue