Commit 7439b168 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch '10io-fix-application-worker-structured-payload' into 'master'

Fix the application worker structured payload

See merge request gitlab-org/gitlab!46903
parents e1d3ecb8 bd71bb53
020b5f709d58277c360ba409b8f8a9e81cee2781 fa974a4ab21aa6acc4c3a00456265248a4d70703
...@@ -168,9 +168,6 @@ export default class CreateMergeRequestDropdown { ...@@ -168,9 +168,6 @@ export default class CreateMergeRequestDropdown {
disable() { disable() {
this.disableCreateAction(); this.disableCreateAction();
this.dropdownToggle.classList.add('disabled');
this.dropdownToggle.setAttribute('disabled', 'disabled');
} }
disableCreateAction() { disableCreateAction() {
...@@ -189,9 +186,6 @@ export default class CreateMergeRequestDropdown { ...@@ -189,9 +186,6 @@ export default class CreateMergeRequestDropdown {
this.createTargetButton.classList.remove('disabled'); this.createTargetButton.classList.remove('disabled');
this.createTargetButton.removeAttribute('disabled'); this.createTargetButton.removeAttribute('disabled');
this.dropdownToggle.classList.remove('disabled');
this.dropdownToggle.removeAttribute('disabled');
} }
static findByValue(objects, ref, returnFirstMatch = false) { static findByValue(objects, ref, returnFirstMatch = false) {
......
...@@ -8,9 +8,9 @@ import { ...@@ -8,9 +8,9 @@ import {
GlBadge, GlBadge,
GlAlert, GlAlert,
GlSprintf, GlSprintf,
GlDeprecatedDropdown, GlDropdown,
GlDeprecatedDropdownItem, GlDropdownItem,
GlDeprecatedDropdownDivider, GlDropdownDivider,
GlIcon, GlIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
...@@ -43,9 +43,9 @@ export default { ...@@ -43,9 +43,9 @@ export default {
GlBadge, GlBadge,
GlAlert, GlAlert,
GlSprintf, GlSprintf,
GlDeprecatedDropdown, GlDropdown,
GlDeprecatedDropdownItem, GlDropdownItem,
GlDeprecatedDropdownDivider, GlDropdownDivider,
TimeAgoTooltip, TimeAgoTooltip,
}, },
directives: { directives: {
...@@ -331,38 +331,38 @@ export default { ...@@ -331,38 +331,38 @@ export default {
</gl-button> </gl-button>
</form> </form>
</div> </div>
<gl-deprecated-dropdown <gl-dropdown
text="Options" text="Options"
class="error-details-options d-md-none" class="error-details-options d-md-none"
right right
:disabled="issueUpdateInProgress" :disabled="issueUpdateInProgress"
> >
<gl-deprecated-dropdown-item <gl-dropdown-item
data-qa-selector="update_ignore_status_button" data-qa-selector="update_ignore_status_button"
@click="onIgnoreStatusUpdate" @click="onIgnoreStatusUpdate"
>{{ ignoreBtnLabel }}</gl-deprecated-dropdown-item >{{ ignoreBtnLabel }}</gl-dropdown-item
> >
<gl-deprecated-dropdown-item <gl-dropdown-item
data-qa-selector="update_resolve_status_button" data-qa-selector="update_resolve_status_button"
@click="onResolveStatusUpdate" @click="onResolveStatusUpdate"
>{{ resolveBtnLabel }}</gl-deprecated-dropdown-item >{{ resolveBtnLabel }}</gl-dropdown-item
> >
<gl-deprecated-dropdown-divider /> <gl-dropdown-divider />
<gl-deprecated-dropdown-item <gl-dropdown-item
v-if="error.gitlabIssuePath" v-if="error.gitlabIssuePath"
data-qa-selector="view_issue_button" data-qa-selector="view_issue_button"
:href="error.gitlabIssuePath" :href="error.gitlabIssuePath"
variant="success" variant="success"
>{{ __('View issue') }}</gl-deprecated-dropdown-item >{{ __('View issue') }}</gl-dropdown-item
> >
<gl-deprecated-dropdown-item <gl-dropdown-item
v-if="!error.gitlabIssuePath" v-if="!error.gitlabIssuePath"
:loading="issueCreationInProgress" :loading="issueCreationInProgress"
data-qa-selector="create_issue_button" data-qa-selector="create_issue_button"
@click="createIssue" @click="createIssue"
>{{ __('Create issue') }}</gl-deprecated-dropdown-item >{{ __('Create issue') }}</gl-dropdown-item
> >
</gl-deprecated-dropdown> </gl-dropdown>
</div> </div>
</div> </div>
<div> <div>
......
...@@ -8,9 +8,9 @@ import { ...@@ -8,9 +8,9 @@ import {
GlLoadingIcon, GlLoadingIcon,
GlTable, GlTable,
GlFormInput, GlFormInput,
GlDeprecatedDropdown, GlDropdown,
GlDeprecatedDropdownItem, GlDropdownItem,
GlDeprecatedDropdownDivider, GlDropdownDivider,
GlTooltipDirective, GlTooltipDirective,
GlPagination, GlPagination,
} from '@gitlab/ui'; } from '@gitlab/ui';
...@@ -72,9 +72,9 @@ export default { ...@@ -72,9 +72,9 @@ export default {
components: { components: {
GlEmptyState, GlEmptyState,
GlButton, GlButton,
GlDeprecatedDropdown, GlDropdown,
GlDeprecatedDropdownItem, GlDropdownItem,
GlDeprecatedDropdownDivider, GlDropdownDivider,
GlIcon, GlIcon,
GlLink, GlLink,
GlLoadingIcon, GlLoadingIcon,
...@@ -233,30 +233,30 @@ export default { ...@@ -233,30 +233,30 @@ export default {
> >
<div class="search-box flex-fill mb-1 mb-md-0"> <div class="search-box flex-fill mb-1 mb-md-0">
<div class="filtered-search-box mb-0"> <div class="filtered-search-box mb-0">
<gl-deprecated-dropdown <gl-dropdown
:text="__('Recent searches')" :text="__('Recent searches')"
class="filtered-search-history-dropdown-wrapper" class="filtered-search-history-dropdown-wrapper"
toggle-class="filtered-search-history-dropdown-toggle-button" toggle-class="filtered-search-history-dropdown-toggle-button gl-shadow-none! gl-border-r-gray-200! gl-border-1! gl-rounded-0!"
:disabled="loading" :disabled="loading"
> >
<div v-if="!$options.hasLocalStorage" class="px-3"> <div v-if="!$options.hasLocalStorage" class="px-3">
{{ __('This feature requires local storage to be enabled') }} {{ __('This feature requires local storage to be enabled') }}
</div> </div>
<template v-else-if="recentSearches.length > 0"> <template v-else-if="recentSearches.length > 0">
<gl-deprecated-dropdown-item <gl-dropdown-item
v-for="searchQuery in recentSearches" v-for="searchQuery in recentSearches"
:key="searchQuery" :key="searchQuery"
@click="setSearchText(searchQuery)" @click="setSearchText(searchQuery)"
>{{ searchQuery }} >{{ searchQuery }}
</gl-deprecated-dropdown-item> </gl-dropdown-item>
<gl-deprecated-dropdown-divider /> <gl-dropdown-divider />
<gl-deprecated-dropdown-item ref="clearRecentSearches" @click="clearRecentSearches" <gl-dropdown-item ref="clearRecentSearches" @click="clearRecentSearches"
>{{ __('Clear recent searches') }} >{{ __('Clear recent searches') }}
</gl-deprecated-dropdown-item> </gl-dropdown-item>
</template> </template>
<div v-else class="px-3">{{ __("You don't have any recent searches") }}</div> <div v-else class="px-3">{{ __("You don't have any recent searches") }}</div>
</gl-deprecated-dropdown> </gl-dropdown>
<div class="filtered-search-input-container flex-fill"> <div class="filtered-search-input-container gl-flex-fill-1">
<gl-form-input <gl-form-input
v-model="errorSearchQuery" v-model="errorSearchQuery"
class="pl-2 filtered-search" class="pl-2 filtered-search"
...@@ -280,49 +280,44 @@ export default { ...@@ -280,49 +280,44 @@ export default {
</div> </div>
</div> </div>
<gl-deprecated-dropdown <gl-dropdown
:text="$options.statusFilters[statusFilter]" :text="$options.statusFilters[statusFilter]"
class="status-dropdown mx-md-1 mb-1 mb-md-0" class="status-dropdown mx-md-1 mb-1 mb-md-0"
menu-class="dropdown"
:disabled="loading" :disabled="loading"
right
> >
<gl-deprecated-dropdown-item <gl-dropdown-item
v-for="(label, status) in $options.statusFilters" v-for="(label, status) in $options.statusFilters"
:key="status" :key="status"
@click="filterErrors(status, label)" @click="filterErrors(status, label)"
> >
<span class="d-flex"> <span class="d-flex">
<gl-icon <gl-icon
class="flex-shrink-0 append-right-4" class="gl-new-dropdown-item-check-icon"
:class="{ invisible: !isCurrentStatusFilter(status) }" :class="{ invisible: !isCurrentStatusFilter(status) }"
name="mobile-issue-close" name="mobile-issue-close"
/> />
{{ label }} {{ label }}
</span> </span>
</gl-deprecated-dropdown-item> </gl-dropdown-item>
</gl-deprecated-dropdown> </gl-dropdown>
<gl-deprecated-dropdown <gl-dropdown :text="$options.sortFields[sortField]" right :disabled="loading">
:text="$options.sortFields[sortField]" <gl-dropdown-item
left
:disabled="loading"
menu-class="dropdown"
>
<gl-deprecated-dropdown-item
v-for="(label, field) in $options.sortFields" v-for="(label, field) in $options.sortFields"
:key="field" :key="field"
@click="sortByField(field)" @click="sortByField(field)"
> >
<span class="d-flex"> <span class="d-flex">
<gl-icon <gl-icon
class="flex-shrink-0 append-right-4" class="gl-new-dropdown-item-check-icon"
:class="{ invisible: !isCurrentSortField(field) }" :class="{ invisible: !isCurrentSortField(field) }"
name="mobile-issue-close" name="mobile-issue-close"
/> />
{{ label }} {{ label }}
</span> </span>
</gl-deprecated-dropdown-item> </gl-dropdown-item>
</gl-deprecated-dropdown> </gl-dropdown>
</div> </div>
<div v-if="loading" class="py-3"> <div v-if="loading" class="py-3">
......
<script> <script>
import { GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { getDisplayName } from '../utils'; import { getDisplayName } from '../utils';
export default { export default {
components: { components: {
GlDeprecatedDropdown, GlDropdown,
GlDeprecatedDropdownItem, GlDropdownItem,
}, },
props: { props: {
dropdownLabel: { dropdownLabel: {
...@@ -52,22 +52,22 @@ export default { ...@@ -52,22 +52,22 @@ export default {
<div :class="{ 'gl-show-field-errors': isProjectInvalid }"> <div :class="{ 'gl-show-field-errors': isProjectInvalid }">
<label class="label-bold" for="project-dropdown">{{ __('Project') }}</label> <label class="label-bold" for="project-dropdown">{{ __('Project') }}</label>
<div class="row"> <div class="row">
<gl-deprecated-dropdown <gl-dropdown
id="project-dropdown" id="project-dropdown"
class="col-8 col-md-9 gl-pr-0" class="col-8 col-md-9 gl-pr-0"
:disabled="!hasProjects" :disabled="!hasProjects"
menu-class="w-100 mw-100" menu-class="w-100 mw-100"
toggle-class="dropdown-menu-toggle w-100 gl-field-error-outline" toggle-class="dropdown-menu-toggle gl-field-error-outline"
:text="dropdownLabel" :text="dropdownLabel"
> >
<gl-deprecated-dropdown-item <gl-dropdown-item
v-for="project in projects" v-for="project in projects"
:key="`${project.organizationSlug}.${project.slug}`" :key="`${project.organizationSlug}.${project.slug}`"
class="w-100" class="w-100"
@click="$emit('select-project', project)" @click="$emit('select-project', project)"
>{{ getDisplayName(project) }}</gl-deprecated-dropdown-item >{{ getDisplayName(project) }}</gl-dropdown-item
> >
</gl-deprecated-dropdown> </gl-dropdown>
</div> </div>
<p v-if="isProjectInvalid" class="js-project-dropdown-error gl-field-error"> <p v-if="isProjectInvalid" class="js-project-dropdown-error gl-field-error">
{{ invalidProjectLabel }} {{ invalidProjectLabel }}
......
...@@ -37,8 +37,6 @@ const restartJobsPolling = () => { ...@@ -37,8 +37,6 @@ const restartJobsPolling = () => {
if (eTagPoll) eTagPoll.restart(); if (eTagPoll) eTagPoll.restart();
}; };
const setFilter = ({ commit }, filter) => commit(types.SET_FILTER, filter);
const setImportTarget = ({ commit }, { repoId, importTarget }) => const setImportTarget = ({ commit }, { repoId, importTarget }) =>
commit(types.SET_IMPORT_TARGET, { repoId, importTarget }); commit(types.SET_IMPORT_TARGET, { repoId, importTarget });
...@@ -172,12 +170,9 @@ const fetchNamespacesFactory = (namespacesPath = isRequired()) => ({ commit }) = ...@@ -172,12 +170,9 @@ const fetchNamespacesFactory = (namespacesPath = isRequired()) => ({ commit }) =
}); });
}; };
const setPage = ({ state, commit, dispatch }, page) => { const setFilter = ({ commit, dispatch }, filter) => {
if (page === state.pageInfo.page) { commit(types.SET_FILTER, filter);
return null;
}
commit(types.SET_PAGE, page);
return dispatch('fetchRepos'); return dispatch('fetchRepos');
}; };
...@@ -188,7 +183,6 @@ export default ({ endpoints = isRequired() }) => ({ ...@@ -188,7 +183,6 @@ export default ({ endpoints = isRequired() }) => ({
setFilter, setFilter,
setImportTarget, setImportTarget,
importAll, importAll,
setPage,
fetchRepos: fetchReposFactory({ reposPath: endpoints.reposPath }), fetchRepos: fetchReposFactory({ reposPath: endpoints.reposPath }),
fetchImport: fetchImportFactory(endpoints.importPath), fetchImport: fetchImportFactory(endpoints.importPath),
fetchJobs: fetchJobsFactory(endpoints.jobsPath), fetchJobs: fetchJobsFactory(endpoints.jobsPath),
......
...@@ -81,7 +81,7 @@ export default { ...@@ -81,7 +81,7 @@ export default {
}) })
.then(({ data }) => { .then(({ data }) => {
if (data.updateIssue.errors.length) { if (data.updateIssue.errors.length) {
createFlash(data.updateIssue.errors.join('. ')); createFlash({ message: data.updateIssue.errors.join('. ') });
return; return;
} }
...@@ -95,7 +95,7 @@ export default { ...@@ -95,7 +95,7 @@ export default {
// Dispatch event which updates open/close state, shared among the issue show page // Dispatch event which updates open/close state, shared among the issue show page
document.dispatchEvent(new CustomEvent('issuable_vue_app:change', payload)); document.dispatchEvent(new CustomEvent('issuable_vue_app:change', payload));
}) })
.catch(() => createFlash(__('Update failed. Please try again.'))) .catch(() => createFlash({ message: __('Update failed. Please try again.') }))
.finally(() => { .finally(() => {
this.isUpdatingState = false; this.isUpdatingState = false;
}); });
......
...@@ -21,35 +21,32 @@ function mountRemoveMemberModal() { ...@@ -21,35 +21,32 @@ function mountRemoveMemberModal() {
}); });
} }
document.addEventListener('DOMContentLoaded', () => { const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
groupsSelect(); initGroupMembersApp(
memberExpirationDate();
memberExpirationDate('.js-access-expiration-date-groups');
mountRemoveMemberModal();
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
initGroupMembersApp(
document.querySelector('.js-group-members-list'), document.querySelector('.js-group-members-list'),
SHARED_FIELDS.concat(['source', 'granted']), SHARED_FIELDS.concat(['source', 'granted']),
memberRequestFormatter, memberRequestFormatter,
); );
initGroupMembersApp( initGroupMembersApp(
document.querySelector('.js-group-linked-list'), document.querySelector('.js-group-linked-list'),
SHARED_FIELDS.concat('granted'), SHARED_FIELDS.concat('granted'),
groupLinkRequestFormatter, groupLinkRequestFormatter,
); );
initGroupMembersApp( initGroupMembersApp(
document.querySelector('.js-group-invited-members-list'), document.querySelector('.js-group-invited-members-list'),
SHARED_FIELDS.concat('invited'), SHARED_FIELDS.concat('invited'),
memberRequestFormatter, memberRequestFormatter,
); );
initGroupMembersApp( initGroupMembersApp(
document.querySelector('.js-group-access-requests-list'), document.querySelector('.js-group-access-requests-list'),
SHARED_FIELDS.concat('requested'), SHARED_FIELDS.concat('requested'),
memberRequestFormatter, memberRequestFormatter,
); );
groupsSelect();
memberExpirationDate();
memberExpirationDate('.js-access-expiration-date-groups');
mountRemoveMemberModal();
new Members(); // eslint-disable-line no-new new Members(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new new UsersSelect(); // eslint-disable-line no-new
});
...@@ -7,11 +7,11 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -7,11 +7,11 @@ document.addEventListener('DOMContentLoaded', () => {
if (gon?.features?.ciLintVue) { if (gon?.features?.ciLintVue) {
import(/* webpackChunkName: 'ciLintIndex' */ '~/ci_lint/index') import(/* webpackChunkName: 'ciLintIndex' */ '~/ci_lint/index')
.then(module => module.default()) .then(module => module.default())
.catch(() => createFlash(ERROR)); .catch(() => createFlash({ message: ERROR }));
} else { } else {
import(/* webpackChunkName: 'ciLintEditor' */ '../ci_lint_editor') import(/* webpackChunkName: 'ciLintEditor' */ '../ci_lint_editor')
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
.then(module => new module.default()) .then(module => new module.default())
.catch(() => createFlash(ERROR)); .catch(() => createFlash({ message: ERROR }));
} }
}); });
...@@ -7,11 +7,11 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -7,11 +7,11 @@ document.addEventListener('DOMContentLoaded', () => {
if (gon?.features?.ciLintVue) { if (gon?.features?.ciLintVue) {
import(/* webpackChunkName: 'ciLintIndex' */ '~/ci_lint/index') import(/* webpackChunkName: 'ciLintIndex' */ '~/ci_lint/index')
.then(module => module.default()) .then(module => module.default())
.catch(() => createFlash(ERROR)); .catch(() => createFlash({ message: ERROR }));
} else { } else {
import(/* webpackChunkName: 'ciLintEditor' */ '../ci_lint_editor') import(/* webpackChunkName: 'ciLintEditor' */ '../ci_lint_editor')
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
.then(module => new module.default()) .then(module => new module.default())
.catch(() => createFlash(ERROR)); .catch(() => createFlash({ message: ERROR }));
} }
}); });
...@@ -40,7 +40,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -40,7 +40,7 @@ document.addEventListener('DOMContentLoaded', () => {
new Diff(); new Diff();
}) })
.catch(() => { .catch(() => {
flash(__('An error occurred while retrieving diff files')); flash({ message: __('An error occurred while retrieving diff files') });
}); });
} else { } else {
new Diff(); new Diff();
......
...@@ -57,7 +57,7 @@ export default { ...@@ -57,7 +57,7 @@ export default {
<tooltip-on-truncate :title="jobName" truncate-target="child" placement="top"> <tooltip-on-truncate :title="jobName" truncate-target="child" placement="top">
<div <div
:id="jobId" :id="jobId"
class="pipeline-job-pill gl-bg-white gl-text-center gl-text-truncate gl-rounded-pill gl-mb-3 gl-px-5 gl-py-2 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease" class="gl-w-15 gl-bg-white gl-text-center gl-text-truncate gl-rounded-pill gl-mb-3 gl-px-5 gl-py-2 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease"
:class="jobPillClasses" :class="jobPillClasses"
@mouseover="onMouseEnter" @mouseover="onMouseEnter"
@mouseleave="onMouseLeave" @mouseleave="onMouseLeave"
......
...@@ -97,15 +97,20 @@ export default { ...@@ -97,15 +97,20 @@ export default {
this.reportFailure(DRAW_FAILURE); this.reportFailure(DRAW_FAILURE);
} }
}, },
getStageBackgroundClass(index) { getStageBackgroundClasses(index) {
const { length } = this.pipelineData.stages; const { length } = this.pipelineData.stages;
// It's possible for a graph to have only one stage, in which
// case we concatenate both the left and right rounding classes
if (length === 1) { if (length === 1) {
return 'stage-rounded'; return 'gl-rounded-bottom-left-6 gl-rounded-top-left-6 gl-rounded-bottom-right-6 gl-rounded-top-right-6';
} else if (index === 0) { }
return 'stage-left-rounded';
} else if (index === length - 1) { if (index === 0) {
return 'stage-right-rounded'; return 'gl-rounded-bottom-left-6 gl-rounded-top-left-6';
}
if (index === length - 1) {
return 'gl-rounded-bottom-right-6 gl-rounded-top-right-6';
} }
return ''; return '';
...@@ -190,7 +195,8 @@ export default { ...@@ -190,7 +195,8 @@ export default {
> >
<div <div
class="gl-display-flex gl-align-items-center gl-bg-white gl-w-full gl-px-8 gl-py-4 gl-mb-5" class="gl-display-flex gl-align-items-center gl-bg-white gl-w-full gl-px-8 gl-py-4 gl-mb-5"
:class="getStageBackgroundClass(index)" :class="getStageBackgroundClasses(index)"
data-testid="stage-background"
> >
<stage-pill :stage-name="stage.name" :is-empty="stage.groups.length === 0" /> <stage-pill :stage-name="stage.name" :is-empty="stage.groups.length === 0" />
</div> </div>
......
...@@ -26,7 +26,7 @@ export default { ...@@ -26,7 +26,7 @@ export default {
<template> <template>
<tooltip-on-truncate :title="stageName" truncate-target="child" placement="top"> <tooltip-on-truncate :title="stageName" truncate-target="child" placement="top">
<div <div
class="gl-px-5 gl-py-2 gl-text-white gl-text-center gl-text-truncate gl-rounded-pill pipeline-stage-pill" class="gl-px-5 gl-py-2 gl-text-white gl-text-center gl-text-truncate gl-rounded-pill gl-w-20"
:class="emptyClass" :class="emptyClass"
> >
{{ stageName }} {{ stageName }}
......
...@@ -26,7 +26,7 @@ export default { ...@@ -26,7 +26,7 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
openPath: { openedPath: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
...@@ -43,7 +43,7 @@ export default { ...@@ -43,7 +43,7 @@ export default {
}, },
}, },
computed: { computed: {
open() { opened() {
return this.total - (this.closed + (this.merged || 0)); return this.total - (this.closed + (this.merged || 0));
}, },
showMerged() { showMerged() {
...@@ -63,8 +63,8 @@ export default { ...@@ -63,8 +63,8 @@ export default {
<span class="gl-white-space-pre-wrap" data-testid="open-stat"> <span class="gl-white-space-pre-wrap" data-testid="open-stat">
<gl-sprintf :message="__('Open: %{open}')"> <gl-sprintf :message="__('Open: %{open}')">
<template #open> <template #open>
<gl-link v-if="openPath" :href="openPath">{{ open }}</gl-link> <gl-link v-if="openedPath" :href="openedPath">{{ opened }}</gl-link>
<template v-else>{{ open }}</template> <template v-else>{{ opened }}</template>
</template> </template>
</gl-sprintf> </gl-sprintf>
</span> </span>
......
...@@ -87,9 +87,14 @@ export default { ...@@ -87,9 +87,14 @@ export default {
<release-block-header :release="release" /> <release-block-header :release="release" />
<div class="card-body"> <div class="card-body">
<div v-if="shouldRenderMilestoneInfo"> <div v-if="shouldRenderMilestoneInfo">
<!-- TODO: Switch open* links to opened* once fields have been updated in GraphQL -->
<release-block-milestone-info <release-block-milestone-info
:milestones="milestones" :milestones="milestones"
:open-issues-path="release._links.issuesUrl" :opened-issues-path="release._links.openedIssuesUrl"
:closed-issues-path="release._links.closedIssuesUrl"
:opened-merge-requests-path="release._links.openedMergeRequestsUrl"
:merged-merge-requests-path="release._links.mergedMergeRequestsUrl"
:closed-merge-requests-path="release._links.closedMergeRequestsUrl"
/> />
<hr class="mb-3 mt-0" /> <hr class="mb-3 mt-0" />
</div> </div>
......
...@@ -20,7 +20,7 @@ export default { ...@@ -20,7 +20,7 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
openIssuesPath: { openedIssuesPath: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
...@@ -30,7 +30,7 @@ export default { ...@@ -30,7 +30,7 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
openMergeRequestsPath: { openedMergeRequestsPath: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
...@@ -173,7 +173,7 @@ export default { ...@@ -173,7 +173,7 @@ export default {
:label="__('Issues')" :label="__('Issues')"
:total="issueCounts.total" :total="issueCounts.total"
:closed="issueCounts.closed" :closed="issueCounts.closed"
:open-path="openIssuesPath" :opened-path="openedIssuesPath"
:closed-path="closedIssuesPath" :closed-path="closedIssuesPath"
data-testid="issue-stats" data-testid="issue-stats"
/> />
...@@ -183,7 +183,7 @@ export default { ...@@ -183,7 +183,7 @@ export default {
:total="mergeRequestCounts.total" :total="mergeRequestCounts.total"
:merged="mergeRequestCounts.merged" :merged="mergeRequestCounts.merged"
:closed="mergeRequestCounts.closed" :closed="mergeRequestCounts.closed"
:open-path="openMergeRequestsPath" :opened-path="openedMergeRequestsPath"
:merged-path="mergedMergeRequestsPath" :merged-path="mergedMergeRequestsPath"
:closed-path="closedMergeRequestsPath" :closed-path="closedMergeRequestsPath"
data-testid="merge-request-stats" data-testid="merge-request-stats"
......
...@@ -33,9 +33,12 @@ fragment Release on Release { ...@@ -33,9 +33,12 @@ fragment Release on Release {
} }
links { links {
editUrl editUrl
issuesUrl
mergeRequestsUrl
selfUrl selfUrl
openedIssuesUrl
closedIssuesUrl
openedMergeRequestsUrl
mergedMergeRequestsUrl
closedMergeRequestsUrl
} }
commit { commit {
sha sha
......
...@@ -137,8 +137,8 @@ export default { ...@@ -137,8 +137,8 @@ export default {
:href="commit.author.webPath" :href="commit.author.webPath"
class="commit-author-link js-user-link" class="commit-author-link js-user-link"
> >
{{ commit.author.name }} {{ commit.author.name }}</gl-link
</gl-link> >
<template v-else> <template v-else>
{{ commit.authorName }} {{ commit.authorName }}
</template> </template>
......
...@@ -65,7 +65,7 @@ export default { ...@@ -65,7 +65,7 @@ export default {
.then(({ data }) => { .then(({ data }) => {
this.milestones = data; this.milestones = data;
}) })
.catch(() => createFlash(__('There was a problem fetching milestones.'))) .catch(() => createFlash({ message: __('There was a problem fetching milestones.') }))
.finally(() => { .finally(() => {
this.loading = false; this.loading = false;
}); });
......
...@@ -486,23 +486,3 @@ ...@@ -486,23 +486,3 @@
.progress-bar.bg-primary { .progress-bar.bg-primary {
background-color: $blue-500 !important; background-color: $blue-500 !important;
} }
.pipeline-stage-pill {
width: 10rem;
}
.pipeline-job-pill {
width: 8rem;
}
.stage-rounded {
border-radius: 2rem;
}
.stage-left-rounded {
border-radius: 2rem 0 0 2rem;
}
.stage-right-rounded {
border-radius: 0 2rem 2rem 0;
}
...@@ -188,6 +188,12 @@ ul.related-merge-requests > li { ...@@ -188,6 +188,12 @@ ul.related-merge-requests > li {
border-width: 1px; border-width: 1px;
line-height: $line-height-base; line-height: $line-height-base;
width: auto; width: auto;
&.disabled {
background-color: $gray-light;
border-color: $gray-100;
color: $gl-text-color-disabled;
}
} }
} }
......
...@@ -132,13 +132,23 @@ class GroupsController < Groups::ApplicationController ...@@ -132,13 +132,23 @@ class GroupsController < Groups::ApplicationController
def update def update
if Groups::UpdateService.new(@group, current_user, group_params).execute if Groups::UpdateService.new(@group, current_user, group_params).execute
redirect_to edit_group_path(@group, anchor: params[:update_section]), notice: "Group '#{@group.name}' was successfully updated." notice = "Group '#{@group.name}' was successfully updated."
redirect_to edit_group_origin_location, notice: notice
else else
@group.reset @group.reset
render action: "edit" render action: "edit"
end end
end end
def edit_group_origin_location
if params.dig(:group, :redirect_target) == 'repository_settings'
group_settings_repository_path(@group, anchor: 'js-default-branch-name')
else
edit_group_path(@group, anchor: params[:update_section])
end
end
def destroy def destroy
Groups::DestroyService.new(@group, current_user).async_execute Groups::DestroyService.new(@group, current_user).async_execute
......
...@@ -48,18 +48,14 @@ class Import::BaseController < ApplicationController ...@@ -48,18 +48,14 @@ class Import::BaseController < ApplicationController
private private
def filter_attribute
:name
end
def sanitized_filter_param def sanitized_filter_param
@filter ||= sanitize(params[:filter]) @filter ||= sanitize(params[:filter])&.downcase
end end
def filtered(collection) def filtered(collection)
return collection unless sanitized_filter_param return collection unless sanitized_filter_param
collection.select { |item| item[filter_attribute].include?(sanitized_filter_param) } collection.select { |item| item[:name].to_s.downcase.include?(sanitized_filter_param) }
end end
def serialized_provider_repos def serialized_provider_repos
......
...@@ -132,8 +132,4 @@ class Import::BitbucketController < Import::BaseController ...@@ -132,8 +132,4 @@ class Import::BitbucketController < Import::BaseController
refresh_token: session[:bitbucket_refresh_token] refresh_token: session[:bitbucket_refresh_token]
} }
end end
def sanitized_filter_param
@filter ||= sanitize(params[:filter])
end
end end
...@@ -170,10 +170,6 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -170,10 +170,6 @@ class Import::BitbucketServerController < Import::BaseController
BitbucketServer::Paginator::PAGE_LENGTH BitbucketServer::Paginator::PAGE_LENGTH
end end
def sanitized_filter_param
sanitize(params[:filter])
end
def bitbucket_connection_error(error) def bitbucket_connection_error(error)
flash[:alert] = _("Unable to connect to server: %{error}") % { error: error } flash[:alert] = _("Unable to connect to server: %{error}") % { error: error }
clear_session_data clear_session_data
......
...@@ -245,14 +245,6 @@ class Import::GithubController < Import::BaseController ...@@ -245,14 +245,6 @@ class Import::GithubController < Import::BaseController
def extra_import_params def extra_import_params
{} {}
end end
def sanitized_filter_param
@filter ||= sanitize(params[:filter])
end
def filter_attribute
:name
end
end end
Import::GithubController.prepend_if_ee('EE::Import::GithubController') Import::GithubController.prepend_if_ee('EE::Import::GithubController')
...@@ -52,7 +52,7 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -52,7 +52,7 @@ class Projects::RunnersController < Projects::ApplicationController
end end
def toggle_shared_runners def toggle_shared_runners
if Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true) && !project.shared_runners_enabled && project.group && project.group.shared_runners_setting == 'disabled_and_unoverridable' if !project.shared_runners_enabled && project.group && project.group.shared_runners_setting == 'disabled_and_unoverridable'
return redirect_to project_runners_path(@project), alert: _("Cannot enable shared runners because parent group does not allow it") return redirect_to project_runners_path(@project), alert: _("Cannot enable shared runners because parent group does not allow it")
end end
......
# frozen_string_literal: true
module Types
class GroupInvitationType < BaseObject
expose_permissions Types::PermissionTypes::Group
authorize :read_group
implements InvitationInterface
graphql_name 'GroupInvitation'
description 'Represents a Group Invitation'
field :group, Types::GroupType, null: true,
description: 'Group that a User is invited to',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.source_id).find }
end
end
# frozen_string_literal: true
module Types
module InvitationInterface
include BaseInterface
field :email, GraphQL::STRING_TYPE, null: false,
description: 'Email of the member to invite'
field :access_level, Types::AccessLevelType, null: true,
description: 'GitLab::Access level'
field :created_by, Types::UserType, null: true,
description: 'User that authorized membership'
field :created_at, Types::TimeType, null: true,
description: 'Date and time the membership was created'
field :updated_at, Types::TimeType, null: true,
description: 'Date and time the membership was last updated'
field :expires_at, Types::TimeType, null: true,
description: 'Date and time the membership expires'
field :user, Types::UserType, null: true,
description: 'User that is associated with the member object'
definition_methods do
def resolve_type(object, context)
case object
when GroupMember
Types::GroupInvitationType
when ProjectMember
Types::ProjectInvitationType
else
raise ::Gitlab::Graphql::Errors::BaseError, "Unknown member type #{object.class.name}"
end
end
end
end
end
...@@ -62,6 +62,8 @@ module Types ...@@ -62,6 +62,8 @@ module Types
description: 'Number of downvotes the issue has received' description: 'Number of downvotes the issue has received'
field :user_notes_count, GraphQL::INT_TYPE, null: false, field :user_notes_count, GraphQL::INT_TYPE, null: false,
description: 'Number of user notes of the issue' description: 'Number of user notes of the issue'
field :user_discussions_count, GraphQL::INT_TYPE, null: false,
description: 'Number of user discussions in the issue'
field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path, field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path,
description: 'Web path of the issue' description: 'Web path of the issue'
field :web_url, GraphQL::STRING_TYPE, null: false, field :web_url, GraphQL::STRING_TYPE, null: false,
...@@ -113,6 +115,26 @@ module Types ...@@ -113,6 +115,26 @@ module Types
field :severity, Types::IssuableSeverityEnum, null: true, field :severity, Types::IssuableSeverityEnum, null: true,
description: 'Severity level of the incident' description: 'Severity level of the incident'
def user_notes_count
BatchLoader::GraphQL.for(object.id).batch(key: :issue_user_notes_count) do |ids, loader, args|
counts = Note.count_for_collection(ids, 'Issue').index_by(&:noteable_id)
ids.each do |id|
loader.call(id, counts[id]&.count || 0)
end
end
end
def user_discussions_count
BatchLoader::GraphQL.for(object.id).batch(key: :issue_user_discussions_count) do |ids, loader, args|
counts = Note.count_for_collection(ids, 'Issue', 'COUNT(DISTINCT discussion_id) as count').index_by(&:noteable_id)
ids.each do |id|
loader.call(id, counts[id]&.count || 0)
end
end
end
def author def author
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
end end
......
...@@ -68,6 +68,8 @@ module Types ...@@ -68,6 +68,8 @@ module Types
description: 'SHA of the merge request commit (set once merged)' description: 'SHA of the merge request commit (set once merged)'
field :user_notes_count, GraphQL::INT_TYPE, null: true, field :user_notes_count, GraphQL::INT_TYPE, null: true,
description: 'User notes count of the merge request' description: 'User notes count of the merge request'
field :user_discussions_count, GraphQL::INT_TYPE, null: true,
description: 'Number of user discussions in the merge request'
field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true, field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true,
description: 'Indicates if the source branch of the merge request will be deleted after merge' description: 'Indicates if the source branch of the merge request will be deleted after merge'
field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true, field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true,
...@@ -158,17 +160,25 @@ module Types ...@@ -158,17 +160,25 @@ module Types
object.approved_by_users object.approved_by_users
end end
# rubocop: disable CodeReuse/ActiveRecord
def user_notes_count def user_notes_count
BatchLoader::GraphQL.for(object.id).batch(key: :merge_request_user_notes_count) do |ids, loader, args| BatchLoader::GraphQL.for(object.id).batch(key: :merge_request_user_notes_count) do |ids, loader, args|
counts = Note.where(noteable_type: 'MergeRequest', noteable_id: ids).user.group(:noteable_id).count counts = Note.count_for_collection(ids, 'MergeRequest').index_by(&:noteable_id)
ids.each do |id| ids.each do |id|
loader.call(id, counts[id] || 0) loader.call(id, counts[id]&.count || 0)
end
end
end
def user_discussions_count
BatchLoader::GraphQL.for(object.id).batch(key: :merge_request_user_discussions_count) do |ids, loader, args|
counts = Note.count_for_collection(ids, 'MergeRequest', 'COUNT(DISTINCT discussion_id) as count').index_by(&:noteable_id)
ids.each do |id|
loader.call(id, counts[id]&.count || 0)
end end
end end
end end
# rubocop: enable CodeReuse/ActiveRecord
def diff_stats(path: nil) def diff_stats(path: nil)
stats = Array.wrap(object.diff_stats&.to_a) stats = Array.wrap(object.diff_stats&.to_a)
......
# frozen_string_literal: true
module Types
class ProjectInvitationType < BaseObject
graphql_name 'ProjectInvitation'
description 'Represents a Project Membership Invitation'
expose_permissions Types::PermissionTypes::Project
implements InvitationInterface
authorize :read_project
field :project, Types::ProjectType, null: true,
description: 'Project ID for the project of the invitation'
def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.source_id).find
end
end
end
...@@ -160,7 +160,7 @@ module IssuesHelper ...@@ -160,7 +160,7 @@ module IssuesHelper
can_report_spam: issue.submittable_as_spam_by?(current_user).to_s, can_report_spam: issue.submittable_as_spam_by?(current_user).to_s,
can_update_issue: can?(current_user, :update_issue, issue).to_s, can_update_issue: can?(current_user, :update_issue, issue).to_s,
iid: issue.iid, iid: issue.iid,
is_issue_author: issue.author == current_user, is_issue_author: (issue.author == current_user).to_s,
new_issue_path: new_project_issue_path(project), new_issue_path: new_project_issue_path(project),
project_path: project.full_path, project_path: project.full_path,
report_abuse_path: new_abuse_report_path(user_id: issue.author.id, ref_url: issue_url(issue)), report_abuse_path: new_abuse_report_path(user_id: issue.author.id, ref_url: issue_url(issue)),
......
...@@ -48,6 +48,8 @@ class ApplicationRecord < ActiveRecord::Base ...@@ -48,6 +48,8 @@ class ApplicationRecord < ActiveRecord::Base
def self.safe_find_or_create_by!(*args, &block) def self.safe_find_or_create_by!(*args, &block)
safe_find_or_create_by(*args, &block).tap do |record| safe_find_or_create_by(*args, &block).tap do |record|
raise ActiveRecord::RecordNotFound unless record.present?
record.validate! unless record.persisted? record.validate! unless record.persisted?
end end
end end
......
...@@ -37,27 +37,6 @@ module FromUnion ...@@ -37,27 +37,6 @@ module FromUnion
# rubocop: disable Gitlab/Union # rubocop: disable Gitlab/Union
extend FromSetOperator extend FromSetOperator
define_set_operator Gitlab::SQL::Union define_set_operator Gitlab::SQL::Union
alias_method :from_union_set_operator, :from_union
def from_union(members, remove_duplicates: true, alias_as: table_name)
if Feature.enabled?(:sql_set_operators)
from_union_set_operator(members, remove_duplicates: remove_duplicates, alias_as: alias_as)
else
# The original from_union method.
standard_from_union(members, remove_duplicates: remove_duplicates, alias_as: alias_as)
end
end
private
def standard_from_union(members, remove_duplicates: true, alias_as: table_name)
union = Gitlab::SQL::Union
.new(members, remove_duplicates: remove_duplicates)
.to_sql
from(Arel.sql("(#{union}) #{alias_as}"))
end
# rubocop: enable Gitlab/Union # rubocop: enable Gitlab/Union
end end
end end
...@@ -109,6 +109,8 @@ class Group < Namespace ...@@ -109,6 +109,8 @@ class Group < Namespace
.where("project_authorizations.user_id IN (?)", user_ids) .where("project_authorizations.user_id IN (?)", user_ids)
end end
delegate :default_branch_name, to: :namespace_settings
class << self class << self
def sort_by_attribute(method) def sort_by_attribute(method)
if method == 'storage_size_desc' if method == 'storage_size_desc'
...@@ -587,7 +589,7 @@ class Group < Namespace ...@@ -587,7 +589,7 @@ class Group < Namespace
def update_two_factor_requirement def update_two_factor_requirement
return unless saved_change_to_require_two_factor_authentication? || saved_change_to_two_factor_grace_period? return unless saved_change_to_require_two_factor_authentication? || saved_change_to_two_factor_grace_period?
members_with_descendants.find_each(&:update_two_factor_requirement) direct_and_indirect_members.find_each(&:update_two_factor_requirement)
end end
def path_changed_hook def path_changed_hook
......
...@@ -96,6 +96,8 @@ class Member < ApplicationRecord ...@@ -96,6 +96,8 @@ class Member < ApplicationRecord
scope :owners, -> { active.where(access_level: OWNER) } scope :owners, -> { active.where(access_level: OWNER) }
scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) } scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) }
scope :with_user, -> (user) { where(user: user) } scope :with_user, -> (user) { where(user: user) }
scope :with_user_by_email, -> (email) { left_join_users.where(users: { email: email } ) }
scope :preload_user_and_notification_settings, -> { preload(user: :notification_settings) } scope :preload_user_and_notification_settings, -> { preload(user: :notification_settings) }
scope :with_source_id, ->(source_id) { where(source_id: source_id) } scope :with_source_id, ->(source_id) { where(source_id: source_id) }
......
...@@ -393,7 +393,6 @@ class Namespace < ApplicationRecord ...@@ -393,7 +393,6 @@ class Namespace < ApplicationRecord
end end
def changing_shared_runners_enabled_is_allowed def changing_shared_runners_enabled_is_allowed
return unless Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true)
return unless new_record? || changes.has_key?(:shared_runners_enabled) return unless new_record? || changes.has_key?(:shared_runners_enabled)
if shared_runners_enabled && has_parent? && parent.shared_runners_setting == 'disabled_and_unoverridable' if shared_runners_enabled && has_parent? && parent.shared_runners_setting == 'disabled_and_unoverridable'
...@@ -402,7 +401,6 @@ class Namespace < ApplicationRecord ...@@ -402,7 +401,6 @@ class Namespace < ApplicationRecord
end end
def changing_allow_descendants_override_disabled_shared_runners_is_allowed def changing_allow_descendants_override_disabled_shared_runners_is_allowed
return unless Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true)
return unless new_record? || changes.has_key?(:allow_descendants_override_disabled_shared_runners) return unless new_record? || changes.has_key?(:allow_descendants_override_disabled_shared_runners)
if shared_runners_enabled && !new_record? if shared_runners_enabled && !new_record?
......
...@@ -6,10 +6,18 @@ class NamespaceSetting < ApplicationRecord ...@@ -6,10 +6,18 @@ class NamespaceSetting < ApplicationRecord
validate :default_branch_name_content validate :default_branch_name_content
validate :allow_mfa_for_group validate :allow_mfa_for_group
before_validation :normalize_default_branch_name
NAMESPACE_SETTINGS_PARAMS = [:default_branch_name].freeze NAMESPACE_SETTINGS_PARAMS = [:default_branch_name].freeze
self.primary_key = :namespace_id self.primary_key = :namespace_id
private
def normalize_default_branch_name
self.default_branch_name = nil if default_branch_name.blank?
end
def default_branch_name_content def default_branch_name_content
return if default_branch_name.nil? return if default_branch_name.nil?
......
...@@ -197,8 +197,8 @@ class Note < ApplicationRecord ...@@ -197,8 +197,8 @@ class Note < ApplicationRecord
.map(&:position) .map(&:position)
end end
def count_for_collection(ids, type) def count_for_collection(ids, type, count_column = 'COUNT(*) as count')
user.select('noteable_id', 'COUNT(*) as count') user.select(:noteable_id, count_column)
.group(:noteable_id) .group(:noteable_id)
.where(noteable_type: type, noteable_id: ids) .where(noteable_type: type, noteable_id: ids)
end end
......
...@@ -1195,7 +1195,6 @@ class Project < ApplicationRecord ...@@ -1195,7 +1195,6 @@ class Project < ApplicationRecord
end end
def changing_shared_runners_enabled_is_allowed def changing_shared_runners_enabled_is_allowed
return unless Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true)
return unless new_record? || changes.has_key?(:shared_runners_enabled) return unless new_record? || changes.has_key?(:shared_runners_enabled)
if shared_runners_enabled && group && group.shared_runners_setting == 'disabled_and_unoverridable' if shared_runners_enabled && group && group.shared_runners_setting == 'disabled_and_unoverridable'
......
# frozen_string_literal: true
class InvitationPresenter < Gitlab::View::Presenter::Delegated
presents :invitation
end
...@@ -163,16 +163,18 @@ module Ci ...@@ -163,16 +163,18 @@ module Ci
end end
def ensure_pending_state def ensure_pending_state
Ci::BuildPendingState.create_or_find_by!( build_state = Ci::BuildPendingState.safe_find_or_create_by(
build_id: build.id, build_id: build.id,
state: params.fetch(:state), state: params.fetch(:state),
trace_checksum: params.fetch(:checksum), trace_checksum: params.fetch(:checksum),
failure_reason: params.dig(:failure_reason) failure_reason: params.dig(:failure_reason)
) )
rescue ActiveRecord::RecordNotFound
unless build_state.present?
metrics.increment_trace_operation(operation: :conflict) metrics.increment_trace_operation(operation: :conflict)
end
build.pending_state build_state || build.pending_state
end end
## ##
......
...@@ -7,7 +7,7 @@ module Clusters ...@@ -7,7 +7,7 @@ module Clusters
GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token' GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token'
GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin' GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin'
PROJECT_CLUSTER_ROLE_NAME = 'edit' PROJECT_CLUSTER_ROLE_NAME = 'admin'
GITLAB_KNATIVE_SERVING_ROLE_NAME = 'gitlab-knative-serving-role' GITLAB_KNATIVE_SERVING_ROLE_NAME = 'gitlab-knative-serving-role'
GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding' GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding'
GITLAB_CROSSPLANE_DATABASE_ROLE_NAME = 'gitlab-crossplane-database-role' GITLAB_CROSSPLANE_DATABASE_ROLE_NAME = 'gitlab-crossplane-database-role'
......
...@@ -123,11 +123,9 @@ module Clusters ...@@ -123,11 +123,9 @@ module Clusters
end end
def role_binding_resource def role_binding_resource
role_name = Feature.enabled?(:kubernetes_cluster_namespace_role_admin) ? 'admin' : Clusters::Kubernetes::PROJECT_CLUSTER_ROLE_NAME
Gitlab::Kubernetes::RoleBinding.new( Gitlab::Kubernetes::RoleBinding.new(
name: role_binding_name, name: role_binding_name,
role_name: role_name, role_name: Clusters::Kubernetes::PROJECT_CLUSTER_ROLE_NAME,
role_kind: :ClusterRole, role_kind: :ClusterRole,
namespace: service_account_namespace, namespace: service_account_namespace,
service_account_name: service_account_name service_account_name: service_account_name
......
# frozen_string_literal: true
module Members
class InviteService < Members::BaseService
DEFAULT_LIMIT = 100
attr_reader :errors
def initialize(current_user, params)
@current_user, @params = current_user, params.dup
@errors = {}
end
def execute(source)
return error(s_('Email cannot be blank')) if params[:email].blank?
emails = params[:email].split(',').uniq.flatten
return error(s_("Too many users specified (limit is %{user_limit})") % { user_limit: user_limit }) if
user_limit && emails.size > user_limit
emails.each do |email|
next if existing_member?(source, email)
next if existing_invite?(source, email)
if existing_user?(email)
add_existing_user_as_member(current_user, source, params, email)
next
end
invite_new_member_and_user(current_user, source, params, email)
end
return success unless errors.any?
error(errors)
end
private
def invite_new_member_and_user(current_user, source, params, email)
new_member = (source.class.name + 'Member').constantize.create(source_id: source.id,
user_id: nil,
access_level: params[:access_level],
invite_email: email,
created_by_id: current_user.id,
expires_at: params[:expires_at],
requested_at: Time.current.utc)
unless new_member.valid? && new_member.persisted?
errors[params[:email]] = new_member.errors.full_messages.to_sentence
end
end
def add_existing_user_as_member(current_user, source, params, email)
new_member = create_member(current_user, existing_user(email), source, params.merge({ invite_email: email }))
unless new_member.valid? && new_member.persisted?
errors[email] = new_member.errors.full_messages.to_sentence
end
end
def create_member(current_user, user, source, params)
source.add_user(user, params[:access_level], current_user: current_user, expires_at: params[:expires_at])
end
def user_limit
limit = params.fetch(:limit, DEFAULT_LIMIT)
limit && limit < 0 ? nil : limit
end
def existing_member?(source, email)
existing_member = source.members.with_user_by_email(email).exists?
if existing_member
errors[email] = "Already a member of #{source.name}"
return true
end
false
end
def existing_invite?(source, email)
existing_invite = source.members.search_invite_email(email).exists?
if existing_invite
errors[email] = "Member already invited to #{source.name}"
return true
end
false
end
def existing_user(email)
User.find_by_email(email)
end
def existing_user?(email)
existing_user(email).present?
end
end
end
...@@ -9,7 +9,7 @@ module Packages ...@@ -9,7 +9,7 @@ module Packages
def execute def execute
if @tag_name.present? if @tag_name.present?
@tag_name.match(Gitlab::Regex.composer_package_version_regex).captures[0] @tag_name.delete_prefix('v')
elsif @branch_name.present? elsif @branch_name.present?
branch_sufix_or_prefix(@branch_name.match(Gitlab::Regex.composer_package_version_regex)) branch_sufix_or_prefix(@branch_name.match(Gitlab::Regex.composer_package_version_regex))
end end
......
...@@ -16,6 +16,7 @@ module Search ...@@ -16,6 +16,7 @@ module Search
Gitlab::SearchResults.new(current_user, Gitlab::SearchResults.new(current_user,
params[:search], params[:search],
projects, projects,
order_by: params[:order_by],
sort: params[:sort], sort: params[:sort],
filters: { state: params[:state], confidential: params[:confidential] }) filters: { state: params[:state], confidential: params[:confidential] })
end end
......
...@@ -16,6 +16,7 @@ module Search ...@@ -16,6 +16,7 @@ module Search
params[:search], params[:search],
projects, projects,
group: group, group: group,
order_by: params[:order_by],
sort: params[:sort], sort: params[:sort],
filters: { state: params[:state], confidential: params[:confidential] } filters: { state: params[:state], confidential: params[:confidential] }
) )
......
...@@ -17,6 +17,7 @@ module Search ...@@ -17,6 +17,7 @@ module Search
params[:search], params[:search],
project: project, project: project,
repository_ref: params[:repository_ref], repository_ref: params[:repository_ref],
order_by: params[:order_by],
sort: params[:sort], sort: params[:sort],
filters: { confidential: params[:confidential], state: params[:state] } filters: { confidential: params[:confidential], state: params[:state] }
) )
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
= notice[:message].html_safe = notice[:message].html_safe
- if @license.present? && show_license_breakdown? - if @license.present? && show_license_breakdown?
.license-panel.gl-mt-5
= render_if_exists 'admin/licenses/summary'
= render_if_exists 'admin/licenses/breakdown' = render_if_exists 'admin/licenses/breakdown'
.admin-dashboard.gl-mt-3 .admin-dashboard.gl-mt-3
......
%section.settings.as-default-branch-name.no-animate#js-default-branch-name{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Default initial branch name')
%button.gl-button.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Set the default name of the initial branch when creating new repositories through the user interface.')
.settings-content
= form_for @group, url: group_path(@group, anchor: 'js-default-branch-name'), html: { class: 'fieldset-form' } do |f|
= form_errors(@group)
- fallback_branch_name = '<code>master</code>'
%fieldset
.form-group
= f.label :default_branch_name, _('Default initial branch name'), class: 'label-light'
= f.text_field :default_branch_name, value: group.namespace_settings&.default_branch_name, placeholder: 'master', class: 'form-control'
%span.form-text.text-muted
= (_("Changes affect new repositories only. If not specified, either the configured application-wide default or Git's default name %{branch_name_default} will be used.") % { branch_name_default: fallback_branch_name }).html_safe
= f.hidden_field :redirect_target, value: "repository_settings"
= f.submit _('Save changes'), class: 'gl-button btn-success'
...@@ -4,3 +4,4 @@ ...@@ -4,3 +4,4 @@
- deploy_token_description = s_('DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group.') - deploy_token_description = s_('DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group.')
= render "shared/deploy_tokens/index", group_or_project: @group, description: deploy_token_description = render "shared/deploy_tokens/index", group_or_project: @group, description: deploy_token_description
= render "initial_branch_name", group: @group
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
%a.btn.gl-button.btn-default.float-right.gl-display-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } %a.btn.gl-button.btn-default.float-right.gl-display-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
= sprite_icon('chevron-double-lg-left') = sprite_icon('chevron-double-lg-left')
- if Feature.enabled?(:vue_issue_header, @project) - if Feature.enabled?(:vue_issue_header, @project) && display_issuable_type == 'issue'
.js-issue-header-actions{ data: issue_header_actions_data(@project, @issue, current_user) } .js-issue-header-actions{ data: issue_header_actions_data(@project, @issue, current_user) }
- else - else
.detail-page-header-actions.js-issuable-actions.js-issuable-buttons{ data: { "action": "close-reopen" } } .detail-page-header-actions.js-issuable-actions.js-issuable-buttons{ data: { "action": "close-reopen" } }
......
- milestone_url = @milestone.project_milestone? ? project_milestone_path(@project, @milestone) : group_milestone_path(@group, @milestone) - milestone_url = @milestone.project_milestone? ? project_milestone_path(@project, @milestone) : group_milestone_path(@group, @milestone)
%button.js-delete-milestone-button.btn.btn-grouped.btn-danger{ data: { milestone_id: @milestone.id, %button.js-delete-milestone-button.btn.gl-button.btn-grouped.btn-danger{ data: { milestone_id: @milestone.id,
milestone_title: markdown_field(@milestone, :title), milestone_title: markdown_field(@milestone, :title),
milestone_url: milestone_url, milestone_url: milestone_url,
milestone_issue_count: @milestone.issues.count, milestone_issue_count: @milestone.issues.count,
......
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
.milestone-buttons .milestone-buttons
- if can?(current_user, :admin_milestone, @group || @project) - if can?(current_user, :admin_milestone, @group || @project)
= link_to _('Edit'), edit_milestone_path(milestone), class: 'btn btn-grouped' = link_to _('Edit'), edit_milestone_path(milestone), class: 'btn gl-button btn-grouped'
- if milestone.project_milestone? && milestone.project.group - if milestone.project_milestone? && milestone.project.group
%button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal', %button.js-promote-project-milestone-button.btn.gl-button.btn-grouped{ data: { toggle: 'modal',
target: '#promote-milestone-modal', target: '#promote-milestone-modal',
milestone_title: milestone.title, milestone_title: milestone.title,
group_name: milestone.project.group.name, group_name: milestone.project.group.name,
...@@ -26,11 +26,11 @@ ...@@ -26,11 +26,11 @@
#promote-milestone-modal #promote-milestone-modal
- if milestone.active? - if milestone.active?
= link_to _('Close milestone'), update_milestone_path(milestone, { state_event: :close }), method: :put, class: 'btn btn-grouped btn-close' = link_to _('Close milestone'), update_milestone_path(milestone, { state_event: :close }), method: :put, class: 'btn gl-button btn-grouped btn-close'
- else - else
= link_to _('Reopen milestone'), update_milestone_path(milestone, { state_event: :activate }), method: :put, class: 'btn btn-grouped btn-reopen' = link_to _('Reopen milestone'), update_milestone_path(milestone, { state_event: :activate }), method: :put, class: 'btn gl-button btn-grouped btn-reopen'
= render 'shared/milestones/delete_button' = render 'shared/milestones/delete_button'
%button.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ type: 'button' } %button.btn.gl-button.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ type: 'button' }
= sprite_icon('chevron-double-lg-left') = sprite_icon('chevron-double-lg-left')
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= markdown_field(label, :description) = markdown_field(label, :description)
.float-right.d-none.d-lg-block.d-xl-block .float-right.d-none.d-lg-block.d-xl-block
= link_to milestones_issues_path(options.merge(state: 'opened')), class: 'btn btn-transparent btn-action' do = link_to milestones_issues_path(options.merge(state: 'opened')), class: 'btn gl-button btn-default-tertiary btn-action' do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), _('open issue') - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), _('open issue')
= link_to milestones_issues_path(options.merge(state: 'closed')), class: 'btn btn-transparent btn-action' do = link_to milestones_issues_path(options.merge(state: 'closed')), class: 'btn gl-button btn-default-tertiary btn-action' do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), _('closed issue') - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), _('closed issue')
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end .milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
- if @project # if in milestones list on project level - if @project # if in milestones list on project level
- if can_admin_group_milestones? - if can_admin_group_milestones?
%button.js-promote-project-milestone-button.btn.btn-blank.btn-sm.btn-grouped.has-tooltip{ title: s_('Milestones|Promote to Group Milestone'), %button.js-promote-project-milestone-button.btn.gl-button.btn-default-tertiary.btn-sm.btn-grouped.has-tooltip{ title: s_('Milestones|Promote to Group Milestone'),
disabled: true, disabled: true,
type: 'button', type: 'button',
data: { url: promote_project_milestone_path(milestone.project, milestone), data: { url: promote_project_milestone_path(milestone.project, milestone),
...@@ -59,6 +59,6 @@ ...@@ -59,6 +59,6 @@
- if can?(current_user, :admin_milestone, milestone) - if can?(current_user, :admin_milestone, milestone)
- if milestone.closed? - if milestone.closed?
= link_to s_('Milestones|Reopen Milestone'), milestone_path(milestone, milestone: { state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" = link_to s_('Milestones|Reopen Milestone'), milestone_path(milestone, milestone: { state_event: :activate }), method: :put, class: "btn gl-button btn-sm btn-grouped btn-reopen"
- else - else
= link_to s_('Milestones|Close Milestone'), milestone_path(milestone, milestone: { state_event: :close }), method: :put, class: "btn btn-sm btn-grouped btn-close" = link_to s_('Milestones|Close Milestone'), milestone_path(milestone, milestone: { state_event: :close }), method: :put, class: "btn gl-button btn-warning-secondary btn-sm btn-grouped btn-close"
---
title: Resolve User stuck in 2FA setup page even if group disable 2FA enforce
merge_request: 46432
author:
type: fixed
---
title: Allow semver versions in composer packages
merge_request: 46301
author:
type: fixed
---
title: Add ability to sort to search API
merge_request: 46646
author:
type: added
---
title: Populate missing `dismissed_at` and `dismissed_by_id` attributes of vulnerabilities
merge_request: 46370
author:
type: fixed
---
title: Add metric count for projects with alerts created
merge_request: 46636
author:
type: added
---
title: Enable refactored union set operator
merge_request: 46295
author:
type: added
---
title: Replace-GlDeprecatedDropdown-with-GlDropdown-in-app/assets/javascripts/error_tracking
merge_request: 41420
author: nuwe1
type: other
---
title: Add API post /invitations by email
merge_request: 45950
author:
type: added
---
title: Add userDiscussionsCount to issues and merge requests GraphQL
merge_request: 46311
author:
type: added
---
title: Fix linebreak issue in last commit anchor
merge_request: 46643
author:
type: fixed
---
title: Add Default Initial Branch Name for Repositories Group Setting
merge_request: 43290
author:
type: added
---
title: Forbid top level route sitemap
merge_request: 46677
author:
type: changed
---
title: Fix project import search box and make it case insensitive
merge_request: 45783
author:
type: fixed
---
title: Switch to admin clusterRole for GitLab created environment Kubernetes service
account
merge_request: 46417
author:
type: changed
---
title: Fixed create merge request dropdown not re-opening after typing invalid source
branch
merge_request: 46802
author:
type: fixed
--- ---
name: kubernetes_cluster_namespace_role_admin name: ci_include_multiple_files_from_project
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45479 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45991
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/270030 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/271560
type: development type: development
group: group::configure group: group::pipeline authoring
default_enabled: false default_enabled: false
---
name: disable_shared_runners_on_group
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36080
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258991
type: development
group: group::runner
default_enabled: true
---
name: sql_set_operators
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39786
rollout_issue_url:
group: group::access
type: development
default_enabled: false
...@@ -20,4 +20,15 @@ if Gitlab::Runtime.console? ...@@ -20,4 +20,15 @@ if Gitlab::Runtime.console?
end end
puts '-' * 80 puts '-' * 80
# Stop irb from writing a history file by default.
module IrbNoHistory
def init_config(*)
super
IRB.conf[:SAVE_HISTORY] = false
end
end
IRB.singleton_class.prepend(IrbNoHistory)
end end
...@@ -8,3 +8,4 @@ Grape::Validations.register_validator(:integer_none_any, ::API::Validations::Val ...@@ -8,3 +8,4 @@ Grape::Validations.register_validator(:integer_none_any, ::API::Validations::Val
Grape::Validations.register_validator(:array_none_any, ::API::Validations::Validators::ArrayNoneAny) Grape::Validations.register_validator(:array_none_any, ::API::Validations::Validators::ArrayNoneAny)
Grape::Validations.register_validator(:check_assignees_count, ::API::Validations::Validators::CheckAssigneesCount) Grape::Validations.register_validator(:check_assignees_count, ::API::Validations::Validators::CheckAssigneesCount)
Grape::Validations.register_validator(:untrusted_regexp, ::API::Validations::Validators::UntrustedRegexp) Grape::Validations.register_validator(:untrusted_regexp, ::API::Validations::Validators::UntrustedRegexp)
Grape::Validations.register_validator(:email_or_email_list, ::API::Validations::Validators::EmailOrEmailList)
...@@ -144,6 +144,8 @@ ...@@ -144,6 +144,8 @@
- 1 - 1
- - group_import - - group_import
- 1 - 1
- - group_saml_group_sync
- 1
- - hashed_storage - - hashed_storage
- 1 - 1
- - import_issues_csv - - import_issues_csv
......
# frozen_string_literal: true
class AddTemporaryIndexToVulnerabilitiesTable < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'temporary_index_vulnerabilities_on_id'
disable_ddl_transaction!
def up
add_concurrent_index :vulnerabilities, :id, where: "state = 2 AND (dismissed_at IS NULL OR dismissed_by_id IS NULL)", name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :vulnerabilities, INDEX_NAME
end
end
# frozen_string_literal: true
class CreateVulnerabilityFindingLinks < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :vulnerability_finding_links, if_not_exists: true do |t|
t.timestamps_with_timezone null: false
t.references :vulnerability_occurrence, index: { name: 'finding_links_on_vulnerability_occurrence_id' }, null: false, foreign_key: { on_delete: :cascade }
t.text :name, limit: 255
t.text :url, limit: 2048, null: false
end
add_text_limit :vulnerability_finding_links, :name, 255
add_text_limit :vulnerability_finding_links, :url, 2048
end
def down
drop_table :vulnerability_finding_links
end
end
# frozen_string_literal: true
class SchedulePopulateMissingDismissalInformationForVulnerabilities < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 1_000
DELAY_INTERVAL = 3.minutes.to_i
MIGRATION_CLASS = 'PopulateMissingVulnerabilityDismissalInformation'
disable_ddl_transaction!
def up
::Gitlab::BackgroundMigration::PopulateMissingVulnerabilityDismissalInformation::Vulnerability.broken.each_batch(of: BATCH_SIZE) do |batch, index|
vulnerability_ids = batch.pluck(:id)
migrate_in(index * DELAY_INTERVAL, MIGRATION_CLASS, vulnerability_ids)
end
end
def down
# no-op
end
end
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class MigrateGeoBlobVerificationPrimaryWorkerSidekiqQueue < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
sidekiq_queue_migrate 'geo:geo_blob_verification_primary', to: 'geo:geo_verification'
end
def down
sidekiq_queue_migrate 'geo:geo_verification', to: 'geo:geo_blob_verification_primary'
end
end
# frozen_string_literal: true
class RenameSitemapNamespace < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::RenameReservedPathsMigration::V1
DOWNTIME = false
disable_ddl_transaction!
# We're taking over the /sitemap namespace
# since it's necessary for the default behavior of Sitemaps
def up
disable_statement_timeout do
rename_root_paths(['sitemap'])
end
end
def down
disable_statement_timeout do
revert_renames
end
end
end
4b0c70d8cd2648149011adab4f302922483436406f361c3037f26efb12b19042
\ No newline at end of file
9ea8e8f1234d6291ea00e725d380bfe33d804853b90da1221be8781b3dd9bb77
\ No newline at end of file
87e330bc15accb10733825b079cf89e78d905a7c4080075489857085f014bfe7
\ No newline at end of file
50e4e42c804d3abdcfe9ab2bbb890262d4b2ddd93bff1b2af1da1e55a0300cf5
\ No newline at end of file
a861c91ebc7f7892020ba10a151df761b38bf69d5e02bcdf72a965eb266e6aff
\ No newline at end of file
...@@ -17104,6 +17104,26 @@ CREATE SEQUENCE vulnerability_feedback_id_seq ...@@ -17104,6 +17104,26 @@ CREATE SEQUENCE vulnerability_feedback_id_seq
ALTER SEQUENCE vulnerability_feedback_id_seq OWNED BY vulnerability_feedback.id; ALTER SEQUENCE vulnerability_feedback_id_seq OWNED BY vulnerability_feedback.id;
CREATE TABLE vulnerability_finding_links (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
vulnerability_occurrence_id bigint NOT NULL,
name text,
url text NOT NULL,
CONSTRAINT check_55f0a95439 CHECK ((char_length(name) <= 255)),
CONSTRAINT check_b7fe886df6 CHECK ((char_length(url) <= 2048))
);
CREATE SEQUENCE vulnerability_finding_links_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE vulnerability_finding_links_id_seq OWNED BY vulnerability_finding_links.id;
CREATE TABLE vulnerability_historical_statistics ( CREATE TABLE vulnerability_historical_statistics (
id bigint NOT NULL, id bigint NOT NULL,
created_at timestamp with time zone NOT NULL, created_at timestamp with time zone NOT NULL,
...@@ -18203,6 +18223,8 @@ ALTER TABLE ONLY vulnerability_exports ALTER COLUMN id SET DEFAULT nextval('vuln ...@@ -18203,6 +18223,8 @@ ALTER TABLE ONLY vulnerability_exports ALTER COLUMN id SET DEFAULT nextval('vuln
ALTER TABLE ONLY vulnerability_feedback ALTER COLUMN id SET DEFAULT nextval('vulnerability_feedback_id_seq'::regclass); ALTER TABLE ONLY vulnerability_feedback ALTER COLUMN id SET DEFAULT nextval('vulnerability_feedback_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_finding_links ALTER COLUMN id SET DEFAULT nextval('vulnerability_finding_links_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_historical_statistics ALTER COLUMN id SET DEFAULT nextval('vulnerability_historical_statistics_id_seq'::regclass); ALTER TABLE ONLY vulnerability_historical_statistics ALTER COLUMN id SET DEFAULT nextval('vulnerability_historical_statistics_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_identifiers ALTER COLUMN id SET DEFAULT nextval('vulnerability_identifiers_id_seq'::regclass); ALTER TABLE ONLY vulnerability_identifiers ALTER COLUMN id SET DEFAULT nextval('vulnerability_identifiers_id_seq'::regclass);
...@@ -19646,6 +19668,9 @@ ALTER TABLE ONLY vulnerability_exports ...@@ -19646,6 +19668,9 @@ ALTER TABLE ONLY vulnerability_exports
ALTER TABLE ONLY vulnerability_feedback ALTER TABLE ONLY vulnerability_feedback
ADD CONSTRAINT vulnerability_feedback_pkey PRIMARY KEY (id); ADD CONSTRAINT vulnerability_feedback_pkey PRIMARY KEY (id);
ALTER TABLE ONLY vulnerability_finding_links
ADD CONSTRAINT vulnerability_finding_links_pkey PRIMARY KEY (id);
ALTER TABLE ONLY vulnerability_historical_statistics ALTER TABLE ONLY vulnerability_historical_statistics
ADD CONSTRAINT vulnerability_historical_statistics_pkey PRIMARY KEY (id); ADD CONSTRAINT vulnerability_historical_statistics_pkey PRIMARY KEY (id);
...@@ -19870,6 +19895,8 @@ CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_and_note_id_index ON epic_user ...@@ -19870,6 +19895,8 @@ CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_and_note_id_index ON epic_user
CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_index ON epic_user_mentions USING btree (epic_id) WHERE (note_id IS NULL); CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_index ON epic_user_mentions USING btree (epic_id) WHERE (note_id IS NULL);
CREATE INDEX finding_links_on_vulnerability_occurrence_id ON vulnerability_finding_links USING btree (vulnerability_occurrence_id);
CREATE INDEX idx_audit_events_on_entity_id_desc_author_id_created_at ON audit_events USING btree (entity_id, entity_type, id DESC, author_id, created_at); CREATE INDEX idx_audit_events_on_entity_id_desc_author_id_created_at ON audit_events USING btree (entity_id, entity_type, id DESC, author_id, created_at);
CREATE INDEX idx_ci_pipelines_artifacts_locked ON ci_pipelines USING btree (ci_ref_id, id) WHERE (locked = 1); CREATE INDEX idx_ci_pipelines_artifacts_locked ON ci_pipelines USING btree (ci_ref_id, id) WHERE (locked = 1);
...@@ -22200,6 +22227,8 @@ CREATE UNIQUE INDEX snippet_user_mentions_on_snippet_id_index ON snippet_user_me ...@@ -22200,6 +22227,8 @@ CREATE UNIQUE INDEX snippet_user_mentions_on_snippet_id_index ON snippet_user_me
CREATE UNIQUE INDEX taggings_idx ON taggings USING btree (tag_id, taggable_id, taggable_type, context, tagger_id, tagger_type); CREATE UNIQUE INDEX taggings_idx ON taggings USING btree (tag_id, taggable_id, taggable_type, context, tagger_id, tagger_type);
CREATE INDEX temporary_index_vulnerabilities_on_id ON vulnerabilities USING btree (id) WHERE ((state = 2) AND ((dismissed_at IS NULL) OR (dismissed_by_id IS NULL)));
CREATE UNIQUE INDEX term_agreements_unique_index ON term_agreements USING btree (user_id, term_id); CREATE UNIQUE INDEX term_agreements_unique_index ON term_agreements USING btree (user_id, term_id);
CREATE INDEX terraform_state_versions_verification_checksum_partial ON terraform_state_versions USING btree (verification_checksum) WHERE (verification_checksum IS NOT NULL); CREATE INDEX terraform_state_versions_verification_checksum_partial ON terraform_state_versions USING btree (verification_checksum) WHERE (verification_checksum IS NOT NULL);
...@@ -24193,6 +24222,9 @@ ALTER TABLE ONLY gpg_signatures ...@@ -24193,6 +24222,9 @@ ALTER TABLE ONLY gpg_signatures
ALTER TABLE ONLY board_group_recent_visits ALTER TABLE ONLY board_group_recent_visits
ADD CONSTRAINT fk_rails_ca04c38720 FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_ca04c38720 FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_finding_links
ADD CONSTRAINT fk_rails_cbdfde27ce FOREIGN KEY (vulnerability_occurrence_id) REFERENCES vulnerability_occurrences(id) ON DELETE CASCADE;
ALTER TABLE ONLY issues_self_managed_prometheus_alert_events ALTER TABLE ONLY issues_self_managed_prometheus_alert_events
ADD CONSTRAINT fk_rails_cc5d88bbb0 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_cc5d88bbb0 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
......
...@@ -133,9 +133,10 @@ Note the following when promoting a secondary: ...@@ -133,9 +133,10 @@ Note the following when promoting a secondary:
``` ```
1. Promote the **secondary** node to the **primary** node. 1. Promote the **secondary** node to the **primary** node.
CAUTION: **Caution:**
DANGER: **Warning:** If the secondary node [has been paused](../../geo/index.md#pausing-and-resuming-replication), this performs
In GitLab 13.2 and later versions, promoting a secondary node to a primary while the secondary is paused fails. We are [investigating the issue](https://gitlab.com/gitlab-org/gitlab/-/issues/225173). Do not pause replication before promoting a secondary. If the node is paused, please resume before promoting. a point-in-time recovery to the last known state.
Data that was created on the primary while the secondary was paused will be lost.
To promote the secondary node to primary along with preflight checks: To promote the secondary node to primary along with preflight checks:
...@@ -166,14 +167,16 @@ conjunction with multiple servers, as it can only ...@@ -166,14 +167,16 @@ conjunction with multiple servers, as it can only
perform changes on a **secondary** with only a single machine. Instead, you must perform changes on a **secondary** with only a single machine. Instead, you must
do this manually. do this manually.
DANGER: **Warning:** CAUTION: **Caution:**
In GitLab 13.2 and later versions, promoting a secondary node to a primary while the secondary is paused fails. We are [investigating the issue](https://gitlab.com/gitlab-org/gitlab/-/issues/225173). Do not pause replication before promoting a secondary. If the node is paused, please resume before promoting. If the secondary node [has been paused](../../geo/index.md#pausing-and-resuming-replication), this performs
a point-in-time recovery to the last known state.
Data that was created on the primary while the secondary was paused will be lost.
1. SSH in to the database node in the **secondary** and trigger PostgreSQL to 1. SSH in to the database node in the **secondary** and trigger PostgreSQL to
promote to read-write: promote to read-write:
```shell ```shell
sudo gitlab-pg-ctl promote sudo gitlab-ctl promote-db
``` ```
In GitLab 12.8 and earlier, see [Message: `sudo: gitlab-pg-ctl: command not found`](../replication/troubleshooting.md#message-sudo-gitlab-pg-ctl-command-not-found). In GitLab 12.8 and earlier, see [Message: `sudo: gitlab-pg-ctl: command not found`](../replication/troubleshooting.md#message-sudo-gitlab-pg-ctl-command-not-found).
...@@ -211,9 +214,6 @@ an external PostgreSQL database, as it can only perform changes on a **secondary ...@@ -211,9 +214,6 @@ an external PostgreSQL database, as it can only perform changes on a **secondary
node with GitLab and the database on the same machine. As a result, a manual process is node with GitLab and the database on the same machine. As a result, a manual process is
required: required:
DANGER: **Warning:**
In GitLab 13.2 and later versions, promoting a secondary node to a primary while the secondary is paused fails. We are [investigating the issue](https://gitlab.com/gitlab-org/gitlab/-/issues/225173). Do not pause replication before promoting a secondary. If the node is paused, please resume before promoting.
1. Promote the replica database associated with the **secondary** site. This will 1. Promote the replica database associated with the **secondary** site. This will
set the database to read-write: set the database to read-write:
- Amazon RDS - [Promoting a Read Replica to Be a Standalone DB Instance](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ReadRepl.html#USER_ReadRepl.Promote) - Amazon RDS - [Promoting a Read Replica to Be a Standalone DB Instance](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ReadRepl.html#USER_ReadRepl.Promote)
......
...@@ -227,14 +227,15 @@ conjunction with multiple servers, as it can only ...@@ -227,14 +227,15 @@ conjunction with multiple servers, as it can only
perform changes on a **secondary** with only a single machine. Instead, you must perform changes on a **secondary** with only a single machine. Instead, you must
do this manually. do this manually.
DANGER: **Warning:** CAUTION: **Caution:**
In GitLab 13.2 and later versions, promoting a secondary node to a primary while the secondary is paused fails. We are [investigating the issue](https://gitlab.com/gitlab-org/gitlab/-/issues/225173). Do not pause replication before promoting a secondary. If the node is paused, please resume before promoting. If the secondary node [has been paused](../../../geo/index.md#pausing-and-resuming-replication), this performs
a point-in-time recovery to the last known state.
Data that was created on the primary while the secondary was paused will be lost.
1. SSH in to the PostgreSQL node in the **secondary** and trigger PostgreSQL to 1. SSH in to the PostgreSQL node in the **secondary** and promote PostgreSQL separately:
promote to read-write:
```shell ```shell
sudo gitlab-pg-ctl promote sudo gitlab-ctl promote-db
``` ```
In GitLab 12.8 and earlier, see [Message: `sudo: gitlab-pg-ctl: command not found`](../../replication/troubleshooting.md#message-sudo-gitlab-pg-ctl-command-not-found). In GitLab 12.8 and earlier, see [Message: `sudo: gitlab-pg-ctl: command not found`](../../replication/troubleshooting.md#message-sudo-gitlab-pg-ctl-command-not-found).
......
...@@ -196,8 +196,9 @@ For information on how to update your Geo nodes to the latest GitLab version, se ...@@ -196,8 +196,9 @@ For information on how to update your Geo nodes to the latest GitLab version, se
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35913) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35913) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
DANGER: **Warning:** CAUTION: **Caution:**
In GitLab 13.2 and later versions, promoting a secondary node to a primary while the secondary is paused fails. We are [investigating the issue](https://gitlab.com/gitlab-org/gitlab/-/issues/225173). Do not pause replication before promoting a secondary. If the node is paused, please resume before promoting. Pausing and resuming of replication is currently only supported for Geo installations using an
Omnibus GitLab-managed database. External databases are currently not supported.
In some circumstances, like during [upgrades](replication/updating_the_geo_nodes.md) or a [planned failover](disaster_recovery/planned_failover.md), it is desirable to pause replication between the primary and secondary. In some circumstances, like during [upgrades](replication/updating_the_geo_nodes.md) or a [planned failover](disaster_recovery/planned_failover.md), it is desirable to pause replication between the primary and secondary.
......
...@@ -21,9 +21,6 @@ Updating Geo nodes involves performing: ...@@ -21,9 +21,6 @@ Updating Geo nodes involves performing:
NOTE: **Note:** NOTE: **Note:**
These general update steps are not intended for [high-availability deployments](https://docs.gitlab.com/omnibus/update/README.html#multi-node--ha-deployment), and will cause downtime. If you want to avoid downtime, consider using [zero downtime updates](https://docs.gitlab.com/omnibus/update/README.html#zero-downtime-updates). These general update steps are not intended for [high-availability deployments](https://docs.gitlab.com/omnibus/update/README.html#multi-node--ha-deployment), and will cause downtime. If you want to avoid downtime, consider using [zero downtime updates](https://docs.gitlab.com/omnibus/update/README.html#zero-downtime-updates).
DANGER: **Warning:**
In GitLab 13.2 and later versions, promoting a secondary node to a primary while the secondary is paused fails. We are [investigating the issue](https://gitlab.com/gitlab-org/gitlab/-/issues/225173). Do not pause replication before promoting a secondary. If the node is paused, please resume before promoting.
To update the Geo nodes when a new GitLab version is released, update **primary** To update the Geo nodes when a new GitLab version is released, update **primary**
and all **secondary** nodes: and all **secondary** nodes:
......
...@@ -34,7 +34,7 @@ rcli() { ...@@ -34,7 +34,7 @@ rcli() {
# This example works for Omnibus installations of GitLab 7.3 or newer. For an # This example works for Omnibus installations of GitLab 7.3 or newer. For an
# installation from source you will have to change the socket path and the # installation from source you will have to change the socket path and the
# path to redis-cli. # path to redis-cli.
sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.shared_state.socket "$@" sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@"
} }
# test the new shell function; the response should be PONG # test the new shell function; the response should be PONG
......
This diff is collapsed.
...@@ -40,6 +40,7 @@ The following API resources are available in the project context: ...@@ -40,6 +40,7 @@ The following API resources are available in the project context:
| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) | | [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
| [Feature Flags](feature_flags.md) | `/projects/:id/feature_flags` | | [Feature Flags](feature_flags.md) | `/projects/:id/feature_flags` |
| [Feature Flag User Lists](feature_flag_user_lists.md) | `/projects/:id/feature_flags_user_lists` | | [Feature Flag User Lists](feature_flag_user_lists.md) | `/projects/:id/feature_flags_user_lists` |
| [Invitations](invitations.md) | `/projects/:id/invitations` (also available for groups) |
| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) | | [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
| [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) | | [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) |
| [Issue boards](boards.md) | `/projects/:id/boards` | | [Issue boards](boards.md) | `/projects/:id/boards` |
...@@ -108,6 +109,7 @@ The following API resources are available in the group context: ...@@ -108,6 +109,7 @@ The following API resources are available in the group context:
| [Group labels](group_labels.md) | `/groups/:id/labels` | | [Group labels](group_labels.md) | `/groups/:id/labels` |
| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` | | [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
| [Group milestones](group_milestones.md) | `/groups/:id/milestones` | | [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
| [Invitations](invitations.md) | `/groups/:id/invitations` (also available for projects) |
| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) | | [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
| [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) | | [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) |
| [Members](members.md) | `/groups/:id/members` (also available for projects) | | [Members](members.md) | `/groups/:id/members` (also available for projects) |
......
...@@ -7495,6 +7495,11 @@ type EpicIssue implements CurrentUserTodos & Noteable { ...@@ -7495,6 +7495,11 @@ type EpicIssue implements CurrentUserTodos & Noteable {
""" """
upvotes: Int! upvotes: Int!
"""
Number of user discussions in the issue
"""
userDiscussionsCount: Int!
""" """
Number of user notes of the issue Number of user notes of the issue
""" """
...@@ -9959,6 +9964,11 @@ type Issue implements CurrentUserTodos & Noteable { ...@@ -9959,6 +9964,11 @@ type Issue implements CurrentUserTodos & Noteable {
""" """
upvotes: Int! upvotes: Int!
"""
Number of user discussions in the issue
"""
userDiscussionsCount: Int!
""" """
Number of user notes of the issue Number of user notes of the issue
""" """
...@@ -12000,6 +12010,11 @@ type MergeRequest implements CurrentUserTodos & Noteable { ...@@ -12000,6 +12010,11 @@ type MergeRequest implements CurrentUserTodos & Noteable {
""" """
upvotes: Int! upvotes: Int!
"""
Number of user discussions in the merge request
"""
userDiscussionsCount: Int
""" """
User notes count of the merge request User notes count of the merge request
""" """
......
...@@ -20682,6 +20682,24 @@ ...@@ -20682,6 +20682,24 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "userDiscussionsCount",
"description": "Number of user discussions in the issue",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "userNotesCount", "name": "userNotesCount",
"description": "Number of user notes of the issue", "description": "Number of user notes of the issue",
...@@ -27153,6 +27171,24 @@ ...@@ -27153,6 +27171,24 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "userDiscussionsCount",
"description": "Number of user discussions in the issue",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "userNotesCount", "name": "userNotesCount",
"description": "Number of user notes of the issue", "description": "Number of user notes of the issue",
...@@ -32838,6 +32874,20 @@ ...@@ -32838,6 +32874,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "userDiscussionsCount",
"description": "Number of user discussions in the merge request",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "userNotesCount", "name": "userNotesCount",
"description": "User notes count of the merge request", "description": "User notes count of the merge request",
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment