Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-05 06:13:16 +00:00
parent 678db4e394
commit 71701f2da5
68 changed files with 920 additions and 265 deletions

View File

@ -22,6 +22,7 @@
variables:
DECOMPOSED_DB: "true"
GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: "main"
GITLAB_USE_MODEL_LOAD_BALANCING: "true"
.rspec-base:
extends: .rails-job-base

View File

@ -138,7 +138,7 @@ export default {
/>
</form>
<template #modal-footer>
<gl-button @click="onCancel">{{ s__('Cancel') }}</gl-button>
<gl-button @click="onCancel">{{ __('Cancel') }}</gl-button>
<gl-button
:disabled="!canSubmit"
category="secondary"

View File

@ -3,7 +3,7 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import createFlash from '~/flash';
import { number } from '~/lib/utils/unit_format';
import { s__ } from '~/locale';
import { __, s__ } from '~/locale';
import usageTrendsCountQuery from '../graphql/queries/usage_trends_count.query.graphql';
const defaultPrecision = 0;
@ -52,7 +52,7 @@ export default {
mergeRequests: s__('UsageTrends|Merge requests'),
pipelines: s__('UsageTrends|Pipelines'),
},
loadCountsError: s__('Could not load usage counts. Please refresh the page to try again.'),
loadCountsError: __('Could not load usage counts. Please refresh the page to try again.'),
},
};
</script>

View File

@ -2,7 +2,7 @@
import { GlSprintf, GlModal } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { __, s__ } from '~/locale';
import Badge from './badge.vue';
import BadgeForm from './badge_form.vue';
import BadgeList from './badge_list.vue';
@ -25,13 +25,13 @@ export default {
...mapState(['badgeInModal', 'isEditing']),
primaryProps() {
return {
text: s__('Delete badge'),
text: __('Delete badge'),
attributes: [{ category: 'primary' }, { variant: 'danger' }],
};
},
cancelProps() {
return {
text: s__('Cancel'),
text: __('Cancel'),
};
},
},

View File

@ -1,7 +1,7 @@
<script>
import { GlModal, GlSprintf, GlLink, GlButton } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { s__ } from '~/locale';
import { __, s__ } from '~/locale';
import Tracking from '~/tracking';
const trackingMixin = Tracking.mixin();
@ -69,7 +69,7 @@ export default {
},
},
i18n: {
modalTitle: s__("That's it, well done!"),
modalTitle: __("That's it, well done!"),
pipelinesButton: s__('MR widget|See your pipeline in action'),
mergeRequestButton: s__('MR widget|Back to the Merge request'),
bodyMessage: s__(

View File

@ -159,7 +159,7 @@ export default {
)
}}</span>
<template #modal-footer>
<gl-button variant="secondary" @click="handleCancel">{{ s__('Cancel') }}</gl-button>
<gl-button variant="secondary" @click="handleCancel">{{ __('Cancel') }}</gl-button>
<template v-if="confirmCleanup">
<gl-button
:disabled="!canSubmit"

View File

@ -91,7 +91,7 @@ export const I18N_INSTALL_AGENT_MODAL = {
),
basicInstallTitle: s__('ClusterAgents|Recommended installation method'),
basicInstallBody: s__(
basicInstallBody: __(
`Open a CLI and connect to the cluster you want to install the Agent in. Use this installation method to minimize any manual steps. The token is already included in the command.`,
),
@ -100,7 +100,7 @@ export const I18N_INSTALL_AGENT_MODAL = {
'ClusterAgents|For alternative installation methods %{linkStart}go to the documentation%{linkEnd}.',
),
registrationErrorTitle: s__('Failed to register Agent'),
registrationErrorTitle: __('Failed to register Agent'),
unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'),
};

View File

@ -62,7 +62,7 @@ export default {
</gl-sprintf>
{{ s__('DeployTokens|This action cannot be undone.') }}
<template #modal-footer>
<gl-button category="secondary" @click="cancelHandler">{{ s__('Cancel') }}</gl-button>
<gl-button category="secondary" @click="cancelHandler">{{ __('Cancel') }}</gl-button>
<gl-button
category="primary"
variant="danger"

View File

@ -50,7 +50,7 @@ export default {
mixins: [glFeatureFlagsMixin(), IdState({ idProp: (vm) => vm.diffFile.file_hash })],
i18n: {
...DIFF_FILE_HEADER,
compareButtonLabel: s__('Compare submodule commit revisions'),
compareButtonLabel: __('Compare submodule commit revisions'),
},
props: {
discussionPath: {
@ -130,7 +130,7 @@ export default {
const truncatedOldSha = escape(truncateSha(this.diffFile.submodule_compare.old_sha));
const truncatedNewSha = escape(truncateSha(this.diffFile.submodule_compare.new_sha));
return sprintf(
s__('Compare %{oldCommitId}...%{newCommitId}'),
__('Compare %{oldCommitId}...%{newCommitId}'),
{
oldCommitId: `<span class="commit-sha">${truncatedOldSha}</span>`,
newCommitId: `<span class="commit-sha">${truncatedNewSha}</span>`,

View File

@ -1,6 +1,6 @@
<script>
import { GlTooltipDirective, GlModal } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import { __, s__, sprintf } from '~/locale';
import eventHub from '../event_hub';
export default {
@ -27,7 +27,7 @@ export default {
},
cancelProps() {
return {
text: s__('Cancel'),
text: __('Cancel'),
};
},
confirmDeleteMessage() {

View File

@ -6,7 +6,7 @@ import Visibility from 'visibilityjs';
import createFlash from '~/flash';
import Poll from '../../lib/utils/poll';
import { getParameterByName } from '../../lib/utils/url_utility';
import { s__ } from '../../locale';
import { s__, __ } from '../../locale';
import tabs from '../../vue_shared/components/navigation_tabs.vue';
import tablePagination from '../../vue_shared/components/pagination/table_pagination.vue';
import container from '../components/container.vue';
@ -207,13 +207,13 @@ export default {
tabs() {
return [
{
name: s__('Available'),
name: __('Available'),
scope: 'available',
count: this.state.availableCounter,
isActive: this.scope === 'available',
},
{
name: s__('Stopped'),
name: __('Stopped'),
scope: 'stopped',
count: this.state.stoppedCounter,
isActive: this.scope === 'stopped',

View File

@ -2,7 +2,7 @@
import { GlModal, GlButton } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import createFlash from '~/flash';
import { __, sprintf, s__ } from '~/locale';
import { __, sprintf } from '~/locale';
import { modalTypes } from '../../constants';
import { trimPathComponents, getPathParent } from '../../utils';
@ -58,7 +58,7 @@ export default {
if (this.modalType === modalTypes.rename) {
if (this.entries[this.entryName] && !this.entries[this.entryName].deleted) {
createFlash({
message: sprintf(s__('The name "%{name}" is already taken in this directory.'), {
message: sprintf(__('The name "%{name}" is already taken in this directory.'), {
name: this.entryName,
}),
fadeTransition: false,

View File

@ -517,7 +517,7 @@ export default {
<gl-empty-state
v-else-if="!hasGroups"
:title="s__('BulkImport|You have no groups to import')"
:description="s__('Check your source instance permissions.')"
:description="__('Check your source instance permissions.')"
/>
<template v-else>
<div

View File

@ -166,7 +166,7 @@ export default {
</gl-sprintf>
</p>
<template #modal-footer>
<gl-button category="secondary" @click="cancelHandler">{{ s__('Cancel') }}</gl-button>
<gl-button category="secondary" @click="cancelHandler">{{ __('Cancel') }}</gl-button>
</template>
</gl-modal>
</div>

View File

@ -4,7 +4,7 @@ import Visibility from 'visibilityjs';
import createFlash from '~/flash';
import Poll from '~/lib/utils/poll';
import { visitUrl } from '~/lib/utils/url_utility';
import { __, s__, sprintf } from '~/locale';
import { __, sprintf } from '~/locale';
import {
IssuableStatus,
IssuableStatusText,
@ -250,7 +250,7 @@ export default {
return false;
},
defaultErrorMessage() {
return sprintf(s__('Error updating %{issuableType}'), { issuableType: this.issuableType });
return sprintf(__('Error updating %{issuableType}'), { issuableType: this.issuableType });
},
isClosed() {
return this.issuableStatus === IssuableStatus.Closed;
@ -437,7 +437,7 @@ export default {
})
.catch(() => {
createFlash({
message: sprintf(s__('Error deleting %{issuableType}'), {
message: sprintf(__('Error deleting %{issuableType}'), {
issuableType: this.issuableType,
}),
});

View File

@ -2,7 +2,7 @@
import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import $ from 'jquery';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import { __, sprintf } from '~/locale';
import TaskList from '../../task_list';
import animateMixin from '../mixins/animate';
@ -104,7 +104,7 @@ export default {
taskListUpdateError() {
createFlash({
message: sprintf(
s__(
__(
'Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again.',
),
{

View File

@ -1,4 +1,4 @@
import { s__ } from '~/locale';
import { __, s__ } from '~/locale';
/**
* Generates empty state messages for Service Desk issues list.
@ -25,7 +25,7 @@ export function generateMessages(emptyStateMeta) {
const commonDescription = `
<span>${serviceDeskSupportedMessage}</span>
<a href="${serviceDeskHelpPage}">${s__('Learn more.')}</a>`;
<a href="${serviceDeskHelpPage}">${__('Learn more.')}</a>`;
return {
serviceDeskEnabledAndCanEditProjectSettings: {
@ -60,7 +60,7 @@ export function generateMessages(emptyStateMeta) {
'ServiceDesk|To enable Service Desk on this instance, an instance administrator must first set up incoming email.',
),
primaryLink: incomingEmailHelpPage,
primaryText: s__('Learn more.'),
primaryText: __('Learn more.'),
},
serviceDeskIsNotEnabled: {
title: s__('ServiceDesk|Service Desk is not enabled'),

View File

@ -1,55 +1,43 @@
<script>
import { GlAlert, GlButton, GlLink, GlSprintf } from '@gitlab/ui';
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { mapState, mapMutations } from 'vuex';
import { retrieveAlert, getLocation } from '~/jira_connect/subscriptions/utils';
import { retrieveAlert } from '~/jira_connect/subscriptions/utils';
import { SET_ALERT } from '../store/mutation_types';
import SubscriptionsList from './subscriptions_list.vue';
import AddNamespaceButton from './add_namespace_button.vue';
import SignInButton from './sign_in_button.vue';
export default {
name: 'JiraConnectApp',
components: {
GlAlert,
GlButton,
GlLink,
GlSprintf,
SubscriptionsList,
AddNamespaceButton,
SignInButton,
},
inject: {
usersPath: {
default: '',
},
},
data() {
return {
location: '',
};
},
computed: {
...mapState(['alert']),
usersPathWithReturnTo() {
if (this.location) {
return `${this.usersPath}?return_to=${this.location}`;
}
return this.usersPath;
},
shouldShowAlert() {
return Boolean(this.alert?.message);
},
userSignedIn() {
return Boolean(!this.usersPath);
},
},
created() {
this.setInitialAlert();
this.setLocation();
},
methods: {
...mapMutations({
setAlert: SET_ALERT,
}),
async setLocation() {
this.location = await getLocation();
},
setInitialAlert() {
const { linkUrl, title, message, variant } = retrieveAlert() || {};
this.setAlert({ linkUrl, title, message, variant });
@ -82,15 +70,7 @@ export default {
<div class="jira-connect-app-body gl-my-7 gl-px-5 gl-pb-4">
<div class="gl-display-flex gl-justify-content-end">
<gl-button
v-if="usersPath"
category="primary"
variant="info"
class="gl-align-self-center"
:href="usersPathWithReturnTo"
target="_blank"
>{{ s__('Integrations|Sign in to add namespaces') }}</gl-button
>
<sign-in-button v-if="!userSignedIn" :users-path="usersPath" />
<add-namespace-button v-else />
</div>

View File

@ -0,0 +1,50 @@
<script>
import { GlButton } from '@gitlab/ui';
import { getLocation } from '~/jira_connect/subscriptions/utils';
import { objectToQuery } from '~/lib/utils/url_utility';
export default {
components: {
GlButton,
},
props: {
usersPath: {
type: String,
required: true,
},
},
data() {
return {
location: '',
};
},
computed: {
usersPathWithReturnTo() {
if (this.location) {
const queryParams = {
return_to: this.location,
};
return `${this.usersPath}?${objectToQuery(queryParams)}`;
}
return this.usersPath;
},
},
created() {
this.setLocation();
},
methods: {
async setLocation() {
this.location = await getLocation();
},
},
};
</script>
<template>
<gl-button category="primary" variant="info" :href="usersPathWithReturnTo" target="_blank">
<slot>
{{ s__('Integrations|Sign in to add namespaces') }}
</slot>
</gl-button>
</template>

View File

@ -11,6 +11,9 @@ import { getLocation, sizeToParent } from './utils';
const store = createStore();
/**
* Add `return_to` query param to all HAML-defined GitLab sign in links.
*/
const updateSignInLinks = async () => {
const location = await getLocation();
Array.from(document.querySelectorAll('.js-jira-connect-sign-in')).forEach((el) => {

View File

@ -14,33 +14,33 @@ import { s__, n__, __, sprintf } from '../../../locale';
export const getMonthNames = (abbreviated) => {
if (abbreviated) {
return [
s__('Jan'),
s__('Feb'),
s__('Mar'),
s__('Apr'),
s__('May'),
s__('Jun'),
s__('Jul'),
s__('Aug'),
s__('Sep'),
s__('Oct'),
s__('Nov'),
s__('Dec'),
__('Jan'),
__('Feb'),
__('Mar'),
__('Apr'),
__('May'),
__('Jun'),
__('Jul'),
__('Aug'),
__('Sep'),
__('Oct'),
__('Nov'),
__('Dec'),
];
}
return [
s__('January'),
s__('February'),
s__('March'),
s__('April'),
s__('May'),
s__('June'),
s__('July'),
s__('August'),
s__('September'),
s__('October'),
s__('November'),
s__('December'),
__('January'),
__('February'),
__('March'),
__('April'),
__('May'),
__('June'),
__('July'),
__('August'),
__('September'),
__('October'),
__('November'),
__('December'),
];
};

View File

@ -130,7 +130,7 @@ export default {
}}
<a :href="clusterApplicationsDocumentationPath">
<strong>
{{ s__('View Documentation') }}
{{ __('View Documentation') }}
</strong>
</a>
</gl-alert>

View File

@ -63,7 +63,7 @@ export default {
return !(this.form.fileName && !this.form.fileName.endsWith('.yml'));
},
fileNameFeedback() {
return !this.fileNameState ? s__('The file name should have a .yml extension') : '';
return !this.fileNameState ? __('The file name should have a .yml extension') : '';
},
},
mounted() {

View File

@ -78,8 +78,8 @@ export default {
v-if="resolveAllDiscussionsIssuePath && !allResolved"
v-gl-tooltip
:href="resolveAllDiscussionsIssuePath"
:title="s__('Create issue to resolve all threads')"
:aria-label="s__('Create issue to resolve all threads')"
:title="__('Create issue to resolve all threads')"
:aria-label="__('Create issue to resolve all threads')"
class="new-issue-for-discussion discussion-create-issue-btn"
icon="issue-new"
/>

View File

@ -70,7 +70,7 @@ export default {
},
primaryProps() {
return {
text: s__('Delete project'),
text: __('Delete project'),
attributes: [{ variant: 'danger' }, { category: 'primary' }, { disabled: !this.canSubmit }],
};
},

View File

@ -47,7 +47,7 @@ export default {
<template>
<new-namespace-page
:jump-to-last-persisted-panel="hasErrors"
:initial-breadcrumb="s__('New group')"
:initial-breadcrumb="__('New group')"
:panels="$options.PANELS"
:title="s__('GroupsNew|Create new group')"
persistence-key="new_group_last_active_tab"

View File

@ -3,7 +3,7 @@ import { GlModal } from '@gitlab/ui';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
import { s__, sprintf } from '~/locale';
import { __, s__, sprintf } from '~/locale';
export default {
components: {
@ -83,7 +83,7 @@ export default {
attributes: [{ variant: 'warning' }],
},
cancelAction: {
text: s__('Cancel'),
text: __('Cancel'),
attributes: [],
},
};

View File

@ -72,18 +72,18 @@ export default {
return [
{
value: KEY_EVERY_DAY,
text: sprintf(s__(`Every day (at %{time})`), { time: this.formattedTime }),
text: sprintf(__(`Every day (at %{time})`), { time: this.formattedTime }),
},
{
value: KEY_EVERY_WEEK,
text: sprintf(s__('Every week (%{weekday} at %{time})'), {
text: sprintf(__('Every week (%{weekday} at %{time})'), {
weekday: this.weekday,
time: this.formattedTime,
}),
},
{
value: KEY_EVERY_MONTH,
text: sprintf(s__('Every month (Day %{day} at %{time})'), {
text: sprintf(__('Every month (Day %{day} at %{time})'), {
day: this.randomDay,
time: this.formattedTime,
}),

View File

@ -2,7 +2,7 @@
import { GlIcon, GlSprintf, GlLink, GlFormCheckbox, GlToggle } from '@gitlab/ui';
import settingsMixin from 'ee_else_ce/pages/projects/shared/permissions/mixins/settings_pannel_mixin';
import { s__ } from '~/locale';
import { __, s__ } from '~/locale';
import {
visibilityOptions,
visibilityLevelDescriptions,
@ -31,7 +31,7 @@ export default {
operationsLabel: s__('ProjectSettings|Operations'),
packagesLabel: s__('ProjectSettings|Packages'),
pagesLabel: s__('ProjectSettings|Pages'),
ciCdLabel: s__('CI/CD'),
ciCdLabel: __('CI/CD'),
repositoryLabel: s__('ProjectSettings|Repository'),
requirementsLabel: s__('ProjectSettings|Requirements'),
securityAndComplianceLabel: s__('ProjectSettings|Security & Compliance'),

View File

@ -12,7 +12,7 @@ import {
import axios from '~/lib/utils/axios_utils';
import csrf from '~/lib/utils/csrf';
import { setUrlFragment } from '~/lib/utils/url_utility';
import { s__, sprintf } from '~/locale';
import { __, s__, sprintf } from '~/locale';
import Tracking from '~/tracking';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import {
@ -83,7 +83,7 @@ export default {
),
},
},
feedbackTip: s__(
feedbackTip: __(
'Tell us your experiences with the new Markdown editor %{linkStart}in this feedback issue%{linkEnd}.',
),
},

View File

@ -1,7 +1,7 @@
<script>
import { GlButton, GlModal, GlModalDirective, GlSegmentedControl } from '@gitlab/ui';
import { s__ } from '~/locale';
import { __, s__ } from '~/locale';
import { sortOrders, sortOrderOptions } from '../constants';
import RequestWarning from './request_warning.vue';
@ -55,7 +55,7 @@ export default {
const summary = {};
if (!this.metricDetails.summaryOptions?.hideTotal) {
summary[s__('Total')] = this.metricDetails.calls;
summary[__('Total')] = this.metricDetails.calls;
}
if (!this.metricDetails.summaryOptions?.hideDuration) {

View File

@ -12,7 +12,7 @@ import { produce } from 'immer';
import { fetchPolicies } from '~/lib/graphql';
import { historyPushState } from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import { __ } from '~/locale';
import {
BRANCH_PAGINATION_LIMIT,
BRANCH_SEARCH_DEBOUNCE,
@ -25,9 +25,9 @@ import getLastCommitBranchQuery from '~/pipeline_editor/graphql/queries/client/l
export default {
i18n: {
dropdownHeader: s__('Switch branch'),
title: s__('Branches'),
fetchError: s__('Unable to fetch branch list for this project.'),
dropdownHeader: __('Switch branch'),
title: __('Branches'),
fetchError: __('Unable to fetch branch list for this project.'),
},
inputDebounce: BRANCH_SEARCH_DEBOUNCE,
components: {

View File

@ -37,7 +37,7 @@ export default {
},
primaryProps() {
return {
text: s__('Delete account'),
text: __('Delete account'),
attributes: [
{ variant: 'danger', 'data-qa-selector': 'confirm_delete_account_button' },
{ category: 'primary' },
@ -47,7 +47,7 @@ export default {
},
cancelProps() {
return {
text: s__('Cancel'),
text: __('Cancel'),
};
},
canSubmit() {

View File

@ -3,7 +3,7 @@ import { GlSafeHtmlDirective as SafeHtml, GlButton, GlModal, GlModalDirective }
import { escape } from 'lodash';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { s__, sprintf } from '~/locale';
import { __, s__, sprintf } from '~/locale';
export default {
components: {
@ -58,7 +58,7 @@ Please update your Git repository remotes as soon as possible.`),
},
primaryProps() {
return {
text: s__('Update username'),
text: __('Update username'),
attributes: [
{ variant: 'warning' },
{ category: 'primary' },
@ -68,7 +68,7 @@ Please update your Git repository remotes as soon as possible.`),
},
cancelProps() {
return {
text: s__('Cancel'),
text: __('Cancel'),
};
},
},

View File

@ -95,7 +95,7 @@ export default {
<template>
<new-namespace-page
:initial-breadcrumb="s__('New project')"
:initial-breadcrumb="__('New project')"
:panels="availablePanels"
:jump-to-last-persisted-panel="hasErrors"
:title="s__('ProjectsNew|Create new project')"

View File

@ -51,7 +51,7 @@ export const ERROR_MESSAGE = s__(
'UsageQuota|Something went wrong while fetching project storage statistics',
);
export const LEARN_MORE_LABEL = s__('Learn more.');
export const LEARN_MORE_LABEL = __('Learn more.');
export const USAGE_QUOTAS_LABEL = s__('UsageQuota|Usage Quotas');
export const HELP_LINK_ARIA_LABEL = s__('UsageQuota|%{linkTitle} help link');
export const TOTAL_USAGE_DEFAULT_TEXT = __('N/A');

View File

@ -58,7 +58,7 @@ export default {
};
this.isLoading = false;
createFlash({
message: s__('Something went wrong on our end'),
message: __('Something went wrong on our end'),
});
},
initPolling() {

View File

@ -1,7 +1,7 @@
<script>
import { GlLink, GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import { sprintf, n__, s__ } from '~/locale';
import { sprintf, __, n__ } from '~/locale';
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
import { parseIssuableData } from '../../issue_show/utils/parse_data';
@ -40,7 +40,7 @@ export default {
this.totalCount,
);
return sprintf(s__('%{mrText}, this issue will be closed automatically.'), { mrText });
return sprintf(__('%{mrText}, this issue will be closed automatically.'), { mrText });
},
},
mounted() {

View File

@ -1,7 +1,7 @@
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { normalizeHeaders } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import { __ } from '~/locale';
import * as types from './mutation_types';
const REQUEST_PAGE_COUNT = 100;
@ -30,7 +30,7 @@ export const fetchMergeRequests = ({ state, dispatch }) => {
.catch(() => {
dispatch('receiveDataError');
createFlash({
message: s__('Something went wrong while fetching related merge requests.'),
message: __('Something went wrong while fetching related merge requests.'),
});
});
};

View File

@ -1,7 +1,7 @@
<script>
import { GlFormRadioGroup, GlFormRadio } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import { sprintf, s__ } from '~/locale';
import { sprintf, __ } from '~/locale';
export default {
name: 'RadioFilter',
@ -49,7 +49,7 @@ export default {
...mapActions(['setQuery']),
radioLabel(filter) {
return filter.value === this.ANY.value
? sprintf(s__('Any %{header}'), { header: this.filterData.header.toLowerCase() })
? sprintf(__('Any %{header}'), { header: this.filterData.header.toLowerCase() })
: filter.label;
},
},

View File

@ -30,7 +30,7 @@ export default {
updatedFileDescription() {
const { sourcePath } = this.appData;
return sprintf(s__('Update %{sourcePath} file'), { sourcePath });
return sprintf(__('Update %{sourcePath} file'), { sourcePath });
},
},
created() {

View File

@ -23,7 +23,7 @@ export default {
translations: {
createdTimeagoLabel: s__('UserList|created %{timeago}'),
deleteListTitle: s__('UserList|Delete %{name}?'),
deleteListMessage: s__('User list %{name} will be removed. Are you sure?'),
deleteListMessage: __('User list %{name} will be removed. Are you sure?'),
editUserListLabel: s__('FeatureFlags|Edit User List'),
},
modal: {

View File

@ -67,8 +67,8 @@ export default {
<template #targetBranch>
<span class="label-branch">{{ targetBranchEscaped }}</span>
</template>
<template v-if="glFeatures.restructuredMrWidget" #squashedCommits>
<template v-if="isSquashEnabled">
<template #squashedCommits>
<template v-if="glFeatures.restructuredMrWidget && isSquashEnabled">
{{ __('(commits will be squashed)') }}</template
></template
>

View File

@ -151,7 +151,7 @@ export default {
right
data-qa-selector="download_dropdown"
>
<gl-dropdown-section-header>{{ s__('Download as') }}</gl-dropdown-section-header>
<gl-dropdown-section-header>{{ __('Download as') }}</gl-dropdown-section-header>
<gl-dropdown-item
:href="mr.emailPatchesPath"
class="js-download-email-patches"

View File

@ -1,4 +1,4 @@
import { s__ } from '~/locale';
import { __ } from '~/locale';
/**
* Validation messages will take priority based on the property order.
@ -12,11 +12,11 @@ import { s__ } from '~/locale';
const defaultFeedbackMap = {
valueMissing: {
isInvalid: (el) => el.validity?.valueMissing,
message: s__('Please fill out this field.'),
message: __('Please fill out this field.'),
},
urlTypeMismatch: {
isInvalid: (el) => el.type === 'url' && el.validity?.typeMismatch,
message: s__('Please enter a valid URL format, ex: http://www.example.com/home'),
message: __('Please enter a valid URL format, ex: http://www.example.com/home'),
},
};

View File

@ -6,27 +6,30 @@ module Ci
def runner_status_icon(runner, size: 16, icon_class: '')
status = runner.status
active = runner.active
title = ''
icon = 'warning-solid'
span_class = ''
case status
when :online
if active
title = s_("Runners|Runner is online, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) }
icon = 'status-active'
span_class = 'gl-text-green-500'
else
title = s_("Runners|Runner is paused, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) }
icon = 'status-paused'
span_class = 'gl-text-gray-600'
end
when :not_connected
title = s_("Runners|New runner, has not connected yet")
icon = 'warning-solid'
when :online
title = s_("Runners|Runner is online, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) }
icon = 'status-active'
span_class = 'gl-text-green-500'
when :offline
title = s_("Runners|Runner is offline, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) }
icon = 'status-failed'
span_class = 'gl-text-red-500'
when :paused
title = s_("Runners|Runner is paused, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) }
icon = 'status-paused'
span_class = 'gl-text-gray-600'
end
content_tag(:span, class: span_class, title: title, data: { toggle: 'tooltip', container: 'body', testid: 'runner_status_icon', qa_selector: "runner_status_#{status}_content" }) do

View File

@ -274,6 +274,14 @@ module Ci
end
def status
return :not_connected unless contacted_at
online? ? :online : :offline
end
# DEPRECATED
# TODO Remove in %15.0 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/344648
def deprecated_rest_status
if contacted_at.nil?
:not_connected
elsif active?

View File

@ -0,0 +1,8 @@
---
name: use_model_load_balancing
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73631
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344797
milestone: '14.5'
type: development
group: group::sharding
default_enabled: false

View File

@ -0,0 +1,16 @@
- name: "REST API Runner will not contain 'paused'"
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
removal_milestone: "15.0" # the milestone when this feature is planned to be removed
body: | # Do not modify this line, instead modify the lines below.
Runner REST API will not return "paused" as a status in GitLab 15.0.
REST API: Paused runners' status will only relate to runner contact status, such as:
"online", "offline", "not_connected". Status "paused" will not appear when the runner is
not active.
When checking if a runner is "paused", API users are advised to check the boolean attribute
"active" to be `false` instead.
stage: Verify
tiers: [Core, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344648
documentation_url: https://docs.gitlab.com/ee/api/runners.html

View File

@ -1585,11 +1585,29 @@ all state associated with a given repository including:
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml remove-repository -virtual-storage <virtual-storage> -repository <repository>
```
- `-virtual-storage` is the virtual storage the repository is located in.
- `-repository` is the repository's relative path in the storage.
- `-virtual-storage` is the virtual storage the repository is located in. Virtual storages are configured in `/etc/gitlab/gitlab.rb` under `praefect['virtual_storages]` and looks like the following:
Sometimes parts of the repository continue to exist after running `remove-repository`. This can be caused
because of:
```ruby
praefect['virtual_storages'] = {
'default' => {
...
},
'storage-1' => {
...
}
}
```
In this example, the virtual storage to specify is `default` or `storage-1`.
- `-repository` is the repository's relative path in the storage [beginning with `@hashed`](../repository_storage_types.md#hashed-storage).
For example:
```plaintext
@hashed/f5/ca/f5ca38f748a1d6eaf726b8a42fb575c3c71f1864a8143301782de13da2d9202b.git
```
Parts of the repository can continue to exist after running `remove-repository`. This can be because of:
- A deletion error.
- An in-flight RPC call targeting the repository.
@ -1613,8 +1631,53 @@ The command outputs:
Each entry is a complete JSON string with a newline at the end (configurable using the
`-delimiter` flag). For example:
```shell
```plaintext
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml list-untracked-repositories
{"virtual_storage":"default","storage":"gitaly-1","relative_path":"@hashed/ab/cd/abcd123456789012345678901234567890123456789012345678901234567890.git"}
{"virtual_storage":"default","storage":"gitaly-1","relative_path":"@hashed/ab/cd/abcd123456789012345678901234567890123456789012345678901234567891.git"}
```
### Manually track repositories
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5658) in GitLab 14.4.
The `track-repository` Praefect sub-command adds repositories on disk to the Praefect database to be tracked.
```shell
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml track-repository -virtual-storage <virtual-storage> -repository <repository>
```
- `-virtual-storage` is the virtual storage the repository is located in. Virtual storages are configured in `/etc/gitlab/gitlab.rb` under `praefect['virtual_storages]` and looks like the following:
```ruby
praefect['virtual_storages'] = {
'default' => {
...
},
'storage-1' => {
...
}
}
```
In this example, the virtual storage to specify is `default` or `storage-1`.
- `-repository` is the repository's relative path in the storage [beginning with `@hashed`](../repository_storage_types.md#hashed-storage).
For example:
```plaintext
@hashed/f5/ca/f5ca38f748a1d6eaf726b8a42fb575c3c71f1864a8143301782de13da2d9202b.git
```
- `-authoritative-storage` is the storage we want Praefect to treat as the primary. Required if
[per-repository replication](#configure-replication-factor) is set as the replication strategy.
The command outputs:
- Results to `STDOUT` and the command's logs.
- Errors to `STDERR`.
This command fails if:
- The repository is already being tracked by the Praefect database.
- The repository does not exist on disk.

View File

@ -83,6 +83,24 @@ request is as follows:
migrations on a fresh database before the MR is reviewed. If the review leads
to large changes in the MR, execute the migrations again once the review is complete.
1. Write tests for more complex migrations.
1. If your merge request adds new validations to existing models, to make sure the
data processing is backwards compatible:
- Ask in the [`#database`](https://gitlab.slack.com/archives/CNZ8E900G) Slack channel
for assistance to execute the database query that checks the existing rows to
ensure existing rows aren't impacted by the change.
- Add the necessary validation with a feature flag to be gradually rolled out
following [the rollout steps](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#rollout).
If this merge request is urgent, the code owners should make the final call on
whether reviewing existing rows should be included as an immediate follow-up task
to the merge request.
NOTE:
There isn't a way to know anything about our customers' data on their
[self-managed instances](../../subscriptions/self_managed/index.md), so keep
that in mind for any data implications with your merge request.
1. Merge requests **must** adhere to the [merge request performance guidelines](../merge_request_performance_guidelines.md).
1. For tests that use Capybara, read
[how to write reliable, asynchronous integration tests](https://thoughtbot.com/blog/write-reliable-asynchronous-integration-tests-with-capybara).

View File

@ -12,7 +12,9 @@ module API
expose :runner_type
expose :name
expose :online?, as: :online
expose :status
# DEPRECATED
# TODO Remove in %15.0 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/344648
expose :status, as: :deprecated_rest_status
end
end
end

View File

@ -5,7 +5,7 @@ module Gitlab
module LoadBalancing
# Class for setting up load balancing of a specific model.
class Setup
attr_reader :configuration
attr_reader :model, :configuration
def initialize(model, start_service_discovery: false)
@model = model
@ -15,8 +15,9 @@ module Gitlab
def setup
configure_connection
setup_load_balancer
setup_connection_proxy
setup_service_discovery
setup_feature_flag_to_model_load_balancing
end
def configure_connection
@ -36,28 +37,82 @@ module Gitlab
@model.establish_connection(hash_config)
end
def setup_load_balancer
lb = LoadBalancer.new(configuration)
def setup_connection_proxy
# We just use a simple `class_attribute` here so we don't need to
# inject any modules and/or expose unnecessary methods.
@model.class_attribute(:connection)
@model.class_attribute(:sticking)
setup_class_attribute(:connection, ConnectionProxy.new(load_balancer))
setup_class_attribute(:sticking, Sticking.new(load_balancer))
end
@model.connection = ConnectionProxy.new(lb)
@model.sticking = Sticking.new(lb)
# TODO: This is temporary code to gradually redirect traffic to use
# a dedicated DB replicas, or DB primaries (depending on configuration)
# This implements a sticky behavior for the current request if enabled.
#
# This is needed for Phase 3 and Phase 4 of application rollout
# https://gitlab.com/groups/gitlab-org/-/epics/6160#progress
#
# If `GITLAB_USE_MODEL_LOAD_BALANCING` is set, its value is preferred
# Otherwise, a `use_model_load_balancing` FF value is used
def setup_feature_flag_to_model_load_balancing
return if active_record_base?
@model.singleton_class.prepend(ModelLoadBalancingFeatureFlagMixin)
end
def setup_service_discovery
return unless configuration.service_discovery_enabled?
lb = @model.connection.load_balancer
sv = ServiceDiscovery.new(lb, **configuration.service_discovery)
sv = ServiceDiscovery.new(load_balancer, **configuration.service_discovery)
sv.perform_service_discovery
sv.start if @start_service_discovery
end
def load_balancer
@load_balancer ||= LoadBalancer.new(configuration)
end
private
def setup_class_attribute(attribute, value)
@model.class_attribute(attribute)
@model.public_send("#{attribute}=", value) # rubocop:disable GitlabSecurity/PublicSend
end
def active_record_base?
@model == ActiveRecord::Base
end
module ModelLoadBalancingFeatureFlagMixin
extend ActiveSupport::Concern
def use_model_load_balancing?
# Cache environment variable and return env variable first if defined
use_model_load_balancing_env = Gitlab::Utils.to_boolean(ENV["GITLAB_USE_MODEL_LOAD_BALANCING"])
unless use_model_load_balancing_env.nil?
return use_model_load_balancing_env
end
# Check a feature flag using RequestStore (if active)
return false unless Gitlab::SafeRequestStore.active?
Gitlab::SafeRequestStore.fetch(:use_model_load_balancing) do
Feature.enabled?(:use_model_load_balancing, default_enabled: :yaml)
end
end
# rubocop:disable Database/MultipleDatabases
def connection
use_model_load_balancing? ? super : ActiveRecord::Base.connection
end
def sticking
use_model_load_balancing? ? super : ActiveRecord::Base.sticking
end
# rubocop:enable Database/MultipleDatabases
end
end
end
end

View File

@ -44,7 +44,7 @@ module Gitlab
# We want to use AttributesCleaner for these relations instead, in the future this should be removed to make sure
# we are using AttributesPermitter for every imported relation.
DISABLED_RELATION_NAMES = %i[user author issuable_sla].freeze
DISABLED_RELATION_NAMES = %i[author issuable_sla].freeze
def initialize(config: ImportExport::Config.new.to_h)
@config = config

View File

@ -178,17 +178,7 @@ included_attributes:
- :project_id
- :key
- :value
label:
- :title
- :color
- :project_id
- :group_id
- :created_at
- :updated_at
- :template
- :description
- :priority
labels:
label: &label_definition
- :title
- :color
- :project_id
@ -198,23 +188,13 @@ included_attributes:
- :template
- :description
- :priority
labels: *label_definition
priorities:
- :project_id
- :priority
- :created_at
- :updated_at
milestone:
- :iid
- :title
- :project_id
- :group_id
- :description
- :due_date
- :created_at
- :updated_at
- :start_date
- :state
milestones:
milestone: &milestone_definition
- :iid
- :title
- :project_id
@ -225,6 +205,7 @@ included_attributes:
- :updated_at
- :start_date
- :state
milestones: *milestone_definition
protected_branches:
- :project_id
- :name
@ -315,6 +296,200 @@ included_attributes:
- :project_id
- :issue_template_key
- :project_key
snippets:
- :title
- :content
- :author_id
- :project_id
- :created_at
- :updated_at
- :file_name
- :visibility_level
- :description
project_members:
- :access_level
- :source_type
- :user_id
- :notification_level
- :created_at
- :updated_at
- :created_by_id
- :invite_email
- :invite_accepted_at
- :requested_at
- :expires_at
- :ldap
- :override
merge_request: &merge_request_definition
- :target_branch
- :source_branch
- :source_project_id
- :author_id
- :assignee_id
- :title
- :created_at
- :updated_at
- :state
- :merge_status
- :target_project_id
- :iid
- :description
- :updated_by_id
- :merge_error
- :merge_params
- :merge_when_pipeline_succeeds
- :merge_user_id
- :merge_commit_sha
- :squash_commit_sha
- :in_progress_merge_commit_sha
- :lock_version
- :approvals_before_merge
- :rebase_commit_sha
- :time_estimate
- :squash
- :last_edited_at
- :last_edited_by_id
- :discussion_locked
- :allow_maintainer_to_push
- :merge_ref_sha
- :draft
- :diff_head_sha
- :source_branch_sha
- :target_branch_sha
merge_requests: *merge_request_definition
award_emoji:
- :user_id
- :name
- :awardable_type
- :created_at
- :updated_at
commit_author:
- :name
- :email
committer:
- :name
- :email
events:
- :target_type
- :action
- :author_id
- :fingerprint
- :created_at
- :updated_at
label_links:
- :target_type
- :created_at
- :updated_at
merge_request_diff:
- :state
- :created_at
- :updated_at
- :base_commit_sha
- :real_size
- :head_commit_sha
- :start_commit_sha
- :commits_count
- :files_count
- :sorted
- :diff_type
merge_request_diff_commits:
- :relative_order
- :sha
- :authored_date
- :committed_date
- :message
- :trailers
merge_request_diff_files:
- :relative_order
- :new_file
- :renamed_file
- :deleted_file
- :new_path
- :old_path
- :a_mode
- :b_mode
- :too_large
- :binary
- :diff
metrics:
- :created_at
- :updated_at
- :latest_closed_by_id
- :latest_closed_at
- :merged_by_id
- :merged_at
- :latest_build_started_at
- :latest_build_finished_at
- :first_deployed_to_production_at
- :first_comment_at
- :first_commit_at
- :last_commit_at
- :diff_size
- :modified_paths_size
- :commits_count
- :first_approved_at
- :first_reassigned_at
- :added_lines
- :target_project_id
- :removed_lines
notes:
- :note
- :noteable_type
- :author_id
- :created_at
- :updated_at
- :project_id
- :attachment
- :line_code
- :commit_id
- :system
- :st_diff
- :updated_by_id
- :type
- :position
- :original_position
- :change_position
- :resolved_at
- :resolved_by_id
- :resolved_by_push
- :discussion_id
- :confidential
- :last_edited_at
push_event_payload:
- :commit_count
- :action
- :ref_type
- :commit_from
- :commit_to
- :ref
- :commit_title
- :ref_count
resource_label_events:
- :action
- :user_id
- :created_at
suggestions:
- :relative_order
- :applied
- :commit_id
- :from_content
- :to_content
- :outdated
- :lines_above
- :lines_below
system_note_metadata:
- :commit_count
- :action
- :created_at
- :updated_at
timelogs:
- :time_spent
- :user_id
- :project_id
- :spent_at
- :created_at
- :updated_at
- :summary
# Do not include the following attributes for the models specified.
excluded_attributes:
@ -430,16 +605,7 @@ excluded_attributes:
- :service_desk_reply_to
- :upvotes_count
- :work_item_type_id
merge_request:
- :milestone_id
- :sprint_id
- :ref_fetched
- :merge_jid
- :rebase_jid
- :latest_merge_request_diff_id
- :head_pipeline_id
- :state_id
merge_requests:
merge_request: &merge_request_excluded_definition
- :milestone_id
- :sprint_id
- :ref_fetched
@ -448,6 +614,7 @@ excluded_attributes:
- :latest_merge_request_diff_id
- :head_pipeline_id
- :state_id
merge_requests: *merge_request_excluded_definition
award_emoji:
- :awardable_id
statuses:
@ -516,10 +683,9 @@ excluded_attributes:
- :issue_id
zoom_meetings:
- :issue_id
design:
- :issue_id
designs:
design: &design_excluded_definition
- :issue_id
designs: *design_excluded_definition
design_versions:
- :issue_id
actions:

View File

@ -38128,9 +38128,6 @@ msgstr ""
msgid "VulnerabilityChart|Severity"
msgstr ""
msgid "VulnerabilityManagement, Fetching linked Jira issues"
msgstr ""
msgid "VulnerabilityManagement|%{statusStart}Confirmed%{statusEnd} %{timeago} by %{user}"
msgstr ""
@ -38158,6 +38155,9 @@ msgstr ""
msgid "VulnerabilityManagement|Detected"
msgstr ""
msgid "VulnerabilityManagement|Fetching linked Jira issues"
msgstr ""
msgid "VulnerabilityManagement|Needs triage"
msgstr ""

View File

@ -203,7 +203,7 @@
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.10.1",
"@gitlab/eslint-plugin": "9.4.0",
"@gitlab/eslint-plugin": "10.0.0",
"@gitlab/stylelint-config": "2.6.0",
"@testing-library/dom": "^7.16.2",
"@vue/test-utils": "1.2.0",

View File

@ -22,20 +22,16 @@ module QA
element :labels_block
end
base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue' do
element :selected_label_content
base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue' do
element :dropdown_input_field
end
base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue' do
base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue' do
element :labels_dropdown_content
end
base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue' do
element :labels_edit_button
end
base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue' do
element :dropdown_input_field
base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue' do
element :selected_label_content
end
base.view 'app/views/shared/issuable/_sidebar.html.haml' do
@ -53,7 +49,7 @@ module QA
end
def assign_milestone(milestone)
within_element(:milestone_block) do
wait_milestone_block_finish_loading do
click_element(:edit_link)
click_on(milestone.title)
end
@ -70,14 +66,14 @@ module QA
end
def has_assignee?(username)
within_element(:assignee_block) do
has_text?(username, wait: 1)
wait_assignees_block_finish_loading do
has_text?(username)
end
end
def has_no_assignee?(username)
within_element(:assignee_block) do
has_no_text?(username, wait: 1)
wait_assignees_block_finish_loading do
has_no_text?(username)
end
end
@ -88,8 +84,14 @@ module QA
end
def has_label?(label)
within_element(:labels_block) do
!!has_element?(:selected_label_content, label_name: label)
wait_labels_block_finish_loading do
has_element?(:selected_label_content, label_name: label)
end
end
def has_no_label?(label)
wait_labels_block_finish_loading do
has_no_element?(:selected_label_content, label_name: label)
end
end
@ -103,27 +105,19 @@ module QA
find_element(:more_assignees_link)
end
def select_labels_and_refresh(labels)
Support::Retrier.retry_until do
click_element(:labels_edit_button)
has_element?(:labels_dropdown_content, text: labels.first)
end
def select_labels(labels)
within_element(:labels_block) do
click_element(:edit_link)
labels.each do |label|
within_element(:labels_dropdown_content) do
send_keys_to_element(:dropdown_input_field, [label, :enter])
labels.each do |label|
within_element(:labels_dropdown_content) do
fill_element(:dropdown_input_field, label)
click_button(text: label)
end
end
end
click_element(:labels_edit_button)
labels.each do |label|
has_element?(:labels_block, text: label, wait: 0)
end
refresh
wait_for_requests
click_element(:title) # to blur dropdown
end
def toggle_more_assignees_link
@ -141,6 +135,15 @@ module QA
end
end
def wait_labels_block_finish_loading
within_element(:labels_block) do
wait_until(reload: false, max_duration: 10, sleep_interval: 1) do
finished_loading_block?
yield
end
end
end
def wait_milestone_block_finish_loading
within_element(:milestone_block) do
wait_until(reload: false, max_duration: 10, sleep_interval: 1) do

View File

@ -19,6 +19,7 @@ module QA
related_issue_item
snippet_description
tag
label
].each do |predicate|
RSpec::Matchers.define "have_#{predicate}" do |*args, **kwargs|
match do |page_object|

View File

@ -1,15 +1,15 @@
import { GlAlert, GlButton, GlLink } from '@gitlab/ui';
import { GlAlert, GlLink } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import JiraConnectApp from '~/jira_connect/subscriptions/components/app.vue';
import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
import SignInButton from '~/jira_connect/subscriptions/components/sign_in_button.vue';
import createStore from '~/jira_connect/subscriptions/store';
import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
import { __ } from '~/locale';
jest.mock('~/jira_connect/subscriptions/utils', () => ({
retrieveAlert: jest.fn().mockReturnValue({ message: 'error message' }),
getLocation: jest.fn(),
}));
describe('JiraConnectApp', () => {
@ -18,7 +18,7 @@ describe('JiraConnectApp', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findAlertLink = () => findAlert().findComponent(GlLink);
const findGlButton = () => wrapper.findComponent(GlButton);
const findSignInButton = () => wrapper.findComponent(SignInButton);
const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton);
const createComponent = ({ provide, mountFn = shallowMount } = {}) => {
@ -35,28 +35,25 @@ describe('JiraConnectApp', () => {
});
describe('template', () => {
describe('when user is not logged in', () => {
describe.each`
scenario | usersPath | expectSignInButton | expectNamespaceButton
${'user is not signed in'} | ${'/users'} | ${true} | ${false}
${'user is signed in'} | ${undefined} | ${false} | ${true}
`('when $scenario', ({ usersPath, expectSignInButton, expectNamespaceButton }) => {
beforeEach(() => {
createComponent({
provide: {
usersPath: '/users',
usersPath,
},
});
});
it('renders "Sign in" button', () => {
expect(findGlButton().text()).toBe('Sign in to add namespaces');
expect(findAddNamespaceButton().exists()).toBe(false);
});
});
describe('when user is logged in', () => {
beforeEach(() => {
createComponent();
it('renders sign in button as expected', () => {
expect(findSignInButton().exists()).toBe(expectSignInButton);
});
it('renders "Add namespace" button ', () => {
expect(findAddNamespaceButton().exists()).toBe(true);
it('renders "Add Namespace" button as expected', () => {
expect(findAddNamespaceButton().exists()).toBe(expectNamespaceButton);
});
});

View File

@ -0,0 +1,50 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { getLocation } from '~/jira_connect/subscriptions/utils';
import SignInButton from '~/jira_connect/subscriptions/components/sign_in_button.vue';
import waitForPromises from 'helpers/wait_for_promises';
const MOCK_USERS_PATH = '/user';
jest.mock('~/jira_connect/subscriptions/utils');
describe('SignInButton', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMount(SignInButton, {
propsData: {
usersPath: MOCK_USERS_PATH,
},
});
};
const findButton = () => wrapper.findComponent(GlButton);
afterEach(() => {
wrapper.destroy();
});
it('displays a button', () => {
createComponent();
expect(findButton().exists()).toBe(true);
});
describe.each`
getLocationValue | expectedHref
${''} | ${MOCK_USERS_PATH}
${undefined} | ${MOCK_USERS_PATH}
${'https://test.jira.com'} | ${`${MOCK_USERS_PATH}?return_to=${encodeURIComponent('https://test.jira.com')}`}
`('when getLocation resolves with `$getLocationValue`', ({ getLocationValue, expectedHref }) => {
it(`sets button href to ${expectedHref}`, async () => {
getLocation.mockResolvedValue(getLocationValue);
createComponent();
expect(getLocation).toHaveBeenCalled();
await waitForPromises();
expect(findButton().attributes('href')).toBe(expectedHref);
});
});
});

View File

@ -185,15 +185,15 @@ describe('dateInWords', () => {
const date = new Date('07/01/2016');
it('should return date in words', () => {
expect(datetimeUtility.dateInWords(date)).toEqual(s__('July 1, 2016'));
expect(datetimeUtility.dateInWords(date)).toEqual(__('July 1, 2016'));
});
it('should return abbreviated month name', () => {
expect(datetimeUtility.dateInWords(date, true)).toEqual(s__('Jul 1, 2016'));
expect(datetimeUtility.dateInWords(date, true)).toEqual(__('Jul 1, 2016'));
});
it('should return date in words without year', () => {
expect(datetimeUtility.dateInWords(date, true, true)).toEqual(s__('Jul 1'));
expect(datetimeUtility.dateInWords(date, true, true)).toEqual(__('Jul 1'));
});
});
@ -201,11 +201,11 @@ describe('monthInWords', () => {
const date = new Date('2017-01-20');
it('returns month name from provided date', () => {
expect(datetimeUtility.monthInWords(date)).toBe(s__('January'));
expect(datetimeUtility.monthInWords(date)).toBe(__('January'));
});
it('returns abbreviated month name from provided date', () => {
expect(datetimeUtility.monthInWords(date, true)).toBe(s__('Jan'));
expect(datetimeUtility.monthInWords(date, true)).toBe(__('Jan'));
});
});

View File

@ -8,8 +8,9 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do
setup = described_class.new(ActiveRecord::Base)
expect(setup).to receive(:configure_connection)
expect(setup).to receive(:setup_load_balancer)
expect(setup).to receive(:setup_connection_proxy)
expect(setup).to receive(:setup_service_discovery)
expect(setup).to receive(:setup_feature_flag_to_model_load_balancing)
setup.setup
end
@ -44,7 +45,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do
end
end
describe '#setup_load_balancer' do
describe '#setup_connection_proxy' do
it 'sets up the load balancer' do
model = Class.new(ActiveRecord::Base)
setup = described_class.new(model)
@ -58,7 +59,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do
.with(setup.configuration)
.and_return(lb)
setup.setup_load_balancer
setup.setup_connection_proxy
expect(model.connection.load_balancer).to eq(lb)
expect(model.sticking)
@ -81,7 +82,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do
model = ActiveRecord::Base
setup = described_class.new(model)
sv = instance_spy(Gitlab::Database::LoadBalancing::ServiceDiscovery)
lb = model.connection.load_balancer
allow(setup.configuration)
.to receive(:service_discovery_enabled?)
@ -89,7 +89,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do
allow(Gitlab::Database::LoadBalancing::ServiceDiscovery)
.to receive(:new)
.with(lb, setup.configuration.service_discovery)
.with(setup.load_balancer, setup.configuration.service_discovery)
.and_return(sv)
expect(sv).to receive(:perform_service_discovery)
@ -102,7 +102,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do
model = ActiveRecord::Base
setup = described_class.new(model, start_service_discovery: true)
sv = instance_spy(Gitlab::Database::LoadBalancing::ServiceDiscovery)
lb = model.connection.load_balancer
allow(setup.configuration)
.to receive(:service_discovery_enabled?)
@ -110,7 +109,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do
allow(Gitlab::Database::LoadBalancing::ServiceDiscovery)
.to receive(:new)
.with(lb, setup.configuration.service_discovery)
.with(setup.load_balancer, setup.configuration.service_discovery)
.and_return(sv)
expect(sv).to receive(:perform_service_discovery)
@ -120,4 +119,172 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do
end
end
end
describe '#setup_feature_flag_to_model_load_balancing', :reestablished_active_record_base do
using RSpec::Parameterized::TableSyntax
where do
{
"with model LB enabled it picks a dedicated CI connection" => {
env_GITLAB_USE_MODEL_LOAD_BALANCING: 'true',
env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil,
request_store_active: false,
ff_use_model_load_balancing: nil,
expectations: {
main: { read: 'main_replica', write: 'main' },
ci: { read: 'ci_replica', write: 'ci' }
}
},
"with model LB enabled and re-use of primary connection it uses CI connection for reads" => {
env_GITLAB_USE_MODEL_LOAD_BALANCING: 'true',
env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: 'main',
request_store_active: false,
ff_use_model_load_balancing: nil,
expectations: {
main: { read: 'main_replica', write: 'main' },
ci: { read: 'ci_replica', write: 'main' }
}
},
"with model LB disabled it fallbacks to use main" => {
env_GITLAB_USE_MODEL_LOAD_BALANCING: 'false',
env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil,
request_store_active: false,
ff_use_model_load_balancing: nil,
expectations: {
main: { read: 'main_replica', write: 'main' },
ci: { read: 'main_replica', write: 'main' }
}
},
"with model LB disabled, but re-use configured it fallbacks to use main" => {
env_GITLAB_USE_MODEL_LOAD_BALANCING: 'false',
env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: 'main',
request_store_active: false,
ff_use_model_load_balancing: nil,
expectations: {
main: { read: 'main_replica', write: 'main' },
ci: { read: 'main_replica', write: 'main' }
}
},
"with FF disabled without RequestStore it uses main" => {
env_GITLAB_USE_MODEL_LOAD_BALANCING: nil,
env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil,
request_store_active: false,
ff_use_model_load_balancing: false,
expectations: {
main: { read: 'main_replica', write: 'main' },
ci: { read: 'main_replica', write: 'main' }
}
},
"with FF enabled without RequestStore sticking of FF does not work, so it fallbacks to use main" => {
env_GITLAB_USE_MODEL_LOAD_BALANCING: nil,
env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil,
request_store_active: false,
ff_use_model_load_balancing: true,
expectations: {
main: { read: 'main_replica', write: 'main' },
ci: { read: 'main_replica', write: 'main' }
}
},
"with FF disabled with RequestStore it uses main" => {
env_GITLAB_USE_MODEL_LOAD_BALANCING: nil,
env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil,
request_store_active: true,
ff_use_model_load_balancing: false,
expectations: {
main: { read: 'main_replica', write: 'main' },
ci: { read: 'main_replica', write: 'main' }
}
},
"with FF enabled with RequestStore it sticks FF and uses CI connection" => {
env_GITLAB_USE_MODEL_LOAD_BALANCING: nil,
env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil,
request_store_active: true,
ff_use_model_load_balancing: true,
expectations: {
main: { read: 'main_replica', write: 'main' },
ci: { read: 'ci_replica', write: 'ci' }
}
},
"with re-use and FF enabled with RequestStore it sticks FF and uses CI connection for reads" => {
env_GITLAB_USE_MODEL_LOAD_BALANCING: nil,
env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: 'main',
request_store_active: true,
ff_use_model_load_balancing: true,
expectations: {
main: { read: 'main_replica', write: 'main' },
ci: { read: 'ci_replica', write: 'main' }
}
}
}
end
with_them do
let(:ci_class) do
Class.new(ActiveRecord::Base) do
def self.name
'Ci::ApplicationRecordTemporary'
end
establish_connection ActiveRecord::DatabaseConfigurations::HashConfig.new(
Rails.env,
'ci',
ActiveRecord::Base.connection_db_config.configuration_hash
)
end
end
let(:models) do
{
main: ActiveRecord::Base,
ci: ci_class
}
end
around do |example|
if request_store_active
Gitlab::WithRequestStore.with_request_store do
RequestStore.clear!
example.run
end
else
example.run
end
end
before do
# Rewrite `class_attribute` to use rspec mocking and prevent modifying the objects
allow_next_instance_of(described_class) do |setup|
allow(setup).to receive(:configure_connection)
allow(setup).to receive(:setup_class_attribute) do |attribute, value|
allow(setup.model).to receive(attribute) { value }
end
end
stub_env('GITLAB_USE_MODEL_LOAD_BALANCING', env_GITLAB_USE_MODEL_LOAD_BALANCING)
stub_env('GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci', env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci)
stub_feature_flags(use_model_load_balancing: ff_use_model_load_balancing)
end
it 'results match expectations' do
result = models.transform_values do |model|
# Make load balancer to force init with a dedicated replicas connections
described_class.new(model).tap do |subject|
subject.configuration.hosts = [subject.configuration.replica_db_config.host]
subject.setup
end
load_balancer = model.connection.load_balancer
{
read: load_balancer.read { |connection| connection.pool.db_config.name },
write: load_balancer.read_write { |connection| connection.pool.db_config.name }
}
end
expect(result).to eq(expectations)
end
end
end
end

View File

@ -80,8 +80,8 @@ RSpec.describe Gitlab::ImportExport::AttributesPermitter do
let(:attributes_permitter) { described_class.new }
where(:relation_name, :permitted_attributes_defined) do
:user | false
where(:relation_name, :permitted_attributes_defined ) do
:user | true
:author | false
:ci_cd_settings | true
:metrics_setting | true
@ -91,6 +91,7 @@ RSpec.describe Gitlab::ImportExport::AttributesPermitter do
:auto_devops | true
:boards | true
:custom_attributes | true
:label | true
:labels | true
:protected_branches | true
:protected_tags | true
@ -99,6 +100,28 @@ RSpec.describe Gitlab::ImportExport::AttributesPermitter do
:push_access_levels | true
:releases | true
:links | true
:priorities | true
:milestone | true
:milestones | true
:snippets | true
:project_members | true
:merge_request | true
:merge_requests | true
:award_emoji | true
:commit_author | true
:committer | true
:events | true
:label_links | true
:merge_request_diff | true
:merge_request_diff_commits | true
:merge_request_diff_files | true
:metrics | true
:notes | true
:push_event_payload | true
:resource_label_events | true
:suggestions | true
:system_note_metadata | true
:timelogs | true
:container_expiration_policy | true
:project_feature | true
:prometheus_metrics | true
@ -113,9 +136,11 @@ RSpec.describe Gitlab::ImportExport::AttributesPermitter do
describe 'included_attributes for Project' do
subject { described_class.new }
additional_attributes = { user: %w[id] }
Gitlab::ImportExport::Config.new.to_h[:included_attributes].each do |relation_sym, permitted_attributes|
context "for #{relation_sym}" do
it_behaves_like 'a permitted attribute', relation_sym, permitted_attributes
it_behaves_like 'a permitted attribute', relation_sym, permitted_attributes, additional_attributes[relation_sym]
end
end
end

View File

@ -626,7 +626,7 @@ RSpec.describe Ci::Runner do
end
describe '#status' do
let(:runner) { create(:ci_runner, :instance, contacted_at: 1.second.ago) }
let(:runner) { build(:ci_runner, :instance) }
subject { runner.status }
@ -638,6 +638,45 @@ RSpec.describe Ci::Runner do
it { is_expected.to eq(:not_connected) }
end
context 'inactive but online' do
before do
runner.contacted_at = 1.second.ago
runner.active = false
end
it { is_expected.to eq(:online) }
end
context 'contacted 1s ago' do
before do
runner.contacted_at = 1.second.ago
end
it { is_expected.to eq(:online) }
end
context 'contacted long time ago' do
before do
runner.contacted_at = 1.year.ago
end
it { is_expected.to eq(:offline) }
end
end
describe '#deprecated_rest_status' do
let(:runner) { build(:ci_runner, :instance, contacted_at: 1.second.ago) }
subject { runner.deprecated_rest_status }
context 'never connected' do
before do
runner.contacted_at = nil
end
it { is_expected.to eq(:not_connected) }
end
context 'contacted 1s ago' do
before do
runner.contacted_at = 1.second.ago

View File

@ -162,12 +162,12 @@ RSpec.describe Clusters::Applications::Runner do
it 'pauses associated runner' do
active_runner = create(:ci_runner, contacted_at: 1.second.ago)
expect(active_runner.status).to eq(:online)
expect(active_runner.active).to be_truthy
application_runner = create(:clusters_applications_runner, :scheduled, runner: active_runner)
application_runner.prepare_uninstall
expect(active_runner.status).to eq(:paused)
expect(active_runner.active).to be_falsey
end
end

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true
RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attributes|
RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attributes, additional_attributes = []|
let(:prohibited_attributes) { %i[remote_url my_attributes my_ids token my_id test] }
let(:import_export_config) { Gitlab::ImportExport::Config.new.to_h }
@ -26,7 +26,7 @@ RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attrib
end
it 'does not contain attributes that would be cleaned with AttributeCleaner' do
expect(cleaned_hash.keys).to include(*permitted_hash.keys)
expect(cleaned_hash.keys + additional_attributes.to_a).to include(*permitted_hash.keys)
end
it 'does not contain prohibited attributes that are not related to given relation' do

View File

@ -873,10 +873,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.7.tgz#1ee6f838cc4410a1d797770934df91d90df8179e"
integrity sha512-c6ySRK/Ma7lxwpIVbSAF3P+xiTLrNTGTLRx4/pHK111AdFxwgUwrYF6aVZFXvmG65jHOJHoa0eQQ21RW6rm0Rg==
"@gitlab/eslint-plugin@9.4.0":
version "9.4.0"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-9.4.0.tgz#cad8f63b7985c22865859cc7d2688eb446ad0bbb"
integrity sha512-llPypEQrm9/6Xas5GCoSPAK7W/DgO7CKhzDvAk/Ea9BP0rI2+t8Wg4PFhE1XDctYnnUIS/GrdqVKQkpODk24hQ==
"@gitlab/eslint-plugin@10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-10.0.0.tgz#83430fb4d0a2467bb54975d0b5b9dc8016005722"
integrity sha512-frCYzjQQaZ5kW1on3XwuVGhvYa6XjD6Q1POTbxDpzl6tNxSeTwOJohC6Joyw76e0Kw4fPQd/fHAfKQAB0AVQ7A==
dependencies:
babel-eslint "^10.0.3"
eslint-config-airbnb-base "^14.2.1"