Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
73507eaf1a
commit
5f85444a43
|
@ -130,27 +130,6 @@ variables:
|
|||
REGISTRY_HOST: "registry.gitlab.com"
|
||||
REGISTRY_GROUP: "gitlab-org"
|
||||
|
||||
# Preparing custom clone path to reduce space used by all random forks
|
||||
# on GitLab.com's Shared Runners. Our main forks - especially the security
|
||||
# ones - will have this variable overwritten in the project settings, so that
|
||||
# a security-related code or code using our protected variables will be never
|
||||
# stored on the same path as the community forks.
|
||||
# Part of the solution for the `no space left on device` problem described at
|
||||
# https://gitlab.com/gitlab-org/gitlab/issues/197876.
|
||||
#
|
||||
# For this purpose the https://gitlab.com/gitlab-org-forks group was created
|
||||
# to host a placeholder for the `/builds/gitlab-org-forks` path and ensure
|
||||
# that no legitimate project will ever use it and - by mistake - execute its
|
||||
# job on a shared working directory. It also requires proper configuration of
|
||||
# the Runner that executes the job (which was prepared for our shared runners
|
||||
# by https://ops.gitlab.net/gitlab-cookbooks/chef-repo/-/merge_requests/3977).
|
||||
#
|
||||
# Because of all of that PLEASE DO NOT CHANGE THE PATH.
|
||||
#
|
||||
# For more details and reasoning that brought this change please check
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24887
|
||||
GIT_CLONE_PATH: "/builds/gitlab-org-forks/${CI_PROJECT_NAME}"
|
||||
|
||||
include:
|
||||
- local: .gitlab/ci/*.gitlab-ci.yml
|
||||
- remote: 'https://gitlab.com/gitlab-org/frontend/untamper-my-lockfile/-/raw/main/templates/merge_request_pipelines.yml'
|
||||
|
|
|
@ -47,7 +47,6 @@ Style/RedundantSelf:
|
|||
- 'app/models/commit_status.rb'
|
||||
- 'app/models/compare.rb'
|
||||
- 'app/models/concerns/after_commit_queue.rb'
|
||||
- 'app/models/concerns/approvable.rb'
|
||||
- 'app/models/concerns/atomic_internal_id.rb'
|
||||
- 'app/models/concerns/avatarable.rb'
|
||||
- 'app/models/concerns/awardable.rb'
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import { GlDropdown, GlButton, GlIcon, GlForm, GlFormGroup, GlLink } from '@gitlab/ui';
|
||||
import {
|
||||
GlDropdown,
|
||||
GlButton,
|
||||
GlIcon,
|
||||
GlForm,
|
||||
GlFormGroup,
|
||||
GlLink,
|
||||
GlFormCheckbox,
|
||||
} from '@gitlab/ui';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import { createAlert } from '~/flash';
|
||||
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
|
||||
import { scrollToElement } from '~/lib/utils/common_utils';
|
||||
import Autosave from '~/autosave';
|
||||
|
@ -15,29 +24,46 @@ export default {
|
|||
GlForm,
|
||||
GlFormGroup,
|
||||
GlLink,
|
||||
GlFormCheckbox,
|
||||
MarkdownField,
|
||||
ApprovalPassword: () => import('ee_component/batch_comments/components/approval_password.vue'),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSubmitting: false,
|
||||
note: '',
|
||||
noteData: {
|
||||
noteable_type: '',
|
||||
noteable_id: '',
|
||||
note: '',
|
||||
approve: false,
|
||||
approval_password: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getNotesData', 'getNoteableData', 'noteableType', 'getCurrentUserLastNote']),
|
||||
},
|
||||
watch: {
|
||||
'noteData.approve': function noteDataApproveWatch() {
|
||||
setTimeout(() => {
|
||||
this.repositionDropdown();
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.autosave = new Autosave(
|
||||
$(this.$refs.textarea),
|
||||
`submit_review_dropdown/${this.getNoteableData.id}`,
|
||||
);
|
||||
this.noteData.noteable_type = this.noteableType;
|
||||
this.noteData.noteable_id = this.getNoteableData.id;
|
||||
|
||||
// We override the Bootstrap Vue click outside behaviour
|
||||
// to allow for clicking in the autocomplete dropdowns
|
||||
// without this override the submit dropdown will close
|
||||
// whenever a item in the autocomplete dropdown is clicked
|
||||
const originalClickOutHandler = this.$refs.dropdown.$refs.dropdown.clickOutHandler;
|
||||
this.$refs.dropdown.$refs.dropdown.clickOutHandler = (e) => {
|
||||
const originalClickOutHandler = this.$refs.submitDropdown.$refs.dropdown.clickOutHandler;
|
||||
this.$refs.submitDropdown.$refs.dropdown.clickOutHandler = (e) => {
|
||||
if (!e.target.closest('.atwho-container')) {
|
||||
originalClickOutHandler(e);
|
||||
}
|
||||
|
@ -45,26 +71,29 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions('batchComments', ['publishReview']),
|
||||
repositionDropdown() {
|
||||
this.$refs.submitDropdown?.$refs.dropdown?.updatePopper();
|
||||
},
|
||||
async submitReview() {
|
||||
const noteData = {
|
||||
noteable_type: this.noteableType,
|
||||
noteable_id: this.getNoteableData.id,
|
||||
note: this.note,
|
||||
};
|
||||
|
||||
this.isSubmitting = true;
|
||||
|
||||
await this.publishReview(noteData);
|
||||
try {
|
||||
await this.publishReview(this.noteData);
|
||||
|
||||
this.autosave.reset();
|
||||
this.autosave.reset();
|
||||
|
||||
if (window.mrTabs && this.note) {
|
||||
window.location.hash = `note_${this.getCurrentUserLastNote.id}`;
|
||||
window.mrTabs.tabShown('show');
|
||||
if (window.mrTabs && this.note) {
|
||||
window.location.hash = `note_${this.getCurrentUserLastNote.id}`;
|
||||
window.mrTabs.tabShown('show');
|
||||
|
||||
setTimeout(() =>
|
||||
scrollToElement(document.getElementById(`note_${this.getCurrentUserLastNote.id}`)),
|
||||
);
|
||||
setTimeout(() =>
|
||||
scrollToElement(document.getElementById(`note_${this.getCurrentUserLastNote.id}`)),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.data?.message) {
|
||||
createAlert({ message: e.data.message, captureError: true });
|
||||
}
|
||||
}
|
||||
|
||||
this.isSubmitting = false;
|
||||
|
@ -79,8 +108,9 @@ export default {
|
|||
|
||||
<template>
|
||||
<gl-dropdown
|
||||
ref="dropdown"
|
||||
ref="submitDropdown"
|
||||
right
|
||||
dropup
|
||||
class="submit-review-dropdown"
|
||||
data-qa-selector="submit_review_dropdown"
|
||||
variant="info"
|
||||
|
@ -110,7 +140,7 @@ export default {
|
|||
<markdown-field
|
||||
:is-submitting="isSubmitting"
|
||||
:add-spacing-classes="false"
|
||||
:textarea-value="note"
|
||||
:textarea-value="noteData.note"
|
||||
:markdown-preview-path="getNoteableData.preview_note_path"
|
||||
:markdown-docs-path="getNotesData.markdownDocsPath"
|
||||
:quick-actions-docs-path="getNotesData.quickActionsDocsPath"
|
||||
|
@ -122,7 +152,7 @@ export default {
|
|||
<textarea
|
||||
id="review-note-body"
|
||||
ref="textarea"
|
||||
v-model="note"
|
||||
v-model="noteData.note"
|
||||
dir="auto"
|
||||
:disabled="isSubmitting"
|
||||
name="review[note]"
|
||||
|
@ -139,6 +169,18 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
</gl-form-group>
|
||||
<template v-if="getNoteableData.current_user.can_approve">
|
||||
<gl-form-checkbox v-model="noteData.approve" data-testid="approve_merge_request">
|
||||
{{ __('Approve merge request') }}
|
||||
</gl-form-checkbox>
|
||||
<approval-password
|
||||
v-if="getNoteableData.require_password_to_approve"
|
||||
v-show="noteData.approve"
|
||||
v-model="noteData.approval_password"
|
||||
class="gl-mt-3"
|
||||
data-testid="approve_password"
|
||||
/>
|
||||
</template>
|
||||
<div class="gl-display-flex gl-justify-content-start gl-mt-5">
|
||||
<gl-button
|
||||
:loading="isSubmitting"
|
||||
|
|
|
@ -84,7 +84,11 @@ export const publishReview = ({ commit, dispatch, getters }, noteData = {}) => {
|
|||
.publish(getters.getNotesData.draftsPublishPath, noteData)
|
||||
.then(() => dispatch('updateDiscussionsAfterPublish'))
|
||||
.then(() => commit(types.RECEIVE_PUBLISH_REVIEW_SUCCESS))
|
||||
.catch(() => commit(types.RECEIVE_PUBLISH_REVIEW_ERROR));
|
||||
.catch((e) => {
|
||||
commit(types.RECEIVE_PUBLISH_REVIEW_ERROR);
|
||||
|
||||
throw e.response;
|
||||
});
|
||||
};
|
||||
|
||||
export const updateDiscussionsAfterPublish = async ({ dispatch, getters, rootGetters }) => {
|
||||
|
|
|
@ -183,7 +183,6 @@ export default {
|
|||
:data-user-id="author.id"
|
||||
:data-username="author.username"
|
||||
>
|
||||
<slot name="note-header-info"></slot>
|
||||
<user-name-with-status
|
||||
:name="authorName"
|
||||
:availability="userAvailability(author)"
|
||||
|
@ -207,6 +206,7 @@ export default {
|
|||
@mouseleave="handleUsernameMouseLeave"
|
||||
><span class="note-headline-light">@{{ author.username }}</span>
|
||||
</a>
|
||||
<slot name="note-header-info"></slot>
|
||||
<gitlab-team-member-badge v-if="author && author.is_gitlab_employee" />
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -440,7 +440,7 @@ export default {
|
|||
</gl-avatar-link>
|
||||
</div>
|
||||
|
||||
<div v-else class="gl-float-left gl-pl-3 gl-mr-3 gl-md-pl-2 gl-md-pr-2">
|
||||
<div v-else class="gl-float-left gl-pl-3 gl-md-pl-2">
|
||||
<gl-avatar-link :href="author.path">
|
||||
<gl-avatar
|
||||
:src="author.avatar_url"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<section class="settings gl-py-7">
|
||||
<div class="gl-lg-display-flex gl-gap-6">
|
||||
<div class="gl-lg-w-40p gl-pr-10 gl-flex-shrink-0">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<h4>
|
||||
<slot name="title"></slot>
|
||||
</h4>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<slot name="description"></slot>
|
||||
</p>
|
||||
</div>
|
||||
<div class="gl-pt-3 gl-flex-grow-1">
|
||||
<div class="col-lg-8 gl-pt-3">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -281,7 +281,6 @@ export default {
|
|||
:type="graphViewType"
|
||||
:show-links="showLinks"
|
||||
:tip-previously-dismissed="hoverTipPreviouslyDismissed"
|
||||
:is-pipeline-complete="pipeline.complete"
|
||||
@dismissHoverTip="handleTipDismissal"
|
||||
@updateViewType="updateViewType"
|
||||
@updateShowLinksState="updateShowLinksState"
|
||||
|
|
|
@ -1,33 +1,19 @@
|
|||
<script>
|
||||
import {
|
||||
GlAlert,
|
||||
GlButton,
|
||||
GlButtonGroup,
|
||||
GlLoadingIcon,
|
||||
GlToggle,
|
||||
GlModalDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon, GlToggle } from '@gitlab/ui';
|
||||
import { __, s__ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import PerformanceInsightsModal from '../performance_insights_modal.vue';
|
||||
import { performanceModalId } from '../../constants';
|
||||
import { STAGE_VIEW, LAYER_VIEW } from './constants';
|
||||
|
||||
export default {
|
||||
name: 'GraphViewSelector',
|
||||
performanceModalId,
|
||||
|
||||
components: {
|
||||
GlAlert,
|
||||
GlButton,
|
||||
GlButtonGroup,
|
||||
GlLoadingIcon,
|
||||
GlToggle,
|
||||
PerformanceInsightsModal,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
|
||||
props: {
|
||||
showLinks: {
|
||||
type: Boolean,
|
||||
|
@ -41,10 +27,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isPipelineComplete: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -59,7 +41,6 @@ export default {
|
|||
hoverTipText: __('Tip: Hover over a job to see the jobs it depends on to run.'),
|
||||
linksLabelText: s__('GraphViewType|Show dependencies'),
|
||||
viewLabelText: __('Group jobs by'),
|
||||
performanceBtnText: __('Performance insights'),
|
||||
},
|
||||
views: {
|
||||
[STAGE_VIEW]: {
|
||||
|
@ -150,9 +131,6 @@ export default {
|
|||
this.$emit('updateShowLinksState', val);
|
||||
});
|
||||
},
|
||||
trackInsightsClick() {
|
||||
this.track('click_insights_button', { label: 'performance_insights' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -178,15 +156,6 @@ export default {
|
|||
</gl-button>
|
||||
</gl-button-group>
|
||||
|
||||
<gl-button
|
||||
v-if="isPipelineComplete"
|
||||
v-gl-modal="$options.performanceModalId"
|
||||
data-testid="pipeline-insights-btn"
|
||||
@click="trackInsightsClick"
|
||||
>
|
||||
{{ $options.i18n.performanceBtnText }}
|
||||
</gl-button>
|
||||
|
||||
<div v-if="showLinksToggle" class="gl-display-flex gl-align-items-center">
|
||||
<gl-toggle
|
||||
v-model="showLinksActive"
|
||||
|
@ -202,7 +171,5 @@ export default {
|
|||
<gl-alert v-if="showTip" class="gl-my-5" variant="tip" @dismiss="dismissTip">
|
||||
{{ $options.i18n.hoverTipText }}
|
||||
</gl-alert>
|
||||
|
||||
<performance-insights-modal />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
<script>
|
||||
import { GlAlert, GlCard, GlLink, GlLoadingIcon, GlModal } from '@gitlab/ui';
|
||||
import { __, s__ } from '~/locale';
|
||||
import { humanizeTimeInterval } from '~/lib/utils/datetime_utility';
|
||||
import HelpPopover from '~/vue_shared/components/help_popover.vue';
|
||||
import getPerformanceInsightsQuery from '../graphql/queries/get_performance_insights.query.graphql';
|
||||
import { performanceModalId } from '../constants';
|
||||
import { calculateJobStats, calculateSlowestFiveJobs } from '../utils';
|
||||
|
||||
export default {
|
||||
name: 'PerformanceInsightsModal',
|
||||
i18n: {
|
||||
queuedCardHeader: s__('Pipeline|Longest queued job'),
|
||||
queuedCardHelp: s__(
|
||||
'Pipeline|The longest queued job is the job that spent the longest time in the pending state, waiting to be picked up by a Runner',
|
||||
),
|
||||
executedCardHeader: s__('Pipeline|Last executed job'),
|
||||
executedCardHelp: s__(
|
||||
'Pipeline|The last executed job is the last job to start in the pipeline.',
|
||||
),
|
||||
viewDependency: s__('Pipeline|View dependency'),
|
||||
slowJobsTitle: s__('Pipeline|Five slowest jobs'),
|
||||
feeback: __('Feedback issue'),
|
||||
insightsLimit: s__('Pipeline|Only able to show first 100 results'),
|
||||
},
|
||||
modal: {
|
||||
title: s__('Pipeline|Performance insights'),
|
||||
actionCancel: {
|
||||
text: __('Close'),
|
||||
attributes: {
|
||||
variant: 'confirm',
|
||||
},
|
||||
},
|
||||
},
|
||||
performanceModalId,
|
||||
components: {
|
||||
GlAlert,
|
||||
GlCard,
|
||||
GlLink,
|
||||
GlModal,
|
||||
GlLoadingIcon,
|
||||
HelpPopover,
|
||||
},
|
||||
inject: {
|
||||
pipelineIid: {
|
||||
default: '',
|
||||
},
|
||||
pipelineProjectPath: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
jobs: {
|
||||
query: getPerformanceInsightsQuery,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.pipelineProjectPath,
|
||||
iid: this.pipelineIid,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return data.project?.pipeline?.jobs;
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
jobs: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
longestQueuedJob() {
|
||||
return calculateJobStats(this.jobs, 'queuedDuration');
|
||||
},
|
||||
lastExecutedJob() {
|
||||
return calculateJobStats(this.jobs, 'startedAt');
|
||||
},
|
||||
slowestFiveJobs() {
|
||||
return calculateSlowestFiveJobs(this.jobs);
|
||||
},
|
||||
queuedDurationDisplay() {
|
||||
return humanizeTimeInterval(this.longestQueuedJob.queuedDuration);
|
||||
},
|
||||
showLimitMessage() {
|
||||
return this.jobs.pageInfo.hasNextPage;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-modal
|
||||
:modal-id="$options.performanceModalId"
|
||||
:title="$options.modal.title"
|
||||
:action-cancel="$options.modal.actionCancel"
|
||||
>
|
||||
<gl-loading-icon v-if="$apollo.queries.jobs.loading" size="lg" />
|
||||
|
||||
<template v-else>
|
||||
<gl-alert class="gl-mb-4" :dismissible="false">
|
||||
<p v-if="showLimitMessage" data-testid="limit-alert-text">
|
||||
{{ $options.i18n.insightsLimit }}
|
||||
</p>
|
||||
<gl-link href="https://gitlab.com/gitlab-org/gitlab/-/issues/365902" class="gl-mt-5">
|
||||
{{ $options.i18n.feeback }}
|
||||
</gl-link>
|
||||
</gl-alert>
|
||||
|
||||
<div class="gl-display-flex gl-justify-content-space-between gl-mt-2 gl-mb-7">
|
||||
<gl-card class="gl-w-half gl-mr-7 gl-text-center">
|
||||
<template #header>
|
||||
<span class="gl-font-weight-bold">{{ $options.i18n.queuedCardHeader }}</span>
|
||||
<help-popover>
|
||||
{{ $options.i18n.queuedCardHelp }}
|
||||
</help-popover>
|
||||
</template>
|
||||
<div class="gl-display-flex gl-flex-direction-column">
|
||||
<span
|
||||
class="gl-font-weight-bold gl-font-size-h2 gl-mb-2"
|
||||
data-testid="insights-queued-card-data"
|
||||
>
|
||||
{{ queuedDurationDisplay }}
|
||||
</span>
|
||||
<gl-link
|
||||
:href="longestQueuedJob.detailedStatus.detailsPath"
|
||||
data-testid="insights-queued-card-link"
|
||||
>
|
||||
{{ longestQueuedJob.name }}
|
||||
</gl-link>
|
||||
</div>
|
||||
</gl-card>
|
||||
<gl-card class="gl-w-half gl-text-center" data-testid="insights-executed-card">
|
||||
<template #header>
|
||||
<span class="gl-font-weight-bold">{{ $options.i18n.executedCardHeader }}</span>
|
||||
<help-popover>
|
||||
{{ $options.i18n.executedCardHelp }}
|
||||
</help-popover>
|
||||
</template>
|
||||
<div class="gl-display-flex gl-flex-direction-column">
|
||||
<span
|
||||
class="gl-font-weight-bold gl-font-size-h2 gl-mb-2"
|
||||
data-testid="insights-executed-card-data"
|
||||
>
|
||||
{{ lastExecutedJob.name }}
|
||||
</span>
|
||||
<gl-link
|
||||
:href="lastExecutedJob.detailedStatus.detailsPath"
|
||||
data-testid="insights-executed-card-link"
|
||||
>
|
||||
{{ $options.i18n.viewDependency }}
|
||||
</gl-link>
|
||||
</div>
|
||||
</gl-card>
|
||||
</div>
|
||||
|
||||
<div class="gl-mt-7">
|
||||
<span class="gl-font-weight-bold">{{ $options.i18n.slowJobsTitle }}</span>
|
||||
<div
|
||||
v-for="job in slowestFiveJobs"
|
||||
:key="job.name"
|
||||
class="gl-display-flex gl-justify-content-space-between gl-mb-3 gl-mt-3 gl-p-4 gl-border-t-1 gl-border-t-solid gl-border-b-0 gl-border-b-solid gl-border-gray-100"
|
||||
>
|
||||
<span data-testid="insights-slow-job-stage">{{ job.stage.name }}</span>
|
||||
<gl-link :href="job.detailedStatus.detailsPath" data-testid="insights-slow-job-link">{{
|
||||
job.name
|
||||
}}</gl-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</gl-modal>
|
||||
</template>
|
|
@ -109,5 +109,3 @@ export const DEFAULT_FIELDS = [
|
|||
columnClass: 'gl-w-20p',
|
||||
},
|
||||
];
|
||||
|
||||
export const performanceModalId = 'performanceInsightsModal';
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
query getPerformanceInsightsData($fullPath: ID!, $iid: ID!) {
|
||||
project(fullPath: $fullPath) {
|
||||
id
|
||||
pipeline(iid: $iid) {
|
||||
id
|
||||
jobs {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
id
|
||||
duration
|
||||
detailedStatus {
|
||||
id
|
||||
detailsPath
|
||||
}
|
||||
name
|
||||
stage {
|
||||
id
|
||||
name
|
||||
}
|
||||
startedAt
|
||||
queuedDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -153,24 +153,3 @@ export const getPipelineDefaultTab = (url) => {
|
|||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const calculateJobStats = (jobs, sortField) => {
|
||||
const jobNodes = [...jobs.nodes];
|
||||
|
||||
const sorted = jobNodes.sort((a, b) => {
|
||||
return b[sortField] - a[sortField];
|
||||
});
|
||||
|
||||
return sorted[0];
|
||||
};
|
||||
|
||||
export const calculateSlowestFiveJobs = (jobs) => {
|
||||
const jobNodes = [...jobs.nodes];
|
||||
const limit = 5;
|
||||
|
||||
return jobNodes
|
||||
.sort((a, b) => {
|
||||
return b.duration - a.duration;
|
||||
})
|
||||
.slice(0, limit);
|
||||
};
|
||||
|
|
|
@ -771,6 +771,7 @@ $tabs-holder-z-index: 250;
|
|||
&.show .dropdown-menu {
|
||||
width: calc(100vw - 20px);
|
||||
max-width: 650px;
|
||||
max-height: calc(100vh - 50px);
|
||||
|
||||
.gl-new-dropdown-inner {
|
||||
max-height: none !important;
|
||||
|
@ -812,8 +813,7 @@ $tabs-holder-z-index: 250;
|
|||
}
|
||||
|
||||
.md-preview-holder {
|
||||
max-height: 180px;
|
||||
height: 180px;
|
||||
max-height: 172px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -164,7 +164,7 @@ $system-note-svg-size: 16px;
|
|||
}
|
||||
|
||||
.note-body {
|
||||
padding: $gl-padding-4;
|
||||
padding: $gl-padding-4 $gl-padding-4 $gl-padding-4 $gl-padding-8;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
|
@ -305,7 +305,7 @@ $system-note-svg-size: 16px;
|
|||
height: $system-note-icon-size;
|
||||
border: 1px solid $gray-10;
|
||||
border-radius: $system-note-icon-size;
|
||||
margin: -6px 20px 0 0;
|
||||
margin: -6px 0 0;
|
||||
|
||||
svg {
|
||||
width: $system-note-svg-size;
|
||||
|
@ -551,6 +551,7 @@ $system-note-svg-size: 16px;
|
|||
.note-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> .note-header-info,
|
||||
|
@ -581,7 +582,7 @@ $system-note-svg-size: 16px;
|
|||
|
||||
.note-header-info {
|
||||
min-width: 0;
|
||||
padding-left: $gl-padding-4;
|
||||
padding-left: $gl-padding-8;
|
||||
|
||||
&.discussion {
|
||||
padding-bottom: 0;
|
||||
|
|
|
@ -105,7 +105,7 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
return redirect_back_or_admin_user(notice: _("Error occurred. A blocked user cannot be deactivated")) if user.blocked?
|
||||
return redirect_back_or_admin_user(notice: _("Successfully deactivated")) if user.deactivated?
|
||||
return redirect_back_or_admin_user(notice: _("Internal users cannot be deactivated")) if user.internal?
|
||||
return redirect_back_or_admin_user(notice: _("The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated") % { minimum_inactive_days: ::User::MINIMUM_INACTIVE_DAYS }) unless user.can_be_deactivated?
|
||||
return redirect_back_or_admin_user(notice: _("The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated") % { minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period }) unless user.can_be_deactivated?
|
||||
|
||||
user.deactivate
|
||||
redirect_back_or_admin_user(notice: _("Successfully deactivated"))
|
||||
|
|
|
@ -49,8 +49,16 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
|
|||
def publish
|
||||
result = DraftNotes::PublishService.new(merge_request, current_user).execute(draft_note(allow_nil: true))
|
||||
|
||||
if Feature.enabled?(:mr_review_submit_comment, @project) && create_note_params[:note]
|
||||
Notes::CreateService.new(@project, current_user, create_note_params).execute
|
||||
if Feature.enabled?(:mr_review_submit_comment, @project)
|
||||
::Notes::CreateService.new(@project, current_user, create_note_params).execute if create_note_params[:note]
|
||||
|
||||
if Gitlab::Utils.to_boolean(approve_params[:approve])
|
||||
success = ::MergeRequests::ApprovalService.new(project: @project, current_user: current_user, params: approve_params).execute(merge_request)
|
||||
|
||||
unless success
|
||||
return render json: { message: _('An error occurred while approving, please try again.') }, status: :internal_server_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if result[:status] == :success
|
||||
|
@ -115,6 +123,10 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
|
|||
end
|
||||
end
|
||||
|
||||
def approve_params
|
||||
params.permit(:approve)
|
||||
end
|
||||
|
||||
def prepare_notes_for_rendering(notes)
|
||||
return [] unless notes
|
||||
|
||||
|
@ -148,3 +160,5 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
|
|||
access_denied! unless can?(current_user, :create_note, merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
Projects::MergeRequests::DraftsController.prepend_mod
|
||||
|
|
|
@ -211,6 +211,7 @@ class DeploymentsFinder
|
|||
environment: [],
|
||||
deployable: {
|
||||
job_artifacts: [],
|
||||
user: [],
|
||||
pipeline: {
|
||||
project: {
|
||||
route: [],
|
||||
|
|
|
@ -92,6 +92,8 @@ module Types
|
|||
description: 'Indicates the job is stuck.'
|
||||
field :triggered, GraphQL::Types::Boolean, null: true,
|
||||
description: 'Whether the job was triggered.'
|
||||
field :web_path, GraphQL::Types::String, null: true,
|
||||
description: 'Web path of the job.'
|
||||
|
||||
def kind
|
||||
return ::Ci::Build unless [::Ci::Build, ::Ci::Bridge].include?(object.class)
|
||||
|
@ -181,6 +183,10 @@ module Types
|
|||
::Gitlab::Routing.url_helpers.project_commits_path(object.project, ref_name)
|
||||
end
|
||||
|
||||
def web_path
|
||||
::Gitlab::Routing.url_helpers.project_job_path(object.project, object)
|
||||
end
|
||||
|
||||
def coverage
|
||||
object&.coverage
|
||||
end
|
||||
|
|
|
@ -50,5 +50,20 @@ module Types
|
|||
field :status,
|
||||
Types::DeploymentStatusEnum,
|
||||
description: 'Status of the deployment.'
|
||||
|
||||
field :commit,
|
||||
Types::CommitType,
|
||||
description: 'Commit details of the deployment.',
|
||||
calls_gitaly: true
|
||||
|
||||
field :job,
|
||||
Types::Ci::JobType,
|
||||
description: 'Pipeline job of the deployment.',
|
||||
method: :build
|
||||
|
||||
field :triggerer,
|
||||
Types::UserType,
|
||||
description: 'User who executed the deployment.',
|
||||
method: :deployed_by
|
||||
end
|
||||
end
|
||||
|
|
|
@ -437,7 +437,10 @@ module ApplicationSettingsHelper
|
|||
:pipeline_limit_per_project_user_sha,
|
||||
:invitation_flow_enforcement
|
||||
].tap do |settings|
|
||||
settings << :deactivate_dormant_users unless Gitlab.com?
|
||||
next if Gitlab.com?
|
||||
|
||||
settings << :deactivate_dormant_users
|
||||
settings << :deactivate_dormant_users_period
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ module Ci
|
|||
include Limitable
|
||||
include IgnorableColumns
|
||||
|
||||
TRIGGER_TOKEN_PREFIX = 'glptt-'
|
||||
|
||||
ignore_column :ref, remove_with: '15.4', remove_after: '2022-08-22'
|
||||
|
||||
self.limit_name = 'pipeline_triggers'
|
||||
|
@ -22,7 +24,7 @@ module Ci
|
|||
before_validation :set_default_values
|
||||
|
||||
def set_default_values
|
||||
self.token = SecureRandom.hex(15) if self.token.blank?
|
||||
self.token = "#{TRIGGER_TOKEN_PREFIX}#{SecureRandom.hex(20)}" if self.token.blank?
|
||||
end
|
||||
|
||||
def last_trigger_request
|
||||
|
@ -34,7 +36,7 @@ module Ci
|
|||
end
|
||||
|
||||
def short_token
|
||||
token[0...4] if token.present?
|
||||
token.delete_prefix(TRIGGER_TOKEN_PREFIX)[0...4] if token.present?
|
||||
end
|
||||
|
||||
def can_access_project?
|
||||
|
|
|
@ -27,12 +27,11 @@ module Approvable
|
|||
|
||||
scope :not_approved_by_users_with_usernames, -> (usernames) do
|
||||
users = User.where(username: usernames).select(:id)
|
||||
self_table = self.arel_table
|
||||
app_table = Approval.arel_table
|
||||
|
||||
where(
|
||||
Approval.where(approvals: { user_id: users })
|
||||
.where(app_table[:merge_request_id].eq(self_table[:id]))
|
||||
.where(app_table[:merge_request_id].eq(arel_table[:id]))
|
||||
.select('true')
|
||||
.arel.exists.not
|
||||
)
|
||||
|
|
|
@ -222,6 +222,10 @@ class Deployment < ApplicationRecord
|
|||
Ci::Build.where(id: deployable_ids)
|
||||
end
|
||||
|
||||
def build
|
||||
deployable if deployable.is_a?(::Ci::Build)
|
||||
end
|
||||
|
||||
class << self
|
||||
##
|
||||
# FastDestroyAll concerns
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
class ResourceStateEvent < ResourceEvent
|
||||
include IssueResourceEvent
|
||||
include MergeRequestResourceEvent
|
||||
include Importable
|
||||
|
||||
validate :exactly_one_issuable
|
||||
validate :exactly_one_issuable, unless: :importing?
|
||||
|
||||
belongs_to :source_merge_request, class_name: 'MergeRequest', foreign_key: :source_merge_request_id
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@ class ResourceTimeboxEvent < ResourceEvent
|
|||
|
||||
include IssueResourceEvent
|
||||
include MergeRequestResourceEvent
|
||||
include Importable
|
||||
|
||||
validate :exactly_one_issuable
|
||||
validate :exactly_one_issuable, unless: :importing?
|
||||
|
||||
enum action: {
|
||||
add: 1,
|
||||
|
|
|
@ -92,7 +92,6 @@ class User < ApplicationRecord
|
|||
include ForcedEmailConfirmation
|
||||
include RequireEmailVerification
|
||||
|
||||
MINIMUM_INACTIVE_DAYS = 90
|
||||
MINIMUM_DAYS_CREATED = 7
|
||||
|
||||
# Override Devise::Models::Trackable#update_tracked_fields!
|
||||
|
@ -488,7 +487,7 @@ class User < ApplicationRecord
|
|||
scope :order_oldest_sign_in, -> { reorder(arel_table[:current_sign_in_at].asc.nulls_last) }
|
||||
scope :order_recent_last_activity, -> { reorder(arel_table[:last_activity_on].desc.nulls_last, arel_table[:id].asc) }
|
||||
scope :order_oldest_last_activity, -> { reorder(arel_table[:last_activity_on].asc.nulls_first, arel_table[:id].desc) }
|
||||
scope :dormant, -> { with_state(:active).human_or_service_user.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) }
|
||||
scope :dormant, -> { with_state(:active).human_or_service_user.where('last_activity_on <= ?', Gitlab::CurrentSettings.deactivate_dormant_users_period.day.ago.to_date) }
|
||||
scope :with_no_activity, -> { with_state(:active).human_or_service_user.where(last_activity_on: nil).where('created_at <= ?', MINIMUM_DAYS_CREATED.day.ago.to_date) }
|
||||
scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) }
|
||||
scope :by_ids_or_usernames, -> (ids, usernames) { where(username: usernames).or(where(id: ids)) }
|
||||
|
@ -2322,7 +2321,7 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def no_recent_activity?
|
||||
last_active_at.to_i <= MINIMUM_INACTIVE_DAYS.days.ago.to_i
|
||||
last_active_at.to_i <= Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_i
|
||||
end
|
||||
|
||||
def update_highest_role?
|
||||
|
|
|
@ -40,6 +40,10 @@ class MergeRequestNoteableEntity < IssuableEntity
|
|||
expose :can_update do |merge_request|
|
||||
can?(current_user, :update_merge_request, merge_request)
|
||||
end
|
||||
|
||||
expose :can_approve do |merge_request|
|
||||
merge_request.can_be_approved_by?(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
expose :locked_discussion_docs_path, if: -> (merge_request) { merge_request.discussion_locked? } do |merge_request|
|
||||
|
@ -65,3 +69,5 @@ class MergeRequestNoteableEntity < IssuableEntity
|
|||
@presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user) # rubocop: disable CodeReuse/Presenter
|
||||
end
|
||||
end
|
||||
|
||||
MergeRequestNoteableEntity.prepend_mod_with('MergeRequestNoteableEntity')
|
||||
|
|
|
@ -52,7 +52,13 @@
|
|||
= f.label :deactivate_dormant_users, _('Dormant users'), class: 'label-bold'
|
||||
- dormant_users_help_link = help_page_path('user/admin_area/moderate_users', anchor: 'automatically-deactivate-dormant-users')
|
||||
- dormant_users_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: dormant_users_help_link }
|
||||
= f.gitlab_ui_checkbox_component :deactivate_dormant_users, _('Deactivate dormant users after 90 days of inactivity'), help_text: _('Users can reactivate their account by signing in. %{link_start}Learn more%{link_end}').html_safe % { link_start: dormant_users_help_link_start, link_end: '</a>'.html_safe }
|
||||
= f.gitlab_ui_checkbox_component :deactivate_dormant_users, _('Deactivate dormant users after a period of inactivity'), help_text: _('Users can reactivate their account by signing in. %{link_start}Learn more%{link_end}').html_safe % { link_start: dormant_users_help_link_start, link_end: '</a>'.html_safe }
|
||||
.form-group
|
||||
= f.label :deactivate_dormant_users_period, _('Period of inactivity (days)'), class: 'label-light'
|
||||
= f.number_field :deactivate_dormant_users_period, class: 'form-control gl-form-input', min: '1'
|
||||
.form-text.text-muted
|
||||
= _('Period of inactivity before deactivation')
|
||||
|
||||
.form-group
|
||||
= f.label :personal_access_token_prefix, _('Personal Access Token prefix'), class: 'label-light'
|
||||
= f.text_field :personal_access_token_prefix, placeholder: _('Maximum 20 characters'), class: 'form-control gl-form-input'
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
.settings-content
|
||||
= render 'visibility_and_access'
|
||||
|
||||
%section.settings.as-account-limit.no-animate#js-account-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'account_and_limit_settings_content' } }
|
||||
%section.settings.as-account-limit.no-animate#js-account-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'account_and_limit_settings_content', testid: 'account-limit' } }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Account and limit')
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: escape_gitaly_refs
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91058
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366437
|
||||
milestone: '15.2'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIssuesAuthorizationIndex < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'idx_open_issues_on_project_and_confidential_and_author_and_id'
|
||||
|
||||
def up
|
||||
prepare_async_index :issues, [:project_id, :confidential, :author_id, :id], name: INDEX_NAME, where: 'state_id = 1'
|
||||
end
|
||||
|
||||
def down
|
||||
unprepare_async_index :issues, INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
035e918bcb674fdf1300a5bccbad87806311e6de8589f2db57d7af9cd0108ee9
|
|
@ -38,9 +38,11 @@ fail. This means the user cannot sign in or push or pull code.
|
|||
|
||||
The process also updates the following user information:
|
||||
|
||||
- Email address
|
||||
- SSH public keys (if `sync_ssh_keys` is set)
|
||||
- Kerberos identity (if Kerberos is enabled)
|
||||
- Name. Because of a [sync issue](https://gitlab.com/gitlab-org/gitlab/-/issues/342598), `name` is not synchronized if
|
||||
[**Prevent users from changing their profile name**](../../../user/admin_area/settings/account_and_limit_settings.md#disable-user-profile-name-changes) is enabled.
|
||||
- Email address.
|
||||
- SSH public keys if `sync_ssh_keys` is set.
|
||||
- Kerberos identity if Kerberos is enabled.
|
||||
|
||||
### Adjust LDAP user sync schedule
|
||||
|
||||
|
|
|
@ -191,6 +191,12 @@ to use the HTTPS protocol.
|
|||
WARNING:
|
||||
Multiple wildcards for one instance is not supported. Only one wildcard per instance can be assigned.
|
||||
|
||||
WARNING:
|
||||
GitLab Pages does not update the OAuth application if changes are made to the redirect URI.
|
||||
Before you reconfigure, remove the `gitlab_pages` section from `/etc/gitlab/gitlab-secrets.json`,
|
||||
then run `gitlab-ctl reconfigure`. For more information, read
|
||||
[GitLab Pages does not regenerate OAuth](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3947).
|
||||
|
||||
### Wildcard domains with TLS-terminating Load Balancer
|
||||
|
||||
**Requirements:**
|
||||
|
|
|
@ -10199,6 +10199,7 @@ CI/CD variables for a GitLab instance.
|
|||
| <a id="cijobtags"></a>`tags` | [`[String!]`](#string) | Tags for the current job. |
|
||||
| <a id="cijobtriggered"></a>`triggered` | [`Boolean`](#boolean) | Whether the job was triggered. |
|
||||
| <a id="cijobuserpermissions"></a>`userPermissions` | [`JobPermissions!`](#jobpermissions) | Permissions for the current user on the resource. |
|
||||
| <a id="cijobwebpath"></a>`webPath` | [`String`](#string) | Web path of the job. |
|
||||
|
||||
### `CiJobArtifact`
|
||||
|
||||
|
@ -11019,14 +11020,17 @@ The deployment of an environment.
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="deploymentcommit"></a>`commit` | [`Commit`](#commit) | Commit details of the deployment. |
|
||||
| <a id="deploymentcreatedat"></a>`createdAt` | [`Time`](#time) | When the deployment record was created. |
|
||||
| <a id="deploymentfinishedat"></a>`finishedAt` | [`Time`](#time) | When the deployment finished. |
|
||||
| <a id="deploymentid"></a>`id` | [`ID`](#id) | Global ID of the deployment. |
|
||||
| <a id="deploymentiid"></a>`iid` | [`ID`](#id) | Project-level internal ID of the deployment. |
|
||||
| <a id="deploymentjob"></a>`job` | [`CiJob`](#cijob) | Pipeline job of the deployment. |
|
||||
| <a id="deploymentref"></a>`ref` | [`String`](#string) | Git-Ref that the deployment ran on. |
|
||||
| <a id="deploymentsha"></a>`sha` | [`String`](#string) | Git-SHA that the deployment ran on. |
|
||||
| <a id="deploymentstatus"></a>`status` | [`DeploymentStatus`](#deploymentstatus) | Status of the deployment. |
|
||||
| <a id="deploymenttag"></a>`tag` | [`Boolean`](#boolean) | True or false if the deployment ran on a Git-tag. |
|
||||
| <a id="deploymenttriggerer"></a>`triggerer` | [`UserCore`](#usercore) | User who executed the deployment. |
|
||||
| <a id="deploymentupdatedat"></a>`updatedAt` | [`Time`](#time) | When the deployment record was updated. |
|
||||
|
||||
### `DeploymentDetails`
|
||||
|
@ -11037,14 +11041,17 @@ The details of the deployment.
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="deploymentdetailscommit"></a>`commit` | [`Commit`](#commit) | Commit details of the deployment. |
|
||||
| <a id="deploymentdetailscreatedat"></a>`createdAt` | [`Time`](#time) | When the deployment record was created. |
|
||||
| <a id="deploymentdetailsfinishedat"></a>`finishedAt` | [`Time`](#time) | When the deployment finished. |
|
||||
| <a id="deploymentdetailsid"></a>`id` | [`ID`](#id) | Global ID of the deployment. |
|
||||
| <a id="deploymentdetailsiid"></a>`iid` | [`ID`](#id) | Project-level internal ID of the deployment. |
|
||||
| <a id="deploymentdetailsjob"></a>`job` | [`CiJob`](#cijob) | Pipeline job of the deployment. |
|
||||
| <a id="deploymentdetailsref"></a>`ref` | [`String`](#string) | Git-Ref that the deployment ran on. |
|
||||
| <a id="deploymentdetailssha"></a>`sha` | [`String`](#string) | Git-SHA that the deployment ran on. |
|
||||
| <a id="deploymentdetailsstatus"></a>`status` | [`DeploymentStatus`](#deploymentstatus) | Status of the deployment. |
|
||||
| <a id="deploymentdetailstag"></a>`tag` | [`Boolean`](#boolean) | True or false if the deployment ran on a Git-tag. |
|
||||
| <a id="deploymentdetailstriggerer"></a>`triggerer` | [`UserCore`](#usercore) | User who executed the deployment. |
|
||||
| <a id="deploymentdetailsupdatedat"></a>`updatedAt` | [`Time`](#time) | When the deployment record was updated. |
|
||||
|
||||
### `Design`
|
||||
|
|
|
@ -9,7 +9,7 @@ description: "How to migrate an existing Git repository to Git LFS with BFG."
|
|||
|
||||
WARNING:
|
||||
The following documentation is deprecated. We recommend using
|
||||
[`git lfs migrate`](https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-migrate.1.ronn)
|
||||
[`git lfs migrate`](https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-migrate.adoc)
|
||||
instead of the method documented below.
|
||||
|
||||
Using Git LFS can help you to reduce the size of your Git
|
||||
|
|
|
@ -169,12 +169,13 @@ Users can also be deactivated using the [GitLab API](../../api/users.md#deactiva
|
|||
|
||||
### Automatically deactivate dormant users
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/320875) in GitLab 14.0.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/320875) in GitLab 14.0.
|
||||
> - Customizable time period [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336747) in GitLab 15.4
|
||||
|
||||
Administrators can enable automatic deactivation of users who either:
|
||||
|
||||
- Were created more than a week ago and have not signed in.
|
||||
- Have no activity in the last 90 days.
|
||||
- Have no activity for a specified period of time (defaults to 90 days).
|
||||
|
||||
To do this:
|
||||
|
||||
|
@ -182,6 +183,7 @@ To do this:
|
|||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Account and limit** section.
|
||||
1. Under **Dormant users**, check **Deactivate dormant users after 90 days of inactivity**.
|
||||
1. Under **Period of inactivity (days)**, enter a period of time before deactivation.
|
||||
1. Select **Save changes**.
|
||||
|
||||
When this feature is enabled, GitLab runs a job once a day to deactivate the dormant users.
|
||||
|
|
|
@ -88,6 +88,7 @@ migrated:
|
|||
- Board Lists
|
||||
- Boards
|
||||
- Epics ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250281) in 13.7)
|
||||
- Epic resource state events ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291983) in GitLab 15.4)
|
||||
- Finisher
|
||||
- Group Labels ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292429) in 13.9)
|
||||
- Iterations ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292428) in 13.10)
|
||||
|
@ -116,6 +117,8 @@ On self-managed GitLab, migrating project resources are not available by default
|
|||
- CI Pipelines ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339407) in GitLab 14.6)
|
||||
- Designs ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339421) in GitLab 15.1)
|
||||
- Issues ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267946) in GitLab 14.4)
|
||||
- Issue resource state events ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291983) in GitLab 15.4)
|
||||
- Issue resource milestone events ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291983) in GitLab 15.4)
|
||||
- Labels ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339419) in GitLab 14.4)
|
||||
- LFS Objects ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339405) in GitLab 14.8)
|
||||
- Members ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/341886) in GitLab 14.8)
|
||||
|
@ -123,6 +126,8 @@ On self-managed GitLab, migrating project resources are not available by default
|
|||
- Multiple merge request assignees ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339520) in GitLab 15.3)
|
||||
- Merge request reviewers ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339520) in GitLab 15.3)
|
||||
- Merge request approvers ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339520) in GitLab 15.3)
|
||||
- Merge request resource state events ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291983) in GitLab 15.4)
|
||||
- Merge request resource milestone events ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291983) in GitLab 15.4)
|
||||
- Migrate Push Rules ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339403) in GitLab 14.6)
|
||||
- Pull Requests (including external pull requests) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339409) in GitLab 14.5)
|
||||
- Pipeline History ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339412) in GitLab 14.6)
|
||||
|
|
|
@ -55,6 +55,7 @@ The following items are exported:
|
|||
- Badges
|
||||
- Subgroups (including all the aforementioned data)
|
||||
- Epics
|
||||
- Epic resource state events ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291983) in GitLab 15.4)
|
||||
- Events
|
||||
- [Wikis](../../project/wiki/group.md)
|
||||
([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53247) in GitLab 13.9)
|
||||
|
|
|
@ -59,7 +59,8 @@ To compare branches in a repository:
|
|||
![Delete merged branches](img/delete_merged_branches.png)
|
||||
|
||||
This feature allows merged branches to be deleted in bulk. Only branches that
|
||||
have been merged and [are not protected](../../protected_branches.md) are deleted as part of
|
||||
have been merged into the project's default branch and
|
||||
[are not protected](../../protected_branches.md) are deleted as part of
|
||||
this operation.
|
||||
|
||||
It's particularly useful to clean up old branches that were not deleted
|
||||
|
|
|
@ -62,8 +62,18 @@ The following items are exported:
|
|||
- Project and wiki repositories
|
||||
- Project uploads
|
||||
- Project configuration, excluding integrations
|
||||
- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, time
|
||||
tracking, and other project entities
|
||||
- Issues
|
||||
- Issue comments
|
||||
- Issue resource state events ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291983) in GitLab 15.4)
|
||||
- Issue resource milestone events ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291983) in GitLab 15.4)
|
||||
- Merge requests
|
||||
- Merge request diffs
|
||||
- Merge request comments
|
||||
- Merge request resource state events ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291983) in GitLab 15.4)
|
||||
- Labels
|
||||
- Milestones
|
||||
- Snippets
|
||||
- Time tracking and other project entities
|
||||
- Design Management files and data
|
||||
- LFS objects
|
||||
- Issue boards
|
||||
|
|
|
@ -733,7 +733,7 @@ module API
|
|||
unless user.can_be_deactivated?
|
||||
forbidden!('A blocked user cannot be deactivated by the API') if user.blocked?
|
||||
forbidden!('An internal user cannot be deactivated by the API') if user.internal?
|
||||
forbidden!("The user you are trying to deactivate has been active in the past #{::User::MINIMUM_INACTIVE_DAYS} days and cannot be deactivated")
|
||||
forbidden!("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated")
|
||||
end
|
||||
|
||||
if user.deactivate
|
||||
|
|
|
@ -71,6 +71,21 @@ module Gitlab
|
|||
encode_utf8(data, replace: UNICODE_REPLACEMENT_CHARACTER)
|
||||
end
|
||||
|
||||
# This method escapes unsupported UTF-8 characters instead of deleting them
|
||||
def encode_utf8_with_escaping!(message)
|
||||
return encode!(message) if Feature.disabled?(:escape_gitaly_refs)
|
||||
|
||||
message = force_encode_utf8(message)
|
||||
return message if message.valid_encoding?
|
||||
|
||||
unless message.valid_encoding?
|
||||
message = message.chars.map { |char| char.valid_encoding? ? char : escape_chars(char) }.join
|
||||
end
|
||||
|
||||
# encode and clean the bad chars
|
||||
message.replace clean(message)
|
||||
end
|
||||
|
||||
def encode_utf8(message, replace: "")
|
||||
message = force_encode_utf8(message)
|
||||
return message if message.valid_encoding?
|
||||
|
@ -145,6 +160,15 @@ module Gitlab
|
|||
message.force_encoding("UTF-8")
|
||||
end
|
||||
|
||||
# Escapes \x80 - \xFF characters not supported by UTF-8
|
||||
def escape_chars(char)
|
||||
bytes = char.bytes
|
||||
|
||||
return char unless bytes.one?
|
||||
|
||||
"%#{bytes.first.to_s(16).upcase}"
|
||||
end
|
||||
|
||||
def clean(message, replace: "")
|
||||
message.encode(
|
||||
"UTF-16BE",
|
||||
|
|
|
@ -25,7 +25,7 @@ module Gitlab
|
|||
include Gitlab::EncodingHelper
|
||||
|
||||
def ref_name(ref)
|
||||
encode!(ref).sub(%r{\Arefs/(tags|heads|remotes)/}, '')
|
||||
encode_utf8_with_escaping!(ref).sub(%r{\Arefs/(tags|heads|remotes)/}, '')
|
||||
end
|
||||
|
||||
def branch_name(ref)
|
||||
|
|
|
@ -1011,16 +1011,20 @@ module Gitlab
|
|||
end
|
||||
|
||||
def search_files_by_name(query, ref)
|
||||
safe_query = Regexp.escape(query.sub(%r{^/*}, ""))
|
||||
safe_query = query.sub(%r{^/*}, "")
|
||||
ref ||= root_ref
|
||||
|
||||
return [] if empty? || safe_query.blank?
|
||||
|
||||
gitaly_repository_client.search_files_by_name(ref, safe_query)
|
||||
gitaly_repository_client.search_files_by_name(ref, safe_query).map do |file|
|
||||
Gitlab::EncodingHelper.encode_utf8(file)
|
||||
end
|
||||
end
|
||||
|
||||
def search_files_by_regexp(filter, ref = 'HEAD')
|
||||
gitaly_repository_client.search_files_by_regexp(ref, filter)
|
||||
gitaly_repository_client.search_files_by_regexp(ref, filter).map do |file|
|
||||
Gitlab::EncodingHelper.encode_utf8(file)
|
||||
end
|
||||
end
|
||||
|
||||
def find_commits_by_message(query, ref, path, limit, offset)
|
||||
|
|
|
@ -63,7 +63,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def init_from_gitaly
|
||||
@name = encode!(@raw_tag.name.dup)
|
||||
@name = encode_utf8_with_escaping!(@raw_tag.name.dup)
|
||||
@target = @raw_tag.id
|
||||
@message = message_from_gitaly_tag
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ module Gitlab
|
|||
return unless branch
|
||||
|
||||
target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit)
|
||||
Gitlab::Git::Branch.new(@repository, encode!(branch.name.dup), branch.target_commit.id, target_commit)
|
||||
Gitlab::Git::Branch.new(@repository, branch.name.dup, branch.target_commit.id, target_commit)
|
||||
end
|
||||
|
||||
def find_tag(tag_name)
|
||||
|
@ -273,7 +273,7 @@ module Gitlab
|
|||
message.branches.map do |gitaly_branch|
|
||||
Gitlab::Git::Branch.new(
|
||||
@repository,
|
||||
encode!(gitaly_branch.name.dup),
|
||||
gitaly_branch.name.dup,
|
||||
gitaly_branch.commit_id,
|
||||
commit_from_local_branches_response(gitaly_branch)
|
||||
)
|
||||
|
|
|
@ -120,6 +120,7 @@ ee:
|
|||
- events:
|
||||
- :push_event_payload
|
||||
- :system_note_metadata
|
||||
- :resource_state_events
|
||||
- boards:
|
||||
- :board_assignee
|
||||
- :milestone
|
||||
|
|
|
@ -114,6 +114,7 @@ ee:
|
|||
- :award_emoji
|
||||
- events:
|
||||
- :push_event_payload
|
||||
- :resource_state_events
|
||||
- boards:
|
||||
- :board_assignee
|
||||
- :milestone
|
||||
|
|
|
@ -29,6 +29,9 @@ tree:
|
|||
- resource_label_events:
|
||||
- label:
|
||||
- :priorities
|
||||
- resource_milestone_events:
|
||||
- :milestone
|
||||
- :resource_state_events
|
||||
- designs:
|
||||
- notes:
|
||||
- :author
|
||||
|
@ -82,6 +85,9 @@ tree:
|
|||
- resource_label_events:
|
||||
- label:
|
||||
- :priorities
|
||||
- resource_milestone_events:
|
||||
- :milestone
|
||||
- :resource_state_events
|
||||
- :external_pull_requests
|
||||
- ci_pipelines:
|
||||
- notes:
|
||||
|
@ -721,6 +727,22 @@ included_attributes:
|
|||
- :build_git_strategy
|
||||
- :build_enabled
|
||||
- :security_and_compliance_enabled
|
||||
resource_iteration_events:
|
||||
- :user_id
|
||||
- :action
|
||||
- :created_at
|
||||
resource_milestone_events:
|
||||
- :user_id
|
||||
- :action
|
||||
- :created_at
|
||||
- :state
|
||||
resource_state_events:
|
||||
- :user_id
|
||||
- :state
|
||||
- :created_at
|
||||
- :source_commit
|
||||
- :close_after_error_tracking_resolve
|
||||
- :close_auto_resolve_prometheus_alert
|
||||
|
||||
# Do not include the following attributes for the models specified.
|
||||
excluded_attributes:
|
||||
|
@ -989,6 +1011,17 @@ excluded_attributes:
|
|||
milestone_releases:
|
||||
- :milestone_id
|
||||
- :release_id
|
||||
resource_milestone_events:
|
||||
- :id
|
||||
- :issue_id
|
||||
- :merge_request_id
|
||||
- :milestone_id
|
||||
resource_state_events:
|
||||
- :id
|
||||
- :issue_id
|
||||
- :merge_request_id
|
||||
- :epic_id
|
||||
- :source_merge_request_id
|
||||
methods:
|
||||
notes:
|
||||
- :type
|
||||
|
|
|
@ -4035,6 +4035,9 @@ msgstr ""
|
|||
msgid "An error occurred while adding formatted title for epic"
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while approving, please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while authorizing your role"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4873,6 +4876,9 @@ msgstr ""
|
|||
msgid "Approve a merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "Approve merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "Approve the current merge request."
|
||||
msgstr ""
|
||||
|
||||
|
@ -12297,7 +12303,7 @@ msgstr ""
|
|||
msgid "Days to merge"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deactivate dormant users after 90 days of inactivity"
|
||||
msgid "Deactivate dormant users after a period of inactivity"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dear Administrator,"
|
||||
|
@ -16409,9 +16415,6 @@ msgstr ""
|
|||
msgid "February"
|
||||
msgstr ""
|
||||
|
||||
msgid "Feedback issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fetch and check out this merge request's feature branch:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -28402,9 +28405,6 @@ msgstr ""
|
|||
msgid "Perform common operations on GitLab project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Performance insights"
|
||||
msgstr ""
|
||||
|
||||
msgid "Performance optimization"
|
||||
msgstr ""
|
||||
|
||||
|
@ -28480,6 +28480,12 @@ msgstr ""
|
|||
msgid "Period in seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "Period of inactivity (days)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Period of inactivity before deactivation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Permalink"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29191,18 +29197,9 @@ msgstr ""
|
|||
msgid "Pipeline|Failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Five slowest jobs"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|In progress"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Last executed job"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Longest queued job"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Manual"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29215,18 +29212,12 @@ msgstr ""
|
|||
msgid "Pipeline|Merged result pipeline"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Only able to show first 100 results"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Passed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Pending"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Performance insights"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Pipeline"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29284,12 +29275,6 @@ msgstr ""
|
|||
msgid "Pipeline|Test coverage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|The last executed job is the last job to start in the pipeline."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|The longest queued job is the job that spent the longest time in the pending state, waiting to be picked up by a Runner"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|This change will decrease the overall test coverage if merged."
|
||||
msgstr ""
|
||||
|
||||
|
@ -29320,9 +29305,6 @@ msgstr ""
|
|||
msgid "Pipeline|View commit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|View dependency"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|View pipeline"
|
||||
msgstr ""
|
||||
|
||||
|
@ -40856,6 +40838,9 @@ msgstr ""
|
|||
msgid "To add the entry manually, provide the following details to the application on your phone."
|
||||
msgstr ""
|
||||
|
||||
msgid "To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
|
||||
msgstr ""
|
||||
|
||||
msgid "To complete registration, we need additional details from you."
|
||||
msgstr ""
|
||||
|
||||
|
@ -45572,6 +45557,9 @@ msgstr ""
|
|||
msgid "Your new comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your password reset token has expired."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -270,19 +270,19 @@ RSpec.describe Admin::UsersController do
|
|||
let(:user) { create(:user, **activity) }
|
||||
|
||||
context 'with no recent activity' do
|
||||
let(:activity) { { last_activity_on: ::User::MINIMUM_INACTIVE_DAYS.next.days.ago } }
|
||||
let(:activity) { { last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.next.days.ago } }
|
||||
|
||||
it_behaves_like 'a request that deactivates the user'
|
||||
end
|
||||
|
||||
context 'with recent activity' do
|
||||
let(:activity) { { last_activity_on: ::User::MINIMUM_INACTIVE_DAYS.pred.days.ago } }
|
||||
let(:activity) { { last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.pred.days.ago } }
|
||||
|
||||
it 'does not deactivate the user' do
|
||||
put :deactivate, params: { id: user.username }
|
||||
user.reload
|
||||
expect(user.deactivated?).to be_falsey
|
||||
expect(flash[:notice]).to eq("The user you are trying to deactivate has been active in the past #{::User::MINIMUM_INACTIVE_DAYS} days and cannot be deactivated")
|
||||
expect(flash[:notice]).to eq("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ RSpec.describe Projects::MergeRequests::DraftsController do
|
|||
include RepoHelpers
|
||||
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
|
||||
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project, author: create(:user)) }
|
||||
let(:user) { project.first_owner }
|
||||
let(:user2) { create(:user) }
|
||||
|
||||
|
@ -417,6 +417,38 @@ RSpec.describe Projects::MergeRequests::DraftsController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'approve merge request' do
|
||||
before do
|
||||
create(:draft_note, merge_request: merge_request, author: user)
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(mr_review_submit_comment: false)
|
||||
end
|
||||
|
||||
it 'does not approve' do
|
||||
post :publish, params: params.merge!(approve: true)
|
||||
|
||||
expect(merge_request.approvals.reload.size).to be(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
it 'approves merge request' do
|
||||
post :publish, params: params.merge!(approve: true)
|
||||
|
||||
expect(merge_request.approvals.reload.size).to be(1)
|
||||
end
|
||||
|
||||
it 'does not approve merge request' do
|
||||
post :publish, params: params.merge!(approve: false)
|
||||
|
||||
expect(merge_request.approvals.reload.size).to be(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE #destroy' do
|
||||
|
|
|
@ -102,7 +102,7 @@ RSpec.describe 'Admin updates settings' do
|
|||
end
|
||||
|
||||
it 'change Account and Limit Settings' do
|
||||
page.within('.as-account-limit') do
|
||||
page.within(find('[data-testid="account-limit"]')) do
|
||||
uncheck 'Gravatar enabled'
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
@ -112,7 +112,7 @@ RSpec.describe 'Admin updates settings' do
|
|||
end
|
||||
|
||||
it 'change Maximum export size' do
|
||||
page.within('.as-account-limit') do
|
||||
page.within(find('[data-testid="account-limit"]')) do
|
||||
fill_in 'Maximum export size (MB)', with: 25
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
@ -122,7 +122,7 @@ RSpec.describe 'Admin updates settings' do
|
|||
end
|
||||
|
||||
it 'change Maximum import size' do
|
||||
page.within('.as-account-limit') do
|
||||
page.within(find('[data-testid="account-limit"]')) do
|
||||
fill_in 'Maximum import size (MB)', with: 15
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
@ -150,16 +150,20 @@ RSpec.describe 'Admin updates settings' do
|
|||
it 'does not expose the setting' do
|
||||
expect(page).to have_no_selector('#application_setting_deactivate_dormant_users')
|
||||
end
|
||||
|
||||
it 'does not expose the setting' do
|
||||
expect(page).to have_no_selector('#application_setting_deactivate_dormant_users_period')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not Gitlab.com' do
|
||||
let(:dot_com?) { false }
|
||||
|
||||
it 'change Dormant users' do
|
||||
expect(page).to have_unchecked_field('Deactivate dormant users after 90 days of inactivity')
|
||||
it 'changes Dormant users' do
|
||||
expect(page).to have_unchecked_field('Deactivate dormant users after a period of inactivity')
|
||||
expect(current_settings.deactivate_dormant_users).to be_falsey
|
||||
|
||||
page.within('.as-account-limit') do
|
||||
page.within(find('[data-testid="account-limit"]')) do
|
||||
check 'application_setting_deactivate_dormant_users'
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
@ -169,7 +173,22 @@ RSpec.describe 'Admin updates settings' do
|
|||
page.refresh
|
||||
|
||||
expect(current_settings.deactivate_dormant_users).to be_truthy
|
||||
expect(page).to have_checked_field('Deactivate dormant users after 90 days of inactivity')
|
||||
expect(page).to have_checked_field('Deactivate dormant users after a period of inactivity')
|
||||
end
|
||||
|
||||
it 'change Dormant users period' do
|
||||
expect(page).to have_field _('Period of inactivity (days)')
|
||||
|
||||
page.within(find('[data-testid="account-limit"]')) do
|
||||
fill_in _('application_setting_deactivate_dormant_users_period'), with: '35'
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
||||
expect(page).to have_content "Application settings saved successfully"
|
||||
|
||||
page.refresh
|
||||
|
||||
expect(page).to have_field _('Period of inactivity (days)'), with: '35'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,11 +22,13 @@
|
|||
"type": "object",
|
||||
"required": [
|
||||
"can_create_note",
|
||||
"can_update"
|
||||
"can_update",
|
||||
"can_approve"
|
||||
],
|
||||
"properties": {
|
||||
"can_create_note": { "type": "boolean" },
|
||||
"can_update": { "type": "boolean" }
|
||||
"can_update": { "type": "boolean" },
|
||||
"can_approve": { "type": "boolean" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
|
|
@ -394,7 +394,41 @@
|
|||
"id": 1,
|
||||
"issue_id": 40,
|
||||
"sentry_issue_identifier": 1234567891
|
||||
}
|
||||
},
|
||||
"resource_milestone_events": [
|
||||
{
|
||||
"user_id": 1,
|
||||
"action": "add",
|
||||
"state": "opened",
|
||||
"created_at": "2022-08-17T13:06:53.547Z",
|
||||
"milestone": {
|
||||
"title": "v4.0",
|
||||
"description": "Totam quam laborum id magnam natus eaque aspernatur.",
|
||||
"created_at": "2016-06-14T15:02:04.590Z",
|
||||
"updated_at": "2016-06-14T15:02:04.590Z",
|
||||
"state": "active",
|
||||
"iid": 5
|
||||
}
|
||||
}
|
||||
],
|
||||
"resource_state_events": [
|
||||
{
|
||||
"user_id": 1,
|
||||
"created_at": "2022-08-17T13:08:16.838Z",
|
||||
"state": "closed",
|
||||
"source_commit": null,
|
||||
"close_after_error_tracking_resolve": false,
|
||||
"close_auto_resolve_prometheus_alert": false
|
||||
},
|
||||
{
|
||||
"user_id": 1,
|
||||
"created_at": "2022-08-17T13:08:17.702Z",
|
||||
"state": "reopened",
|
||||
"source_commit": null,
|
||||
"close_after_error_tracking_resolve": false,
|
||||
"close_auto_resolve_prometheus_alert": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
|
@ -3215,6 +3249,40 @@
|
|||
"created_at": "2020-01-07T11:21:21.235Z",
|
||||
"updated_at": "2020-01-08T11:21:21.235Z"
|
||||
}
|
||||
],
|
||||
"resource_milestone_events": [
|
||||
{
|
||||
"user_id": 1,
|
||||
"action": "add",
|
||||
"state": "opened",
|
||||
"created_at": "2022-08-17T13:06:53.547Z",
|
||||
"milestone": {
|
||||
"title": "v4.0",
|
||||
"description": "Totam quam laborum id magnam natus eaque aspernatur.",
|
||||
"created_at": "2016-06-14T15:02:04.590Z",
|
||||
"updated_at": "2016-06-14T15:02:04.590Z",
|
||||
"state": "active",
|
||||
"iid": 5
|
||||
}
|
||||
}
|
||||
],
|
||||
"resource_state_events": [
|
||||
{
|
||||
"user_id": 1,
|
||||
"created_at": "2022-08-17T13:08:16.838Z",
|
||||
"state": "closed",
|
||||
"source_commit": null,
|
||||
"close_after_error_tracking_resolve": false,
|
||||
"close_auto_resolve_prometheus_alert": false
|
||||
},
|
||||
{
|
||||
"user_id": 1,
|
||||
"created_at": "2022-08-17T13:08:17.702Z",
|
||||
"state": "reopened",
|
||||
"source_commit": null,
|
||||
"close_after_error_tracking_resolve": false,
|
||||
"close_auto_resolve_prometheus_alert": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -8,7 +8,7 @@ Vue.use(Vuex);
|
|||
let wrapper;
|
||||
let publishReview;
|
||||
|
||||
function factory() {
|
||||
function factory({ canApprove = true } = {}) {
|
||||
publishReview = jest.fn();
|
||||
|
||||
const store = new Vuex.Store({
|
||||
|
@ -17,7 +17,11 @@ function factory() {
|
|||
markdownDocsPath: '/markdown/docs',
|
||||
quickActionsDocsPath: '/quickactions/docs',
|
||||
}),
|
||||
getNoteableData: () => ({ id: 1, preview_note_path: '/preview' }),
|
||||
getNoteableData: () => ({
|
||||
id: 1,
|
||||
preview_note_path: '/preview',
|
||||
current_user: { can_approve: canApprove },
|
||||
}),
|
||||
noteableType: () => 'merge_request',
|
||||
},
|
||||
modules: {
|
||||
|
@ -54,6 +58,8 @@ describe('Batch comments submit dropdown', () => {
|
|||
noteable_type: 'merge_request',
|
||||
noteable_id: 1,
|
||||
note: 'Hello world',
|
||||
approve: false,
|
||||
approval_password: '',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -66,4 +72,14 @@ describe('Batch comments submit dropdown', () => {
|
|||
|
||||
expect(findSubmitButton().props('loading')).toBe(true);
|
||||
});
|
||||
|
||||
it.each`
|
||||
canApprove | exists | existsText
|
||||
${true} | ${true} | ${'shows'}
|
||||
${false} | ${false} | ${'hides'}
|
||||
`('it $existsText approve checkbox if can_approve is $canApprove', ({ canApprove, exists }) => {
|
||||
factory({ canApprove });
|
||||
|
||||
expect(wrapper.findByTestId('approve_merge_request').exists()).toBe(exists);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -180,6 +180,7 @@ describe('Batch comments store actions', () => {
|
|||
});
|
||||
|
||||
it('calls service with notes data', () => {
|
||||
mock.onAny().reply(200);
|
||||
jest.spyOn(axios, 'post');
|
||||
|
||||
return actions
|
||||
|
@ -192,7 +193,7 @@ describe('Batch comments store actions', () => {
|
|||
it('dispatches error commits', () => {
|
||||
mock.onAny().reply(500);
|
||||
|
||||
return actions.publishReview({ dispatch, commit, getters, rootGetters }).then(() => {
|
||||
return actions.publishReview({ dispatch, commit, getters, rootGetters }).catch(() => {
|
||||
expect(commit.mock.calls[0]).toEqual(['REQUEST_PUBLISH_REVIEW']);
|
||||
expect(commit.mock.calls[1]).toEqual(['RECEIVE_PUBLISH_REVIEW_ERROR']);
|
||||
});
|
||||
|
|
|
@ -30,16 +30,10 @@ import * as Api from '~/pipelines/components/graph_shared/api';
|
|||
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
|
||||
import * as parsingUtils from '~/pipelines/components/parsing_utils';
|
||||
import getPipelineHeaderData from '~/pipelines/graphql/queries/get_pipeline_header_data.query.graphql';
|
||||
import getPerformanceInsights from '~/pipelines/graphql/queries/get_performance_insights.query.graphql';
|
||||
import * as sentryUtils from '~/pipelines/utils';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
import { mockRunningPipelineHeaderData } from '../mock_data';
|
||||
import {
|
||||
mapCallouts,
|
||||
mockCalloutsResponse,
|
||||
mockPipelineResponse,
|
||||
mockPerformanceInsightsResponse,
|
||||
} from './mock_data';
|
||||
import { mapCallouts, mockCalloutsResponse, mockPipelineResponse } from './mock_data';
|
||||
|
||||
const defaultProvide = {
|
||||
graphqlResourceEtag: 'frog/amphibirama/etag/',
|
||||
|
@ -95,15 +89,11 @@ describe('Pipeline graph wrapper', () => {
|
|||
const callouts = mapCallouts(calloutsList);
|
||||
const getUserCalloutsHandler = jest.fn().mockResolvedValue(mockCalloutsResponse(callouts));
|
||||
const getPipelineHeaderDataHandler = jest.fn().mockResolvedValue(mockRunningPipelineHeaderData);
|
||||
const getPerformanceInsightsHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockPerformanceInsightsResponse);
|
||||
|
||||
const requestHandlers = [
|
||||
[getPipelineHeaderData, getPipelineHeaderDataHandler],
|
||||
[getPipelineDetails, getPipelineDetailsHandler],
|
||||
[getUserCallouts, getUserCalloutsHandler],
|
||||
[getPerformanceInsights, getPerformanceInsightsHandler],
|
||||
];
|
||||
|
||||
const apolloProvider = createMockApollo(requestHandlers);
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import { LAYER_VIEW, STAGE_VIEW } from '~/pipelines/components/graph/constants';
|
||||
import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.vue';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import getPerformanceInsights from '~/pipelines/graphql/queries/get_performance_insights.query.graphql';
|
||||
import { mockPerformanceInsightsResponse } from './mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('the graph view selector component', () => {
|
||||
let wrapper;
|
||||
let trackingSpy;
|
||||
|
||||
const findDependenciesToggle = () => wrapper.find('[data-testid="show-links-toggle"]');
|
||||
const findViewTypeSelector = () => wrapper.findComponent(GlButtonGroup);
|
||||
|
@ -22,13 +13,11 @@ describe('the graph view selector component', () => {
|
|||
const findSwitcherLoader = () => wrapper.find('[data-testid="switcher-loading-state"]');
|
||||
const findToggleLoader = () => findDependenciesToggle().find(GlLoadingIcon);
|
||||
const findHoverTip = () => wrapper.findComponent(GlAlert);
|
||||
const findPipelineInsightsBtn = () => wrapper.find('[data-testid="pipeline-insights-btn"]');
|
||||
|
||||
const defaultProps = {
|
||||
showLinks: false,
|
||||
tipPreviouslyDismissed: false,
|
||||
type: STAGE_VIEW,
|
||||
isPipelineComplete: true,
|
||||
};
|
||||
|
||||
const defaultData = {
|
||||
|
@ -38,14 +27,6 @@ describe('the graph view selector component', () => {
|
|||
showLinksActive: false,
|
||||
};
|
||||
|
||||
const getPerformanceInsightsHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockPerformanceInsightsResponse);
|
||||
|
||||
const requestHandlers = [[getPerformanceInsights, getPerformanceInsightsHandler]];
|
||||
|
||||
const apolloProvider = createMockApollo(requestHandlers);
|
||||
|
||||
const createComponent = ({ data = {}, mountFn = shallowMount, props = {} } = {}) => {
|
||||
wrapper = mountFn(GraphViewSelector, {
|
||||
propsData: {
|
||||
|
@ -58,7 +39,6 @@ describe('the graph view selector component', () => {
|
|||
...data,
|
||||
};
|
||||
},
|
||||
apolloProvider,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -222,44 +202,5 @@ describe('the graph view selector component', () => {
|
|||
expect(findHoverTip().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pipeline insights', () => {
|
||||
it.each`
|
||||
isPipelineComplete | shouldShow
|
||||
${true} | ${true}
|
||||
${false} | ${false}
|
||||
`(
|
||||
'button should display $shouldShow if isPipelineComplete is $isPipelineComplete ',
|
||||
({ isPipelineComplete, shouldShow }) => {
|
||||
createComponent({
|
||||
props: {
|
||||
isPipelineComplete,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findPipelineInsightsBtn().exists()).toBe(shouldShow);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('tracking', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
|
||||
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('tracks performance insights button click', () => {
|
||||
findPipelineInsightsBtn().vm.$emit('click');
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_insights_button', {
|
||||
label: 'performance_insights',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1038,245 +1038,3 @@ export const triggerJob = {
|
|||
action: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const mockPerformanceInsightsResponse = {
|
||||
data: {
|
||||
project: {
|
||||
__typename: 'Project',
|
||||
id: 'gid://gitlab/Project/20',
|
||||
pipeline: {
|
||||
__typename: 'Pipeline',
|
||||
id: 'gid://gitlab/Ci::Pipeline/97',
|
||||
jobs: {
|
||||
__typename: 'CiJobConnection',
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo',
|
||||
hasNextPage: false,
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Bridge/2502',
|
||||
duration: null,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'success-2502-2502',
|
||||
detailsPath: '/root/lots-of-jobs-project/-/pipelines/98',
|
||||
},
|
||||
name: 'trigger_job',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/303',
|
||||
name: 'deploy',
|
||||
},
|
||||
startedAt: null,
|
||||
queuedDuration: 424850.376278,
|
||||
},
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Build/2501',
|
||||
duration: 10,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'success-2501-2501',
|
||||
detailsPath: '/root/ci-project/-/jobs/2501',
|
||||
},
|
||||
name: 'artifact_job',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/303',
|
||||
name: 'deploy',
|
||||
},
|
||||
startedAt: '2022-07-01T16:31:41Z',
|
||||
queuedDuration: 2.621553,
|
||||
},
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Build/2500',
|
||||
duration: 4,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'success-2500-2500',
|
||||
detailsPath: '/root/ci-project/-/jobs/2500',
|
||||
},
|
||||
name: 'coverage_job',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/302',
|
||||
name: 'test',
|
||||
},
|
||||
startedAt: '2022-07-01T16:31:33Z',
|
||||
queuedDuration: 14.388869,
|
||||
},
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Build/2499',
|
||||
duration: 4,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'success-2499-2499',
|
||||
detailsPath: '/root/ci-project/-/jobs/2499',
|
||||
},
|
||||
name: 'test_job_two',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/302',
|
||||
name: 'test',
|
||||
},
|
||||
startedAt: '2022-07-01T16:31:28Z',
|
||||
queuedDuration: 15.792664,
|
||||
},
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Build/2498',
|
||||
duration: 4,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'success-2498-2498',
|
||||
detailsPath: '/root/ci-project/-/jobs/2498',
|
||||
},
|
||||
name: 'test_job_one',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/302',
|
||||
name: 'test',
|
||||
},
|
||||
startedAt: '2022-07-01T16:31:17Z',
|
||||
queuedDuration: 8.317072,
|
||||
},
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Build/2497',
|
||||
duration: 5,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'failed-2497-2497',
|
||||
detailsPath: '/root/ci-project/-/jobs/2497',
|
||||
},
|
||||
name: 'allow_failure_test_job',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/302',
|
||||
name: 'test',
|
||||
},
|
||||
startedAt: '2022-07-01T16:31:22Z',
|
||||
queuedDuration: 3.547553,
|
||||
},
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Build/2496',
|
||||
duration: null,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'manual-2496-2496',
|
||||
detailsPath: '/root/ci-project/-/jobs/2496',
|
||||
},
|
||||
name: 'test_manual_job',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/302',
|
||||
name: 'test',
|
||||
},
|
||||
startedAt: null,
|
||||
queuedDuration: null,
|
||||
},
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Build/2495',
|
||||
duration: 5,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'success-2495-2495',
|
||||
detailsPath: '/root/ci-project/-/jobs/2495',
|
||||
},
|
||||
name: 'large_log_output',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/301',
|
||||
name: 'build',
|
||||
},
|
||||
startedAt: '2022-07-01T16:31:11Z',
|
||||
queuedDuration: 79.128625,
|
||||
},
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Build/2494',
|
||||
duration: 5,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'success-2494-2494',
|
||||
detailsPath: '/root/ci-project/-/jobs/2494',
|
||||
},
|
||||
name: 'build_job',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/301',
|
||||
name: 'build',
|
||||
},
|
||||
startedAt: '2022-07-01T16:31:05Z',
|
||||
queuedDuration: 73.286895,
|
||||
},
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Build/2493',
|
||||
duration: 16,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'success-2493-2493',
|
||||
detailsPath: '/root/ci-project/-/jobs/2493',
|
||||
},
|
||||
name: 'wait_job',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/301',
|
||||
name: 'build',
|
||||
},
|
||||
startedAt: '2022-07-01T16:30:48Z',
|
||||
queuedDuration: 56.258856,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockPerformanceInsightsNextPageResponse = {
|
||||
data: {
|
||||
project: {
|
||||
__typename: 'Project',
|
||||
id: 'gid://gitlab/Project/20',
|
||||
pipeline: {
|
||||
__typename: 'Pipeline',
|
||||
id: 'gid://gitlab/Ci::Pipeline/97',
|
||||
jobs: {
|
||||
__typename: 'CiJobConnection',
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo',
|
||||
hasNextPage: true,
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
__typename: 'CiJob',
|
||||
id: 'gid://gitlab/Ci::Bridge/2502',
|
||||
duration: null,
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'success-2502-2502',
|
||||
detailsPath: '/root/lots-of-jobs-project/-/pipelines/98',
|
||||
},
|
||||
name: 'trigger_job',
|
||||
stage: {
|
||||
__typename: 'CiStage',
|
||||
id: 'gid://gitlab/Ci::Stage/303',
|
||||
name: 'deploy',
|
||||
},
|
||||
startedAt: null,
|
||||
queuedDuration: 424850.376278,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
import { GlAlert, GlLink, GlModal } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import PerformanceInsightsModal from '~/pipelines/components/performance_insights_modal.vue';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import getPerformanceInsights from '~/pipelines/graphql/queries/get_performance_insights.query.graphql';
|
||||
import {
|
||||
mockPerformanceInsightsResponse,
|
||||
mockPerformanceInsightsNextPageResponse,
|
||||
} from './graph/mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('Performance insights modal', () => {
|
||||
let wrapper;
|
||||
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findLink = () => wrapper.findComponent(GlLink);
|
||||
const findLimitText = () => wrapper.findByTestId('limit-alert-text');
|
||||
const findQueuedCardData = () => wrapper.findByTestId('insights-queued-card-data');
|
||||
const findQueuedCardLink = () => wrapper.findByTestId('insights-queued-card-link');
|
||||
const findExecutedCardData = () => wrapper.findByTestId('insights-executed-card-data');
|
||||
const findExecutedCardLink = () => wrapper.findByTestId('insights-executed-card-link');
|
||||
const findSlowJobsStage = (index) => wrapper.findAllByTestId('insights-slow-job-stage').at(index);
|
||||
const findSlowJobsLink = (index) => wrapper.findAllByTestId('insights-slow-job-link').at(index);
|
||||
|
||||
const getPerformanceInsightsHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockPerformanceInsightsResponse);
|
||||
|
||||
const getPerformanceInsightsNextPageHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockPerformanceInsightsNextPageResponse);
|
||||
|
||||
const requestHandlers = [[getPerformanceInsights, getPerformanceInsightsHandler]];
|
||||
|
||||
const createComponent = (handlers = requestHandlers) => {
|
||||
wrapper = shallowMountExtended(PerformanceInsightsModal, {
|
||||
provide: {
|
||||
pipelineIid: '1',
|
||||
pipelineProjectPath: 'root/ci-project',
|
||||
},
|
||||
apolloProvider: createMockApollo(handlers),
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('without next page', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('displays modal', () => {
|
||||
expect(findModal().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays alert', () => {
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays feedback issue link', () => {
|
||||
expect(findLink().text()).toBe('Feedback issue');
|
||||
expect(findLink().attributes('href')).toBe(
|
||||
'https://gitlab.com/gitlab-org/gitlab/-/issues/365902',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not display limit text', () => {
|
||||
expect(findLimitText().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('queued duration card', () => {
|
||||
it('displays card data', () => {
|
||||
expect(trimText(findQueuedCardData().text())).toBe('4.9 days');
|
||||
});
|
||||
it('displays card link', () => {
|
||||
expect(findQueuedCardLink().attributes('href')).toBe(
|
||||
'/root/lots-of-jobs-project/-/pipelines/98',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('executed duration card', () => {
|
||||
it('displays card data', () => {
|
||||
expect(trimText(findExecutedCardData().text())).toBe('trigger_job');
|
||||
});
|
||||
it('displays card link', () => {
|
||||
expect(findExecutedCardLink().attributes('href')).toBe(
|
||||
'/root/lots-of-jobs-project/-/pipelines/98',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('slow jobs', () => {
|
||||
it.each`
|
||||
index | expectedStage | expectedName | expectedLink
|
||||
${0} | ${'build'} | ${'wait_job'} | ${'/root/ci-project/-/jobs/2493'}
|
||||
${1} | ${'deploy'} | ${'artifact_job'} | ${'/root/ci-project/-/jobs/2501'}
|
||||
${2} | ${'test'} | ${'allow_failure_test_job'} | ${'/root/ci-project/-/jobs/2497'}
|
||||
${3} | ${'build'} | ${'large_log_output'} | ${'/root/ci-project/-/jobs/2495'}
|
||||
${4} | ${'build'} | ${'build_job'} | ${'/root/ci-project/-/jobs/2494'}
|
||||
`(
|
||||
'should display slow job correctly',
|
||||
({ index, expectedStage, expectedName, expectedLink }) => {
|
||||
expect(findSlowJobsStage(index).text()).toBe(expectedStage);
|
||||
expect(findSlowJobsLink(index).text()).toBe(expectedName);
|
||||
expect(findSlowJobsLink(index).attributes('href')).toBe(expectedLink);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with next page', () => {
|
||||
it('displays limit text when there is a next page', async () => {
|
||||
createComponent([[getPerformanceInsights, getPerformanceInsightsNextPageHandler]]);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findLimitText().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,14 +8,10 @@ import {
|
|||
removeOrphanNodes,
|
||||
getMaxNodes,
|
||||
} from '~/pipelines/components/parsing_utils';
|
||||
import { createNodeDict, calculateJobStats, calculateSlowestFiveJobs } from '~/pipelines/utils';
|
||||
import { createNodeDict } from '~/pipelines/utils';
|
||||
|
||||
import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data';
|
||||
import {
|
||||
generateResponse,
|
||||
mockPipelineResponse,
|
||||
mockPerformanceInsightsResponse,
|
||||
} from './graph/mock_data';
|
||||
import { generateResponse, mockPipelineResponse } from './graph/mock_data';
|
||||
|
||||
describe('DAG visualization parsing utilities', () => {
|
||||
const nodeDict = createNodeDict(mockParsedGraphQLNodes);
|
||||
|
@ -162,40 +158,4 @@ describe('DAG visualization parsing utilities', () => {
|
|||
expect(columns).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('performance insights', () => {
|
||||
const {
|
||||
data: {
|
||||
project: {
|
||||
pipeline: { jobs },
|
||||
},
|
||||
},
|
||||
} = mockPerformanceInsightsResponse;
|
||||
|
||||
describe('calculateJobStats', () => {
|
||||
const expectedJob = jobs.nodes[0];
|
||||
|
||||
it('returns the job that spent this longest time queued', () => {
|
||||
expect(calculateJobStats(jobs, 'queuedDuration')).toEqual(expectedJob);
|
||||
});
|
||||
|
||||
it('returns the job that was executed last', () => {
|
||||
expect(calculateJobStats(jobs, 'startedAt')).toEqual(expectedJob);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateSlowestFiveJobs', () => {
|
||||
it('returns the slowest five jobs of the pipeline', () => {
|
||||
const expectedJobs = [
|
||||
jobs.nodes[9],
|
||||
jobs.nodes[1],
|
||||
jobs.nodes[5],
|
||||
jobs.nodes[7],
|
||||
jobs.nodes[8],
|
||||
];
|
||||
|
||||
expect(calculateSlowestFiveJobs(jobs)).toEqual(expectedJobs);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Types::Ci::JobType do
|
||||
include GraphqlHelpers
|
||||
|
||||
specify { expect(described_class.graphql_name).to eq('CiJob') }
|
||||
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Ci::Job) }
|
||||
|
||||
|
@ -45,8 +47,21 @@ RSpec.describe Types::Ci::JobType do
|
|||
tags
|
||||
triggered
|
||||
userPermissions
|
||||
webPath
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
end
|
||||
|
||||
describe '#web_path' do
|
||||
subject { resolve_field(:web_path, build, current_user: user, object_type: described_class) }
|
||||
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:build) { create(:ci_build, project: project, user: user) }
|
||||
|
||||
it 'returns the web path of the job' do
|
||||
is_expected.to eq("/#{project.full_path}/-/jobs/#{build.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['DeploymentDetails'] do
|
|||
|
||||
it 'has the expected fields' do
|
||||
expected_fields = %w[
|
||||
id iid ref tag sha created_at updated_at finished_at status
|
||||
id iid ref tag sha created_at updated_at finished_at status commit job triggerer
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Deployment'] do
|
|||
|
||||
it 'has the expected fields' do
|
||||
expected_fields = %w[
|
||||
id iid ref tag sha created_at updated_at finished_at status
|
||||
id iid ref tag sha created_at updated_at finished_at status commit job triggerer
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
|
|
@ -46,6 +46,10 @@ RSpec.describe ApplicationSettingsHelper do
|
|||
expect(helper.visible_attributes).to include(:deactivate_dormant_users)
|
||||
end
|
||||
|
||||
it 'contains :deactivate_dormant_users_period' do
|
||||
expect(helper.visible_attributes).to include(:deactivate_dormant_users_period)
|
||||
end
|
||||
|
||||
it 'contains rate limit parameters' do
|
||||
expect(helper.visible_attributes).to include(*%i(
|
||||
issues_create_limit notes_create_limit project_export_limit
|
||||
|
@ -63,6 +67,10 @@ RSpec.describe ApplicationSettingsHelper do
|
|||
it 'does not contain :deactivate_dormant_users' do
|
||||
expect(helper.visible_attributes).not_to include(:deactivate_dormant_users)
|
||||
end
|
||||
|
||||
it 'does not contain :deactivate_dormant_users_period' do
|
||||
expect(helper.visible_attributes).not_to include(:deactivate_dormant_users_period)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -98,6 +98,36 @@ RSpec.describe Gitlab::EncodingHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#encode_utf8_with_escaping!' do
|
||||
where(:input, :expected) do
|
||||
"abcd" | "abcd"
|
||||
"DzDzDz" | "DzDzDz"
|
||||
"\xC7\xB2\xC7DzDzDz" | "Dz%C7DzDzDz"
|
||||
"🐤🐤🐤🐤\xF0\x9F\x90" | "🐤🐤🐤🐤%F0%9F%90"
|
||||
"\xD0\x9F\xD1\x80 \x90" | "Пр %90"
|
||||
"\x41" | "A"
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'escapes invalid UTF-8' do
|
||||
expect(ext_class.encode_utf8_with_escaping!(input.dup.force_encoding(Encoding::ASCII_8BIT))).to eq(expected)
|
||||
expect(ext_class.encode_utf8_with_escaping!(input)).to eq(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(escape_gitaly_refs: false)
|
||||
end
|
||||
|
||||
it 'uses #encode! method' do
|
||||
expect(ext_class).to receive(:encode!).with('String')
|
||||
|
||||
ext_class.encode_utf8_with_escaping!('String')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#encode_utf8' do
|
||||
[
|
||||
["nil", nil, nil],
|
||||
|
|
|
@ -673,6 +673,61 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#search_files_by_name' do
|
||||
let(:ref) { 'master' }
|
||||
|
||||
subject(:result) { mutable_repository.search_files_by_name(query, ref) }
|
||||
|
||||
context 'when sending a valid name' do
|
||||
let(:query) { 'files/ruby/popen.rb' }
|
||||
|
||||
it 'returns matched files' do
|
||||
expect(result).to contain_exactly('files/ruby/popen.rb')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sending a name with space' do
|
||||
let(:query) { 'file with space.md' }
|
||||
|
||||
before do
|
||||
mutable_repository.multi_action(
|
||||
user,
|
||||
actions: [{ action: :create, file_path: "file with space.md", content: "Test content" }],
|
||||
branch_name: ref, message: "Test"
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns matched files' do
|
||||
expect(result).to contain_exactly('file with space.md')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sending a name with special ASCII characters' do
|
||||
let(:file_name) { 'Hello !@#$%^&*()' }
|
||||
let(:query) { file_name }
|
||||
|
||||
before do
|
||||
mutable_repository.multi_action(
|
||||
user,
|
||||
actions: [{ action: :create, file_path: file_name, content: "Test content" }],
|
||||
branch_name: ref, message: "Test"
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns matched files' do
|
||||
expect(result).to contain_exactly(file_name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sending a non-existing name' do
|
||||
let(:query) { 'please do not exist.md' }
|
||||
|
||||
it 'raises error' do
|
||||
expect(result).to eql([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_remote_root_ref' do
|
||||
it 'gets the remote root ref from GitalyClient' do
|
||||
expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
|
||||
|
|
|
@ -7,10 +7,18 @@ RSpec.describe Gitlab::Git do
|
|||
let(:committer_name) { 'John Doe' }
|
||||
|
||||
describe '.ref_name' do
|
||||
it 'ensure ref is a valid UTF-8 string' do
|
||||
utf8_invalid_ref = Gitlab::Git::BRANCH_REF_PREFIX + "an_invalid_ref_\xE5"
|
||||
let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + "an_invalid_ref_\xE5" }
|
||||
|
||||
expect(described_class.ref_name(utf8_invalid_ref)).to eq("an_invalid_ref_å")
|
||||
it 'ensure ref is a valid UTF-8 string' do
|
||||
expect(described_class.ref_name(ref)).to eq("an_invalid_ref_%E5")
|
||||
end
|
||||
|
||||
context 'when ref contains characters \x80 - \xFF' do
|
||||
let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + "\x90" }
|
||||
|
||||
it 'correctly converts it' do
|
||||
expect(described_class.ref_name(ref)).to eq("%90")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -819,3 +819,14 @@ service_desk_setting:
|
|||
approvals:
|
||||
- user
|
||||
- merge_request
|
||||
resource_milestone_events:
|
||||
- user
|
||||
- issue
|
||||
- merge_request
|
||||
- milestone
|
||||
resource_state_events:
|
||||
- user
|
||||
- issue
|
||||
- merge_request
|
||||
- source_merge_request
|
||||
- epic
|
||||
|
|
|
@ -192,10 +192,26 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
expect(Issue.find_by(title: 'Voluptatem').resource_label_events).not_to be_empty
|
||||
end
|
||||
|
||||
it 'restores issue resource milestone events' do
|
||||
expect(Issue.find_by(title: 'Voluptatem').resource_milestone_events).not_to be_empty
|
||||
end
|
||||
|
||||
it 'restores issue resource state events' do
|
||||
expect(Issue.find_by(title: 'Voluptatem').resource_state_events).not_to be_empty
|
||||
end
|
||||
|
||||
it 'restores merge requests resource label events' do
|
||||
expect(MergeRequest.find_by(title: 'MR1').resource_label_events).not_to be_empty
|
||||
end
|
||||
|
||||
it 'restores merge request resource milestone events' do
|
||||
expect(MergeRequest.find_by(title: 'MR1').resource_milestone_events).not_to be_empty
|
||||
end
|
||||
|
||||
it 'restores merge request resource state events' do
|
||||
expect(MergeRequest.find_by(title: 'MR1').resource_state_events).not_to be_empty
|
||||
end
|
||||
|
||||
it 'restores suggestion' do
|
||||
note = Note.find_by("note LIKE 'Saepe asperiores exercitationem non dignissimos laborum reiciendis et ipsum%'")
|
||||
|
||||
|
|
|
@ -917,3 +917,15 @@ Approval:
|
|||
- user_id
|
||||
- created_at
|
||||
- updated_at
|
||||
ResourceMilestoneEvent:
|
||||
- user_id
|
||||
- action
|
||||
- state
|
||||
- created_at
|
||||
ResourceStateEvent:
|
||||
- user_id
|
||||
- created_at
|
||||
- state
|
||||
- source_commit
|
||||
- close_after_error_tracking_resolve
|
||||
- close_auto_resolve_prometheus_alert
|
||||
|
|
|
@ -20,6 +20,7 @@ RSpec.describe Ci::Trigger do
|
|||
trigger = create(:ci_trigger_without_token, project: project)
|
||||
|
||||
expect(trigger.token).not_to be_nil
|
||||
expect(trigger.token).to start_with(Ci::Trigger::TRIGGER_TOKEN_PREFIX)
|
||||
end
|
||||
|
||||
it 'does not set a random token if one provided' do
|
||||
|
@ -30,12 +31,22 @@ RSpec.describe Ci::Trigger do
|
|||
end
|
||||
|
||||
describe '#short_token' do
|
||||
let(:trigger) { create(:ci_trigger, token: '12345678') }
|
||||
let(:trigger) { create(:ci_trigger) }
|
||||
|
||||
subject { trigger.short_token }
|
||||
|
||||
it 'returns shortened token' do
|
||||
is_expected.to eq('1234')
|
||||
it 'returns shortened token without prefix' do
|
||||
is_expected.not_to start_with(Ci::Trigger::TRIGGER_TOKEN_PREFIX)
|
||||
end
|
||||
|
||||
context 'token does not have a prefix' do
|
||||
before do
|
||||
trigger.token = '12345678'
|
||||
end
|
||||
|
||||
it 'returns shortened token' do
|
||||
is_expected.to eq('1234')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -897,6 +897,22 @@ RSpec.describe Deployment do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#build' do
|
||||
let!(:deployment) { create(:deployment) }
|
||||
|
||||
subject { deployment.build }
|
||||
|
||||
it 'retrieves build for the deployment' do
|
||||
is_expected.to eq(deployment.deployable)
|
||||
end
|
||||
|
||||
it 'returns nil when the associated build is not found' do
|
||||
deployment.update!(deployable_id: nil, deployable_type: nil)
|
||||
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#previous_deployment' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
|
|
|
@ -3810,8 +3810,8 @@ RSpec.describe User do
|
|||
describe '#can_be_deactivated?' do
|
||||
let(:activity) { {} }
|
||||
let(:user) { create(:user, name: 'John Smith', **activity) }
|
||||
let(:day_within_minium_inactive_days_threshold) { User::MINIMUM_INACTIVE_DAYS.pred.days.ago }
|
||||
let(:day_outside_minium_inactive_days_threshold) { User::MINIMUM_INACTIVE_DAYS.next.days.ago }
|
||||
let(:day_within_minium_inactive_days_threshold) { Gitlab::CurrentSettings.deactivate_dormant_users_period.pred.days.ago }
|
||||
let(:day_outside_minium_inactive_days_threshold) { Gitlab::CurrentSettings.deactivate_dormant_users_period.next.days.ago }
|
||||
|
||||
shared_examples 'not eligible for deactivation' do
|
||||
it 'returns false' do
|
||||
|
@ -7193,8 +7193,8 @@ RSpec.describe User do
|
|||
describe '.dormant' do
|
||||
it 'returns dormant users' do
|
||||
freeze_time do
|
||||
not_that_long_ago = (described_class::MINIMUM_INACTIVE_DAYS - 1).days.ago.to_date
|
||||
too_long_ago = described_class::MINIMUM_INACTIVE_DAYS.days.ago.to_date
|
||||
not_that_long_ago = (Gitlab::CurrentSettings.deactivate_dormant_users_period - 1).days.ago.to_date
|
||||
too_long_ago = Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date
|
||||
|
||||
create(:user, :deactivated, last_activity_on: too_long_ago)
|
||||
|
||||
|
@ -7214,8 +7214,8 @@ RSpec.describe User do
|
|||
describe '.with_no_activity' do
|
||||
it 'returns users with no activity' do
|
||||
freeze_time do
|
||||
active_not_that_long_ago = (described_class::MINIMUM_INACTIVE_DAYS - 1).days.ago.to_date
|
||||
active_too_long_ago = described_class::MINIMUM_INACTIVE_DAYS.days.ago.to_date
|
||||
active_not_that_long_ago = (Gitlab::CurrentSettings.deactivate_dormant_users_period - 1).days.ago.to_date
|
||||
active_too_long_ago = Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date
|
||||
created_recently = (described_class::MINIMUM_DAYS_CREATED - 1).days.ago.to_date
|
||||
created_not_recently = described_class::MINIMUM_DAYS_CREATED.days.ago.to_date
|
||||
|
||||
|
|
|
@ -295,6 +295,147 @@ RSpec.describe 'Environments Deployments query' do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'avoids N+1 database queries' do
|
||||
it 'does not increase the query count' do
|
||||
create_deployments
|
||||
|
||||
baseline = ActiveRecord::QueryRecorder.new do
|
||||
run_with_clean_state(query, context: { current_user: user })
|
||||
end
|
||||
|
||||
create_deployments
|
||||
|
||||
multi = ActiveRecord::QueryRecorder.new do
|
||||
run_with_clean_state(query, context: { current_user: user })
|
||||
end
|
||||
|
||||
expect(multi).not_to exceed_query_limit(baseline)
|
||||
end
|
||||
|
||||
def create_deployments
|
||||
create_list(:deployment, 3, environment: environment, project: project).each do |deployment|
|
||||
deployment.user = create(:user).tap { |u| project.add_developer(u) }
|
||||
deployment.deployable =
|
||||
create(:ci_build, project: project, environment: environment.name, deployment: deployment,
|
||||
user: deployment.user)
|
||||
|
||||
deployment.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when requesting commits of deployments' do
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
project(fullPath: "#{project.full_path}") {
|
||||
environment(name: "#{environment.name}") {
|
||||
deployments {
|
||||
nodes {
|
||||
iid
|
||||
commit {
|
||||
author {
|
||||
avatarUrl
|
||||
name
|
||||
webPath
|
||||
}
|
||||
fullTitle
|
||||
webPath
|
||||
sha
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'avoids N+1 database queries'
|
||||
|
||||
it 'returns commits of deployments' do
|
||||
deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
|
||||
|
||||
deployments.each do |deployment|
|
||||
deployment_in_record = project.deployments.find_by_iid(deployment['iid'])
|
||||
|
||||
expect(deployment_in_record.sha).to eq(deployment['commit']['sha'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when requesting triggerers of deployments' do
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
project(fullPath: "#{project.full_path}") {
|
||||
environment(name: "#{environment.name}") {
|
||||
deployments {
|
||||
nodes {
|
||||
iid
|
||||
triggerer {
|
||||
id
|
||||
avatarUrl
|
||||
name
|
||||
webPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'avoids N+1 database queries'
|
||||
|
||||
it 'returns triggerers of deployments' do
|
||||
deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
|
||||
|
||||
deployments.each do |deployment|
|
||||
deployment_in_record = project.deployments.find_by_iid(deployment['iid'])
|
||||
|
||||
expect(deployment_in_record.deployed_by.name).to eq(deployment['triggerer']['name'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when requesting jobs of deployments' do
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
project(fullPath: "#{project.full_path}") {
|
||||
environment(name: "#{environment.name}") {
|
||||
deployments {
|
||||
nodes {
|
||||
iid
|
||||
job {
|
||||
id
|
||||
status
|
||||
name
|
||||
webPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'avoids N+1 database queries'
|
||||
|
||||
it 'returns jobs of deployments' do
|
||||
deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
|
||||
|
||||
deployments.each do |deployment|
|
||||
deployment_in_record = project.deployments.find_by_iid(deployment['iid'])
|
||||
|
||||
expect(deployment_in_record.build.to_global_id.to_s).to eq(deployment['job']['id'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sorting and pagination' do
|
||||
let(:data_path) { [:project, :environment, :deployments] }
|
||||
let(:current_user) { user }
|
||||
|
|
|
@ -3238,7 +3238,7 @@ RSpec.describe API::Users do
|
|||
let(:user) { create(:user, **activity) }
|
||||
|
||||
context 'with no recent activity' do
|
||||
let(:activity) { { last_activity_on: ::User::MINIMUM_INACTIVE_DAYS.next.days.ago } }
|
||||
let(:activity) { { last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.next.days.ago } }
|
||||
|
||||
it 'deactivates an active user' do
|
||||
deactivate
|
||||
|
@ -3249,13 +3249,13 @@ RSpec.describe API::Users do
|
|||
end
|
||||
|
||||
context 'with recent activity' do
|
||||
let(:activity) { { last_activity_on: ::User::MINIMUM_INACTIVE_DAYS.pred.days.ago } }
|
||||
let(:activity) { { last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.pred.days.ago } }
|
||||
|
||||
it 'does not deactivate an active user' do
|
||||
deactivate
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(json_response['message']).to eq("403 Forbidden - The user you are trying to deactivate has been active in the past #{::User::MINIMUM_INACTIVE_DAYS} days and cannot be deactivated")
|
||||
expect(json_response['message']).to eq("403 Forbidden - The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated")
|
||||
expect(user.reload.state).to eq('active')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ RSpec.describe Users::DeactivateDormantUsersWorker do
|
|||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
describe '#perform' do
|
||||
let_it_be(:dormant) { create(:user, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date) }
|
||||
let_it_be(:dormant) { create(:user, last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date) }
|
||||
let_it_be(:inactive) { create(:user, last_activity_on: nil, created_at: User::MINIMUM_DAYS_CREATED.days.ago.to_date) }
|
||||
let_it_be(:inactive_recently_created) { create(:user, last_activity_on: nil, created_at: (User::MINIMUM_DAYS_CREATED - 1).days.ago.to_date) }
|
||||
|
||||
|
@ -14,7 +14,7 @@ RSpec.describe Users::DeactivateDormantUsersWorker do
|
|||
|
||||
it 'does not run for GitLab.com' do
|
||||
expect(Gitlab).to receive(:com?).and_return(true)
|
||||
expect(Gitlab::CurrentSettings).not_to receive(:current_application_settings)
|
||||
# Now makes a call to current settings to determine period of dormancy
|
||||
|
||||
worker.perform
|
||||
|
||||
|
@ -48,7 +48,7 @@ RSpec.describe Users::DeactivateDormantUsersWorker do
|
|||
end
|
||||
with_them do
|
||||
it 'deactivates certain user types' do
|
||||
user = create(:user, user_type: user_type, state: :active, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date)
|
||||
user = create(:user, user_type: user_type, state: :active, last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date)
|
||||
|
||||
worker.perform
|
||||
|
||||
|
@ -57,8 +57,8 @@ RSpec.describe Users::DeactivateDormantUsersWorker do
|
|||
end
|
||||
|
||||
it 'does not deactivate non-active users' do
|
||||
human_user = create(:user, user_type: :human, state: :blocked, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date)
|
||||
service_user = create(:user, user_type: :service_user, state: :blocked, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date)
|
||||
human_user = create(:user, user_type: :human, state: :blocked, last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date)
|
||||
service_user = create(:user, user_type: :service_user, state: :blocked, last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date)
|
||||
|
||||
worker.perform
|
||||
|
||||
|
|
Loading…
Reference in New Issue