Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
26bba9525d
commit
c0dd450008
|
@ -21,6 +21,8 @@ import {
|
|||
import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
|
||||
|
||||
export const i18n = {
|
||||
deleteIntegration: s__('AlertSettings|Delete integration'),
|
||||
editIntegration: s__('AlertSettings|Edit integration'),
|
||||
title: s__('AlertsIntegrations|Current integrations'),
|
||||
emptyState: s__('AlertsIntegrations|No integrations have been added yet'),
|
||||
status: {
|
||||
|
@ -174,11 +176,16 @@ export default {
|
|||
|
||||
<template #cell(actions)="{ item }">
|
||||
<gl-button-group class="gl-ml-3">
|
||||
<gl-button icon="settings" @click="editIntegration(item)" />
|
||||
<gl-button
|
||||
icon="settings"
|
||||
:aria-label="$options.i18n.editIntegration"
|
||||
@click="editIntegration(item)"
|
||||
/>
|
||||
<gl-button
|
||||
v-gl-modal.deleteIntegration
|
||||
:disabled="item.type === $options.typeSet.prometheus"
|
||||
icon="remove"
|
||||
:aria-label="$options.i18n.deleteIntegration"
|
||||
@click="setIntegrationToDelete(item)"
|
||||
/>
|
||||
</gl-button-group>
|
||||
|
@ -198,8 +205,8 @@ export default {
|
|||
</gl-table>
|
||||
<gl-modal
|
||||
modal-id="deleteIntegration"
|
||||
:title="s__('AlertSettings|Delete integration')"
|
||||
:ok-title="s__('AlertSettings|Delete integration')"
|
||||
:title="$options.i18n.deleteIntegration"
|
||||
:ok-title="$options.i18n.deleteIntegration"
|
||||
ok-variant="danger"
|
||||
@ok="deleteIntegration"
|
||||
>
|
||||
|
|
|
@ -44,7 +44,7 @@ const Api = {
|
|||
projectMilestonesPath: '/api/:version/projects/:id/milestones',
|
||||
projectIssuePath: '/api/:version/projects/:id/issues/:issue_iid',
|
||||
mergeRequestsPath: '/api/:version/merge_requests',
|
||||
groupLabelsPath: '/groups/:namespace_path/-/labels',
|
||||
groupLabelsPath: '/api/:version/groups/:namespace_path/labels',
|
||||
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
|
||||
issuableTemplatesPath: '/:namespace_path/:project_path/templates/:type',
|
||||
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
|
||||
|
@ -402,18 +402,29 @@ const Api = {
|
|||
|
||||
newLabel(namespacePath, projectPath, data, callback) {
|
||||
let url;
|
||||
let payload;
|
||||
|
||||
if (projectPath) {
|
||||
url = Api.buildUrl(Api.projectLabelsPath)
|
||||
.replace(':namespace_path', namespacePath)
|
||||
.replace(':project_path', projectPath);
|
||||
payload = {
|
||||
label: data,
|
||||
};
|
||||
} else {
|
||||
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
|
||||
|
||||
// groupLabelsPath uses public API which accepts
|
||||
// `name` and `color` props.
|
||||
payload = {
|
||||
name: data.title,
|
||||
color: data.color,
|
||||
};
|
||||
}
|
||||
|
||||
return axios
|
||||
.post(url, {
|
||||
label: data,
|
||||
...payload,
|
||||
})
|
||||
.then((res) => callback(res.data))
|
||||
.catch((e) => callback(e.response.data));
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
<script>
|
||||
import { GlLoadingIcon, GlTooltipDirective, GlIcon, GlButton } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
buttonLabel: s__('Badges|Reload badge image'),
|
||||
},
|
||||
// name: 'Badge' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
name: 'Badge',
|
||||
|
@ -94,7 +98,8 @@ export default {
|
|||
<gl-button
|
||||
v-show="hasError"
|
||||
v-gl-tooltip.hover
|
||||
:title="s__('Badges|Reload badge image')"
|
||||
:title="$options.i18n.buttonLabel"
|
||||
:aria-label="$options.i18n.buttonLabel"
|
||||
category="tertiary"
|
||||
variant="confirm"
|
||||
type="button"
|
||||
|
|
|
@ -163,6 +163,9 @@ export default {
|
|||
currentMutation() {
|
||||
return this.board.id ? updateBoardMutation : createBoardMutation;
|
||||
},
|
||||
deleteMutation() {
|
||||
return destroyBoardMutation;
|
||||
},
|
||||
baseMutationVariables() {
|
||||
const { board } = this;
|
||||
const variables = {
|
||||
|
@ -244,17 +247,20 @@ export default {
|
|||
|
||||
return this.boardUpdateResponse(response.data);
|
||||
},
|
||||
async deleteBoard() {
|
||||
await this.$apollo.mutate({
|
||||
mutation: this.deleteMutation,
|
||||
variables: {
|
||||
id: fullBoardId(this.board.id),
|
||||
},
|
||||
});
|
||||
},
|
||||
async submit() {
|
||||
if (this.board.name.length === 0) return;
|
||||
this.isLoading = true;
|
||||
if (this.isDeleteForm) {
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: destroyBoardMutation,
|
||||
variables: {
|
||||
id: fullBoardId(this.board.id),
|
||||
},
|
||||
});
|
||||
await this.deleteBoard();
|
||||
visitUrl(this.rootPath);
|
||||
} catch {
|
||||
Flash(this.$options.i18n.deleteErrorMessage);
|
||||
|
|
|
@ -47,6 +47,7 @@ export default {
|
|||
class="js-focus-mode-btn"
|
||||
data-qa-selector="focus_mode_button"
|
||||
:title="$options.i18n.toggleFocusMode"
|
||||
:aria-label="$options.i18n.toggleFocusMode"
|
||||
@click="toggleFocusMode"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,10 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
|||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
editButton: s__('Pipelines|Edit'),
|
||||
revokeButton: s__('Pipelines|Revoke'),
|
||||
},
|
||||
components: {
|
||||
GlTable,
|
||||
GlButton,
|
||||
|
@ -108,13 +112,15 @@ export default {
|
|||
</template>
|
||||
<template #cell(actions)="{ item }">
|
||||
<gl-button
|
||||
:title="s__('Pipelines|Edit')"
|
||||
:title="$options.i18n.editButton"
|
||||
:aria-label="$options.i18n.editButton"
|
||||
icon="pencil"
|
||||
data-testid="edit-btn"
|
||||
:href="item.editProjectTriggerPath"
|
||||
/>
|
||||
<gl-button
|
||||
:title="s__('Pipelines|Revoke')"
|
||||
:title="$options.i18n.revokeButton"
|
||||
:aria-label="$options.i18n.revokeButton"
|
||||
icon="remove"
|
||||
variant="warning"
|
||||
:data-confirm="
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import fuzzaldrinPlus from 'fuzzaldrin-plus';
|
||||
import $ from 'jquery';
|
||||
import { debounce } from 'lodash';
|
||||
import { isObject } from '~/lib/utils/type_utility';
|
||||
|
||||
const BLUR_KEYCODES = [27, 40];
|
||||
|
@ -11,13 +12,21 @@ const HAS_VALUE_CLASS = 'has-value';
|
|||
export class GitLabDropdownFilter {
|
||||
constructor(input, options) {
|
||||
let ref;
|
||||
let timeout;
|
||||
this.input = input;
|
||||
this.options = options;
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true;
|
||||
const $inputContainer = this.input.parent();
|
||||
const $clearButton = $inputContainer.find('.js-dropdown-input-clear');
|
||||
const filterRemoteDebounced = debounce(() => {
|
||||
$inputContainer.parent().addClass('is-loading');
|
||||
|
||||
return this.options.query(this.input.val(), (data) => {
|
||||
$inputContainer.parent().removeClass('is-loading');
|
||||
return this.options.callback(data);
|
||||
});
|
||||
}, 500);
|
||||
|
||||
$clearButton.on('click', (e) => {
|
||||
// Clear click
|
||||
e.preventDefault();
|
||||
|
@ -25,7 +34,6 @@ export class GitLabDropdownFilter {
|
|||
return this.input.val('').trigger('input').focus();
|
||||
});
|
||||
// Key events
|
||||
timeout = '';
|
||||
this.input
|
||||
.on('keydown', (e) => {
|
||||
const keyCode = e.which;
|
||||
|
@ -41,16 +49,7 @@ export class GitLabDropdownFilter {
|
|||
}
|
||||
// Only filter asynchronously only if option remote is set
|
||||
if (this.options.remote) {
|
||||
clearTimeout(timeout);
|
||||
// eslint-disable-next-line no-return-assign
|
||||
return (timeout = setTimeout(() => {
|
||||
$inputContainer.parent().addClass('is-loading');
|
||||
|
||||
return this.options.query(this.input.val(), (data) => {
|
||||
$inputContainer.parent().removeClass('is-loading');
|
||||
return this.options.callback(data);
|
||||
});
|
||||
}, 250));
|
||||
return filterRemoteDebounced();
|
||||
}
|
||||
return this.filter(this.input.val());
|
||||
});
|
||||
|
|
|
@ -12,6 +12,10 @@ import allDesignsMixin from '../../mixins/all_designs';
|
|||
import { DESIGN_ROUTE_NAME } from '../../router/constants';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
nextButton: s__('DesignManagement|Go to next design'),
|
||||
previousButton: s__('DesignManagement|Go to previous design'),
|
||||
},
|
||||
components: {
|
||||
GlButton,
|
||||
GlButtonGroup,
|
||||
|
@ -81,7 +85,8 @@ export default {
|
|||
<gl-button
|
||||
v-gl-tooltip.bottom
|
||||
:disabled="!previousDesign"
|
||||
:title="s__('DesignManagement|Go to previous design')"
|
||||
:title="$options.i18n.previousButton"
|
||||
:aria-label="$options.i18n.previousButton"
|
||||
icon="angle-left"
|
||||
class="js-previous-design"
|
||||
@click="navigateToDesign(previousDesign)"
|
||||
|
@ -89,7 +94,8 @@ export default {
|
|||
<gl-button
|
||||
v-gl-tooltip.bottom
|
||||
:disabled="!nextDesign"
|
||||
:title="s__('DesignManagement|Go to next design')"
|
||||
:title="$options.i18n.nextButton"
|
||||
:aria-label="$options.i18n.nextButton"
|
||||
icon="angle-right"
|
||||
class="js-next-design"
|
||||
@click="navigateToDesign(nextDesign)"
|
||||
|
|
|
@ -84,6 +84,7 @@ export default {
|
|||
icon="file-tree"
|
||||
class="gl-mr-3 js-toggle-tree-list"
|
||||
:title="toggleFileBrowserTitle"
|
||||
:aria-label="toggleFileBrowserTitle"
|
||||
:selected="showTreeList"
|
||||
@click="setShowTreeList({ showTreeList: !showTreeList })"
|
||||
/>
|
||||
|
|
|
@ -71,6 +71,7 @@ export default {
|
|||
class="gl-display-none gl-md-display-block text-secondary"
|
||||
:loading="isLoading"
|
||||
:title="title"
|
||||
:aria-label="title"
|
||||
:icon="isLastDeployment ? 'repeat' : 'redo'"
|
||||
@click="onClick"
|
||||
/>
|
||||
|
|
|
@ -14,6 +14,9 @@ import { sprintf, __ } from '~/locale';
|
|||
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
sendEmail: __('Send email'),
|
||||
},
|
||||
name: 'IssuableByEmail',
|
||||
components: {
|
||||
GlButton,
|
||||
|
@ -116,7 +119,8 @@ export default {
|
|||
<gl-button
|
||||
v-gl-tooltip.hover
|
||||
:href="mailToLink"
|
||||
:title="__('Send email')"
|
||||
:title="$options.i18n.sendEmail"
|
||||
:aria-label="$options.i18n.sendEmail"
|
||||
icon="mail"
|
||||
data-testid="mail-to-btn"
|
||||
/>
|
||||
|
|
|
@ -6,8 +6,12 @@ import {
|
|||
GlTooltipDirective,
|
||||
GlSafeHtmlDirective as SafeHtml,
|
||||
} from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
editTitleAndDescription: __('Edit title and description'),
|
||||
},
|
||||
components: {
|
||||
GlIcon,
|
||||
GlButton,
|
||||
|
@ -58,7 +62,8 @@ export default {
|
|||
<gl-button
|
||||
v-if="enableEdit"
|
||||
v-gl-tooltip.bottom
|
||||
:title="__('Edit title and description')"
|
||||
:title="$options.i18n.editTitleAndDescription"
|
||||
:aria-label="$options.i18n.editTitleAndDescription"
|
||||
icon="pencil"
|
||||
class="btn-edit js-issuable-edit qa-edit-button"
|
||||
@click="$emit('edit-issuable', $event)"
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
<script>
|
||||
import { GlButton, GlTooltipDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import eventHub from '../event_hub';
|
||||
import animateMixin from '../mixins/animate';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
editTitleAndDescription: __('Edit title and description'),
|
||||
},
|
||||
components: {
|
||||
GlButton,
|
||||
},
|
||||
|
@ -78,7 +82,8 @@ export default {
|
|||
v-gl-tooltip.bottom
|
||||
icon="pencil"
|
||||
class="btn-edit js-issuable-edit qa-edit-button"
|
||||
title="Edit title and description"
|
||||
:title="$options.i18n.editTitleAndDescription"
|
||||
:aria-label="$options.i18n.editTitleAndDescription"
|
||||
@click="edit"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -14,6 +14,7 @@ import { debounce } from 'lodash';
|
|||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
import invalidUrl from '~/lib/utils/invalid_url';
|
||||
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
|
||||
import { s__ } from '~/locale';
|
||||
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
|
||||
|
||||
import { timeRanges } from '~/vue_shared/constants';
|
||||
|
@ -24,6 +25,9 @@ import DashboardsDropdown from './dashboards_dropdown.vue';
|
|||
import RefreshButton from './refresh_button.vue';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
metricsSettings: s__('Metrics|Metrics Settings'),
|
||||
},
|
||||
components: {
|
||||
GlIcon,
|
||||
GlButton,
|
||||
|
@ -282,7 +286,8 @@ export default {
|
|||
data-testid="metrics-settings-button"
|
||||
icon="settings"
|
||||
:href="operationsSettingsPath"
|
||||
:title="s__('Metrics|Metrics Settings')"
|
||||
:title="$options.i18n.metricsSettings"
|
||||
:aria-label="$options.i18n.metricsSettings"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import Visibility from 'visibilityjs';
|
||||
import { mapActions } from 'vuex';
|
||||
import { n__, __ } from '~/locale';
|
||||
import { n__, __, s__ } from '~/locale';
|
||||
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
|
@ -45,6 +45,9 @@ const makeInterval = (length = 0, unit = 's') => {
|
|||
};
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
refreshDashboard: s__('Metrics|Refresh dashboard'),
|
||||
},
|
||||
components: {
|
||||
GlButtonGroup,
|
||||
GlButton,
|
||||
|
@ -148,7 +151,8 @@ export default {
|
|||
v-gl-tooltip
|
||||
class="gl-flex-grow-1"
|
||||
variant="default"
|
||||
:title="s__('Metrics|Refresh dashboard')"
|
||||
:title="$options.i18n.refreshDashboard"
|
||||
:aria-label="$options.i18n.refreshDashboard"
|
||||
icon="retry"
|
||||
@click="refresh"
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
buttonLabel: s__('MergeRequests|Resolve this thread in a new issue'),
|
||||
},
|
||||
name: 'ResolveWithIssueButton',
|
||||
components: {
|
||||
GlButton,
|
||||
|
@ -23,7 +27,8 @@ export default {
|
|||
<gl-button
|
||||
v-gl-tooltip
|
||||
:href="url"
|
||||
:title="s__('MergeRequests|Resolve this thread in a new issue')"
|
||||
:title="$options.i18n.buttonLabel"
|
||||
:aria-label="$options.i18n.buttonLabel"
|
||||
class="new-issue-for-discussion discussion-create-issue-btn"
|
||||
icon="issue-new"
|
||||
/>
|
||||
|
|
|
@ -49,18 +49,17 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-mr-3 gl-display-inline-block gl-vertical-align-bottom full-width-mobile">
|
||||
<div
|
||||
data-testid="sort-discussion-filter"
|
||||
class="gl-mr-3 gl-display-inline-block gl-vertical-align-bottom full-width-mobile"
|
||||
>
|
||||
<local-storage-sync
|
||||
:value="sortDirection"
|
||||
:storage-key="storageKey"
|
||||
:persist="persistSortOrder"
|
||||
@input="setDiscussionSortDirection({ direction: $event })"
|
||||
/>
|
||||
<gl-dropdown
|
||||
:text="dropdownText"
|
||||
data-testid="sort-discussion-filter"
|
||||
class="js-dropdown-text full-width-mobile"
|
||||
>
|
||||
<gl-dropdown :text="dropdownText" class="js-dropdown-text full-width-mobile">
|
||||
<gl-dropdown-item
|
||||
v-for="{ text, key, cls } in $options.SORT_OPTIONS"
|
||||
:key="key"
|
||||
|
|
|
@ -110,12 +110,12 @@ export default {
|
|||
mutationVariables() {
|
||||
return {
|
||||
projectPath: this.projectPath,
|
||||
enabled: this.value.enabled,
|
||||
cadence: this.value.cadence,
|
||||
olderThan: this.value.olderThan,
|
||||
keepN: this.value.keepN,
|
||||
nameRegex: this.value.nameRegex,
|
||||
nameRegexKeep: this.value.nameRegexKeep,
|
||||
enabled: this.prefilledForm.enabled,
|
||||
cadence: this.prefilledForm.cadence,
|
||||
olderThan: this.prefilledForm.olderThan,
|
||||
keepN: this.prefilledForm.keepN,
|
||||
nameRegex: this.prefilledForm.nameRegex,
|
||||
nameRegexKeep: this.prefilledForm.nameRegexKeep,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -291,8 +291,8 @@ export default {
|
|||
type="submit"
|
||||
:disabled="isSubmitButtonDisabled"
|
||||
:loading="showLoadingIcon"
|
||||
variant="success"
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
class="js-no-auto-disable gl-mr-4"
|
||||
>
|
||||
{{ $options.i18n.SET_CLEANUP_POLICY_BUTTON }}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlLink, GlBadge, GlButton, GlIcon } from '@gitlab/ui';
|
||||
import { setUrlParams } from '~/lib/utils/url_utility';
|
||||
import { __ } from '~/locale';
|
||||
import { BACK_URL_PARAM } from '~/releases/constants';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
editButton: __('Edit this release'),
|
||||
},
|
||||
name: 'ReleaseBlockHeader',
|
||||
components: {
|
||||
GlLink,
|
||||
|
@ -69,7 +73,8 @@ export default {
|
|||
variant="default"
|
||||
icon="pencil"
|
||||
class="gl-mr-3 js-edit-button ml-2 pb-2"
|
||||
:title="__('Edit this release')"
|
||||
:title="$options.i18n.editButton"
|
||||
:aria-label="$options.i18n.editButton"
|
||||
:href="editLink"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -96,6 +96,7 @@ export default {
|
|||
v-gl-tooltip
|
||||
:disabled="!selectedSortOption.sortable"
|
||||
:title="sortDirectionData.tooltip"
|
||||
:aria-label="sortDirectionData.tooltip"
|
||||
:icon="sortDirectionData.icon"
|
||||
@click="handleSortDirectionChange"
|
||||
/>
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
<script>
|
||||
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import { __, sprintf, s__ } from '~/locale';
|
||||
import ReviewerAvatarLink from './reviewer_avatar_link.vue';
|
||||
|
||||
const LOADING_STATE = 'loading';
|
||||
const SUCCESS_STATE = 'success';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
reRequestReview: __('Re-request review'),
|
||||
},
|
||||
components: {
|
||||
GlButton,
|
||||
GlIcon,
|
||||
|
@ -109,7 +112,8 @@ export default {
|
|||
<gl-button
|
||||
v-else-if="user.can_update_merge_request && user.reviewed"
|
||||
v-gl-tooltip.left
|
||||
:title="__('Re-request review')"
|
||||
:title="$options.i18n.reRequestReview"
|
||||
:aria-label="$options.i18n.reRequestReview"
|
||||
:loading="loadingStates[user.id] === $options.LOADING_STATE"
|
||||
class="float-right gl-text-gray-500!"
|
||||
size="small"
|
||||
|
|
|
@ -65,6 +65,7 @@ export default {
|
|||
<gl-button
|
||||
v-gl-tooltip.hover
|
||||
:title="$options.MSG_COPY"
|
||||
:aria-label="$options.MSG_COPY"
|
||||
:data-clipboard-text="value"
|
||||
icon="copy-to-clipboard"
|
||||
data-qa-selector="copy_button"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import appDataQuery from './queries/app_data.query.graphql';
|
||||
import fileResolver from './resolvers/file';
|
||||
import hasSubmittedChangesResolver from './resolvers/has_submitted_changes';
|
||||
import submitContentChangesResolver from './resolvers/submit_content_changes';
|
||||
|
@ -28,7 +29,8 @@ const createApolloProvider = (appData) => {
|
|||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
const mounts = appData.mounts.map((mount) => ({ __typename: 'Mount', ...mount }));
|
||||
|
||||
defaultClient.cache.writeData({
|
||||
defaultClient.cache.writeQuery({
|
||||
query: appDataQuery,
|
||||
data: {
|
||||
appData: {
|
||||
__typename: 'AppData',
|
||||
|
|
|
@ -56,6 +56,7 @@ export default {
|
|||
<gl-button
|
||||
v-gl-tooltip.hover
|
||||
:title="$options.copyURLTooltip"
|
||||
:aria-label="$options.copyURLTooltip"
|
||||
:data-clipboard-text="sshLink"
|
||||
data-qa-selector="copy_ssh_url_button"
|
||||
icon="copy-to-clipboard"
|
||||
|
@ -75,6 +76,7 @@ export default {
|
|||
<gl-button
|
||||
v-gl-tooltip.hover
|
||||
:title="$options.copyURLTooltip"
|
||||
:aria-label="$options.copyURLTooltip"
|
||||
:data-clipboard-text="httpLink"
|
||||
data-qa-selector="copy_http_url_button"
|
||||
icon="copy-to-clipboard"
|
||||
|
|
|
@ -363,6 +363,7 @@ export default {
|
|||
<gl-button
|
||||
v-gl-tooltip
|
||||
:title="sortDirectionTooltip"
|
||||
:aria-label="sortDirectionTooltip"
|
||||
:icon="sortDirectionIcon"
|
||||
class="flex-shrink-1"
|
||||
@click="handleSortDirectionClick"
|
||||
|
|
|
@ -46,7 +46,7 @@ export default {
|
|||
},
|
||||
activeLabel() {
|
||||
return this.labels.find(
|
||||
(label) => label.title.toLowerCase() === stripQuotes(this.currentValue),
|
||||
(label) => this.getLabelName(label).toLowerCase() === stripQuotes(this.currentValue),
|
||||
);
|
||||
},
|
||||
containerStyle() {
|
||||
|
@ -69,6 +69,21 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* There's an inconsistency between private and public API
|
||||
* for labels where label name is included in a different
|
||||
* property;
|
||||
*
|
||||
* Private API => `label.title`
|
||||
* Public API => `label.name`
|
||||
*
|
||||
* This method allows compatibility as there may be instances
|
||||
* where `config.fetchLabels` provided externally may still be
|
||||
* using either of the two APIs.
|
||||
*/
|
||||
getLabelName(label) {
|
||||
return label.name || label.title;
|
||||
},
|
||||
fetchLabelBySearchTerm(searchTerm) {
|
||||
this.loading = true;
|
||||
this.config
|
||||
|
@ -85,7 +100,7 @@ export default {
|
|||
});
|
||||
},
|
||||
searchLabels: debounce(function debouncedSearch({ data }) {
|
||||
this.fetchLabelBySearchTerm(data);
|
||||
if (!this.loading) this.fetchLabelBySearchTerm(data);
|
||||
}, DEBOUNCE_DELAY),
|
||||
},
|
||||
};
|
||||
|
@ -100,7 +115,7 @@ export default {
|
|||
>
|
||||
<template #view-token="{ inputValue, cssClasses, listeners }">
|
||||
<gl-token variant="search-value" :class="cssClasses" :style="containerStyle" v-on="listeners"
|
||||
>~{{ activeLabel ? activeLabel.title : inputValue }}</gl-token
|
||||
>~{{ activeLabel ? getLabelName(activeLabel) : inputValue }}</gl-token
|
||||
>
|
||||
</template>
|
||||
<template #suggestions>
|
||||
|
@ -114,13 +129,17 @@ export default {
|
|||
<gl-dropdown-divider v-if="defaultLabels.length" />
|
||||
<gl-loading-icon v-if="loading" />
|
||||
<template v-else>
|
||||
<gl-filtered-search-suggestion v-for="label in labels" :key="label.id" :value="label.title">
|
||||
<div class="gl-display-flex">
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="label in labels"
|
||||
:key="label.id"
|
||||
:value="getLabelName(label)"
|
||||
>
|
||||
<div class="gl-display-flex gl-align-items-center">
|
||||
<span
|
||||
:style="{ backgroundColor: label.color }"
|
||||
class="gl-display-inline-block mr-2 p-2"
|
||||
></span>
|
||||
<div>{{ label.title }}</div>
|
||||
<div>{{ getLabelName(label) }}</div>
|
||||
</div>
|
||||
</gl-filtered-search-suggestion>
|
||||
</template>
|
||||
|
|
|
@ -101,6 +101,7 @@ export default {
|
|||
:data-clipboard-target="target"
|
||||
:data-clipboard-text="text"
|
||||
:title="title"
|
||||
:aria-label="title"
|
||||
:category="category"
|
||||
icon="copy-to-clipboard"
|
||||
/>
|
||||
|
|
|
@ -316,6 +316,7 @@ class MergeRequest < ApplicationRecord
|
|||
}
|
||||
|
||||
scope :with_csv_entity_associations, -> { preload(:assignees, :approved_by_users, :author, :milestone, metrics: [:merged_by]) }
|
||||
scope :with_jira_integration_associations, -> { preload(:metrics, :assignees, :author, :target_project, :source_project) }
|
||||
|
||||
scope :by_target_branch_wildcard, ->(wildcard_branch_name) do
|
||||
where("target_branch LIKE ?", ApplicationRecord.sanitize_sql_like(wildcard_branch_name).tr('*', '%'))
|
||||
|
|
|
@ -343,6 +343,10 @@ class Namespace < ApplicationRecord
|
|||
Plan.default
|
||||
end
|
||||
|
||||
def paid?
|
||||
root? && actual_plan.paid?
|
||||
end
|
||||
|
||||
def actual_limits
|
||||
# We default to PlanLimits.new otherwise a lot of specs would fail
|
||||
# On production each plan should already have associated limits record
|
||||
|
|
|
@ -41,7 +41,6 @@ class PipelineSerializer < BaseSerializer
|
|||
def preloaded_relations
|
||||
[
|
||||
:cancelable_statuses,
|
||||
:latest_statuses_ordered_by_stage,
|
||||
:retryable_builds,
|
||||
:stages,
|
||||
:latest_statuses,
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
= _('Removing this group also removes all child projects, including archived projects, and their resources.')
|
||||
%br
|
||||
%strong= _('Removed group can not be restored!')
|
||||
= button_to _('Remove group'), '#', class: 'btn gl-button btn-danger js-confirm-danger', data: { 'confirm-danger-message' => remove_group_message(group) }
|
||||
|
||||
= render 'groups/settings/remove_button', group: group
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
- if group.paid?
|
||||
.gl-alert.gl-alert-info.gl-mb-5{ data: { testid: 'group-has-linked-subscription-alert' } }
|
||||
= sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
.gl-alert-body
|
||||
= html_escape(_("This group can't be removed because it is linked to a subscription. To remove this group, %{linkStart}link the subscription%{linkEnd} with a different group.")) % { linkStart: "<a href=\"#{help_page_path('subscriptions/index', anchor: 'change-the-linked-namespace')}\">".html_safe, linkEnd: '</a>'.html_safe }
|
||||
|
||||
= button_to _('Remove group'), '#', class: ['btn gl-button btn-danger js-confirm-danger', ('disabled' if group.paid?)], data: { 'confirm-danger-message' => remove_group_message(group), 'testid' => 'remove-group-button' }
|
|
@ -9,7 +9,7 @@
|
|||
= button_tag class: 'toggle-mobile-nav', type: 'button' do
|
||||
%span.sr-only= _("Open sidebar")
|
||||
= sprite_icon('hamburger', size: 18)
|
||||
.breadcrumbs-links.js-title-container{ data: { qa_selector: 'breadcrumb_links_content' } }
|
||||
.breadcrumbs-links{ data: { testid: 'breadcrumb-links', qa_selector: 'breadcrumb_links_content' } }
|
||||
%ul.list-unstyled.breadcrumbs-list.js-breadcrumbs-list
|
||||
- unless hide_top_links
|
||||
= header_title
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Drop unused preload from PipelineSerializer
|
||||
merge_request: 56988
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Always save default on empty values in Exp Policies
|
||||
merge_request: 57470
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update the Package settings to use the blue primary button
|
||||
merge_request: 57468
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add aria labels to icon buttons
|
||||
merge_request: 57261
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Resolve N + 1 for JIRA pulls
|
||||
merge_request: 57482
|
||||
author:
|
||||
type: performance
|
|
@ -3,6 +3,6 @@ name: usage_data_api
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41301
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267114
|
||||
milestone: '13.4'
|
||||
type: development
|
||||
group: group::product analytics
|
||||
type: ops
|
||||
group: group::product intelligence
|
||||
default_enabled: true
|
|
@ -6,7 +6,7 @@ type: howto
|
|||
---
|
||||
|
||||
|
||||
# Geo Glossary
|
||||
# Geo Glossary **(PREMIUM SELF)**
|
||||
|
||||
NOTE:
|
||||
We are updating the Geo documentation, user interface and commands to reflect these changes. Not all pages comply with
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: howto
|
||||
---
|
||||
|
||||
# Version-specific update instructions
|
||||
# Version-specific update instructions **(PREMIUM SELF)**
|
||||
|
||||
Review this page for update instructions for your version. These steps
|
||||
accompany the [general steps](updating_the_geo_nodes.md#general-update-steps)
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: howto
|
||||
---
|
||||
|
||||
# Setting up Geo
|
||||
# Setting up Geo **(PREMIUM SELF)**
|
||||
|
||||
These instructions assume you have a working instance of GitLab. They guide you through:
|
||||
|
||||
|
|
|
@ -4101,6 +4101,7 @@ finishes.
|
|||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/19298) in GitLab 13.2.
|
||||
|
||||
Use `release` to create a [release](../../user/project/releases/index.md).
|
||||
Requires the `release-cli` to be available in your GitLab Runner Docker or shell executor.
|
||||
|
||||
These keywords are supported:
|
||||
|
||||
|
@ -4122,6 +4123,69 @@ You must specify the Docker image to use for the `release-cli`:
|
|||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
```
|
||||
|
||||
#### `release-cli` for shell executors
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/release-cli/-/issues/21) in GitLab 13.8.
|
||||
|
||||
For GitLab Runner shell executors, you can download and install the `release-cli` manually for your [supported OS and architecture](https://release-cli-downloads.s3.amazonaws.com/latest/index.html).
|
||||
Once installed, the `release` keyword should be available to you.
|
||||
|
||||
**Install on Unix/Linux**
|
||||
|
||||
1. Download the binary for your system, in the following example for amd64 systems:
|
||||
|
||||
```shell
|
||||
curl --location --output /usr/local/bin/release-cli "https://release-cli-downloads.s3.amazonaws.com/latest/release-cli-linux-amd64"
|
||||
```
|
||||
|
||||
1. Give it permissions to execute:
|
||||
|
||||
```shell
|
||||
sudo chmod +x /usr/local/bin/release-cli
|
||||
```
|
||||
|
||||
1. Verify `release-cli` is available:
|
||||
|
||||
```shell
|
||||
$ release-cli -v
|
||||
|
||||
release-cli version 0.6.0
|
||||
```
|
||||
|
||||
**Install on Windows PowerShell**
|
||||
|
||||
1. Create a folder somewhere in your system, for example `C:\GitLab\Release-CLI\bin`
|
||||
|
||||
```shell
|
||||
New-Item -Path 'C:\GitLab\Release-CLI\bin' -ItemType Directory
|
||||
```
|
||||
|
||||
1. Download the executable file:
|
||||
|
||||
```shell
|
||||
PS C:\> Invoke-WebRequest -Uri "https://release-cli-downloads.s3.amazonaws.com/latest/release-cli-windows-amd64.exe" -OutFile "C:\GitLab\Release-CLI\bin\release-cli.exe"
|
||||
|
||||
Directory: C:\GitLab\Release-CLI
|
||||
Mode LastWriteTime Length Name
|
||||
---- ------------- ------ ----
|
||||
d----- 3/16/2021 4:17 AM bin
|
||||
|
||||
```
|
||||
|
||||
1. Add the directory to your `$env:PATH`:
|
||||
|
||||
```shell
|
||||
$env:PATH += ";C:\GitLab\Release-CLI\bin"
|
||||
```
|
||||
|
||||
1. Verify `release-cli` is available:
|
||||
|
||||
```shell
|
||||
PS C:\> release-cli -v
|
||||
|
||||
release-cli version 0.6.0
|
||||
```
|
||||
|
||||
#### `script`
|
||||
|
||||
All jobs except [trigger](#trigger) jobs must have the `script` keyword. A `release`
|
||||
|
|
|
@ -9956,6 +9956,54 @@ Status: `implemented`
|
|||
|
||||
Tiers: `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.epics_usage.g_project_management_users_setting_epic_due_date_as_fixed_monthly`
|
||||
|
||||
Counts of MAU setting epic due date as inherited
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210325060507_g_project_management_users_setting_epic_due_date_as_fixed_monthly.yml)
|
||||
|
||||
Group: `group::product planning`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.epics_usage.g_project_management_users_setting_epic_due_date_as_fixed_weekly`
|
||||
|
||||
Counts of WAU setting epic due date as fixed
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210325060623_g_project_management_users_setting_epic_due_date_as_fixed_weekly.yml)
|
||||
|
||||
Group: `group::product planning`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.epics_usage.g_project_management_users_setting_epic_due_date_as_inherited_monthly`
|
||||
|
||||
Counts of MAU setting epic due date as inherited
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210325060315_g_project_management_users_setting_epic_due_date_as_inherited_monthly.yml)
|
||||
|
||||
Group: `group::product planning`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.epics_usage.g_project_management_users_setting_epic_due_date_as_inherited_weekly`
|
||||
|
||||
Counts of WAU setting epic due date as inherited
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210325060903_g_project_management_users_setting_epic_due_date_as_inherited_weekly.yml)
|
||||
|
||||
Group: `group::product planning`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.epics_usage.g_project_management_users_setting_epic_start_date_as_fixed_monthly`
|
||||
|
||||
Counts of MAU setting epic start date as fixed
|
||||
|
|
|
@ -326,10 +326,11 @@ To enable it, you need to enable [ActionCable in-app mode](https://docs.gitlab.c
|
|||
## Cached issue count **(FREE SELF)**
|
||||
|
||||
> - [Introduced]([link-to-issue](https://gitlab.com/gitlab-org/gitlab/-/issues/243753)) in GitLab 13.9.
|
||||
> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default.
|
||||
> - It's disabled on GitLab.com.
|
||||
> - It's not recommended for production use.
|
||||
> - To use this feature in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-cached-issue-count) **(FREE SELF)**
|
||||
> - It was [deployed behind a feature flag](../../feature_flags.md), disabled by default.
|
||||
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/323493) in GitLab 13.10.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - It's recommended for production use.
|
||||
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-cached-issue-count) **(FREE SELF)**
|
||||
|
||||
WARNING:
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
|
@ -373,7 +374,7 @@ You can then see issue statuses in the issues list and the
|
|||
|
||||
## Enable or disable cached issue count **(FREE SELF)**
|
||||
|
||||
Cached issue count in the left sidebar is under development and not ready for production use. It is
|
||||
Cached issue count in the left sidebar is under development but ready for production use. It is
|
||||
deployed behind a feature flag that is **enabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
|
||||
can disable it.
|
||||
|
|
|
@ -8,7 +8,7 @@ module API
|
|||
|
||||
namespace 'usage_data' do
|
||||
before do
|
||||
not_found! unless Feature.enabled?(:usage_data_api, default_enabled: true)
|
||||
not_found! unless Feature.enabled?(:usage_data_api, default_enabled: :yaml, type: :ops)
|
||||
forbidden!('Invalid CSRF token is provided') unless verified_request?
|
||||
end
|
||||
|
||||
|
|
|
@ -75,11 +75,14 @@ module API
|
|||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def authorized_merge_requests
|
||||
MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?).execute
|
||||
MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?)
|
||||
.execute.with_jira_integration_associations
|
||||
end
|
||||
|
||||
def authorized_merge_requests_for_project(project)
|
||||
MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?, project_id: project.id).execute
|
||||
MergeRequestsFinder
|
||||
.new(current_user, authorized_only: !current_user.admin?, project_id: project.id)
|
||||
.execute.with_jira_integration_associations
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
|
|
@ -45,7 +45,7 @@ module Gitlab
|
|||
# Initialize gon.features with any flags that should be
|
||||
# made globally available to the frontend
|
||||
push_frontend_feature_flag(:snippets_binary_blob, default_enabled: false)
|
||||
push_frontend_feature_flag(:usage_data_api, default_enabled: true)
|
||||
push_frontend_feature_flag(:usage_data_api, type: :ops, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:security_auto_fix, default_enabled: false)
|
||||
end
|
||||
|
||||
|
|
|
@ -51,6 +51,18 @@
|
|||
aggregation: daily
|
||||
feature_flag: track_epics_activity
|
||||
|
||||
- name: g_project_management_users_setting_epic_due_date_as_fixed
|
||||
category: epics_usage
|
||||
redis_slot: project_management
|
||||
aggregation: daily
|
||||
feature_flag: track_epics_activity
|
||||
|
||||
- name: g_project_management_users_setting_epic_due_date_as_inherited
|
||||
category: epics_usage
|
||||
redis_slot: project_management
|
||||
aggregation: daily
|
||||
feature_flag: track_epics_activity
|
||||
|
||||
- name: g_project_management_epic_issue_added
|
||||
category: epics_usage
|
||||
redis_slot: project_management
|
||||
|
|
|
@ -2917,6 +2917,9 @@ msgstr ""
|
|||
msgid "AlertSettings|Delete integration"
|
||||
msgstr ""
|
||||
|
||||
msgid "AlertSettings|Edit integration"
|
||||
msgstr ""
|
||||
|
||||
msgid "AlertSettings|Edit payload"
|
||||
msgstr ""
|
||||
|
||||
|
@ -31061,6 +31064,9 @@ msgstr ""
|
|||
msgid "This group"
|
||||
msgstr ""
|
||||
|
||||
msgid "This group can't be removed because it is linked to a subscription. To remove this group, %{linkStart}link the subscription%{linkEnd} with a different group."
|
||||
msgstr ""
|
||||
|
||||
msgid "This group cannot be invited to a project inside a group with enforced SSO"
|
||||
msgstr ""
|
||||
|
||||
|
@ -31070,6 +31076,9 @@ msgstr ""
|
|||
msgid "This group has been scheduled for permanent removal on %{date}"
|
||||
msgstr ""
|
||||
|
||||
msgid "This group is linked to a subscription"
|
||||
msgstr ""
|
||||
|
||||
msgid "This group, including all subgroups, projects and git repositories, will be reachable from only the specified IP address ranges."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
|
|||
select('7 days', from: 'Remove tags older than:')
|
||||
fill_in('Remove tags matching:', with: '.*-production')
|
||||
|
||||
submit_button = find('.btn.gl-button.btn-success')
|
||||
submit_button = find('[data-testid="save-button"')
|
||||
expect(submit_button).not_to be_disabled
|
||||
submit_button.click
|
||||
end
|
||||
|
@ -53,7 +53,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
|
|||
within '#js-registry-policies' do
|
||||
fill_in('Remove tags matching:', with: '*-production')
|
||||
|
||||
submit_button = find('.btn.gl-button.btn-success')
|
||||
submit_button = find('[data-testid="save-button"')
|
||||
expect(submit_button).not_to be_disabled
|
||||
submit_button.click
|
||||
end
|
||||
|
|
|
@ -16,18 +16,18 @@ RSpec.describe 'Subgroup Issuables', :js do
|
|||
it 'shows the full subgroup title when issues index page is empty' do
|
||||
visit project_issues_path(project)
|
||||
|
||||
expect_to_have_full_subgroup_title
|
||||
expect_to_have_breadcrumb_links
|
||||
end
|
||||
|
||||
it 'shows the full subgroup title when merge requests index page is empty' do
|
||||
visit project_merge_requests_path(project)
|
||||
|
||||
expect_to_have_full_subgroup_title
|
||||
expect_to_have_breadcrumb_links
|
||||
end
|
||||
|
||||
def expect_to_have_full_subgroup_title
|
||||
title = find('.breadcrumbs-links')
|
||||
def expect_to_have_breadcrumb_links
|
||||
links = find('[data-testid="breadcrumb-links"]')
|
||||
|
||||
expect(title).to have_content 'group subgroup project'
|
||||
expect(links).to have_content 'group subgroup project'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -264,18 +264,18 @@ describe('Api', () => {
|
|||
it('fetches group labels', (done) => {
|
||||
const options = { params: { search: 'foo' } };
|
||||
const expectedGroup = 'gitlab-org';
|
||||
const expectedUrl = `${dummyUrlRoot}/groups/${expectedGroup}/-/labels`;
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${expectedGroup}/labels`;
|
||||
mock.onGet(expectedUrl).reply(httpStatus.OK, [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Foo Label',
|
||||
name: 'Foo Label',
|
||||
},
|
||||
]);
|
||||
|
||||
Api.groupLabels(expectedGroup, options)
|
||||
.then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0].title).toBe('Foo Label');
|
||||
expect(res[0].name).toBe('Foo Label');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
@ -593,7 +593,7 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('newLabel', () => {
|
||||
it('creates a new label', (done) => {
|
||||
it('creates a new project label', (done) => {
|
||||
const namespace = 'some namespace';
|
||||
const project = 'some project';
|
||||
const labelData = { some: 'data' };
|
||||
|
@ -618,26 +618,23 @@ describe('Api', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('creates a group label', (done) => {
|
||||
it('creates a new group label', (done) => {
|
||||
const namespace = 'group/subgroup';
|
||||
const labelData = { some: 'data' };
|
||||
const labelData = { name: 'Foo', color: '#000000' };
|
||||
const expectedUrl = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace);
|
||||
const expectedData = {
|
||||
label: labelData,
|
||||
};
|
||||
mock.onPost(expectedUrl).reply((config) => {
|
||||
expect(config.data).toBe(JSON.stringify(expectedData));
|
||||
expect(config.data).toBe(JSON.stringify({ color: labelData.color }));
|
||||
|
||||
return [
|
||||
httpStatus.OK,
|
||||
{
|
||||
name: 'test',
|
||||
...labelData,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
Api.newLabel(namespace, undefined, labelData, (response) => {
|
||||
expect(response.name).toBe('test');
|
||||
expect(response.name).toBe('Foo');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ exports[`Design management pagination component renders navigation buttons 1`] =
|
|||
class="gl-mx-5"
|
||||
>
|
||||
<gl-button-stub
|
||||
aria-label="Go to previous design"
|
||||
buttontextclasses=""
|
||||
category="primary"
|
||||
class="js-previous-design"
|
||||
|
@ -24,6 +25,7 @@ exports[`Design management pagination component renders navigation buttons 1`] =
|
|||
/>
|
||||
|
||||
<gl-button-stub
|
||||
aria-label="Go to next design"
|
||||
buttontextclasses=""
|
||||
category="primary"
|
||||
class="js-next-design"
|
||||
|
|
|
@ -77,33 +77,47 @@ describe('Settings Form', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const mountComponentWithApollo = ({ provide = defaultProvidedValues, resolver } = {}) => {
|
||||
const mountComponentWithApollo = ({
|
||||
provide = defaultProvidedValues,
|
||||
mutationResolver,
|
||||
queryPayload = expirationPolicyPayload(),
|
||||
} = {}) => {
|
||||
localVue.use(VueApollo);
|
||||
|
||||
const requestHandlers = [
|
||||
[updateContainerExpirationPolicyMutation, resolver],
|
||||
[expirationPolicyQuery, jest.fn().mockResolvedValue(expirationPolicyPayload())],
|
||||
[updateContainerExpirationPolicyMutation, mutationResolver],
|
||||
[expirationPolicyQuery, jest.fn().mockResolvedValue(queryPayload)],
|
||||
];
|
||||
|
||||
fakeApollo = createMockApollo(requestHandlers);
|
||||
|
||||
// This component does not do the query directly, but we need a proper cache to update
|
||||
fakeApollo.defaultClient.cache.writeQuery({
|
||||
query: expirationPolicyQuery,
|
||||
variables: {
|
||||
projectPath: provide.projectPath,
|
||||
},
|
||||
...expirationPolicyPayload(),
|
||||
...queryPayload,
|
||||
});
|
||||
|
||||
// we keep in sync what prop we pass to the component with the cache
|
||||
const {
|
||||
data: {
|
||||
project: { containerExpirationPolicy: value },
|
||||
},
|
||||
} = queryPayload;
|
||||
|
||||
mountComponent({
|
||||
provide,
|
||||
props: {
|
||||
...defaultProps,
|
||||
value,
|
||||
},
|
||||
config: {
|
||||
localVue,
|
||||
apolloProvider: fakeApollo,
|
||||
},
|
||||
});
|
||||
|
||||
return requestHandlers.map((resolvers) => resolvers[1]);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -253,19 +267,44 @@ describe('Settings Form', () => {
|
|||
expect(findSaveButton().attributes('type')).toBe('submit');
|
||||
});
|
||||
|
||||
it('dispatches the correct apollo mutation', async () => {
|
||||
const [expirationPolicyMutationResolver] = mountComponentWithApollo({
|
||||
resolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
|
||||
it('dispatches the correct apollo mutation', () => {
|
||||
const mutationResolver = jest.fn().mockResolvedValue(expirationPolicyMutationPayload());
|
||||
mountComponentWithApollo({
|
||||
mutationResolver,
|
||||
});
|
||||
|
||||
findForm().trigger('submit');
|
||||
await expirationPolicyMutationResolver();
|
||||
expect(expirationPolicyMutationResolver).toHaveBeenCalled();
|
||||
|
||||
expect(mutationResolver).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('saves the default values when a value is missing did not change the default options', async () => {
|
||||
const mutationResolver = jest.fn().mockResolvedValue(expirationPolicyMutationPayload());
|
||||
mountComponentWithApollo({
|
||||
mutationResolver,
|
||||
queryPayload: expirationPolicyPayload({ keepN: null, cadence: null, olderThan: null }),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
findForm().trigger('submit');
|
||||
|
||||
expect(mutationResolver).toHaveBeenCalledWith({
|
||||
input: {
|
||||
cadence: 'EVERY_DAY',
|
||||
enabled: true,
|
||||
keepN: 'TEN_TAGS',
|
||||
nameRegex: 'asdasdssssdfdf',
|
||||
nameRegexKeep: 'sss',
|
||||
olderThan: 'NINETY_DAYS',
|
||||
projectPath: 'path',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('tracks the submit event', () => {
|
||||
mountComponentWithApollo({
|
||||
resolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
|
||||
mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
|
||||
});
|
||||
|
||||
findForm().trigger('submit');
|
||||
|
@ -274,12 +313,12 @@ describe('Settings Form', () => {
|
|||
});
|
||||
|
||||
it('show a success toast when submit succeed', async () => {
|
||||
const handlers = mountComponentWithApollo({
|
||||
resolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
|
||||
mountComponentWithApollo({
|
||||
mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
|
||||
});
|
||||
|
||||
findForm().trigger('submit');
|
||||
await Promise.all(handlers);
|
||||
await waitForPromises();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, {
|
||||
|
@ -290,14 +329,14 @@ describe('Settings Form', () => {
|
|||
describe('when submit fails', () => {
|
||||
describe('user recoverable errors', () => {
|
||||
it('when there is an error is shown in a toast', async () => {
|
||||
const handlers = mountComponentWithApollo({
|
||||
resolver: jest
|
||||
mountComponentWithApollo({
|
||||
mutationResolver: jest
|
||||
.fn()
|
||||
.mockResolvedValue(expirationPolicyMutationPayload({ errors: ['foo'] })),
|
||||
});
|
||||
|
||||
findForm().trigger('submit');
|
||||
await Promise.all(handlers);
|
||||
await waitForPromises();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('foo', {
|
||||
|
@ -308,13 +347,12 @@ describe('Settings Form', () => {
|
|||
|
||||
describe('global errors', () => {
|
||||
it('shows an error', async () => {
|
||||
const handlers = mountComponentWithApollo({
|
||||
resolver: jest.fn().mockRejectedValue(expirationPolicyMutationPayload()),
|
||||
mountComponentWithApollo({
|
||||
mutationResolver: jest.fn().mockRejectedValue(expirationPolicyMutationPayload()),
|
||||
});
|
||||
|
||||
findForm().trigger('submit');
|
||||
await Promise.all(handlers);
|
||||
await wrapper.vm.$nextTick();
|
||||
await waitForPromises();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE, {
|
||||
|
|
|
@ -40,6 +40,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
|
|||
tag="div"
|
||||
>
|
||||
<gl-button-stub
|
||||
aria-label="Copy URL"
|
||||
buttontextclasses=""
|
||||
category="primary"
|
||||
class="d-inline-flex"
|
||||
|
@ -82,6 +83,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
|
|||
tag="div"
|
||||
>
|
||||
<gl-button-stub
|
||||
aria-label="Copy URL"
|
||||
buttontextclasses=""
|
||||
category="primary"
|
||||
class="d-inline-flex"
|
||||
|
|
|
@ -118,6 +118,22 @@ describe('LabelToken', () => {
|
|||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
describe('getLabelName', () => {
|
||||
it('returns value of `name` or `title` property present in provided label param', () => {
|
||||
let mockLabel = {
|
||||
title: 'foo',
|
||||
};
|
||||
|
||||
expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.title);
|
||||
|
||||
mockLabel = {
|
||||
name: 'foo',
|
||||
};
|
||||
|
||||
expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.name);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchLabelBySearchTerm', () => {
|
||||
it('calls `config.fetchLabels` with provided searchTerm param', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchLabels');
|
||||
|
|
|
@ -1415,6 +1415,12 @@ RSpec.describe Namespace do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#paid?' do
|
||||
it 'returns false for a root namespace with a free plan' do
|
||||
expect(namespace.paid?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#shared_runners_setting' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::V3::Github do
|
||||
let(:user) { create(:user) }
|
||||
let(:unauthorized_user) { create(:user) }
|
||||
let(:admin) { create(:user, :admin) }
|
||||
let(:project) { create(:project, :repository, creator: user) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:unauthorized_user) { create(:user) }
|
||||
let_it_be(:admin) { create(:user, :admin) }
|
||||
let_it_be(:project) { create(:project, :repository, creator: user) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
@ -210,14 +210,14 @@ RSpec.describe API::V3::Github do
|
|||
end
|
||||
|
||||
describe 'repo pulls' do
|
||||
let(:project2) { create(:project, :repository, creator: user) }
|
||||
let(:assignee) { create(:user) }
|
||||
let(:assignee2) { create(:user) }
|
||||
let!(:merge_request) do
|
||||
let_it_be(:project2) { create(:project, :repository, creator: user) }
|
||||
let_it_be(:assignee) { create(:user) }
|
||||
let_it_be(:assignee2) { create(:user) }
|
||||
let_it_be(:merge_request) do
|
||||
create(:merge_request, source_project: project, target_project: project, author: user, assignees: [assignee])
|
||||
end
|
||||
|
||||
let!(:merge_request_2) do
|
||||
let_it_be(:merge_request_2) do
|
||||
create(:merge_request, source_project: project2, target_project: project2, author: user, assignees: [assignee, assignee2])
|
||||
end
|
||||
|
||||
|
@ -225,26 +225,54 @@ RSpec.describe API::V3::Github do
|
|||
project2.add_maintainer(user)
|
||||
end
|
||||
|
||||
def perform_request
|
||||
jira_get v3_api(route, user)
|
||||
end
|
||||
|
||||
describe 'GET /-/jira/pulls' do
|
||||
let(:route) { '/repos/-/jira/pulls' }
|
||||
|
||||
it 'returns an array of merge requests with github format' do
|
||||
jira_get v3_api('/repos/-/jira/pulls', user)
|
||||
perform_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.size).to eq(2)
|
||||
expect(response).to match_response_schema('entities/github/pull_requests')
|
||||
end
|
||||
|
||||
it 'returns multiple merge requests without N + 1' do
|
||||
perform_request
|
||||
|
||||
control_count = ActiveRecord::QueryRecorder.new { perform_request }.count
|
||||
|
||||
create(:merge_request, source_project: project, source_branch: 'fix')
|
||||
|
||||
expect { perform_request }.not_to exceed_query_limit(control_count)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /repos/:namespace/:project/pulls' do
|
||||
let(:route) { "/repos/#{project.namespace.path}/#{project.path}/pulls" }
|
||||
|
||||
it 'returns an array of merge requests for the proper project in github format' do
|
||||
jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/pulls", user)
|
||||
perform_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.size).to eq(1)
|
||||
expect(response).to match_response_schema('entities/github/pull_requests')
|
||||
end
|
||||
|
||||
it 'returns multiple merge requests without N + 1' do
|
||||
perform_request
|
||||
|
||||
control_count = ActiveRecord::QueryRecorder.new { perform_request }.count
|
||||
|
||||
create(:merge_request, source_project: project, source_branch: 'fix')
|
||||
|
||||
expect { perform_request }.not_to exceed_query_limit(control_count)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /repos/:namespace/:project/pulls/:id' do
|
||||
|
|
|
@ -202,7 +202,7 @@ RSpec.describe PipelineSerializer do
|
|||
# Existing numbers are high and require performance optimization
|
||||
# Ongoing issue:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/225156
|
||||
expected_queries = Gitlab.ee? ? 85 : 76
|
||||
expected_queries = Gitlab.ee? ? 82 : 76
|
||||
|
||||
expect(recorded.count).to be_within(2).of(expected_queries)
|
||||
expect(recorded.cached_count).to eq(0)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
RSpec.shared_examples 'error tracking index page' do
|
||||
it 'renders the error index page', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
|
||||
within('div.js-title-container') do
|
||||
within('[data-testid="breadcrumb-links"]') do
|
||||
expect(page).to have_content(project.namespace.name)
|
||||
expect(page).to have_content(project.name)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'groups/settings/_remove.html.haml' do
|
||||
describe 'render' do
|
||||
it 'enables the Remove group button for a group' do
|
||||
group = build(:group)
|
||||
|
||||
render 'groups/settings/remove', group: group
|
||||
|
||||
expect(rendered).to have_selector '[data-testid="remove-group-button"]'
|
||||
expect(rendered).not_to have_selector '[data-testid="remove-group-button"].disabled'
|
||||
expect(rendered).not_to have_selector '[data-testid="group-has-linked-subscription-alert"]'
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue