Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-05-30 21:09:08 +00:00
parent 2655ac9cd5
commit e690e4ea1f
33 changed files with 342 additions and 222 deletions

View File

@ -1355,12 +1355,12 @@
##########
.notify:rules:create-issues-for-failing-tests:
rules:
- <<: *if-not-canonical-namespace
- if: '$CREATE_ISSUES_FOR_FAILING_TESTS != "true"'
when: never
# Don't report child pipeline failures
- if: '$CI_PIPELINE_SOURCE == "parent_pipeline"'
when: never
- if: '$CREATE_ISSUES_FOR_FAILING_TESTS == "true"'
- <<: *if-dot-com-gitlab-org-default-branch
when: on_failure
allow_failure: true

View File

@ -0,0 +1,8 @@
---
Cop/IgnoredColumns:
Details: grace period
Exclude:
- 'app/models/loose_foreign_keys/deleted_record.rb'
- 'ee/lib/ee/gitlab/background_migration/create_vulnerability_links.rb'
- 'ee/lib/ee/gitlab/background_migration/migrate_vulnerabilities_feedback_to_vulnerabilities_state_transition.rb'
- 'spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb'

View File

@ -983,18 +983,6 @@ Layout/ArgumentAlignment:
- 'ee/app/models/ee/merge_request.rb'
- 'ee/app/models/ee/namespace.rb'
- 'ee/app/models/ee/namespace/storage/notification.rb'
- 'ee/app/models/ee/project.rb'
- 'ee/app/models/ee/project_group_link.rb'
- 'ee/app/models/ee/projects/wiki_repository.rb'
- 'ee/app/models/ee/upload.rb'
- 'ee/app/models/ee/user.rb'
- 'ee/app/models/ee/vulnerability.rb'
- 'ee/app/models/geo/hashed_storage_migrated_event.rb'
- 'ee/app/models/geo/project_registry.rb'
- 'ee/app/models/geo/project_wiki_repository_state.rb'
- 'ee/app/models/geo/wiki_repository_state.rb'
- 'ee/app/models/geo_node.rb'
- 'ee/app/models/geo_node_status.rb'
- 'ee/app/models/gitlab_subscriptions/upcoming_reconciliation.rb'
- 'ee/app/models/group_merge_request_approval_setting.rb'
- 'ee/app/models/incident_management/escalation_rule.rb'

View File

@ -215,7 +215,6 @@ Layout/LineEndStringConcatenationIndentation:
- 'rubocop/cop/graphql/descriptions.rb'
- 'rubocop/cop/graphql/resolver_type.rb'
- 'rubocop/cop/group_public_or_visible_to_user.rb'
- 'rubocop/cop/ignored_columns.rb'
- 'rubocop/cop/inject_enterprise_edition_module.rb'
- 'rubocop/cop/migration/add_concurrent_index.rb'
- 'rubocop/cop/migration/add_limit_to_text_columns.rb'

View File

@ -53,7 +53,6 @@ Naming/InclusiveLanguage:
- 'rubocop/cop/destroy_all.rb'
- 'rubocop/cop/graphql/id_type.rb'
- 'rubocop/cop/group_public_or_visible_to_user.rb'
- 'rubocop/cop/ignored_columns.rb'
- 'rubocop/cop/inject_enterprise_edition_module.rb'
- 'rubocop/cop/migration/add_columns_to_wide_tables.rb'
- 'spec/controllers/concerns/issuable_collections_spec.rb'

View File

@ -5310,7 +5310,6 @@ RSpec/MissingFeatureCategory:
- 'spec/rubocop/cop/graphql/old_types_spec.rb'
- 'spec/rubocop/cop/graphql/resolver_type_spec.rb'
- 'spec/rubocop/cop/group_public_or_visible_to_user_spec.rb'
- 'spec/rubocop/cop/ignored_columns_spec.rb'
- 'spec/rubocop/cop/include_sidekiq_worker_spec.rb'
- 'spec/rubocop/cop/inject_enterprise_edition_module_spec.rb'
- 'spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb'

View File

@ -14,13 +14,19 @@ export default {
EnvironmentForm,
},
mixins: [glFeatureFlagsMixin()],
inject: ['projectEnvironmentsPath', 'updateEnvironmentPath', 'projectPath', 'environmentName'],
inject: ['projectEnvironmentsPath', 'updateEnvironmentPath', 'projectPath'],
props: {
environment: {
required: true,
type: Object,
},
},
apollo: {
environment: {
query: getEnvironment,
variables() {
return {
environmentName: this.environmentName,
environmentName: this.environment.name,
projectFullPath: this.projectPath,
};
},
@ -31,16 +37,38 @@ export default {
},
data() {
return {
isQueryLoading: false,
loading: false,
formEnvironment: null,
};
},
computed: {
isQueryLoading() {
return this.$apollo.queries.environment.loading;
},
mounted() {
if (this.glFeatures?.environmentSettingsToGraphql) {
this.fetchWithGraphql();
} else {
this.formEnvironment = {
id: this.environment.id,
name: this.environment.name,
externalUrl: this.environment.external_url,
};
}
},
methods: {
async fetchWithGraphql() {
this.$apollo.addSmartQuery('environmentData', {
variables() {
return { environmentName: this.environment.name, projectFullPath: this.projectPath };
},
query: getEnvironment,
update(data) {
const result = data?.project?.environment || {};
this.formEnvironment = { ...result, clusterAgentId: result?.clusterAgent?.id };
},
watchLoading: (isLoading) => {
this.isQueryLoading = isLoading;
},
});
},
onChange(environment) {
this.formEnvironment = environment;
},

View File

@ -112,7 +112,7 @@ export default {
const selectedAgentById = this.agentsList.find(
(agent) => agent.value === this.selectedAgentId,
);
return selectedAgentById?.text;
return selectedAgentById?.text || this.environment.clusterAgent?.name;
},
filteredAgentsList() {
const lowerCasedSearchTerm = this.searchTerm.toLowerCase();

View File

@ -15,7 +15,7 @@ export default (el) => {
updateEnvironmentPath,
protectedEnvironmentSettingsPath,
projectPath,
environmentName,
environment,
} = el.dataset;
return new Vue({
@ -26,10 +26,13 @@ export default (el) => {
updateEnvironmentPath,
protectedEnvironmentSettingsPath,
projectPath,
environmentName,
},
render(h) {
return h(EditEnvironment);
return h(EditEnvironment, {
props: {
environment: JSON.parse(environment),
},
});
},
});
};

View File

@ -5,6 +5,10 @@ query getEnvironment($projectFullPath: ID!, $environmentName: String) {
id
name
externalUrl
clusterAgent {
id
name
}
}
}
}

View File

@ -1,17 +0,0 @@
# frozen_string_literal: true
module Mutations
module Ci
# TODO: Remove after 16.0, see https://gitlab.com/gitlab-org/gitlab/-/issues/361801#note_1373963840
class CiCdSettingsUpdate < ProjectCiCdSettingsUpdate
graphql_name 'CiCdSettingsUpdate'
def ready?(**args)
raise Gitlab::Graphql::Errors::ResourceNotAvailable, '`remove_cicd_settings_update` feature flag is enabled.' \
if Feature.enabled?(:remove_cicd_settings_update)
super
end
end
end
end

View File

@ -142,11 +142,6 @@ module Types
mount_mutation Mutations::Ci::PipelineSchedule::Play
mount_mutation Mutations::Ci::PipelineSchedule::Create
mount_mutation Mutations::Ci::PipelineSchedule::Update
mount_mutation Mutations::Ci::CiCdSettingsUpdate, deprecated: {
reason: :renamed,
replacement: 'ProjectCiCdSettingsUpdate',
milestone: '15.0'
}
mount_mutation Mutations::Ci::ProjectCiCdSettingsUpdate
mount_mutation Mutations::Ci::Job::ArtifactsDestroy
mount_mutation Mutations::Ci::Job::Play

View File

@ -37,6 +37,8 @@ module SearchHelper
end
def resource_results(term)
return [] if term.length < Gitlab::Search::Params::MIN_TERM_LENGTH
[
groups_autocomplete(term),
projects_autocomplete(term),
@ -307,7 +309,7 @@ module SearchHelper
# Autocomplete results for the current user's groups
# rubocop: disable CodeReuse/ActiveRecord
def groups_autocomplete(term, limit = 5)
current_user.authorized_groups.order_id_desc.search(term).limit(limit).map do |group|
current_user.authorized_groups.order_id_desc.search(term, use_minimum_char_limit: false).limit(limit).map do |group|
{
category: "Groups",
id: group.id,
@ -341,7 +343,7 @@ module SearchHelper
# Autocomplete results for the current user's projects
# rubocop: disable CodeReuse/ActiveRecord
def projects_autocomplete(term, limit = 5)
current_user.authorized_projects.order_id_desc.search(term, include_namespace: true)
current_user.authorized_projects.order_id_desc.search(term, include_namespace: true, use_minimum_char_limit: false)
.sorted_by_stars_desc.non_archived.limit(limit).map do |p|
{
category: "Projects",
@ -357,9 +359,11 @@ module SearchHelper
def users_autocomplete(term, limit = 5)
return [] unless current_user && Ability.allowed?(current_user, :read_users_list)
SearchService
.new(current_user, { scope: 'users', per_page: limit, search: term })
.search_objects
is_current_user_admin = current_user.can_admin_all_resources?
scope = is_current_user_admin ? User.all : User.without_forbidden_states
scope.search(term, with_private_emails: is_current_user_admin, use_minimum_char_limit: false)
.limit(limit)
.map do |user|
{
category: "Users",

View File

@ -3,6 +3,7 @@
module Clusters
class Agent < ApplicationRecord
include FromUnion
include Gitlab::Utils::StrongMemoize
self.table_name = 'cluster_agents'
@ -67,10 +68,8 @@ module Clusters
return false unless user
return false unless ::Feature.enabled?(:expose_authorized_cluster_agents, project)
::Project.from_union(
all_ci_access_authorized_projects_for(user).limit(1),
all_ci_access_authorized_namespaces_for(user).limit(1)
).exists?
all_ci_access_authorized_projects_for(user).exists? ||
all_ci_access_authorized_namespaces_for(user).exists?
end
def user_access_authorized_for?(user)
@ -95,47 +94,35 @@ module Clusters
def all_ci_access_authorized_projects_for(user)
::Project.joins(:ci_access_project_authorizations)
.joins(:project_authorizations)
.joins(:namespace)
.where(agent_project_authorizations: { agent_id: id })
.where(project_authorizations: { user_id: user.id, access_level: Gitlab::Access::DEVELOPER.. })
.where('namespaces.traversal_ids @> ARRAY[?]', root_namespace.id)
end
def all_ci_access_authorized_namespaces_for(user)
::Project.with(root_namespace_cte.to_arel)
.with(all_ci_access_authorized_namespaces_cte.to_arel)
::Project.with(all_ci_access_authorized_namespaces_cte.to_arel)
.joins('INNER JOIN all_authorized_namespaces ON all_authorized_namespaces.id = projects.namespace_id')
.joins(:project_authorizations)
.where(project_authorizations: { user_id: user.id, access_level: Gitlab::Access::DEVELOPER.. })
end
def root_namespace_cte
Gitlab::SQL::CTE.new(:root_namespace, root_namespace.to_sql)
end
def all_ci_access_authorized_namespaces_cte
Gitlab::SQL::CTE.new(:all_authorized_namespaces, all_ci_access_authorized_namespaces.to_sql)
end
def all_ci_access_authorized_namespaces
Namespace.select("traversal_ids[array_length(traversal_ids, 1)] AS id")
.joins("INNER JOIN root_namespace ON " \
"namespaces.traversal_ids @> ARRAY[root_namespace.root_id]")
.joins("INNER JOIN agent_group_authorizations ON " \
"namespaces.traversal_ids @> ARRAY[agent_group_authorizations.group_id::integer]")
.where(agent_group_authorizations: { agent_id: id })
.where('namespaces.traversal_ids @> ARRAY[?]', root_namespace.id)
end
def root_namespace
Namespace.select("traversal_ids[1] AS root_id")
.where("traversal_ids @> ARRAY(?)", project_namespace)
.limit(1)
end
def project_namespace
::Project.select('namespace_id')
.joins(:cluster_agents)
.where(cluster_agents: { id: id })
.limit(1)
project.root_namespace
end
strong_memoize_attr :root_namespace
end
end

View File

@ -230,11 +230,15 @@ class Namespace < ApplicationRecord
# query - The search query as a String.
#
# Returns an ActiveRecord::Relation.
def search(query, include_parents: false)
def search(query, include_parents: false, use_minimum_char_limit: true)
if include_parents
without_project_namespaces.where(id: Route.for_routable_type(Namespace.name).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name]]).select(:source_id))
without_project_namespaces
.where(id: Route.for_routable_type(Namespace.name)
.fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name]],
use_minimum_char_limit: use_minimum_char_limit)
.select(:source_id))
else
without_project_namespaces.fuzzy_search(query, [:path, :name])
without_project_namespaces.fuzzy_search(query, [:path, :name], use_minimum_char_limit: use_minimum_char_limit)
end
end

