Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2bae6e5b96
commit
23b5f76606
|
|
@ -378,7 +378,6 @@ Gitlab/StrongMemoizeAttr:
|
|||
- 'ee/lib/gitlab/code_owners/entry.rb'
|
||||
- 'ee/lib/gitlab/code_owners/loader.rb'
|
||||
- 'ee/lib/gitlab/custom_file_templates.rb'
|
||||
- 'ee/lib/gitlab/elastic/document_reference.rb'
|
||||
- 'ee/lib/gitlab/elastic/indexer.rb'
|
||||
- 'ee/lib/gitlab/elastic/project_search_results.rb'
|
||||
- 'ee/lib/gitlab/expiring_subscription_message.rb'
|
||||
|
|
|
|||
|
|
@ -940,7 +940,6 @@ Layout/LineLength:
|
|||
- 'ee/lib/ee/sidebars/projects/menus/security_compliance_menu.rb'
|
||||
- 'ee/lib/elastic/latest/custom_language_analyzers.rb'
|
||||
- 'ee/lib/elastic/latest/git_instance_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/issue_instance_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/note_class_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/repository_class_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/repository_instance_proxy.rb'
|
||||
|
|
@ -1434,7 +1433,6 @@ Layout/LineLength:
|
|||
- 'ee/spec/models/concerns/ee/noteable_spec.rb'
|
||||
- 'ee/spec/models/concerns/ee/project_security_scanners_information_spec.rb'
|
||||
- 'ee/spec/models/concerns/elastic/application_versioned_search_spec.rb'
|
||||
- 'ee/spec/models/concerns/elastic/issue_spec.rb'
|
||||
- 'ee/spec/models/concerns/elastic/merge_request_spec.rb'
|
||||
- 'ee/spec/models/concerns/elastic/note_spec.rb'
|
||||
- 'ee/spec/models/concerns/elastic/repository_spec.rb'
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ Lint/RedundantCopDisableDirective:
|
|||
# Temporarily disabled due to too many offenses
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'app/controllers/concerns/enforces_two_factor_authentication.rb'
|
||||
- 'app/controllers/groups/milestones_controller.rb'
|
||||
- 'app/models/concerns/integrations/base/integration.rb'
|
||||
- 'app/models/work_items/type.rb'
|
||||
|
|
@ -13,15 +12,12 @@ Lint/RedundantCopDisableDirective:
|
|||
- 'app/services/ci/runners/set_runner_associated_projects_service.rb'
|
||||
- 'app/services/work_items/data_sync/clone_service.rb'
|
||||
- 'app/services/work_items/data_sync/move_service.rb'
|
||||
- 'app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb'
|
||||
- 'config/initializers/warden.rb'
|
||||
- 'db/migrate/20241021082113_create_partitioned_ci_runners.rb'
|
||||
- 'db/migrate/20241024204816_create_partitioned_ci_runner_managers.rb'
|
||||
- 'db/post_migrate/20241021163518_drop_user_canonical_emails_table.rb'
|
||||
- 'ee/app/controllers/ee/projects/settings/ci_cd_controller.rb'
|
||||
- 'ee/app/models/ee/audit_events/project_audit_event.rb'
|
||||
- 'ee/app/services/search/zoekt/routing_service.rb'
|
||||
- 'ee/app/services/security/token_revocation_service.rb'
|
||||
- 'ee/app/services/vulnerabilities/statistics/adjustment_service.rb'
|
||||
- 'ee/db/geo/migrate/20210504143244_add_verification_to_merge_request_diff_registry.rb'
|
||||
- 'ee/spec/features/groups/settings/domain_verification_spec.rb'
|
||||
|
|
@ -33,9 +29,6 @@ Lint/RedundantCopDisableDirective:
|
|||
- 'ee/spec/support/shared_examples/features/dashboard_saml_reauth_banner_shared_examples.rb'
|
||||
- 'ee/spec/views/admin/users/show.html.haml_spec.rb'
|
||||
- 'lib/api/submodules.rb'
|
||||
- 'lib/gitlab/auth/current_user_mode.rb'
|
||||
- 'lib/gitlab/cleanup/personal_access_tokens.rb'
|
||||
- 'lib/gitlab/lfs_token.rb'
|
||||
- 'lib/gitlab/popen/runner.rb'
|
||||
- 'qa/qa/resource/user_runners.rb'
|
||||
- 'qa/qa/service/docker_run/gitlab_runner.rb'
|
||||
|
|
|
|||
|
|
@ -246,7 +246,6 @@ Lint/UnusedMethodArgument:
|
|||
- 'ee/lib/ee/gitlab/auth/ldap/sync/proxy.rb'
|
||||
- 'ee/lib/ee/gitlab/geo_git_access.rb'
|
||||
- 'ee/lib/elastic/as_json.rb'
|
||||
- 'ee/lib/elastic/latest/issue_instance_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/milestone_instance_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/note_class_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/note_instance_proxy.rb'
|
||||
|
|
|
|||
|
|
@ -207,7 +207,6 @@ Style/RedundantSelf:
|
|||
- 'ee/lib/gitlab/auth/smartcard.rb'
|
||||
- 'ee/lib/gitlab/ci/reports/license_scanning/report.rb'
|
||||
- 'ee/lib/gitlab/elastic/client.rb'
|
||||
- 'ee/lib/gitlab/elastic/document_reference.rb'
|
||||
- 'ee/lib/gitlab/elastic/helper.rb'
|
||||
- 'ee/lib/gitlab/geo.rb'
|
||||
- 'ee/lib/gitlab/geo/oauth/login_state.rb'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
85bae8b147813e5a2e79f1a96ab0c30dee8ad6a1
|
||||
48b28c1cc502cdcafb209491ce21b84c42358c03
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Mousetrap, MOUSETRAP_COPY_KEYBOARD_SHORTCUT } from '~/lib/mousetrap';
|
|||
import { __ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import PageHeading from '~/vue_shared/components/page_heading.vue';
|
||||
import {
|
||||
COPY_BUTTON_ACTION,
|
||||
DOWNLOAD_BUTTON_ACTION,
|
||||
|
|
@ -33,7 +34,7 @@ export default {
|
|||
recoveryCodeDownloadFilename: RECOVERY_CODE_DOWNLOAD_FILENAME,
|
||||
i18n,
|
||||
mousetrap: null,
|
||||
components: { GlSprintf, GlButton, GlAlert, ClipboardButton, GlCard },
|
||||
components: { GlSprintf, GlButton, GlAlert, ClipboardButton, GlCard, PageHeading },
|
||||
mixins: [Tracking.mixin()],
|
||||
props: {
|
||||
codes: {
|
||||
|
|
@ -100,20 +101,19 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="page-title gl-text-size-h-display">
|
||||
{{ $options.i18n.pageTitle }}
|
||||
</h1>
|
||||
<hr />
|
||||
<page-heading :heading="$options.i18n.pageTitle">
|
||||
<template #description>
|
||||
<gl-sprintf :message="$options.i18n.pageDescription">
|
||||
<template #bold="{ content }"
|
||||
><strong>{{ content }}</strong></template
|
||||
>
|
||||
</gl-sprintf>
|
||||
</template>
|
||||
</page-heading>
|
||||
|
||||
<gl-alert variant="info" :dismissible="false">
|
||||
{{ $options.i18n.alertTitle }}
|
||||
</gl-alert>
|
||||
<p class="gl-mt-5">
|
||||
<gl-sprintf :message="$options.i18n.pageDescription">
|
||||
<template #bold="{ content }"
|
||||
><strong>{{ content }}</strong></template
|
||||
>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
|
||||
<gl-card class="codes-to-print gl-my-5" data-testid="recovery-codes">
|
||||
<ul class="gl-m-0 gl-pl-5">
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import {
|
|||
I18N_DEVICE_NAME_PLACEHOLDER,
|
||||
I18N_ERROR_HTTP,
|
||||
I18N_ERROR_UNSUPPORTED_BROWSER,
|
||||
I18N_INFO_TEXT,
|
||||
I18N_NOTICE,
|
||||
I18N_PASSWORD,
|
||||
I18N_PASSWORD_DESCRIPTION,
|
||||
|
|
@ -61,7 +60,6 @@ export default {
|
|||
I18N_DEVICE_NAME_PLACEHOLDER,
|
||||
I18N_ERROR_HTTP,
|
||||
I18N_ERROR_UNSUPPORTED_BROWSER,
|
||||
I18N_INFO_TEXT,
|
||||
I18N_NOTICE,
|
||||
I18N_PASSWORD,
|
||||
I18N_PASSWORD_DESCRIPTION,
|
||||
|
|
@ -132,22 +130,13 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="gl-mb-5">
|
||||
<template v-if="isCurrentState($options.STATE_UNSUPPORTED)">
|
||||
<gl-alert variant="danger" :dismissible="false">{{ errorMessage }}</gl-alert>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isCurrentState($options.STATE_READY)">
|
||||
<div class="row">
|
||||
<div class="col-md-5 gl-mb-5">
|
||||
<gl-button variant="confirm" @click="onRegister">{{
|
||||
$options.I18N_BUTTON_SETUP
|
||||
}}</gl-button>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<p>{{ $options.I18N_INFO_TEXT }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<gl-button variant="confirm" @click="onRegister">{{ $options.I18N_BUTTON_SETUP }}</gl-button>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isCurrentState($options.STATE_WAITING)">
|
||||
|
|
@ -169,53 +158,50 @@ export default {
|
|||
</gl-sprintf>
|
||||
</gl-alert>
|
||||
|
||||
<div class="row">
|
||||
<gl-form method="post" :action="targetPath" class="col-md-9" data-testid="create-webauthn">
|
||||
<gl-form-group
|
||||
v-if="passwordRequired"
|
||||
:description="$options.I18N_PASSWORD_DESCRIPTION"
|
||||
:label="$options.I18N_PASSWORD"
|
||||
label-for="webauthn-registration-current-password"
|
||||
>
|
||||
<gl-form-input
|
||||
id="webauthn-registration-current-password"
|
||||
v-model="form.password"
|
||||
name="current_password"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
data-testid="current-password-input"
|
||||
/>
|
||||
</gl-form-group>
|
||||
<gl-form method="post" :action="targetPath" data-testid="create-webauthn">
|
||||
<gl-form-group
|
||||
v-if="passwordRequired"
|
||||
:description="$options.I18N_PASSWORD_DESCRIPTION"
|
||||
:label="$options.I18N_PASSWORD"
|
||||
label-for="webauthn-registration-current-password"
|
||||
>
|
||||
<gl-form-input
|
||||
id="webauthn-registration-current-password"
|
||||
v-model="form.password"
|
||||
name="current_password"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
data-testid="current-password-input"
|
||||
/>
|
||||
</gl-form-group>
|
||||
|
||||
<gl-form-group
|
||||
:description="$options.I18N_DEVICE_NAME_DESCRIPTION"
|
||||
:label="$options.I18N_DEVICE_NAME"
|
||||
label-for="device-name"
|
||||
>
|
||||
<gl-form-input
|
||||
id="device-name"
|
||||
v-model="form.deviceName"
|
||||
name="device_registration[name]"
|
||||
:placeholder="$options.I18N_DEVICE_NAME_PLACEHOLDER"
|
||||
data-testid="device-name-input"
|
||||
/>
|
||||
</gl-form-group>
|
||||
<gl-form-group
|
||||
:description="$options.I18N_DEVICE_NAME_DESCRIPTION"
|
||||
:label="$options.I18N_DEVICE_NAME"
|
||||
label-for="device-name"
|
||||
>
|
||||
<gl-form-input
|
||||
id="device-name"
|
||||
v-model="form.deviceName"
|
||||
name="device_registration[name]"
|
||||
:placeholder="$options.I18N_DEVICE_NAME_PLACEHOLDER"
|
||||
data-testid="device-name-input"
|
||||
/>
|
||||
</gl-form-group>
|
||||
|
||||
<input type="hidden" name="device_registration[device_response]" :value="credentials" />
|
||||
<input :value="csrfToken" type="hidden" name="authenticity_token" />
|
||||
<input type="hidden" name="device_registration[device_response]" :value="credentials" />
|
||||
<input :value="csrfToken" type="hidden" name="authenticity_token" />
|
||||
|
||||
<gl-button type="submit" :disabled="disabled" variant="confirm">{{
|
||||
$options.I18N_BUTTON_REGISTER
|
||||
}}</gl-button>
|
||||
</gl-form>
|
||||
</div>
|
||||
<gl-button type="submit" :disabled="disabled" variant="confirm">{{
|
||||
$options.I18N_BUTTON_REGISTER
|
||||
}}</gl-button>
|
||||
</gl-form>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isCurrentState($options.STATE_ERROR)">
|
||||
<gl-alert
|
||||
variant="danger"
|
||||
:dismissible="false"
|
||||
class="gl-mb-5"
|
||||
:secondary-button-text="$options.I18N_BUTTON_TRY_AGAIN"
|
||||
@secondaryAction="onRegister"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,9 +15,6 @@ export const I18N_ERROR_HTTP = __(
|
|||
export const I18N_ERROR_UNSUPPORTED_BROWSER = __(
|
||||
"Your browser doesn't support WebAuthn. Please use a supported browser, e.g. Chrome (67+) or Firefox (60+).",
|
||||
);
|
||||
export const I18N_INFO_TEXT = __(
|
||||
'Your device needs to be set up. Plug it in (if needed) and click the button on the left.',
|
||||
);
|
||||
export const I18N_NOTICE = __(
|
||||
'You must save your recovery codes after you first register a two-factor authenticator, so you do not lose access to your account. %{linkStart}See the documentation on managing your WebAuthn device for more information.%{linkEnd}',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ const initMermaid = () => {
|
|||
},
|
||||
secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize', 'htmlLabels'],
|
||||
securityLevel: 'strict',
|
||||
dompurifyConfig: {
|
||||
ADD_TAGS: ['foreignObject'],
|
||||
HTML_INTEGRATION_POINTS: { foreignobject: true },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import Vue from 'vue';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import Vuex from 'vuex';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import VueRouter from 'vue-router';
|
||||
import { provideWebIdeLink } from 'ee_else_ce/pages/projects/shared/web_ide_link/provide_web_ide_link';
|
||||
import TableOfContents from '~/blob/components/table_contents.vue';
|
||||
import { BlobViewer, initAuxiliaryViewer } from '~/blob/viewer/index';
|
||||
import { __ } from '~/locale';
|
||||
import GpgBadges from '~/gpg_badges';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import initBlob from '~/pages/projects/init_blob';
|
||||
|
|
@ -18,7 +16,6 @@ import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
|
|||
import '~/sourcegraph/load';
|
||||
import createStore from '~/code_navigation/store';
|
||||
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
|
||||
import { generateHistoryUrl } from '~/repository/utils/url_utility';
|
||||
import RefSelector from '~/ref/components/ref_selector.vue';
|
||||
import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
|
|
@ -208,30 +205,3 @@ if (tableContentsEl) {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
const treeHistoryLinkEl = document.getElementById('js-commit-history-link');
|
||||
if (treeHistoryLinkEl) {
|
||||
const { historyLink } = treeHistoryLinkEl.dataset;
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: treeHistoryLinkEl,
|
||||
router,
|
||||
render(h) {
|
||||
const url = generateHistoryUrl(
|
||||
historyLink,
|
||||
this.$route.params.path,
|
||||
this.$route.meta.refType || this.$route.query.ref_type,
|
||||
);
|
||||
return h(
|
||||
GlButton,
|
||||
{
|
||||
attrs: {
|
||||
href: url.href,
|
||||
category: 'tertiary',
|
||||
},
|
||||
},
|
||||
[__('History')],
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { sanitize } from '~/lib/dompurify';
|
|||
import { InternalEvents } from '~/tracking';
|
||||
import { FIND_FILE_BUTTON_CLICK } from '~/tracking/constants';
|
||||
import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
|
||||
import { generateHistoryUrl } from '~/repository/utils/url_utility';
|
||||
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
|
||||
import RefSelector from '~/ref/components/ref_selector.vue';
|
||||
import Breadcrumbs from '~/repository/components/header_area/breadcrumbs.vue';
|
||||
|
|
@ -18,6 +19,7 @@ export default {
|
|||
i18n: {
|
||||
compare: __('Compare'),
|
||||
findFile: __('Find file'),
|
||||
history: __('History'),
|
||||
},
|
||||
components: {
|
||||
GlButton,
|
||||
|
|
@ -61,6 +63,10 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
historyLink: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectId: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -70,6 +76,15 @@ export default {
|
|||
isTreeView() {
|
||||
return this.$route.name !== 'blobPathDecoded';
|
||||
},
|
||||
historyPath() {
|
||||
const url = generateHistoryUrl(
|
||||
this.historyLink,
|
||||
this.$route.params.path,
|
||||
this.$route.meta.refType || this.$route.query.ref_type,
|
||||
);
|
||||
|
||||
return url.href;
|
||||
},
|
||||
getRefType() {
|
||||
return this.$route.query.ref_type;
|
||||
},
|
||||
|
|
@ -151,6 +166,9 @@ export default {
|
|||
class="shortcuts-compare"
|
||||
>{{ $options.i18n.compare }}</gl-button
|
||||
>
|
||||
<gl-button v-if="!isReadmeView" :href="historyPath" data-testid="tree-history-control">{{
|
||||
$options.i18n.history
|
||||
}}</gl-button>
|
||||
<gl-button
|
||||
v-gl-tooltip.html="findFileTooltip"
|
||||
:aria-keyshortcuts="findFileShortcutKey"
|
||||
|
|
|
|||
|
|
@ -177,12 +177,7 @@ export default {
|
|||
{{ $options.i18n.blame }}
|
||||
</gl-button>
|
||||
|
||||
<gl-button
|
||||
data-testid="history"
|
||||
:href="blobInfo.historyPath"
|
||||
:class="$options.buttonClassList"
|
||||
class="gl-block sm:gl-hidden"
|
||||
>
|
||||
<gl-button data-testid="history" :href="blobInfo.historyPath" :class="$options.buttonClassList">
|
||||
{{ $options.i18n.history }}
|
||||
</gl-button>
|
||||
|
||||
|
|
|
|||
|
|
@ -67,11 +67,6 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
historyUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -113,18 +108,17 @@ export default {
|
|||
|
||||
<template>
|
||||
<gl-loading-icon v-if="isLoading" size="md" color="dark" class="m-auto gl-min-h-8 gl-py-6" />
|
||||
|
||||
<commit-info v-else-if="commit" :commit="commit">
|
||||
<div class="commit-actions gl-flex gl-items-start">
|
||||
<div class="commit-actions gl-flex-align gl-flex gl-flex-row gl-items-center">
|
||||
<signature-badge v-if="commit.signature" :signature="commit.signature" />
|
||||
<div v-if="commit.pipeline" class="gl-ml-5 gl-flex gl-h-7 gl-items-center">
|
||||
<div v-if="commit.pipeline" class="gl-ml-5">
|
||||
<ci-icon
|
||||
:status="commit.pipeline.detailedStatus"
|
||||
:aria-label="statusTitle"
|
||||
class="js-commit-pipeline"
|
||||
/>
|
||||
</div>
|
||||
<gl-button-group class="js-commit-sha-group gl-ml-4 gl-flex gl-items-center">
|
||||
<gl-button-group class="js-commit-sha-group gl-ml-4">
|
||||
<gl-button label class="gl-font-monospace" data-testid="last-commit-id-label">{{
|
||||
showCommitId
|
||||
}}</gl-button>
|
||||
|
|
@ -134,14 +128,6 @@ export default {
|
|||
class="input-group-text"
|
||||
/>
|
||||
</gl-button-group>
|
||||
<gl-button
|
||||
category="tertiary"
|
||||
data-testid="last-commit-history"
|
||||
:href="historyUrl"
|
||||
class="gl-ml-4"
|
||||
>
|
||||
{{ __('History') }}
|
||||
</gl-button>
|
||||
</div>
|
||||
</commit-info>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -116,24 +116,16 @@ export default function setupVueRepositoryList() {
|
|||
});
|
||||
};
|
||||
|
||||
const lastCommitEl = document.getElementById('js-last-commit');
|
||||
|
||||
const initLastCommitApp = () =>
|
||||
new Vue({
|
||||
el: lastCommitEl,
|
||||
el: document.getElementById('js-last-commit'),
|
||||
router,
|
||||
apolloProvider,
|
||||
render(h) {
|
||||
const historyUrl = generateHistoryUrl(
|
||||
lastCommitEl.dataset.historyLink,
|
||||
this.$route.params.path,
|
||||
this.$route.meta.refType || this.$route.query.ref_type,
|
||||
);
|
||||
return h(LastCommit, {
|
||||
props: {
|
||||
currentPath: this.$route.params.path,
|
||||
refType: this.$route.meta.refType || this.$route.query.ref_type,
|
||||
historyUrl: historyUrl.href,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export default function initHeaderApp(isReadmeView = false) {
|
|||
const headerEl = document.getElementById('js-repository-blob-header-app');
|
||||
if (headerEl) {
|
||||
const {
|
||||
historyLink,
|
||||
ref,
|
||||
escapedRef,
|
||||
refType,
|
||||
|
|
@ -57,6 +58,7 @@ export default function initHeaderApp(isReadmeView = false) {
|
|||
props: {
|
||||
refType,
|
||||
currentRef: ref,
|
||||
historyLink,
|
||||
// BlobControls:
|
||||
projectPath,
|
||||
// RefSelector:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { GlIntersectionObserver } from '@gitlab/ui';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import TokenPermissions from './token_permissions.vue';
|
||||
import OutboundTokenAccess from './outbound_token_access.vue';
|
||||
|
|
@ -6,21 +7,33 @@ import InboundTokenAccess from './inbound_token_access.vue';
|
|||
import AuthLog from './auth_log.vue';
|
||||
|
||||
export default {
|
||||
name: 'TokenAccessApp',
|
||||
components: {
|
||||
GlIntersectionObserver,
|
||||
AuthLog,
|
||||
InboundTokenAccess,
|
||||
OutboundTokenAccess,
|
||||
TokenPermissions,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
data() {
|
||||
return {
|
||||
isVisible: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updateVisible({ isIntersecting }) {
|
||||
this.isVisible = isIntersecting;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<token-permissions v-if="glFeatures.allowPushRepositoryForJobToken" />
|
||||
<inbound-token-access />
|
||||
<auth-log />
|
||||
<outbound-token-access />
|
||||
</div>
|
||||
<gl-intersection-observer @update="updateVisible">
|
||||
<template v-if="isVisible">
|
||||
<token-permissions v-if="glFeatures.allowPushRepositoryForJobToken" />
|
||||
<inbound-token-access />
|
||||
<auth-log />
|
||||
<outbound-token-access />
|
||||
</template>
|
||||
</gl-intersection-observer>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@
|
|||
.commit-content {
|
||||
padding-right: 10px;
|
||||
white-space: normal;
|
||||
min-width: 0;
|
||||
|
||||
.commit-title {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ module EnforcesTwoFactorAuthentication
|
|||
end
|
||||
|
||||
def two_factor_verifier
|
||||
@two_factor_verifier ||= Gitlab::Auth::TwoFactorAuthVerifier.new(current_user, request) # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
@two_factor_verifier ||= Gitlab::Auth::TwoFactorAuthVerifier.new(current_user, request)
|
||||
end
|
||||
|
||||
def mfa_help_page_url
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ module Integrations
|
|||
field :send_from_committer_email,
|
||||
type: :checkbox,
|
||||
title: -> { s_("EmailsOnPushService|Send from committer") },
|
||||
description: -> { s_("EmailsOnPushService|Send from committer") },
|
||||
help: -> do
|
||||
@help ||= begin
|
||||
domains = Notify.allowed_email_domains.map { |domain| "user@#{domain}" }.join(", ")
|
||||
|
|
@ -23,15 +24,18 @@ module Integrations
|
|||
field :disable_diffs,
|
||||
type: :checkbox,
|
||||
title: -> { s_("EmailsOnPushService|Disable code diffs") },
|
||||
help: -> { s_("EmailsOnPushService|Don't include possibly sensitive code diffs in notification body.") }
|
||||
help: -> { s_("EmailsOnPushService|Don't include possibly sensitive code diffs in notification body.") },
|
||||
description: -> { s_("EmailsOnPushService|Disable code diffs") }
|
||||
|
||||
field :branches_to_be_notified,
|
||||
type: :select,
|
||||
title: -> { s_('Integrations|Branches for which notifications are to be sent') },
|
||||
description: -> { _('Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`.') },
|
||||
choices: branch_choices
|
||||
|
||||
field :recipients,
|
||||
type: :textarea,
|
||||
required: true,
|
||||
placeholder: -> { s_('EmailsOnPushService|tanuki@example.com gitlab@example.com') },
|
||||
help: -> { s_('EmailsOnPushService|Emails separated by whitespace.') }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SentNotification < ApplicationRecord
|
||||
include EachBatch
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :noteable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
belongs_to :recipient, class_name: "User"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class Subscription < ApplicationRecord
|
||||
include FromUnion
|
||||
include EachBatch
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :project
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module WorkItems
|
|||
attr_reader :work_item, :service_response, :target_namespace
|
||||
|
||||
# work_item - original work item
|
||||
# target_namespace - ProjectNamespace(not Project) or Group
|
||||
# target_namespace - ProjectNamespace, Group or Project
|
||||
# current_user - user performing the move/clone action
|
||||
def initialize(work_item:, target_namespace:, current_user: nil, params: {})
|
||||
@work_item = work_item
|
||||
|
|
@ -22,11 +22,7 @@ module WorkItems
|
|||
|
||||
return verification_response if verification_response.error?
|
||||
|
||||
::ApplicationRecord.transaction do
|
||||
@service_response = data_sync_action
|
||||
end
|
||||
|
||||
service_response
|
||||
data_sync_action
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -5,11 +5,64 @@ module WorkItems
|
|||
module Widgets
|
||||
class Notifications < Base
|
||||
def after_save_commit
|
||||
# copy Notifications
|
||||
return unless params[:operation] == :move
|
||||
return unless target_work_item.get_widget(:notifications)
|
||||
|
||||
work_item.subscriptions.each_batch(of: BATCH_SIZE) do |subscriptions_batch|
|
||||
::Subscription.insert_all(new_work_item_subscriptions(subscriptions_batch))
|
||||
end
|
||||
|
||||
# This replicates current move Issues::MoveService behaviour. This should be changed though to
|
||||
# move sent_notifications for any work_item that is being moved.
|
||||
return unless work_item.from_service_desk?
|
||||
|
||||
# When moving sent notifications for any work item this can entail updating many records in some instances.
|
||||
# We should consider moving this to an async worker rather than have it run in a single request.
|
||||
work_item.sent_notifications.each_batch(of: BATCH_SIZE) do |sent_notifications_batch|
|
||||
# SentNotification does not have a sharding key yet.
|
||||
#
|
||||
# However when it has one it would potentially break
|
||||
# the immutability of the sharding key, because we need to update the record in place to keep the same
|
||||
# reply_key, which is a unique key.
|
||||
#
|
||||
# The alternative implies to create a new unique key and a mapping between the two keys, which requires
|
||||
# adding a new table or array column to keep the old keys, i.e. move development effort.
|
||||
::SentNotification.upsert_all(
|
||||
new_work_item_sent_notifications(sent_notifications_batch), unique_by: :reply_key
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def post_move_cleanup
|
||||
# do it
|
||||
work_item.subscriptions.each_batch(of: BATCH_SIZE) do |subscriptions_batch|
|
||||
subscriptions_batch.delete_all
|
||||
end
|
||||
|
||||
# When moving sent notifications for any work item this can entail deleting many records in some instances.
|
||||
# We should consider moving this to an async worker rather than have it run in a single request.
|
||||
work_item.sent_notifications.each_batch(of: BATCH_SIZE) do |sent_notifications_batch|
|
||||
sent_notifications_batch.delete_all
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_work_item_subscriptions(subscriptions_batch)
|
||||
subscriptions_batch.map do |subscription|
|
||||
subscription.attributes.except("id").tap do |ep|
|
||||
ep["subscribable_id"] = target_work_item.id
|
||||
ep["project_id"] = target_work_item.project_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def new_work_item_sent_notifications(sent_notifications_batch)
|
||||
sent_notifications_batch.map do |sent_notification|
|
||||
sent_notification.attributes.except("id").tap do |ep|
|
||||
ep["noteable_id"] = target_work_item.id
|
||||
ep["project_id"] = target_work_item.project_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
- breadcrumb_title _('Two-Factor Authentication')
|
||||
- page_title _('Two-Factor Authentication'), _('Account')
|
||||
- title = _('Two-Factor Authentication')
|
||||
- breadcrumb_title title
|
||||
- page_title title, _('Account')
|
||||
- add_to_breadcrumbs _('Account'), profile_account_path
|
||||
- troubleshooting_link = link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication_troubleshooting.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
= render ::Layouts::PageHeadingComponent.new(title, options: { class: 'gl-mb-3' })
|
||||
|
||||
- if @error
|
||||
= render Pajamas::AlertComponent.new(title: @error[:message],
|
||||
variant: :danger,
|
||||
|
|
@ -10,24 +13,23 @@
|
|||
dismissible: false) do |c|
|
||||
- c.with_body do
|
||||
= troubleshooting_link
|
||||
.row.gl-mt-3
|
||||
.col-lg-4
|
||||
%h4.gl-mt-0
|
||||
= _('Register a one-time password authenticator')
|
||||
%p
|
||||
= _('Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).')
|
||||
.col-lg-8
|
||||
|
||||
= render ::Layouts::SettingsSectionComponent.new(_('Register a one-time password authenticator')) do |c|
|
||||
- c.with_description do
|
||||
= _('Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).')
|
||||
- if current_user.two_factor_otp_enabled?
|
||||
= _("You've already enabled two-factor authentication using a one-time password authenticator. In order to register a different device, you must first delete this authenticator.")
|
||||
- else
|
||||
- register_2fa_token = _('We recommend using cloud-based authenticator applications that can restore access if you lose your hardware device.')
|
||||
= register_2fa_token.html_safe
|
||||
= link_to _('What are some examples?'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'enable-a-one-time-password-authenticator'), target: '_blank', rel: 'noopener noreferrer'
|
||||
- c.with_body do
|
||||
- if current_user.two_factor_otp_enabled?
|
||||
%p
|
||||
= _("You've already enabled two-factor authentication using a one-time password authenticator. In order to register a different device, you must first delete this authenticator.")
|
||||
.gl-inline-block.gl-w-full.sm:gl-w-auto
|
||||
.js-two-factor-action-confirm{ data: delete_otp_authenticator_data(current_password_required?) }
|
||||
|
||||
- else
|
||||
%p
|
||||
- register_2fa_token = _('We recommend using cloud-based authenticator applications that can restore access if you lose your hardware device.')
|
||||
= register_2fa_token.html_safe
|
||||
= link_to _('What are some examples?'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'enable-a-one-time-password-authenticator'), target: '_blank', rel: 'noopener noreferrer'
|
||||
.gl-p-2.gl-mb-3{ style: 'background: #fff' }
|
||||
.gl-inline-block.gl-p-3.gl-pb-2.gl-mb-5{ style: 'background: #fff' }
|
||||
= raw @qr_code
|
||||
.gl-mb-5
|
||||
= render Pajamas::CardComponent.new do |c|
|
||||
|
|
@ -36,9 +38,9 @@
|
|||
= _("Can't scan the code?")
|
||||
%p.gl-mt-0.gl-mb-3
|
||||
= _("To add the entry manually, provide the following details to the application on your phone.")
|
||||
%p.gl-mt-0.gl-mb-0
|
||||
%p.gl-my-0
|
||||
= _('Account: %{account}') % { account: @account_string }
|
||||
%p.gl-mt-0.gl-mb-0{ data: { testid: 'otp-secret-content' } }
|
||||
%p.gl-my-0{ data: { testid: 'otp-secret-content' } }
|
||||
= _('Key:')
|
||||
%code.two-factor-secret= current_user.otp_secret.scan(/.{4}/).join(' ')
|
||||
%p.gl-mb-0.two-factor-new-manual-content
|
||||
|
|
@ -63,58 +65,55 @@
|
|||
.gl-mt-3
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { data: { testid: 'register-2fa-app-button' } }) do
|
||||
= _('Register with two-factor app')
|
||||
%hr
|
||||
.row.gl-mt-3
|
||||
.col-lg-4
|
||||
%h4.gl-mt-0
|
||||
= _('Register a WebAuthn device')
|
||||
%p
|
||||
= _('Set up a hardware device to enable two-factor authentication (2FA).')
|
||||
%p
|
||||
= _("Not all browsers support WebAuthn. You must save your recovery codes after you first register a two-factor authenticator to be able to sign in, even from an unsupported browser.")
|
||||
.col-lg-8
|
||||
|
||||
= render ::Layouts::SettingsSectionComponent.new(_('Register a WebAuthn device')) do |c|
|
||||
- c.with_description do
|
||||
= _('Set up a hardware device to enable two-factor authentication (2FA).')
|
||||
= _("Not all browsers support WebAuthn. You must save your recovery codes after you first register a two-factor authenticator to be able to sign in, even from an unsupported browser.")
|
||||
- c.with_body do
|
||||
- if @webauthn_registration.errors.present?
|
||||
= form_errors(@webauthn_registration)
|
||||
|
||||
#js-device-registration{ data: device_registration_data(current_password_required: current_password_required?, target_path: create_webauthn_profile_two_factor_auth_path, webauthn_error: @webauthn_error) }
|
||||
%hr
|
||||
%h5
|
||||
= _('WebAuthn Devices (%{length})') % { length: @registrations.length }
|
||||
- if @registrations.present?
|
||||
.table-responsive
|
||||
%table.table
|
||||
%colgroup
|
||||
%col{ width: "50%" }
|
||||
%col{ width: "30%" }
|
||||
%col{ width: "20%" }
|
||||
%thead
|
||||
%tr
|
||||
%th= _('Name')
|
||||
%th= s_('2FADevice|Registered On')
|
||||
%th
|
||||
%tbody
|
||||
- @registrations.each do |registration|
|
||||
%tr
|
||||
%td
|
||||
- if registration[:name].present?
|
||||
= registration[:name]
|
||||
- else
|
||||
%span.gl-text-gray-500
|
||||
= _("no name set")
|
||||
%td= registration[:created_at].to_date.to_fs(:medium)
|
||||
%td
|
||||
.gl-float-right
|
||||
.js-two-factor-action-confirm{ data: delete_webauthn_device_data(current_password_required?, registration[:delete_path]) }
|
||||
- else
|
||||
.settings-message.text-center
|
||||
= _("You don't have any WebAuthn devices registered yet.")
|
||||
%hr
|
||||
.row.gl-mt-3
|
||||
.col-lg-4
|
||||
%h4.gl-mt-0
|
||||
= _('Disable two-factor authentication')
|
||||
%p
|
||||
= _('Use this section to disable your one-time password authenticator and WebAuthn devices. You can also generate new recovery codes.')
|
||||
.col-lg-8
|
||||
|
||||
= render ::Layouts::CrudComponent.new(_('WebAuthn Devices'),
|
||||
icon: 'lock',
|
||||
count: @registrations.length) do |c|
|
||||
- c.with_body do
|
||||
- if @registrations.present?
|
||||
.table-responsive
|
||||
%table.table.gl-table
|
||||
%colgroup
|
||||
%col{ width: "50%" }
|
||||
%col{ width: "30%" }
|
||||
%col{ width: "20%" }
|
||||
%thead
|
||||
%tr
|
||||
%th= _('Name')
|
||||
%th= s_('2FADevice|Registered On')
|
||||
%th
|
||||
%tbody
|
||||
- @registrations.each do |registration|
|
||||
%tr
|
||||
%td
|
||||
- if registration[:name].present?
|
||||
= registration[:name]
|
||||
- else
|
||||
%span.gl-text-subtle
|
||||
= _("no name set")
|
||||
%td= registration[:created_at].to_date.to_fs(:medium)
|
||||
%td{ class: '!gl-py-3' }
|
||||
.gl-float-right
|
||||
.js-two-factor-action-confirm{ data: delete_webauthn_device_data(current_password_required?, registration[:delete_path]) }
|
||||
|
||||
- else
|
||||
.gl-text-subtle
|
||||
= _("You don't have any WebAuthn devices registered yet.")
|
||||
|
||||
= render ::Layouts::SettingsSectionComponent.new(_('Disable two-factor authentication')) do |c|
|
||||
- c.with_description do
|
||||
= _('Use this section to disable your one-time password authenticator and WebAuthn devices. You can also generate new recovery codes.')
|
||||
- c.with_body do
|
||||
- if current_user.two_factor_enabled?
|
||||
%p
|
||||
= _('If you lose your recovery codes you can generate new ones, invalidating all previous codes.')
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#js-fork-info{ data: vue_fork_divergence_data(project, ref) }
|
||||
|
||||
.info-well.gl-hidden.sm:gl-flex.project-last-commit.gl-flex-col.gl-mt-5
|
||||
#js-last-commit.gl-m-auto{ data: {ref_type: @ref_type.to_s, history_link: project_commits_path(@project, @ref)} }
|
||||
#js-last-commit.gl-m-auto{ data: {ref_type: @ref_type.to_s} }
|
||||
= gl_loading_icon(size: 'md')
|
||||
- if project.licensed_feature_available?(:code_owners)
|
||||
#js-code-owners{ data: { branch: @ref, can_view_branch_rules: can_view_branch_rules?, branch_rules_path: branch_rules_path } }
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
.info-well.gl-hidden.sm:gl-block
|
||||
.well-segment
|
||||
%ul.blob-commit-info
|
||||
= render 'projects/commits/commit', commit: @last_commit, project: @project, ref: @ref, show_legacy_ci_icon: false, show_history_button: true
|
||||
= render 'projects/commits/commit', commit: @last_commit, project: @project, ref: @ref, show_legacy_ci_icon: false
|
||||
|
||||
- if project.licensed_feature_available?(:code_owners)
|
||||
#js-code-owners{ data: { blob_path: blob.path, project_path: @project.full_path, branch: @ref, can_view_branch_rules: can_view_branch_rules?, branch_rules_path: branch_rules_path } }
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@
|
|||
- else
|
||||
= link_button_to _('Blame'), project_blame_path(@project, @id, ref_type: @ref_type), data: { event_tracking: 'click_blame_control_on_blob_page' }, class: 'js-blob-blame-link' unless blob.empty?
|
||||
|
||||
.gl-block.sm:gl-hidden.gl-w-full
|
||||
= link_button_to _('History'), project_commits_path(@project, @id, ref_type: @ref_type), data: { event_tracking: 'click_history_control_on_blob_page' }
|
||||
= link_button_to _('History'), project_commits_path(@project, @id, ref_type: @ref_type), data: { event_tracking: 'click_history_control_on_blob_page' }
|
||||
|
||||
- permalink_description = _('Go to permalink')
|
||||
- permalink_shortcut = 'y'
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
- commit = commit.present(current_user: current_user)
|
||||
- commit_status = commit.detailed_status_for(ref)
|
||||
- show_legacy_ci_icon = local_assigns.fetch(:show_legacy_ci_icon, true)
|
||||
- show_history_button = local_assigns.fetch(:show_history_button, false)
|
||||
- tags = commit.tags_for_display
|
||||
- collapsible = local_assigns.fetch(:collapsible, true)
|
||||
- link_data_attrs = local_assigns.fetch(:link_data_attrs, {})
|
||||
|
|
@ -29,7 +28,7 @@
|
|||
.gl-self-start.gl-hidden.sm:gl-block
|
||||
= author_avatar(commit, size: 32, has_tooltip: false)
|
||||
|
||||
.commit-detail.flex-list.gl-flex.gl-justify-between.gl-items-start.gl-grow.gl-min-w-0
|
||||
.commit-detail.flex-list.gl-flex.gl-justify-between.gl-items-center.gl-grow.gl-min-w-0
|
||||
.commit-content{ data: { testid: 'commit-content' } }
|
||||
- if view_details && merge_request
|
||||
= link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: ["commit-row-message item-title js-onboarding-commit-item", ("font-italic" if commit.message.empty?)], data: link_data_attrs
|
||||
|
|
@ -59,8 +58,7 @@
|
|||
%pre{ class: ["commit-row-description gl-mb-3 gl-whitespace-pre-wrap", (collapsible ? "js-toggle-content" : "!gl-block")] }
|
||||
= preserve(markdown_field(commit, :description))
|
||||
|
||||
|
||||
.commit-actions.gl-flex.gl-items-center
|
||||
.commit-actions.flex-row
|
||||
- if tags.present?
|
||||
= gl_badge_tag(variant: :neutral, icon: 'tag', class: 'gl-font-monospace') do
|
||||
- if tags.size > 1
|
||||
|
|
@ -84,8 +82,3 @@
|
|||
= commit.short_id
|
||||
= clipboard_button(text: commit.id, category: :primary, size: :medium, title: _("Copy commit SHA"))
|
||||
= link_to_browse_code(project, commit)
|
||||
|
||||
- if show_history_button
|
||||
#js-commit-history-link{ data: { history_link: project_commits_path(@project, @ref) } }
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary, button_options: {class: 'gl-ml-4'}) do
|
||||
= _('History')
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@
|
|||
= render_if_exists 'projects/tree/lock_link'
|
||||
= render 'projects/buttons/compare', project: @project, ref: @ref, root_ref: @repository&.root_ref
|
||||
|
||||
.gl-block.sm:gl-hidden.gl-w-full
|
||||
#js-tree-history-link{ data: { history_link: project_commits_path(@project, @ref) } }
|
||||
#js-tree-history-link{ data: { history_link: project_commits_path(@project, @ref) } }
|
||||
|
||||
= render 'projects/find_file_link'
|
||||
= render 'shared/web_ide_button', blob: nil, css_classes: 'gl-w-full sm:gl-w-auto'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AuthorizedProjectUpdate
|
||||
class UserRefreshOverUserRangeWorker # rubocop:disable Scalability/IdempotentWorker
|
||||
class UserRefreshOverUserRangeWorker
|
||||
# This worker checks if users requires an update to their project_authorizations records.
|
||||
# This check is done via the data read from the database replica (and not from the primary).
|
||||
# If this check returns true, a completely new Sidekiq job is enqueued for a specific user
|
||||
|
|
|
|||
|
|
@ -8,5 +8,11 @@ module Search
|
|||
feature_category :global_search
|
||||
concurrency_limit -> { ::Search.default_concurrency_limit }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def logger
|
||||
::Gitlab::Elasticsearch::Logger.build
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -813,6 +813,9 @@ Gitlab.ee do
|
|||
Settings.cron_jobs['elastic_index_bulk_cron_worker'] ||= {}
|
||||
Settings.cron_jobs['elastic_index_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
|
||||
Settings.cron_jobs['elastic_index_bulk_cron_worker']['job_class'] ||= 'ElasticIndexBulkCronWorker'
|
||||
Settings.cron_jobs['elastic_indexing_control_worker'] ||= {}
|
||||
Settings.cron_jobs['elastic_indexing_control_worker']['cron'] ||= '*/1 * * * *'
|
||||
Settings.cron_jobs['elastic_indexing_control_worker']['job_class'] ||= 'ElasticIndexingControlWorker'
|
||||
Settings.cron_jobs['elastic_index_embedding_bulk_cron_worker'] ||= {}
|
||||
Settings.cron_jobs['elastic_index_embedding_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
|
||||
Settings.cron_jobs['elastic_index_embedding_bulk_cron_worker']['job_class'] ||= 'Search::ElasticIndexEmbeddingBulkCronWorker'
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Rails.application.configure do |config|
|
|||
when :set_user
|
||||
activity.user_authenticated!
|
||||
activity.user_session_override!
|
||||
when :fetch # rubocop:disable Lint/EmptyWhen
|
||||
when :fetch
|
||||
# We ignore session fetch events
|
||||
else
|
||||
activity.user_session_override!
|
||||
|
|
|
|||
|
|
@ -321,8 +321,6 @@
|
|||
- 1
|
||||
- - elastic_full_index
|
||||
- 1
|
||||
- - elastic_indexing_control
|
||||
- 1
|
||||
- - elastic_namespace_indexer
|
||||
- 1
|
||||
- - elastic_namespace_rollout
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
migration_job_name: DeleteDuplicateIssuableResourceLinks
|
||||
description: Remove the duplicates for the production DB.
|
||||
feature_category: database
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172230
|
||||
milestone: '17.6'
|
||||
queued_migration_version: 20241111055711
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
@ -7,4 +7,4 @@ milestone: '17.4'
|
|||
queued_migration_version: 20240910154923
|
||||
# Replace with the approximate date you think it's best to ensure the completion of this BBM.
|
||||
finalize_after: '2024-09-17'
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
finalized_by: 20241115122644
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UpdateSubscriptionsIndexOnNoteable < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.6'
|
||||
|
||||
INDEX_NAME = 'index_subscriptions_on_subscribable_type_subscribable_id_and_id'
|
||||
COLUMN_NAMES = %i[subscribable_id subscribable_type id]
|
||||
|
||||
# Index to be created synchronously in https://gitlab.com/gitlab-org/gitlab/-/issues/502840
|
||||
#
|
||||
# This is designed to improve iterating over related subscriptions records in batches:
|
||||
def up
|
||||
prepare_async_index :subscriptions, COLUMN_NAMES, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
unprepare_async_index :subscriptions, COLUMN_NAMES, name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop: disable Migration/PreventIndexCreation -- update an existing index
|
||||
class UpdateSentNotificationsIndexOnNoteable < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.6'
|
||||
|
||||
INDEX_NAME = 'index_sent_notifications_on_noteable_type_noteable_id_and_id'
|
||||
COLUMN_NAMES = %i[noteable_id id]
|
||||
|
||||
# Index to be created synchronously in https://gitlab.com/gitlab-org/gitlab/-/issues/502841
|
||||
#
|
||||
# This is designed to replace existing index:
|
||||
# "index_sent_notifications_on_noteable_type_noteable_id" btree (noteable_id) WHERE noteable_type = 'Issue'
|
||||
# with
|
||||
# "index_sent_notifications_on_noteable_type_noteable_id_id" btree (noteable_id, id) WHERE noteable_type = 'Issue'
|
||||
# to improve iterating over issue related sent notification records in batches.
|
||||
def up
|
||||
prepare_async_index :sent_notifications, COLUMN_NAMES, where: "noteable_type = 'Issue'", name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
unprepare_async_index :sent_notifications, COLUMN_NAMES, name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
# rubocop: enable Migration/PreventIndexCreation
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueDeleteDuplicateIssuableResourceLinks < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.6'
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
MIGRATION = "DeleteDuplicateIssuableResourceLinks"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 1000
|
||||
SUB_BATCH_SIZE = 100
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:issuable_resource_links,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :issuable_resource_links, :id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FinalizeDeleteOrphanedBuildRecords < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_ci
|
||||
|
||||
MIGRATION = 'DeleteOrphanedBuildRecords'
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: MIGRATION,
|
||||
table_name: :p_ci_builds,
|
||||
column_name: :commit_id,
|
||||
job_arguments: [],
|
||||
finalize: true
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
569a9ecb955d32ad1b10c50906dd3659ee7524b71be7a7b6e0c9c99525c2c4db
|
||||
|
|
@ -0,0 +1 @@
|
|||
c5170b7d25515fe41978abf6bf370adb943e6d134738180116dbf9eb046065aa
|
||||
|
|
@ -0,0 +1 @@
|
|||
390cf83a10bc461d45f0c47eada3f25f3dd611d7c6a124bfd8bf01099f793d60
|
||||
|
|
@ -0,0 +1 @@
|
|||
374b6394e31f923d98c2fd5b5eedb3aaf46bdcef977f5bda6535569334835e82
|
||||
|
|
@ -114,32 +114,7 @@ module API
|
|||
chat_notification_channels
|
||||
].flatten,
|
||||
'drone-ci' => ::Integrations::DroneCi.api_arguments,
|
||||
'emails-on-push' => [
|
||||
{
|
||||
required: true,
|
||||
name: :recipients,
|
||||
type: String,
|
||||
desc: 'Comma-separated list of recipient email addresses'
|
||||
},
|
||||
{
|
||||
required: false,
|
||||
name: :disable_diffs,
|
||||
type: ::Grape::API::Boolean,
|
||||
desc: 'Disable code diffs'
|
||||
},
|
||||
{
|
||||
required: false,
|
||||
name: :send_from_committer_email,
|
||||
type: ::Grape::API::Boolean,
|
||||
desc: 'Send from committer'
|
||||
},
|
||||
{
|
||||
required: false,
|
||||
name: :branches_to_be_notified,
|
||||
type: String,
|
||||
desc: 'Branches for which notifications are to be sent'
|
||||
}
|
||||
],
|
||||
'emails-on-push' => ::Integrations::EmailsOnPush.api_arguments,
|
||||
'external-wiki' => ::Integrations::ExternalWiki.api_arguments,
|
||||
'gitlab-slack-application' => [
|
||||
::Integrations::GitlabSlackApplication.api_arguments,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ module Atlassian
|
|||
ALGORITHM = 'RS256'
|
||||
DEFAULT_PUBLIC_KEY_CDN_URL = 'https://connect-install-keys.atlassian.com'
|
||||
PROXY_PUBLIC_KEY_PATH = '/-/jira_connect/public_keys'
|
||||
UUID4_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/
|
||||
KEY_ID_REGEX = %r{\A[A-Za-z0-9_\-\/]+\z}
|
||||
|
||||
def initialize(token, verification_claims)
|
||||
@token = token
|
||||
|
|
@ -61,7 +61,7 @@ module Atlassian
|
|||
end
|
||||
|
||||
def retrieve_public_key(key_id)
|
||||
raise KeyFetchError unless UUID4_REGEX.match?(key_id)
|
||||
raise KeyFetchError unless KEY_ID_REGEX.match?(key_id)
|
||||
|
||||
public_key = Gitlab::HTTP.try_get("#{public_key_cdn_url}/#{key_id}").try(:body)
|
||||
|
||||
|
|
|
|||
|
|
@ -91,9 +91,7 @@ module Gitlab
|
|||
def optionally_run_in_admin_mode(user)
|
||||
raise NonSidekiqEnvironmentError unless Gitlab::Runtime.sidekiq?
|
||||
|
||||
unless Gitlab::CurrentSettings.admin_mode && user.admin? # rubocop:disable Cop/UserAdmin -- policy checks should be enforced further down the stack
|
||||
return yield
|
||||
end
|
||||
return yield unless Gitlab::CurrentSettings.admin_mode && user.admin?
|
||||
|
||||
bypass_session!(user.id) do
|
||||
with_current_admin(user) do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class DeleteDuplicateIssuableResourceLinks < BatchedMigrationJob
|
||||
operation_name :delete_duplicate_issuable_resource_links
|
||||
scope_to ->(relation) { relation.where(is_unique: false) }
|
||||
feature_category :database
|
||||
|
||||
def perform
|
||||
each_sub_batch do |sub_batch|
|
||||
sub_batch.delete_all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -13,10 +13,7 @@ module Gitlab
|
|||
def initialize(group_full_path:, cut_off_date: DEFAULT_TIME_PERIOD.ago.beginning_of_day, logger: nil)
|
||||
@cut_off_date = cut_off_date
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
@group = Group.find_by_full_path(group_full_path)
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
raise "Group with full_path #{group_full_path} not found" unless @group
|
||||
raise "Invalid time: #{@cut_off_date}" unless @cut_off_date <= MINIMUM_TIME_PERIOD.ago
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ module Gitlab
|
|||
ActionController::HttpAuthentication::Basic.encode_credentials(actor_name, token)
|
||||
end
|
||||
|
||||
private # rubocop:disable Lint/UselessAccessModifier
|
||||
private
|
||||
|
||||
attr_reader :container
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ module Gitlab
|
|||
click_house_migration: ::Gitlab::SidekiqMiddleware::PauseControl::Strategies::ClickHouseMigration,
|
||||
zoekt: ::Gitlab::SidekiqMiddleware::PauseControl::Strategies::Zoekt,
|
||||
none: ::Gitlab::SidekiqMiddleware::PauseControl::Strategies::None,
|
||||
advanced_search: ::Gitlab::SidekiqMiddleware::PauseControl::Strategies::AdvancedSearch,
|
||||
deprecated: ::Gitlab::SidekiqMiddleware::PauseControl::Strategies::Deprecated
|
||||
}.freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module SidekiqMiddleware
|
||||
module PauseControl
|
||||
module Strategies
|
||||
class AdvancedSearch < Base
|
||||
override :should_pause?
|
||||
def should_pause?
|
||||
Gitlab::CurrentSettings.elasticsearch_pause_indexing?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -61808,7 +61808,7 @@ msgstr ""
|
|||
msgid "Web terminal"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebAuthn Devices (%{length})"
|
||||
msgid "WebAuthn Devices"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebAuthn only works with HTTPS-enabled websites. Contact your administrator for more details."
|
||||
|
|
@ -64547,9 +64547,6 @@ msgstr ""
|
|||
msgid "Your device is not compatible with GitLab. Please try another device"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your device needs to be set up. Plug it in (if needed) and click the button on the left."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@
|
|||
"deckar01-task_list": "^2.3.1",
|
||||
"dexie": "^3.2.3",
|
||||
"diff": "^3.4.0",
|
||||
"dompurify": "^3.1.6",
|
||||
"dompurify": "^3.2.0",
|
||||
"dropzone": "^4.2.0",
|
||||
"editorconfig": "^0.15.3",
|
||||
"emoji-regex": "^10.0.0",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ module RuboCop
|
|||
Search EE::Search API::Search EE::API::Search API::Admin::Search RuboCop::Cop::Search Types Resolvers
|
||||
API::Entities::Search::Zoekt API::Internal::Search::Zoekt
|
||||
Keeps
|
||||
Gitlab::SidekiqMiddleware::PauseControl::Strategies::AdvancedSearch
|
||||
].map { |x| x.split('::') }.freeze
|
||||
|
||||
SEARCH_REGEXES = [
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ global.Option = window.Option;
|
|||
DOMPurify.addHook = () => {};
|
||||
DOMPurify.sanitize = (x) => x;
|
||||
|
||||
const defaultGlob = "doc/**/*.md";
|
||||
const defaultGlob = 'doc/**/*.md';
|
||||
const mermaidMatch = /```mermaid(.*?)```/gms;
|
||||
|
||||
const argv = process.argv.length > 2 ? process.argv.slice(2) : [defaultGlob];
|
||||
const mdFiles = argv.flatMap((arg) => glob.sync(arg))
|
||||
const mdFiles = argv.flatMap((arg) => glob.sync(arg));
|
||||
|
||||
console.log(`Checking ${mdFiles.length} markdown files...`);
|
||||
|
||||
|
|
@ -43,6 +43,10 @@ mermaid.initialize({
|
|||
},
|
||||
secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize', 'htmlLabels'],
|
||||
securityLevel: 'strict',
|
||||
dompurifyConfig: {
|
||||
ADD_TAGS: ['foreignObject'],
|
||||
HTML_INTEGRATION_POINTS: { foreignobject: true },
|
||||
},
|
||||
});
|
||||
|
||||
let errors = 0;
|
||||
|
|
@ -71,6 +75,8 @@ await Promise.all(
|
|||
|
||||
if (errors > 0) {
|
||||
console.log(`Total errors: ${errors}`);
|
||||
console.log(`To fix these errors, see https://docs.gitlab.com/ee/development/documentation/testing/#mermaid-chart-linting.`);
|
||||
console.log(
|
||||
`To fix these errors, see https://docs.gitlab.com/ee/development/documentation/testing/#mermaid-chart-linting.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ RSpec.describe 'Projects tree', :js, feature_category: :web_ide do
|
|||
|
||||
# Check last commit
|
||||
expect(find('.commit-content').text).to include(message)
|
||||
expect(find_by_testid('last-commit-id-label').text).to eq(short_newrev)
|
||||
expect(find('.js-commit-sha-group').text).to eq(short_newrev)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
I18N_BUTTON_TRY_AGAIN,
|
||||
I18N_ERROR_HTTP,
|
||||
I18N_ERROR_UNSUPPORTED_BROWSER,
|
||||
I18N_INFO_TEXT,
|
||||
I18N_STATUS_SUCCESS,
|
||||
I18N_STATUS_WAITING,
|
||||
STATE_ERROR,
|
||||
|
|
@ -89,11 +88,10 @@ describe('Registration', () => {
|
|||
});
|
||||
|
||||
describe(`when ${STATE_READY} state`, () => {
|
||||
it('shows button and explanation text', () => {
|
||||
it('shows button', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findButton().text()).toBe(I18N_BUTTON_SETUP);
|
||||
expect(wrapper.text()).toContain(I18N_INFO_TEXT);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
describe('dompurify dependency', () => {
|
||||
it('should not be updated unless mermaid is verified', () => {
|
||||
// why: We use `require` so that we don't have to manually dig through node_modules
|
||||
// eslint-disable-next-line global-require
|
||||
const { version } = require('dompurify/package.json');
|
||||
|
||||
// NOTE: Bumping this to 3.1.7 breaks mermaid diagrams. When upgrading DOMPurify, you **must** manually verify that
|
||||
// mermaid diagrams work visually before fixing this test. For more context:
|
||||
// - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167644
|
||||
// - https://github.com/cure53/DOMPurify/issues/1002#issuecomment-2381258197
|
||||
expect(version).toEqual('3.1.6');
|
||||
});
|
||||
});
|
||||
|
|
@ -5,10 +5,10 @@ exports[`Repository last commit component renders commit widget 1`] = `
|
|||
commit="[object Object]"
|
||||
>
|
||||
<div
|
||||
class="commit-actions gl-flex gl-items-start"
|
||||
class="commit-actions gl-flex gl-flex-align gl-flex-row gl-items-center"
|
||||
>
|
||||
<div
|
||||
class="gl-flex gl-h-7 gl-items-center gl-ml-5"
|
||||
class="gl-ml-5"
|
||||
>
|
||||
<ci-icon-stub
|
||||
aria-label="Pipeline: failed"
|
||||
|
|
@ -19,7 +19,7 @@ exports[`Repository last commit component renders commit widget 1`] = `
|
|||
/>
|
||||
</div>
|
||||
<gl-button-group-stub
|
||||
class="gl-flex gl-items-center gl-ml-4 js-commit-sha-group"
|
||||
class="gl-ml-4 js-commit-sha-group"
|
||||
>
|
||||
<gl-button-stub
|
||||
buttontextclasses=""
|
||||
|
|
@ -43,18 +43,6 @@ exports[`Repository last commit component renders commit widget 1`] = `
|
|||
variant="default"
|
||||
/>
|
||||
</gl-button-group-stub>
|
||||
<gl-button-stub
|
||||
buttontextclasses=""
|
||||
category="tertiary"
|
||||
class="gl-ml-4"
|
||||
data-testid="last-commit-history"
|
||||
href="/history"
|
||||
icon=""
|
||||
size="medium"
|
||||
variant="default"
|
||||
>
|
||||
History
|
||||
</gl-button-stub>
|
||||
</div>
|
||||
</commit-info-stub>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ describe('HeaderArea', () => {
|
|||
|
||||
const findBreadcrumbs = () => wrapper.findComponent(Breadcrumbs);
|
||||
const findRefSelector = () => wrapper.findComponent(RefSelector);
|
||||
const findHistoryButton = () => wrapper.findByTestId('tree-history-control');
|
||||
const findFindFileButton = () => wrapper.findByTestId('tree-find-file-control');
|
||||
const findCompareButton = () => wrapper.findByTestId('tree-compare-control');
|
||||
const { bindInternalEventDocument } = useMockInternalEventsTracking();
|
||||
|
|
@ -94,6 +95,13 @@ describe('HeaderArea', () => {
|
|||
wrapper = createComponent({}, 'treePathDecoded');
|
||||
});
|
||||
|
||||
describe('History button', () => {
|
||||
it('renders History button with correct href', () => {
|
||||
expect(findHistoryButton().exists()).toBe(true);
|
||||
expect(findHistoryButton().attributes('href')).toContain('/history');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Find file button', () => {
|
||||
it('renders Find file button', () => {
|
||||
expect(findFindFileButton().exists()).toBe(true);
|
||||
|
|
@ -143,9 +151,10 @@ describe('HeaderArea', () => {
|
|||
wrapper = createComponent({}, 'treePathDecoded', { isReadmeView: true });
|
||||
});
|
||||
|
||||
it('does not render RefSelector or Breadcrumbs', () => {
|
||||
it('does not render RefSelector, Breadcrumbs and History button', () => {
|
||||
expect(findRefSelector().exists()).toBe(false);
|
||||
expect(findBreadcrumbs().exists()).toBe(false);
|
||||
expect(findHistoryButton().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ const findLastCommitLabel = () => wrapper.findByTestId('last-commit-id-label');
|
|||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findStatusBox = () => wrapper.findComponent(SignatureBadge);
|
||||
const findCommitInfo = () => wrapper.findComponent(CommitInfo);
|
||||
const findHistoryButton = () => wrapper.findByTestId('last-commit-history');
|
||||
|
||||
const defaultPipelineEdges = [
|
||||
{
|
||||
|
|
@ -99,7 +98,7 @@ const createComponent = async (data = {}) => {
|
|||
|
||||
wrapper = shallowMountExtended(LastCommit, {
|
||||
apolloProvider: createMockApollo([[pathLastCommitQuery, mockResolver]]),
|
||||
propsData: { currentPath, historyUrl: '/history' },
|
||||
propsData: { currentPath },
|
||||
mixins: [{ data: () => ({ ref: refMock }) }],
|
||||
stubs: {
|
||||
SignatureBadge,
|
||||
|
|
@ -145,11 +144,6 @@ describe('Repository last commit component', () => {
|
|||
expect(findLastCommitLabel().text()).toBe('12345678');
|
||||
});
|
||||
|
||||
it('renders History button with correct href', () => {
|
||||
expect(findHistoryButton().exists()).toBe(true);
|
||||
expect(findHistoryButton().attributes('href')).toContain('/history');
|
||||
});
|
||||
|
||||
it('hides pipeline components when pipeline does not exist', async () => {
|
||||
createComponent({ pipelineEdges: [] });
|
||||
await waitForPromises();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlIntersectionObserver } from '@gitlab/ui';
|
||||
import InboundTokenAccess from '~/token_access/components/inbound_token_access.vue';
|
||||
import OutboundTokenAccess from '~/token_access/components/outbound_token_access.vue';
|
||||
import TokenAccessApp from '~/token_access/components/token_access_app.vue';
|
||||
|
|
@ -10,38 +11,50 @@ describe('TokenAccessApp component', () => {
|
|||
const findInboundTokenAccess = () => wrapper.findComponent(InboundTokenAccess);
|
||||
const findOutboundTokenAccess = () => wrapper.findComponent(OutboundTokenAccess);
|
||||
const findTokenPermissions = () => wrapper.findComponent(TokenPermissions);
|
||||
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
|
||||
|
||||
const createComponent = ({ allowPushRepositoryForJobToken = true } = {}) => {
|
||||
wrapper = shallowMount(TokenAccessApp, {
|
||||
provide: {
|
||||
glFeatures: {
|
||||
allowPushRepositoryForJobToken,
|
||||
},
|
||||
},
|
||||
provide: { glFeatures: { allowPushRepositoryForJobToken } },
|
||||
});
|
||||
};
|
||||
|
||||
describe('default', () => {
|
||||
const emitIntersectionObserverUpdate = (isIntersecting) => {
|
||||
findIntersectionObserver().vm.$emit('update', { isIntersecting });
|
||||
};
|
||||
|
||||
describe.each`
|
||||
phrase | action | expected
|
||||
${'on page load'} | ${() => {}} | ${false}
|
||||
${'when section is expanded'} | ${() => emitIntersectionObserverUpdate(true)} | ${true}
|
||||
${'when section is collapsed'} | ${() => emitIntersectionObserverUpdate(false)} | ${false}
|
||||
`('$phrase', ({ action, expected }) => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
action();
|
||||
});
|
||||
|
||||
it('renders the outbound token access component', () => {
|
||||
expect(findOutboundTokenAccess().exists()).toBe(true);
|
||||
it('renders intersection observer', () => {
|
||||
expect(findIntersectionObserver().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the inbound token access component', () => {
|
||||
expect(findInboundTokenAccess().exists()).toBe(true);
|
||||
it('renders/does not render the outbound token access component', () => {
|
||||
expect(findOutboundTokenAccess().exists()).toBe(expected);
|
||||
});
|
||||
|
||||
it('renders the token permissions component', () => {
|
||||
expect(findTokenPermissions().exists()).toBe(true);
|
||||
it('renders/does not render the inbound token access component', () => {
|
||||
expect(findInboundTokenAccess().exists()).toBe(expected);
|
||||
});
|
||||
|
||||
it('renders/does not render the token permissions component', () => {
|
||||
expect(findTokenPermissions().exists()).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when allowPushRepositoryForJobToken feature flag is disabled', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ allowPushRepositoryForJobToken: false });
|
||||
findIntersectionObserver().vm.$emit('update', { isIntersecting: true });
|
||||
});
|
||||
|
||||
it('does not render the token permissions component', () => {
|
||||
|
|
|
|||
|
|
@ -44,8 +44,18 @@ RSpec.describe Atlassian::JiraConnect::Jwt::Asymmetric, feature_category: :integ
|
|||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'JWT contains a key ID that is not a valid UUID4' do
|
||||
let(:public_key_id) { '123' }
|
||||
context 'When public key ID is a string' do
|
||||
let(:public_key_id) { 'generic-keypair/cs-migrations/installation-key-pair--r0d6u65570m0lc8n' }
|
||||
|
||||
specify do
|
||||
expect(asymmetric_jwt).to be_valid
|
||||
|
||||
expect(WebMock).to have_requested(:get, install_keys_url)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when public key ID contains invalid characters' do
|
||||
let(:public_key_id) { '$#%' }
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable RSpec/MultipleMemoizedHelpers -- Needed in specs
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::DeleteDuplicateIssuableResourceLinks, feature_category: :database do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
let(:projects) { table(:projects) }
|
||||
let(:users) { table(:users) }
|
||||
let(:issues) { table(:issues) }
|
||||
let(:issuable_resource_links) { table(:issuable_resource_links) }
|
||||
let(:issue_base_type_enum_value) { 0 }
|
||||
let(:issue_type) { table(:work_item_types).find_by!(base_type: issue_base_type_enum_value) }
|
||||
|
||||
let(:user) { create_user(email: "test1@example.com", username: "test1") }
|
||||
let(:namespace) { namespaces.create!(name: "test-1", path: "test-1", owner_id: user.id) }
|
||||
let!(:project) do
|
||||
projects.create!(
|
||||
id: 9999, namespace_id: namespace.id,
|
||||
project_namespace_id: namespace.id,
|
||||
creator_id: user.id
|
||||
)
|
||||
end
|
||||
|
||||
let!(:issue1) { create_issue }
|
||||
let!(:issue2) { create_issue }
|
||||
|
||||
let!(:issuable_resource_link1) { issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link1", is_unique: false) }
|
||||
let!(:issuable_resource_link1_alt1) { issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link1alt", is_unique: false) }
|
||||
let!(:issuable_resource_link1_alt2) { issuable_resource_links.create!(issue_id: issue2.id, link: "https://gitlab.com/link1", is_unique: false) }
|
||||
let!(:issuable_resource_link2) do
|
||||
irl = issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link2")
|
||||
irl.update!(is_unique: true)
|
||||
irl
|
||||
end
|
||||
|
||||
let!(:issuable_resource_link2_alt1) do
|
||||
irl = issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link2alt")
|
||||
irl.update!(is_unique: true)
|
||||
irl
|
||||
end
|
||||
|
||||
let!(:issuable_resource_link2_alt2) do
|
||||
irl = issuable_resource_links.create!(issue_id: issue2.id, link: "https://gitlab.com/link2")
|
||||
irl.update!(is_unique: true)
|
||||
irl
|
||||
end
|
||||
|
||||
let!(:issuable_resource_link3) do
|
||||
irl = issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link3")
|
||||
irl.update!(is_unique: nil)
|
||||
irl
|
||||
end
|
||||
|
||||
let!(:issuable_resource_link3_alt1) do
|
||||
irl = issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link3alt")
|
||||
irl.update!(is_unique: nil)
|
||||
irl
|
||||
end
|
||||
|
||||
let!(:issuable_resource_link3_alt2) do
|
||||
irl = issuable_resource_links.create!(issue_id: issue2.id, link: "https://gitlab.com/link3")
|
||||
irl.update!(is_unique: nil)
|
||||
irl
|
||||
end
|
||||
|
||||
let!(:issuable_resource_link4) { issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link4", is_unique: true) }
|
||||
let!(:issuable_resource_link4_dup) { issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link4", is_unique: false) }
|
||||
let!(:issuable_resource_link5) { issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link5", is_unique: false) }
|
||||
let!(:issuable_resource_link5_dup) { issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link5", is_unique: false) }
|
||||
let!(:issuable_resource_link6) { issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link6", is_unique: nil) }
|
||||
let!(:issuable_resource_link6_dup) { issuable_resource_links.create!(issue_id: issue1.id, link: "https://gitlab.com/link6", is_unique: nil) }
|
||||
|
||||
let(:stating_id) { issuable_resource_links.pluck(:id).min }
|
||||
let(:end_id) { issuable_resource_links.pluck(:id).max }
|
||||
|
||||
subject(:migration) do
|
||||
described_class.new(
|
||||
start_id: stating_id,
|
||||
end_id: end_id,
|
||||
batch_table: :issuable_resource_links,
|
||||
batch_column: :id,
|
||||
sub_batch_size: 100,
|
||||
pause_ms: 0,
|
||||
connection: ApplicationRecord.connection
|
||||
)
|
||||
end
|
||||
|
||||
describe 'the deletion of issuable_resource_links' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:issuable_resource_link, :expected_is_unique, :expected_to_get_deleted) do
|
||||
ref(:issuable_resource_link1) | be(false) | true
|
||||
ref(:issuable_resource_link1_alt1) | be(false) | true
|
||||
ref(:issuable_resource_link1_alt2) | be(false) | true
|
||||
ref(:issuable_resource_link2) | be(true) | false
|
||||
ref(:issuable_resource_link2_alt1) | be(true) | false
|
||||
ref(:issuable_resource_link2_alt2) | be(true) | false
|
||||
ref(:issuable_resource_link3) | be_nil | false
|
||||
ref(:issuable_resource_link3_alt1) | be_nil | false
|
||||
ref(:issuable_resource_link3_alt2) | be_nil | false
|
||||
ref(:issuable_resource_link4) | be(true) | false
|
||||
ref(:issuable_resource_link4_dup) | be(false) | true
|
||||
ref(:issuable_resource_link5) | be(false) | true
|
||||
ref(:issuable_resource_link5_dup) | be(false) | true
|
||||
ref(:issuable_resource_link6) | be_nil | false
|
||||
ref(:issuable_resource_link6_dup) | be_nil | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'verifies the presence of the record' do
|
||||
expect(issuable_resource_links.find(issuable_resource_link.id).is_unique).to expected_is_unique
|
||||
|
||||
expect { migration.perform }.to change { issuable_resource_links.count }.by(-6)
|
||||
|
||||
if expected_to_get_deleted
|
||||
expect(issuable_resource_links.where(id: issuable_resource_link.id)).to be_empty
|
||||
else
|
||||
expect(issuable_resource_links.where(id: issuable_resource_link.id)).not_to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_issue
|
||||
issues.create!(
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
project_id: project.id,
|
||||
namespace_id: project.project_namespace_id,
|
||||
work_item_type_id: issue_type.id
|
||||
)
|
||||
end
|
||||
|
||||
def create_user(overrides = {})
|
||||
attrs = {
|
||||
email: "test@example.com",
|
||||
notification_email: "test@example.com",
|
||||
name: "test",
|
||||
username: "test",
|
||||
state: "active",
|
||||
projects_limit: 10
|
||||
}.merge(overrides)
|
||||
|
||||
users.create!(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:enable RSpec/MultipleMemoizedHelpers
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueDeleteDuplicateIssuableResourceLinks, migration: :gitlab_main, feature_category: :database do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
gitlab_schema: :gitlab_main,
|
||||
table_name: :issuable_resource_links,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Integrations::EmailsOnPush do
|
||||
let_it_be(:project) { create_default(:project).freeze }
|
||||
RSpec.describe Integrations::EmailsOnPush, feature_category: :integrations do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
describe 'Validations' do
|
||||
context 'when integration is active' do
|
||||
|
|
@ -87,7 +87,6 @@ RSpec.describe Integrations::EmailsOnPush do
|
|||
end
|
||||
|
||||
describe '#execute' do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let(:push_data) { { object_kind: 'push' } }
|
||||
let(:integration) { create(:emails_on_push_integration, project: project) }
|
||||
let(:recipients) { 'test@gitlab.com' }
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ RSpec.describe WorkItems::DataSync::Widgets::AwardEmoji, feature_category: :team
|
|||
context 'when cloning work item' do
|
||||
let(:params) { { operation: :clone } }
|
||||
|
||||
it 'copies award_emoji from work_item to target_work_item' do
|
||||
it 'does not copy award_emoji' do
|
||||
expect(callback).not_to receive(:new_work_item_award_emoji)
|
||||
expect(::AwardEmoji).not_to receive(:insert_all)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WorkItems::DataSync::Widgets::Notifications, feature_category: :team_planning do
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be_with_reload(:work_item) { create(:work_item) }
|
||||
let_it_be_with_reload(:subscription1) do
|
||||
create(:subscription, subscribable: work_item, user: create(:user), subscribed: true)
|
||||
end
|
||||
|
||||
let_it_be_with_reload(:subscription2) do
|
||||
create(:subscription, subscribable: work_item, user: create(:user), subscribed: false)
|
||||
end
|
||||
|
||||
let_it_be_with_reload(:sent_notification1) do
|
||||
create(:sent_notification, project: work_item.project, noteable: work_item, recipient: create(:user))
|
||||
end
|
||||
|
||||
let_it_be_with_reload(:sent_notification2) do
|
||||
create(:sent_notification, project: work_item.project, noteable: work_item, recipient: create(:user))
|
||||
end
|
||||
|
||||
let_it_be(:target_work_item) { create(:work_item) }
|
||||
let(:params) { { operation: :move } }
|
||||
|
||||
subject(:callback) do
|
||||
described_class.new(
|
||||
work_item: work_item, target_work_item: target_work_item, current_user: current_user, params: params
|
||||
)
|
||||
end
|
||||
|
||||
describe '#after_save_commit' do
|
||||
context 'when target work item has notifications widget' do
|
||||
before do
|
||||
allow(target_work_item).to receive(:get_widget).with(:notifications).and_return(true)
|
||||
allow(work_item).to receive(:from_service_desk?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when moving work item' do
|
||||
it 'copies notifications from work_item to target_work_item' do
|
||||
expect(callback).to receive(:new_work_item_subscriptions).and_call_original
|
||||
expect(callback).to receive(:new_work_item_sent_notifications).and_call_original
|
||||
expect(::Subscription).to receive(:insert_all).and_call_original
|
||||
expect(::SentNotification).to receive(:upsert_all).and_call_original
|
||||
|
||||
expected_subscriptions = work_item.subscriptions.pluck(:user_id)
|
||||
expected_sent_notifications = work_item.sent_notifications.pluck(:recipient_id)
|
||||
|
||||
callback.after_save_commit
|
||||
|
||||
subscriptions = target_work_item.reload.subscriptions.pluck(:user_id)
|
||||
sent_notifications = target_work_item.reload.sent_notifications.pluck(:recipient_id)
|
||||
expect(subscriptions).to match_array(expected_subscriptions)
|
||||
expect(sent_notifications).to match_array(expected_sent_notifications)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cloning work item' do
|
||||
let(:params) { { operation: :clone } }
|
||||
|
||||
it 'does not copy subscriptions or notifications' do
|
||||
expect(callback).not_to receive(:new_work_item_subscriptions)
|
||||
expect(callback).not_to receive(:new_work_item_sent_notifications)
|
||||
expect(::Subscription).not_to receive(:insert_all)
|
||||
expect(::SentNotification).not_to receive(:upsert_all)
|
||||
|
||||
callback.after_save_commit
|
||||
|
||||
expect(target_work_item.reload.subscriptions).to be_empty
|
||||
expect(target_work_item.reload.sent_notifications).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when work item is not a service desk work item' do
|
||||
before do
|
||||
allow(work_item).to receive(:from_service_desk?).and_return(false)
|
||||
end
|
||||
|
||||
it 'copies subscriptions but does not copy notifications' do
|
||||
expect(callback).to receive(:new_work_item_subscriptions).and_call_original
|
||||
expect(callback).not_to receive(:new_work_item_sent_notifications)
|
||||
expect(::Subscription).to receive(:insert_all).and_call_original
|
||||
expect(::SentNotification).not_to receive(:upsert_all)
|
||||
|
||||
expected_subscriptions = work_item.subscriptions.pluck(:user_id)
|
||||
|
||||
callback.after_save_commit
|
||||
|
||||
subscriptions = target_work_item.reload.subscriptions.pluck(:user_id)
|
||||
expect(subscriptions).to match_array(expected_subscriptions)
|
||||
expect(target_work_item.reload.sent_notifications).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when target work item does not have notifications widget' do
|
||||
before do
|
||||
target_work_item.reload
|
||||
allow(target_work_item).to receive(:get_widget).with(:notifications).and_return(false)
|
||||
end
|
||||
|
||||
it 'does not copy subscriptions or notifications' do
|
||||
expect(callback).not_to receive(:new_work_item_subscriptions)
|
||||
expect(callback).not_to receive(:new_work_item_sent_notifications)
|
||||
expect(::Subscription).not_to receive(:insert_all)
|
||||
expect(::SentNotification).not_to receive(:upsert_all)
|
||||
|
||||
callback.after_save_commit
|
||||
|
||||
expect(target_work_item.reload.subscriptions).to be_empty
|
||||
expect(target_work_item.reload.sent_notifications).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#post_move_cleanup' do
|
||||
it 'removes original work item notifications' do
|
||||
expect(work_item.subscriptions.count).to eq(2)
|
||||
expect(work_item.sent_notifications.count).to eq(2)
|
||||
|
||||
expect { callback.post_move_cleanup }.not_to raise_error
|
||||
|
||||
expect(work_item.subscriptions).to be_empty
|
||||
expect(work_item.sent_notifications).to be_empty
|
||||
end
|
||||
|
||||
context 'when cleanup data in batches' do
|
||||
before do
|
||||
stub_const("#{described_class}::BATCH_SIZE", 2)
|
||||
end
|
||||
|
||||
it 'removes original work item notifications' do
|
||||
create(:subscription, subscribable: work_item, user: create(:user), subscribed: true)
|
||||
create(:subscription, subscribable: work_item, user: create(:user), subscribed: false)
|
||||
create(:sent_notification, project: work_item.project, noteable: work_item, recipient: create(:user))
|
||||
create(:sent_notification, project: work_item.project, noteable: work_item, recipient: create(:user))
|
||||
|
||||
expect(work_item.subscriptions.count).to eq(4)
|
||||
expect(work_item.sent_notifications.count).to eq(4)
|
||||
|
||||
callback.post_move_cleanup
|
||||
|
||||
expect(work_item.subscriptions).to be_empty
|
||||
expect(work_item.sent_notifications).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -66,15 +66,36 @@ RSpec.shared_examples 'cloneable and moveable widget data' do
|
|||
work_item.reload.milestone&.title
|
||||
end
|
||||
|
||||
def work_item_subscriptions(work_item)
|
||||
work_item.reload.subscriptions.pluck(:user_id)
|
||||
end
|
||||
|
||||
def work_item_sent_notifications(work_item)
|
||||
work_item.reload.sent_notifications.pluck(:recipient_id)
|
||||
end
|
||||
|
||||
let(:move) { WorkItems::DataSync::MoveService }
|
||||
let(:clone) { WorkItems::DataSync::CloneService }
|
||||
|
||||
let_it_be(:users) { create_list(:user, 3) }
|
||||
let_it_be(:thumbs_ups) { create_list(:award_emoji, 2, name: 'thumbsup', awardable: original_work_item) }
|
||||
let_it_be(:thumbs_downs) { create_list(:award_emoji, 2, name: 'thumbsdown', awardable: original_work_item) }
|
||||
let_it_be(:participant2) { create(:issue_email_participant, issue: original_work_item, email: 'user2@example.com') }
|
||||
let_it_be(:award_emojis) { original_work_item.reload.award_emoji.pluck(:user_id, :name) }
|
||||
|
||||
let_it_be(:subscriptions) do
|
||||
create_list(:subscription, 2, subscribable: original_work_item)
|
||||
# create subscriptions for original work item and return subscribers as `expected_data` for later comparison.
|
||||
original_work_item.reload.subscriptions.pluck(:user_id)
|
||||
end
|
||||
|
||||
let_it_be(:notifications) do
|
||||
create_list(:sent_notification, 2, noteable: original_work_item,
|
||||
project: original_work_item.project, recipient: create(:user)
|
||||
)
|
||||
# create sent notification for original work item and return recipients as `expected_data` for later comparison.
|
||||
original_work_item.reload.sent_notifications.pluck(:recipient_id)
|
||||
end
|
||||
|
||||
let_it_be(:emails) do
|
||||
create_list(:issue_email_participant, 2, issue: original_work_item)
|
||||
# create email participants on original work item and return emails as `expected_data` for later comparison.
|
||||
|
|
@ -109,14 +130,20 @@ RSpec.shared_examples 'cloneable and moveable widget data' do
|
|||
end
|
||||
|
||||
where(:widget_name, :eval_value, :expected_data, :operations) do
|
||||
:assignees | :work_item_assignees | ref(:assignees) | [ref(:move), ref(:clone)]
|
||||
:award_emoji | :work_item_award_emoji | ref(:award_emojis) | [ref(:move)]
|
||||
:email_participants | :work_item_emails | ref(:emails) | [ref(:move)]
|
||||
:milestone | :work_item_milestone | ref(:milestone) | [ref(:move), ref(:clone)]
|
||||
:assignees | :work_item_assignees | ref(:assignees) | [ref(:move), ref(:clone)]
|
||||
:award_emoji | :work_item_award_emoji | ref(:award_emojis) | [ref(:move)]
|
||||
:email_participants | :work_item_emails | ref(:emails) | [ref(:move)]
|
||||
:milestone | :work_item_milestone | ref(:milestone) | [ref(:move), ref(:clone)]
|
||||
:subscriptions | :work_item_subscriptions | ref(:subscriptions) | [ref(:move)]
|
||||
:sent_notifications | :work_item_sent_notifications | ref(:notifications) | [ref(:move)]
|
||||
end
|
||||
|
||||
with_them do
|
||||
context "with widget" do
|
||||
before do
|
||||
allow(original_work_item).to receive(:from_service_desk?).and_return(true)
|
||||
end
|
||||
|
||||
it 'clones and moves widget data' do
|
||||
new_work_item = service.execute[:work_item]
|
||||
widget_value = send(eval_value, new_work_item)
|
||||
|
|
|
|||
|
|
@ -108,45 +108,4 @@ RSpec.describe 'projects/commits/_commit.html.haml' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with history button' do
|
||||
let(:ref) { 'master' }
|
||||
|
||||
it 'does not render the history button when show_history_button is not provided' do
|
||||
render partial: template, formats: :html, locals: {
|
||||
project: project,
|
||||
ref: ref,
|
||||
commit: commit
|
||||
}
|
||||
|
||||
expect(rendered).not_to have_css('#js-commit-history-link')
|
||||
expect(rendered).not_to have_content('History')
|
||||
end
|
||||
|
||||
it 'renders the history button when show_history_button is true' do
|
||||
allow(view).to receive(:project_commits_path).and_return('/commits/123')
|
||||
|
||||
render partial: template, formats: :html, locals: {
|
||||
project: project,
|
||||
ref: ref,
|
||||
commit: commit,
|
||||
show_history_button: true
|
||||
}
|
||||
|
||||
expect(rendered).to have_css('#js-commit-history-link')
|
||||
expect(rendered).to have_content('History')
|
||||
end
|
||||
|
||||
it 'does not render the history button when show_history_button is false' do
|
||||
render partial: template, formats: :html, locals: {
|
||||
project: project,
|
||||
ref: ref,
|
||||
commit: commit,
|
||||
show_history_button: false
|
||||
}
|
||||
|
||||
expect(rendered).not_to have_css('#js-commit-history-link')
|
||||
expect(rendered).not_to have_content('History')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ RSpec.describe Search::Worker, feature_category: :global_search do
|
|||
|
||||
include ApplicationWorker
|
||||
include ::Search::Worker
|
||||
|
||||
def perform
|
||||
logger.info 'Worker start'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -26,4 +30,12 @@ RSpec.describe Search::Worker, feature_category: :global_search do
|
|||
|
||||
expect(Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap.limit_for(worker: worker_class)).to eq(limit)
|
||||
end
|
||||
|
||||
it 'uses a logger built with Gitlab::Elasticsearch::Logger' do
|
||||
logger = instance_double(Gitlab::Elasticsearch::Logger)
|
||||
expect(Gitlab::Elasticsearch::Logger).to receive(:build).and_return(logger)
|
||||
expect(logger).to receive(:info).with('Worker start')
|
||||
|
||||
worker.perform
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6740,10 +6740,10 @@ domexception@^4.0.0:
|
|||
dependencies:
|
||||
webidl-conversions "^7.0.0"
|
||||
|
||||
dompurify@^3.0.5, dompurify@^3.1.6:
|
||||
version "3.1.6"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.6.tgz#43c714a94c6a7b8801850f82e756685300a027e2"
|
||||
integrity sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==
|
||||
dompurify@^3.0.5, dompurify@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.0.tgz#53c414317c51503183696fcdef6dd3f916c607ed"
|
||||
integrity sha512-AMdOzK44oFWqHEi0wpOqix/fUNY707OmoeFDnbi3Q5I8uOpy21ufUA5cDJPr0bosxrflOVD/H2DMSvuGKJGfmQ==
|
||||
|
||||
dropzone@^4.2.0:
|
||||
version "4.2.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue