Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-16 21:12:05 +00:00
parent c1436508fa
commit f9e0126cad
52 changed files with 994 additions and 236 deletions

View File

@ -2606,7 +2606,6 @@ Style/OpenStructUse:
- 'spec/models/design_management/design_at_version_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/presenters/packages/nuget/search_results_presenter_spec.rb'
- 'spec/requests/api/graphql/mutations/design_management/delete_spec.rb'
- 'spec/requests/api/import_github_spec.rb'
- 'spec/services/packages/nuget/metadata_extraction_service_spec.rb'
- 'spec/services/projects/import_service_spec.rb'

View File

@ -2,6 +2,7 @@
import { mapState, mapGetters, mapActions } from 'vuex';
import { s__ } from '~/locale';
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import MultilineCommentForm from '../../notes/components/multiline_comment_form.vue';
import {
@ -177,16 +178,16 @@ export default {
'saveDiffDiscussion',
'setSuggestPopoverDismissed',
]),
handleCancelCommentForm(shouldConfirm, isDirty) {
async handleCancelCommentForm(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) {
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
// eslint-disable-next-line no-alert
if (!window.confirm(msg)) {
const confirmed = await confirmAction(msg);
if (!confirmed) {
return;
}
}
this.cancelCommentForm({
lineCode: this.line.line_code,
fileHash: this.diffFileHash,

View File

@ -1,25 +1,9 @@
import Vue from 'vue';
export function confirmViaGlModal(message, element) {
export function confirmAction(message, { primaryBtnVariant, primaryBtnText } = {}) {
return new Promise((resolve) => {
let confirmed = false;
const props = {};
const confirmBtnVariant = element.getAttribute('data-confirm-btn-variant');
if (confirmBtnVariant) {
props.primaryVariant = confirmBtnVariant;
}
const screenReaderText =
element.querySelector('.gl-sr-only')?.textContent ||
element.querySelector('.sr-only')?.textContent ||
element.getAttribute('aria-label');
if (screenReaderText) {
props.primaryText = screenReaderText;
}
const component = new Vue({
components: {
ConfirmModal: () => import('./confirm_modal.vue'),
@ -28,7 +12,10 @@ export function confirmViaGlModal(message, element) {
return h(
'confirm-modal',
{
props,
props: {
primaryVariant: primaryBtnVariant,
primaryText: primaryBtnText,
},
on: {
confirmed() {
confirmed = true;
@ -45,3 +32,24 @@ export function confirmViaGlModal(message, element) {
}).$mount();
});
}
export function confirmViaGlModal(message, element) {
const primaryBtnConfig = {};
const confirmBtnVariant = element.getAttribute('data-confirm-btn-variant');
if (confirmBtnVariant) {
primaryBtnConfig.primaryBtnVariant = confirmBtnVariant;
}
const screenReaderText =
element.querySelector('.gl-sr-only')?.textContent ||
element.querySelector('.sr-only')?.textContent ||
element.getAttribute('aria-label');
if (screenReaderText) {
primaryBtnConfig.primaryBtnText = screenReaderText;
}
return confirmAction(message, primaryBtnConfig);
}

View File

@ -36,6 +36,11 @@ export default {
required: false,
default: false,
},
scrollToCommitForm: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -52,6 +57,13 @@ export default {
return !(this.message && this.targetBranch);
},
},
watch: {
scrollToCommitForm(flag) {
if (flag) {
this.scrollIntoView();
}
},
},
methods: {
onSubmit() {
this.$emit('submit', {
@ -63,6 +75,10 @@ export default {
onReset() {
this.$emit('cancel');
},
scrollIntoView() {
this.$el.scrollIntoView({ behavior: 'smooth' });
this.$emit('scrolled-to-commit-form');
},
},
i18n: {
commitMessage: __('Commit message'),

View File

@ -45,6 +45,11 @@ export default {
required: false,
default: false,
},
scrollToCommitForm: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -146,6 +151,8 @@ export default {
:current-branch="currentBranch"
:default-message="defaultCommitMessage"
:is-saving="isSaving"
:scroll-to-commit-form="scrollToCommitForm"
v-on="$listeners"
@cancel="onCommitCancel"
@submit="onCommitSubmit"
/>

View File

@ -2,6 +2,7 @@
import { GlButton, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { experiment } from '~/experimentation/utils';
import { DRAWER_EXPANDED_KEY } from '../../constants';
import FirstPipelineCard from './cards/first_pipeline_card.vue';
import GettingStartedCard from './cards/getting_started_card.vue';
@ -53,12 +54,23 @@ export default {
},
methods: {
setInitialExpandState() {
let isExpanded;
experiment('pipeline_editor_walkthrough', {
control: () => {
isExpanded = true;
},
candidate: () => {
isExpanded = false;
},
});
// We check in the local storage and if no value is defined, we want the default
// to be true. We want to explicitly set it to true here so that the drawer
// animates to open on load.
const localValue = localStorage.getItem(this.$options.localDrawerKey);
if (localValue === null) {
this.isExpanded = true;
this.isExpanded = isExpanded;
}
},
setTopPosition() {

View File

@ -112,7 +112,7 @@ export default {
isBranchesLoading() {
return this.$apollo.queries.availableBranches.loading || this.isSearchingBranches;
},
showBranchSwitcher() {
enableBranchSwitcher() {
return this.branches.length > 0 || this.searchTerm.length > 0;
},
},
@ -230,11 +230,11 @@ export default {
<template>
<gl-dropdown
v-if="showBranchSwitcher"
v-gl-tooltip.hover
:title="$options.i18n.dropdownHeader"
:header-text="$options.i18n.dropdownHeader"
:text="currentBranch"
:disabled="!enableBranchSwitcher"
icon="branch"
data-qa-selector="branch_selector_button"
data-testid="branch-selector"

View File

@ -4,6 +4,7 @@ import { s__ } from '~/locale';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { getParameterValues, setUrlParams, updateHistory } from '~/lib/utils/url_utility';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import {
CREATE_TAB,
EDITOR_APP_STATUS_EMPTY,
@ -22,6 +23,7 @@ import CiEditorHeader from './editor/ci_editor_header.vue';
import TextEditor from './editor/text_editor.vue';
import CiLint from './lint/ci_lint.vue';
import EditorTab from './ui/editor_tab.vue';
import WalkthroughPopover from './walkthrough_popover.vue';
export default {
i18n: {
@ -63,6 +65,8 @@ export default {
GlTabs,
PipelineGraph,
TextEditor,
GitlabExperiment,
WalkthroughPopover,
},
mixins: [glFeatureFlagsMixin()],
props: {
@ -79,6 +83,10 @@ export default {
required: false,
default: '',
},
isNewCiConfigFile: {
type: Boolean,
required: true,
},
},
apollo: {
appStatus: {
@ -136,11 +144,17 @@ export default {
>
<editor-tab
class="gl-mb-3"
title-link-class="js-walkthrough-popover-target"
:title="$options.i18n.tabEdit"
lazy
data-testid="editor-tab"
@click="setCurrentTab($options.tabConstants.CREATE_TAB)"
>
<gitlab-experiment name="pipeline_editor_walkthrough">
<template #candidate>
<walkthrough-popover v-if="isNewCiConfigFile" v-on="$listeners" />
</template>
</gitlab-experiment>
<ci-editor-header />
<text-editor :commit-sha="commitSha" :value="ciFileContent" v-on="$listeners" />
</editor-tab>

View File

@ -0,0 +1,83 @@
<script>
import { GlButton, GlPopover, GlSprintf, GlOutsideDirective as Outside } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
directives: { Outside },
i18n: {
title: s__('pipelineEditorWalkthrough|See how GitLab pipelines work'),
description: s__(
'pipelineEditorWalkthrough|This %{codeStart}.gitlab-ci.yml%{codeEnd} file creates a simple test pipeline.',
),
instruction: s__(
'pipelineEditorWalkthrough|Use the %{boldStart}commit changes%{boldEnd} button at the bottom of the page to run the pipeline.',
),
ctaText: s__("pipelineEditorWalkthrough|Let's do this!"),
},
components: {
GlButton,
GlPopover,
GlSprintf,
},
data() {
return {
show: true,
};
},
computed: {
targetElement() {
return document.querySelector('.js-walkthrough-popover-target');
},
},
methods: {
close() {
this.show = false;
},
handleClickCta() {
this.close();
this.$emit('walkthrough-popover-cta-clicked');
},
},
};
</script>
<template>
<gl-popover
:show.sync="show"
:title="$options.i18n.title"
:target="targetElement"
placement="right"
triggers="focus"
>
<div v-outside="close" class="gl-display-flex gl-flex-direction-column">
<p>
<gl-sprintf :message="$options.i18n.description">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</p>
<p>
<gl-sprintf :message="$options.i18n.instruction">
<template #bold="{ content }">
<strong>
{{ content }}
</strong>
</template>
</gl-sprintf>
</p>
<gl-button
class="gl-align-self-end"
category="tertiary"
data-testid="ctaBtn"
variant="confirm"
@click="handleClickCta"
>
<gl-emoji data-name="rocket" />
{{ this.$options.i18n.ctaText }}
</gl-button>
</div>
</gl-popover>
</template>

View File

@ -58,6 +58,7 @@ export default {
data() {
return {
currentTab: CREATE_TAB,
scrollToCommitForm: false,
shouldLoadNewBranch: false,
showSwitchBranchModal: false,
};
@ -81,6 +82,9 @@ export default {
setCurrentTab(tabName) {
this.currentTab = tabName;
},
setScrollToCommitForm(newValue = true) {
this.scrollToCommitForm = newValue;
},
},
};
</script>
@ -117,8 +121,10 @@ export default {
:ci-config-data="ciConfigData"
:ci-file-content="ciFileContent"
:commit-sha="commitSha"
:is-new-ci-config-file="isNewCiConfigFile"
v-on="$listeners"
@set-current-tab="setCurrentTab"
@walkthrough-popover-cta-clicked="setScrollToCommitForm"
/>
<commit-section
v-if="showCommitForm"
@ -126,6 +132,8 @@ export default {
:ci-file-content="ciFileContent"
:commit-sha="commitSha"
:is-new-ci-config-file="isNewCiConfigFile"
:scroll-to-commit-form="scrollToCommitForm"
@scrolled-to-commit-form="setScrollToCommitForm(false)"
v-on="$listeners"
/>
<pipeline-editor-drawer />

View File

@ -1,9 +1,9 @@
<script>
import { GlLink } from '@gitlab/ui';
import { GlBadge, GlLink } from '@gitlab/ui';
import createFlash from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { updateHistory } from '~/lib/utils/url_utility';
import { formatNumber, sprintf, __ } from '~/locale';
import { sprintf, __ } from '~/locale';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
@ -14,7 +14,13 @@ import RunnerTypeTabs from '../components/runner_type_tabs.vue';
import { statusTokenConfig } from '../components/search_tokens/status_token_config';
import { tagTokenConfig } from '../components/search_tokens/tag_token_config';
import { ADMIN_FILTERED_SEARCH_NAMESPACE, INSTANCE_TYPE, I18N_FETCH_ERROR } from '../constants';
import {
ADMIN_FILTERED_SEARCH_NAMESPACE,
INSTANCE_TYPE,
GROUP_TYPE,
PROJECT_TYPE,
I18N_FETCH_ERROR,
} from '../constants';
import getRunnersQuery from '../graphql/get_runners.query.graphql';
import {
fromUrlQueryToSearch,
@ -26,6 +32,7 @@ import { captureException } from '../sentry_utils';
export default {
name: 'AdminRunnersApp',
components: {
GlBadge,
GlLink,
RegistrationDropdown,
RunnerFilteredSearchBar,
@ -35,11 +42,27 @@ export default {
RunnerTypeTabs,
},
props: {
activeRunnersCount: {
type: Number,
registrationToken: {
type: String,
required: true,
},
registrationToken: {
activeRunnersCount: {
type: String,
required: true,
},
allRunnersCount: {
type: String,
required: true,
},
instanceRunnersCount: {
type: String,
required: true,
},
groupRunnersCount: {
type: String,
required: true,
},
projectRunnersCount: {
type: String,
required: true,
},
@ -89,7 +112,7 @@ export default {
},
activeRunnersMessage() {
return sprintf(__('Runners currently online: %{active_runners_count}'), {
active_runners_count: formatNumber(this.activeRunnersCount),
active_runners_count: this.activeRunnersCount,
});
},
searchTokens() {
@ -118,6 +141,20 @@ export default {
this.reportToSentry(error);
},
methods: {
tabCount({ runnerType }) {
switch (runnerType) {
case null:
return this.allRunnersCount;
case INSTANCE_TYPE:
return this.instanceRunnersCount;
case GROUP_TYPE:
return this.groupRunnersCount;
case PROJECT_TYPE:
return this.projectRunnersCount;
default:
return null;
}
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
@ -128,15 +165,25 @@ export default {
</script>
<template>
<div>
<div class="gl-display-flex gl-align-items-center">
<div
class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0"
>
<runner-type-tabs
v-model="search"
class="gl-w-full"
content-class="gl-display-none"
nav-class="gl-border-none!"
/>
>
<template #title="{ tab }">
{{ tab.title }}
<gl-badge v-if="tabCount(tab)" class="gl-ml-1" size="sm">
{{ tabCount(tab) }}
</gl-badge>
</template>
</runner-type-tabs>
<registration-dropdown
class="gl-ml-auto"
class="gl-w-full gl-sm-w-auto gl-mr-auto"
:registration-token="registrationToken"
:type="$options.INSTANCE_TYPE"
right

View File

@ -16,7 +16,16 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
// TODO `activeRunnersCount` should be implemented using a GraphQL API
// https://gitlab.com/gitlab-org/gitlab/-/issues/333806
const { activeRunnersCount, registrationToken, runnerInstallHelpPage } = el.dataset;
const {
runnerInstallHelpPage,
registrationToken,
activeRunnersCount,
allRunnersCount,
instanceRunnersCount,
groupRunnersCount,
projectRunnersCount,
} = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@ -31,8 +40,15 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
render(h) {
return h(AdminRunnersApp, {
props: {
activeRunnersCount: parseInt(activeRunnersCount, 10),
registrationToken,
// All runner counts are returned as formatted
// strings, we do not use `parseInt`.
activeRunnersCount,
allRunnersCount,
instanceRunnersCount,
groupRunnersCount,
projectRunnersCount,
},
});
},

View File

@ -51,13 +51,16 @@ export default {
};
</script>
<template>
<gl-tabs v-bind="$attrs">
<gl-tabs v-bind="$attrs" data-testid="runner-type-tabs">
<gl-tab
v-for="tab in $options.tabs"
:key="`${tab.runnerType}`"
:active="isTabActive(tab)"
:title="tab.title"
@click="onTabSelected(tab)"
/>
>
<template #title>
<slot name="title" :tab="tab">{{ tab.title }}</slot>
</template>
</gl-tab>
</gl-tabs>
</template>

View File

@ -8,7 +8,6 @@ class Admin::RunnersController < Admin::ApplicationController
feature_category :runner
def index
@active_runners_count = Ci::Runner.online.count
end
def show

View File

@ -116,7 +116,9 @@ class Import::BitbucketController < Import::BaseController
redirect_to oauth_client.auth_code.authorize_url(redirect_uri: users_import_bitbucket_callback_url)
end
def bitbucket_unauthorized
def bitbucket_unauthorized(exception)
log_exception(exception)
go_to_bitbucket_for_permissions
end

View File

@ -2,6 +2,7 @@
class Projects::Ci::PipelineEditorController < Projects::ApplicationController
before_action :check_can_collaborate!
before_action :setup_walkthrough_experiment, only: :show
before_action do
push_frontend_feature_flag(:schema_linting, @project, default_enabled: :yaml)
end
@ -16,4 +17,10 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController
def check_can_collaborate!
render_404 unless can_collaborate_with_project?(@project)
end
def setup_walkthrough_experiment
experiment(:pipeline_editor_walkthrough, actor: current_user) do |e|
e.candidate {}
end
end
end

View File

@ -23,13 +23,14 @@ git push -uf origin <%= @project.default_branch_or_main %>
## Integrate with your tools
- [ ] [Set up project integrations](<%= redirect("https://docs.gitlab.com/ee/user/project/integrations/") %>)
- [ ] [Set up project integrations](<%= redirect(project_settings_integrations_url(@project)) %>)
## Collaborate with your team
- [ ] [Invite team members and collaborators](<%= redirect("https://docs.gitlab.com/ee/user/project/members/") %>)
- [ ] [Create a new merge request](<%= redirect("https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html") %>)
- [ ] [Automatically close issues from merge requests](<%= redirect("https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically") %>)
- [ ] [Enable merge request approvals](<%= redirect("https://docs.gitlab.com/ee/user/project/merge_requests/approvals/") %>)
- [ ] [Automatically merge when pipeline succeeds](<%= redirect("https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html") %>)
## Test and Deploy
@ -40,6 +41,7 @@ Use the built-in continuous integration in GitLab.
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](<%= redirect("https://docs.gitlab.com/ee/user/application_security/sast/") %>)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](<%= redirect("https://docs.gitlab.com/ee/topics/autodevops/requirements.html") %>)
- [ ] [Use pull-based deployments for improved Kubernetes management](<%= redirect("https://docs.gitlab.com/ee/user/clusters/agent/") %>)
- [ ] [Set up protected environments](<%= redirect("https://docs.gitlab.com/ee/ci/environments/protected_environments.html") %>)
***

View File

@ -60,6 +60,22 @@ module Ci
end
end
def admin_runners_data_attributes
{
# Runner install help page is external, located at
# https://gitlab.com/gitlab-org/gitlab-runner
runner_install_help_page: 'https://docs.gitlab.com/runner/install/',
registration_token: Gitlab::CurrentSettings.runners_registration_token,
# All runner counts are returned as formatted strings
active_runners_count: Ci::Runner.online.count.to_s,
all_runners_count: limited_counter_with_delimiter(Ci::Runner),
instance_runners_count: limited_counter_with_delimiter(Ci::Runner.instance_type),
group_runners_count: limited_counter_with_delimiter(Ci::Runner.group_type),
project_runners_count: limited_counter_with_delimiter(Ci::Runner.project_type)
}
end
def group_shared_runners_settings_data(group)
{
update_path: api_v4_groups_path(id: group.id),

View File

@ -1,4 +1,4 @@
- breadcrumb_title _('Runners')
- page_title _('Runners')
#js-admin-runners{ data: { registration_token: Gitlab::CurrentSettings.runners_registration_token, runner_install_help_page: 'https://docs.gitlab.com/runner/install/', active_runners_count: @active_runners_count } }
#js-admin-runners{ data: admin_runners_data_attributes }

View File

@ -18,6 +18,36 @@ module Gitlab
class Application < Rails::Application
config.load_defaults 6.1
# This section contains configuration from Rails upgrades to override the new defaults so that we
# keep existing behavior.
#
# For boolean values, the new default is the opposite of the value being set in this section.
# For other types, the new default is noted in the comments. These are also documented in
# https://guides.rubyonrails.org/configuring.html#results-of-config-load-defaults
#
# To switch a setting to the new default value, we just need to delete the specific line here.
# Rails 6.1
config.action_dispatch.cookies_same_site_protection = nil # New default is :lax
ActiveSupport.utc_to_local_returns_utc_offset_times = false
config.action_controller.urlsafe_csrf_tokens = false
config.action_view.preload_links_header = false
# Rails 5.2
config.action_dispatch.use_authenticated_cookie_encryption = false
config.active_support.use_authenticated_message_encryption = false
config.active_support.hash_digest_class = ::Digest::MD5 # New default is ::Digest::SHA1
config.action_controller.default_protect_from_forgery = false
config.action_view.form_with_generates_ids = false
# Rails 5.1
config.assets.unknown_asset_fallback = true
# Rails 5.0
config.action_controller.per_form_csrf_tokens = false
config.action_controller.forgery_protection_origin_check = false
ActiveSupport.to_time_preserves_timezone = false
require_dependency Rails.root.join('lib/gitlab')
require_dependency Rails.root.join('lib/gitlab/utils')
require_dependency Rails.root.join('lib/gitlab/action_cable/config')

View File

@ -0,0 +1,8 @@
---
name: pipeline_editor_walkthrough
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73050
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345558
milestone: '14.5'
type: experiment
group: group::activation
default_enabled: false

View File

@ -1,33 +0,0 @@
# frozen_string_literal: true
# This contains configuration from Rails upgrades to override the new defaults so that we
# keep existing behavior.
#
# For boolean values, the new default is the opposite of the value being set in this file.
# For other types, the new default is noted in the comments. These are also documented in
# https://guides.rubyonrails.org/configuring.html#results-of-config-load-defaults
#
# To switch a setting to the new default value, we just need to delete the specific line here.
Rails.application.configure do
# Rails 6.1
config.action_dispatch.cookies_same_site_protection = nil # New default is :lax
ActiveSupport.utc_to_local_returns_utc_offset_times = false
config.action_controller.urlsafe_csrf_tokens = false
config.action_view.preload_links_header = false
# Rails 5.2
config.action_dispatch.use_authenticated_cookie_encryption = false
config.active_support.use_authenticated_message_encryption = false
config.active_support.hash_digest_class = ::Digest::MD5 # New default is ::Digest::SHA1
config.action_controller.default_protect_from_forgery = false
config.action_view.form_with_generates_ids = false
# Rails 5.1
config.assets.unknown_asset_fallback = true
# Rails 5.0
config.action_controller.per_form_csrf_tokens = false
config.action_controller.forgery_protection_origin_check = false
ActiveSupport.to_time_preserves_timezone = false
end

View File

@ -48,10 +48,18 @@ Please consider creating a merge request to
for them.
MARKDOWN
def group_not_available_template(slack_channel, gitlab_group)
<<~TEMPLATE
No engineer is available for automated assignment, please reach out to `#{slack_channel}` slack channel or mention `#{gitlab_group}` for assistance.
TEMPLATE
end
OPTIONAL_REVIEW_TEMPLATE = '%{role} review is optional for %{category}'
NOT_AVAILABLE_TEMPLATES = {
default: 'No %{role} available',
product_intelligence: "No engineer is available for automated assignment, please reach out to `#g_product_intelligence` slack channel or mention `@gitlab-org/growth/product-intelligence/engineers` for assistance."
product_intelligence: group_not_available_template('#g_product_intelligence', '@gitlab-org/growth/product-intelligence/engineers'),
integrations_be: group_not_available_template('#g_ecosystem_integrations', '@gitlab-org/ecosystem-stage/integrations'),
integrations_fe: group_not_available_template('#g_ecosystem_integrations', '@gitlab-org/ecosystem-stage/integrations')
}.freeze
def note_for_spins_role(spins, role, category)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

@ -115,32 +115,28 @@ your chosen [provider](#supported-providers).
## Enable OmniAuth for an existing user
Existing users can enable OmniAuth for specific providers after the account is
created. For example, if the user originally signed in with LDAP, an OmniAuth
provider such as Twitter can be enabled. Follow the steps below to enable an
OmniAuth provider for an existing user.
If you're an existing user, after your GitLab account is
created, you can activate an OmniAuth provider. For example, if you originally signed in with LDAP, you can enable an OmniAuth
provider like Twitter.
1. Sign in normally - whether standard sign in, LDAP, or another OmniAuth provider.
1. In the top-right corner, select your avatar.
1. Sign in to GitLab with your GitLab credentials, LDAP, or another OmniAuth provider.
1. On the top bar, in the top right corner, select your avatar.
1. Select **Edit profile**.
1. On the left sidebar, select **Account**.
1. In the **Connected Accounts** section, select the desired OmniAuth provider, such as Twitter.
1. The user is redirected to the provider. After the user authorizes GitLab,
they are redirected back to GitLab.
1. In the **Connected Accounts** section, select the OmniAuth provider, such as Twitter.
1. You are redirected to the provider. After you authorize GitLab,
you are redirected back to GitLab.
The chosen OmniAuth provider is now active and can be used to sign in to GitLab from then on.
You can now use your chosen OmniAuth provider to sign in to GitLab.
## Link existing users to OmniAuth users
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36664) in GitLab 13.4.
You can automatically link OmniAuth users with existing GitLab users if their email addresses match.
Automatic linking using this method works for all providers
[except the SAML provider](https://gitlab.com/gitlab-org/gitlab/-/issues/338293). For automatic
linking using the SAML provider, see [SAML-specific](saml.md#general-setup) instructions.
As an example, the following configuration is used to enable the auto link
feature for both an **OpenID Connect provider** and a **Twitter OAuth provider**.
The following example enables automatic linking
for the OpenID Connect provider and the Twitter OAuth provider.
- **For Omnibus installations**
@ -155,18 +151,23 @@ feature for both an **OpenID Connect provider** and a **Twitter OAuth provider**
auto_link_user: ["openid_connect", "twitter"]
```
## Configure OmniAuth providers as external
This method of enabling automatic linking works for all providers
[except SAML](https://gitlab.com/gitlab-org/gitlab/-/issues/338293).
To enable automatic linking for SAML, see the [SAML setup instructions](saml.md#general-setup).
You can define which OmniAuth providers you want to be `external`. Users
creating accounts, or logging in by using these `external` providers cannot have
access to internal projects. You must use the full name of the provider,
like `google_oauth2` for Google. Refer to the examples for the full names of the
supported providers.
## Create an external providers list
You can define a list of external OmniAuth providers.
Users who create accounts or sign in to GitLab through the listed providers do not get access to [internal projects](../public_access/public_access.md#internal-projects-and-groups).
To define the external providers list, use the full name of the provider,
for example, `google_oauth2` for Google. For provider names, see the
**OmniAuth provider name** column in the [supported providers table](#supported-providers).
NOTE:
If you decide to remove an OmniAuth provider from the external providers list,
you must manually update the users that use this method to sign in if you want
their accounts to be upgraded to full internal accounts.
If you remove an OmniAuth provider from the external providers list,
you must manually update the users that use this sign-in method so their
accounts are upgraded to full internal accounts.
- **For Omnibus installations**
@ -184,70 +185,67 @@ their accounts to be upgraded to full internal accounts.
## Use a custom OmniAuth provider
NOTE:
The following information only applies for installations from source.
The following information only applies to installations from source.
GitLab uses [OmniAuth](https://github.com/omniauth/omniauth) for authentication and already ships
with a few providers pre-installed, such as LDAP, GitHub, and Twitter. You may also
have to integrate with other authentication solutions. For
these cases, you can use the OmniAuth provider.
If you have to integrate with an authentication solution other than the [OmniAuth](https://github.com/omniauth/omniauth) providers included with GitLab,
you can use a custom OmniAuth provider.
These steps are fairly general and you must figure out the exact details
from the OmniAuth provider's documentation.
These steps are general. Read the OmniAuth provider's documentation for the exact
implementation details.
- Stop GitLab:
1. Stop GitLab:
```shell
sudo service gitlab stop
```
```shell
sudo service gitlab stop
```
- Add the gem to your [`Gemfile`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/Gemfile):
1. Add the gem to your [`Gemfile`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/Gemfile):
```shell
gem "omniauth-your-auth-provider"
```
```shell
gem "omniauth-your-auth-provider"
```
- Install the new OmniAuth provider gem by running the following command:
1. Install the new OmniAuth provider gem:
```shell
sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment
```
```shell
sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment
```
> These are the same commands you used during initial installation in the [Install Gems section](../install/installation.md#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`.
These commands are the same as the commands for [installing gems](../install/installation.md#install-gems)
during initial installation, with `--path vendor/bundle --no-deployment` instead of `--deployment`.
- Start GitLab:
1. Start GitLab:
```shell
sudo service gitlab start
```
```shell
sudo service gitlab start
```
### Custom OmniAuth provider examples
If you have successfully set up a provider that is not shipped with GitLab itself,
please let us know.
If you have successfully set up a provider that is not already integrated with GitLab,
let us know.
While we can't officially support every possible authentication mechanism out there,
we'd like to at least help those with specific needs.
We can't officially support every possible authentication mechanism available,
but we'd like to at least help those with specific needs.
## Enable or disable sign-in with an OmniAuth provider without disabling import sources
Administrators are able to enable or disable **Sign In** by using some OmniAuth providers.
Administrators can enable or disable sign-in for some OmniAuth providers.
NOTE:
By default, **Sign In** is enabled by using all the OAuth Providers that have been configured in `config/gitlab.yml`.
By default, sign-in is enabled for all the OAuth providers configured in `config/gitlab.yml`.
To enable/disable an OmniAuth provider:
To enable or disable an OmniAuth provider:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, go to **Settings**.
1. Scroll to the **Sign-in Restrictions** section, and click **Expand**.
1. Below **Enabled OAuth Sign-In sources**, select the checkbox for each provider you want to enable or disable.
![Enabled OAuth Sign-In sources](img/enabled-oauth-sign-in-sources_v13_10.png)
1. On the left sidebar, select **Settings**.
1. Expand **Sign-in restrictions**.
1. In the **Enabled OAuth authentication sources** section, select or clear the checkbox for each provider you want to enable or disable.
## Disable OmniAuth
Starting from version 11.4 of GitLab, OmniAuth is enabled by default. This only
has an effect if providers are configured and [enabled](#enable-or-disable-sign-in-with-an-omniauth-provider-without-disabling-import-sources).
In GitLab 11.4 and later, OmniAuth is enabled by default. However, OmniAuth only works
if providers are configured and [enabled](#enable-or-disable-sign-in-with-an-omniauth-provider-without-disabling-import-sources).
If OmniAuth providers are causing problems even when individually disabled, you
can disable the entire OmniAuth subsystem by modifying the configuration file:
@ -267,7 +265,8 @@ can disable the entire OmniAuth subsystem by modifying the configuration file:
## Keep OmniAuth user profiles up to date
You can enable profile syncing from selected OmniAuth providers and for all or for specific user information.
You can enable profile syncing from selected OmniAuth providers. You can sync
all or specific user information.
When authenticating using LDAP, the user's name and email are always synced.
@ -288,13 +287,20 @@ When authenticating using LDAP, the user's name and email are always synced.
## Bypass two-factor authentication
In GitLab 12.3 or later, users can sign in with specified providers _without_
using two factor authentication.
> Introduced in GitLab 12.3.
Define the allowed providers using an array (for example, `["twitter", 'google_oauth2']`),
or as `true` or `false` to allow all providers (or none). This option should be
configured only for providers which already have two factor authentication
(default: false). This configuration doesn't apply to SAML.
With certain OmniAuth providers, users can sign in without
using two-factor authentication.
To bypass two-factor authentication, you can either:
- Define the allowed providers using an array (for example, `['twitter', 'google_oauth2']`).
- Specify `true` to allow all providers, or `false` to allow none.
This option should be configured only for providers that already have
two-factor authentication. The default is `false`.
This configuration doesn't apply to SAML.
- **For Omnibus package**
@ -313,10 +319,10 @@ configured only for providers which already have two factor authentication
You can add the `auto_sign_in_with_provider` setting to your GitLab
configuration to redirect login requests to your OmniAuth provider for
authentication. This removes the need to click a button before actually signing in.
authentication. This removes the need to select the provider before signing in.
For example, when using the [Azure v2 integration](azure.md#microsoft-azure-oauth-20-omniauth-provider-v2), set the following to enable auto
sign-in:
For example, to enable automatic sign-in for the
[Azure v2 integration](azure.md#microsoft-azure-oauth-20-omniauth-provider-v2):
- **For Omnibus package**
@ -332,10 +338,10 @@ sign-in:
```
Keep in mind that every sign-in attempt is redirected to the OmniAuth
provider; you can't sign in using local credentials. Ensure at least
one of the OmniAuth users has an administrator role.
provider, so you can't sign in using local credentials. Ensure at least
one of the OmniAuth users is an administrator.
You may also bypass the auto sign in feature by browsing to
You can also bypass automatic sign-in by browsing to
`https://gitlab.example.com/users/sign_in?auto_sign_in=false`.
## Passwords for users created via OmniAuth
@ -344,11 +350,12 @@ The [Generated passwords for users created through integrated authentication](..
guide provides an overview about how GitLab generates and sets passwords for
users created with OmniAuth.
## Custom OmniAuth provider icon
## Use a custom OmniAuth provider icon
Most supported providers include a built-in icon for the rendered sign-in button.
After you ensure your image is optimized for rendering at 64 x 64 pixels,
you can override this icon in one of two ways:
To use your own icon, ensure your image is optimized for rendering at 64 x 64 pixels,
then override the icon in one of two ways:
- **Provide a custom image path**:
@ -359,11 +366,11 @@ you can override this icon in one of two ways:
to your GitLab configuration file. Read [OpenID Connect OmniAuth provider](../administration/auth/oidc.md)
for an example for the OpenID Connect provider.
- **Directly embed an image in a configuration file**: This example creates a Base64-encoded
- **Embed an image directly in a configuration file**: This example creates a Base64-encoded
version of your image you can serve through a
[Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs):
1. Encode your image file with GNU `base64` command (such as `base64 -w 0 <logo.png>`)
1. Encode your image file with a GNU `base64` command (such as `base64 -w 0 <logo.png>`)
which returns a single-line `<base64-data>` string.
1. Add the Base64-encoded data to a custom `icon` parameter in your GitLab
configuration file:

View File

@ -68,7 +68,7 @@ You can set a global prefix for all generated Personal Access Tokens.
A prefix can help you identify PATs visually, as well as with automation tools.
NOTE:
For GitLab.com and new self-managed instances, the default prefix is `glpat-`.
For GitLab.com and self-managed instances, the default prefix is `glpat-`.
### Set a prefix

View File

@ -41,6 +41,10 @@ The Agent can be configured to enable access to the CI/CD Tunnel to other projec
You can read more on how to [authorize access in the Agent configuration reference](repository.md#authorize-projects-and-groups-to-use-an-agent).
## Restrict access of authorized projects and groups **(PREMIUM)**
You can [configure various impersonations](repository.md#use-impersonation-to-restrict-project-and-group-access) to restrict the permissions of a shared CI/CD Tunnel.
## Example for a `kubectl` command using the CI/CD Tunnel
The following example shows a CI/CD job that runs a `kubectl` command using the CI/CD Tunnel.

View File

@ -198,6 +198,87 @@ To grant access to all projects within a group:
- id: path/to/group/subgroup
```
### Use impersonation to restrict project and group access **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345014) in GitLab 14.5.
By default, the [CI/CD Tunnel](ci_cd_tunnel.md) inherits all the permissions from the service account used to install the
Agent in the cluster.
To restrict access to your cluster, you can use [impersonation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation).
To specify impersonations, use the `access_as` attribute in your Agent's configuration file and use Kubernetes RBAC rules to manage impersonated account permissions.
You can impersonate:
- The Agent itself (default).
- The CI job that accesses the cluster.
- A specific user or system account defined within the cluster.
#### Impersonate the Agent
The Agent is impersonated by default. You don't need to do anything to impersonate it.
#### Impersonate the CI job that accesses the cluster
To impersonate the CI job that accesses the cluster, add the `ci_job: {}` key-value
under the `access_as` key.
When the agent makes the request to the actual Kubernetes API, it sets the
impersonation credentials in the following way:
- `UserName` is set to `gitlab:ci_job:<job id>`. Example: `gitlab:ci_job:1074499489`.
- `Groups` is set to:
- `gitlab:ci_job` to identify all requests coming from CI jobs.
- The list of IDs of groups the project is in.
- The project ID.
- The slug of the environment this job belongs to.
Example: for a CI job in `group1/group1-1/project1` where:
- Group `group1` has ID 23.
- Group `group1/group1-1` has ID 25.
- Project `group1/group1-1/project1` has ID 150.
- Job running in a prod environment.
Group list would be `[gitlab:ci_job, gitlab:group:23, gitlab:group:25, gitlab:project:150, gitlab:project_env:150:prod]`.
- `Extra` carries extra information about the request. The following properties are set on the impersonated identity:
| Property | Description |
| -------- | ----------- |
| `agent.gitlab.com/id` | Contains the agent ID. |
| `agent.gitlab.com/config_project_id` | Contains the agent's configuration project ID. |
| `agent.gitlab.com/project_id` | Contains the CI project ID. |
| `agent.gitlab.com/ci_pipeline_id` | Contains the CI pipeline ID. |
| `agent.gitlab.com/ci_job_id` | Contains the CI job ID. |
| `agent.gitlab.com/username` | Contains the username of the user the CI job is running as. |
| `agent.gitlab.com/environment_slug` | Contains the slug of the environment. Only set if running in an environment. |
Example to restrict access by the CI job's identity:
```yaml
ci_access:
projects:
- id: path/to/project
access_as:
ci_job: {}
```
#### Impersonate a static identity
For the given CI/CD Tunnel connection, you can use a static identity for the impersonation.
Add the `impersonate` key under the `access_as` key to make the request using the provided identity.
The identity can be specified with the following keys:
- `username` (required)
- `uid`
- `groups`
- `extra`
See the [official Kubernetes documentation for more details](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation) on the usage of these keys.
## Surface network security alerts from cluster to GitLab **(ULTIMATE)**
The GitLab Agent provides an [integration with Cilium](index.md#kubernetes-network-security-alerts).

View File

@ -6,6 +6,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Container Host Security **(FREE)**
NOTE:
In GitLab 14.5, using a certificate to connect GitLab to a Kubernetes cluster is [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8).
You can continue using Container Host Security, even though it relies on this certificate-based
method. The work to allow all aspects of Container Host Security to function through the [GitLab Kubernetes Agent](../../../../clusters/agent/index.md)
instead of the certificate-based method can be tracked [in this GitLab issue](https://gitlab.com/gitlab-org/gitlab/-/issues/299350).
Container Host Security in GitLab provides Intrusion Detection and Prevention capabilities that can
monitor and (optionally) block activity inside the containers themselves. This is done by leveraging
an integration with Falco to provide the monitoring capabilities and an integration with Pod

View File

@ -6,6 +6,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Container Network Security **(FREE)**
NOTE:
In GitLab 14.5, using a certificate to connect GitLab to a Kubernetes cluster is [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8).
You can continue using Container Network Security, even though it relies on this certificate-based
method. The work to allow all aspects of Container Network Security to function through the [GitLab Kubernetes Agent](../../../../clusters/agent/index.md)
instead of the certificate-based method can be tracked [in this GitLab issue](https://gitlab.com/gitlab-org/gitlab/-/issues/299350) and [this GitLab Epic](https://gitlab.com/groups/gitlab-org/-/epics/7057).
Container Network Security in GitLab provides basic firewall functionality by leveraging Cilium
NetworkPolicies to filter traffic going in and out of the cluster as well as traffic between pods
inside the cluster. Container Network Security can be used to enforce L3, L4, and L7 policies and

View File

@ -34,7 +34,7 @@ For examples of how you can use a project access token to authenticate with the
[relevant section from our API Docs](../../../api/index.md#personalproject-access-tokens).
NOTE:
For GitLab.com and new self-managed instances, the default prefix is `glpat-`.
For GitLab.com and self-managed instances, the default prefix is `glpat-`.
## Creating a project access token

View File

@ -41698,6 +41698,18 @@ msgstr ""
msgid "pipeline schedules documentation"
msgstr ""
msgid "pipelineEditorWalkthrough|Let's do this!"
msgstr ""
msgid "pipelineEditorWalkthrough|See how GitLab pipelines work"
msgstr ""
msgid "pipelineEditorWalkthrough|This %{codeStart}.gitlab-ci.yml%{codeEnd} file creates a simple test pipeline."
msgstr ""
msgid "pipelineEditorWalkthrough|Use the %{boldStart}commit changes%{boldEnd} button at the bottom of the page to run the pipeline."
msgstr ""
msgid "pod_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
msgstr ""

View File

@ -12,9 +12,11 @@ RSpec.describe Admin::RunnersController do
describe '#index' do
render_views
it 'lists all runners' do
before do
get :index
end
it 'renders index template' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:index)
end

View File

@ -252,6 +252,30 @@ RSpec.describe Import::BitbucketController do
end
end
end
context "when exceptions occur" do
shared_examples "handles exceptions" do
it "logs an exception" do
expect(Bitbucket::Client).to receive(:new).and_raise(error)
expect(controller).to receive(:log_exception)
post :create, format: :json
end
end
context "for OAuth2 errors" do
let(:fake_response) { double('Faraday::Response', headers: {}, body: '', status: 403) }
let(:error) { OAuth2::Error.new(OAuth2::Response.new(fake_response)) }
it_behaves_like "handles exceptions"
end
context "for Bitbucket errors" do
let(:error) { Bitbucket::Error::Unauthorized.new("error") }
it_behaves_like "handles exceptions"
end
end
end
context 'user has chosen an existing nested namespace and name for the project' do

View File

@ -6,6 +6,8 @@ RSpec.describe Projects::Ci::PipelineEditorController do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
subject(:show_request) { get :show, params: { namespace_id: project.namespace, project_id: project } }
before do
sign_in(user)
end
@ -14,8 +16,7 @@ RSpec.describe Projects::Ci::PipelineEditorController do
context 'with enough privileges' do
before do
project.add_developer(user)
get :show, params: { namespace_id: project.namespace, project_id: project }
show_request
end
it { expect(response).to have_gitlab_http_status(:ok) }
@ -28,13 +29,27 @@ RSpec.describe Projects::Ci::PipelineEditorController do
context 'without enough privileges' do
before do
project.add_reporter(user)
get :show, params: { namespace_id: project.namespace, project_id: project }
show_request
end
it 'responds with 404' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
describe 'pipeline_editor_walkthrough experiment' do
before do
project.add_developer(user)
end
it 'tracks the assignment', :experiment do
expect(experiment(:pipeline_editor_walkthrough))
.to track(:assignment)
.with_context(actor: user)
.on_next_instance
show_request
end
end
end
end

View File

@ -66,11 +66,11 @@ RSpec.describe "Admin Runners" do
visit admin_runners_path
end
it 'runner type can be selected' do
expect(page).to have_link('All')
expect(page).to have_link('Instance')
expect(page).to have_link('Group')
expect(page).to have_link('Project')
it 'runner types tabs have total counts and can be selected' do
expect(page).to have_link('All 2')
expect(page).to have_link('Instance 2')
expect(page).to have_link('Group 0')
expect(page).to have_link('Project 0')
end
it 'shows runners' do
@ -162,10 +162,12 @@ RSpec.describe "Admin Runners" do
create(:ci_runner, :group, description: 'runner-group', groups: [group])
end
it 'shows correct runner when type matches' do
it '"All" tab is selected by default' do
visit admin_runners_path
expect(page).to have_link('All', class: 'active')
page.within('[data-testid="runner-type-tabs"]') do
expect(page).to have_link('All', class: 'active')
end
end
it 'shows correct runner when type matches' do
@ -174,9 +176,11 @@ RSpec.describe "Admin Runners" do
expect(page).to have_content 'runner-project'
expect(page).to have_content 'runner-group'
click_on 'Project'
page.within('[data-testid="runner-type-tabs"]') do
click_on('Project')
expect(page).to have_link('Project', class: 'active')
expect(page).to have_link('Project', class: 'active')
end
expect(page).to have_content 'runner-project'
expect(page).not_to have_content 'runner-group'
@ -185,9 +189,11 @@ RSpec.describe "Admin Runners" do
it 'shows no runner when type does not match' do
visit admin_runners_path
click_on 'Instance'
page.within('[data-testid="runner-type-tabs"]') do
click_on 'Instance'
expect(page).to have_link('Instance', class: 'active')
expect(page).to have_link('Instance', class: 'active')
end
expect(page).not_to have_content 'runner-project'
expect(page).not_to have_content 'runner-group'
@ -200,7 +206,9 @@ RSpec.describe "Admin Runners" do
visit admin_runners_path
click_on 'Project'
page.within('[data-testid="runner-type-tabs"]') do
click_on 'Project'
end
expect(page).to have_content 'runner-project'
expect(page).to have_content 'runner-2-project'
@ -224,7 +232,9 @@ RSpec.describe "Admin Runners" do
expect(page).to have_content 'runner-group'
expect(page).not_to have_content 'runner-paused-project'
click_on 'Project'
page.within('[data-testid="runner-type-tabs"]') do
click_on 'Project'
end
expect(page).to have_content 'runner-project'
expect(page).not_to have_content 'runner-group'

View File

@ -238,8 +238,10 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
def should_allow_dismissing_a_comment(line_holder, diff_side = nil)
write_comment_on_line(line_holder, diff_side)
accept_confirm do
find('.js-close-discussion-note-form').click
find('.js-close-discussion-note-form').click
page.within('.modal') do
click_button 'OK'
end
assert_comment_dismissal(line_holder)

View File

@ -1,10 +1,18 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue';
import { createStore } from '~/mr_notes/stores';
import NoteForm from '~/notes/components/note_form.vue';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import { noteableDataMock } from '../../notes/mock_data';
import diffFileMockData from '../mock_data/diff_file';
jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal', () => {
return {
confirmAction: jest.fn(),
};
});
describe('DiffLineNoteForm', () => {
let wrapper;
let diffFile;
@ -36,49 +44,56 @@ describe('DiffLineNoteForm', () => {
});
};
const findNoteForm = () => wrapper.findComponent(NoteForm);
describe('methods', () => {
beforeEach(() => {
wrapper = createComponent();
});
describe('handleCancelCommentForm', () => {
afterEach(() => {
confirmAction.mockReset();
});
it('should ask for confirmation when shouldConfirm and isDirty passed as truthy', () => {
jest.spyOn(window, 'confirm').mockReturnValue(false);
confirmAction.mockResolvedValueOnce(false);
wrapper.vm.handleCancelCommentForm(true, true);
findNoteForm().vm.$emit('cancelForm', true, true);
expect(window.confirm).toHaveBeenCalled();
expect(confirmAction).toHaveBeenCalled();
});
it('should ask for confirmation when one of the params false', () => {
jest.spyOn(window, 'confirm').mockReturnValue(false);
it('should not ask for confirmation when one of the params false', () => {
confirmAction.mockResolvedValueOnce(false);
wrapper.vm.handleCancelCommentForm(true, false);
findNoteForm().vm.$emit('cancelForm', true, false);
expect(window.confirm).not.toHaveBeenCalled();
expect(confirmAction).not.toHaveBeenCalled();
wrapper.vm.handleCancelCommentForm(false, true);
findNoteForm().vm.$emit('cancelForm', false, true);
expect(window.confirm).not.toHaveBeenCalled();
expect(confirmAction).not.toHaveBeenCalled();
});
it('should call cancelCommentForm with lineCode', (done) => {
jest.spyOn(window, 'confirm').mockImplementation(() => {});
it('should call cancelCommentForm with lineCode', async () => {
confirmAction.mockResolvedValueOnce(true);
jest.spyOn(wrapper.vm, 'cancelCommentForm').mockImplementation(() => {});
jest.spyOn(wrapper.vm, 'resetAutoSave').mockImplementation(() => {});
wrapper.vm.handleCancelCommentForm();
expect(window.confirm).not.toHaveBeenCalled();
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.cancelCommentForm).toHaveBeenCalledWith({
lineCode: diffLines[1].line_code,
fileHash: wrapper.vm.diffFileHash,
});
findNoteForm().vm.$emit('cancelForm', true, true);
expect(wrapper.vm.resetAutoSave).toHaveBeenCalled();
await nextTick();
done();
expect(confirmAction).toHaveBeenCalled();
await nextTick();
expect(wrapper.vm.cancelCommentForm).toHaveBeenCalledWith({
lineCode: diffLines[1].line_code,
fileHash: wrapper.vm.diffFileHash,
});
expect(wrapper.vm.resetAutoSave).toHaveBeenCalled();
});
});

View File

@ -5,6 +5,9 @@ import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import { mockCommitMessage, mockDefaultBranch } from '../../mock_data';
const scrollIntoViewMock = jest.fn();
HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
describe('Pipeline Editor | Commit Form', () => {
let wrapper;
@ -113,4 +116,20 @@ describe('Pipeline Editor | Commit Form', () => {
expect(findSubmitBtn().attributes('disabled')).toBe('disabled');
});
});
describe('when scrollToCommitForm becomes true', () => {
beforeEach(async () => {
createComponent();
wrapper.setProps({ scrollToCommitForm: true });
await wrapper.vm.$nextTick();
});
it('scrolls into view', () => {
expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth' });
});
it('emits "scrolled-to-commit-form"', () => {
expect(wrapper.emitted()['scrolled-to-commit-form']).toBeTruthy();
});
});
});

View File

@ -277,4 +277,16 @@ describe('Pipeline Editor | Commit section', () => {
expect(wrapper.emitted('resetContent')).toHaveLength(1);
});
});
it('sets listeners on commit form', () => {
const handler = jest.fn();
createComponent({ options: { listeners: { event: handler } } });
findCommitForm().vm.$emit('event');
expect(handler).toHaveBeenCalled();
});
it('passes down scroll-to-commit-form prop to commit form', () => {
createComponent({ props: { 'scroll-to-commit-form': true } });
expect(findCommitForm().props('scrollToCommitForm')).toBe(true);
});
});

View File

@ -1,6 +1,7 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { stubExperiments } from 'helpers/experimentation_helper';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import FirstPipelineCard from '~/pipeline_editor/components/drawer/cards/first_pipeline_card.vue';
import GettingStartedCard from '~/pipeline_editor/components/drawer/cards/getting_started_card.vue';
@ -33,19 +34,41 @@ describe('Pipeline editor drawer', () => {
const clickToggleBtn = async () => findToggleBtn().vm.$emit('click');
const originalObjects = [];
beforeEach(() => {
originalObjects.push(window.gon, window.gl);
stubExperiments({ pipeline_editor_walkthrough: 'control' });
});
afterEach(() => {
wrapper.destroy();
localStorage.clear();
[window.gon, window.gl] = originalObjects;
});
it('it sets the drawer to be opened by default', async () => {
createComponent();
describe('default expanded state', () => {
describe('when experiment control', () => {
it('sets the drawer to be opened by default', async () => {
createComponent();
expect(findDrawerContent().exists()).toBe(false);
await nextTick();
expect(findDrawerContent().exists()).toBe(true);
});
});
expect(findDrawerContent().exists()).toBe(false);
describe('when experiment candidate', () => {
beforeEach(() => {
stubExperiments({ pipeline_editor_walkthrough: 'candidate' });
});
await nextTick();
expect(findDrawerContent().exists()).toBe(true);
it('sets the drawer to be closed by default', async () => {
createComponent();
expect(findDrawerContent().exists()).toBe(false);
await nextTick();
expect(findDrawerContent().exists()).toBe(false);
});
});
});
describe('when the drawer is collapsed', () => {

View File

@ -141,8 +141,8 @@ describe('Pipeline editor branch switcher', () => {
createComponentWithApollo();
});
it('does not render dropdown', () => {
expect(findDropdown().exists()).toBe(false);
it('disables the dropdown', () => {
expect(findDropdown().props('disabled')).toBe(true);
});
});
@ -189,7 +189,7 @@ describe('Pipeline editor branch switcher', () => {
});
it('does not render dropdown', () => {
expect(findDropdown().exists()).toBe(false);
expect(findDropdown().props('disabled')).toBe(true);
});
it('shows an error message', () => {

View File

@ -1,11 +1,13 @@
import { GlAlert, GlLoadingIcon, GlTabs } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vue, { nextTick } from 'vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue';
import WalkthroughPopover from '~/pipeline_editor/components/walkthrough_popover.vue';
import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue';
import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
import EditorTab from '~/pipeline_editor/components/ui/editor_tab.vue';
import { stubExperiments } from 'helpers/experimentation_helper';
import {
CREATE_TAB,
EDITOR_APP_STATUS_EMPTY,
@ -19,6 +21,8 @@ import {
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import { mockLintResponse, mockLintResponseWithoutMerged, mockCiYml } from '../mock_data';
Vue.config.ignoredElements = ['gl-emoji'];
describe('Pipeline editor tabs component', () => {
let wrapper;
const MockTextEditor = {
@ -26,6 +30,7 @@ describe('Pipeline editor tabs component', () => {
};
const createComponent = ({
listeners = {},
props = {},
provide = {},
appStatus = EDITOR_APP_STATUS_VALID,
@ -35,6 +40,7 @@ describe('Pipeline editor tabs component', () => {
propsData: {
ciConfigData: mockLintResponse,
ciFileContent: mockCiYml,
isNewCiConfigFile: true,
...props,
},
data() {
@ -47,6 +53,7 @@ describe('Pipeline editor tabs component', () => {
TextEditor: MockTextEditor,
EditorTab,
},
listeners,
});
};
@ -62,6 +69,7 @@ describe('Pipeline editor tabs component', () => {
const findPipelineGraph = () => wrapper.findComponent(PipelineGraph);
const findTextEditor = () => wrapper.findComponent(MockTextEditor);
const findMergedPreview = () => wrapper.findComponent(CiConfigMergedPreview);
const findWalkthroughPopover = () => wrapper.findComponent(WalkthroughPopover);
afterEach(() => {
wrapper.destroy();
@ -236,4 +244,63 @@ describe('Pipeline editor tabs component', () => {
expect(findGlTabs().props('syncActiveTabWithQueryParams')).toBe(true);
});
});
describe('pipeline_editor_walkthrough experiment', () => {
describe('when in control path', () => {
beforeEach(() => {
stubExperiments({ pipeline_editor_walkthrough: 'control' });
});
it('does not show walkthrough popover', async () => {
createComponent({ mountFn: mount });
await nextTick();
expect(findWalkthroughPopover().exists()).toBe(false);
});
});
describe('when in candidate path', () => {
beforeEach(() => {
stubExperiments({ pipeline_editor_walkthrough: 'candidate' });
});
describe('when isNewCiConfigFile prop is true (default)', () => {
beforeEach(async () => {
createComponent({
mountFn: mount,
});
await nextTick();
});
it('shows walkthrough popover', async () => {
expect(findWalkthroughPopover().exists()).toBe(true);
});
});
describe('when isNewCiConfigFile prop is false', () => {
it('does not show walkthrough popover', async () => {
createComponent({ props: { isNewCiConfigFile: false }, mountFn: mount });
await nextTick();
expect(findWalkthroughPopover().exists()).toBe(false);
});
});
});
});
it('sets listeners on walkthrough popover', async () => {
stubExperiments({ pipeline_editor_walkthrough: 'candidate' });
const handler = jest.fn();
createComponent({
mountFn: mount,
listeners: {
event: handler,
},
});
await nextTick();
findWalkthroughPopover().vm.$emit('event');
expect(handler).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,29 @@
import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import WalkthroughPopover from '~/pipeline_editor/components/walkthrough_popover.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
Vue.config.ignoredElements = ['gl-emoji'];
describe('WalkthroughPopover component', () => {
let wrapper;
const createComponent = (mountFn = shallowMount) => {
return extendedWrapper(mountFn(WalkthroughPopover));
};
afterEach(() => {
wrapper.destroy();
});
describe('CTA button clicked', () => {
beforeEach(async () => {
wrapper = createComponent(mount);
await wrapper.findByTestId('ctaBtn').trigger('click');
});
it('emits "walkthrough-popover-cta-clicked" event', async () => {
expect(wrapper.emitted()['walkthrough-popover-cta-clicked']).toBeTruthy();
});
});
});

View File

@ -152,4 +152,27 @@ describe('Pipeline editor home wrapper', () => {
expect(findCommitSection().exists()).toBe(true);
});
});
describe('WalkthroughPopover events', () => {
beforeEach(() => {
createComponent();
});
describe('when "walkthrough-popover-cta-clicked" is emitted from pipeline editor tabs', () => {
it('passes down `scrollToCommitForm=true` to commit section', async () => {
expect(findCommitSection().props('scrollToCommitForm')).toBe(false);
await findPipelineEditorTabs().vm.$emit('walkthrough-popover-cta-clicked');
expect(findCommitSection().props('scrollToCommitForm')).toBe(true);
});
});
describe('when "scrolled-to-commit-form" is emitted from commit section', () => {
it('passes down `scrollToCommitForm=false` to commit section', async () => {
await findPipelineEditorTabs().vm.$emit('walkthrough-popover-cta-clicked');
expect(findCommitSection().props('scrollToCommitForm')).toBe(true);
await findCommitSection().vm.$emit('scrolled-to-commit-form');
expect(findCommitSection().props('scrollToCommitForm')).toBe(false);
});
});
});
});

View File

@ -10,6 +10,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory } from '~/lib/utils/url_utility';
import AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
@ -33,7 +34,11 @@ import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered
import { runnersData, runnersDataPaginated } from '../mock_data';
const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
const mockActiveRunnersCount = 2;
const mockActiveRunnersCount = '2';
const mockAllRunnersCount = '6';
const mockInstanceRunnersCount = '3';
const mockGroupRunnersCount = '2';
const mockProjectRunnersCount = '1';
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
@ -50,6 +55,7 @@ describe('AdminRunnersApp', () => {
let mockRunnersQuery;
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
const findRunnerTypeTabs = () => wrapper.findComponent(RunnerTypeTabs);
const findRunnerList = () => wrapper.findComponent(RunnerList);
const findRunnerPagination = () => extendedWrapper(wrapper.findComponent(RunnerPagination));
const findRunnerPaginationPrev = () =>
@ -65,8 +71,12 @@ describe('AdminRunnersApp', () => {
localVue,
apolloProvider: createMockApollo(handlers),
propsData: {
activeRunnersCount: mockActiveRunnersCount,
registrationToken: mockRegistrationToken,
activeRunnersCount: mockActiveRunnersCount,
allRunnersCount: mockAllRunnersCount,
instanceRunnersCount: mockInstanceRunnersCount,
groupRunnersCount: mockGroupRunnersCount,
projectRunnersCount: mockProjectRunnersCount,
...props,
},
});
@ -85,6 +95,16 @@ describe('AdminRunnersApp', () => {
wrapper.destroy();
});
it('shows the runner tabs with a runner count', async () => {
createComponent({ mountFn: mount });
await waitForPromises();
expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
`All ${mockAllRunnersCount} Instance ${mockInstanceRunnersCount} Group ${mockGroupRunnersCount} Project ${mockProjectRunnersCount}`,
);
});
it('shows the runner setup instructions', () => {
expect(findRegistrationDropdown().props('registrationToken')).toBe(mockRegistrationToken);
expect(findRegistrationDropdown().props('type')).toBe(INSTANCE_TYPE);

View File

@ -14,11 +14,16 @@ describe('RunnerTypeTabs', () => {
.filter((tab) => tab.attributes('active') === 'true')
.at(0);
const createComponent = ({ value = mockSearch } = {}) => {
const createComponent = ({ props, ...options } = {}) => {
wrapper = shallowMount(RunnerTypeTabs, {
propsData: {
value,
value: mockSearch,
...props,
},
stubs: {
GlTab,
},
...options,
});
};
@ -31,7 +36,7 @@ describe('RunnerTypeTabs', () => {
});
it('Renders options to filter runners', () => {
expect(findTabs().wrappers.map((tab) => tab.attributes('title'))).toEqual([
expect(findTabs().wrappers.map((tab) => tab.text())).toEqual([
'All',
'Instance',
'Group',
@ -40,18 +45,20 @@ describe('RunnerTypeTabs', () => {
});
it('"All" is selected by default', () => {
expect(findActiveTab().attributes('title')).toBe('All');
expect(findActiveTab().text()).toBe('All');
});
it('Another tab can be preselected by the user', () => {
createComponent({
value: {
...mockSearch,
runnerType: INSTANCE_TYPE,
props: {
value: {
...mockSearch,
runnerType: INSTANCE_TYPE,
},
},
});
expect(findActiveTab().attributes('title')).toBe('Instance');
expect(findActiveTab().text()).toBe('Instance');
});
describe('When the user selects a tab', () => {
@ -72,7 +79,31 @@ describe('RunnerTypeTabs', () => {
const newValue = emittedValue();
await wrapper.setProps({ value: newValue });
expect(findActiveTab().attributes('title')).toBe('Group');
expect(findActiveTab().text()).toBe('Group');
});
});
describe('When using a custom slot', () => {
const mockContent = 'content';
beforeEach(() => {
createComponent({
scopedSlots: {
title: `
<span>
{{props.tab.title}} ${mockContent}
</span>`,
},
});
});
it('Renders tabs with additional information', () => {
expect(findTabs().wrappers.map((tab) => tab.text())).toEqual([
`All ${mockContent}`,
`Instance ${mockContent}`,
`Group ${mockContent}`,
`Project ${mockContent}`,
]);
});
});
});

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Ci::RunnersHelper do
let_it_be(:user, refind: true) { create(:user) }
let_it_be(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
@ -12,22 +12,22 @@ RSpec.describe Ci::RunnersHelper do
describe '#runner_status_icon', :clean_gitlab_redis_cache do
it "returns - not contacted yet" do
runner = create(:ci_runner)
expect(runner_status_icon(runner)).to include("not connected yet")
expect(helper.runner_status_icon(runner)).to include("not connected yet")
end
it "returns offline text" do
runner = create(:ci_runner, contacted_at: 1.day.ago, active: true)
expect(runner_status_icon(runner)).to include("Runner is offline")
expect(helper.runner_status_icon(runner)).to include("Runner is offline")
end
it "returns online text" do
runner = create(:ci_runner, contacted_at: 1.second.ago, active: true)
expect(runner_status_icon(runner)).to include("Runner is online")
expect(helper.runner_status_icon(runner)).to include("Runner is online")
end
it "returns paused text" do
runner = create(:ci_runner, contacted_at: 1.second.ago, active: false)
expect(runner_status_icon(runner)).to include("Runner is paused")
expect(helper.runner_status_icon(runner)).to include("Runner is paused")
end
end
@ -42,7 +42,7 @@ RSpec.describe Ci::RunnersHelper do
context 'without sorting' do
it 'returns cached value' do
expect(runner_contacted_at(runner)).to eq(contacted_at_cached)
expect(helper.runner_contacted_at(runner)).to eq(contacted_at_cached)
end
end
@ -52,7 +52,7 @@ RSpec.describe Ci::RunnersHelper do
end
it 'returns cached value' do
expect(runner_contacted_at(runner)).to eq(contacted_at_cached)
expect(helper.runner_contacted_at(runner)).to eq(contacted_at_cached)
end
end
@ -62,11 +62,33 @@ RSpec.describe Ci::RunnersHelper do
end
it 'returns stored value' do
expect(runner_contacted_at(runner)).to eq(contacted_at_stored)
expect(helper.runner_contacted_at(runner)).to eq(contacted_at_stored)
end
end
end
describe '#admin_runners_data_attributes' do
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:instance_runner) { create(:ci_runner, :instance) }
let_it_be(:project_runner) { create(:ci_runner, :project ) }
before do
allow(helper).to receive(:current_user).and_return(admin)
end
it 'returns the data in format' do
expect(helper.admin_runners_data_attributes).to eq({
runner_install_help_page: 'https://docs.gitlab.com/runner/install/',
registration_token: Gitlab::CurrentSettings.runners_registration_token,
active_runners_count: '0',
all_runners_count: '2',
instance_runners_count: '1',
group_runners_count: '0',
project_runners_count: '1'
})
end
end
describe '#group_shared_runners_settings_data' do
let_it_be(:parent) { create(:group) }
let_it_be(:group) { create(:group, parent: parent, shared_runners_enabled: false) }
@ -86,7 +108,7 @@ RSpec.describe Ci::RunnersHelper do
parent_shared_runners_availability: nil
}.merge(runner_constants)
expect(group_shared_runners_settings_data(parent)).to eq result
expect(helper.group_shared_runners_settings_data(parent)).to eq result
end
it 'returns group data for child group' do
@ -96,7 +118,7 @@ RSpec.describe Ci::RunnersHelper do
parent_shared_runners_availability: Namespace::SR_ENABLED
}.merge(runner_constants)
expect(group_shared_runners_settings_data(group)).to eq result
expect(helper.group_shared_runners_settings_data(group)).to eq result
end
end
@ -104,7 +126,7 @@ RSpec.describe Ci::RunnersHelper do
let(:group) { create(:group) }
it 'returns group data to render a runner list' do
data = group_runners_data_attributes(group)
data = helper.group_runners_data_attributes(group)
expect(data[:registration_token]).to eq(group.runners_token)
expect(data[:group_id]).to eq(group.id)

View File

@ -53,7 +53,7 @@ RSpec.describe "deleting designs" do
context 'the designs list contains filenames we cannot find' do
it_behaves_like 'a failed request' do
let(:designs) { %w/foo bar baz/.map { |fn| OpenStruct.new(filename: fn) } }
let(:designs) { %w/foo bar baz/.map { |fn| instance_double('file', filename: fn) } }
let(:the_error) { a_string_matching %r/filenames were not found/ }
end
end

View File

@ -201,8 +201,8 @@ RSpec.configure do |config|
# Do not retry controller tests because rspec-retry cannot properly
# reset the controller which may contain data from last attempt. See
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73360
config.around(:each, type: :controller) do |example|
example.run_with_retry(retry: 1)
config.prepend_before(:each, type: :controller) do |example|
example.metadata[:retry] = 1
end
config.exceptions_to_hard_fail = [DeprecationToolkitEnv::DeprecationBehaviors::SelectiveRaise::RaiseDisallowedDeprecation]

View File

@ -193,6 +193,54 @@ RSpec.describe Tooling::Danger::ProjectHelper do
'config/metrics/schema.json' | [:product_intelligence]
'doc/api/usage_data.md' | [:product_intelligence]
'spec/lib/gitlab/usage_data_spec.rb' | [:product_intelligence]
'app/models/integration.rb' | [:integrations_be, :backend]
'ee/app/models/integrations/github.rb' | [:integrations_be, :backend]
'ee/app/models/ee/integrations/jira.rb' | [:integrations_be, :backend]
'app/models/integrations/chat_message/pipeline_message.rb' | [:integrations_be, :backend]
'app/models/jira_connect_subscription.rb' | [:integrations_be, :backend]
'app/models/hooks/service_hook.rb' | [:integrations_be, :backend]
'ee/app/models/ee/hooks/system_hook.rb' | [:integrations_be, :backend]
'app/services/concerns/integrations/project_test_data.rb' | [:integrations_be, :backend]
'ee/app/services/ee/integrations/test/project_service.rb' | [:integrations_be, :backend]
'app/controllers/concerns/integrations/actions.rb' | [:integrations_be, :backend]
'ee/app/controllers/concerns/ee/integrations/params.rb' | [:integrations_be, :backend]
'ee/app/controllers/projects/integrations/jira/issues_controller.rb' | [:integrations_be, :backend]
'app/controllers/projects/hooks_controller.rb' | [:integrations_be, :backend]
'app/controllers/admin/hook_logs_controller.rb' | [:integrations_be, :backend]
'app/controllers/groups/settings/integrations_controller.rb' | [:integrations_be, :backend]
'app/controllers/jira_connect/branches_controller.rb' | [:integrations_be, :backend]
'app/controllers/oauth/jira/authorizations_controller.rb' | [:integrations_be, :backend]
'ee/app/finders/projects/integrations/jira/by_ids_finder.rb' | [:integrations_be, :database, :backend]
'app/workers/jira_connect/sync_merge_request_worker.rb' | [:integrations_be, :backend]
'app/workers/propagate_integration_inherit_worker.rb' | [:integrations_be, :backend]
'app/workers/web_hooks/log_execution_worker.rb' | [:integrations_be, :backend]
'app/workers/web_hook_worker.rb' | [:integrations_be, :backend]
'app/workers/project_service_worker.rb' | [:integrations_be, :backend]
'lib/atlassian/jira_connect/serializers/commit_entity.rb' | [:integrations_be, :backend]
'lib/api/entities/project_integration.rb' | [:integrations_be, :backend]
'lib/gitlab/hook_data/note_builder.rb' | [:integrations_be, :backend]
'lib/gitlab/data_builder/note.rb' | [:integrations_be, :backend]
'ee/lib/ee/gitlab/integrations/sti_type.rb' | [:integrations_be, :backend]
'ee/lib/ee/api/helpers/integrations_helpers.rb' | [:integrations_be, :backend]
'ee/app/serializers/integrations/jira_serializers/issue_entity.rb' | [:integrations_be, :backend]
'lib/api/github/entities.rb' | [:integrations_be, :backend]
'lib/api/v3/github.rb' | [:integrations_be, :backend]
'app/models/clusters/integrations/elastic_stack.rb' | [:backend]
'app/controllers/clusters/integrations_controller.rb' | [:backend]
'app/services/clusters/integrations/prometheus_health_check_service.rb' | [:backend]
'app/graphql/types/alert_management/integration_type.rb' | [:backend]
'app/views/jira_connect/branches/new.html.haml' | [:integrations_fe, :frontend]
'app/views/layouts/jira_connect.html.haml' | [:integrations_fe, :frontend]
'app/assets/javascripts/jira_connect/branches/pages/index.vue' | [:integrations_fe, :frontend]
'ee/app/views/projects/integrations/jira/issues/show.html.haml' | [:integrations_fe, :frontend]
'ee/app/assets/javascripts/integrations/zentao/issues_list/graphql/queries/get_zentao_issues.query.graphql' | [:integrations_fe, :frontend]
'app/assets/javascripts/pages/projects/settings/integrations/show/index.js' | [:integrations_fe, :frontend]
'ee/app/assets/javascripts/pages/groups/hooks/index.js' | [:integrations_fe, :frontend]
'app/views/clusters/clusters/_integrations_tab.html.haml' | [:frontend]
'app/assets/javascripts/alerts_settings/graphql/fragments/integration_item.fragment.graphql' | [:frontend]
'app/assets/javascripts/filtered_search/droplab/hook_input.js' | [:frontend]
end
with_them do
@ -212,6 +260,11 @@ RSpec.describe Tooling::Danger::ProjectHelper do
[:backend, :product_intelligence] | '+ count(User.active)' | ['lib/gitlab/usage_data/topology.rb']
[:backend, :product_intelligence] | '+ foo_count(User.active)' | ['lib/gitlab/usage_data.rb']
[:backend] | '+ count(User.active)' | ['user.rb']
[:integrations_be, :database, :migration] | '+ add_column :integrations, :foo, :text' | ['db/migrate/foo.rb']
[:integrations_be, :database, :migration] | '+ create_table :zentao_tracker_data do |t|' | ['ee/db/post_migrate/foo.rb']
[:integrations_be, :backend] | '+ Integrations::Foo' | ['app/foo/bar.rb']
[:integrations_be, :backend] | '+ project.execute_hooks(foo, :bar)' | ['ee/lib/ee/foo.rb']
[:integrations_be, :backend] | '+ project.execute_integrations(foo, :bar)' | ['app/foo.rb']
end
with_them do

View File

@ -44,6 +44,28 @@ module Tooling
%r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs,
%r{\Adata/whats_new/} => :docs,
%r{\A((ee|jh)/)?app/finders/(.+/)?integrations/} => [:integrations_be, :database, :backend],
[%r{\A((ee|jh)/)?db/(geo/)?(migrate|post_migrate)/}, %r{(:integrations|:\w+_tracker_data)\b}] => [:integrations_be, :database, :migration],
[%r{\A((ee|jh)/)?(app|lib)/.+\.rb}, %r{\b(Integrations::|\.execute_(integrations|hooks))\b}] => [:integrations_be, :backend],
%r{\A(
((ee|jh)/)?app/((?!.*clusters)(?!.*alert_management)(?!.*views)(?!.*assets).+/)?integration.+ |
((ee|jh)/)?app/((?!.*search).+/)?project_service.+ |
((ee|jh)/)?app/(models|helpers|workers|services|controllers)/(.+/)?(jira_connect.+|.*hook.+) |
((ee|jh)/)?app/controllers/(.+/)?oauth/jira/.+ |
((ee|jh)/)?app/services/(.+/)?jira.+ |
((ee|jh)/)?app/workers/(.+/)?(propagate_integration.+|irker_worker\.rb) |
((ee|jh)/)?lib/(.+/)?(atlassian|data_builder|hook_data)/.+ |
((ee|jh)/)?lib/(.+/)?.*integration.+ |
((ee|jh)/)?lib/(.+/)?api/v3/github\.rb |
((ee|jh)/)?lib/(.+/)?api/github/entities\.rb
)\z}x => [:integrations_be, :backend],
%r{\A(
((ee|jh)/)?app/(views|assets)/((?!.*clusters)(?!.*alerts_settings).+/)?integration.+ |
((ee|jh)/)?app/(views|assets)/(.+/)?jira_connect.+ |
((ee|jh)/)?app/(views|assets)/((?!.*filtered_search).+/)?hooks?.+
)\z}x => [:integrations_fe, :frontend],
%r{\A(
app/assets/javascripts/tracking/.*\.js |
spec/frontend/tracking/.*\.js |