Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c1436508fa
commit
f9e0126cad
|
@ -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'
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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 />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -8,7 +8,6 @@ class Admin::RunnersController < Admin::ApplicationController
|
|||
feature_category :runner
|
||||
|
||||
def index
|
||||
@active_runners_count = Ci::Runner.online.count
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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") %>)
|
||||
|
||||
***
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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 |
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
||||
|
|
Loading…
Reference in New Issue