Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
62098c11d1
commit
8bdfdd49b3
85 changed files with 1050 additions and 384 deletions
|
@ -38,6 +38,15 @@ rules:
|
|||
promise/always-return: off
|
||||
promise/no-callback-in-promise: off
|
||||
'@gitlab/no-global-event-off': error
|
||||
'@gitlab/vue-no-new-non-primitive-in-template':
|
||||
- error
|
||||
- allowNames:
|
||||
- 'class(es)?$'
|
||||
- '^style$'
|
||||
- '^to$'
|
||||
- '^$'
|
||||
- '^variables$'
|
||||
- 'attrs?$'
|
||||
no-param-reassign:
|
||||
- error
|
||||
- props: true
|
||||
|
|
|
@ -283,13 +283,17 @@ export default {
|
|||
<paginated-table-with-search-and-tabs
|
||||
:show-error-msg="showErrorMsg"
|
||||
:i18n="$options.i18n"
|
||||
:items="alerts.list || []"
|
||||
:items="
|
||||
alerts.list || [] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */
|
||||
"
|
||||
:page-info="alerts.pageInfo"
|
||||
:items-count="alertsCount"
|
||||
:status-tabs="$options.statusTabs"
|
||||
:track-views-options="$options.trackAlertListViewsOptions"
|
||||
:server-error-message="serverErrorMessage"
|
||||
:filter-search-tokens="['assignee_username']"
|
||||
:filter-search-tokens="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
|
||||
'assignee_username',
|
||||
] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
filter-search-key="alerts"
|
||||
@page-changed="pageChanged"
|
||||
@tabs-changed="statusChanged"
|
||||
|
@ -305,7 +309,11 @@ export default {
|
|||
<template #table>
|
||||
<gl-table
|
||||
class="alert-management-table"
|
||||
:items="alerts ? alerts.list : []"
|
||||
:items="
|
||||
alerts
|
||||
? alerts.list
|
||||
: [] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */
|
||||
"
|
||||
:fields="$options.fields"
|
||||
:show-empty="true"
|
||||
:busy="loading"
|
||||
|
|
|
@ -132,12 +132,12 @@ export default {
|
|||
v-else
|
||||
:option="options"
|
||||
:include-legend-avg-max="true"
|
||||
:data="[
|
||||
:data="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
|
||||
{
|
||||
name: $options.i18n.yAxisTitle,
|
||||
data: chartUserData,
|
||||
},
|
||||
]"
|
||||
] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -85,10 +85,11 @@ export default {
|
|||
:list="list"
|
||||
:data-draggable-item-type="$options.draggableItemTypes.list"
|
||||
:disabled="disabled"
|
||||
:class="{ 'gl-xs-display-none!': addColumnFormVisible }"
|
||||
/>
|
||||
|
||||
<transition name="slide" @after-enter="afterFormEnters">
|
||||
<board-add-new-column v-if="addColumnFormVisible" />
|
||||
<board-add-new-column v-if="addColumnFormVisible" class="gl-xs-w-full!" />
|
||||
</transition>
|
||||
</component>
|
||||
|
||||
|
|
|
@ -135,14 +135,14 @@ export default {
|
|||
:modal-id="$options.modalId"
|
||||
:title="$options.i18n.modalAction"
|
||||
size="sm"
|
||||
:action-primary="{
|
||||
:action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: $options.i18n.modalAction,
|
||||
attributes: [{ variant: 'danger' }],
|
||||
}"
|
||||
:action-secondary="{
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:action-secondary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: $options.i18n.modalCancel,
|
||||
attributes: [{ variant: 'default' }],
|
||||
}"
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
@primary="handleModalPrimary"
|
||||
>
|
||||
<p>{{ $options.i18n.modalCopy }}</p>
|
||||
|
|
|
@ -33,7 +33,7 @@ export default {
|
|||
class="issues-details-filters filtered-search-block gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row row-content-block second-block"
|
||||
>
|
||||
<div
|
||||
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-flex-grow-1 gl-lg-mb-0! mb-md-2 mb-sm-0 gl-w-full"
|
||||
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-flex-grow-1 gl-lg-mb-0 gl-mb-3 gl-w-full"
|
||||
>
|
||||
<boards-selector />
|
||||
<new-board-button />
|
||||
|
@ -41,7 +41,7 @@ export default {
|
|||
<issue-board-filtered-search v-else />
|
||||
</div>
|
||||
<div
|
||||
class="filter-dropdown-container gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-align-items-flex-start"
|
||||
class="filter-dropdown-container gl-md-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-align-items-flex-start"
|
||||
>
|
||||
<toggle-labels />
|
||||
<toggle-epics-swimlanes v-if="swimlanesFeatureAvailable && isSignedIn" />
|
||||
|
|
|
@ -107,7 +107,9 @@ export default {
|
|||
ref="modal"
|
||||
:modal-id="modalId"
|
||||
:title="__('Please solve the captcha')"
|
||||
:action-cancel="{ text: __('Cancel') }"
|
||||
:action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: __('Cancel'),
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
@shown="shown"
|
||||
@hide="hide"
|
||||
@hidden="$emit('hidden')"
|
||||
|
|
|
@ -212,7 +212,6 @@ export default {
|
|||
<template #table-header-actions>
|
||||
<div v-if="canRenderPipelineButton" class="gl-text-right">
|
||||
<gl-button
|
||||
variant="confirm"
|
||||
data-testid="run_pipeline_button"
|
||||
:loading="state.isRunningMergeRequestPipeline"
|
||||
@click="tryRunPipeline"
|
||||
|
|
|
@ -97,7 +97,9 @@ export default {
|
|||
:editor="tiptapEditor"
|
||||
plugin-key="bubbleMenuCodeBlock"
|
||||
:should-show="shouldShow"
|
||||
:tippy-options="{ getReferenceClientRect }"
|
||||
:tippy-options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
getReferenceClientRect,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
>
|
||||
<editor-state-observer @transaction="updateSelectedLanguage">
|
||||
<gl-button-group>
|
||||
|
|
|
@ -84,7 +84,9 @@ export default {
|
|||
content-type="link"
|
||||
icon-name="link"
|
||||
editor-command="toggleLink"
|
||||
:editor-command-params="{ href: '' }"
|
||||
:editor-command-params="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
href: '',
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
category="tertiary"
|
||||
size="medium"
|
||||
:label="__('Insert link')"
|
||||
|
|
|
@ -116,7 +116,9 @@ export default {
|
|||
:editor="tiptapEditor"
|
||||
plugin-key="bubbleMenuLink"
|
||||
:should-show="() => shouldShow()"
|
||||
:tippy-options="{ placement: 'bottom' }"
|
||||
:tippy-options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
placement: 'bottom',
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
>
|
||||
<editor-state-observer @transaction="updateLinkToState">
|
||||
<gl-button-group v-if="!isEditing" class="gl-display-flex gl-align-items-center">
|
||||
|
|
|
@ -124,7 +124,9 @@ export default {
|
|||
no-caret
|
||||
text-sr-only
|
||||
:text="$options.i18n.editTableActions"
|
||||
:popper-opts="{ positionFixed: true }"
|
||||
:popper-opts="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
positionFixed: true,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
@hide="handleHide($event)"
|
||||
>
|
||||
<gl-dropdown-item @click="runCommand('addColumnBefore')">
|
||||
|
|
|
@ -349,7 +349,9 @@ export default {
|
|||
class="gl-display-flex gl-overflow-hidden gl-flex-grow-1 gl-flex-direction-column gl-relative"
|
||||
>
|
||||
<design-destroyer
|
||||
:filenames="[design.filename]"
|
||||
:filenames="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
|
||||
design.filename,
|
||||
] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:project-path="projectPath"
|
||||
:iid="issueIid"
|
||||
@done="$router.push({ name: $options.DESIGNS_ROUTE_NAME })"
|
||||
|
|
|
@ -51,7 +51,7 @@ export default {
|
|||
__(
|
||||
'To preserve performance only %{strongStart}%{visible} of %{total}%{strongEnd} files are displayed.',
|
||||
),
|
||||
{ visible, total },
|
||||
{ visible, total } /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */,
|
||||
)
|
||||
"
|
||||
>
|
||||
|
|
|
@ -132,10 +132,10 @@ export default {
|
|||
|
||||
<design-note-pin
|
||||
v-if="canComment && currentCommentForm"
|
||||
:position="{
|
||||
:position="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
left: `${currentCommentForm.xPercent}%`,
|
||||
top: `${currentCommentForm.yPercent}%`,
|
||||
}"
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -54,7 +54,9 @@ export default {
|
|||
:key="`${tab.name}-${i}`"
|
||||
:active="tab.isActive"
|
||||
:title-item-class="tab.isActive ? 'gl-outline-none' : ''"
|
||||
:title-link-attributes="{ 'data-testid': `environments-tab-${tab.scope}` }"
|
||||
:title-link-attributes="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
'data-testid': `environments-tab-${tab.scope}`,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
@click="onChangeTab(tab.scope)"
|
||||
>
|
||||
<template #title>
|
||||
|
|
|
@ -216,7 +216,9 @@ export default {
|
|||
modal-id="ide-commit-error-modal"
|
||||
:title="lastCommitError.title"
|
||||
:action-primary="commitErrorPrimaryAction.button"
|
||||
:action-cancel="{ text: __('Cancel') }"
|
||||
:action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: __('Cancel'),
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
@ok="commitErrorPrimaryAction.callback"
|
||||
>
|
||||
<div v-safe-html="lastCommitError.messageHTML"></div>
|
||||
|
|
|
@ -338,7 +338,9 @@ export default {
|
|||
:show-items="showList"
|
||||
:show-error-msg="showErrorMsg"
|
||||
:i18n="$options.i18n"
|
||||
:items="incidents.list || []"
|
||||
:items="
|
||||
incidents.list || [] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */
|
||||
"
|
||||
:page-info="incidents.pageInfo"
|
||||
:items-count="incidentsCount"
|
||||
:status-tabs="$options.statusTabs"
|
||||
|
@ -372,7 +374,10 @@ export default {
|
|||
|
||||
<template #table>
|
||||
<gl-table
|
||||
:items="incidents.list || []"
|
||||
:items="
|
||||
incidents.list ||
|
||||
[] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */
|
||||
"
|
||||
:fields="availableFields"
|
||||
:busy="loading"
|
||||
stacked="md"
|
||||
|
|
|
@ -88,6 +88,11 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
usersLimitDataset: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -146,6 +151,18 @@ export default {
|
|||
isOnLearnGitlab() {
|
||||
return this.source === LEARN_GITLAB;
|
||||
},
|
||||
reachedLimit() {
|
||||
if (this.usersLimitDataset.freeUsersLimit && this.usersLimitDataset.membersCount) {
|
||||
return this.usersLimitDataset.membersCount >= this.usersLimitDataset.freeUsersLimit;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
formGroupDescription() {
|
||||
return this.reachedLimit
|
||||
? this.$options.labels.placeHolderDisabled
|
||||
: this.$options.labels.placeHolder;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
eventHub.$on('openModal', (options) => {
|
||||
|
@ -274,12 +291,15 @@ export default {
|
|||
:help-link="helpLink"
|
||||
:label-intro-text="labelIntroText"
|
||||
:label-search-field="$options.labels.searchField"
|
||||
:form-group-description="$options.labels.placeHolder"
|
||||
:form-group-description="formGroupDescription"
|
||||
:submit-disabled="inviteDisabled"
|
||||
:invalid-feedback-message="invalidFeedbackMessage"
|
||||
:is-loading="isLoading"
|
||||
:new-users-to-invite="newUsersToInvite"
|
||||
:root-group-id="rootId"
|
||||
:reached-limit="reachedLimit"
|
||||
:members-path="usersLimitDataset.membersPath"
|
||||
:purchase-path="usersLimitDataset.purchasePath"
|
||||
@reset="resetFields"
|
||||
@submit="sendInvite"
|
||||
@access-level="onAccessLevelUpdate"
|
||||
|
@ -294,7 +314,10 @@ export default {
|
|||
</template>
|
||||
|
||||
<template #user-limit-notification>
|
||||
<user-limit-notification />
|
||||
<user-limit-notification
|
||||
:reached-limit="reachedLimit"
|
||||
:users-limit-dataset="usersLimitDataset"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #select="{ validationState, labelId }">
|
||||
|
|
|
@ -8,7 +8,9 @@ import {
|
|||
GlLink,
|
||||
GlSprintf,
|
||||
GlFormInput,
|
||||
GlIcon,
|
||||
} from '@gitlab/ui';
|
||||
import Tracking from '~/tracking';
|
||||
import { sprintf } from '~/locale';
|
||||
import ContentTransition from '~/vue_shared/components/content_transition.vue';
|
||||
import {
|
||||
|
@ -16,8 +18,13 @@ import {
|
|||
ACCESS_EXPIRE_DATE,
|
||||
READ_MORE_TEXT,
|
||||
INVITE_BUTTON_TEXT,
|
||||
INVITE_BUTTON_TEXT_DISABLED,
|
||||
CANCEL_BUTTON_TEXT,
|
||||
CANCEL_BUTTON_TEXT_DISABLED,
|
||||
HEADER_CLOSE_LABEL,
|
||||
ON_SHOW_TRACK_LABEL,
|
||||
ON_CLOSE_TRACK_LABEL,
|
||||
ON_SUBMIT_TRACK_LABEL,
|
||||
} from '../constants';
|
||||
|
||||
const DEFAULT_SLOT = 'default';
|
||||
|
@ -41,8 +48,10 @@ export default {
|
|||
GlDropdownItem,
|
||||
GlSprintf,
|
||||
GlFormInput,
|
||||
GlIcon,
|
||||
ContentTransition,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
modalTitle: {
|
||||
|
@ -122,6 +131,21 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
reachedLimit: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
membersPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
purchasePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
// Be sure to check out reset!
|
||||
|
@ -151,20 +175,25 @@ export default {
|
|||
},
|
||||
actionPrimary() {
|
||||
return {
|
||||
text: this.submitButtonText,
|
||||
text: this.reachedLimit ? INVITE_BUTTON_TEXT_DISABLED : this.submitButtonText,
|
||||
attributes: {
|
||||
variant: 'confirm',
|
||||
disabled: this.submitDisabled,
|
||||
loading: this.isLoading,
|
||||
disabled: this.reachedLimit ? false : this.submitDisabled,
|
||||
loading: this.reachedLimit ? false : this.isLoading,
|
||||
'data-qa-selector': 'invite_button',
|
||||
...(this.reachedLimit && { href: this.membersPath }),
|
||||
},
|
||||
};
|
||||
},
|
||||
actionCancel() {
|
||||
return {
|
||||
text: this.cancelButtonText,
|
||||
text: this.reachedLimit ? CANCEL_BUTTON_TEXT_DISABLED : this.cancelButtonText,
|
||||
...(this.reachedLimit && { attributes: { href: this.purchasePath } }),
|
||||
};
|
||||
},
|
||||
selectLabelClass() {
|
||||
return `col-form-label ${this.reachedLimit ? 'gl-text-gray-500' : ''}`;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedAccessLevel: {
|
||||
|
@ -183,15 +212,24 @@ export default {
|
|||
|
||||
this.$emit('reset');
|
||||
},
|
||||
onShowModal() {
|
||||
if (this.reachedLimit) {
|
||||
this.track('render', { category: 'default', label: ON_SHOW_TRACK_LABEL });
|
||||
}
|
||||
},
|
||||
onCloseModal(e) {
|
||||
if (this.preventCancelDefault) {
|
||||
if (this.preventCancelDefault || this.reachedLimit) {
|
||||
e.preventDefault();
|
||||
} else {
|
||||
this.onReset();
|
||||
this.$refs.modal.hide();
|
||||
}
|
||||
|
||||
this.$emit('cancel');
|
||||
if (this.reachedLimit) {
|
||||
this.track('click_button', { category: 'default', label: ON_CLOSE_TRACK_LABEL });
|
||||
} else {
|
||||
this.$emit('cancel');
|
||||
}
|
||||
},
|
||||
changeSelectedItem(item) {
|
||||
this.selectedAccessLevel = item;
|
||||
|
@ -200,10 +238,14 @@ export default {
|
|||
// We never want to hide when submitting
|
||||
e.preventDefault();
|
||||
|
||||
this.$emit('submit', {
|
||||
accessLevel: this.selectedAccessLevel,
|
||||
expiresAt: this.selectedDate,
|
||||
});
|
||||
if (this.reachedLimit) {
|
||||
this.track('click_button', { category: 'default', label: ON_SUBMIT_TRACK_LABEL });
|
||||
} else {
|
||||
this.$emit('submit', {
|
||||
accessLevel: this.selectedAccessLevel,
|
||||
expiresAt: this.selectedDate,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
HEADER_CLOSE_LABEL,
|
||||
|
@ -227,6 +269,7 @@ export default {
|
|||
:header-close-label="$options.HEADER_CLOSE_LABEL"
|
||||
:action-primary="actionPrimary"
|
||||
:action-cancel="actionCancel"
|
||||
@shown="onShowModal"
|
||||
@primary="onSubmit"
|
||||
@cancel="onCloseModal"
|
||||
@hidden="onReset"
|
||||
|
@ -255,64 +298,73 @@ export default {
|
|||
<gl-form-group
|
||||
:invalid-feedback="invalidFeedbackMessage"
|
||||
:state="validationState"
|
||||
:description="formGroupDescription"
|
||||
data-testid="members-form-group"
|
||||
>
|
||||
<label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
|
||||
<slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
|
||||
<template #description>
|
||||
<gl-icon v-if="reachedLimit" name="lock" />
|
||||
{{ formGroupDescription }}
|
||||
</template>
|
||||
|
||||
<label :id="selectLabelId" :class="selectLabelClass">{{ labelSearchField }}</label>
|
||||
<gl-form-input v-if="reachedLimit" data-testid="disabled-input" disabled />
|
||||
<slot v-else name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
|
||||
</gl-form-group>
|
||||
|
||||
<label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
|
||||
<div class="gl-mt-2 gl-w-half gl-xs-w-full">
|
||||
<gl-dropdown
|
||||
class="gl-shadow-none gl-w-full"
|
||||
data-qa-selector="access_level_dropdown"
|
||||
v-bind="$attrs"
|
||||
:text="selectedRoleName"
|
||||
>
|
||||
<template v-for="(key, item) in accessLevels">
|
||||
<gl-dropdown-item
|
||||
:key="key"
|
||||
active-class="is-active"
|
||||
is-check-item
|
||||
:is-checked="key === selectedAccessLevel"
|
||||
@click="changeSelectedItem(key)"
|
||||
>
|
||||
<div>{{ item }}</div>
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
<template v-if="!reachedLimit">
|
||||
<label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
|
||||
|
||||
<div class="gl-mt-2 gl-w-half gl-xs-w-full">
|
||||
<gl-sprintf :message="$options.READ_MORE_TEXT">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
<div class="gl-mt-2 gl-w-half gl-xs-w-full">
|
||||
<gl-dropdown
|
||||
class="gl-shadow-none gl-w-full"
|
||||
data-qa-selector="access_level_dropdown"
|
||||
v-bind="$attrs"
|
||||
:text="selectedRoleName"
|
||||
>
|
||||
<template v-for="(key, item) in accessLevels">
|
||||
<gl-dropdown-item
|
||||
:key="key"
|
||||
active-class="is-active"
|
||||
is-check-item
|
||||
:is-checked="key === selectedAccessLevel"
|
||||
@click="changeSelectedItem(key)"
|
||||
>
|
||||
<div>{{ item }}</div>
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
|
||||
<label class="gl-mt-5 gl-display-block" for="expires_at">{{
|
||||
$options.ACCESS_EXPIRE_DATE
|
||||
}}</label>
|
||||
<div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
|
||||
<gl-datepicker
|
||||
v-model="selectedDate"
|
||||
class="gl-display-inline!"
|
||||
:min-date="minDate"
|
||||
:target="null"
|
||||
>
|
||||
<template #default="{ formattedDate }">
|
||||
<gl-form-input
|
||||
class="gl-w-full"
|
||||
:value="formattedDate"
|
||||
:placeholder="__(`YYYY-MM-DD`)"
|
||||
/>
|
||||
</template>
|
||||
</gl-datepicker>
|
||||
</div>
|
||||
<slot name="form-after"></slot>
|
||||
<div class="gl-mt-2 gl-w-half gl-xs-w-full">
|
||||
<gl-sprintf :message="$options.READ_MORE_TEXT">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
|
||||
<label class="gl-mt-5 gl-display-block" for="expires_at">{{
|
||||
$options.ACCESS_EXPIRE_DATE
|
||||
}}</label>
|
||||
<div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
|
||||
<gl-datepicker
|
||||
v-model="selectedDate"
|
||||
class="gl-display-inline!"
|
||||
:min-date="minDate"
|
||||
:target="null"
|
||||
>
|
||||
<template #default="{ formattedDate }">
|
||||
<gl-form-input
|
||||
class="gl-w-full"
|
||||
:value="formattedDate"
|
||||
:placeholder="__(`YYYY-MM-DD`)"
|
||||
/>
|
||||
</template>
|
||||
</gl-datepicker>
|
||||
</div>
|
||||
<slot name="form-after"></slot>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-for="{ key } in extraSlots" #[key]>
|
||||
<slot :name="key"></slot>
|
||||
</template>
|
||||
|
|
|
@ -134,10 +134,10 @@ export default {
|
|||
:hide-dropdown-with-no-items="hideDropdownWithNoItems"
|
||||
:placeholder="placeholderText"
|
||||
:aria-labelledby="ariaLabelledby"
|
||||
:text-input-attrs="{
|
||||
:text-input-attrs="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
'data-testid': 'members-token-select-input',
|
||||
'data-qa-selector': 'members_token_select_input',
|
||||
}"
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
@blur="handleBlur"
|
||||
@text-input="handleTextInput"
|
||||
@input="handleInput"
|
||||
|
|
|
@ -1,35 +1,50 @@
|
|||
<script>
|
||||
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { s__, n__, sprintf } from '~/locale';
|
||||
import { n__, sprintf } from '~/locale';
|
||||
|
||||
import {
|
||||
WARNING_ALERT_TITLE,
|
||||
DANGER_ALERT_TITLE,
|
||||
REACHED_LIMIT_MESSAGE,
|
||||
CLOSE_TO_LIMIT_MESSAGE,
|
||||
} from '../constants';
|
||||
|
||||
const CLOSE_TO_LIMIT_COUNT = 2;
|
||||
|
||||
const WARNING_ALERT_TITLE = s__(
|
||||
'InviteMembersModal|You only have space for %{count} more %{members} in %{name}',
|
||||
);
|
||||
|
||||
const DANGER_ALERT_TITLE = s__(
|
||||
"InviteMembersModal|You've reached your %{count} %{members} limit for %{name}",
|
||||
);
|
||||
|
||||
const CLOSE_TO_LIMIT_MESSAGE = s__(
|
||||
'InviteMembersModal|To get more members an owner of this namespace can %{trialLinkStart}start a trial%{trialLinkEnd} or %{upgradeLinkStart}upgrade%{upgradeLinkEnd} to a paid tier.',
|
||||
);
|
||||
|
||||
const REACHED_LIMIT_MESSAGE = s__(
|
||||
'InviteMembersModal|New members will be unable to participate. You can manage your members by removing ones you no longer need.',
|
||||
).concat(' ', CLOSE_TO_LIMIT_MESSAGE);
|
||||
|
||||
export default {
|
||||
name: 'UserLimitNotification',
|
||||
components: { GlAlert, GlSprintf, GlLink },
|
||||
inject: ['name', 'newTrialRegistrationPath', 'purchasePath', 'freeUsersLimit', 'membersCount'],
|
||||
inject: ['name'],
|
||||
props: {
|
||||
reachedLimit: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
usersLimitDataset: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
reachedLimit() {
|
||||
return this.isLimit();
|
||||
freeUsersLimit() {
|
||||
return this.usersLimitDataset.freeUsersLimit;
|
||||
},
|
||||
membersCount() {
|
||||
return this.usersLimitDataset.membersCount;
|
||||
},
|
||||
newTrialRegistrationPath() {
|
||||
return this.usersLimitDataset.newTrialRegistrationPath;
|
||||
},
|
||||
purchasePath() {
|
||||
return this.usersLimitDataset.purchasePath;
|
||||
},
|
||||
closeToLimit() {
|
||||
return this.isLimit(CLOSE_TO_LIMIT_COUNT);
|
||||
if (this.freeUsersLimit && this.membersCount) {
|
||||
return this.membersCount >= this.freeUsersLimit - CLOSE_TO_LIMIT_COUNT;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
warningAlertTitle() {
|
||||
return sprintf(WARNING_ALERT_TITLE, {
|
||||
|
@ -60,13 +75,6 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
isLimit(deviation = 0) {
|
||||
if (this.freeUsersLimit && this.membersCount) {
|
||||
return this.membersCount >= this.freeUsersLimit - deviation;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
pluralMembers(count) {
|
||||
return n__('member', 'members', count);
|
||||
},
|
||||
|
|
|
@ -35,8 +35,11 @@ export const MEMBERS_TO_PROJECT_DEFAULT_INTRO_TEXT = s__(
|
|||
export const MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT = s__(
|
||||
"InviteMembersModal|Congratulations on creating your project, you're almost there!",
|
||||
);
|
||||
export const MEMBERS_SEARCH_FIELD = s__('InviteMembersModal|GitLab member or email address');
|
||||
export const MEMBERS_SEARCH_FIELD = s__('InviteMembersModal|Username or email address');
|
||||
export const MEMBERS_PLACEHOLDER = s__('InviteMembersModal|Select members or type email addresses');
|
||||
export const MEMBERS_PLACEHOLDER_DISABLED = s__(
|
||||
'InviteMembersModal|This feature is disabled until this group has space for more members.',
|
||||
);
|
||||
export const MEMBERS_TASKS_TO_BE_DONE_TITLE = s__(
|
||||
'InviteMembersModal|Create issues for your new team member to work on (optional)',
|
||||
);
|
||||
|
@ -66,7 +69,9 @@ export const READ_MORE_TEXT = s__(
|
|||
`InviteMembersModal|%{linkStart}Read more%{linkEnd} about role permissions`,
|
||||
);
|
||||
export const INVITE_BUTTON_TEXT = s__('InviteMembersModal|Invite');
|
||||
export const INVITE_BUTTON_TEXT_DISABLED = s__('InviteMembersModal|Manage members');
|
||||
export const CANCEL_BUTTON_TEXT = s__('InviteMembersModal|Cancel');
|
||||
export const CANCEL_BUTTON_TEXT_DISABLED = s__('InviteMembersModal|Explore paid plans');
|
||||
export const HEADER_CLOSE_LABEL = s__('InviteMembersModal|Close invite team members');
|
||||
|
||||
export const MEMBER_MODAL_LABELS = {
|
||||
|
@ -94,6 +99,7 @@ export const MEMBER_MODAL_LABELS = {
|
|||
},
|
||||
searchField: MEMBERS_SEARCH_FIELD,
|
||||
placeHolder: MEMBERS_PLACEHOLDER,
|
||||
placeHolderDisabled: MEMBERS_PLACEHOLDER_DISABLED,
|
||||
tasksToBeDone: {
|
||||
title: MEMBERS_TASKS_TO_BE_DONE_TITLE,
|
||||
noProjects: MEMBERS_TASKS_TO_BE_DONE_NO_PROJECTS,
|
||||
|
@ -118,3 +124,19 @@ export const GROUP_MODAL_LABELS = {
|
|||
};
|
||||
|
||||
export const LEARN_GITLAB = 'learn_gitlab';
|
||||
export const ON_SHOW_TRACK_LABEL = 'locked_modal_viewed';
|
||||
export const ON_CLOSE_TRACK_LABEL = 'explore_paid_plans_clicked';
|
||||
export const ON_SUBMIT_TRACK_LABEL = 'manage_members_clicked';
|
||||
|
||||
export const WARNING_ALERT_TITLE = s__(
|
||||
'InviteMembersModal|You only have space for %{count} more %{members} in %{name}',
|
||||
);
|
||||
export const DANGER_ALERT_TITLE = s__(
|
||||
"InviteMembersModal|You've reached your %{count} %{members} limit for %{name}",
|
||||
);
|
||||
export const REACHED_LIMIT_MESSAGE = s__(
|
||||
'InviteMembersModal|You cannot add more members, but you can remove members who no longer need access. To get more members and access to additional paid features, an owner of this namespace can start a trial or upgrade to a paid tier.',
|
||||
);
|
||||
export const CLOSE_TO_LIMIT_MESSAGE = s__(
|
||||
'InviteMembersModal|To get more members an owner of this namespace can %{trialLinkStart}start a trial%{trialLinkEnd} or %{upgradeLinkStart}upgrade%{upgradeLinkEnd} to a paid tier.',
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { GlToast } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
|
||||
Vue.use(GlToast);
|
||||
|
||||
|
@ -26,10 +26,6 @@ export default (function initInviteMembersModal() {
|
|||
provide: {
|
||||
name: el.dataset.name,
|
||||
newProjectPath: el.dataset.newProjectPath,
|
||||
newTrialRegistrationPath: el.dataset.newTrialRegistrationPath,
|
||||
purchasePath: el.dataset.purchasePath,
|
||||
freeUsersLimit: el.dataset.freeUsersLimit && parseInt(el.dataset.freeUsersLimit, 10),
|
||||
membersCount: el.dataset.membersCount && parseInt(el.dataset.membersCount, 10),
|
||||
},
|
||||
render: (createElement) =>
|
||||
createElement(InviteMembersModal, {
|
||||
|
@ -42,6 +38,9 @@ export default (function initInviteMembersModal() {
|
|||
projects: JSON.parse(el.dataset.projects || '[]'),
|
||||
usersFilter: el.dataset.usersFilter,
|
||||
filterId: parseInt(el.dataset.filterId, 10),
|
||||
usersLimitDataset: convertObjectPropsToCamelCase(
|
||||
JSON.parse(el.dataset.usersLimitDataset || '{}'),
|
||||
),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -304,7 +304,10 @@ export default {
|
|||
:text="data.value || $options.currentUsername"
|
||||
class="w-100"
|
||||
:aria-label="
|
||||
sprintf($options.dropdownLabel, { jiraDisplayName: data.item.jiraDisplayName })
|
||||
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
|
||||
sprintf($options.dropdownLabel, {
|
||||
jiraDisplayName: data.item.jiraDisplayName,
|
||||
}) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
|
||||
"
|
||||
@hide="resetDropdown"
|
||||
>
|
||||
|
|
|
@ -54,7 +54,9 @@ export default {
|
|||
<gl-tab
|
||||
v-for="tab in tabs"
|
||||
:key="tab.text"
|
||||
:title-link-attributes="{ 'data-testid': tab.testId }"
|
||||
:title-link-attributes="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
'data-testid': tab.testId,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
@click="$emit('fetchJobsByStatus', tab.scope)"
|
||||
>
|
||||
<template #title>
|
||||
|
|
|
@ -492,7 +492,9 @@ export default {
|
|||
v-if="!groupSingleEmptyState(groupData.key)"
|
||||
:value="groupData.panels"
|
||||
group="metrics-dashboard"
|
||||
:component-data="{ attrs: { class: 'row mx-0 w-100' } }"
|
||||
:component-data="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
attrs: { class: 'row mx-0 w-100' },
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:disabled="!isRearrangingPanels"
|
||||
@input="updatePanels(groupData.key, $event)"
|
||||
>
|
||||
|
|
|
@ -83,11 +83,13 @@ export default {
|
|||
modal-id="delete-tag-modal"
|
||||
ok-variant="danger"
|
||||
size="sm"
|
||||
:action-primary="{
|
||||
:action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: __('Delete'),
|
||||
attributes: [{ variant: 'danger' }, { disabled: disablePrimaryButton }],
|
||||
}"
|
||||
:action-cancel="{ text: __('Cancel') }"
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: __('Cancel'),
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
@primary="$emit('confirmDelete')"
|
||||
@cancel="$emit('cancelDelete')"
|
||||
@change="projectPath = ''"
|
||||
|
|
|
@ -168,7 +168,9 @@ export default {
|
|||
<div>
|
||||
<persisted-search
|
||||
class="gl-mb-5"
|
||||
:sortable-fields="[$options.searchConfig.NAME_SORT_FIELD]"
|
||||
:sortable-fields="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
|
||||
$options.searchConfig.NAME_SORT_FIELD,
|
||||
] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:default-order="$options.searchConfig.NAME_SORT_FIELD.orderBy"
|
||||
default-sort="asc"
|
||||
@update="handleSearchUpdate"
|
||||
|
|
|
@ -370,7 +370,10 @@ export default {
|
|||
ref="deleteModal"
|
||||
size="sm"
|
||||
modal-id="delete-image-modal"
|
||||
:action-primary="{ text: __('Remove'), attributes: { variant: 'danger' } }"
|
||||
:action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: __('Remove'),
|
||||
attributes: { variant: 'danger' },
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
@primary="doDelete"
|
||||
@cancel="track('cancel_delete')"
|
||||
>
|
||||
|
|
|
@ -191,7 +191,10 @@ export default {
|
|||
<package-list-row
|
||||
v-for="v in packageEntity.versions"
|
||||
:key="v.id"
|
||||
:package-entity="{ name: packageEntity.name, ...v }"
|
||||
:package-entity="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
name: packageEntity.name,
|
||||
...v,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:package-link="v.id.toString()"
|
||||
:disable-delete="true"
|
||||
:show-package-type="false"
|
||||
|
|
|
@ -33,7 +33,7 @@ export default {
|
|||
<registry-search
|
||||
:filter="filter"
|
||||
:sorting="sorting"
|
||||
:tokens="[]"
|
||||
:tokens="[] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:sortable-fields="sortableFields"
|
||||
@sorting:changed="updateSorting"
|
||||
@filter:changed="setFilter"
|
||||
|
|
|
@ -21,9 +21,7 @@ const PANELS = [
|
|||
name: 'import-group-pane',
|
||||
selector: '#import-group-pane',
|
||||
title: s__('GroupsNew|Import group'),
|
||||
description: s__(
|
||||
'GroupsNew|Export groups with all their related data and move to a new GitLab instance.',
|
||||
),
|
||||
description: s__('GroupsNew|Import a group and related data from another GitLab instance.'),
|
||||
illustration: importGroupIllustration,
|
||||
details: 'Migrate your existing groups from another instance of GitLab.',
|
||||
},
|
||||
|
|
|
@ -46,7 +46,9 @@ export default {
|
|||
:value="mergedYaml"
|
||||
:file-name="ciConfigPath"
|
||||
:file-global-id="fileGlobalId"
|
||||
:editor-options="{ readOnly: true }"
|
||||
:editor-options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
readOnly: true,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -87,7 +87,9 @@ export default {
|
|||
<div v-if="pipelineStages.length > 0" class="stage-cell gl-mr-5">
|
||||
<linked-pipelines-mini-list
|
||||
v-if="upstreamPipeline"
|
||||
:triggered-by="[upstreamPipeline]"
|
||||
:triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
|
||||
upstreamPipeline,
|
||||
] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
data-testid="pipeline-editor-mini-graph-upstream"
|
||||
/>
|
||||
<pipeline-mini-graph class="gl-display-inline" :stages="pipelineStages" />
|
||||
|
|
|
@ -204,7 +204,9 @@ export default {
|
|||
<h3 class="gl-font-lg gl-text-gray-900 gl-mt-5">{{ $options.i18n.noWalkthroughTitle }}</h3>
|
||||
<p>{{ $options.i18n.noWalkthroughExplanation }}</p>
|
||||
<ci-templates
|
||||
:filter-templates="[$options.iOSTemplateName]"
|
||||
:filter-templates="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
|
||||
$options.iOSTemplateName,
|
||||
] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:disabled="!isRunnerSetupFinished"
|
||||
/>
|
||||
<p>
|
||||
|
|
|
@ -114,7 +114,9 @@ export default {
|
|||
variant="link"
|
||||
:aria-label="stageAriaLabel(stage.title)"
|
||||
:lazy="true"
|
||||
:popper-opts="{ placement: 'bottom' }"
|
||||
:popper-opts="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
placement: 'bottom',
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:toggle-class="['mini-pipeline-graph-dropdown-toggle', triggerButtonClass]"
|
||||
menu-class="mini-pipeline-graph-dropdown-menu"
|
||||
@show="onShowDropdown"
|
||||
|
|
|
@ -174,7 +174,9 @@ export default {
|
|||
<div></div>
|
||||
<linked-pipelines-mini-list
|
||||
v-if="item.triggered_by"
|
||||
:triggered-by="[item.triggered_by]"
|
||||
:triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
|
||||
item.triggered_by,
|
||||
] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
data-testid="mini-graph-upstream"
|
||||
/>
|
||||
<pipeline-mini-graph
|
||||
|
|
|
@ -126,7 +126,9 @@ export default {
|
|||
<div v-else>
|
||||
<linked-pipelines-mini-list
|
||||
v-if="upstreamPipeline"
|
||||
:triggered-by="[upstreamPipeline]"
|
||||
:triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
|
||||
upstreamPipeline,
|
||||
] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
data-testid="commit-box-mini-graph-upstream"
|
||||
/>
|
||||
|
||||
|
|
|
@ -195,7 +195,10 @@ export default {
|
|||
:path-id-separator="pathIdSeparator"
|
||||
:input-value="inputValue"
|
||||
:auto-complete-sources="transformedAutocompleteSources"
|
||||
:auto-complete-options="{ issues: autoCompleteIssues, epics: autoCompleteEpics }"
|
||||
:auto-complete-options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
issues: autoCompleteIssues,
|
||||
epics: autoCompleteEpics,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:issuable-type="issuableType"
|
||||
@pendingIssuableRemoveRequest="onPendingIssuableRemoveRequest"
|
||||
@formCancel="onFormCancel"
|
||||
|
|
|
@ -115,14 +115,14 @@ export default {
|
|||
<gl-modal
|
||||
size="sm"
|
||||
:modal-id="$options.modalId"
|
||||
:action-primary="{
|
||||
:action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: $options.i18n.modalAction,
|
||||
attributes: [{ variant: 'danger' }],
|
||||
}"
|
||||
:action-secondary="{
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:action-secondary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: $options.i18n.modalCancel,
|
||||
attributes: [{ variant: 'default' }],
|
||||
}"
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:title="$options.i18n.modalTitle"
|
||||
@primary="handleModalPrimary"
|
||||
>
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
PARAM_KEY_BEFORE,
|
||||
DEFAULT_SORT,
|
||||
RUNNER_PAGE_SIZE,
|
||||
STATUS_NEVER_CONTACTED,
|
||||
} from './constants';
|
||||
import { getPaginationVariables } from './utils';
|
||||
|
||||
|
@ -84,7 +83,6 @@ const getPaginationFromParams = (params) => {
|
|||
};
|
||||
|
||||
// Outdated URL parameters
|
||||
const STATUS_NOT_CONNECTED = 'NOT_CONNECTED';
|
||||
const STATUS_ACTIVE = 'ACTIVE';
|
||||
const STATUS_PAUSED = 'PAUSED';
|
||||
|
||||
|
@ -116,10 +114,6 @@ export const updateOutdatedUrl = (url = window.location.href) => {
|
|||
const status = params[PARAM_KEY_STATUS]?.[0] || null;
|
||||
|
||||
switch (status) {
|
||||
case STATUS_NOT_CONNECTED:
|
||||
return updateUrlParams(url, {
|
||||
[PARAM_KEY_STATUS]: [STATUS_NEVER_CONTACTED],
|
||||
});
|
||||
case STATUS_ACTIVE:
|
||||
return updateUrlParams(url, {
|
||||
[PARAM_KEY_PAUSED]: ['false'],
|
||||
|
|
|
@ -132,7 +132,7 @@ export default {
|
|||
<gl-area-chart
|
||||
ref="areaChart"
|
||||
v-bind="$attrs"
|
||||
:data="[]"
|
||||
:data="[] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:option="chartOptions"
|
||||
:format-tooltip-text="formatTooltipText"
|
||||
:width="width"
|
||||
|
|
|
@ -167,7 +167,9 @@ export default {
|
|||
:content="editableContent"
|
||||
:initial-edit-type="editorMode"
|
||||
:image-root="imageRoot"
|
||||
:options="{ customRenderers }"
|
||||
:options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
customRenderers,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
class="mb-9 pb-6 h-100"
|
||||
@modeChange="onModeChange"
|
||||
@input="onInputChange"
|
||||
|
|
|
@ -311,7 +311,10 @@ export default {
|
|||
data-testid="extension-list-item"
|
||||
>
|
||||
<gl-intersection-observer
|
||||
:options="{ rootMargin: '100px', thresholds: 0.1 }"
|
||||
:options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
rootMargin: '100px',
|
||||
thresholds: 0.1,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
class="gl-w-full"
|
||||
@appear="appear(index)"
|
||||
@disappear="disappear(index)"
|
||||
|
|
|
@ -328,7 +328,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vue-filtered-search-bar-container d-md-flex">
|
||||
<div class="vue-filtered-search-bar-container gl-md-display-flex">
|
||||
<gl-form-checkbox
|
||||
v-if="showCheckbox"
|
||||
class="gl-align-self-center"
|
||||
|
|
|
@ -211,10 +211,22 @@ export default {
|
|||
@select="handleTokenValueSelected"
|
||||
>
|
||||
<template #view-token="viewTokenProps">
|
||||
<slot name="view-token" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
|
||||
<slot
|
||||
name="view-token"
|
||||
:view-token-props="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
...viewTokenProps,
|
||||
activeTokenValue,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
></slot>
|
||||
</template>
|
||||
<template #view="viewTokenProps">
|
||||
<slot name="view" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
|
||||
<slot
|
||||
name="view"
|
||||
:view-token-props="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
...viewTokenProps,
|
||||
activeTokenValue,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
></slot>
|
||||
</template>
|
||||
<template v-if="suggestionsEnabled" #suggestions>
|
||||
<template v-if="showDefaultSuggestions">
|
||||
|
|
|
@ -198,7 +198,10 @@ export default {
|
|||
<toolbar-button
|
||||
tag="**"
|
||||
:button-title="
|
||||
sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { modifierKey })
|
||||
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
|
||||
sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), {
|
||||
modifierKey,
|
||||
}) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
|
||||
"
|
||||
:shortcuts="$options.shortcuts.bold"
|
||||
icon="bold"
|
||||
|
@ -206,7 +209,10 @@ export default {
|
|||
<toolbar-button
|
||||
tag="_"
|
||||
:button-title="
|
||||
sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { modifierKey })
|
||||
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
|
||||
sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), {
|
||||
modifierKey,
|
||||
}) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
|
||||
"
|
||||
:shortcuts="$options.shortcuts.italic"
|
||||
icon="italic"
|
||||
|
@ -215,8 +221,9 @@ export default {
|
|||
v-if="!restrictedToolBarItems.includes('strikethrough')"
|
||||
tag="~~"
|
||||
:button-title="
|
||||
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
|
||||
sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)'), {
|
||||
modifierKey,
|
||||
modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
|
||||
})
|
||||
"
|
||||
:shortcuts="$options.shortcuts.strikethrough"
|
||||
|
@ -273,7 +280,10 @@ export default {
|
|||
tag="[{text}](url)"
|
||||
tag-select="url"
|
||||
:button-title="
|
||||
sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { modifierKey })
|
||||
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
|
||||
sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), {
|
||||
modifierKey,
|
||||
}) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
|
||||
"
|
||||
:shortcuts="$options.shortcuts.link"
|
||||
icon="link"
|
||||
|
|
|
@ -90,7 +90,9 @@ export default {
|
|||
modal-id="upload-metric-modal"
|
||||
size="sm"
|
||||
:action-primary="actionPrimaryProps"
|
||||
:action-cancel="{ text: $options.i18n.modalCancel }"
|
||||
:action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: $options.i18n.modalCancel,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:title="$options.i18n.modalTitle"
|
||||
:visible="modalVisible"
|
||||
@hidden="clearInputs"
|
||||
|
|
|
@ -159,7 +159,9 @@ export default {
|
|||
size="sm"
|
||||
:visible="modalVisible"
|
||||
:action-primary="deleteActionPrimaryProps"
|
||||
:action-cancel="{ text: $options.i18n.modalCancel }"
|
||||
:action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: $options.i18n.modalCancel,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
@primary.prevent="onDelete"
|
||||
@hidden="resetEditFields"
|
||||
>
|
||||
|
@ -177,7 +179,9 @@ export default {
|
|||
modal-id="edit-metric-modal"
|
||||
size="sm"
|
||||
:action-primary="updateActionPrimaryProps"
|
||||
:action-cancel="{ text: $options.i18n.modalCancel }"
|
||||
:action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
text: $options.i18n.modalCancel,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:visible="editModalVisible"
|
||||
data-testid="metric-image-edit-modal"
|
||||
@hidden="resetEditFields"
|
||||
|
|
|
@ -61,7 +61,9 @@ export default {
|
|||
v-for="(tab, i) in tabs"
|
||||
:key="i"
|
||||
:title-link-class="`js-${scope}-tab-${tab.scope} gl-display-inline-flex`"
|
||||
:title-link-attributes="{ 'data-testid': `${scope}-tab-${tab.scope}` }"
|
||||
:title-link-attributes="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
|
||||
'data-testid': `${scope}-tab-${tab.scope}`,
|
||||
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
:active="tab.isActive"
|
||||
@click="onTabClick(tab)"
|
||||
>
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.dropdown-menu-toggle,
|
||||
.dropdown-menu-toggle.dropdown-menu-toggle,
|
||||
.update-issues-btn .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -738,7 +738,7 @@ body {
|
|||
}
|
||||
}
|
||||
@media (max-width: 767.98px) {
|
||||
.dropdown-menu-toggle {
|
||||
.dropdown-menu-toggle.dropdown-menu-toggle {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -723,7 +723,7 @@ body {
|
|||
}
|
||||
}
|
||||
@media (max-width: 767.98px) {
|
||||
.dropdown-menu-toggle {
|
||||
.dropdown-menu-toggle.dropdown-menu-toggle {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ module Resolvers
|
|||
}).freeze
|
||||
|
||||
def ready?(**args)
|
||||
# TODO remove when cleaning up packages_graphql_pipelines_resolver
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/358432
|
||||
context.scoped_set!(:packages_access_level, :group)
|
||||
|
||||
context[self.class] ||= { executions: 0 }
|
||||
context[self.class][:executions] += 1
|
||||
raise GraphQL::ExecutionError, "Packages can be requested only for one group at a time" if context[self.class][:executions] > 1
|
||||
|
@ -26,7 +30,10 @@ module Resolvers
|
|||
def resolve(sort:, **filters)
|
||||
return unless packages_available?
|
||||
|
||||
::Packages::GroupPackagesFinder.new(current_user, object, filters.merge(GROUP_SORT_TO_PARAMS_MAP.fetch(sort))).execute
|
||||
params = filters.merge(GROUP_SORT_TO_PARAMS_MAP.fetch(sort))
|
||||
params[:preload_pipelines] = false
|
||||
|
||||
::Packages::GroupPackagesFinder.new(current_user, object, params).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,86 @@ module Resolvers
|
|||
|
||||
alias_method :package, :object
|
||||
|
||||
# this resolver can be called for 100 packages max and we want to limit the
|
||||
# number of build infos returned for _each_ package when using the new finder.
|
||||
MAX_PAGE_SIZE = 20
|
||||
|
||||
def resolve(first: nil, last: nil, after: nil, before: nil, lookahead:)
|
||||
case detect_mode
|
||||
when :object_field
|
||||
package.pipelines
|
||||
when :new_finder
|
||||
resolve_with_new_finder(first: first, last: last, after: after, before: before, lookahead: lookahead)
|
||||
else
|
||||
resolve_with_old_finder(first: first, last: last, after: after, before: before, lookahead: lookahead)
|
||||
end
|
||||
end
|
||||
|
||||
# we manage the pagination manually, so opt out of the connection field extension
|
||||
def self.field_options
|
||||
super.merge(
|
||||
connection: false,
|
||||
extras: [:lookahead]
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# TODO remove when cleaning up packages_graphql_pipelines_resolver
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/358432
|
||||
def detect_mode
|
||||
return :new_finder if Feature.enabled?(:packages_graphql_pipelines_resolver, default_enabled: :yaml)
|
||||
return :object_field if context[:packages_access_level] == :group || context[:packages_access_level] == :project
|
||||
|
||||
:old_finder
|
||||
end
|
||||
|
||||
# This returns a promise for a connection of promises for pipelines:
|
||||
# Lazy[Connection[Lazy[Pipeline]]] structure
|
||||
# TODO rename to #resolve when cleaning up packages_graphql_pipelines_resolver
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/358432
|
||||
def resolve_with_new_finder(first:, last:, after:, before:, lookahead:)
|
||||
default_value = default_value_for(first: first, last: last, after: after, before: before)
|
||||
BatchLoader::GraphQL.for(package.id)
|
||||
.batch(default_value: default_value) do |package_ids, loader|
|
||||
build_infos = ::Packages::BuildInfosForManyPackagesFinder.new(
|
||||
package_ids,
|
||||
first: first,
|
||||
last: last,
|
||||
after: decode_cursor(after),
|
||||
before: decode_cursor(before),
|
||||
max_page_size: MAX_PAGE_SIZE,
|
||||
support_next_page: lookahead.selects?(:page_info)
|
||||
).execute
|
||||
|
||||
build_infos.each do |build_info|
|
||||
loader.call(build_info.package_id) do |connection|
|
||||
connection.items << lazy_load_pipeline(build_info.pipeline_id)
|
||||
connection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def lazy_load_pipeline(id)
|
||||
::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, id)
|
||||
.find
|
||||
end
|
||||
|
||||
def default_value_for(first:, last:, after:, before:)
|
||||
Gitlab::Graphql::Pagination::ActiveRecordArrayConnection.new(
|
||||
[],
|
||||
first: first,
|
||||
last: last,
|
||||
after: after,
|
||||
before: before,
|
||||
max_page_size: MAX_PAGE_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
# TODO remove when cleaning up packages_graphql_pipelines_resolver
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/358432
|
||||
def resolve_with_old_finder(first:, last:, after:, before:, lookahead:)
|
||||
finder = ::Packages::BuildInfosFinder.new(
|
||||
package,
|
||||
first: first,
|
||||
|
@ -29,16 +108,6 @@ module Resolvers
|
|||
::Ci::Pipeline.id_in(build_infos.pluck_pipeline_ids)
|
||||
end
|
||||
|
||||
# we manage the pagination manually, so opt out of the connection field extension
|
||||
def self.field_options
|
||||
super.merge(
|
||||
connection: false,
|
||||
extras: [:lookahead]
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def decode_cursor(encoded)
|
||||
return unless encoded
|
||||
|
||||
|
|
|
@ -5,10 +5,21 @@ module Resolvers
|
|||
class ProjectPackagesResolver < PackagesBaseResolver
|
||||
# The GraphQL type is defined in the extended class
|
||||
|
||||
# TODO remove when cleaning up packages_graphql_pipelines_resolver
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/358432
|
||||
def ready?(**args)
|
||||
context.scoped_set!(:packages_access_level, :project)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def resolve(sort:, **filters)
|
||||
return unless packages_available?
|
||||
|
||||
::Packages::PackagesFinder.new(object, filters.merge(SORT_TO_PARAMS_MAP.fetch(sort))).execute
|
||||
params = filters.merge(SORT_TO_PARAMS_MAP.fetch(sort))
|
||||
params[:preload_pipelines] = false
|
||||
|
||||
::Packages::PackagesFinder.new(object, params).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,13 +17,6 @@ module Types
|
|||
|
||||
field :dependency_links, Types::Packages::PackageDependencyLinkType.connection_type, null: true, description: 'Dependency link.'
|
||||
|
||||
# this is an override of Types::Packages::PackageType.pipelines
|
||||
# in order to use a custom resolver: Resolvers::PackagePipelinesResolver
|
||||
field :pipelines,
|
||||
resolver: Resolvers::PackagePipelinesResolver,
|
||||
description: 'Pipelines that built the package.',
|
||||
deprecated: { reason: 'Due to scalability concerns, this field is going to be removed', milestone: '14.6' }
|
||||
|
||||
field :composer_config_repository_url, GraphQL::Types::String, null: true, description: 'Url of the Composer setup endpoint.'
|
||||
field :composer_url, GraphQL::Types::String, null: true, description: 'Url of the Composer endpoint.'
|
||||
field :conan_url, GraphQL::Types::String, null: true, description: 'Url of the Conan project endpoint.'
|
||||
|
|
|
@ -19,9 +19,9 @@ module Types
|
|||
description: 'Package metadata.'
|
||||
field :name, GraphQL::Types::String, null: false, description: 'Name of the package.'
|
||||
field :package_type, Types::Packages::PackageTypeEnum, null: false, description: 'Package type.'
|
||||
field :pipelines, Types::Ci::PipelineType.connection_type, null: true,
|
||||
description: 'Pipelines that built the package.',
|
||||
deprecated: { reason: 'Due to scalability concerns, this field is going to be removed', milestone: '14.6' }
|
||||
field :pipelines,
|
||||
resolver: Resolvers::PackagePipelinesResolver,
|
||||
description: "Pipelines that built the package. Max page size #{Resolvers::PackagePipelinesResolver::MAX_PAGE_SIZE}."
|
||||
field :project, Types::ProjectType, null: false, description: 'Project where the package is stored.'
|
||||
field :status, Types::Packages::PackageStatusEnum, null: false, description: 'Package status.'
|
||||
field :tags, Types::Packages::PackageTagType.connection_type, null: true, description: 'Package tags.'
|
||||
|
|
|
@ -14,11 +14,21 @@ module Integrations
|
|||
# - https://gitlab.com/gitlab-org/slack-notifier#middleware
|
||||
# - https://gitlab.com/gitlab-org/gitlab/-/issues/347048
|
||||
notifier = ::Slack::Messenger.new(webhook, opts.merge(http_client: HTTPClient))
|
||||
notifier.ping(
|
||||
responses = notifier.ping(
|
||||
message.pretext,
|
||||
attachments: message.attachments,
|
||||
fallback: message.fallback
|
||||
)
|
||||
|
||||
responses.each do |response|
|
||||
unless response.success?
|
||||
log_error('SlackMattermostNotifier HTTP error response',
|
||||
request_host: response.request.uri.host,
|
||||
response_code: response.code,
|
||||
response_body: response.body
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class HTTPClient
|
||||
|
|
|
@ -462,7 +462,7 @@ class ContainerRepository < ApplicationRecord
|
|||
end
|
||||
|
||||
def start_expiration_policy!
|
||||
update!(expiration_policy_started_at: Time.zone.now)
|
||||
update!(expiration_policy_started_at: Time.zone.now, last_cleanup_deleted_tags_count: nil)
|
||||
end
|
||||
|
||||
def size
|
||||
|
|
|
@ -7,6 +7,6 @@ class Packages::BuildInfo < ApplicationRecord
|
|||
scope :pluck_pipeline_ids, -> { pluck(:pipeline_id) }
|
||||
scope :without_empty_pipelines, -> { where.not(pipeline_id: nil) }
|
||||
scope :order_by_pipeline_id, -> (direction) { order(pipeline_id: direction) }
|
||||
scope :with_pipeline_id_less_than, -> (pipeline_id) { where("pipeline_id < ?", pipeline_id) }
|
||||
scope :with_pipeline_id_greater_than, -> (pipeline_id) { where("pipeline_id > ?", pipeline_id) }
|
||||
scope :with_pipeline_id_less_than, -> (pipeline_id) { where("#{table_name}.pipeline_id < ?", pipeline_id) }
|
||||
scope :with_pipeline_id_greater_than, -> (pipeline_id) { where("#{table_name}.pipeline_id > ?", pipeline_id) }
|
||||
end
|
||||
|
|
|
@ -35,7 +35,8 @@ module ContainerExpirationPolicies
|
|||
if service_result[:status] == :success
|
||||
repository.update!(
|
||||
expiration_policy_cleanup_status: :cleanup_unscheduled,
|
||||
expiration_policy_completed_at: Time.zone.now
|
||||
expiration_policy_completed_at: Time.zone.now,
|
||||
last_cleanup_deleted_tags_count: service_result[:deleted_size]
|
||||
)
|
||||
|
||||
success(:finished, service_result)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: packages_graphql_pipelines_resolver
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82496
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/358432
|
||||
milestone: '14.10'
|
||||
type: development
|
||||
group: group::package
|
||||
default_enabled: false
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddLastCleanupDeletedTagsCountToContainerRepository < Gitlab::Database::Migration[2.0]
|
||||
def change
|
||||
add_column :container_repositories, :last_cleanup_deleted_tags_count, :integer
|
||||
end
|
||||
end
|
1
db/schema_migrations/20220421180321
Normal file
1
db/schema_migrations/20220421180321
Normal file
|
@ -0,0 +1 @@
|
|||
71cde7610713f9e2e21f87a2176cc4ec5fdc797021edab144adfaaf463acb8ef
|
|
@ -13783,6 +13783,7 @@ CREATE TABLE container_repositories (
|
|||
migration_state text DEFAULT 'default'::text NOT NULL,
|
||||
migration_aborted_in_state text,
|
||||
migration_plan text,
|
||||
last_cleanup_deleted_tags_count integer,
|
||||
CONSTRAINT check_05e9012f36 CHECK ((char_length(migration_plan) <= 255)),
|
||||
CONSTRAINT check_13c58fe73a CHECK ((char_length(migration_state) <= 255)),
|
||||
CONSTRAINT check_97f0249439 CHECK ((char_length(migration_aborted_in_state) <= 255))
|
||||
|
|
|
@ -13928,7 +13928,7 @@ Represents a package in the Package Registry. Note that this type is in beta and
|
|||
| <a id="packagemetadata"></a>`metadata` | [`PackageMetadata`](#packagemetadata) | Package metadata. |
|
||||
| <a id="packagename"></a>`name` | [`String!`](#string) | Name of the package. |
|
||||
| <a id="packagepackagetype"></a>`packageType` | [`PackageTypeEnum!`](#packagetypeenum) | Package type. |
|
||||
| <a id="packagepipelines"></a>`pipelines` **{warning-solid}** | [`PipelineConnection`](#pipelineconnection) | **Deprecated** in 14.6. Due to scalability concerns, this field is going to be removed. |
|
||||
| <a id="packagepipelines"></a>`pipelines` | [`PipelineConnection`](#pipelineconnection) | Pipelines that built the package. Max page size 20. (see [Connections](#connections)) |
|
||||
| <a id="packageproject"></a>`project` | [`Project!`](#project) | Project where the package is stored. |
|
||||
| <a id="packagestatus"></a>`status` | [`PackageStatus!`](#packagestatus) | Package status. |
|
||||
| <a id="packagetags"></a>`tags` | [`PackageTagConnection`](#packagetagconnection) | Package tags. (see [Connections](#connections)) |
|
||||
|
@ -13995,7 +13995,7 @@ Represents a package details in the Package Registry. Note that this type is in
|
|||
| <a id="packagedetailstypenugeturl"></a>`nugetUrl` | [`String`](#string) | Url of the Nuget project endpoint. |
|
||||
| <a id="packagedetailstypepackagefiles"></a>`packageFiles` | [`PackageFileConnection`](#packagefileconnection) | Package files. (see [Connections](#connections)) |
|
||||
| <a id="packagedetailstypepackagetype"></a>`packageType` | [`PackageTypeEnum!`](#packagetypeenum) | Package type. |
|
||||
| <a id="packagedetailstypepipelines"></a>`pipelines` **{warning-solid}** | [`PipelineConnection`](#pipelineconnection) | **Deprecated** in 14.6. Due to scalability concerns, this field is going to be removed. |
|
||||
| <a id="packagedetailstypepipelines"></a>`pipelines` | [`PipelineConnection`](#pipelineconnection) | Pipelines that built the package. Max page size 20. (see [Connections](#connections)) |
|
||||
| <a id="packagedetailstypeproject"></a>`project` | [`Project!`](#project) | Project where the package is stored. |
|
||||
| <a id="packagedetailstypepypisetupurl"></a>`pypiSetupUrl` | [`String`](#string) | Url of the PyPi project setup endpoint. |
|
||||
| <a id="packagedetailstypepypiurl"></a>`pypiUrl` | [`String`](#string) | Url of the PyPi project endpoint. |
|
||||
|
|
|
@ -5931,6 +5931,9 @@ msgstr ""
|
|||
msgid "Billings|Extend trial"
|
||||
msgstr ""
|
||||
|
||||
msgid "Billings|In a seat"
|
||||
msgstr ""
|
||||
|
||||
msgid "Billings|Reactivate trial"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5943,6 +5946,9 @@ msgstr ""
|
|||
msgid "Billings|Shared runners cannot be enabled until a valid credit card is on file."
|
||||
msgstr ""
|
||||
|
||||
msgid "Billings|To make this member active, you must first remove an existing active member, or toggle them to over limit."
|
||||
msgstr ""
|
||||
|
||||
msgid "Billings|To use free CI/CD minutes on shared runners, you’ll need to validate your account with a credit card. If you prefer not to provide one, you can run pipelines by bringing your own runners and disabling shared runners for your project. This is required to discourage and reduce abuse on GitLab infrastructure. %{strongStart}GitLab will not charge your card, it will only be used for validation.%{strongEnd} %{linkStart}Learn more%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
@ -18553,15 +18559,15 @@ msgstr ""
|
|||
msgid "GroupsNew|Create this in the %{pat_link_start}user settings%{pat_link_end} of the source GitLab instance. For %{short_living_link_start}security reasons%{short_living_link_end}, use a short expiration date when creating the token."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|Export groups with all their related data and move to a new GitLab instance."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|GitLab source URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|Groups can also be nested by creating %{linkStart}subgroups%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|Import a group and related data from another GitLab instance."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|Import group"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19545,6 +19551,9 @@ msgstr ""
|
|||
msgid "Improve customer support with Service Desk"
|
||||
msgstr ""
|
||||
|
||||
msgid "In a seat"
|
||||
msgstr ""
|
||||
|
||||
msgid "In case of pull mirroring, your user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
|
||||
msgstr ""
|
||||
|
||||
|
@ -21056,10 +21065,10 @@ msgstr ""
|
|||
msgid "InviteMembersModal|Create issues for your new team member to work on (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|GitLab is better with colleagues!"
|
||||
msgid "InviteMembersModal|Explore paid plans"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|GitLab member or email address"
|
||||
msgid "InviteMembersModal|GitLab is better with colleagues!"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|How about inviting a colleague or two to join you?"
|
||||
|
@ -21074,10 +21083,10 @@ msgstr ""
|
|||
msgid "InviteMembersModal|Invite members"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|Members were successfully added"
|
||||
msgid "InviteMembersModal|Manage members"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|New members will be unable to participate. You can manage your members by removing ones you no longer need."
|
||||
msgid "InviteMembersModal|Members were successfully added"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|Search for a group to invite"
|
||||
|
@ -21095,12 +21104,21 @@ msgstr ""
|
|||
msgid "InviteMembersModal|Something went wrong"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|This feature is disabled until this group has space for more members."
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|To assign issues to a new team member, you need a project for the issues. %{linkStart}Create a project to get started.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|To get more members an owner of this namespace can %{trialLinkStart}start a trial%{trialLinkEnd} or %{upgradeLinkStart}upgrade%{upgradeLinkEnd} to a paid tier."
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|Username or email address"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|You cannot add more members, but you can remove members who no longer need access. To get more members and access to additional paid features, an owner of this namespace can start a trial or upgrade to a paid tier."
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|You only have space for %{count} more %{members} in %{name}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -25824,6 +25842,9 @@ msgstr ""
|
|||
msgid "No triggers exist yet. Use the form above to create one."
|
||||
msgstr ""
|
||||
|
||||
msgid "No user provided"
|
||||
msgstr ""
|
||||
|
||||
msgid "No vulnerabilities present"
|
||||
msgstr ""
|
||||
|
||||
|
@ -39241,27 +39262,12 @@ msgstr ""
|
|||
msgid "ThreatMonitoring|All Environments"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Anomalous Requests"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Container Network Policies are not installed or have been disabled. To view this data, ensure your Network Policies are installed and enabled for your cluster."
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Container Network Policy"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Container NetworkPolicies not detected"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Date and time"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Dismissed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Dropped Packets"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Environment"
|
||||
msgstr ""
|
||||
|
||||
|
@ -39289,33 +39295,15 @@ msgstr ""
|
|||
msgid "ThreatMonitoring|No alerts to display."
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|No environments detected"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Operations Per Second"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Packet Activity"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Requests"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Resolved"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Show last"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Something went wrong, unable to fetch environments"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Something went wrong, unable to fetch statistics"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Statistics"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Status"
|
||||
msgstr ""
|
||||
|
||||
|
@ -39331,18 +39319,6 @@ msgstr ""
|
|||
msgid "ThreatMonitoring|Threat Monitoring help page link"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Time"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|To view this data, ensure you have configured an environment for this project and that at least one threat monitoring feature is enabled. %{linkStart}More information%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Total Packets"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Total Requests"
|
||||
msgstr ""
|
||||
|
||||
msgid "ThreatMonitoring|Unreviewed"
|
||||
msgstr ""
|
||||
|
||||
|
@ -43506,6 +43482,9 @@ msgstr ""
|
|||
msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
|
||||
msgstr ""
|
||||
|
||||
msgid "You do not have permission to set a member awaiting"
|
||||
msgstr ""
|
||||
|
||||
msgid "You do not have permission to update the environment."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/svgs": "2.11.0",
|
||||
"@gitlab/ui": "39.6.0",
|
||||
"@gitlab/visual-review-tools": "1.7.0",
|
||||
"@gitlab/visual-review-tools": "1.7.1",
|
||||
"@rails/actioncable": "6.1.4-7",
|
||||
"@rails/ujs": "6.1.4-7",
|
||||
"@sentry/browser": "5.30.0",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { GlLink, GlModal, GlSprintf } from '@gitlab/ui';
|
||||
import { GlLink, GlModal, GlSprintf, GlFormGroup } from '@gitlab/ui';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { nextTick } from 'vue';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
|
@ -15,6 +15,7 @@ import {
|
|||
MEMBERS_MODAL_CELEBRATE_INTRO,
|
||||
MEMBERS_MODAL_CELEBRATE_TITLE,
|
||||
MEMBERS_PLACEHOLDER,
|
||||
MEMBERS_PLACEHOLDER_DISABLED,
|
||||
MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT,
|
||||
LEARN_GITLAB,
|
||||
} from '~/invite_members/constants';
|
||||
|
@ -28,6 +29,8 @@ import {
|
|||
propsData,
|
||||
inviteSource,
|
||||
newProjectPath,
|
||||
freeUsersLimit,
|
||||
membersCount,
|
||||
user1,
|
||||
user2,
|
||||
user3,
|
||||
|
@ -45,12 +48,13 @@ describe('InviteMembersModal', () => {
|
|||
let wrapper;
|
||||
let mock;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
const createComponent = (props = {}, stubs = {}) => {
|
||||
wrapper = shallowMountExtended(InviteMembersModal, {
|
||||
provide: {
|
||||
newProjectPath,
|
||||
},
|
||||
propsData: {
|
||||
usersLimitDataset: {},
|
||||
...propsData,
|
||||
...props,
|
||||
},
|
||||
|
@ -62,16 +66,17 @@ describe('InviteMembersModal', () => {
|
|||
template: '<div><slot></slot><slot name="modal-footer"></slot></div>',
|
||||
}),
|
||||
GlEmoji,
|
||||
...stubs,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const createInviteMembersToProjectWrapper = () => {
|
||||
createComponent({ isProject: true });
|
||||
const createInviteMembersToProjectWrapper = (usersLimitDataset = {}, stubs = {}) => {
|
||||
createComponent({ usersLimitDataset, isProject: true }, stubs);
|
||||
};
|
||||
|
||||
const createInviteMembersToGroupWrapper = () => {
|
||||
createComponent({ isProject: false });
|
||||
const createInviteMembersToGroupWrapper = (usersLimitDataset = {}, stubs = {}) => {
|
||||
createComponent({ usersLimitDataset, isProject: false }, stubs);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -95,7 +100,7 @@ describe('InviteMembersModal', () => {
|
|||
const findMembersFormGroup = () => wrapper.findByTestId('members-form-group');
|
||||
const membersFormGroupInvalidFeedback = () =>
|
||||
findMembersFormGroup().attributes('invalid-feedback');
|
||||
const membersFormGroupDescription = () => findMembersFormGroup().attributes('description');
|
||||
const membersFormGroupText = () => findMembersFormGroup().text();
|
||||
const findMembersSelect = () => wrapper.findComponent(MembersTokenSelect);
|
||||
const findTasksToBeDone = () => wrapper.findByTestId('invite-members-modal-tasks-to-be-done');
|
||||
const findTasks = () => wrapper.findByTestId('invite-members-modal-tasks');
|
||||
|
@ -259,16 +264,33 @@ describe('InviteMembersModal', () => {
|
|||
expect(wrapper.findComponent(ModalConfetti).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('includes the correct invitee, type, and formatted name', () => {
|
||||
it('includes the correct invitee', () => {
|
||||
expect(findIntroText()).toBe("You're inviting members to the test name project.");
|
||||
expect(findCelebrationEmoji().exists()).toBe(false);
|
||||
expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER);
|
||||
});
|
||||
|
||||
describe('members form group description', () => {
|
||||
it('renders correct description', () => {
|
||||
createInviteMembersToProjectWrapper({ freeUsersLimit, membersCount }, { GlFormGroup });
|
||||
expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER);
|
||||
});
|
||||
|
||||
describe('when reached user limit', () => {
|
||||
it('renders correct description', () => {
|
||||
createInviteMembersToProjectWrapper(
|
||||
{ freeUsersLimit, membersCount: 5 },
|
||||
{ GlFormGroup },
|
||||
);
|
||||
|
||||
expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER_DISABLED);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when inviting members with celebration', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent({ isProject: true });
|
||||
createInviteMembersToProjectWrapper();
|
||||
await triggerOpenModal({ mode: 'celebrate' });
|
||||
});
|
||||
|
||||
|
@ -285,7 +307,28 @@ describe('InviteMembersModal', () => {
|
|||
`${MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT} ${MEMBERS_MODAL_CELEBRATE_INTRO}`,
|
||||
);
|
||||
expect(findCelebrationEmoji().exists()).toBe(true);
|
||||
expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER);
|
||||
});
|
||||
|
||||
describe('members form group description', () => {
|
||||
it('renders correct description', async () => {
|
||||
createInviteMembersToProjectWrapper({ freeUsersLimit, membersCount }, { GlFormGroup });
|
||||
await triggerOpenModal({ mode: 'celebrate' });
|
||||
|
||||
expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER);
|
||||
});
|
||||
|
||||
describe('when reached user limit', () => {
|
||||
it('renders correct description', async () => {
|
||||
createInviteMembersToProjectWrapper(
|
||||
{ freeUsersLimit, membersCount: 5 },
|
||||
{ GlFormGroup },
|
||||
);
|
||||
|
||||
await triggerOpenModal({ mode: 'celebrate' });
|
||||
|
||||
expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER_DISABLED);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -295,7 +338,20 @@ describe('InviteMembersModal', () => {
|
|||
createInviteMembersToGroupWrapper();
|
||||
|
||||
expect(findIntroText()).toBe("You're inviting members to the test name group.");
|
||||
expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER);
|
||||
});
|
||||
|
||||
describe('members form group description', () => {
|
||||
it('renders correct description', () => {
|
||||
createInviteMembersToGroupWrapper({ freeUsersLimit, membersCount }, { GlFormGroup });
|
||||
expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER);
|
||||
});
|
||||
|
||||
describe('when reached user limit', () => {
|
||||
it('renders correct description', () => {
|
||||
createInviteMembersToGroupWrapper({ freeUsersLimit, membersCount: 5 }, { GlFormGroup });
|
||||
expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER_DISABLED);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,18 +6,30 @@ import {
|
|||
GlSprintf,
|
||||
GlLink,
|
||||
GlModal,
|
||||
GlIcon,
|
||||
} from '@gitlab/ui';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import InviteModalBase from '~/invite_members/components/invite_modal_base.vue';
|
||||
import ContentTransition from '~/vue_shared/components/content_transition.vue';
|
||||
import { CANCEL_BUTTON_TEXT, INVITE_BUTTON_TEXT } from '~/invite_members/constants';
|
||||
import { propsData } from '../mock_data/modal_base';
|
||||
|
||||
import {
|
||||
CANCEL_BUTTON_TEXT,
|
||||
INVITE_BUTTON_TEXT_DISABLED,
|
||||
INVITE_BUTTON_TEXT,
|
||||
CANCEL_BUTTON_TEXT_DISABLED,
|
||||
ON_SHOW_TRACK_LABEL,
|
||||
ON_CLOSE_TRACK_LABEL,
|
||||
ON_SUBMIT_TRACK_LABEL,
|
||||
} from '~/invite_members/constants';
|
||||
|
||||
import { propsData, membersPath, purchasePath } from '../mock_data/modal_base';
|
||||
|
||||
describe('InviteModalBase', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
const createComponent = (props = {}, stubs = {}) => {
|
||||
wrapper = shallowMountExtended(InviteModalBase, {
|
||||
propsData: {
|
||||
...propsData,
|
||||
|
@ -33,8 +45,9 @@ describe('InviteModalBase', () => {
|
|||
GlDropdownItem: true,
|
||||
GlSprintf,
|
||||
GlFormGroup: stubComponent(GlFormGroup, {
|
||||
props: ['state', 'invalidFeedback', 'description'],
|
||||
props: ['state', 'invalidFeedback'],
|
||||
}),
|
||||
...stubs,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -48,8 +61,12 @@ describe('InviteModalBase', () => {
|
|||
const findDropdownItems = () => findDropdown().findAllComponents(GlDropdownItem);
|
||||
const findDatepicker = () => wrapper.findComponent(GlDatepicker);
|
||||
const findLink = () => wrapper.findComponent(GlLink);
|
||||
const findIcon = () => wrapper.findComponent(GlIcon);
|
||||
const findIntroText = () => wrapper.findByTestId('modal-base-intro-text').text();
|
||||
const findMembersFormGroup = () => wrapper.findByTestId('members-form-group');
|
||||
const findDisabledInput = () => wrapper.findByTestId('disabled-input');
|
||||
const findCancelButton = () => wrapper.find('.js-modal-action-cancel');
|
||||
const findActionButton = () => wrapper.find('.js-modal-action-primary');
|
||||
|
||||
describe('rendering the modal', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -106,11 +123,89 @@ describe('InviteModalBase', () => {
|
|||
|
||||
it('renders the members form group', () => {
|
||||
expect(findMembersFormGroup().props()).toEqual({
|
||||
description: propsData.formGroupDescription,
|
||||
invalidFeedback: '',
|
||||
state: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders description', () => {
|
||||
createComponent({}, { GlFormGroup });
|
||||
|
||||
expect(findMembersFormGroup().text()).toContain(propsData.formGroupDescription);
|
||||
});
|
||||
|
||||
describe('when users limit is reached', () => {
|
||||
let trackingSpy;
|
||||
|
||||
const expectTracking = (action, label) =>
|
||||
expect(trackingSpy).toHaveBeenCalledWith('default', action, {
|
||||
label,
|
||||
category: 'default',
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent(
|
||||
{ membersPath, purchasePath, reachedLimit: true },
|
||||
{ GlModal, GlFormGroup },
|
||||
);
|
||||
});
|
||||
|
||||
it('renders correct blocks', () => {
|
||||
expect(findIcon().exists()).toBe(true);
|
||||
expect(findDisabledInput().exists()).toBe(true);
|
||||
expect(findDropdown().exists()).toBe(false);
|
||||
expect(findDatepicker().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders correct buttons', () => {
|
||||
const cancelButton = findCancelButton();
|
||||
const actionButton = findActionButton();
|
||||
|
||||
expect(cancelButton.attributes('href')).toBe(purchasePath);
|
||||
expect(cancelButton.text()).toBe(CANCEL_BUTTON_TEXT_DISABLED);
|
||||
expect(actionButton.attributes('href')).toBe(membersPath);
|
||||
expect(actionButton.text()).toBe(INVITE_BUTTON_TEXT_DISABLED);
|
||||
});
|
||||
|
||||
it('tracks actions', () => {
|
||||
createComponent({ reachedLimit: true }, { GlFormGroup, GlModal });
|
||||
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||
|
||||
const modal = wrapper.findComponent(GlModal);
|
||||
|
||||
modal.vm.$emit('shown');
|
||||
expectTracking('render', ON_SHOW_TRACK_LABEL);
|
||||
|
||||
modal.vm.$emit('cancel', { preventDefault: jest.fn() });
|
||||
expectTracking('click_button', ON_CLOSE_TRACK_LABEL);
|
||||
|
||||
modal.vm.$emit('primary', { preventDefault: jest.fn() });
|
||||
expectTracking('click_button', ON_SUBMIT_TRACK_LABEL);
|
||||
|
||||
unmockTracking();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when users limit is not reached', () => {
|
||||
const textRegex = /Select a role.+Read more about role permissions Access expiration date \(optional\)/;
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({ reachedLimit: false }, { GlModal, GlFormGroup });
|
||||
});
|
||||
|
||||
it('renders correct blocks', () => {
|
||||
expect(findIcon().exists()).toBe(false);
|
||||
expect(findDisabledInput().exists()).toBe(false);
|
||||
expect(findDropdown().exists()).toBe(true);
|
||||
expect(findDatepicker().exists()).toBe(true);
|
||||
expect(wrapper.findComponent(GlModal).text()).toMatch(textRegex);
|
||||
});
|
||||
|
||||
it('renders correct buttons', () => {
|
||||
expect(findCancelButton().text()).toBe(CANCEL_BUTTON_TEXT);
|
||||
expect(findActionButton().text()).toBe(INVITE_BUTTON_TEXT);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with isLoading, shows loading for invite button', () => {
|
||||
|
@ -127,7 +222,6 @@ describe('InviteModalBase', () => {
|
|||
});
|
||||
|
||||
expect(findMembersFormGroup().props()).toEqual({
|
||||
description: propsData.formGroupDescription,
|
||||
invalidFeedback: 'invalid message!',
|
||||
state: false,
|
||||
});
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
import { GlAlert, GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import UserLimitNotification from '~/invite_members/components/user_limit_notification.vue';
|
||||
import { REACHED_LIMIT_MESSAGE } from '~/invite_members/constants';
|
||||
import { freeUsersLimit, membersCount } from '../mock_data/member_modal';
|
||||
|
||||
describe('UserLimitNotification', () => {
|
||||
let wrapper;
|
||||
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
|
||||
const createComponent = (providers = {}) => {
|
||||
const createComponent = (reachedLimit = false, usersLimitDataset = {}) => {
|
||||
wrapper = shallowMountExtended(UserLimitNotification, {
|
||||
provide: {
|
||||
name: 'my group',
|
||||
newTrialRegistrationPath: 'newTrialRegistrationPath',
|
||||
purchasePath: 'purchasePath',
|
||||
freeUsersLimit: 5,
|
||||
membersCount: 1,
|
||||
...providers,
|
||||
propsData: {
|
||||
reachedLimit,
|
||||
usersLimitDataset: {
|
||||
freeUsersLimit,
|
||||
membersCount,
|
||||
newTrialRegistrationPath: 'newTrialRegistrationPath',
|
||||
purchasePath: 'purchasePath',
|
||||
...usersLimitDataset,
|
||||
},
|
||||
},
|
||||
provide: { name: 'my group' },
|
||||
stubs: { GlSprintf },
|
||||
});
|
||||
};
|
||||
|
@ -37,7 +42,7 @@ describe('UserLimitNotification', () => {
|
|||
|
||||
describe('when close to limit', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ membersCount: 3 });
|
||||
createComponent(false, { membersCount: 3 });
|
||||
});
|
||||
|
||||
it("renders user's limit notification", () => {
|
||||
|
@ -55,17 +60,14 @@ describe('UserLimitNotification', () => {
|
|||
|
||||
describe('when limit is reached', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ membersCount: 5 });
|
||||
createComponent(true);
|
||||
});
|
||||
|
||||
it("renders user's limit notification", () => {
|
||||
const alert = findAlert();
|
||||
|
||||
expect(alert.attributes('title')).toEqual("You've reached your 5 members limit for my group");
|
||||
|
||||
expect(alert.text()).toEqual(
|
||||
'New members will be unable to participate. You can manage your members by removing ones you no longer need. To get more members an owner of this namespace can start a trial or upgrade to a paid tier.',
|
||||
);
|
||||
expect(alert.text()).toEqual(REACHED_LIMIT_MESSAGE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,6 +18,8 @@ export const propsData = {
|
|||
|
||||
export const inviteSource = 'unknown';
|
||||
export const newProjectPath = 'projects/new';
|
||||
export const freeUsersLimit = 5;
|
||||
export const membersCount = 1;
|
||||
|
||||
export const user1 = { id: 1, name: 'Name One', username: 'one_1', avatar_url: '' };
|
||||
export const user2 = { id: 2, name: 'Name Two', username: 'one_2', avatar_url: '' };
|
||||
|
|
|
@ -9,3 +9,6 @@ export const propsData = {
|
|||
labelSearchField: '_label_search_field_',
|
||||
formGroupDescription: '_form_group_description_',
|
||||
};
|
||||
|
||||
export const membersPath = '/members_path';
|
||||
export const purchasePath = '/purchase_path';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { GlDropdownItem, GlIcon, GlDropdown } from '@gitlab/ui';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { nextTick } from 'vue';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { useFakeDate } from 'helpers/fake_date';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
@ -28,7 +28,6 @@ import { imageTagsCountMock } from '../../mock_data';
|
|||
describe('Details Header', () => {
|
||||
let wrapper;
|
||||
let apolloProvider;
|
||||
let localVue;
|
||||
|
||||
const defaultImage = {
|
||||
name: 'foo',
|
||||
|
@ -64,28 +63,18 @@ describe('Details Header', () => {
|
|||
const mountComponent = ({
|
||||
propsData = { image: defaultImage },
|
||||
resolver = jest.fn().mockResolvedValue(imageTagsCountMock()),
|
||||
$apollo = undefined,
|
||||
} = {}) => {
|
||||
const mocks = {};
|
||||
Vue.use(VueApollo);
|
||||
|
||||
if ($apollo) {
|
||||
mocks.$apollo = $apollo;
|
||||
} else {
|
||||
localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
const requestHandlers = [[getContainerRepositoryMetadata, resolver]];
|
||||
apolloProvider = createMockApollo(requestHandlers);
|
||||
}
|
||||
const requestHandlers = [[getContainerRepositoryMetadata, resolver]];
|
||||
apolloProvider = createMockApollo(requestHandlers);
|
||||
|
||||
wrapper = shallowMount(component, {
|
||||
localVue,
|
||||
apolloProvider,
|
||||
propsData,
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
mocks,
|
||||
stubs: {
|
||||
TitleArea,
|
||||
GlDropdown,
|
||||
|
@ -98,7 +87,6 @@ describe('Details Header', () => {
|
|||
// if we want to mix createMockApollo and manual mocks we need to reset everything
|
||||
wrapper.destroy();
|
||||
apolloProvider = undefined;
|
||||
localVue = undefined;
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
|
@ -194,10 +182,7 @@ describe('Details Header', () => {
|
|||
describe('metadata items', () => {
|
||||
describe('tags count', () => {
|
||||
it('displays "-- tags" while loading', async () => {
|
||||
// here we are forced to mock apollo because `waitForMetadataItems` waits
|
||||
// for two ticks, de facto allowing the promise to resolve, so there is
|
||||
// no way to catch the component as both rendered and in loading state
|
||||
mountComponent({ $apollo: { queries: { containerRepository: { loading: true } } } });
|
||||
mountComponent();
|
||||
|
||||
await waitForMetadataItems();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { nextTick } from 'vue';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { GlCard, GlLoadingIcon } from 'jest/packages_and_registries/shared/stubs';
|
||||
|
@ -14,8 +14,6 @@ import expirationPolicyQuery from '~/packages_and_registries/settings/project/gr
|
|||
import Tracking from '~/tracking';
|
||||
import { expirationPolicyPayload, expirationPolicyMutationPayload } from '../mock_data';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
describe('Settings Form', () => {
|
||||
let wrapper;
|
||||
let fakeApollo;
|
||||
|
@ -59,7 +57,6 @@ describe('Settings Form', () => {
|
|||
data,
|
||||
config,
|
||||
provide = defaultProvidedValues,
|
||||
mocks,
|
||||
} = {}) => {
|
||||
wrapper = shallowMount(component, {
|
||||
stubs: {
|
||||
|
@ -77,7 +74,6 @@ describe('Settings Form', () => {
|
|||
$toast: {
|
||||
show: jest.fn(),
|
||||
},
|
||||
...mocks,
|
||||
},
|
||||
...config,
|
||||
});
|
||||
|
@ -88,7 +84,7 @@ describe('Settings Form', () => {
|
|||
mutationResolver,
|
||||
queryPayload = expirationPolicyPayload(),
|
||||
} = {}) => {
|
||||
localVue.use(VueApollo);
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const requestHandlers = [
|
||||
[updateContainerExpirationPolicyMutation, mutationResolver],
|
||||
|
@ -120,7 +116,6 @@ describe('Settings Form', () => {
|
|||
value,
|
||||
},
|
||||
config: {
|
||||
localVue,
|
||||
apolloProvider: fakeApollo,
|
||||
},
|
||||
});
|
||||
|
@ -356,8 +351,8 @@ describe('Settings Form', () => {
|
|||
});
|
||||
|
||||
it('parses the error messages', async () => {
|
||||
const mutate = jest.fn().mockRejectedValue({
|
||||
graphQLErrors: [
|
||||
const mutate = jest.fn().mockResolvedValue({
|
||||
errors: [
|
||||
{
|
||||
extensions: {
|
||||
problems: [{ path: ['nameRegexKeep'], message: 'baz' }],
|
||||
|
@ -365,7 +360,9 @@ describe('Settings Form', () => {
|
|||
},
|
||||
],
|
||||
});
|
||||
mountComponent({ mocks: { $apollo: { mutate } } });
|
||||
mountComponentWithApollo({
|
||||
mutationResolver: mutate,
|
||||
});
|
||||
|
||||
await submitForm();
|
||||
|
||||
|
|
|
@ -220,13 +220,11 @@ describe('search_params.js', () => {
|
|||
});
|
||||
|
||||
it.each`
|
||||
query | updatedQuery
|
||||
${'status[]=NOT_CONNECTED'} | ${'status[]=NEVER_CONTACTED'}
|
||||
${'status[]=NOT_CONNECTED&a=b'} | ${'status[]=NEVER_CONTACTED&a=b'}
|
||||
${'status[]=ACTIVE'} | ${'paused[]=false'}
|
||||
${'status[]=ACTIVE&a=b'} | ${'a=b&paused[]=false'}
|
||||
${'status[]=ACTIVE'} | ${'paused[]=false'}
|
||||
${'status[]=PAUSED'} | ${'paused[]=true'}
|
||||
query | updatedQuery
|
||||
${'status[]=ACTIVE'} | ${'paused[]=false'}
|
||||
${'status[]=ACTIVE&a=b'} | ${'a=b&paused[]=false'}
|
||||
${'status[]=ACTIVE'} | ${'paused[]=false'}
|
||||
${'status[]=PAUSED'} | ${'paused[]=true'}
|
||||
`('updates "$query" to "$updatedQuery"', ({ query, updatedQuery }) => {
|
||||
const mockUrl = 'http://test.host/admin/runners?';
|
||||
|
||||
|
|
|
@ -9,10 +9,23 @@ RSpec.describe Resolvers::PackagePipelinesResolver do
|
|||
let_it_be(:pipelines) { create_list(:ci_pipeline, 3, project: package.project) }
|
||||
|
||||
let(:user) { package.project.first_owner }
|
||||
let(:args) { {} }
|
||||
|
||||
describe '#resolve' do
|
||||
subject { resolve(described_class, obj: package, args: args, ctx: { current_user: user }) }
|
||||
let(:returned_pipeline_ids) { graphql_dig_at(subject, 'data', 'package', 'pipelines', 'nodes', 'id') }
|
||||
let(:returned_errors) { graphql_dig_at(subject, 'errors', 'message') }
|
||||
let(:pagination_args) { {} }
|
||||
let(:query) do
|
||||
pipelines_nodes = 'nodes { id }'
|
||||
graphql_query_for(
|
||||
:package,
|
||||
{ id: global_id_of(package) },
|
||||
query_graphql_field('pipelines', pagination_args, pipelines_nodes)
|
||||
)
|
||||
end
|
||||
|
||||
subject do
|
||||
GitlabSchema.execute(query, context: { current_user: user })
|
||||
end
|
||||
|
||||
before do
|
||||
pipelines.each do |pipeline|
|
||||
|
@ -20,67 +33,138 @@ RSpec.describe Resolvers::PackagePipelinesResolver do
|
|||
end
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(*pipelines) }
|
||||
shared_examples 'returning the expected pipelines' do
|
||||
it 'contains the expected pipelines' do
|
||||
expect_to_contain_exactly(*pipelines)
|
||||
end
|
||||
|
||||
context 'with invalid after' do
|
||||
let(:args) { { first: 1, after: 'not_json_string' } }
|
||||
context 'with valid after' do
|
||||
let(:pagination_args) { { first: 1, after: encode_cursor(id: pipelines[1].id) } }
|
||||
|
||||
it 'generates an argument error' do
|
||||
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do
|
||||
subject
|
||||
it 'contains the expected pipelines' do
|
||||
expect_to_contain_exactly(pipelines[0])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid before' do
|
||||
let(:pagination_args) { { last: 1, before: encode_cursor(id: pipelines[1].id) } }
|
||||
|
||||
it 'contains the expected pipelines' do
|
||||
expect_to_contain_exactly(pipelines[2])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid after' do
|
||||
let(:pagination_args) { { first: 1, after: 'not_json_string' } }
|
||||
|
||||
it 'generates an argument error' do
|
||||
expect(returned_errors).to include('Please provide a valid cursor')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid after key' do
|
||||
let(:pagination_args) { { first: 1, after: encode_cursor(foo: 3) } }
|
||||
|
||||
it 'generates an argument error' do
|
||||
expect(returned_errors).to include('Please provide a valid cursor')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid before' do
|
||||
let(:pagination_args) { { last: 1, before: 'not_json_string' } }
|
||||
|
||||
it 'generates an argument error' do
|
||||
expect(returned_errors).to include('Please provide a valid cursor')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid before key' do
|
||||
let(:pagination_args) { { last: 1, before: encode_cursor(foo: 3) } }
|
||||
|
||||
it 'generates an argument error' do
|
||||
expect(returned_errors).to include('Please provide a valid cursor')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unauthorized user' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
it 'returns nothing' do
|
||||
expect(returned_pipeline_ids).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with many packages' do
|
||||
let_it_be_with_reload(:other_package) { create(:package, project: package.project) }
|
||||
let_it_be(:other_pipelines) { create_list(:ci_pipeline, 3, project: package.project) }
|
||||
|
||||
let(:returned_pipeline_ids) do
|
||||
graphql_dig_at(subject, 'data', 'project', 'packages', 'nodes', 'pipelines', 'nodes', 'id')
|
||||
end
|
||||
|
||||
let(:query) do
|
||||
pipelines_query = query_graphql_field('pipelines', pagination_args, 'nodes { id }')
|
||||
<<~QUERY
|
||||
{
|
||||
project(fullPath: "#{package.project.full_path}") {
|
||||
packages {
|
||||
nodes { #{pipelines_query} }
|
||||
}
|
||||
}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
before do
|
||||
other_pipelines.each do |pipeline|
|
||||
create(:package_build_info, package: other_package, pipeline: pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
it 'contains the expected pipelines' do
|
||||
expect_to_contain_exactly(*(pipelines + other_pipelines))
|
||||
end
|
||||
|
||||
it 'handles n+1 situations' do
|
||||
control = ActiveRecord::QueryRecorder.new do
|
||||
GitlabSchema.execute(query, context: { current_user: user })
|
||||
end
|
||||
|
||||
create_package_with_pipelines(package.project)
|
||||
|
||||
expectation = expect { GitlabSchema.execute(query, context: { current_user: user }) }
|
||||
|
||||
if Feature.enabled?(:packages_graphql_pipelines_resolver, default_enabled: :yaml)
|
||||
expectation.not_to exceed_query_limit(control)
|
||||
else
|
||||
expectation.to exceed_query_limit(control)
|
||||
end
|
||||
end
|
||||
|
||||
def create_package_with_pipelines(project)
|
||||
extra_package = create(:package, project: project)
|
||||
create_list(:ci_pipeline, 3, project: project).each do |pipeline|
|
||||
create(:package_build_info, package: extra_package, pipeline: pipeline)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid after key' do
|
||||
let(:args) { { first: 1, after: encode_cursor(foo: 3) } }
|
||||
|
||||
it 'generates an argument error' do
|
||||
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do
|
||||
subject
|
||||
end
|
||||
context 'with packages_graphql_pipelines_resolver enabled' do
|
||||
before do
|
||||
expect_detect_mode([:new_finder])
|
||||
end
|
||||
|
||||
it_behaves_like 'returning the expected pipelines'
|
||||
end
|
||||
|
||||
context 'with invalid before' do
|
||||
let(:args) { { last: 1, before: 'not_json_string' } }
|
||||
|
||||
it 'generates an argument error' do
|
||||
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid before key' do
|
||||
let(:args) { { last: 1, before: encode_cursor(foo: 3) } }
|
||||
|
||||
it 'generates an argument error' do
|
||||
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'field options' do
|
||||
let(:field) do
|
||||
field_options = described_class.field_options.merge(
|
||||
owner: resolver_parent,
|
||||
name: 'dummy_field'
|
||||
)
|
||||
::Types::BaseField.new(**field_options)
|
||||
context 'with packages_graphql_pipelines_resolver disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_graphql_pipelines_resolver: false)
|
||||
expect_detect_mode([:old_finder, :object_field])
|
||||
end
|
||||
|
||||
it 'sets them properly' do
|
||||
expect(field).not_to be_connection
|
||||
expect(field.extras).to match_array([:lookahead])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unauthorized user' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
it_behaves_like 'returning the expected pipelines'
|
||||
end
|
||||
|
||||
def encode_cursor(json)
|
||||
|
@ -89,5 +173,37 @@ RSpec.describe Resolvers::PackagePipelinesResolver do
|
|||
nonce: true
|
||||
)
|
||||
end
|
||||
|
||||
def expect_to_contain_exactly(*pipelines)
|
||||
ids = pipelines.map { |pipeline| global_id_of(pipeline) }
|
||||
expect(returned_pipeline_ids).to contain_exactly(*ids)
|
||||
end
|
||||
|
||||
def expect_detect_mode(modes)
|
||||
allow_next_instance_of(described_class) do |resolver|
|
||||
detect_mode_method = resolver.method(:detect_mode)
|
||||
allow(resolver).to receive(:detect_mode) do
|
||||
result = detect_mode_method.call
|
||||
|
||||
expect(modes).to include(result)
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.field options' do
|
||||
let(:field) do
|
||||
field_options = described_class.field_options.merge(
|
||||
owner: resolver_parent,
|
||||
name: 'dummy_field'
|
||||
)
|
||||
::Types::BaseField.new(**field_options)
|
||||
end
|
||||
|
||||
it 'sets them properly' do
|
||||
expect(field).not_to be_connection
|
||||
expect(field.extras).to match_array([:lookahead])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -631,10 +631,15 @@ RSpec.describe ContainerRepository, :aggregate_failures do
|
|||
describe '#start_expiration_policy!' do
|
||||
subject { repository.start_expiration_policy! }
|
||||
|
||||
before do
|
||||
repository.update_column(:last_cleanup_deleted_tags_count, 10)
|
||||
end
|
||||
|
||||
it 'sets the expiration policy started at to now' do
|
||||
freeze_time do
|
||||
expect { subject }
|
||||
.to change { repository.expiration_policy_started_at }.from(nil).to(Time.zone.now)
|
||||
.and change { repository.last_cleanup_deleted_tags_count }.from(10).to(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
|
|||
it 'completely clean up the repository' do
|
||||
expect(Projects::ContainerRepository::CleanupTagsService)
|
||||
.to receive(:new).with(repository, nil, cleanup_tags_service_params).and_return(cleanup_tags_service)
|
||||
expect(cleanup_tags_service).to receive(:execute).and_return(status: :success)
|
||||
expect(cleanup_tags_service).to receive(:execute).and_return(status: :success, deleted_size: 1)
|
||||
|
||||
response = subject
|
||||
|
||||
|
@ -36,6 +36,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
|
|||
expect(repository.reload.cleanup_unscheduled?).to be_truthy
|
||||
expect(repository.expiration_policy_completed_at).not_to eq(nil)
|
||||
expect(repository.expiration_policy_started_at).not_to eq(nil)
|
||||
expect(repository.last_cleanup_deleted_tags_count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -58,6 +59,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
|
|||
expect(repository.reload.cleanup_unfinished?).to be_truthy
|
||||
expect(repository.expiration_policy_started_at).not_to eq(nil)
|
||||
expect(repository.expiration_policy_completed_at).to eq(nil)
|
||||
expect(repository.last_cleanup_deleted_tags_count).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -94,6 +96,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
|
|||
expect(repository.reload.cleanup_unfinished?).to be_truthy
|
||||
expect(repository.expiration_policy_started_at).not_to eq(nil)
|
||||
expect(repository.expiration_policy_completed_at).to eq(nil)
|
||||
expect(repository.last_cleanup_deleted_tags_count).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -138,6 +141,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
|
|||
expect(repository.reload.cleanup_unfinished?).to be_truthy
|
||||
expect(repository.expiration_policy_started_at).not_to eq(nil)
|
||||
expect(repository.expiration_policy_completed_at).to eq(nil)
|
||||
expect(repository.last_cleanup_deleted_tags_count).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,7 +4,11 @@ RSpec.shared_examples 'group and projects packages resolver' do
|
|||
context 'without sort' do
|
||||
let_it_be(:npm_package) { create(:package, project: project) }
|
||||
|
||||
it { is_expected.to contain_exactly(npm_package) }
|
||||
it 'returns the proper packages' do
|
||||
expect(::Packages::Package).not_to receive(:preload_pipelines)
|
||||
|
||||
expect(subject).to contain_exactly(npm_package)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with sorting and filtering' do
|
||||
|
|
|
@ -45,9 +45,33 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
|
|||
end
|
||||
|
||||
it "notifies about #{event_type} events" do
|
||||
expect(chat_integration).not_to receive(:log_error)
|
||||
|
||||
chat_integration.execute(data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, stubbed_resolved_hostname)
|
||||
end
|
||||
|
||||
context 'when the response is not successful' do
|
||||
let!(:stubbed_resolved_hostname) do
|
||||
stub_full_request(webhook_url, method: :post)
|
||||
.to_return(status: 409, body: 'error message')
|
||||
.request_pattern.uri_pattern.to_s
|
||||
end
|
||||
|
||||
it 'logs an error' do
|
||||
expect(chat_integration).to receive(:log_error).with(
|
||||
'SlackMattermostNotifier HTTP error response',
|
||||
request_host: 'example.gitlab.com',
|
||||
response_code: 409,
|
||||
response_body: 'error message'
|
||||
)
|
||||
|
||||
chat_integration.execute(data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, stubbed_resolved_hostname)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "untriggered #{integration_name} integration" do |event_type: nil, branches_to_be_notified: nil|
|
||||
|
@ -59,8 +83,9 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
|
|||
stub_full_request(webhook_url, method: :post).request_pattern.uri_pattern.to_s
|
||||
end
|
||||
|
||||
it "notifies about #{event_type} events" do
|
||||
it "does not notify about #{event_type} events" do
|
||||
chat_integration.execute(data)
|
||||
|
||||
expect(WebMock).not_to have_requested(:post, stubbed_resolved_hostname)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -191,4 +191,91 @@ RSpec.shared_examples 'group and project packages query' do
|
|||
it { is_expected.to include({ "name" => versionless_package.name }) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reading pipelines' do
|
||||
let(:npm_pipelines) { create_list(:ci_pipeline, 6, project: project1) }
|
||||
let(:npm_pipeline_gids) { npm_pipelines.sort_by(&:id).map(&:to_gid).map(&:to_s).reverse }
|
||||
let(:composer_pipelines) { create_list(:ci_pipeline, 6, project: project2) }
|
||||
let(:composer_pipeline_gids) { composer_pipelines.sort_by(&:id).map(&:to_gid).map(&:to_s).reverse }
|
||||
let(:npm_end_cursor) { graphql_data_npm_package.dig('pipelines', 'pageInfo', 'endCursor') }
|
||||
let(:npm_start_cursor) { graphql_data_npm_package.dig('pipelines', 'pageInfo', 'startCursor') }
|
||||
let(:pipelines_nodes) do
|
||||
<<~QUERY
|
||||
nodes {
|
||||
id
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
before do
|
||||
resource.add_maintainer(current_user)
|
||||
|
||||
npm_pipelines.each do |pipeline|
|
||||
create(:package_build_info, package: npm_package, pipeline: pipeline)
|
||||
end
|
||||
|
||||
composer_pipelines.each do |pipeline|
|
||||
create(:package_build_info, package: composer_package, pipeline: pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
it 'loads the second page with pagination first correctly' do
|
||||
run_query(first: 2)
|
||||
expect(npm_pipeline_ids).to eq(npm_pipeline_gids[0..1])
|
||||
expect(composer_pipeline_ids).to eq(composer_pipeline_gids[0..1])
|
||||
|
||||
run_query(first: 2, after: npm_end_cursor)
|
||||
expect(npm_pipeline_ids).to eq(npm_pipeline_gids[2..3])
|
||||
expect(composer_pipeline_ids).to be_empty
|
||||
end
|
||||
|
||||
it 'loads the second page with pagination last correctly' do
|
||||
run_query(last: 2)
|
||||
expect(npm_pipeline_ids).to eq(npm_pipeline_gids[4..5])
|
||||
expect(composer_pipeline_ids).to eq(composer_pipeline_gids[4..5])
|
||||
|
||||
run_query(last: 2, before: npm_start_cursor)
|
||||
expect(npm_pipeline_ids).to eq(npm_pipeline_gids[2..3])
|
||||
expect(composer_pipeline_ids).to eq(composer_pipeline_gids[4..5])
|
||||
end
|
||||
|
||||
def run_query(args)
|
||||
pipelines_field = query_graphql_field('pipelines', args, pipelines_nodes)
|
||||
|
||||
packages_nodes = <<~QUERY
|
||||
nodes {
|
||||
id
|
||||
#{pipelines_field}
|
||||
}
|
||||
QUERY
|
||||
|
||||
query = graphql_query_for(
|
||||
resource_type,
|
||||
{ 'fullPath' => resource.full_path },
|
||||
query_graphql_field('packages', {}, packages_nodes)
|
||||
)
|
||||
|
||||
post_graphql(query, current_user: current_user)
|
||||
end
|
||||
|
||||
def npm_pipeline_ids
|
||||
graphql_data_npm_package.dig('pipelines', 'nodes').map { |pipeline| pipeline['id'] }
|
||||
end
|
||||
|
||||
def composer_pipeline_ids
|
||||
graphql_data_composer_package.dig('pipelines', 'nodes').map { |pipeline| pipeline['id'] }
|
||||
end
|
||||
|
||||
def graphql_data_npm_package
|
||||
graphql_data_at(resource_type, :packages, :nodes).find { |pkg| pkg['id'] == npm_package.to_gid.to_s }
|
||||
end
|
||||
|
||||
def graphql_data_composer_package
|
||||
graphql_data_at(resource_type, :packages, :nodes).find { |pkg| pkg['id'] == composer_package.to_gid.to_s }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -982,10 +982,10 @@
|
|||
portal-vue "^2.1.6"
|
||||
vue-runtime-helpers "^1.1.2"
|
||||
|
||||
"@gitlab/visual-review-tools@1.7.0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.7.0.tgz#18a59911484ddbbee433d3701316b5ab8e4077a5"
|
||||
integrity sha512-o9gM3W6guSl00aS0hJcXePuR/mkmq38F5FhUgTlMBkB5+R68aO87md3cSvSMfJin0MjPlWktBNAfaz+y5CQ39g==
|
||||
"@gitlab/visual-review-tools@1.7.1":
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.7.1.tgz#9cc51c40bb530a621d0f5cb48ef3e891a79e92cc"
|
||||
integrity sha512-64lbKhJierSKOQxZQ30gimUDZhOXjtC7GdovSJwKMECqUB5pmDzmQn4fY0Nxn8jREWluiur8N3+z3jr2HJJofg==
|
||||
|
||||
"@graphql-eslint/eslint-plugin@3.10.2":
|
||||
version "3.10.2"
|
||||
|
|
Loading…
Reference in a new issue