Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1de9854406
commit
a57cec4bb8
|
|
@ -16,6 +16,50 @@ function decodeUrlParameter(val) {
|
|||
return decodeURIComponent(val.replace(/\+/g, '%20'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely encodes a string to be used as a path
|
||||
*
|
||||
* Note: This function DOES encode typical URL parts like ?, =, &, #, and +
|
||||
* If you need to use search parameters or URL fragments, they should be
|
||||
* added AFTER calling this function, not before.
|
||||
*
|
||||
* Usecase: An image filename is stored verbatim, and you need to load it in
|
||||
* the browser.
|
||||
*
|
||||
* Example: /some_path/file #1.jpg ==> /some_path/file%20%231.jpg
|
||||
* Example: /some-path/file! Final!.jpg ==> /some-path/file%21%20Final%21.jpg
|
||||
*
|
||||
* Essentially, if a character *could* present a problem in a URL, it's escaped
|
||||
* to the hexadecimal representation instead. This means it's a bit more
|
||||
* aggressive than encodeURIComponent: that built-in function doesn't
|
||||
* encode some characters that *could* be problematic, so this function
|
||||
* adds them (#, !, ~, *, ', (, and )).
|
||||
* Additionally, encodeURIComponent *does* encode `/`, but we want safer
|
||||
* URLs, not non-functional URLs, so this function DEcodes slashes ('%2F').
|
||||
*
|
||||
* @param {String} potentiallyUnsafePath
|
||||
* @returns {String}
|
||||
*/
|
||||
export function encodeSaferUrl(potentiallyUnsafePath) {
|
||||
const unencode = ['%2F'];
|
||||
const encode = ['#', '!', '~', '\\*', "'", '\\(', '\\)'];
|
||||
let saferPath = encodeURIComponent(potentiallyUnsafePath);
|
||||
|
||||
unencode.forEach((code) => {
|
||||
saferPath = saferPath.replace(new RegExp(code, 'g'), decodeURIComponent(code));
|
||||
});
|
||||
encode.forEach((code) => {
|
||||
const encodedValue = code
|
||||
.codePointAt(code.length - 1)
|
||||
.toString(16)
|
||||
.toUpperCase();
|
||||
|
||||
saferPath = saferPath.replace(new RegExp(code, 'g'), `%${encodedValue}`);
|
||||
});
|
||||
|
||||
return saferPath;
|
||||
}
|
||||
|
||||
export function cleanLeadingSeparator(path) {
|
||||
return path.replace(PATH_SEPARATOR_LEADING_REGEX, '');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<script>
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import { ACTION_TEXT } from '../constants';
|
||||
|
||||
export default {
|
||||
components: { GlLink },
|
||||
i18n: {
|
||||
ACTION_TEXT,
|
||||
},
|
||||
props: {
|
||||
actions: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<ul>
|
||||
<li v-for="(value, action) in actions" :key="action">
|
||||
<span v-if="value.completed">{{ $options.i18n.ACTION_TEXT[action] }}</span>
|
||||
<span v-else>
|
||||
<gl-link :href="value.url">{{ $options.i18n.ACTION_TEXT[action] }}</gl-link>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<script>
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import { ACTION_TEXT } from '../constants';
|
||||
|
||||
export default {
|
||||
components: { GlLink },
|
||||
i18n: {
|
||||
ACTION_TEXT,
|
||||
},
|
||||
props: {
|
||||
actions: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<ul>
|
||||
<li v-for="(value, action) in actions" :key="action">
|
||||
<span v-if="value.completed">{{ $options.i18n.ACTION_TEXT[action] }}</span>
|
||||
<span v-else>
|
||||
<gl-link :href="value.url">{{ $options.i18n.ACTION_TEXT[action] }}</gl-link>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { s__ } from '~/locale';
|
||||
|
||||
export const ACTION_TEXT = {
|
||||
gitWrite: s__('LearnGitLab|Create a repository'),
|
||||
userAdded: s__('LearnGitLab|Invite your colleagues'),
|
||||
pipelineCreated: s__('LearnGitLab|Set-up CI/CD'),
|
||||
trialStarted: s__('LearnGitLab|Start a free trial of GitLab Gold'),
|
||||
codeOwnersEnabled: s__('LearnGitLab|Add code owners'),
|
||||
requiredMrApprovalsEnabled: s__('LearnGitLab|Enable require merge approvals'),
|
||||
mergeRequestCreated: s__('LearnGitLab|Submit a merge request (MR)'),
|
||||
securityScanEnabled: s__('LearnGitLab|Run a Security scan using CI/CD'),
|
||||
};
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import Vue from 'vue';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import LearnGitlabA from '../components/learn_gitlab_a.vue';
|
||||
import LearnGitlabB from '../components/learn_gitlab_b.vue';
|
||||
|
||||
function initLearnGitlab() {
|
||||
const el = document.getElementById('js-learn-gitlab-app');
|
||||
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const actions = convertObjectPropsToCamelCase(JSON.parse(el.dataset.actions));
|
||||
|
||||
const { learnGitlabA } = gon.experiments;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
render(createElement) {
|
||||
return createElement(learnGitlabA ? LearnGitlabA : LearnGitlabB, { props: { actions } });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
initLearnGitlab();
|
||||
|
|
@ -11,7 +11,6 @@ export default class AccessDropdown {
|
|||
const { $dropdown, accessLevel, accessLevelsData, hasLicense = true } = options;
|
||||
this.options = options;
|
||||
this.hasLicense = hasLicense;
|
||||
this.deployKeysOnProtectedBranchesEnabled = gon.features.deployKeysOnProtectedBranches;
|
||||
this.groups = [];
|
||||
this.accessLevel = accessLevel;
|
||||
this.accessLevelsData = accessLevelsData.roles;
|
||||
|
|
@ -330,11 +329,7 @@ export default class AccessDropdown {
|
|||
);
|
||||
})
|
||||
.catch(() => {
|
||||
if (this.deployKeysOnProtectedBranchesEnabled) {
|
||||
createFlash({ message: __('Failed to load groups, users and deploy keys.') });
|
||||
} else {
|
||||
createFlash({ message: __('Failed to load groups & users.') });
|
||||
}
|
||||
createFlash({ message: __('Failed to load groups, users and deploy keys.') });
|
||||
});
|
||||
} else {
|
||||
this.getDeployKeys(query)
|
||||
|
|
@ -445,35 +440,33 @@ export default class AccessDropdown {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.deployKeysOnProtectedBranchesEnabled) {
|
||||
const deployKeys = deployKeysResponse.map((response) => {
|
||||
const {
|
||||
id,
|
||||
fingerprint,
|
||||
title,
|
||||
owner: { avatar_url, name, username },
|
||||
} = response;
|
||||
const deployKeys = deployKeysResponse.map((response) => {
|
||||
const {
|
||||
id,
|
||||
fingerprint,
|
||||
title,
|
||||
owner: { avatar_url, name, username },
|
||||
} = response;
|
||||
|
||||
const shortFingerprint = `(${fingerprint.substring(0, 14)}...)`;
|
||||
const shortFingerprint = `(${fingerprint.substring(0, 14)}...)`;
|
||||
|
||||
return {
|
||||
id,
|
||||
title: title.concat(' ', shortFingerprint),
|
||||
avatar_url,
|
||||
fullname: name,
|
||||
username,
|
||||
type: LEVEL_TYPES.DEPLOY_KEY,
|
||||
};
|
||||
});
|
||||
return {
|
||||
id,
|
||||
title: title.concat(' ', shortFingerprint),
|
||||
avatar_url,
|
||||
fullname: name,
|
||||
username,
|
||||
type: LEVEL_TYPES.DEPLOY_KEY,
|
||||
};
|
||||
});
|
||||
|
||||
if (this.accessLevel === ACCESS_LEVELS.PUSH) {
|
||||
if (deployKeys.length) {
|
||||
consolidatedData = consolidatedData.concat(
|
||||
[{ type: 'divider' }],
|
||||
[{ type: 'header', content: s__('AccessDropdown|Deploy Keys') }],
|
||||
deployKeys,
|
||||
);
|
||||
}
|
||||
if (this.accessLevel === ACCESS_LEVELS.PUSH) {
|
||||
if (deployKeys.length) {
|
||||
consolidatedData = consolidatedData.concat(
|
||||
[{ type: 'divider' }],
|
||||
[{ type: 'header', content: s__('AccessDropdown|Deploy Keys') }],
|
||||
deployKeys,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -501,19 +494,15 @@ export default class AccessDropdown {
|
|||
}
|
||||
|
||||
getDeployKeys(query) {
|
||||
if (this.deployKeysOnProtectedBranchesEnabled) {
|
||||
return axios.get(this.buildUrl(gon.relative_url_root, this.deployKeysPath), {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
active: true,
|
||||
project_id: gon.current_project_id,
|
||||
push_code: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: [] });
|
||||
return axios.get(this.buildUrl(gon.relative_url_root, this.deployKeysPath), {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
active: true,
|
||||
project_id: gon.current_project_id,
|
||||
push_code: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
buildUrl(urlRoot, url) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { throttle } from 'lodash';
|
||||
import { numberToHumanSize } from '../../../../lib/utils/number_utils';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { encodeSaferUrl } from '~/lib/utils/url_utility';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -43,6 +44,9 @@ export default {
|
|||
hasDimensions() {
|
||||
return this.width && this.height;
|
||||
},
|
||||
safePath() {
|
||||
return encodeSaferUrl(this.path);
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.resizeThrottled, false);
|
||||
|
|
@ -84,7 +88,7 @@ export default {
|
|||
<template>
|
||||
<div data-testid="image-viewer" data-qa-selector="image_viewer_container">
|
||||
<div :class="innerCssClasses" class="position-relative">
|
||||
<img ref="contentImg" :src="path" @load="onImgLoad" />
|
||||
<img ref="contentImg" :src="safePath" @load="onImgLoad" />
|
||||
<slot
|
||||
name="image-overlay"
|
||||
:rendered-width="renderedWidth"
|
||||
|
|
|
|||
|
|
@ -66,7 +66,11 @@ module BoardsResponses
|
|||
end
|
||||
|
||||
def respond_with_board
|
||||
respond_with(@board) # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
return render_404 unless @board
|
||||
|
||||
respond_with(@board)
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
end
|
||||
|
||||
def serialize_as_json(resource)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Projects::LearnGitlabController < Projects::ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :check_experiment_enabled?
|
||||
|
||||
feature_category :users
|
||||
|
||||
def index
|
||||
push_frontend_experiment(:learn_gitlab_a, subject: current_user)
|
||||
push_frontend_experiment(:learn_gitlab_b, subject: current_user)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_experiment_enabled?
|
||||
return access_denied! unless helpers.learn_gitlab_experiment_enabled?(project)
|
||||
end
|
||||
end
|
||||
|
|
@ -7,7 +7,6 @@ module Projects
|
|||
before_action :define_variables, only: [:create_deploy_token]
|
||||
before_action do
|
||||
push_frontend_feature_flag(:ajax_new_deploy_token, @project)
|
||||
push_frontend_feature_flag(:deploy_keys_on_protected_branches, @project)
|
||||
end
|
||||
|
||||
feature_category :source_code_management, [:show, :cleanup]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module LearnGitlabHelper
|
||||
def learn_gitlab_experiment_enabled?(project)
|
||||
return false unless current_user
|
||||
return false unless experiment_enabled_for_user?
|
||||
|
||||
learn_gitlab_onboarding_available?(project)
|
||||
end
|
||||
|
||||
def onboarding_actions_data(project)
|
||||
attributes = onboarding_progress(project).attributes.symbolize_keys
|
||||
|
||||
action_urls.map do |action, url|
|
||||
[
|
||||
action,
|
||||
url: url,
|
||||
completed: attributes[OnboardingProgress.column_name(action)].present?
|
||||
]
|
||||
end.to_h
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
ACTION_ISSUE_IDS = {
|
||||
git_write: 2,
|
||||
pipeline_created: 4,
|
||||
merge_request_created: 6,
|
||||
user_added: 7,
|
||||
trial_started: 13,
|
||||
required_mr_approvals_enabled: 15,
|
||||
code_owners_enabled: 16
|
||||
}.freeze
|
||||
|
||||
ACTION_DOC_URLS = {
|
||||
security_scan_enabled: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/#gitlab-security-dashboard-security-center-and-vulnerability-reports'
|
||||
}.freeze
|
||||
|
||||
def action_urls
|
||||
ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) }.merge(ACTION_DOC_URLS)
|
||||
end
|
||||
|
||||
def learn_gitlab_project
|
||||
@learn_gitlab_project ||= LearnGitlab.new(current_user).project
|
||||
end
|
||||
|
||||
def onboarding_progress(project)
|
||||
OnboardingProgress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
def experiment_enabled_for_user?
|
||||
Gitlab::Experimentation.in_experiment_group?(:learn_gitlab_a, subject: current_user) ||
|
||||
Gitlab::Experimentation.in_experiment_group?(:learn_gitlab_b, subject: current_user)
|
||||
end
|
||||
|
||||
def learn_gitlab_onboarding_available?(project)
|
||||
OnboardingProgress.onboarding?(project.namespace) &&
|
||||
LearnGitlab.new(current_user).available?
|
||||
end
|
||||
end
|
||||
|
|
@ -433,6 +433,8 @@ module ProjectsHelper
|
|||
|
||||
nav_tabs += package_nav_tabs(project, current_user)
|
||||
|
||||
nav_tabs << :learn_gitlab if learn_gitlab_experiment_enabled?(project)
|
||||
|
||||
nav_tabs
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Clusters
|
||||
module Applications
|
||||
# DEPRECATED for removal in %14.0
|
||||
# See https://gitlab.com/groups/gitlab-org/-/epics/4280
|
||||
class CertManager < ApplicationRecord
|
||||
VERSION = 'v0.10.1'
|
||||
CRD_VERSION = '0.10'
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Clusters
|
||||
module Applications
|
||||
# DEPRECATED for removal in %14.0
|
||||
# See https://gitlab.com/groups/gitlab-org/-/epics/4280
|
||||
class Crossplane < ApplicationRecord
|
||||
VERSION = '0.4.1'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Clusters
|
||||
module Applications
|
||||
# DEPRECATED for removal in %14.0
|
||||
# See https://gitlab.com/groups/gitlab-org/-/epics/4280
|
||||
class Ingress < ApplicationRecord
|
||||
VERSION = '1.40.2'
|
||||
INGRESS_CONTAINER_NAME = 'nginx-ingress-controller'
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ require 'securerandom'
|
|||
|
||||
module Clusters
|
||||
module Applications
|
||||
# DEPRECATED for removal in %14.0
|
||||
# See https://gitlab.com/groups/gitlab-org/-/epics/4280
|
||||
class Jupyter < ApplicationRecord
|
||||
VERSION = '0.9.0'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Clusters
|
||||
module Applications
|
||||
# DEPRECATED for removal in %14.0
|
||||
# See https://gitlab.com/groups/gitlab-org/-/epics/4280
|
||||
class Knative < ApplicationRecord
|
||||
VERSION = '0.10.0'
|
||||
REPOSITORY = 'https://charts.gitlab.io'
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ class OnboardingProgress < ApplicationRecord
|
|||
safe_find_or_create_by(namespace: namespace)
|
||||
end
|
||||
|
||||
def onboarding?(namespace)
|
||||
where(namespace: namespace).any?
|
||||
end
|
||||
|
||||
def register(namespace, action)
|
||||
return unless root_namespace?(namespace) && ACTIONS.include?(action)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class ProtectedBranch::PushAccessLevel < ApplicationRecord
|
|||
end
|
||||
|
||||
def check_access(user)
|
||||
if Feature.enabled?(:deploy_keys_on_protected_branches, project) && user && deploy_key.present?
|
||||
if user && deploy_key.present?
|
||||
return true if user.can?(:read_project, project) && enabled_deploy_key_for_user?(deploy_key, user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@
|
|||
= link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do
|
||||
%span= _('Releases')
|
||||
|
||||
- if project_nav_tab? :learn_gitlab
|
||||
= nav_link(controller: :learn_gitlab, html_options: { class: 'home' }) do
|
||||
= link_to project_learn_gitlab_path(@project) do
|
||||
.nav-icon-container
|
||||
= sprite_icon('home')
|
||||
%span.nav-item-name
|
||||
= _('Learn GitLab')
|
||||
|
||||
- if project_nav_tab? :files
|
||||
= nav_link(controller: sidebar_repository_paths, unless: -> { current_path?('projects/graphs#charts') }) do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
- breadcrumb_title _("Learn GitLab")
|
||||
- page_title _("Learn GitLab")
|
||||
|
||||
#js-learn-gitlab-app{ data: { actions: onboarding_actions_data(@project).to_json } }
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
- select_mode_for_dropdown = Feature.enabled?(:deploy_keys_on_protected_branches, @project) ? 'js-multiselect' : ''
|
||||
|
||||
- content_for :merge_access_levels do
|
||||
.merge_access_levels-container
|
||||
= dropdown_tag('Select',
|
||||
|
|
@ -9,7 +7,7 @@
|
|||
- content_for :push_access_levels do
|
||||
.push_access_levels-container
|
||||
= dropdown_tag('Select',
|
||||
options: { toggle_class: "js-allowed-to-push qa-allowed-to-push-select #{select_mode_for_dropdown} wide",
|
||||
options: { toggle_class: "js-allowed-to-push qa-allowed-to-push-select js-multiselect wide",
|
||||
dropdown_class: 'dropdown-menu-selectable qa-allowed-to-push-dropdown rspec-allowed-to-push-dropdown capitalize-header',
|
||||
data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
- select_mode_for_dropdown = Feature.enabled?(:deploy_keys_on_protected_branches, protected_branch.project) ? 'js-multiselect' : ''
|
||||
|
||||
- merge_access_levels = protected_branch.merge_access_levels.for_role
|
||||
- push_access_levels = protected_branch.push_access_levels.for_role
|
||||
|
||||
|
|
@ -25,7 +23,7 @@
|
|||
%td.push_access_levels-container
|
||||
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", push_access_levels.first&.access_level
|
||||
= dropdown_tag( (push_access_levels.first&.humanize || 'Select') ,
|
||||
options: { toggle_class: "js-allowed-to-push #{select_mode_for_dropdown}", dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header',
|
||||
options: { toggle_class: "js-allowed-to-push js-multiselect", dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header',
|
||||
data: { field_name: "allowed_to_push_#{protected_branch.id}", preselected_items: access_levels_data(push_access_levels) }})
|
||||
- if user_push_access_levels.any?
|
||||
%p.small
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Deprecate GitLab-managed (v1) apps that will be removed in 14.0
|
||||
merge_request: 54162
|
||||
author:
|
||||
type: deprecated
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow deploy keys to push to a protected branch
|
||||
merge_request: 53812
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix some image diff URLs with special characters causing the diff to not show
|
||||
merge_request: 53638
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: deploy_keys_on_protected_branches
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35638
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247866
|
||||
milestone: '13.5'
|
||||
type: development
|
||||
group: group::release
|
||||
name: learn_gitlab_a_experiment_percentage
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53089
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/281022
|
||||
milestone: '13.9'
|
||||
type: experiment
|
||||
group: group::conversion
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: learn_gitlab_b_experiment_percentage
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53089
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/306
|
||||
milestone: '13.9'
|
||||
type: experiment
|
||||
group: group::conversion
|
||||
default_enabled: false
|
||||
|
|
@ -87,6 +87,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
end
|
||||
end
|
||||
|
||||
get :learn_gitlab, action: :index, controller: 'learn_gitlab'
|
||||
|
||||
namespace :ci do
|
||||
resource :lint, only: [:show, :create]
|
||||
resource :pipeline_editor, only: [:show], controller: :pipeline_editor, path: 'editor'
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ and [code review guidelines](https://docs.gitlab.com/ee/development/code_review.
|
|||
Please consider assigning a reviewer or maintainer who is a
|
||||
[domain expert](https://about.gitlab.com/handbook/engineering/projects/#gitlab) in the area of the merge request.
|
||||
|
||||
Once you've decided who will review this merge request, mention them as you
|
||||
normally would! Danger does not automatically notify them for you.
|
||||
Once you've decided who will review this merge request, assign them as a reviewer!
|
||||
Danger does not automatically notify them for you.
|
||||
|
||||
| Category | Reviewer | Maintainer |
|
||||
| -------- | -------- | ---------- |
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ they are set to Maintainers by default.
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30769) in GitLab 13.7.
|
||||
> - This feature is being selectively deployed in GitLab.com 13.7, and may not be available for all users.
|
||||
> - This feature is available for all users in GitLab 13.9.
|
||||
|
||||
You can allow specific machines to access protected branches in your repository with
|
||||
[deploy keys](deploy_keys/index.md). This can be useful for your CI/CD workflow,
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ For GitLab.com, it is set to 10 MB.
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/290813) in GitLab 13.8.
|
||||
> - Revised CSV column headers [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299247) in GitLab 13.9.
|
||||
> - Ability to select which fields to export [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/290823) in GitLab 13.9.
|
||||
|
||||
You can export GitLab requirements to a
|
||||
[CSV file](https://en.wikipedia.org/wiki/Comma-separated_values) sent to your default notification
|
||||
|
|
@ -276,7 +277,14 @@ Users with Reporter or higher [permissions](../../permissions.md) can export req
|
|||
To export requirements:
|
||||
|
||||
1. In a project, go to **Requirements**.
|
||||
1. Select the **Export as CSV** icon (**{export}**) in the top right. A confirmation modal appears.
|
||||
1. In the top right, select the **Export as CSV** icon (**{export}**).
|
||||
|
||||
A confirmation modal appears.
|
||||
|
||||
1. Under **Advanced export options**, select which fields to export.
|
||||
|
||||
All fields are selected by default. To exclude a field from being exported, clear the checkbox next to it.
|
||||
|
||||
1. Select **Export requirements**. The exported CSV file is sent to the email address associated with your user.
|
||||
|
||||
### Exported CSV file format
|
||||
|
|
|
|||
|
|
@ -95,6 +95,12 @@ module Gitlab
|
|||
trial_onboarding_issues: {
|
||||
tracking_category: 'Growth::Conversion::Experiment::TrialOnboardingIssues'
|
||||
},
|
||||
learn_gitlab_a: {
|
||||
tracking_category: 'Growth::Conversion::Experiment::LearnGitLabA'
|
||||
},
|
||||
learn_gitlab_b: {
|
||||
tracking_category: 'Growth::Activation::Experiment::LearnGitLabB'
|
||||
},
|
||||
in_product_marketing_emails: {
|
||||
tracking_category: 'Growth::Activation::Experiment::InProductMarketingEmails'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,10 +319,8 @@ module Gitlab
|
|||
end
|
||||
|
||||
def check_change_access!
|
||||
return if deploy_key? && !deploy_keys_on_protected_branches_enabled?
|
||||
|
||||
if changes == ANY
|
||||
can_push = (deploy_key? && deploy_keys_on_protected_branches_enabled?) ||
|
||||
can_push = deploy_key? ||
|
||||
user_can_push? ||
|
||||
project&.any_branch_allows_collaboration?(user_access.user)
|
||||
|
||||
|
|
@ -453,7 +451,7 @@ module Gitlab
|
|||
CiAccess.new
|
||||
elsif user && request_from_ci_build?
|
||||
BuildAccess.new(user, container: container)
|
||||
elsif deploy_key? && deploy_keys_on_protected_branches_enabled?
|
||||
elsif deploy_key?
|
||||
DeployKeyAccess.new(deploy_key, container: container)
|
||||
else
|
||||
UserAccess.new(user, container: container)
|
||||
|
|
@ -532,10 +530,6 @@ module Gitlab
|
|||
def size_checker
|
||||
container.repository_size_checker
|
||||
end
|
||||
|
||||
def deploy_keys_on_protected_branches_enabled?
|
||||
Feature.enabled?(:deploy_keys_on_protected_branches, project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -738,9 +738,6 @@ msgstr ""
|
|||
msgid "%{reportType} detected %{totalStart}no%{totalEnd} vulnerabilities."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{requirementCount} requirements have been selected for export. These will be sent to %{email} as an attachment once finished."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2552,6 +2549,9 @@ msgstr ""
|
|||
msgid "Advanced Settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Advanced export options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -12125,6 +12125,9 @@ msgstr ""
|
|||
msgid "Export"
|
||||
msgstr ""
|
||||
|
||||
msgid "Export %{requirementsCount} requirements?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Export as CSV"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -12320,9 +12323,6 @@ msgstr ""
|
|||
msgid "Failed to load group activity metrics. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load groups & users."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load groups, users and deploy keys."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17445,6 +17445,30 @@ msgstr ""
|
|||
msgid "Learn more."
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Add code owners"
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Create a repository"
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Enable require merge approvals"
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Invite your colleagues"
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Run a Security scan using CI/CD"
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Set-up CI/CD"
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Start a free trial of GitLab Gold"
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Submit a merge request (MR)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -22166,6 +22190,9 @@ msgstr ""
|
|||
msgid "Please select at least one filter to see results"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select what should be included in each exported requirement."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please set a new password before proceeding."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29784,6 +29811,9 @@ msgstr ""
|
|||
msgid "These variables are inherited from the parent group."
|
||||
msgstr ""
|
||||
|
||||
msgid "These will be sent to %{email} in an attachment once finished."
|
||||
msgstr ""
|
||||
|
||||
msgid "Third Party Advisory Link"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::LearnGitlabController do
|
||||
describe 'GET #index' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, namespace: user.namespace) }
|
||||
|
||||
let(:learn_gitlab_experiment_enabled) { true }
|
||||
let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
|
||||
|
||||
subject { get :index, params: params }
|
||||
|
||||
before do
|
||||
allow(controller.helpers).to receive(:learn_gitlab_experiment_enabled?).and_return(learn_gitlab_experiment_enabled)
|
||||
end
|
||||
|
||||
context 'unauthenticated user' do
|
||||
it { is_expected.to have_gitlab_http_status(:redirect) }
|
||||
end
|
||||
|
||||
context 'authenticated user' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it { is_expected.to render_template(:index) }
|
||||
|
||||
it 'pushes experiment to frontend' do
|
||||
expect(controller).to receive(:push_frontend_experiment).with(:learn_gitlab_a, subject: user)
|
||||
expect(controller).to receive(:push_frontend_experiment).with(:learn_gitlab_b, subject: user)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'learn_gitlab experiment not enabled' do
|
||||
let(:learn_gitlab_experiment_enabled) { false }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -9,10 +9,6 @@ RSpec.describe 'Protected Branches', :js do
|
|||
let(:admin) { create(:admin) }
|
||||
let(:project) { create(:project, :repository) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(deploy_keys_on_protected_branches: false)
|
||||
end
|
||||
|
||||
context 'logged in as developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
|
@ -174,7 +170,7 @@ RSpec.describe 'Protected Branches', :js do
|
|||
stub_licensed_features(protected_refs_for_users: false)
|
||||
end
|
||||
|
||||
include_examples 'when the deploy_keys_on_protected_branches FF is turned on' do
|
||||
include_examples 'Deploy keys with protected branches' do
|
||||
let(:all_dropdown_sections) { %w(Roles Deploy\ Keys) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -880,4 +880,37 @@ describe('URL utility', () => {
|
|||
expect(urlUtils.getURLOrigin(url)).toBe(expectation);
|
||||
});
|
||||
});
|
||||
|
||||
describe('encodeSaferUrl', () => {
|
||||
it.each`
|
||||
character | input | output
|
||||
${' '} | ${'/url/hello 1.jpg'} | ${'/url/hello%201.jpg'}
|
||||
${'#'} | ${'/url/hello#1.jpg'} | ${'/url/hello%231.jpg'}
|
||||
${'!'} | ${'/url/hello!.jpg'} | ${'/url/hello%21.jpg'}
|
||||
${'~'} | ${'/url/hello~.jpg'} | ${'/url/hello%7E.jpg'}
|
||||
${'*'} | ${'/url/hello*.jpg'} | ${'/url/hello%2A.jpg'}
|
||||
${"'"} | ${"/url/hello'.jpg"} | ${'/url/hello%27.jpg'}
|
||||
${'('} | ${'/url/hello(.jpg'} | ${'/url/hello%28.jpg'}
|
||||
${')'} | ${'/url/hello).jpg'} | ${'/url/hello%29.jpg'}
|
||||
${'?'} | ${'/url/hello?.jpg'} | ${'/url/hello%3F.jpg'}
|
||||
${'='} | ${'/url/hello=.jpg'} | ${'/url/hello%3D.jpg'}
|
||||
${'+'} | ${'/url/hello+.jpg'} | ${'/url/hello%2B.jpg'}
|
||||
${'&'} | ${'/url/hello&.jpg'} | ${'/url/hello%26.jpg'}
|
||||
`(
|
||||
'properly escapes `$character` characters while retaining the integrity of the URL',
|
||||
({ input, output }) => {
|
||||
expect(urlUtils.encodeSaferUrl(input)).toBe(output);
|
||||
},
|
||||
);
|
||||
|
||||
it.each`
|
||||
character | input
|
||||
${'/, .'} | ${'/url/hello.png'}
|
||||
${'\\d'} | ${'/url/hello123.png'}
|
||||
${'-'} | ${'/url/hello-123.png'}
|
||||
${'_'} | ${'/url/hello_123.png'}
|
||||
`('makes no changes to unproblematic characters ($character)', ({ input }) => {
|
||||
expect(urlUtils.encodeSaferUrl(input)).toBe(input);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Learn GitLab Design A should render the loading state 1`] = `
|
||||
<ul>
|
||||
<li>
|
||||
<span>
|
||||
Create a repository
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
Invite your colleagues
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
Set-up CI/CD
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<gl-link-stub
|
||||
href="http://example.com/"
|
||||
>
|
||||
Start a free trial of GitLab Gold
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<gl-link-stub
|
||||
href="http://example.com/"
|
||||
>
|
||||
Add code owners
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<gl-link-stub
|
||||
href="http://example.com/"
|
||||
>
|
||||
Enable require merge approvals
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<gl-link-stub
|
||||
href="http://example.com/"
|
||||
>
|
||||
Submit a merge request (MR)
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<gl-link-stub
|
||||
href="http://example.com/"
|
||||
>
|
||||
Run a Security scan using CI/CD
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Learn GitLab Design B should render the loading state 1`] = `
|
||||
<ul>
|
||||
<li>
|
||||
<span>
|
||||
Create a repository
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
Invite your colleagues
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
Set-up CI/CD
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<gl-link-stub
|
||||
href="http://example.com/"
|
||||
>
|
||||
Start a free trial of GitLab Gold
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<gl-link-stub
|
||||
href="http://example.com/"
|
||||
>
|
||||
Add code owners
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<gl-link-stub
|
||||
href="http://example.com/"
|
||||
>
|
||||
Enable require merge approvals
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<gl-link-stub
|
||||
href="http://example.com/"
|
||||
>
|
||||
Submit a merge request (MR)
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<gl-link-stub
|
||||
href="http://example.com/"
|
||||
>
|
||||
Run a Security scan using CI/CD
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import LearnGitlabA from '~/pages/projects/learn_gitlab/components/learn_gitlab_a.vue';
|
||||
|
||||
const TEST_ACTIONS = {
|
||||
gitWrite: {
|
||||
url: 'http://example.com/',
|
||||
completed: true,
|
||||
},
|
||||
userAdded: {
|
||||
url: 'http://example.com/',
|
||||
completed: true,
|
||||
},
|
||||
pipelineCreated: {
|
||||
url: 'http://example.com/',
|
||||
completed: true,
|
||||
},
|
||||
trialStarted: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
},
|
||||
codeOwnersEnabled: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
},
|
||||
requiredMrApprovalsEnabled: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
},
|
||||
mergeRequestCreated: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
},
|
||||
securityScanEnabled: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
},
|
||||
};
|
||||
|
||||
describe('Learn GitLab Design A', () => {
|
||||
let wrapper;
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const createWrapper = () => {
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(LearnGitlabA, {
|
||||
propsData: {
|
||||
actions: TEST_ACTIONS,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
it('should render the loading state', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import LearnGitlabA from '~/pages/projects/learn_gitlab/components/learn_gitlab_a.vue';
|
||||
|
||||
const TEST_ACTIONS = {
|
||||
gitWrite: {
|
||||
url: 'http://example.com/',
|
||||
completed: true,
|
||||
},
|
||||
userAdded: {
|
||||
url: 'http://example.com/',
|
||||
completed: true,
|
||||
},
|
||||
pipelineCreated: {
|
||||
url: 'http://example.com/',
|
||||
completed: true,
|
||||
},
|
||||
trialStarted: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
},
|
||||
codeOwnersEnabled: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
},
|
||||
requiredMrApprovalsEnabled: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
},
|
||||
mergeRequestCreated: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
},
|
||||
securityScanEnabled: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
},
|
||||
};
|
||||
|
||||
describe('Learn GitLab Design B', () => {
|
||||
let wrapper;
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const createWrapper = () => {
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(LearnGitlabA, {
|
||||
propsData: {
|
||||
actions: TEST_ACTIONS,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
it('should render the loading state', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -14,7 +14,6 @@ describe('AccessDropdown', () => {
|
|||
`);
|
||||
const $dropdown = $('#dummy-dropdown');
|
||||
$dropdown.data('defaultLabel', defaultLabel);
|
||||
gon.features = { deployKeysOnProtectedBranches: true };
|
||||
const options = {
|
||||
$dropdown,
|
||||
accessLevelsData: {
|
||||
|
|
|
|||
|
|
@ -33,4 +33,14 @@ describe('Image Viewer', () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('file path', () => {
|
||||
it('should output a valid URL path for the image', () => {
|
||||
wrapper = mount(ImageViewer, {
|
||||
propsData: { path: '/url/hello#1.jpg' },
|
||||
});
|
||||
|
||||
expect(wrapper.find('img').attributes('src')).toBe('/url/hello%231.jpg');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe LearnGitlabHelper do
|
||||
include AfterNextHelpers
|
||||
include Devise::Test::ControllerHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, name: LearnGitlab::PROJECT_NAME, namespace: user.namespace) }
|
||||
let_it_be(:namespace) { project.namespace }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
||||
allow(helper).to receive(:user).and_return(user)
|
||||
allow_next_instance_of(LearnGitlab) do |learn_gitlab|
|
||||
allow(learn_gitlab).to receive(:project).and_return(project)
|
||||
end
|
||||
|
||||
OnboardingProgress.onboard(namespace)
|
||||
OnboardingProgress.register(namespace, :git_write)
|
||||
end
|
||||
|
||||
describe '.onboarding_actions_data' do
|
||||
subject(:onboarding_actions_data) { helper.onboarding_actions_data(project) }
|
||||
|
||||
it 'has all actions' do
|
||||
expect(onboarding_actions_data.keys).to contain_exactly(
|
||||
:git_write,
|
||||
:pipeline_created,
|
||||
:merge_request_created,
|
||||
:user_added,
|
||||
:trial_started,
|
||||
:required_mr_approvals_enabled,
|
||||
:code_owners_enabled,
|
||||
:security_scan_enabled
|
||||
)
|
||||
end
|
||||
|
||||
it 'sets correct path and completion status' do
|
||||
expect(onboarding_actions_data[:git_write]).to eq({
|
||||
url: project_issue_url(project, LearnGitlabHelper::ACTION_ISSUE_IDS[:git_write]),
|
||||
completed: true
|
||||
})
|
||||
expect(onboarding_actions_data[:pipeline_created]).to eq({
|
||||
url: project_issue_url(project, LearnGitlabHelper::ACTION_ISSUE_IDS[:pipeline_created]),
|
||||
completed: false
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
describe '.learn_gitlab_experiment_enabled?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, namespace: user.namespace) }
|
||||
|
||||
let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
|
||||
|
||||
subject { helper.learn_gitlab_experiment_enabled?(project) }
|
||||
|
||||
where(:experiment_a, :experiment_b, :onboarding, :learn_gitlab_available, :result) do
|
||||
true | false | true | true | true
|
||||
false | true | true | true | true
|
||||
false | false | true | true | false
|
||||
true | true | true | false | false
|
||||
true | true | false | true | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
stub_experiment_for_subject(learn_gitlab_a: experiment_a, learn_gitlab_b: experiment_b)
|
||||
allow(OnboardingProgress).to receive(:onboarding?).with(project.namespace).and_return(onboarding)
|
||||
allow_next(LearnGitlab, user).to receive(:available?).and_return(learn_gitlab_available)
|
||||
end
|
||||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe ProjectsHelper do
|
||||
include ProjectForksHelper
|
||||
include AfterNextHelpers
|
||||
|
||||
let_it_be_with_reload(:project) { create(:project) }
|
||||
let_it_be_with_refind(:project_with_repo) { create(:project, :repository) }
|
||||
|
|
@ -498,6 +499,20 @@ RSpec.describe ProjectsHelper do
|
|||
it { is_expected.not_to include(:confluence) }
|
||||
it { is_expected.to include(:wiki) }
|
||||
end
|
||||
|
||||
context 'learn gitlab experiment' do
|
||||
context 'when it is enabled' do
|
||||
before do
|
||||
expect(helper).to receive(:learn_gitlab_experiment_enabled?).with(project).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to include(:learn_gitlab) }
|
||||
end
|
||||
|
||||
context 'when it is not enabled' do
|
||||
it { is_expected.not_to include(:learn_gitlab) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_view_operations_tab?' do
|
||||
|
|
|
|||
|
|
@ -114,6 +114,22 @@ RSpec.describe OnboardingProgress do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.onboarding?' do
|
||||
subject(:onboarding?) { described_class.onboarding?(namespace) }
|
||||
|
||||
context 'when onboarded' do
|
||||
before do
|
||||
described_class.onboard(namespace)
|
||||
end
|
||||
|
||||
it { is_expected.to eq true }
|
||||
end
|
||||
|
||||
context 'when not onboarding' do
|
||||
it { is_expected.to eq false }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.register' do
|
||||
subject(:register_action) { described_class.register(namespace, action) }
|
||||
|
||||
|
|
|
|||
|
|
@ -54,16 +54,6 @@ RSpec.describe ProtectedBranch::PushAccessLevel do
|
|||
specify do
|
||||
expect(push_access_level.check_access(user)).to be_truthy
|
||||
end
|
||||
|
||||
context 'when the deploy_keys_on_protected_branches FF is false' do
|
||||
before do
|
||||
stub_feature_flags(deploy_keys_on_protected_branches: false)
|
||||
end
|
||||
|
||||
it 'is false' do
|
||||
expect(push_access_level.check_access(user)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the deploy key is not among the active keys of this project' do
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ RSpec.shared_examples "protected branches > access control > CE" do
|
|||
expect(first("li")).to have_content("Roles")
|
||||
find(:link, access_type_name).click
|
||||
end
|
||||
|
||||
find(".js-allowed-to-push").click
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'when the deploy_keys_on_protected_branches FF is turned on' do
|
||||
RSpec.shared_examples 'Deploy keys with protected branches' do
|
||||
before do
|
||||
stub_feature_flags(deploy_keys_on_protected_branches: true)
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue