Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ace0df53d3
commit
2ddcd634fc
|
@ -306,7 +306,7 @@ export default () => {
|
||||||
|
|
||||||
const issueBoardsModal = document.getElementById('js-add-issues-btn');
|
const issueBoardsModal = document.getElementById('js-add-issues-btn');
|
||||||
|
|
||||||
if (issueBoardsModal) {
|
if (issueBoardsModal && gon.features.addIssuesButton) {
|
||||||
// eslint-disable-next-line no-new
|
// eslint-disable-next-line no-new
|
||||||
new Vue({
|
new Vue({
|
||||||
el: issueBoardsModal,
|
el: issueBoardsModal,
|
||||||
|
|
|
@ -33,7 +33,14 @@ export default {
|
||||||
mixins: [glFeatureFlagsMixin()],
|
mixins: [glFeatureFlagsMixin()],
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['currentKey', 'propsSource', 'isDisabled']),
|
...mapGetters(['currentKey', 'propsSource', 'isDisabled']),
|
||||||
...mapState(['defaultState', 'override', 'isSaving', 'isTesting', 'isResetting']),
|
...mapState([
|
||||||
|
'defaultState',
|
||||||
|
'customState',
|
||||||
|
'override',
|
||||||
|
'isSaving',
|
||||||
|
'isTesting',
|
||||||
|
'isResetting',
|
||||||
|
]),
|
||||||
isEditable() {
|
isEditable() {
|
||||||
return this.propsSource.editable;
|
return this.propsSource.editable;
|
||||||
},
|
},
|
||||||
|
@ -42,8 +49,8 @@ export default {
|
||||||
},
|
},
|
||||||
isInstanceOrGroupLevel() {
|
isInstanceOrGroupLevel() {
|
||||||
return (
|
return (
|
||||||
this.propsSource.integrationLevel === integrationLevels.INSTANCE ||
|
this.customState.integrationLevel === integrationLevels.INSTANCE ||
|
||||||
this.propsSource.integrationLevel === integrationLevels.GROUP
|
this.customState.integrationLevel === integrationLevels.GROUP
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
showJiraIssuesFields() {
|
showJiraIssuesFields() {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import ActionComponent from './action_component.vue';
|
||||||
import JobNameComponent from './job_name_component.vue';
|
import JobNameComponent from './job_name_component.vue';
|
||||||
import { sprintf } from '~/locale';
|
import { sprintf } from '~/locale';
|
||||||
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
|
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
|
||||||
import { accessors } from './accessors';
|
import { accessValue } from './accessors';
|
||||||
import { REST } from './constants';
|
import { REST } from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,10 +79,10 @@ export default {
|
||||||
return this.dropdownLength === 1 ? 'viewport' : 'scrollParent';
|
return this.dropdownLength === 1 ? 'viewport' : 'scrollParent';
|
||||||
},
|
},
|
||||||
detailsPath() {
|
detailsPath() {
|
||||||
return this.status[accessors[this.dataMethod].detailsPath];
|
return accessValue(this.dataMethod, 'detailsPath', this.status);
|
||||||
},
|
},
|
||||||
hasDetails() {
|
hasDetails() {
|
||||||
return this.status[accessors[this.dataMethod].hasDetails];
|
return accessValue(this.dataMethod, 'hasDetails', this.status);
|
||||||
},
|
},
|
||||||
status() {
|
status() {
|
||||||
return this.job && this.job.status ? this.job.status : {};
|
return this.job && this.job.status ? this.job.status : {};
|
||||||
|
|
|
@ -5,7 +5,7 @@ import JobItem from './job_item.vue';
|
||||||
import JobGroupDropdown from './job_group_dropdown.vue';
|
import JobGroupDropdown from './job_group_dropdown.vue';
|
||||||
import ActionComponent from './action_component.vue';
|
import ActionComponent from './action_component.vue';
|
||||||
import { GRAPHQL } from './constants';
|
import { GRAPHQL } from './constants';
|
||||||
import { accessors } from './accessors';
|
import { accessValue } from './accessors';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -39,7 +39,6 @@ export default {
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
accessors,
|
|
||||||
titleClasses: [
|
titleClasses: [
|
||||||
'gl-font-weight-bold',
|
'gl-font-weight-bold',
|
||||||
'gl-pipeline-job-width',
|
'gl-pipeline-job-width',
|
||||||
|
@ -56,8 +55,8 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getAccessor(property) {
|
getGroupId(group) {
|
||||||
return accessors[GRAPHQL][property];
|
return accessValue(GRAPHQL, 'groupId', group);
|
||||||
},
|
},
|
||||||
groupId(group) {
|
groupId(group) {
|
||||||
return `ci-badge-${escape(group.name)}`;
|
return `ci-badge-${escape(group.name)}`;
|
||||||
|
@ -87,7 +86,7 @@ export default {
|
||||||
<div
|
<div
|
||||||
v-for="group in groups"
|
v-for="group in groups"
|
||||||
:id="groupId(group)"
|
:id="groupId(group)"
|
||||||
:key="group[getAccessor('groupId')]"
|
:key="getGroupId(group)"
|
||||||
data-testid="stage-column-group"
|
data-testid="stage-column-group"
|
||||||
class="gl-relative gl-mb-3 gl-white-space-normal gl-pipeline-job-width"
|
class="gl-relative gl-mb-3 gl-white-space-normal gl-pipeline-job-width"
|
||||||
>
|
>
|
||||||
|
|
|
@ -229,6 +229,7 @@ export default {
|
||||||
v-if="pipeline.cancelable"
|
v-if="pipeline.cancelable"
|
||||||
:loading="isCanceling"
|
:loading="isCanceling"
|
||||||
:disabled="isCanceling"
|
:disabled="isCanceling"
|
||||||
|
class="gl-ml-3"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
data-testid="cancelPipeline"
|
data-testid="cancelPipeline"
|
||||||
@click="cancelPipeline()"
|
@click="cancelPipeline()"
|
||||||
|
|
|
@ -30,6 +30,10 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
customEmailEnabled: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
selectedTemplate: {
|
selectedTemplate: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -140,6 +144,7 @@ export default {
|
||||||
:is-enabled="isEnabled"
|
:is-enabled="isEnabled"
|
||||||
:incoming-email="incomingEmail"
|
:incoming-email="incomingEmail"
|
||||||
:custom-email="updatedCustomEmail"
|
:custom-email="updatedCustomEmail"
|
||||||
|
:custom-email-enabled="customEmailEnabled"
|
||||||
:initial-selected-template="selectedTemplate"
|
:initial-selected-template="selectedTemplate"
|
||||||
:initial-outgoing-name="outgoingName"
|
:initial-outgoing-name="outgoingName"
|
||||||
:initial-project-key="projectKey"
|
:initial-project-key="projectKey"
|
||||||
|
|
|
@ -31,6 +31,10 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
customEmailEnabled: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
initialSelectedTemplate: {
|
initialSelectedTemplate: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -69,7 +73,7 @@ export default {
|
||||||
return [''].concat(this.templates);
|
return [''].concat(this.templates);
|
||||||
},
|
},
|
||||||
hasProjectKeySupport() {
|
hasProjectKeySupport() {
|
||||||
return Boolean(this.glFeatures.serviceDeskCustomAddress);
|
return Boolean(this.customEmailEnabled);
|
||||||
},
|
},
|
||||||
email() {
|
email() {
|
||||||
return this.customEmail || this.incomingEmail;
|
return this.customEmail || this.incomingEmail;
|
||||||
|
|
|
@ -18,6 +18,7 @@ export default () => {
|
||||||
endpoint: dataset.endpoint,
|
endpoint: dataset.endpoint,
|
||||||
incomingEmail: dataset.incomingEmail,
|
incomingEmail: dataset.incomingEmail,
|
||||||
customEmail: dataset.customEmail,
|
customEmail: dataset.customEmail,
|
||||||
|
customEmailEnabled: parseBoolean(dataset.customEmailEnabled),
|
||||||
selectedTemplate: dataset.selectedTemplate,
|
selectedTemplate: dataset.selectedTemplate,
|
||||||
outgoingName: dataset.outgoingName,
|
outgoingName: dataset.outgoingName,
|
||||||
projectKey: dataset.projectKey,
|
projectKey: dataset.projectKey,
|
||||||
|
@ -31,6 +32,7 @@ export default () => {
|
||||||
endpoint: this.endpoint,
|
endpoint: this.endpoint,
|
||||||
incomingEmail: this.incomingEmail,
|
incomingEmail: this.incomingEmail,
|
||||||
customEmail: this.customEmail,
|
customEmail: this.customEmail,
|
||||||
|
customEmailEnabled: this.customEmailEnabled,
|
||||||
selectedTemplate: this.selectedTemplate,
|
selectedTemplate: this.selectedTemplate,
|
||||||
outgoingName: this.outgoingName,
|
outgoingName: this.outgoingName,
|
||||||
projectKey: this.projectKey,
|
projectKey: this.projectKey,
|
||||||
|
|
|
@ -129,17 +129,6 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to Gitlab UI
|
|
||||||
.gl-font-weight-100 {
|
|
||||||
font-weight: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gl-active-text-decoration-none:active,
|
|
||||||
.gl-focus-text-decoration-none:focus,
|
|
||||||
.gl-hover-text-decoration-none:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are single-value classes to use with utility-class style CSS
|
// These are single-value classes to use with utility-class style CSS
|
||||||
// but to still access this variable. Do not add other styles.
|
// but to still access this variable. Do not add other styles.
|
||||||
.gl-pipeline-min-h {
|
.gl-pipeline-min-h {
|
||||||
|
@ -150,10 +139,6 @@
|
||||||
width: 186px;
|
width: 186px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gl-pipeline-title-width {
|
|
||||||
width: 176px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gl-build-content {
|
.gl-build-content {
|
||||||
@include build-content();
|
@include build-content();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,9 @@ class Projects::BoardsController < Projects::ApplicationController
|
||||||
before_action :check_issues_available!
|
before_action :check_issues_available!
|
||||||
before_action :authorize_read_board!, only: [:index, :show]
|
before_action :authorize_read_board!, only: [:index, :show]
|
||||||
before_action :assign_endpoint_vars
|
before_action :assign_endpoint_vars
|
||||||
|
before_action do
|
||||||
|
push_frontend_feature_flag(:add_issues_button)
|
||||||
|
end
|
||||||
|
|
||||||
feature_category :boards
|
feature_category :boards
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ class ProjectsController < Projects::ApplicationController
|
||||||
before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export]
|
before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export]
|
||||||
|
|
||||||
before_action only: [:edit] do
|
before_action only: [:edit] do
|
||||||
push_frontend_feature_flag(:service_desk_custom_address, @project)
|
|
||||||
push_frontend_feature_flag(:approval_suggestions, @project, default_enabled: true)
|
push_frontend_feature_flag(:approval_suggestions, @project, default_enabled: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2507,8 +2507,7 @@ class Project < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def service_desk_custom_address
|
def service_desk_custom_address
|
||||||
return unless ::Gitlab::ServiceDeskEmail.enabled?
|
return unless service_desk_custom_address_enabled?
|
||||||
return unless ::Feature.enabled?(:service_desk_custom_address, self)
|
|
||||||
|
|
||||||
key = service_desk_setting&.project_key
|
key = service_desk_setting&.project_key
|
||||||
return unless key.present?
|
return unless key.present?
|
||||||
|
@ -2516,6 +2515,10 @@ class Project < ApplicationRecord
|
||||||
::Gitlab::ServiceDeskEmail.address_for_key("#{full_path_slug}-#{key}")
|
::Gitlab::ServiceDeskEmail.address_for_key("#{full_path_slug}-#{key}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def service_desk_custom_address_enabled?
|
||||||
|
::Gitlab::ServiceDeskEmail.enabled? && ::Feature.enabled?(:service_desk_custom_address, self)
|
||||||
|
end
|
||||||
|
|
||||||
def root_namespace
|
def root_namespace
|
||||||
if namespace.has_parent?
|
if namespace.has_parent?
|
||||||
namespace.root_ancestor
|
namespace.root_ancestor
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
enabled: "#{@project.service_desk_enabled}",
|
enabled: "#{@project.service_desk_enabled}",
|
||||||
incoming_email: (@project.service_desk_incoming_address if @project.service_desk_enabled),
|
incoming_email: (@project.service_desk_incoming_address if @project.service_desk_enabled),
|
||||||
custom_email: (@project.service_desk_custom_address if @project.service_desk_enabled),
|
custom_email: (@project.service_desk_custom_address if @project.service_desk_enabled),
|
||||||
|
custom_email_enabled: "#{@project.service_desk_custom_address_enabled?}",
|
||||||
selected_template: "#{@project.service_desk_setting&.issue_template_key}",
|
selected_template: "#{@project.service_desk_setting&.issue_template_key}",
|
||||||
outgoing_name: "#{@project.service_desk_setting&.outgoing_name}",
|
outgoing_name: "#{@project.service_desk_setting&.outgoing_name}",
|
||||||
project_key: "#{@project.service_desk_setting&.project_key}",
|
project_key: "#{@project.service_desk_setting&.project_key}",
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
= _('In %{time_to_now}') % { time_to_now: distance_of_time_in_words_to_now(token.expires_at) }
|
= _('In %{time_to_now}') % { time_to_now: distance_of_time_in_words_to_now(token.expires_at) }
|
||||||
- else
|
- else
|
||||||
%span.token-never-expires-label= _('Never')
|
%span.token-never-expires-label= _('Never')
|
||||||
%td= token.scopes.present? ? token.scopes.join(', ') : html_escape_once(_('<no scopes selected>')).html_safe
|
%td= token.scopes.present? ? token.scopes.join(', ') : _('no scopes selected')
|
||||||
%td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: 'btn btn-danger float-right qa-revoke-button', data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
|
%td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: 'btn btn-danger float-right qa-revoke-button', data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
|
||||||
- else
|
- else
|
||||||
.settings-message.text-center
|
.settings-message.text-center
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
In #{distance_of_time_in_words_to_now(token.expires_at)}
|
In #{distance_of_time_in_words_to_now(token.expires_at)}
|
||||||
- else
|
- else
|
||||||
%span.token-never-expires-label= _('Never')
|
%span.token-never-expires-label= _('Never')
|
||||||
%td= token.scopes.present? ? token.scopes.join(", ") : html_escape_once(_('<no scopes selected>')).html_safe
|
%td= token.scopes.present? ? token.scopes.join(', ') : _('no scopes selected')
|
||||||
%td= link_to s_('DeployTokens|Revoke'), "#", class: "btn btn-danger float-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"}
|
%td= link_to s_('DeployTokens|Revoke'), "#", class: "btn btn-danger float-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"}
|
||||||
= render 'shared/deploy_tokens/revoke_modal', token: token, group_or_project: group_or_project
|
= render 'shared/deploy_tokens/revoke_modal', token: token, group_or_project: group_or_project
|
||||||
- else
|
- else
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove brackets in no scopes selected message in access and deploy tokens lists
|
||||||
|
merge_request: 47628
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove `Add Issues` button and a related modal
|
||||||
|
merge_request: 47898
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix styling of various dropdowns
|
||||||
|
merge_request: 48800
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix confirmation modal showing on project integration
|
||||||
|
merge_request: 48720
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix spacing between buttons on pipeline header
|
||||||
|
merge_request: 48660
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: add_issues_button
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47898
|
||||||
|
rollout_issue_url:
|
||||||
|
milestone: '13.6'
|
||||||
|
type: development
|
||||||
|
group: group::project management
|
||||||
|
default_enabled: false
|
|
@ -1042,6 +1042,10 @@ production: &base
|
||||||
shared:
|
shared:
|
||||||
# path: /mnt/gitlab # Default: shared
|
# path: /mnt/gitlab # Default: shared
|
||||||
|
|
||||||
|
# Encrypted Settings configuration
|
||||||
|
encrypted_settings:
|
||||||
|
# path: /mnt/gitlab/encrypted_settings # Default: shared/encrypted_settings
|
||||||
|
|
||||||
# Gitaly settings
|
# Gitaly settings
|
||||||
gitaly:
|
gitaly:
|
||||||
# Path to the directory containing Gitaly client executables.
|
# Path to the directory containing Gitaly client executables.
|
||||||
|
|
|
@ -34,6 +34,9 @@ def create_tokens
|
||||||
openid_connect_signing_key: generate_new_rsa_private_key
|
openid_connect_signing_key: generate_new_rsa_private_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# encrypted_settings_key_base is optional for now
|
||||||
|
defaults[:encrypted_settings_key_base] = generate_new_secure_token if ENV['GITLAB_GENERATE_ENCRYPTED_SETTINGS_KEY_BASE']
|
||||||
|
|
||||||
missing_secrets = set_missing_keys(defaults)
|
missing_secrets = set_missing_keys(defaults)
|
||||||
write_secrets_yml(missing_secrets) unless missing_secrets.empty?
|
write_secrets_yml(missing_secrets) unless missing_secrets.empty?
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,13 @@ require_relative '../object_store_settings'
|
||||||
require_relative '../smime_signature_settings'
|
require_relative '../smime_signature_settings'
|
||||||
|
|
||||||
# Default settings
|
# Default settings
|
||||||
|
Settings['shared'] ||= Settingslogic.new({})
|
||||||
|
Settings.shared['path'] = Settings.absolute(Settings.shared['path'] || "shared")
|
||||||
|
|
||||||
|
Settings['encrypted_settings'] ||= Settingslogic.new({})
|
||||||
|
Settings.encrypted_settings['path'] ||= File.join(Settings.shared['path'], "encrypted_settings")
|
||||||
|
Settings.encrypted_settings['path'] = Settings.absolute(Settings.encrypted_settings['path'])
|
||||||
|
|
||||||
Settings['ldap'] ||= Settingslogic.new({})
|
Settings['ldap'] ||= Settingslogic.new({})
|
||||||
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
|
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
|
||||||
Settings.ldap['prevent_ldap_sign_in'] = false if Settings.ldap['prevent_ldap_sign_in'].blank?
|
Settings.ldap['prevent_ldap_sign_in'] = false if Settings.ldap['prevent_ldap_sign_in'].blank?
|
||||||
|
@ -140,9 +147,6 @@ if Gitlab.ee? && Rails.env.test? && !saml_provider_enabled
|
||||||
Settings.omniauth.providers << Settingslogic.new({ 'name' => 'group_saml' })
|
Settings.omniauth.providers << Settingslogic.new({ 'name' => 'group_saml' })
|
||||||
end
|
end
|
||||||
|
|
||||||
Settings['shared'] ||= Settingslogic.new({})
|
|
||||||
Settings.shared['path'] = Settings.absolute(Settings.shared['path'] || "shared")
|
|
||||||
|
|
||||||
Settings['issues_tracker'] ||= {}
|
Settings['issues_tracker'] ||= {}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -152,6 +152,14 @@ class Settings < Settingslogic
|
||||||
Gitlab::Application.secrets.db_key_base
|
Gitlab::Application.secrets.db_key_base
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def encrypted(path)
|
||||||
|
Gitlab::EncryptedConfiguration.new(
|
||||||
|
content_path: path,
|
||||||
|
base_key: Gitlab::Application.secrets.encrypted_settings_key_base,
|
||||||
|
previous_keys: Gitlab::Application.secrets.rotated_encrypted_settings_key_base || []
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def load_dynamic_cron_schedules!
|
def load_dynamic_cron_schedules!
|
||||||
cron_jobs['gitlab_usage_ping_worker']['cron'] ||= cron_for_usage_ping
|
cron_jobs['gitlab_usage_ping_worker']['cron'] ||= cron_for_usage_ping
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,7 @@ This page is a development guide for application secrets.
|
||||||
| `otp_key_base` | The base key for One Time Passwords, described in [User management](../raketasks/user_management.md#rotate-two-factor-authentication-encryption-key) |
|
| `otp_key_base` | The base key for One Time Passwords, described in [User management](../raketasks/user_management.md#rotate-two-factor-authentication-encryption-key) |
|
||||||
|`db_key_base` | The base key to encrypt the data for `attr_encrypted` columns |
|
|`db_key_base` | The base key to encrypt the data for `attr_encrypted` columns |
|
||||||
|`openid_connect_signing_key` | The singing key for OpenID Connect |
|
|`openid_connect_signing_key` | The singing key for OpenID Connect |
|
||||||
|
| `encrypted_settings_key_base` | The base key to encrypt settings files with |
|
||||||
|
|
||||||
## Where the secrets are stored
|
## Where the secrets are stored
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@ description: 'Writing styles, markup, formatting, and other standards for GitLab
|
||||||
|
|
||||||
# Documentation Style Guide
|
# Documentation Style Guide
|
||||||
|
|
||||||
This document defines the standards for GitLab's documentation content and
|
This document defines the standards for GitLab documentation.
|
||||||
files.
|
|
||||||
|
|
||||||
For broader information about the documentation, see the [Documentation guidelines](../index.md).
|
For broader information about the documentation, see the [Documentation guidelines](../index.md).
|
||||||
|
|
||||||
|
@ -242,10 +241,10 @@ to update.
|
||||||
Put files for a specific product area into the related folder:
|
Put files for a specific product area into the related folder:
|
||||||
|
|
||||||
| Directory | What belongs here |
|
| Directory | What belongs here |
|
||||||
|:----------------------|:----------------------------------------------------------------------------------------------------------------------------------------|
|
|:----------------------|:------------------|
|
||||||
| `doc/user/` | User related documentation. Anything that can be done within the GitLab user interface goes here, including usage of the `/admin` interface. |
|
| `doc/user/` | User related documentation. Anything that can be done within the GitLab user interface goes here, including usage of the `/admin` interface. |
|
||||||
| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed by using GitLab's interface exist under `doc/user/admin_area/`. |
|
| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. Administrator settings in the GitLab user interface are under `doc/user/admin_area/`. |
|
||||||
| `doc/api/` | API related documentation. |
|
| `doc/api/` | API-related documentation. |
|
||||||
| `doc/development/` | Documentation related to the development of GitLab, whether contributing code or documentation. Related process and style guides should go here. |
|
| `doc/development/` | Documentation related to the development of GitLab, whether contributing code or documentation. Related process and style guides should go here. |
|
||||||
| `doc/legal/` | Legal documents about contributing to GitLab. |
|
| `doc/legal/` | Legal documents about contributing to GitLab. |
|
||||||
| `doc/install/` | Contains instructions for installing GitLab. |
|
| `doc/install/` | Contains instructions for installing GitLab. |
|
||||||
|
@ -277,10 +276,10 @@ Refer to the following items when working with directories and files:
|
||||||
Every page you would navigate under `/profile` should have its own document,
|
Every page you would navigate under `/profile` should have its own document,
|
||||||
for example, `account.md`, `applications.md`, or `emails.md`.
|
for example, `account.md`, `applications.md`, or `emails.md`.
|
||||||
- `doc/user/dashboard/` should contain all dashboard related documentation.
|
- `doc/user/dashboard/` should contain all dashboard related documentation.
|
||||||
- `doc/user/admin_area/` should contain all admin related documentation
|
- `doc/user/admin_area/` should contain all administrator-related
|
||||||
describing what can be achieved by accessing GitLab's admin interface
|
documentation describing what can be achieved by accessing the GitLab
|
||||||
(_not to be confused with `doc/administration` where server access is
|
administrator interface (not to be confused with `doc/administration` where
|
||||||
required_).
|
server access is required).
|
||||||
- Every category under `/admin/application_settings/` should have its
|
- Every category under `/admin/application_settings/` should have its
|
||||||
own document located at `doc/user/admin_area/settings/`. For example,
|
own document located at `doc/user/admin_area/settings/`. For example,
|
||||||
the **Visibility and Access Controls** category should have a document
|
the **Visibility and Access Controls** category should have a document
|
||||||
|
@ -567,9 +566,9 @@ tenses, words, and phrases:
|
||||||
- Avoid using the word *currently* when talking about the product or its
|
- Avoid using the word *currently* when talking about the product or its
|
||||||
features. The documentation describes the product as it is, and not as it
|
features. The documentation describes the product as it is, and not as it
|
||||||
will be at some indeterminate point in the future.
|
will be at some indeterminate point in the future.
|
||||||
- Avoid the using the word *scalability* with increasing GitLab's performance
|
- Avoid using the word scalability when talking about increasing GitLab
|
||||||
for additional users. Using the words *scale* or *scaling* in other cases is
|
performance for additional users. The words scale or scaling are sometimes
|
||||||
acceptable, but references to increasing GitLab's performance for additional
|
acceptable, but references to increasing GitLab performance for additional
|
||||||
users should direct readers to the GitLab
|
users should direct readers to the GitLab
|
||||||
[reference architectures](../../../administration/reference_architectures/index.md)
|
[reference architectures](../../../administration/reference_architectures/index.md)
|
||||||
page.
|
page.
|
||||||
|
@ -577,8 +576,8 @@ tenses, words, and phrases:
|
||||||
direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md)
|
direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md)
|
||||||
for information about configuring GitLab to have the performance needed for
|
for information about configuring GitLab to have the performance needed for
|
||||||
additional users over time.
|
additional users over time.
|
||||||
- Don't use profanity or obscenities. Doing so may negatively affect other
|
- Don't use profanity or obscenities. Doing so may negatively affect other users
|
||||||
users and contributors, which is contrary to GitLab's value of
|
and contributors, which is contrary to the GitLab value of
|
||||||
[Diversity, Inclusion, and Belonging](https://about.gitlab.com/handbook/values/#diversity-inclusion).
|
[Diversity, Inclusion, and Belonging](https://about.gitlab.com/handbook/values/#diversity-inclusion).
|
||||||
- Avoid the use of [racially-insensitive terminology or phrases](https://www.marketplace.org/2020/06/17/tech-companies-update-language-to-avoid-offensive-terms/). For example:
|
- Avoid the use of [racially-insensitive terminology or phrases](https://www.marketplace.org/2020/06/17/tech-companies-update-language-to-avoid-offensive-terms/). For example:
|
||||||
- Use *primary* and *secondary* for database and server relationships.
|
- Use *primary* and *secondary* for database and server relationships.
|
||||||
|
@ -982,9 +981,9 @@ Important:
|
||||||
tutorials, presentations, StackOverflow posts, and other sources.
|
tutorials, presentations, StackOverflow posts, and other sources.
|
||||||
- Do not link to `h1` headings.
|
- Do not link to `h1` headings.
|
||||||
|
|
||||||
Note that, with Kramdown, it is possible to add a custom ID to an HTML element
|
Note that with Kramdown, it's possible to add a custom ID to an HTML element
|
||||||
with Markdown markup, but they _do not_ work in GitLab's `/help`. Therefore,
|
with Markdown markup, but they don't work in `/help`. Because of this, don't use
|
||||||
do not use this option until further notice.
|
this option.
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
|
@ -1268,7 +1267,7 @@ request.
|
||||||
|
|
||||||
## Videos
|
## Videos
|
||||||
|
|
||||||
Adding GitLab's existing YouTube video tutorials to the documentation is highly
|
Adding GitLab YouTube video tutorials to the documentation is highly
|
||||||
encouraged, unless the video is outdated. Videos should not replace
|
encouraged, unless the video is outdated. Videos should not replace
|
||||||
documentation, but complement or illustrate it. If content in a video is
|
documentation, but complement or illustrate it. If content in a video is
|
||||||
fundamental to a feature and its key use cases, but this is not adequately
|
fundamental to a feature and its key use cases, but this is not adequately
|
||||||
|
@ -1297,7 +1296,7 @@ You can link any up-to-date video that's useful to the GitLab user.
|
||||||
The [GitLab documentation site](https://docs.gitlab.com) supports embedded
|
The [GitLab documentation site](https://docs.gitlab.com) supports embedded
|
||||||
videos.
|
videos.
|
||||||
|
|
||||||
You can only embed videos from [GitLab's official YouTube account](https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg).
|
You can embed videos from [the official YouTube account for GitLab](https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg) only.
|
||||||
For videos from other sources, [link](#link-to-video) them instead.
|
For videos from other sources, [link](#link-to-video) them instead.
|
||||||
|
|
||||||
In most cases, it is better to [link to video](#link-to-video) instead, because
|
In most cases, it is better to [link to video](#link-to-video) instead, because
|
||||||
|
@ -1341,9 +1340,9 @@ This is how it renders on the GitLab documentation site:
|
||||||
> - The `figure` tag is required for semantic SEO and the `video_container`
|
> - The `figure` tag is required for semantic SEO and the `video_container`
|
||||||
class is necessary to make sure the video is responsive and displays on
|
class is necessary to make sure the video is responsive and displays on
|
||||||
different mobile devices.
|
different mobile devices.
|
||||||
> - The `<div class="video-fallback">` is a fallback necessary for GitLab's
|
> - The `<div class="video-fallback">` is a fallback necessary for
|
||||||
`/help`, as GitLab's Markdown processor does not support iframes. It's hidden on
|
`/help`, because the GitLab Markdown processor doesn't support iframes. It's
|
||||||
the documentation site, but will be displayed on GitLab's `/help`.
|
hidden on the documentation site, but is displayed by `/help`.
|
||||||
|
|
||||||
## Code blocks
|
## Code blocks
|
||||||
|
|
||||||
|
@ -1659,8 +1658,8 @@ elements:
|
||||||
To help users be aware of recent product improvements or additions, we add
|
To help users be aware of recent product improvements or additions, we add
|
||||||
GitLab version information to our documentation.
|
GitLab version information to our documentation.
|
||||||
|
|
||||||
The GitLab Technical Writing team determines which versions of GitLab's
|
The GitLab Technical Writing team determines which versions of
|
||||||
documentation to display on this site based on GitLab's
|
documentation to display on this site based on the GitLab
|
||||||
[Statement of Support](https://about.gitlab.com/support/statement-of-support.html#we-support-the-current-major-version-and-the-two-previous-major-versions).
|
[Statement of Support](https://about.gitlab.com/support/statement-of-support.html#we-support-the-current-major-version-and-the-two-previous-major-versions).
|
||||||
|
|
||||||
### View older GitLab documentation versions
|
### View older GitLab documentation versions
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
|
@ -433,7 +433,13 @@ To remove a list from an issue board:
|
||||||
1. Select **Remove list**. A confirmation dialog appears.
|
1. Select **Remove list**. A confirmation dialog appears.
|
||||||
1. Select **OK**.
|
1. Select **OK**.
|
||||||
|
|
||||||
### Add issues to a list
|
### Add issues to a list **(CORE ONLY)**
|
||||||
|
|
||||||
|
> - Feature flag [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47898) in GitLab 13.7.
|
||||||
|
> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default.
|
||||||
|
> - It's disabled on GitLab.com.
|
||||||
|
> - It's recommended for production use.
|
||||||
|
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-adding-issues-to-the-list). **(CORE ONLY)**
|
||||||
|
|
||||||
You can add issues to a list in a project issue board by clicking the **Add issues** button
|
You can add issues to a list in a project issue board by clicking the **Add issues** button
|
||||||
in the top right corner of the issue board. This opens up a modal
|
in the top right corner of the issue board. This opens up a modal
|
||||||
|
@ -453,7 +459,23 @@ the list by filtering by the following:
|
||||||
- Release
|
- Release
|
||||||
- Weight
|
- Weight
|
||||||
|
|
||||||
![Bulk adding issues to lists](img/issue_boards_add_issues_modal_v13_6.png)
|
#### Enable or disable adding issues to the list **(CORE ONLY)**
|
||||||
|
|
||||||
|
Adding issues to the list is deployed behind a feature flag that is **disabled by default**.
|
||||||
|
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||||
|
can enable it.
|
||||||
|
|
||||||
|
To enable it:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Feature.enable(:add_issues_button)
|
||||||
|
```
|
||||||
|
|
||||||
|
To disable it:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Feature.disable(:add_issues_button)
|
||||||
|
```
|
||||||
|
|
||||||
### Remove an issue from a list
|
### Remove an issue from a list
|
||||||
|
|
||||||
|
|
|
@ -254,18 +254,18 @@ NOTE: **Note:**
|
||||||
Although the Code Climate spec supports more properties, those are ignored by
|
Although the Code Climate spec supports more properties, those are ignored by
|
||||||
GitLab.
|
GitLab.
|
||||||
|
|
||||||
## Code Quality reports **(STARTER)**
|
## Code Quality reports
|
||||||
|
|
||||||
Once the Code Quality job has completed:
|
After the Code Quality job completes:
|
||||||
|
|
||||||
- The full list of code quality violations generated by a pipeline is shown in the
|
|
||||||
Code Quality tab of the Pipeline Details page.
|
|
||||||
- Potential changes to code quality are shown directly in the merge request.
|
- Potential changes to code quality are shown directly in the merge request.
|
||||||
The Code Quality widget in the merge request compares the reports from the base and head of the branch,
|
The Code Quality widget in the merge request compares the reports from the base and head of the branch,
|
||||||
then lists any violations that are resolved or created when the branch is merged.
|
then lists any violations that are resolved or created when the branch is merged.
|
||||||
- The full JSON report is available as a
|
- The full JSON report is available as a
|
||||||
[downloadable artifact](../../../ci/pipelines/job_artifacts.md#downloading-artifacts)
|
[downloadable artifact](../../../ci/pipelines/job_artifacts.md#downloading-artifacts)
|
||||||
for the `code_quality` job.
|
for the `code_quality` job.
|
||||||
|
- The full list of code quality violations generated by a pipeline is shown in the
|
||||||
|
Code Quality tab of the Pipeline Details page. **(STARTER)**
|
||||||
|
|
||||||
### Generating an HTML report
|
### Generating an HTML report
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,16 @@ To create a new release through the GitLab UI:
|
||||||
[release notes](#release-notes-description), or [assets links](#links).
|
[release notes](#release-notes-description), or [assets links](#links).
|
||||||
1. Click **Create release**.
|
1. Click **Create release**.
|
||||||
|
|
||||||
|
### Create release from GitLab CI
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19298) in GitLab 12.7.
|
||||||
|
|
||||||
|
You can [create a release directly from the GitLab CI pipeline](../../../ci/yaml/README.md#release)
|
||||||
|
by using a `release` node in the job definition.
|
||||||
|
|
||||||
|
The release is created only if the job processes without error. If the Rails API returns an error
|
||||||
|
during release creation, the release job fails.
|
||||||
|
|
||||||
### Schedule a future release
|
### Schedule a future release
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/38105) in GitLab 12.1.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/38105) in GitLab 12.1.
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
class EncryptedConfiguration
|
||||||
|
delegate :[], :fetch, to: :config
|
||||||
|
delegate_missing_to :options
|
||||||
|
attr_reader :content_path, :key, :previous_keys
|
||||||
|
|
||||||
|
CIPHER = "aes-256-gcm"
|
||||||
|
SALT = "GitLabEncryptedConfigSalt"
|
||||||
|
|
||||||
|
class MissingKeyError < RuntimeError
|
||||||
|
def initialize(msg = "Missing encryption key to encrypt/decrypt file with.")
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class InvalidConfigError < RuntimeError
|
||||||
|
def initialize(msg = "Content was not a valid yml config file")
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.generate_key(base_key)
|
||||||
|
# Because the salt is static, we want uniqueness to be coming from the base_key
|
||||||
|
# Error if the base_key is empty or suspiciously short
|
||||||
|
raise 'Base key too small' if base_key.blank? || base_key.length < 16
|
||||||
|
|
||||||
|
ActiveSupport::KeyGenerator.new(base_key).generate_key(SALT, ActiveSupport::MessageEncryptor.key_len(CIPHER))
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(content_path: nil, base_key: nil, previous_keys: [])
|
||||||
|
@content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path } if content_path
|
||||||
|
@key = self.class.generate_key(base_key) if base_key
|
||||||
|
@previous_keys = previous_keys
|
||||||
|
end
|
||||||
|
|
||||||
|
def active?
|
||||||
|
content_path&.exist?
|
||||||
|
end
|
||||||
|
|
||||||
|
def read
|
||||||
|
if active?
|
||||||
|
decrypt(content_path.binread)
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(contents)
|
||||||
|
# ensure contents are valid to deserialize before write
|
||||||
|
deserialize(contents)
|
||||||
|
|
||||||
|
temp_file = Tempfile.new(File.basename(content_path), File.dirname(content_path))
|
||||||
|
File.open(temp_file.path, 'wb') do |file|
|
||||||
|
file.write(encrypt(contents))
|
||||||
|
end
|
||||||
|
FileUtils.mv(temp_file.path, content_path)
|
||||||
|
ensure
|
||||||
|
temp_file&.unlink
|
||||||
|
end
|
||||||
|
|
||||||
|
def config
|
||||||
|
return @config if @config
|
||||||
|
|
||||||
|
contents = deserialize(read)
|
||||||
|
|
||||||
|
raise InvalidConfigError.new unless contents.is_a?(Hash)
|
||||||
|
|
||||||
|
@config = contents.deep_symbolize_keys
|
||||||
|
end
|
||||||
|
|
||||||
|
def change(&block)
|
||||||
|
writing(read, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def writing(contents)
|
||||||
|
updated_contents = yield contents
|
||||||
|
|
||||||
|
write(updated_contents) if updated_contents != contents
|
||||||
|
end
|
||||||
|
|
||||||
|
def encrypt(contents)
|
||||||
|
handle_missing_key!
|
||||||
|
encryptor.encrypt_and_sign(contents)
|
||||||
|
end
|
||||||
|
|
||||||
|
def decrypt(contents)
|
||||||
|
handle_missing_key!
|
||||||
|
encryptor.decrypt_and_verify(contents)
|
||||||
|
end
|
||||||
|
|
||||||
|
def encryptor
|
||||||
|
return @encryptor if @encryptor
|
||||||
|
|
||||||
|
@encryptor = ActiveSupport::MessageEncryptor.new(key, cipher: CIPHER)
|
||||||
|
|
||||||
|
# Allow fallback to previous keys
|
||||||
|
@previous_keys.each do |key|
|
||||||
|
@encryptor.rotate(self.class.generate_key(key))
|
||||||
|
end
|
||||||
|
|
||||||
|
@encryptor
|
||||||
|
end
|
||||||
|
|
||||||
|
def options
|
||||||
|
# Allows top level keys to be referenced using dot syntax
|
||||||
|
@options ||= ActiveSupport::InheritableOptions.new(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize(contents)
|
||||||
|
YAML.safe_load(contents, permitted_classes: [Symbol]).presence || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_missing_key!
|
||||||
|
raise MissingKeyError.new if @key.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,8 +6,8 @@ namespace :gitlab do
|
||||||
|
|
||||||
result = User.where(id: group.direct_and_indirect_users_with_inactive.select(:id)).update_all(projects_limit: 0, can_create_group: false)
|
result = User.where(id: group.direct_and_indirect_users_with_inactive.select(:id)).update_all(projects_limit: 0, can_create_group: false)
|
||||||
ids_count = group.direct_and_indirect_users_with_inactive.count
|
ids_count = group.direct_and_indirect_users_with_inactive.count
|
||||||
puts "Done".green if result == ids_count
|
puts "Done".color(:green) if result == ids_count
|
||||||
puts "Something went wrong".red if result != ids_count
|
puts "Something went wrong".color(:red) if result != ids_count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -896,9 +896,6 @@ msgstr ""
|
||||||
msgid "< 1 hour"
|
msgid "< 1 hour"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "<no scopes selected>"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "'%{data}' at %{location} does not match format: %{format}"
|
msgid "'%{data}' at %{location} does not match format: %{format}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -32767,6 +32764,9 @@ msgstr ""
|
||||||
msgid "no one can merge"
|
msgid "no one can merge"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "no scopes selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "none"
|
msgid "none"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
"@babel/preset-env": "^7.10.1",
|
"@babel/preset-env": "^7.10.1",
|
||||||
"@gitlab/at.js": "1.5.5",
|
"@gitlab/at.js": "1.5.5",
|
||||||
"@gitlab/svgs": "1.175.0",
|
"@gitlab/svgs": "1.175.0",
|
||||||
"@gitlab/ui": "24.3.0",
|
"@gitlab/ui": "24.3.1",
|
||||||
"@gitlab/visual-review-tools": "1.6.1",
|
"@gitlab/visual-review-tools": "1.6.1",
|
||||||
"@rails/actioncable": "^6.0.3-3",
|
"@rails/actioncable": "^6.0.3-3",
|
||||||
"@rails/ujs": "^6.0.3-2",
|
"@rails/ujs": "^6.0.3-2",
|
||||||
|
|
|
@ -134,4 +134,20 @@ RSpec.describe Settings do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.encrypted' do
|
||||||
|
before do
|
||||||
|
allow(Gitlab::Application.secrets).to receive(:encryped_settings_key_base).and_return(SecureRandom.hex(64))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'defaults to using the encrypted_settings_key_base for the key' do
|
||||||
|
expect(Gitlab::EncryptedConfiguration).to receive(:new).with(hash_including(base_key: Gitlab::Application.secrets.encrypted_settings_key_base))
|
||||||
|
Settings.encrypted('tmp/tests/test.enc')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns empty encrypted config when a key has not been set' do
|
||||||
|
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
|
||||||
|
expect(Settings.encrypted('tmp/tests/test.enc').read).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,6 +37,10 @@ RSpec.describe 'Issue Boards add issue modal', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'modal interaction' do
|
context 'modal interaction' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(add_issues_button: true)
|
||||||
|
end
|
||||||
|
|
||||||
it 'opens modal' do
|
it 'opens modal' do
|
||||||
click_button('Add issues')
|
click_button('Add issues')
|
||||||
|
|
||||||
|
@ -72,6 +76,7 @@ RSpec.describe 'Issue Boards add issue modal', :js do
|
||||||
|
|
||||||
context 'issues list' do
|
context 'issues list' do
|
||||||
before do
|
before do
|
||||||
|
stub_feature_flags(add_issues_button: true)
|
||||||
click_button('Add issues')
|
click_button('Add issues')
|
||||||
|
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
|
|
|
@ -14,20 +14,57 @@ RSpec.describe 'Service Desk Setting', :js do
|
||||||
allow_any_instance_of(Project).to receive(:present).with(current_user: user).and_return(presenter)
|
allow_any_instance_of(Project).to receive(:present).with(current_user: user).and_return(presenter)
|
||||||
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
|
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
|
||||||
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
|
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows activation checkbox' do
|
||||||
|
visit edit_project_path(project)
|
||||||
|
|
||||||
|
expect(page).to have_selector("#service-desk-checkbox")
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when service_desk_email is disabled' do
|
||||||
|
before do
|
||||||
|
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(false)
|
||||||
|
|
||||||
visit edit_project_path(project)
|
visit edit_project_path(project)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows activation checkbox' do
|
it 'shows incoming email but not project name suffix after activating' do
|
||||||
expect(page).to have_selector("#service-desk-checkbox")
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'shows incoming email after activating' do
|
|
||||||
find("#service-desk-checkbox").click
|
find("#service-desk-checkbox").click
|
||||||
|
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
|
|
||||||
project.reload
|
project.reload
|
||||||
expect(project.service_desk_enabled).to be_truthy
|
expect(project.service_desk_enabled).to be_truthy
|
||||||
expect(project.service_desk_address).to be_present
|
expect(project.service_desk_address).to be_present
|
||||||
expect(find('[data-testid="incoming-email"]').value).to eq(project.service_desk_address)
|
expect(find('[data-testid="incoming-email"]').value).to eq(project.service_desk_incoming_address)
|
||||||
|
expect(page).not_to have_selector('#service-desk-project-suffix')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when service_desk_email is enabled' do
|
||||||
|
before do
|
||||||
|
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?) { true }
|
||||||
|
allow(::Gitlab::ServiceDeskEmail).to receive(:address_for_key) { 'address-suffix@example.com' }
|
||||||
|
|
||||||
|
visit edit_project_path(project)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows setting of custom address suffix' do
|
||||||
|
find("#service-desk-checkbox").click
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
project.reload
|
||||||
|
expect(find('[data-testid="incoming-email"]').value).to eq(project.service_desk_incoming_address)
|
||||||
|
|
||||||
|
page.within '#js-service-desk' do
|
||||||
|
fill_in('service-desk-project-suffix', with: 'foo')
|
||||||
|
click_button 'Save changes'
|
||||||
|
end
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
expect(find('[data-testid="incoming-email"]').value).to eq('address-suffix@example.com')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,7 +9,7 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
|
||||||
menu-class="dropdown-menu-large"
|
menu-class="dropdown-menu-large"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="btn btn-danger btn-md gl-button split-content-button btn-danger-secondary"
|
class="btn btn-danger btn-md gl-button split-content-button"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<!---->
|
<!---->
|
||||||
|
@ -27,7 +27,7 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
|
||||||
<button
|
<button
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
class="btn dropdown-toggle btn-danger btn-md gl-button gl-dropdown-toggle btn-danger-secondary dropdown-toggle-split"
|
class="btn dropdown-toggle btn-danger btn-md gl-button gl-dropdown-toggle dropdown-toggle-split"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
|
exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
|
||||||
<gl-dropdown-stub
|
<gl-dropdown-stub
|
||||||
category="tertiary"
|
category="primary"
|
||||||
headertext=""
|
headertext=""
|
||||||
issueiid=""
|
issueiid=""
|
||||||
projectpath=""
|
projectpath=""
|
||||||
|
@ -42,7 +42,7 @@ exports[`Design management design version dropdown component renders design vers
|
||||||
|
|
||||||
exports[`Design management design version dropdown component renders design version list 1`] = `
|
exports[`Design management design version dropdown component renders design version list 1`] = `
|
||||||
<gl-dropdown-stub
|
<gl-dropdown-stub
|
||||||
category="tertiary"
|
category="primary"
|
||||||
headertext=""
|
headertext=""
|
||||||
issueiid=""
|
issueiid=""
|
||||||
projectpath=""
|
projectpath=""
|
||||||
|
|
|
@ -47,7 +47,7 @@ exports[`Alert integration settings form default state should match the default
|
||||||
|
|
||||||
<gl-dropdown-stub
|
<gl-dropdown-stub
|
||||||
block="true"
|
block="true"
|
||||||
category="tertiary"
|
category="primary"
|
||||||
data-qa-selector="incident_templates_dropdown"
|
data-qa-selector="incident_templates_dropdown"
|
||||||
headertext=""
|
headertext=""
|
||||||
id="alert-integration-settings-issue-template"
|
id="alert-integration-settings-issue-template"
|
||||||
|
|
|
@ -86,7 +86,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] =
|
||||||
<button
|
<button
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary"
|
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<!---->
|
<!---->
|
||||||
|
@ -201,7 +201,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] =
|
||||||
<button
|
<button
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary"
|
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<!---->
|
<!---->
|
||||||
|
|
|
@ -33,7 +33,7 @@ exports[`Dashboard template matches the default snapshot 1`] = `
|
||||||
class="mb-2 pr-2 d-flex d-sm-block"
|
class="mb-2 pr-2 d-flex d-sm-block"
|
||||||
>
|
>
|
||||||
<gl-dropdown-stub
|
<gl-dropdown-stub
|
||||||
category="tertiary"
|
category="primary"
|
||||||
class="flex-grow-1"
|
class="flex-grow-1"
|
||||||
data-qa-selector="environments_dropdown"
|
data-qa-selector="environments_dropdown"
|
||||||
headertext=""
|
headertext=""
|
||||||
|
|
|
@ -10,7 +10,7 @@ exports[`Code Coverage when fetching data is successful matches the snapshot 1`]
|
||||||
<!---->
|
<!---->
|
||||||
|
|
||||||
<gl-dropdown-stub
|
<gl-dropdown-stub
|
||||||
category="tertiary"
|
category="primary"
|
||||||
headertext=""
|
headertext=""
|
||||||
size="medium"
|
size="medium"
|
||||||
text="rspec"
|
text="rspec"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
exports[`SplitButton renders actionItems 1`] = `
|
exports[`SplitButton renders actionItems 1`] = `
|
||||||
<gl-dropdown-stub
|
<gl-dropdown-stub
|
||||||
category="tertiary"
|
category="primary"
|
||||||
headertext=""
|
headertext=""
|
||||||
menu-class=""
|
menu-class=""
|
||||||
size="medium"
|
size="medium"
|
||||||
|
|
|
@ -24,7 +24,7 @@ RSpec.describe 'create_tokens' do
|
||||||
|
|
||||||
describe 'ensure acknowledged secrets in any installations' do
|
describe 'ensure acknowledged secrets in any installations' do
|
||||||
let(:acknowledged_secrets) do
|
let(:acknowledged_secrets) do
|
||||||
%w[secret_key_base otp_key_base db_key_base openid_connect_signing_key]
|
%w[secret_key_base otp_key_base db_key_base openid_connect_signing_key encrypted_settings_key_base rotated_encrypted_settings_key_base]
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not allow to add a new secret without a proper handling' do
|
it 'does not allow to add a new secret without a proper handling' do
|
||||||
|
@ -90,6 +90,7 @@ RSpec.describe 'create_tokens' do
|
||||||
expect(new_secrets['otp_key_base']).to eq(secrets.otp_key_base)
|
expect(new_secrets['otp_key_base']).to eq(secrets.otp_key_base)
|
||||||
expect(new_secrets['db_key_base']).to eq(secrets.db_key_base)
|
expect(new_secrets['db_key_base']).to eq(secrets.db_key_base)
|
||||||
expect(new_secrets['openid_connect_signing_key']).to eq(secrets.openid_connect_signing_key)
|
expect(new_secrets['openid_connect_signing_key']).to eq(secrets.openid_connect_signing_key)
|
||||||
|
expect(new_secrets['encrypted_settings_key_base']).to eq(secrets.encrypted_settings_key_base)
|
||||||
end
|
end
|
||||||
|
|
||||||
create_tokens
|
create_tokens
|
||||||
|
@ -106,6 +107,7 @@ RSpec.describe 'create_tokens' do
|
||||||
before do
|
before do
|
||||||
secrets.db_key_base = 'db_key_base'
|
secrets.db_key_base = 'db_key_base'
|
||||||
secrets.openid_connect_signing_key = 'openid_connect_signing_key'
|
secrets.openid_connect_signing_key = 'openid_connect_signing_key'
|
||||||
|
secrets.encrypted_settings_key_base = 'encrypted_settings_key_base'
|
||||||
|
|
||||||
allow(File).to receive(:exist?).with('.secret').and_return(true)
|
allow(File).to receive(:exist?).with('.secret').and_return(true)
|
||||||
stub_file_read('.secret', content: 'file_key')
|
stub_file_read('.secret', content: 'file_key')
|
||||||
|
@ -158,6 +160,7 @@ RSpec.describe 'create_tokens' do
|
||||||
expect(secrets.otp_key_base).to eq('otp_key_base')
|
expect(secrets.otp_key_base).to eq('otp_key_base')
|
||||||
expect(secrets.db_key_base).to eq('db_key_base')
|
expect(secrets.db_key_base).to eq('db_key_base')
|
||||||
expect(secrets.openid_connect_signing_key).to eq('openid_connect_signing_key')
|
expect(secrets.openid_connect_signing_key).to eq('openid_connect_signing_key')
|
||||||
|
expect(secrets.encrypted_settings_key_base).to eq('encrypted_settings_key_base')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'deletes the .secret file' do
|
it 'deletes the .secret file' do
|
||||||
|
@ -208,12 +211,34 @@ RSpec.describe 'create_tokens' do
|
||||||
create_tokens
|
create_tokens
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when rotated_encrypted_settings_key_base does not exist' do
|
||||||
|
before do
|
||||||
|
secrets.secret_key_base = 'secret_key_base'
|
||||||
|
secrets.otp_key_base = 'otp_key_base'
|
||||||
|
secrets.openid_connect_signing_key = 'openid_connect_signing_key'
|
||||||
|
secrets.encrypted_settings_key_base = 'encrypted_settings_key_base'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not warn about the missing secrets' do
|
||||||
|
expect(self).not_to receive(:warn_missing_secret).with('rotated_encrypted_settings_key_base')
|
||||||
|
|
||||||
|
create_tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not update secrets.yml' do
|
||||||
|
expect(File).not_to receive(:write)
|
||||||
|
|
||||||
|
create_tokens
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when db_key_base is blank but exists in secrets.yml' do
|
context 'when db_key_base is blank but exists in secrets.yml' do
|
||||||
before do
|
before do
|
||||||
secrets.otp_key_base = 'otp_key_base'
|
secrets.otp_key_base = 'otp_key_base'
|
||||||
secrets.secret_key_base = 'secret_key_base'
|
secrets.secret_key_base = 'secret_key_base'
|
||||||
|
secrets.encrypted_settings_key_base = 'encrypted_settings_key_base'
|
||||||
yaml_secrets = secrets.to_h.stringify_keys.merge('db_key_base' => '<%= an_erb_expression %>')
|
yaml_secrets = secrets.to_h.stringify_keys.merge('db_key_base' => '<%= an_erb_expression %>')
|
||||||
|
|
||||||
allow(File).to receive(:exist?).with('.secret').and_return(false)
|
allow(File).to receive(:exist?).with('.secret').and_return(false)
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "spec_helper"
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::EncryptedConfiguration do
|
||||||
|
subject(:configuration) { described_class.new }
|
||||||
|
|
||||||
|
let!(:config_tmp_dir) { Dir.mktmpdir('config-') }
|
||||||
|
|
||||||
|
after do
|
||||||
|
FileUtils.rm_f(config_tmp_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#initialize' do
|
||||||
|
it 'accepts all args as optional fields' do
|
||||||
|
expect { configuration }.not_to raise_exception
|
||||||
|
|
||||||
|
expect(configuration.key).to be_nil
|
||||||
|
expect(configuration.previous_keys).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'generates 32 byte key when provided a larger base key' do
|
||||||
|
configuration = described_class.new(base_key: 'A' * 64)
|
||||||
|
|
||||||
|
expect(configuration.key.bytesize).to eq 32
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'generates 32 byte key when provided a smaller base key' do
|
||||||
|
configuration = described_class.new(base_key: 'A' * 16)
|
||||||
|
|
||||||
|
expect(configuration.key.bytesize).to eq 32
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'throws an error when the base key is too small' do
|
||||||
|
expect { described_class.new(base_key: 'A' * 12) }.to raise_error 'Base key too small'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when provided a config file but no key' do
|
||||||
|
let(:config_path) { File.join(config_tmp_dir, 'credentials.yml.enc') }
|
||||||
|
|
||||||
|
it 'throws an error when writing without a key' do
|
||||||
|
expect { described_class.new(content_path: config_path).write('test') }.to raise_error Gitlab::EncryptedConfiguration::MissingKeyError
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'throws an error when reading without a key' do
|
||||||
|
config = described_class.new(content_path: config_path)
|
||||||
|
File.write(config_path, 'test')
|
||||||
|
expect { config.read }.to raise_error Gitlab::EncryptedConfiguration::MissingKeyError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when provided key and config file' do
|
||||||
|
let(:credentials_config_path) { File.join(config_tmp_dir, 'credentials.yml.enc') }
|
||||||
|
let(:credentials_key) { SecureRandom.hex(64) }
|
||||||
|
|
||||||
|
describe '#write' do
|
||||||
|
it 'encrypts the file using the provided key' do
|
||||||
|
encryptor = ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(credentials_key), cipher: 'aes-256-gcm')
|
||||||
|
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
|
||||||
|
|
||||||
|
config.write('sample-content')
|
||||||
|
expect(encryptor.decrypt_and_verify(File.read(credentials_config_path))).to eq('sample-content')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#read' do
|
||||||
|
it 'reads yaml configuration' do
|
||||||
|
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
|
||||||
|
|
||||||
|
config.write({ foo: { bar: true } }.to_yaml)
|
||||||
|
expect(config[:foo][:bar]).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows referencing top level keys via dot syntax' do
|
||||||
|
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
|
||||||
|
|
||||||
|
config.write({ foo: { bar: true } }.to_yaml)
|
||||||
|
expect(config.foo[:bar]).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'throws a custom error when referencing an invalid key map config' do
|
||||||
|
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
|
||||||
|
|
||||||
|
config.write("stringcontent")
|
||||||
|
expect { config[:foo] }.to raise_error Gitlab::EncryptedConfiguration::InvalidConfigError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#change' do
|
||||||
|
it 'changes yaml configuration' do
|
||||||
|
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
|
||||||
|
|
||||||
|
config.write({ foo: { bar: true } }.to_yaml)
|
||||||
|
config.change do |unencrypted_contents|
|
||||||
|
contents = YAML.safe_load(unencrypted_contents, permitted_classes: [Symbol])
|
||||||
|
contents.merge(beef: "stew").to_yaml
|
||||||
|
end
|
||||||
|
expect(config.foo[:bar]).to be true
|
||||||
|
expect(config.beef).to eq('stew')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when provided previous_keys for rotation' do
|
||||||
|
let(:credential_key_original) { SecureRandom.hex(64) }
|
||||||
|
let(:credential_key_latest) { SecureRandom.hex(64) }
|
||||||
|
let(:config_path_original) { File.join(config_tmp_dir, 'credentials-orig.yml.enc') }
|
||||||
|
let(:config_path_latest) { File.join(config_tmp_dir, 'credentials-latest.yml.enc') }
|
||||||
|
|
||||||
|
def encryptor(key)
|
||||||
|
ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(key), cipher: 'aes-256-gcm')
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#write' do
|
||||||
|
it 'rotates the key when provided a new key' do
|
||||||
|
config1 = described_class.new(content_path: config_path_original, base_key: credential_key_original)
|
||||||
|
config1.write('sample-content1')
|
||||||
|
|
||||||
|
config2 = described_class.new(content_path: config_path_latest, base_key: credential_key_latest, previous_keys: [credential_key_original])
|
||||||
|
config2.write('sample-content2')
|
||||||
|
|
||||||
|
original_key_encryptor = encryptor(credential_key_original) # can read with the initial key
|
||||||
|
latest_key_encryptor = encryptor(credential_key_latest) # can read with the new key
|
||||||
|
both_key_encryptor = encryptor(credential_key_latest) # can read with either key
|
||||||
|
both_key_encryptor.rotate(Gitlab::EncryptedConfiguration.generate_key(credential_key_original))
|
||||||
|
|
||||||
|
expect(original_key_encryptor.decrypt_and_verify(File.read(config_path_original))).to eq('sample-content1')
|
||||||
|
expect(both_key_encryptor.decrypt_and_verify(File.read(config_path_original))).to eq('sample-content1')
|
||||||
|
expect(latest_key_encryptor.decrypt_and_verify(File.read(config_path_latest))).to eq('sample-content2')
|
||||||
|
expect(both_key_encryptor.decrypt_and_verify(File.read(config_path_latest))).to eq('sample-content2')
|
||||||
|
expect { original_key_encryptor.decrypt_and_verify(File.read(config_path_latest)) }.to raise_error(ActiveSupport::MessageEncryptor::InvalidMessage)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#read' do
|
||||||
|
it 'supports reading using rotated config' do
|
||||||
|
described_class.new(content_path: config_path_original, base_key: credential_key_original).write({ foo: { bar: true } }.to_yaml)
|
||||||
|
|
||||||
|
config = described_class.new(content_path: config_path_original, base_key: credential_key_latest, previous_keys: [credential_key_original])
|
||||||
|
expect(config[:foo][:bar]).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1535,6 +1535,42 @@ RSpec.describe Project, factory_default: :keep do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.service_desk_custom_address_enabled?' do
|
||||||
|
let_it_be(:project) { create(:project, service_desk_enabled: true) }
|
||||||
|
|
||||||
|
subject(:address_enabled) { project.service_desk_custom_address_enabled? }
|
||||||
|
|
||||||
|
context 'when service_desk_email is enabled' do
|
||||||
|
before do
|
||||||
|
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
expect(address_enabled).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when service_desk_custom_address flag is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(service_desk_custom_address: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(address_enabled).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when service_desk_email is disabled' do
|
||||||
|
before do
|
||||||
|
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false when service_desk_email is disabled' do
|
||||||
|
expect(address_enabled).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '.find_by_service_desk_project_key' do
|
describe '.find_by_service_desk_project_key' do
|
||||||
it 'returns the correct project' do
|
it 'returns the correct project' do
|
||||||
project1 = create(:project)
|
project1 = create(:project)
|
||||||
|
|
|
@ -866,10 +866,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.175.0.tgz#734f341784af1cd1d62d160a17bcdfb61ff7b04d"
|
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.175.0.tgz#734f341784af1cd1d62d160a17bcdfb61ff7b04d"
|
||||||
integrity sha512-gXpc87TGSXIzfAr4QER1Qw1v3P47pBO6BXkma52blgwXVmcFNe3nhQzqsqt66wKNzrIrk3lAcB4GUyPHbPVXpg==
|
integrity sha512-gXpc87TGSXIzfAr4QER1Qw1v3P47pBO6BXkma52blgwXVmcFNe3nhQzqsqt66wKNzrIrk3lAcB4GUyPHbPVXpg==
|
||||||
|
|
||||||
"@gitlab/ui@24.3.0":
|
"@gitlab/ui@24.3.1":
|
||||||
version "24.3.0"
|
version "24.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-24.3.0.tgz#6e3f7d6e198d12faad046ba819ffc56aa520d2ea"
|
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-24.3.1.tgz#3bc40a1e33326fa89cf25d29afa1628c0ab59684"
|
||||||
integrity sha512-jfDMEEUrJUC8OoQrtpeupomfbiIX0AnQ5SYaugVwkNocRQ/dWwFf1OwbnKh3BNRNUS2581Vt1yjZb32CO8+GiA==
|
integrity sha512-m2/Q/vuWrDt5ZGcdbf7VB6h152VtDA+BeVZu1T8RxzQGzgCJ9XnRqb8wTvW4KOXzdbsY/iqCHPRYhZKVrnakqg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/standalone" "^7.0.0"
|
"@babel/standalone" "^7.0.0"
|
||||||
"@gitlab/vue-toasted" "^1.3.0"
|
"@gitlab/vue-toasted" "^1.3.0"
|
||||||
|
|
Loading…
Reference in New Issue