View File

@ -900,11 +900,12 @@ class Project < ApplicationRecord
# search.
#
# query - The search query as a String.
def search(query, include_namespace: false)
def search(query, include_namespace: false, use_minimum_char_limit: true)
if include_namespace
joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description])
joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description],
use_minimum_char_limit: use_minimum_char_limit)
else
fuzzy_search(query, [:path, :name, :description])
fuzzy_search(query, [:path, :name, :description], use_minimum_char_limit: use_minimum_char_limit)
end
end

View File

@ -5,4 +5,4 @@
update_environment_path: project_environment_path(@project, @environment),
protected_environment_settings_path: (project_settings_ci_cd_path(@project, anchor: 'js-protected-environments-settings') if @project.licensed_feature_available?(:protected_environments)),
project_path: @project.full_path,
environment_name: @environment.name } }
environment: environment_data(@environment) } }

View File

@ -1,8 +1,8 @@
---
name: remove_cicd_settings_update
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119061
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/409379
milestone: '16.0'
name: value_stream_dashboard_on_off_setting
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120610
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/411223
milestone: '16.1'
type: development
group: group::runner
group: group::optimize
default_enabled: false

View File

@ -1514,35 +1514,6 @@ Input type: `CiAiGenerateConfigInput`
| <a id="mutationciaigenerateconfigerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationciaigenerateconfigusermessage"></a>`userMessage` | [`AiMessageType`](#aimessagetype) | User chat message. |
### `Mutation.ciCdSettingsUpdate`
WARNING:
**Deprecated** in 15.0.
This was renamed.
Use: `ProjectCiCdSettingsUpdate`.
Input type: `CiCdSettingsUpdateInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcicdsettingsupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcicdsettingsupdatefullpath"></a>`fullPath` | [`ID!`](#id) | Full Path of the project the settings belong to. |
| <a id="mutationcicdsettingsupdateinboundjobtokenscopeenabled"></a>`inboundJobTokenScopeEnabled` | [`Boolean`](#boolean) | Indicates CI/CD job tokens generated in other projects have restricted access to this project. |
| <a id="mutationcicdsettingsupdatejobtokenscopeenabled"></a>`jobTokenScopeEnabled` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated:** Outbound job token scope is being removed. This field can now only be set to false. Deprecated in 16.0. |
| <a id="mutationcicdsettingsupdatekeeplatestartifact"></a>`keepLatestArtifact` | [`Boolean`](#boolean) | Indicates if the latest artifact should be kept for the project. |
| <a id="mutationcicdsettingsupdatemergepipelinesenabled"></a>`mergePipelinesEnabled` | [`Boolean`](#boolean) | Indicates if merge pipelines are enabled for the project. |
| <a id="mutationcicdsettingsupdatemergetrainsenabled"></a>`mergeTrainsEnabled` | [`Boolean`](#boolean) | Indicates if merge trains are enabled for the project. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcicdsettingsupdatecicdsettings"></a>`ciCdSettings` | [`ProjectCiCdSetting!`](#projectcicdsetting) | CI/CD settings after mutation. |
| <a id="mutationcicdsettingsupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcicdsettingsupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.ciJobTokenScopeAddProject`
Input type: `CiJobTokenScopeAddProjectInput`

View File

@ -8,7 +8,6 @@ module Gitlab
ABUSIVE_TERM_SIZE = 100
ALLOWED_CHARS_REGEX = %r{\A[[:alnum:]_\-\/\.!]+\z}.freeze
MINIMUM_SEARCH_CHARS = 2
ALLOWED_SCOPES = %w(
blobs
@ -50,7 +49,8 @@ module Gitlab
exclusion: { in: STOP_WORDS, message: 'stopword only abusive search detected' }, allow_blank: true
validates :query_string,
length: { minimum: MINIMUM_SEARCH_CHARS, message: 'abusive tiny search detected' }, unless: :skip_tiny_search_validation?, allow_blank: true
length: { minimum: Params::MIN_TERM_LENGTH, message: 'abusive tiny search detected' },
unless: :skip_tiny_search_validation?, allow_blank: true
validates :query_string,
no_abusive_term_length: { maximum: ABUSIVE_TERM_SIZE, maximum_for_url: ABUSIVE_TERM_SIZE * 2 }

View File

@ -7,7 +7,7 @@ module Gitlab
SEARCH_CHAR_LIMIT = 4096
SEARCH_TERM_LIMIT = 64
MIN_TERM_LENGTH = 3
MIN_TERM_LENGTH = 2
# Generic validation
validates :query_string, length: { maximum: SEARCH_CHAR_LIMIT }

View File

@ -21599,6 +21599,9 @@ msgstr ""
msgid "GroupSettings|Email notifications are disabled"
msgstr ""
msgid "GroupSettings|Enable overview background aggregation for Value Streams Dashboard"
msgstr ""
msgid "GroupSettings|Export group"
msgstr ""
@ -21692,6 +21695,9 @@ msgstr ""
msgid "GroupSettings|Users can create %{link_start_project}project access tokens%{link_end} and %{link_start_group}group access tokens%{link_end} in this group"
msgstr ""
msgid "GroupSettings|Value Streams Dashboard"
msgstr ""
msgid "GroupSettings|What are badges?"
msgstr ""

View File

@ -2,40 +2,60 @@
module RuboCop
module Cop
# Cop that blacklists the usage of `ActiveRecord::Base.ignored_columns=` directly
# Cop that flags the usage of `ActiveRecord::Base.ignored_columns=` directly
#
# @example
# # bad
# class User < ApplicationRecord
# self.ignored_columns = [:name]
# self.ignored_columns += [:full_name]
# end
#
# # good
# class User < ApplicationRecord
# include IgnorableColumns
#
# ignore_column :name, remove_after: '2023-05-22', remove_with: '16.0'
# ignore_column :full_name, remove_after: '2023-05-22', remove_with: '16.0'
# end
class IgnoredColumns < RuboCop::Cop::Base
USE_CONCERN_MSG = 'Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.'
WRONG_MODEL_MSG = 'If the model exists in CE and EE, the column has to be ignored ' \
'in the CE model. If the model only exists in EE, then it has to be added there.'
USE_CONCERN_ADD_MSG = 'Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.'
USE_CONCERN_SET_MSG = 'Use `IgnoredColumns` concern instead of setting `self.ignored_columns`.'
WRONG_MODEL_MSG = <<~MSG
If the model exists in CE and EE, the column has to be ignored
in the CE model. If the model only exists in EE, then it has to be added there.
MSG
def_node_matcher :ignored_columns?, <<~PATTERN
RESTRICT_ON_SEND = %i[ignored_columns ignored_columns= ignore_column ignore_columns].freeze
def_node_matcher :ignored_columns_add?, <<~PATTERN
(send (self) :ignored_columns)
PATTERN
def_node_matcher :ignore_columns?, <<~PATTERN
(send nil? :ignore_columns ...)
def_node_matcher :ignored_columns_set?, <<~PATTERN
(send (self) :ignored_columns= ...)
PATTERN
def_node_matcher :ignore_column?, <<~PATTERN
(send nil? :ignore_column ...)
def_node_matcher :using_ignore_columns?, <<~PATTERN
(send nil? {:ignore_columns :ignore_column}...)
PATTERN
def on_send(node)
if ignored_columns?(node)
add_offense(node, message: USE_CONCERN_MSG)
if ignored_columns_add?(node)
add_offense(node.loc.selector, message: USE_CONCERN_ADD_MSG)
end
if using_ignore?(node) && used_in_wrong_model?
if ignored_columns_set?(node)
add_offense(node.loc.selector, message: USE_CONCERN_SET_MSG)
end
if using_ignore_columns?(node) && used_in_wrong_model?
add_offense(node, message: WRONG_MODEL_MSG)
end
end
private
def using_ignore?(node)
ignore_columns?(node) || ignore_column?(node)
end
def used_in_wrong_model?
file_path = processed_source.file_path

View File

@ -18,7 +18,12 @@ jest.mock('~/lib/utils/url_utility');
jest.mock('~/alert');
const newExternalUrl = 'https://google.ca';
const environment = { id: '1', name: 'foo', externalUrl: 'https://foo.example.com' };
const environment = {
id: '1',
name: 'foo',
externalUrl: 'https://foo.example.com',
clusterAgent: null,
};
const resolvedEnvironment = { project: { id: '1', environment } };
const environmentUpdate = {
environment: { id: '1', path: 'path/to/environment', clusterAgentId: null },
@ -34,40 +39,43 @@ const provide = {
updateEnvironmentPath: '/projects/environments/1',
protectedEnvironmentSettingsPath: '/projects/1/settings/ci_cd',
projectPath: '/path/to/project',
environmentName: environment.name,
};
describe('~/environments/components/edit.vue', () => {
let wrapper;
let mock;
const createMockApolloProvider = (mutationResult, environmentSettingsToGraphql) => {
const createMockApolloProvider = (mutationResult) => {
Vue.use(VueApollo);
const mocks = [[getEnvironment, jest.fn().mockResolvedValue({ data: resolvedEnvironment })]];
if (environmentSettingsToGraphql) {
mocks.push([
const mocks = [
[getEnvironment, jest.fn().mockResolvedValue({ data: resolvedEnvironment })],
[
updateEnvironment,
jest.fn().mockResolvedValue({ data: { environmentUpdate: mutationResult } }),
]);
}
],
];
return createMockApollo(mocks);
};
const createWrapper = async ({
mutationResult = environmentUpdate,
environmentSettingsToGraphql = false,
} = {}) => {
const createWrapper = () => {
wrapper = mountExtended(EditEnvironment, {
propsData: { environment: { id: '1', name: 'foo', external_url: 'https://foo.example.com' } },
provide,
});
};
const createWrapperWithApollo = async ({ mutationResult = environmentUpdate } = {}) => {
wrapper = mountExtended(EditEnvironment, {
propsData: { environment: {} },
provide: {
...provide,
glFeatures: {
environmentSettingsToGraphql,
environmentSettingsToGraphql: true,
},
},
apolloProvider: createMockApolloProvider(mutationResult, environmentSettingsToGraphql),
apolloProvider: createMockApolloProvider(mutationResult),
});
await waitForPromises();
@ -109,9 +117,18 @@ describe('~/environments/components/edit.vue', () => {
});
describe('when environmentSettingsToGraphql feature is enabled', () => {
describe('when mounted', () => {
beforeEach(() => {
createWrapperWithApollo();
});
it('renders loading icon when environment query is loading', () => {
expect(showsLoading()).toBe(true);
});
});
describe('when mutation successful', () => {
beforeEach(async () => {
await createWrapper({ environmentSettingsToGraphql: true });
await createWrapperWithApollo();
});
it('shows loader after form is submitted', async () => {
@ -132,9 +149,8 @@ describe('~/environments/components/edit.vue', () => {
describe('when mutation failed', () => {
beforeEach(async () => {
await createWrapper({
await createWrapperWithApollo({
mutationResult: environmentUpdateError,
environmentSettingsToGraphql: true,
});
});
@ -149,9 +165,9 @@ describe('~/environments/components/edit.vue', () => {
});
describe('when environmentSettingsToGraphql feature is disabled', () => {
beforeEach(async () => {
beforeEach(() => {
mock = new MockAdapter(axios);
await createWrapper();
createWrapper();
});
afterEach(() => {
@ -202,14 +218,4 @@ describe('~/environments/components/edit.vue', () => {
expect(showsLoading()).toBe(false);
});
});
describe('when environment query is loading', () => {
beforeEach(() => {
createWrapper();
});
it('renders loading icon', () => {
expect(showsLoading()).toBe(true);
});
});
});

View File

@ -270,4 +270,21 @@ describe('~/environments/components/form.vue', () => {
]);
});
});
describe('when environment has an associated agent', () => {
const environmentWithAgent = {
...DEFAULT_PROPS.environment,
clusterAgent: { id: '1', name: 'agent-1' },
clusterAgentId: '1',
};
beforeEach(() => {
wrapper = createWrapperWithApollo({
propsData: { environment: environmentWithAgent },
});
});
it('updates agent selector field with the name of the associated agent', () => {
expect(findAgentSelector().props('toggleText')).toBe('agent-1');
});
});
});

View File

@ -64,13 +64,6 @@ RSpec.describe SearchHelper, feature_category: :global_search do
let_it_be(:another_user) { create(:user, name: 'Jane Doe') }
let(:term) { 'jane' }
it 'makes a call to SearchService' do
params = { search: term, per_page: 5, scope: 'users' }
expect(SearchService).to receive(:new).with(current_user, params).and_call_original
search_autocomplete_opts(term)
end
it 'returns users matching the term' do
result = search_autocomplete_opts(term)
expect(result.size).to eq(1)
@ -88,6 +81,68 @@ RSpec.describe SearchHelper, feature_category: :global_search do
end
end
describe 'permissions' do
let(:term) { 'jane@doe' }
let(:private_email_user) { create(:user, email: term) }
let(:public_email_user) { create(:user, :public_email, email: term) }
let(:banned_user) { create(:user, :banned, email: term) }
let(:user_with_other_email) { create(:user, email: 'something@else') }
let(:secondary_email) { create(:email, :confirmed, user: user_with_other_email, email: term) }
let(:ids) { search_autocomplete_opts(term).pluck(:id) }
context 'when current_user is an admin' do
before do
allow(current_user).to receive(:can_admin_all_resources?).and_return(true)
end
it 'includes users with matching public emails' do
public_email_user
expect(ids).to include(public_email_user.id)
end
it 'includes users in forbidden states' do
banned_user
expect(ids).to include(banned_user.id)
end
it 'includes users without matching public emails but with matching private emails' do
private_email_user
expect(ids).to include(private_email_user.id)
end
it 'includes users matching on secondary email' do
secondary_email
expect(ids).to include(secondary_email.user_id)
end
end
context 'when current_user is not an admin' do
before do
allow(current_user).to receive(:can_admin_all_resources?).and_return(false)
end
it 'includes users with matching public emails' do
public_email_user
expect(ids).to include(public_email_user.id)
end
it 'does not include users in forbidden states' do
banned_user
expect(ids).not_to include(banned_user.id)
end
it 'does not include users without matching public emails but with matching private emails' do
private_email_user
expect(ids).not_to include(private_email_user.id)
end
it 'does not include users matching on secondary email' do
secondary_email
expect(ids).not_to include(secondary_email.user_id)
end
end
end
context 'with limiting' do
let!(:users) { create_list(:user, 6, name: 'Jane Doe') }
@ -268,7 +323,7 @@ RSpec.describe SearchHelper, feature_category: :global_search do
expect(results.first).to include({
category: 'In this project',
id: issue.id,
label: 'test title (#1)',
label: "test title (##{issue.iid})",
url: ::Gitlab::Routing.url_helpers.project_issue_path(issue.project, issue),
avatar_url: '' # project has no avatar
})
@ -306,8 +361,47 @@ RSpec.describe SearchHelper, feature_category: :global_search do
end
end
describe 'resource_results' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user, name: 'User') }
let_it_be(:group) { create(:group, name: 'Group') }
let_it_be(:project) { create(:project, name: 'Project') }
let!(:issue) { create(:issue, project: project) }
let(:issue_iid) { "\##{issue.iid}" }
before do
allow(self).to receive(:current_user).and_return(user)
group.add_owner(user)
project.add_owner(user)
@project = project
end
where(:term, :size, :category) do
'g' | 0 | 'Groups'
'gr' | 1 | 'Groups'
'gro' | 1 | 'Groups'
'p' | 0 | 'Projects'
'pr' | 1 | 'Projects'
'pro' | 1 | 'Projects'
'u' | 0 | 'Users'
'us' | 1 | 'Users'
'use' | 1 | 'Users'
ref(:issue_iid) | 1 | 'In this project'
end
with_them do
it 'returns results only if the term is more than or equal to Gitlab::Search::Params::MIN_TERM_LENGTH' do
results = resource_results(term)
expect(results.size).to eq(size)
expect(results.first[:category]).to eq(category) if size == 1
end
end
end
describe 'projects_autocomplete' do
let_it_be(:user) { create(:user, name: "madelein") }
let_it_be(:user) { create(:user) }
let_it_be(:project_1) { create(:project, name: 'test 1') }
let_it_be(:project_2) { create(:project, name: 'test 2') }
let(:search_term) { 'test' }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Search::AbuseDetection do
RSpec.describe Gitlab::Search::AbuseDetection, feature_category: :global_search do
subject { described_class.new(params) }
let(:params) { { query_string: 'foobar' } }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Search::Params do
RSpec.describe Gitlab::Search::Params, feature_category: :global_search do
subject { described_class.new(params, detect_abuse: detect_abuse) }
let(:search) { 'search' }

View File

@ -1067,6 +1067,18 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
expect(described_class.search('PARENT-PATH/NEW-PATH', include_parents: true)).to eq([second_group])
end
it 'defaults use_minimum_char_limit to true' do
expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: true).once
described_class.search('my namespace')
end
it 'passes use_minimum_char_limit if it is set' do
expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: false).once
described_class.search('my namespace', use_minimum_char_limit: false)
end
context 'with project namespaces' do
let_it_be(:project) { create(:project, namespace: parent_group, path: 'some-new-path') }
let_it_be(:project_namespace) { project.project_namespace }

View File

@ -2997,6 +2997,18 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
expect(described_class.search(project.path.upcase)).to eq([project])
end
it 'defaults use_minimum_char_limit to true' do
expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: true).once
described_class.search('kitten')
end
it 'passes use_minimum_char_limit if it is set' do
expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: false).once
described_class.search('kitten', use_minimum_char_limit: false)
end
context 'when include_namespace is true' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }

View File

@ -64,35 +64,6 @@ RSpec.describe 'ProjectCiCdSettingsUpdate', feature_category: :continuous_integr
expect(project.keep_latest_artifact).to eq(false)
end
describe 'ci_cd_settings_update deprecated mutation' do
let(:mutation) { graphql_mutation(:ci_cd_settings_update, variables) }
it 'returns error' do
post_graphql_mutation(mutation, current_user: user)
expect(graphql_errors).to(
include(
hash_including('message' => '`remove_cicd_settings_update` feature flag is enabled.')
)
)
end
context 'when remove_cicd_settings_update FF is disabled' do
before do
stub_feature_flags(remove_cicd_settings_update: false)
end
it 'updates ci cd settings' do
post_graphql_mutation(mutation, current_user: user)
project.reload
expect(response).to have_gitlab_http_status(:success)
expect(project.keep_latest_artifact).to eq(false)
end
end
end
it 'allows setting job_token_scope_enabled to false' do
post_graphql_mutation(mutation, current_user: user)

View File

@ -5,12 +5,12 @@ require 'spec_helper'
RSpec.describe API::Release::Links, feature_category: :release_orchestration do
include Ci::JobTokenScopeHelpers
let(:project) { create(:project, :repository, :private) }
let(:maintainer) { create(:user) }
let(:developer) { create(:user) }
let(:reporter) { create(:user) }
let(:non_project_member) { create(:user) }
let(:commit) { create(:commit, project: project) }
let_it_be_with_reload(:project) { create(:project, :repository, :private) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
let_it_be(:reporter) { create(:user) }
let_it_be(:non_project_member) { create(:user) }
let_it_be(:commit) { create(:commit, project: project) }
let!(:release) do
create(:release,
@ -19,7 +19,7 @@ RSpec.describe API::Release::Links, feature_category: :release_orchestration do
author: maintainer)
end
before do
before_all do
project.add_maintainer(maintainer)
project.add_developer(developer)
project.add_reporter(reporter)

View File

@ -3,12 +3,21 @@
require 'rubocop_spec_helper'
require_relative '../../../rubocop/cop/ignored_columns'
RSpec.describe RuboCop::Cop::IgnoredColumns do
it 'flags direct use of ignored_columns instead of the IgnoredColumns concern' do
RSpec.describe RuboCop::Cop::IgnoredColumns, feature_category: :database do
it 'flags use of `self.ignored_columns +=` instead of the IgnoredColumns concern' do
expect_offense(<<~RUBY)
class Foo < ApplicationRecord
self.ignored_columns += %i[id]
^^^^^^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.
^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.
end
RUBY
end
it 'flags use of `self.ignored_columns =` instead of the IgnoredColumns concern' do
expect_offense(<<~RUBY)
class Foo < ApplicationRecord
self.ignored_columns = %i[id]
^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of setting `self.ignored_columns`.
end
RUBY
end
@ -16,7 +25,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do
context 'when only CE model exist' do
let(:file_path) { full_path('app/models/bar.rb') }
it 'does not flag ignore_columns usage in CE model' do
it 'does not flag `ignore_columns` usage in CE model' do
expect_no_offenses(<<~RUBY, file_path)
class Bar < ApplicationRecord
ignore_columns :foo, remove_with: '14.3', remove_after: '2021-09-22'
@ -24,7 +33,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do
RUBY
end
it 'flags ignore_column usage in EE model' do
it 'does not flag `ignore_column` usage in CE model' do
expect_no_offenses(<<~RUBY, file_path)
class Baz < ApplicationRecord
ignore_column :bar, remove_with: '14.3', remove_after: '2021-09-22'
@ -40,7 +49,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do
allow(File).to receive(:exist?).with(full_path('app/models/bar.rb')).and_return(false)
end
it 'flags ignore_columns usage in EE model' do
it 'does not flag `ignore_columns` usage in EE model' do
expect_no_offenses(<<~RUBY, file_path)
class Bar < ApplicationRecord
ignore_columns :foo, remove_with: '14.3', remove_after: '2021-09-22'
@ -48,7 +57,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do
RUBY
end
it 'flags ignore_column usage in EE model' do
it 'does not flag `ignore_column` usage in EE model' do
expect_no_offenses(<<~RUBY, file_path)
class Bar < ApplicationRecord
ignore_column :foo, remove_with: '14.3', remove_after: '2021-09-22'
@ -64,7 +73,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do
allow(File).to receive(:exist?).with(full_path('app/models/bar.rb')).and_return(true)
end
it 'flags ignore_columns usage in EE model' do
it 'flags `ignore_columns` usage in EE model' do
expect_offense(<<~RUBY, file_path)
class Bar < ApplicationRecord
ignore_columns :foo, remove_with: '14.3', remove_after: '2021-09-22'
@ -73,7 +82,7 @@ RSpec.describe RuboCop::Cop::IgnoredColumns do
RUBY
end
it 'flags ignore_column usage in EE model' do
it 'flags `ignore_column` usage in EE model' do
expect_offense(<<~RUBY, file_path)
class Bar < ApplicationRecord
ignore_column :foo, remove_with: '14.3', remove_after: '2021-09-22'