Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b87af16bf2
commit
b63258f304
|
|
@ -15,7 +15,7 @@ include:
|
|||
gitlab_auth_token_variable_name: "PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE"
|
||||
allure_job_name: "${QA_RUN_TYPE}"
|
||||
- project: gitlab-org/quality/pipeline-common
|
||||
ref: 8.3.0
|
||||
ref: 8.3.2
|
||||
file:
|
||||
- /ci/base.gitlab-ci.yml
|
||||
- /ci/knapsack-report.yml
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export default {
|
|||
return getIdFromGraphQLId(this.resource.id);
|
||||
},
|
||||
hasLatestVersion() {
|
||||
return this.latestVersion?.tagName;
|
||||
return this.latestVersion?.name;
|
||||
},
|
||||
hasPipelineStatus() {
|
||||
return this.pipelineStatus?.text;
|
||||
|
|
@ -58,7 +58,7 @@ export default {
|
|||
return this.resource.latestVersion;
|
||||
},
|
||||
versionBadgeText() {
|
||||
return this.latestVersion.tagName;
|
||||
return this.latestVersion.name;
|
||||
},
|
||||
webPath() {
|
||||
return cleanLeadingSeparator(this.resource?.webPath);
|
||||
|
|
@ -92,7 +92,7 @@ export default {
|
|||
v-if="hasLatestVersion"
|
||||
size="sm"
|
||||
class="gl-ml-3 gl-my-1"
|
||||
:href="latestVersion.tagPath"
|
||||
:href="latestVersion.path"
|
||||
>
|
||||
{{ versionBadgeText }}
|
||||
</gl-badge>
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ export default {
|
|||
releasedAt() {
|
||||
return getTimeago().format(this.latestVersion?.releasedAt);
|
||||
},
|
||||
tagName() {
|
||||
return this.latestVersion?.tagName || this.$options.i18n.unreleased;
|
||||
name() {
|
||||
return this.latestVersion?.name || this.$options.i18n.unreleased;
|
||||
},
|
||||
webPath() {
|
||||
return cleanLeadingSeparator(this.resource?.webPath);
|
||||
|
|
@ -117,7 +117,7 @@ export default {
|
|||
<b> {{ resource.name }}</b>
|
||||
</gl-link>
|
||||
<div class="gl-display-flex gl-flex-grow-1 gl-md-justify-content-space-between">
|
||||
<gl-badge size="sm" class="gl-h-5 gl-align-self-center">{{ tagName }}</gl-badge>
|
||||
<gl-badge size="sm" class="gl-h-5 gl-align-self-center">{{ name }}</gl-badge>
|
||||
<span class="gl-display-flex gl-align-items-center gl-ml-5">
|
||||
<span
|
||||
v-gl-tooltip.top
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ fragment CatalogResourceFields on CiCatalogResource {
|
|||
starCount
|
||||
latestVersion {
|
||||
id
|
||||
tagName
|
||||
tagPath
|
||||
name
|
||||
path
|
||||
releasedAt
|
||||
author {
|
||||
id
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ query getCiCatalogResourceDetails($fullPath: ID!) {
|
|||
}
|
||||
}
|
||||
}
|
||||
tagName
|
||||
name
|
||||
releasedAt
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
<script>
|
||||
import { GlButton, GlModal, GlModalDirective, GlCard, GlIcon } from '@gitlab/ui';
|
||||
import {
|
||||
GlButton,
|
||||
GlModal,
|
||||
GlModalDirective,
|
||||
GlCard,
|
||||
GlIcon,
|
||||
GlDisclosureDropdown,
|
||||
GlCollapsibleListbox,
|
||||
GlFormGroup,
|
||||
} from '@gitlab/ui';
|
||||
import { createAlert } from '~/alert';
|
||||
import branchRulesQuery from 'ee_else_ce/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { expandSection } from '~/settings_panels';
|
||||
import { scrollToElement } from '~/lib/utils/common_utils';
|
||||
import createBranchRuleMutation from './graphql/mutations/create_branch_rule.mutation.graphql';
|
||||
import BranchRule from './components/branch_rule.vue';
|
||||
import { I18N, PROTECTED_BRANCHES_ANCHOR, BRANCH_PROTECTION_MODAL_ID } from './constants';
|
||||
|
||||
|
|
@ -14,12 +25,16 @@ export default {
|
|||
BranchRule,
|
||||
GlButton,
|
||||
GlModal,
|
||||
GlFormGroup,
|
||||
GlCard,
|
||||
GlIcon,
|
||||
GlCollapsibleListbox,
|
||||
GlDisclosureDropdown,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
apollo: {
|
||||
branchRules: {
|
||||
query: branchRulesQuery,
|
||||
|
|
@ -38,18 +53,87 @@ export default {
|
|||
},
|
||||
inject: {
|
||||
projectPath: { default: '' },
|
||||
branchRulesPath: { default: '' },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
branchRules: [],
|
||||
branchRuleName: '',
|
||||
searchQuery: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
addRuleItems() {
|
||||
return [{ text: this.$options.i18n.branchName, action: () => this.openCreateRuleModal() }];
|
||||
},
|
||||
createRuleItems() {
|
||||
return this.isWildcardAvailable ? [this.wildcardItem] : this.filteredOpenBranches;
|
||||
},
|
||||
filteredOpenBranches() {
|
||||
const openBranches = window.gon.open_branches.map((item) => ({
|
||||
text: item.text,
|
||||
value: item.text,
|
||||
}));
|
||||
return openBranches.filter((item) => item.text.includes(this.searchQuery));
|
||||
},
|
||||
wildcardItem() {
|
||||
return { text: this.$options.i18n.createWildcard, value: this.searchQuery };
|
||||
},
|
||||
isWildcardAvailable() {
|
||||
return this.searchQuery.includes('*');
|
||||
},
|
||||
createRuleText() {
|
||||
return this.branchRuleName || this.$options.i18n.branchNamePlaceholder;
|
||||
},
|
||||
branchRuleEditPath() {
|
||||
return `${this.branchRulesPath}?branch=${encodeURIComponent(this.branchRuleName)}`;
|
||||
},
|
||||
primaryProps() {
|
||||
return {
|
||||
text: this.$options.i18n.createProtectedBranch,
|
||||
attributes: {
|
||||
variant: 'confirm',
|
||||
disabled: !this.branchRuleName,
|
||||
},
|
||||
};
|
||||
},
|
||||
cancelProps() {
|
||||
return {
|
||||
text: this.$options.i18n.createBranchRule,
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showProtectedBranches() {
|
||||
// Protected branches section is on the same page as the branch rules section.
|
||||
expandSection(this.$options.protectedBranchesAnchor);
|
||||
scrollToElement(this.$options.protectedBranchesAnchor);
|
||||
},
|
||||
openCreateRuleModal() {
|
||||
this.$refs[this.$options.modalId].show();
|
||||
},
|
||||
handleBranchRuleSearch(query) {
|
||||
this.searchQuery = query;
|
||||
},
|
||||
addBranchRule() {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: createBranchRuleMutation,
|
||||
variables: {
|
||||
projectPath: this.projectPath,
|
||||
name: this.branchRuleName,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
window.location.assign(this.branchRuleEditPath);
|
||||
})
|
||||
.catch(() => {
|
||||
createAlert({ message: this.$options.i18n.createBranchRuleError });
|
||||
});
|
||||
},
|
||||
selectBranchRuleName(branchName) {
|
||||
this.branchRuleName = branchName;
|
||||
},
|
||||
},
|
||||
modalId: BRANCH_PROTECTION_MODAL_ID,
|
||||
protectedBranchesAnchor: PROTECTED_BRANCHES_ANCHOR,
|
||||
|
|
@ -72,7 +156,15 @@ export default {
|
|||
{{ branchRules.length }}
|
||||
</div>
|
||||
</div>
|
||||
<gl-disclosure-dropdown
|
||||
v-if="glFeatures.addBranchRule"
|
||||
:toggle-text="$options.i18n.addBranchRule"
|
||||
:items="addRuleItems"
|
||||
size="small"
|
||||
no-caret
|
||||
/>
|
||||
<gl-button
|
||||
v-else
|
||||
v-gl-modal="$options.modalId"
|
||||
size="small"
|
||||
class="gl-ml-3"
|
||||
|
|
@ -99,6 +191,37 @@ export default {
|
|||
</div>
|
||||
</ul>
|
||||
<gl-modal
|
||||
v-if="glFeatures.addBranchRule"
|
||||
:ref="$options.modalId"
|
||||
:modal-id="$options.modalId"
|
||||
:title="$options.i18n.createBranchRule"
|
||||
:action-primary="primaryProps"
|
||||
:action-cancel="cancelProps"
|
||||
@primary="addBranchRule"
|
||||
@change="searchQuery = ''"
|
||||
>
|
||||
<gl-form-group
|
||||
:label="$options.i18n.branchName"
|
||||
:description="$options.i18n.branchNameDescription"
|
||||
>
|
||||
<gl-collapsible-listbox
|
||||
v-model="branchRuleName"
|
||||
searchable
|
||||
:items="createRuleItems"
|
||||
:toggle-text="createRuleText"
|
||||
block
|
||||
@search="handleBranchRuleSearch"
|
||||
@select="selectBranchRuleName"
|
||||
>
|
||||
<template v-if="isWildcardAvailable" #list-item="{ item }">
|
||||
{{ item.text }}
|
||||
<code>{{ searchQuery }}</code>
|
||||
</template>
|
||||
</gl-collapsible-listbox>
|
||||
</gl-form-group>
|
||||
</gl-modal>
|
||||
<gl-modal
|
||||
v-else
|
||||
:ref="$options.modalId"
|
||||
:modal-id="$options.modalId"
|
||||
:title="$options.i18n.addBranchRule"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,16 @@ export const I18N = {
|
|||
'BranchRules|After a protected branch is created, it will show up in the list as a branch rule.',
|
||||
),
|
||||
createProtectedBranch: s__('BranchRules|Create protected branch'),
|
||||
createBranchRule: s__('BranchRules|Create branch rule'),
|
||||
branchName: s__('BranchRules|Branch name or pattern'),
|
||||
branchNamePlaceholder: s__('BranchRules|Select Branch or create wildcard'),
|
||||
branchNameDescription: s__(
|
||||
'BranchRules|Wildcards such as *-stable or production/* are supported',
|
||||
),
|
||||
createBranchRuleError: s__('BranchRules|Something went wrong while creating branch rule.'),
|
||||
createBranchRuleSuccess: s__('BranchRules|Branch rule created.'),
|
||||
createWildcard: s__('BranchRules|Create wildcard'),
|
||||
cancel: s__('BranchRules|Cancel'),
|
||||
};
|
||||
|
||||
export const PROTECTED_BRANCHES_ANCHOR = '#js-protected-branches-settings';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
mutation createBranchRule($projectPath: ID!, $name: String!) {
|
||||
branchRuleCreate(input: { projectPath: $projectPath, name: $name }) {
|
||||
errors
|
||||
branchRule {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,10 @@ module Projects
|
|||
before_action :authorize_admin_project!
|
||||
before_action :define_variables, only: [:create_deploy_token]
|
||||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:add_branch_rule, @project)
|
||||
end
|
||||
|
||||
feature_category :source_code_management, [:show, :cleanup, :update]
|
||||
feature_category :continuous_delivery, [:create_deploy_token]
|
||||
urgency :low, [:show, :create_deploy_token]
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
push_frontend_feature_flag(:explain_code_chat, current_user)
|
||||
push_frontend_feature_flag(:issue_email_participants, @project)
|
||||
push_frontend_feature_flag(:encoding_logs_tree)
|
||||
push_frontend_feature_flag(:add_branch_rule, @project)
|
||||
# TODO: We need to remove the FF eventually when we rollout page_specific_styles
|
||||
push_frontend_feature_flag(:page_specific_styles, current_user)
|
||||
push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Cloud Connector Access",
|
||||
"type": "object",
|
||||
"available_services": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: add_branch_rule
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/435948
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/437003
|
||||
milestone: '16.8'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
table_name: cloud_connector_access
|
||||
feature_categories:
|
||||
- cloud_connector
|
||||
description: Information about Cloud Connector features
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140456
|
||||
milestone: '16.8'
|
||||
gitlab_schema: gitlab_main_clusterwide
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateCloudConnectorAccess < Gitlab::Database::Migration[2.2]
|
||||
enable_lock_retries!
|
||||
milestone '16.8'
|
||||
|
||||
def change
|
||||
create_table :cloud_connector_access do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
t.jsonb :data, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
1403f5bcc26c0f8b76f27b77305fc4089c87cf06362c3c9919a71dd3efcee492
|
||||
|
|
@ -14887,6 +14887,22 @@ CREATE SEQUENCE ci_variables_id_seq
|
|||
|
||||
ALTER SEQUENCE ci_variables_id_seq OWNED BY ci_variables.id;
|
||||
|
||||
CREATE TABLE cloud_connector_access (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
data jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE cloud_connector_access_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE cloud_connector_access_id_seq OWNED BY cloud_connector_access.id;
|
||||
|
||||
CREATE TABLE cluster_agent_tokens (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
|
|
@ -26626,6 +26642,8 @@ ALTER TABLE ONLY ci_unit_tests ALTER COLUMN id SET DEFAULT nextval('ci_unit_test
|
|||
|
||||
ALTER TABLE ONLY ci_variables ALTER COLUMN id SET DEFAULT nextval('ci_variables_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY cloud_connector_access ALTER COLUMN id SET DEFAULT nextval('cloud_connector_access_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY cluster_agent_tokens ALTER COLUMN id SET DEFAULT nextval('cluster_agent_tokens_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY cluster_agents ALTER COLUMN id SET DEFAULT nextval('cluster_agents_id_seq'::regclass);
|
||||
|
|
@ -28637,6 +28655,9 @@ ALTER TABLE ONLY ci_unit_tests
|
|||
ALTER TABLE ONLY ci_variables
|
||||
ADD CONSTRAINT ci_variables_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY cloud_connector_access
|
||||
ADD CONSTRAINT cloud_connector_access_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY cluster_agent_tokens
|
||||
ADD CONSTRAINT cluster_agent_tokens_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
You can organize GitLab [groups](../index.md) into subgroups. You can use subgroups to:
|
||||
|
||||
- Separate internal and external organizations. Because every subgroup can have its own
|
||||
- Separate internal and external content. Because every subgroup can have its own
|
||||
[visibility level](../../public_access.md), you can host groups for different
|
||||
purposes under the same parent group.
|
||||
- Organize large projects. You can use subgroups to give different access to parts of
|
||||
- Organize large projects. You can use subgroups to manage who can access parts of
|
||||
the source code.
|
||||
- Manage people and control visibility. Give a user a different
|
||||
- Manage permissions. Give a user a different
|
||||
[role](../../permissions.md#group-members-permissions) for each group they're [a member of](#subgroup-membership).
|
||||
|
||||
Subgroups can:
|
||||
|
|
@ -25,7 +25,7 @@ Subgroups can:
|
|||
- Be nested up to 20 levels.
|
||||
- Use [runners](../../../ci/runners/index.md) registered to parent groups:
|
||||
- Secrets configured for the parent group are available to subgroup jobs.
|
||||
- Users with the Maintainer role in projects that belong to subgroups can see the details of runners registered to
|
||||
- Users with at least the Maintainer role in projects that belong to subgroups can see the details of runners registered to
|
||||
parent groups.
|
||||
|
||||
For example:
|
||||
|
|
@ -117,7 +117,7 @@ For more information, view the [permissions table](../../permissions.md#group-me
|
|||
## Subgroup membership
|
||||
|
||||
When you add a member to a group, that member is also added to all subgroups of that group.
|
||||
The member's permissions are inherited from the group's parent.
|
||||
The member's permissions are inherited from the group into all subgroups.
|
||||
|
||||
Subgroup members can be:
|
||||
|
||||
|
|
@ -189,8 +189,8 @@ Members can be [filtered by inherited or direct membership](../index.md#filter-a
|
|||
|
||||
Users with the Owner role in a subgroup can add members to it.
|
||||
|
||||
You can't give a user a role in a subgroup that is lower than the roles the user has in ancestor groups.
|
||||
To override a user's role in an ancestor group, add the user to the subgroup again with a higher role.
|
||||
You can't give a user a role in a subgroup that is lower than the roles the user has in parent groups.
|
||||
To override a user's role in a parent group, add the user to the subgroup again with a higher role.
|
||||
For example:
|
||||
|
||||
- If User 1 is added to group _Two_ with the Developer role, User 1 inherits that role in every subgroup of group _Two_.
|
||||
|
|
@ -201,7 +201,7 @@ For example:
|
|||
|
||||
## Mention subgroups
|
||||
|
||||
Mentioning subgroups ([`@<subgroup_name>`](../../discussions/index.md#mentions)) in issues, commits, and merge requests
|
||||
Mentioning subgroups ([`@<subgroup_name>`](../../discussions/index.md#mentions)) in epics, issues, commits, and merge requests
|
||||
notifies all direct members of that group. Inherited members of a subgroup are not notified by mentions.
|
||||
Mentioning works the same as for projects and groups, and you can choose the group of members to be notified.
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ This specific Ruby image is maintained on [DockerHub](https://hub.docker.com/_/r
|
|||
Edit your `.gitlab-ci.yml` file and add this text as the first line:
|
||||
|
||||
```yaml
|
||||
image: ruby:2.7
|
||||
image: ruby:3.2
|
||||
```
|
||||
|
||||
If your SSG needs [NodeJS](https://nodejs.org/) to build, you must specify an
|
||||
|
|
@ -156,7 +156,7 @@ pages:
|
|||
Your `.gitlab-ci.yml` file should now look like this:
|
||||
|
||||
```yaml
|
||||
image: ruby:2.7
|
||||
image: ruby:3.2
|
||||
|
||||
pages:
|
||||
script:
|
||||
|
|
@ -198,7 +198,7 @@ First, add a `workflow` section to force the pipeline to run only when changes a
|
|||
pushed to branches:
|
||||
|
||||
```yaml
|
||||
image: ruby:2.7
|
||||
image: ruby:3.2
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
|
|
@ -218,7 +218,7 @@ Then configure the pipeline to run the job for the
|
|||
[default branch](../../repository/branches/default.md) (here, `main`) only.
|
||||
|
||||
```yaml
|
||||
image: ruby:2.7
|
||||
image: ruby:3.2
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
|
|
@ -249,7 +249,7 @@ To specify a stage for your job to run in,
|
|||
add a `stage` line to your CI file:
|
||||
|
||||
```yaml
|
||||
image: ruby:2.7
|
||||
image: ruby:3.2
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
|
|
@ -273,7 +273,7 @@ Now add another job to the CI file, telling it to
|
|||
test every push to every branch **except** the `main` branch:
|
||||
|
||||
```yaml
|
||||
image: ruby:2.7
|
||||
image: ruby:3.2
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
|
|
@ -325,7 +325,7 @@ for both jobs, `pages` and `test`.
|
|||
Move these commands to a `before_script` section:
|
||||
|
||||
```yaml
|
||||
image: ruby:2.7
|
||||
image: ruby:3.2
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
|
|
@ -366,7 +366,7 @@ This example caches Jekyll dependencies in a `vendor` directory
|
|||
when you run `bundle install`:
|
||||
|
||||
```yaml
|
||||
image: ruby:2.7
|
||||
image: ruby:3.2
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@ module Gitlab
|
|||
end
|
||||
|
||||
def current_application_settings
|
||||
cached_application_settings
|
||||
cached_application_settings || uncached_application_settings
|
||||
end
|
||||
|
||||
def current_application_settings?
|
||||
::ApplicationSetting.current.present?
|
||||
end
|
||||
|
||||
def expire_current_application_settings
|
||||
::ApplicationSetting.expire
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -25,9 +33,48 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def uncached_application_settings
|
||||
return fake_application_settings if Gitlab::Runtime.rake? && !connect_to_db?
|
||||
|
||||
current_settings = ::ApplicationSetting.current
|
||||
|
||||
# If there are pending migrations, it's possible there are columns that
|
||||
# need to be added to the application settings. To prevent Rake tasks
|
||||
# and other callers from failing, use any loaded settings and return
|
||||
# defaults for missing columns.
|
||||
if Gitlab::Runtime.rake? && ::ApplicationSetting.connection.migration_context.needs_migration?
|
||||
db_attributes = current_settings&.attributes || {}
|
||||
fake_application_settings(db_attributes)
|
||||
elsif current_settings.present?
|
||||
current_settings
|
||||
else
|
||||
::ApplicationSetting.create_from_defaults
|
||||
end
|
||||
rescue ::ApplicationSetting::Recursion
|
||||
in_memory_application_settings
|
||||
end
|
||||
|
||||
def in_memory_application_settings
|
||||
@in_memory_application_settings ||= ::ApplicationSetting.build_from_defaults
|
||||
end
|
||||
|
||||
def fake_application_settings(attributes = {})
|
||||
Gitlab::FakeApplicationSettings.new(::ApplicationSetting.defaults.merge(attributes || {}))
|
||||
end
|
||||
|
||||
def connect_to_db?
|
||||
# When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
|
||||
active_db_connection = begin
|
||||
::ApplicationSetting.connection.active?
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
|
||||
active_db_connection &&
|
||||
ApplicationSetting.database.cached_table_exists?
|
||||
rescue ActiveRecord::NoDatabaseError
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,22 +12,18 @@ module Gitlab
|
|||
end
|
||||
|
||||
def current_application_settings
|
||||
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
|
||||
Gitlab::SafeRequestStore.fetch(:current_application_settings) { Gitlab::ApplicationSettingFetcher.current_application_settings }
|
||||
end
|
||||
|
||||
def current_application_settings?
|
||||
Gitlab::SafeRequestStore.exist?(:current_application_settings) || ::ApplicationSetting.current.present?
|
||||
Gitlab::SafeRequestStore.exist?(:current_application_settings) || Gitlab::ApplicationSettingFetcher.current_application_settings?
|
||||
end
|
||||
|
||||
def expire_current_application_settings
|
||||
::ApplicationSetting.expire
|
||||
Gitlab::ApplicationSettingFetcher.expire_current_application_settings
|
||||
Gitlab::SafeRequestStore.delete(:current_application_settings)
|
||||
end
|
||||
|
||||
def clear_in_memory_application_settings!
|
||||
@in_memory_application_settings = nil
|
||||
end
|
||||
|
||||
def method_missing(name, *args, **kwargs, &block)
|
||||
current_application_settings.send(name, *args, **kwargs, &block) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
|
@ -35,54 +31,6 @@ module Gitlab
|
|||
def respond_to_missing?(name, include_private = false)
|
||||
current_application_settings.respond_to?(name, include_private) || super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_application_settings!
|
||||
Gitlab::ApplicationSettingFetcher.current_application_settings || uncached_application_settings
|
||||
end
|
||||
|
||||
def uncached_application_settings
|
||||
return fake_application_settings if Gitlab::Runtime.rake? && !connect_to_db?
|
||||
|
||||
current_settings = ::ApplicationSetting.current
|
||||
# If there are pending migrations, it's possible there are columns that
|
||||
# need to be added to the application settings. To prevent Rake tasks
|
||||
# and other callers from failing, use any loaded settings and return
|
||||
# defaults for missing columns.
|
||||
if Gitlab::Runtime.rake? && ::ApplicationSetting.connection.migration_context.needs_migration?
|
||||
db_attributes = current_settings&.attributes || {}
|
||||
fake_application_settings(db_attributes)
|
||||
elsif current_settings.present?
|
||||
current_settings
|
||||
else
|
||||
::ApplicationSetting.create_from_defaults
|
||||
end
|
||||
rescue ::ApplicationSetting::Recursion
|
||||
in_memory_application_settings
|
||||
end
|
||||
|
||||
def fake_application_settings(attributes = {})
|
||||
Gitlab::FakeApplicationSettings.new(::ApplicationSetting.defaults.merge(attributes || {}))
|
||||
end
|
||||
|
||||
def in_memory_application_settings
|
||||
@in_memory_application_settings ||= ::ApplicationSetting.build_from_defaults
|
||||
end
|
||||
|
||||
def connect_to_db?
|
||||
# When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
|
||||
active_db_connection = begin
|
||||
::ApplicationSetting.connection.active?
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
|
||||
active_db_connection &&
|
||||
ApplicationSetting.database.cached_table_exists?
|
||||
rescue ActiveRecord::NoDatabaseError
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8663,15 +8663,27 @@ msgstr ""
|
|||
msgid "BranchRules|Branch name or pattern"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Branch rule created."
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Branch rules details"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Check for a status response in merge requests. Failures do not block merges. %{linkStart}Learn more.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Create branch rule"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Create protected branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Create wildcard"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Create wildcard: %{searchTerm}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -8729,6 +8741,12 @@ msgstr ""
|
|||
msgid "BranchRules|Roles"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Select Branch or create wildcard"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Something went wrong while creating branch rule."
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Status checks"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -8747,6 +8765,9 @@ msgstr ""
|
|||
msgid "BranchRules|View details"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Wildcards such as *-stable or production/* are supported"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|default"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ describe('CiResourceAbout', () => {
|
|||
openMergeRequestsCount: 9,
|
||||
latestVersion: {
|
||||
id: 1,
|
||||
tagName: 'v1.0.0',
|
||||
tagPath: 'path/to/release',
|
||||
name: 'v1.0.0',
|
||||
path: 'path/to/release',
|
||||
releasedAt: '2022-08-23T17:19:09Z',
|
||||
},
|
||||
webPath: 'path/to/project',
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ describe('CiResourceHeader', () => {
|
|||
createComponent({
|
||||
props: {
|
||||
pipelineStatus: status,
|
||||
latestVersion: { tagName: '1.0.0', tagPath: 'path/to/release' },
|
||||
latestVersion: { name: '1.0.0', path: 'path/to/release' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ describe('CiResourcesListItem', () => {
|
|||
const release = {
|
||||
author: { name: 'author', webUrl: '/user/1' },
|
||||
releasedAt: Date.now(),
|
||||
tagName: '1.0.0',
|
||||
name: '1.0.0',
|
||||
};
|
||||
const defaultProps = {
|
||||
resource,
|
||||
|
|
@ -114,7 +114,7 @@ describe('CiResourcesListItem', () => {
|
|||
|
||||
it('renders the version badge', () => {
|
||||
expect(findBadge().exists()).toBe(true);
|
||||
expect(findBadge().text()).toBe(release.tagName);
|
||||
expect(findBadge().text()).toBe(release.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -298,8 +298,8 @@ export const catalogSharedDataMock = {
|
|||
latestVersion: {
|
||||
__typename: 'Release',
|
||||
id: '3',
|
||||
tagName: '1.0.0',
|
||||
tagPath: 'path/to/release',
|
||||
name: '1.0.0',
|
||||
path: 'path/to/release',
|
||||
releasedAt: Date.now(),
|
||||
author: { id: 1, webUrl: 'profile/1', name: 'username' },
|
||||
},
|
||||
|
|
@ -344,7 +344,7 @@ export const catalogAdditionalDetailsMock = {
|
|||
],
|
||||
},
|
||||
},
|
||||
tagName: 'v1.0.2',
|
||||
name: 'v1.0.2',
|
||||
releasedAt: '2022-08-23T17:19:09Z',
|
||||
},
|
||||
],
|
||||
|
|
@ -366,8 +366,8 @@ const generateResourcesNodes = (count = 20, startId = 0) => {
|
|||
latestVersion: {
|
||||
__typename: 'Release',
|
||||
id: '3',
|
||||
tagName: '1.0.0',
|
||||
tagPath: 'path/to/release',
|
||||
name: '1.0.0',
|
||||
path: 'path/to/release',
|
||||
releasedAt: Date.now(),
|
||||
author: { id: 1, webUrl: 'profile/1', name: 'username' },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
import Vue from 'vue';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import { GlModal, GlCollapsibleListbox, GlDisclosureDropdown } from '@gitlab/ui';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import BranchRules from '~/projects/settings/repository/branch_rules/app.vue';
|
||||
import BranchRule from '~/projects/settings/repository/branch_rules/components/branch_rule.vue';
|
||||
import branchRulesQuery from 'ee_else_ce/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql';
|
||||
import createBranchRuleMutation from '~/projects/settings/repository/branch_rules/graphql/mutations/create_branch_rule.mutation.graphql';
|
||||
|
||||
import { createAlert } from '~/alert';
|
||||
import {
|
||||
branchRulesMockResponse,
|
||||
appProvideMock,
|
||||
createBranchRuleMockResponse,
|
||||
} from 'ee_else_ce_jest/projects/settings/repository/branch_rules/mock_data';
|
||||
import {
|
||||
I18N,
|
||||
|
|
@ -31,16 +36,33 @@ Vue.use(VueApollo);
|
|||
describe('Branch rules app', () => {
|
||||
let wrapper;
|
||||
let fakeApollo;
|
||||
|
||||
const openBranches = [
|
||||
{ text: 'branch1', id: 'branch1', title: 'branch1' },
|
||||
{ text: 'branch2', id: 'branch2', title: 'branch2' },
|
||||
];
|
||||
const branchRulesQuerySuccessHandler = jest.fn().mockResolvedValue(branchRulesMockResponse);
|
||||
const addRuleMutationSuccessHandler = jest.fn().mockResolvedValue(createBranchRuleMockResponse);
|
||||
|
||||
const createComponent = async ({ queryHandler = branchRulesQuerySuccessHandler } = {}) => {
|
||||
fakeApollo = createMockApollo([[branchRulesQuery, queryHandler]]);
|
||||
const createComponent = async ({
|
||||
glFeatures = { addBranchRule: true },
|
||||
queryHandler = branchRulesQuerySuccessHandler,
|
||||
mutationHandler = addRuleMutationSuccessHandler,
|
||||
} = {}) => {
|
||||
fakeApollo = createMockApollo([
|
||||
[branchRulesQuery, queryHandler],
|
||||
[createBranchRuleMutation, mutationHandler],
|
||||
]);
|
||||
|
||||
wrapper = mountExtended(BranchRules, {
|
||||
apolloProvider: fakeApollo,
|
||||
provide: appProvideMock,
|
||||
stubs: { GlModal: stubComponent(GlModal, { template: RENDER_ALL_SLOTS_TEMPLATE }) },
|
||||
provide: {
|
||||
...appProvideMock,
|
||||
glFeatures,
|
||||
},
|
||||
stubs: {
|
||||
GlDisclosureDropdown,
|
||||
GlModal: stubComponent(GlModal, { template: RENDER_ALL_SLOTS_TEMPLATE }),
|
||||
},
|
||||
directives: { GlModal: createMockDirective('gl-modal') },
|
||||
});
|
||||
|
||||
|
|
@ -51,19 +73,18 @@ describe('Branch rules app', () => {
|
|||
const findEmptyState = () => wrapper.findByTestId('empty');
|
||||
const findAddBranchRuleButton = () => wrapper.findByRole('button', I18N.addBranchRule);
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findAddBranchRuleDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
|
||||
const findCreateBranchRuleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
|
||||
|
||||
beforeEach(() => {
|
||||
window.gon = {
|
||||
open_branches: openBranches,
|
||||
};
|
||||
setWindowLocation(TEST_HOST);
|
||||
});
|
||||
|
||||
beforeEach(() => createComponent());
|
||||
|
||||
it('displays an error if branch rules query fails', async () => {
|
||||
await createComponent({ queryHandler: jest.fn().mockRejectedValue() });
|
||||
expect(createAlert).toHaveBeenCalledWith({ message: I18N.queryError });
|
||||
});
|
||||
|
||||
it('displays an empty state if no branch rules are present', async () => {
|
||||
await createComponent({ queryHandler: jest.fn().mockRejectedValue() });
|
||||
expect(findEmptyState().text()).toBe(I18N.emptyState);
|
||||
});
|
||||
|
||||
it('renders branch rules', () => {
|
||||
const { nodes } = branchRulesMockResponse.data.project.branchRules;
|
||||
|
||||
|
|
@ -78,7 +99,75 @@ describe('Branch rules app', () => {
|
|||
expect(findAllBranchRules().at(1).props('branchProtection')).toEqual(nodes[1].branchProtection);
|
||||
});
|
||||
|
||||
it('displays an error if branch rules query fails', async () => {
|
||||
await createComponent({ queryHandler: jest.fn().mockRejectedValue() });
|
||||
expect(createAlert).toHaveBeenCalledWith({ message: I18N.queryError });
|
||||
});
|
||||
|
||||
it('displays an empty state if no branch rules are present', async () => {
|
||||
await createComponent({ queryHandler: jest.fn().mockRejectedValue() });
|
||||
expect(findEmptyState().text()).toBe(I18N.emptyState);
|
||||
});
|
||||
|
||||
describe('Add branch rule', () => {
|
||||
it('renders an Add branch rule dropdown', () => {
|
||||
expect(findAddBranchRuleDropdown().props('toggleText')).toBe('Add branch rule');
|
||||
});
|
||||
|
||||
it('renders a modal with correct props/attributes', () => {
|
||||
expect(findModal().props()).toMatchObject({
|
||||
title: I18N.createBranchRule,
|
||||
modalId: BRANCH_PROTECTION_MODAL_ID,
|
||||
actionCancel: {
|
||||
text: 'Create branch rule',
|
||||
},
|
||||
actionPrimary: {
|
||||
attributes: {
|
||||
disabled: true,
|
||||
variant: 'confirm',
|
||||
},
|
||||
text: 'Create protected branch',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders listbox with branch names', () => {
|
||||
expect(findCreateBranchRuleListbox().exists()).toBe(true);
|
||||
expect(findCreateBranchRuleListbox().props('items')).toHaveLength(openBranches.length);
|
||||
expect(findCreateBranchRuleListbox().props('toggleText')).toBe(
|
||||
'Select Branch or create wildcard',
|
||||
);
|
||||
});
|
||||
|
||||
it('when the primary modal action is clicked it calls create rule mutation', async () => {
|
||||
findCreateBranchRuleListbox().vm.$emit('select', openBranches[0].text);
|
||||
await nextTick();
|
||||
findModal().vm.$emit('primary');
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
expect(addRuleMutationSuccessHandler).toHaveBeenCalledWith({
|
||||
name: 'branch1',
|
||||
projectPath: 'some/project/path',
|
||||
});
|
||||
});
|
||||
|
||||
it('shows alert when mutation fails', async () => {
|
||||
createComponent({ mutationHandler: jest.fn().mockRejectedValue() });
|
||||
findCreateBranchRuleListbox().vm.$emit('select', openBranches[0].text);
|
||||
await nextTick();
|
||||
findModal().vm.$emit('primary');
|
||||
await waitForPromises();
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: 'Something went wrong while creating branch rule.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Add branch rule when addBranchRule FF disabled', () => {
|
||||
beforeEach(() => {
|
||||
window.gon.open_branches = openBranches;
|
||||
createComponent({ glFeatures: { addBranchRule: false } });
|
||||
});
|
||||
it('renders an Add branch rule button', () => {
|
||||
expect(findAddBranchRuleButton().exists()).toBe(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -65,8 +65,22 @@ export const branchRulesMockResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
export const createBranchRuleMockResponse = {
|
||||
data: {
|
||||
branchRuleCreate: {
|
||||
errors: [],
|
||||
branchRule: {
|
||||
name: '*dkd',
|
||||
__typename: 'BranchRule',
|
||||
},
|
||||
__typename: 'BranchRuleCreatePayload',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const appProvideMock = {
|
||||
projectPath: 'some/project/path',
|
||||
branchRulesPath: 'settings/repository/branch_rules',
|
||||
};
|
||||
|
||||
export const branchRuleProvideMock = {
|
||||
|
|
|
|||
|
|
@ -44,23 +44,181 @@ RSpec.describe Gitlab::ApplicationSettingFetcher, feature_category: :cell do
|
|||
end
|
||||
|
||||
context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is false' do
|
||||
let_it_be(:settings) { create(:application_setting) }
|
||||
|
||||
context 'and an error is raised' do
|
||||
before do
|
||||
allow(ApplicationSetting).to receive(:cached).and_raise(StandardError)
|
||||
# The cached method is called twice:
|
||||
# - ApplicationSettingFetcher
|
||||
# - ApplicationSetting (CachedAttribute module)
|
||||
# For this test, the first needs to raise an exception
|
||||
# The second is swallowed on production so that should not raise an exception
|
||||
# So we only let the first call raise an exception
|
||||
# Alternatively, we could mock Rails.env.production? but I prefer not to
|
||||
raise_exception = true
|
||||
allow(ApplicationSetting).to receive(:cached).twice do
|
||||
if raise_exception
|
||||
raise_exception = false
|
||||
raise(StandardError)
|
||||
else
|
||||
ApplicationSetting.last
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
expect(current_application_settings).to be_nil
|
||||
it 'will retrieve uncached ApplicationSetting' do
|
||||
expect(ApplicationSetting).to receive(:current).and_call_original
|
||||
|
||||
expect(current_application_settings).to eq(settings)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and settings in cache' do
|
||||
before do
|
||||
# Warm the cache
|
||||
ApplicationSetting.current
|
||||
end
|
||||
|
||||
it 'fetches the settings from cache' do
|
||||
expect(::ApplicationSetting).to receive(:cached).and_call_original
|
||||
|
||||
expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
|
||||
expect(ActiveRecord::QueryRecorder.new { current_application_settings }.count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and settings are not in cache' do
|
||||
before do
|
||||
allow(ApplicationSetting).to receive(:cached).and_return(nil)
|
||||
end
|
||||
|
||||
context 'and we are running a Rake task' do
|
||||
before do
|
||||
allow(Gitlab::Runtime).to receive(:rake?).and_return(true)
|
||||
end
|
||||
|
||||
context 'and database does not exist' do
|
||||
before do
|
||||
allow(::ApplicationSetting.database)
|
||||
.to receive(:cached_table_exists?).and_raise(ActiveRecord::NoDatabaseError)
|
||||
end
|
||||
|
||||
it 'uses Gitlab::FakeApplicationSettings' do
|
||||
expect(current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and database connection is not active' do
|
||||
before do
|
||||
allow(::ApplicationSetting.connection).to receive(:active?).and_return(false)
|
||||
end
|
||||
|
||||
it 'uses Gitlab::FakeApplicationSettings' do
|
||||
expect(current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and table does not exist' do
|
||||
before do
|
||||
allow(::ApplicationSetting.database).to receive(:cached_table_exists?).and_return(false)
|
||||
end
|
||||
|
||||
it 'uses Gitlab::FakeApplicationSettings' do
|
||||
expect(current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and database connection raises some error' do
|
||||
before do
|
||||
allow(::ApplicationSetting.connection).to receive(:active?).and_raise(StandardError)
|
||||
end
|
||||
|
||||
it 'uses Gitlab::FakeApplicationSettings' do
|
||||
expect(current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and there are pending database migrations' do
|
||||
before do
|
||||
allow_next_instance_of(ActiveRecord::MigrationContext) do |migration_context|
|
||||
allow(migration_context).to receive(:needs_migration?).and_return(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses Gitlab::FakeApplicationSettings' do
|
||||
expect(current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
|
||||
end
|
||||
|
||||
context 'when a new setting is used but the migration did not run yet' do
|
||||
let(:default_attributes) { { new_column: 'some_value' } }
|
||||
|
||||
before do
|
||||
allow(ApplicationSetting).to receive(:defaults).and_return(default_attributes)
|
||||
end
|
||||
|
||||
it 'uses the default value if present' do
|
||||
expect(current_application_settings.new_column).to eq(
|
||||
default_attributes[:new_column]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and settings are in database' do
|
||||
it 'returns settings from database' do
|
||||
expect(current_application_settings).to eq(settings)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and settings are not in the database' do
|
||||
before do
|
||||
allow(ApplicationSetting).to receive(:current).and_return(nil)
|
||||
end
|
||||
|
||||
it 'returns default settings' do
|
||||
expect(ApplicationSetting).to receive(:create_from_defaults).and_call_original
|
||||
|
||||
expect(current_application_settings).to eq(settings)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when we hit a recursive loop' do
|
||||
before do
|
||||
allow(ApplicationSetting).to receive(:current).and_raise(ApplicationSetting::Recursion)
|
||||
end
|
||||
|
||||
it 'recovers and returns in-memory settings' do
|
||||
settings = described_class.current_application_settings
|
||||
|
||||
expect(settings).to be_a(ApplicationSetting)
|
||||
expect(settings).not_to be_persisted
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.expire_current_application_settings' do
|
||||
subject(:expire) { described_class.expire_current_application_settings }
|
||||
|
||||
it 'expires ApplicationSetting' do
|
||||
expect(ApplicationSetting).to receive(:expire)
|
||||
|
||||
expire
|
||||
end
|
||||
end
|
||||
|
||||
describe '.current_application_settings?' do
|
||||
subject(:settings?) { described_class.current_application_settings? }
|
||||
|
||||
context 'when settings exist' do
|
||||
let_it_be(:settings) { create(:application_setting) }
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context 'when settings do not exist' do
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -102,193 +102,39 @@ RSpec.describe Gitlab::CurrentSettings, feature_category: :shared do
|
|||
|
||||
described_class.home_page_url
|
||||
end
|
||||
|
||||
context 'in a Rake task with DB unavailable' do
|
||||
before do
|
||||
allow(Gitlab::Runtime).to receive(:rake?).and_return(true)
|
||||
allow(Gitlab::ApplicationSettingFetcher).to receive(:current_application_settings).and_return(nil)
|
||||
|
||||
# For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues
|
||||
# during the initialization phase of the test suite, so instead let's mock the internals of it
|
||||
allow(ApplicationSetting.connection).to receive(:active?).and_return(false)
|
||||
end
|
||||
|
||||
context 'and no settings in cache' do
|
||||
it 'returns a FakeApplicationSettings object' do
|
||||
expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
|
||||
end
|
||||
|
||||
it 'does not issue any query' do
|
||||
expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with DB available' do
|
||||
# This method returns the ::ApplicationSetting.defaults hash
|
||||
# but with respect of custom attribute accessors of ApplicationSetting model
|
||||
def settings_from_defaults
|
||||
ar_wrapped_defaults = ::ApplicationSetting.build_from_defaults.attributes
|
||||
ar_wrapped_defaults.slice(*::ApplicationSetting.defaults.keys)
|
||||
end
|
||||
|
||||
context 'and settings in cache' do
|
||||
include_context 'with settings in cache'
|
||||
|
||||
it 'fetches the settings from cache' do
|
||||
# For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues
|
||||
# during the initialization phase of the test suite, so instead let's mock the internals of it
|
||||
expect(ApplicationSetting.connection).not_to receive(:active?)
|
||||
expect(ApplicationSetting.connection).not_to receive(:cached_table_exists?)
|
||||
expect_any_instance_of(ActiveRecord::MigrationContext).not_to receive(:needs_migration?)
|
||||
expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and no settings in cache' do
|
||||
before do
|
||||
allow(ApplicationSetting.connection).to receive(:active?).and_return(true)
|
||||
allow(ApplicationSetting.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true)
|
||||
end
|
||||
|
||||
context 'with RequestStore enabled', :request_store do
|
||||
it 'fetches the settings from DB only once' do
|
||||
described_class.current_application_settings # warm the cache
|
||||
|
||||
expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates default ApplicationSettings if none are present' do
|
||||
settings = described_class.current_application_settings
|
||||
|
||||
expect(settings).to be_a(ApplicationSetting)
|
||||
expect(settings).to be_persisted
|
||||
expect(settings).to have_attributes(settings_from_defaults)
|
||||
end
|
||||
|
||||
context 'when we hit a recursive loop' do
|
||||
before do
|
||||
expect(ApplicationSetting).to receive(:create_from_defaults) do
|
||||
raise ApplicationSetting::Recursion
|
||||
end
|
||||
end
|
||||
|
||||
it 'recovers and returns in-memory settings' do
|
||||
settings = described_class.current_application_settings
|
||||
|
||||
expect(settings).to be_a(ApplicationSetting)
|
||||
expect(settings).not_to be_persisted
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ApplicationSettings does not have a primary key' do
|
||||
before do
|
||||
allow(ApplicationSetting.connection).to receive(:primary_key).with('application_settings').and_return(nil)
|
||||
end
|
||||
|
||||
it 'raises an exception if ApplicationSettings does not have a primary key' do
|
||||
expect { described_class.current_application_settings }.to raise_error(/table is missing a primary key constraint/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with pending migrations' do
|
||||
let(:current_settings) { described_class.current_application_settings }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Runtime).to receive(:rake?).and_return(false)
|
||||
end
|
||||
|
||||
shared_examples 'a non-persisted ApplicationSetting object' do
|
||||
it 'uses the default value from ApplicationSetting.defaults' do
|
||||
expect(current_settings.signup_enabled).to eq(ApplicationSetting.defaults[:signup_enabled])
|
||||
end
|
||||
|
||||
it 'uses the default value from custom ApplicationSetting accessors' do
|
||||
expect(current_settings.commit_email_hostname).to eq(ApplicationSetting.default_commit_email_hostname)
|
||||
end
|
||||
|
||||
it 'responds to predicate methods' do
|
||||
expect(current_settings.signup_enabled?).to eq(current_settings.signup_enabled)
|
||||
end
|
||||
end
|
||||
|
||||
context 'in a Rake task' do
|
||||
before do
|
||||
allow(Gitlab::Runtime).to receive(:rake?).and_return(true)
|
||||
expect_any_instance_of(ActiveRecord::MigrationContext).to receive(:needs_migration?).and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'a non-persisted ApplicationSetting object'
|
||||
|
||||
it 'returns a FakeApplicationSettings object' do
|
||||
expect(current_settings).to be_a(Gitlab::FakeApplicationSettings)
|
||||
end
|
||||
|
||||
context 'when a new column is used before being migrated' do
|
||||
before do
|
||||
allow(ApplicationSetting).to receive(:defaults).and_return({ foo: 'bar' })
|
||||
end
|
||||
|
||||
it 'uses the default value if present' do
|
||||
expect(current_settings.foo).to eq('bar')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with no ApplicationSetting DB record' do
|
||||
it_behaves_like 'a non-persisted ApplicationSetting object'
|
||||
end
|
||||
|
||||
context 'with an existing ApplicationSetting DB record' do
|
||||
before do
|
||||
described_class.update!(home_page_url: 'http://mydomain.com')
|
||||
end
|
||||
|
||||
it_behaves_like 'a non-persisted ApplicationSetting object'
|
||||
|
||||
it 'uses the value from the DB attribute if present and not overridden by an accessor' do
|
||||
expect(current_settings.home_page_url).to eq('http://mydomain.com')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ApplicationSettings.current is present' do
|
||||
it 'returns the existing application settings' do
|
||||
expect(ApplicationSetting).to receive(:current).and_return(:current_settings)
|
||||
|
||||
expect(described_class.current_application_settings).to eq(:current_settings)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#current_application_settings?', :use_clean_rails_memory_store_caching do
|
||||
describe '#current_application_settings?' do
|
||||
subject(:settings_set) { described_class.current_application_settings? }
|
||||
|
||||
before do
|
||||
# unstub, it is stubbed in spec/spec_helper.rb
|
||||
allow(described_class).to receive(:current_application_settings?).and_call_original
|
||||
ApplicationSetting.delete_all # ensure no settings exist
|
||||
end
|
||||
|
||||
it 'returns true when settings exist' do
|
||||
described_class.update!(
|
||||
home_page_url: 'http://mydomain.com',
|
||||
signup_enabled: false)
|
||||
context 'when settings are cached in RequestStore' do
|
||||
before do
|
||||
allow(Gitlab::SafeRequestStore).to receive(:exist?).with(:current_application_settings).and_return(true)
|
||||
end
|
||||
|
||||
expect(described_class.current_application_settings?).to eq(true)
|
||||
it 'returns true' do
|
||||
expect(settings_set).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false when settings do not exist' do
|
||||
expect(described_class.current_application_settings?).to eq(false)
|
||||
context 'when ApplicationSettingFetcher.current_application_settings? returns true' do
|
||||
before do
|
||||
allow(Gitlab::ApplicationSettingFetcher).to receive(:current_application_settings?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(settings_set).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with cache', :request_store do
|
||||
include_context 'with settings in cache'
|
||||
|
||||
it 'returns an in-memory ApplicationSetting object' do
|
||||
expect(ApplicationSetting).not_to receive(:current)
|
||||
|
||||
expect(described_class.current_application_settings?).to eq(true)
|
||||
context 'when not cached and not in ApplicationSettingFetcher' do
|
||||
it 'returns false' do
|
||||
expect(settings_set).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -420,7 +420,6 @@ RSpec.configure do |config|
|
|||
|
||||
config.after do
|
||||
Fog.unmock! if Fog.mock?
|
||||
Gitlab::CurrentSettings.clear_in_memory_application_settings!
|
||||
Gitlab::ApplicationSettingFetcher.clear_in_memory_application_settings!
|
||||
|
||||
# Reset all feature flag stubs to default for testing
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ RSpec.configure do |config|
|
|||
end
|
||||
|
||||
config.after(:context, :migration) do
|
||||
Gitlab::CurrentSettings.clear_in_memory_application_settings!
|
||||
Gitlab::ApplicationSettingFetcher.clear_in_memory_application_settings!
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue