diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-20 06:20:24 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-20 06:20:24 +0300 |
commit | d99f2ee027ec0f098207ce7df55feac0021a36c1 (patch) | |
tree | 10229774f2d7f11d3bd1981b8999e4be41b42114 | |
parent | 27272e0696cedeed1f55f3ce09983fe8e849f7ba (diff) |
Add latest changes from gitlab-org/gitlab@master
25 files changed, 490 insertions, 141 deletions
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 2a781c037f6..57eea577419 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -29,7 +29,7 @@ # repository_storage: string # not_aimed_for_deletion: boolean # full_paths: string[] -# organization_id: int +# organization: Scope the groups to the Organizations::Organization # class ProjectsFinder < UnionFinder include CustomAttributesFilter @@ -96,7 +96,7 @@ class ProjectsFinder < UnionFinder collection = by_language(collection) collection = by_feature_availability(collection) collection = by_updated_at(collection) - collection = by_organization_id(collection) + collection = by_organization(collection) by_repository_storage(collection) end @@ -295,8 +295,11 @@ class ProjectsFinder < UnionFinder items end - def by_organization_id(items) - params[:organization_id].present? ? items.in_organization(params[:organization_id]) : items + def by_organization(items) + organization = params[:organization] + return items unless organization + + items.in_organization(organization) end def finder_params diff --git a/app/graphql/mutations/branch_rules/create.rb b/app/graphql/mutations/branch_rules/create.rb new file mode 100644 index 00000000000..c478d981c33 --- /dev/null +++ b/app/graphql/mutations/branch_rules/create.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Mutations + module BranchRules + class Create < BaseMutation + graphql_name 'BranchRuleCreate' + + argument :project_path, GraphQL::Types::ID, + required: true, + description: 'Full path to the project that the branch is associated with.' + + argument :name, GraphQL::Types::String, + required: true, + description: 'Branch name, with wildcards, for the branch rules.' + + field :branch_rule, + Types::Projects::BranchRuleType, + null: true, + description: 'Branch rule after mutation.' + + def resolve(project_path:, name:) + project = Project.find_by_full_path(project_path) + + service_params = protected_branch_params(name) + protected_branch = ::ProtectedBranches::CreateService.new(project, current_user, service_params).execute + + if protected_branch.persisted? + { + branch_rule: ::Projects::BranchRule.new(project, protected_branch), + errors: [] + } + else + { errors: errors_on_object(protected_branch) } + end + rescue Gitlab::Access::AccessDeniedError + raise_resource_not_available_error! + end + + def protected_branch_params(name) + { + name: name, + push_access_levels_attributes: access_level_attributes(:push), + merge_access_levels_attributes: access_level_attributes(:merge) + } + end + + def access_level_attributes(type) + ::ProtectedRefs::AccessLevelParams.new( + type, + {}, + with_defaults: true + ).access_levels + end + end + end +end diff --git a/app/graphql/resolvers/organizations/projects_resolver.rb b/app/graphql/resolvers/organizations/projects_resolver.rb new file mode 100644 index 00000000000..836fe0ae059 --- /dev/null +++ b/app/graphql/resolvers/organizations/projects_resolver.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Resolvers + module Organizations + class ProjectsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type Types::ProjectType, null: true + + authorize :read_project + + alias_method :organization, :object + + def resolve + ::ProjectsFinder.new(current_user: current_user, params: { organization: organization }).execute + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 590bc0ed282..4c987c657ef 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -111,6 +111,7 @@ module Types mount_mutation Mutations::Projects::SyncFork, calls_gitaly: true, alpha: { milestone: '15.9' } mount_mutation Mutations::Projects::Star, alpha: { milestone: '16.7' } mount_mutation Mutations::BranchRules::Update, alpha: { milestone: '16.7' } + mount_mutation Mutations::BranchRules::Create, alpha: { milestone: '16.7' } mount_mutation Mutations::Releases::Create mount_mutation Mutations::Releases::Update mount_mutation Mutations::Releases::Delete diff --git a/app/graphql/types/organizations/organization_type.rb b/app/graphql/types/organizations/organization_type.rb index 379bf9956a3..d36c92541ef 100644 --- a/app/graphql/types/organizations/organization_type.rb +++ b/app/graphql/types/organizations/organization_type.rb @@ -43,6 +43,10 @@ module Types null: false, description: 'Path of the organization.', alpha: { milestone: '16.4' } + field :projects, Types::ProjectType.connection_type, null: false, + description: 'Projects within this organization that the user has access to.', + alpha: { milestone: '16.8' }, + resolver: ::Resolvers::Organizations::ProjectsResolver field :web_url, GraphQL::Types::String, null: false, diff --git a/app/models/integrations/squash_tm.rb b/app/models/integrations/squash_tm.rb index 1b4ab152b1d..7aaef0c22cc 100644 --- a/app/models/integrations/squash_tm.rb +++ b/app/models/integrations/squash_tm.rb @@ -7,12 +7,14 @@ module Integrations field :url, placeholder: 'https://your-instance.squashcloud.io/squash/plugin/xsquash4gitlab/webhook/issue', title: -> { s_('SquashTmIntegration|Squash TM webhook URL') }, + description: -> { s_('URL of the Squash TM webhook.') }, exposes_secrets: true, required: true field :token, type: :password, title: -> { s_('SquashTmIntegration|Secret token (optional)') }, + description: -> { s_('Secret token.') }, non_empty_password_title: -> { s_('ProjectService|Enter new token') }, non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') }, required: false diff --git a/doc/administration/gitaly/troubleshooting.md b/doc/administration/gitaly/troubleshooting.md index 7d0255a3efb..211b5d67ef4 100644 --- a/doc/administration/gitaly/troubleshooting.md +++ b/doc/administration/gitaly/troubleshooting.md @@ -441,6 +441,23 @@ To resolve this, remove the `noexec` option from the file system mount. An alter Because Gitaly commit signing is headless and not associated with a specific user, the GPG signing key must be created without a passphrase, or the passphrase must be removed before export. +### Gitaly logs show errors in `info` messages + +Because of a bug [introduced](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/6201) in GitLab 16.3, additional entries were written to the +[Gitaly logs](../logs/index.md#gitaly-logs). These log entries contained `"level":"info"` but the `msg` string appeared to contain an error. + +For example: + +```json +{"level":"info","msg":"[core] [Server #3] grpc: Server.Serve failed to create ServerTransport: connection error: desc = \"ServerHandshake(\\\"x.x.x.x:x\\\") failed: wrapped server handshake: EOF\"","pid":6145,"system":"system","time":"2023-12-14T21:20:39.999Z"} +``` + +The reason for this log entry is that the underlying gRPC library sometimes output verbose transportation logs. These log entries appear to be errors but are, in general, +safe to ignore. + +This bug was [fixed](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/6513/) in GitLab 16.4.5, 16.5.5, and 16.6.0, which prevents these types of messages from +being written to the Gitaly logs. + ## Troubleshoot Praefect (Gitaly Cluster) The following sections provide possible solutions to Gitaly Cluster errors. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index ee44d76a773..7f5b6cf52c9 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1945,6 +1945,30 @@ Input type: `BoardListUpdateLimitMetricsInput` | <a id="mutationboardlistupdatelimitmetricserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | <a id="mutationboardlistupdatelimitmetricslist"></a>`list` | [`BoardList`](#boardlist) | Updated list. | +### `Mutation.branchRuleCreate` + +WARNING: +**Introduced** in 16.7. +This feature is an Experiment. It can be changed or removed at any time. + +Input type: `BranchRuleCreateInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationbranchrulecreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationbranchrulecreatename"></a>`name` | [`String!`](#string) | Branch name, with wildcards, for the branch rules. | +| <a id="mutationbranchrulecreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path to the project that the branch is associated with. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationbranchrulecreatebranchrule"></a>`branchRule` | [`BranchRule`](#branchrule) | Branch rule after mutation. | +| <a id="mutationbranchrulecreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationbranchrulecreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | + ### `Mutation.branchRuleUpdate` WARNING: @@ -23335,6 +23359,7 @@ Active period time range for on-call rotation. | <a id="organizationname"></a>`name` **{warning-solid}** | [`String!`](#string) | **Introduced** in 16.4. This feature is an Experiment. It can be changed or removed at any time. Name of the organization. | | <a id="organizationorganizationusers"></a>`organizationUsers` **{warning-solid}** | [`OrganizationUserConnection!`](#organizationuserconnection) | **Introduced** in 16.4. This feature is an Experiment. It can be changed or removed at any time. Users with access to the organization. | | <a id="organizationpath"></a>`path` **{warning-solid}** | [`String!`](#string) | **Introduced** in 16.4. This feature is an Experiment. It can be changed or removed at any time. Path of the organization. | +| <a id="organizationprojects"></a>`projects` **{warning-solid}** | [`ProjectConnection!`](#projectconnection) | **Introduced** in 16.8. This feature is an Experiment. It can be changed or removed at any time. Projects within this organization that the user has access to. | | <a id="organizationweburl"></a>`webUrl` **{warning-solid}** | [`String!`](#string) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Web URL of the organization. | #### Fields with arguments diff --git a/doc/api/integrations.md b/doc/api/integrations.md index f713d845762..ab0337e25c8 100644 --- a/doc/api/integrations.md +++ b/doc/api/integrations.md @@ -1423,7 +1423,7 @@ Parameters: | Parameter | Type | Required | Description | |-------------------------|--------|----------|-------------------------------| | `url` | string | yes | URL of the Squash TM webhook. | -| `token` | string | no | Optional token. | +| `token` | string | no | Secret token. | ### Disable Squash TM diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md index 9d0d59e27c5..1f34ab3fb1e 100644 --- a/doc/development/documentation/styleguide/word_list.md +++ b/doc/development/documentation/styleguide/word_list.md @@ -239,6 +239,22 @@ Use **text box** to refer to the UI field. Do not use **field** or **box**. For - In the **Variable name** text box, enter a value. +## branch + +Use **branch** by itself to describe a branch. For specific branches, use these terms only: + +- **default branch**: The primary branch in the repository. Users can use the UI to set the default + branch. For examples that use the default branch, use `main` instead of [`master`](#master). +- **source branch**: The branch you're merging from. +- **target branch**: The branch you're merging to. +- **current branch**: The branch you have checked out. + This branch might be the default branch, a branch you've created, a source branch, or some other branch. + +Do not use the terms **feature branch** or **merge request branch**. Be as specific as possible. For example: + +- The branch you have checked out... +- The branch you added commits to... + ## bullet Don't refer to individual items in an ordered or unordered list as **bullets**. Use **list item** instead. If you need to be less ambiguous, you can use: @@ -428,13 +444,6 @@ Instead of: - Data are collected. - The data show a performance increase. -## default branch - -Use **default branch** to refer generically to the primary branch in the repository. -Users can set the default branch by using a UI setting. - -For examples that use the default branch, use `main` instead of [`master`](#master). - ## delete Use **delete** when an object is completely deleted. **Delete** is the opposite of **create**. @@ -661,6 +670,10 @@ Instead of: - Use the merge request feature to incorporate changes into the target branch. +## feature branch + +Do not use **feature branch**. See [branch](#branch). + ## field Use **text box** instead of **field** or **box**. @@ -1083,7 +1096,7 @@ Do not use **manpower**. Use words like **workforce** or **GitLab team members** ## master -Do not use `master`. Use `main` when you need a sample [default branch name](#default-branch). +Do not use **master**. Use **main** when you need a sample [default branch name](#branch). ([Vale](../testing.md#vale) rule: [`InclusionCultural.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/InclusionCultural.yml)) ## may, might @@ -1115,6 +1128,10 @@ For **MB** and **GB**, follow the [Microsoft guidance](https://learn.microsoft.c When you add a [user account](#user-account) to a group or project, the user account becomes a **member**. +## merge request branch + +Do not use **merge request branch**. See [branch](#branch). + ## merge requests Use lowercase for **merge requests**. If you use **MR** as the acronym, spell it out on first use. diff --git a/doc/user/packages/container_registry/authenticate_with_container_registry.md b/doc/user/packages/container_registry/authenticate_with_container_registry.md index ae19a891fc9..6e1c0ded758 100644 --- a/doc/user/packages/container_registry/authenticate_with_container_registry.md +++ b/doc/user/packages/container_registry/authenticate_with_container_registry.md @@ -20,9 +20,9 @@ All of these authentication methods require the minimum scope: To authenticate, run the `docker login` command. For example: - ```shell - docker login registry.example.com -u <username> -p <token> - ``` +```shell +docker login registry.example.com -u <username> -p <token> +``` ## Use GitLab CI/CD to authenticate diff --git a/doc/user/project/repository/code_suggestions/index.md b/doc/user/project/repository/code_suggestions/index.md index cc036ae00d8..875b72354e4 100644 --- a/doc/user/project/repository/code_suggestions/index.md +++ b/doc/user/project/repository/code_suggestions/index.md @@ -18,8 +18,10 @@ Write code more efficiently by using generative AI to suggest code while you're With Code Suggestions, you get: -- Code Completion, which suggests completions the current line you are typing. These suggestions are usually low latency. -- Code Generation, which generates code based on a natural language code comment block. Responses for code generation are streamed in VS Code to begin giving results quickly. In other IDEs, response time can exceed multiple seconds. +- Code Completion, which suggests completions to the current line you are typing. These suggestions are usually low latency. +- Code Generation, which generates code based on a natural language code comment block. + - Algorithms or large code blocks may take more than 10 seconds to generate. + - Streaming of code generation responses is supported in VS Code, leading to faster average response times. Other supported IDEs offer slower response times and will return the generated code in a single block. ## Start using Code Suggestions @@ -30,10 +32,10 @@ GitLab Duo Code Suggestions are available: - In the GitLab Web IDE. <div class="video-fallback"> - <a href="https://youtu.be/wAYiy05fjF0">View how to setup and use GitLab Duo Code Suggestions</a>. + <a href="https://youtu.be/xQUlrbIWo8o">View how to setup and use GitLab Duo Code Suggestions</a>. </div> <figure class="video-container"> - <iframe src="https://www.youtube-nocookie.com/embed/wAYiy05fjF0" frameborder="0" allowfullscreen> </iframe> + <iframe src="https://www.youtube-nocookie.com/embed/xQUlrbIWo8o" frameborder="0" allowfullscreen> </iframe> </figure> During Beta, usage of Code Suggestions is governed by the [GitLab Testing Agreement](https://about.gitlab.com/handbook/legal/testing-agreement/). diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md index f4946230360..07c5ce73470 100644 --- a/doc/user/project/wiki/index.md +++ b/doc/user/project/wiki/index.md @@ -94,8 +94,12 @@ Users with at least the Developer role can create new wiki pages: Wikis are based on Git repositories, so you can clone them locally and edit them like you would do with every other Git repository. To clone a wiki repository -locally, select **Clone repository** from the right-hand sidebar of any wiki page, -and follow the on-screen instructions. +locally: + +1. On the left sidebar, select **Search or go to** and find your project or group. +1. Select **Plan > Wiki**. +1. On the right sidebar, select **Clone repository**. +1. Follow the on-screen instructions. Files you add to your wiki locally must use one of the following supported extensions, depending on the markup language you wish to use. @@ -155,7 +159,9 @@ For an example, read [Table of contents](../../markdown.md#table-of-contents). ## Delete a wiki page -You need at least the Developer role to delete a wiki page: +Prerequisites: + +- You must have at least the Developer role. 1. On the left sidebar, select **Search or go to** and find your project or group. 1. Select **Plan > Wiki**. @@ -166,7 +172,9 @@ You need at least the Developer role to delete a wiki page: ## Move a wiki page -You need at least the Developer role to move a wiki page: +Prerequisites: + +- You must have at least the Developer role. 1. On the left sidebar, select **Search or go to** and find your project or group. 1. Select **Plan > Wiki**. @@ -245,8 +253,11 @@ Commits to wikis are not counted in [repository analytics](../../analytics/repos > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23109) in GitLab 13.8, the sidebar can be customized by selecting the **Edit sidebar** button. -You need at least the Developer role to customize the wiki -navigation sidebar. This process creates a wiki page named `_sidebar` which fully +Prerequisites: + +- You must have at least the Developer role. + +This process creates a wiki page named `_sidebar` which fully replaces the default sidebar navigation: 1. On the left sidebar, select **Search or go to** and find your project or group. @@ -312,7 +323,7 @@ To disable a project's internal wiki: 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Settings > General**. 1. Expand **Visibility, project features, permissions**. -1. Scroll down to find **Wiki** and toggle it off (in gray). +1. Scroll down to find and turn off the **Wiki** toggle (in gray). 1. Select **Save changes**. The internal wiki is now disabled, and users and project members: diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index dd3009ff1d7..bc4c07442eb 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -879,20 +879,7 @@ module API desc: 'The product ID of ZenTao project' } ], - 'squash-tm' => [ - { - required: true, - name: :url, - type: String, - desc: 'The Squash TM webhook URL' - }, - { - required: false, - name: :token, - type: String, - desc: 'The secret token' - } - ] + 'squash-tm' => ::Integrations::SquashTm.api_fields } end diff --git a/lib/gitlab/application_setting_fetcher.rb b/lib/gitlab/application_setting_fetcher.rb new file mode 100644 index 00000000000..ec33a36f028 --- /dev/null +++ b/lib/gitlab/application_setting_fetcher.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Gitlab + module ApplicationSettingFetcher + class << self + def clear_in_memory_application_settings! + @in_memory_application_settings = nil + end + + def current_application_settings + cached_application_settings + end + + private + + def cached_application_settings + return in_memory_application_settings if ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true' + + begin + ::ApplicationSetting.cached + rescue StandardError + # In case Redis isn't running + # or the Redis UNIX socket file is not available + # or the DB is not running (we use migrations in the cache key) + end + end + + def in_memory_application_settings + @in_memory_application_settings ||= ::ApplicationSetting.build_from_defaults + end + end + end +end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 5c4899da11f..9b8d9880434 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -39,19 +39,7 @@ module Gitlab private def ensure_application_settings! - cached_application_settings || uncached_application_settings - end - - def cached_application_settings - return in_memory_application_settings if ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true' - - begin - ::ApplicationSetting.cached - rescue StandardError - # In case Redis isn't running - # or the Redis UNIX socket file is not available - # or the DB is not running (we use migrations in the cache key) - end + Gitlab::ApplicationSettingFetcher.current_application_settings || uncached_application_settings end def uncached_application_settings diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 172d1f3d9aa..c0b0c156308 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -43091,6 +43091,9 @@ msgstr "" msgid "Secret token" msgstr "" +msgid "Secret token." +msgstr "" + msgid "SecretDetection|This comment appears to have a token in it. Are you sure you want to add it?" msgstr "" @@ -51611,6 +51614,9 @@ msgstr "" msgid "URL of the Grafana instance to link to from the Metrics Dashboard menu item." msgstr "" +msgid "URL of the Squash TM webhook." +msgstr "" + msgid "URL of the external Spam Check endpoint" msgstr "" diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index e570b49e1da..f991ecd369c 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -482,11 +482,11 @@ RSpec.describe ProjectsFinder, feature_category: :groups_and_projects do it { is_expected.to match_array([internal_project]) } end - describe 'filter by organization_id' do + describe 'filter by organization' do let_it_be(:organization) { create(:organization) } let_it_be(:organization_project) { create(:project, organization: organization) } - let(:params) { { organization_id: organization.id } } + let(:params) { { organization: organization } } before do organization_project.add_maintainer(current_user) diff --git a/spec/graphql/types/organizations/organization_type_spec.rb b/spec/graphql/types/organizations/organization_type_spec.rb index 6bc4bac6ba2..33d0376e418 100644 --- a/spec/graphql/types/organizations/organization_type_spec.rb +++ b/spec/graphql/types/organizations/organization_type_spec.rb @@ -3,7 +3,9 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['Organization'], feature_category: :cell do - let(:expected_fields) { %w[avatar_url description description_html groups id name organization_users path web_url] } + let(:expected_fields) do + %w[avatar_url description description_html groups id name organization_users path projects web_url] + end specify { expect(described_class.graphql_name).to eq('Organization') } specify { expect(described_class).to require_graphql_authorizations(:read_organization) } diff --git a/spec/lib/gitlab/application_setting_fetcher_spec.rb b/spec/lib/gitlab/application_setting_fetcher_spec.rb new file mode 100644 index 00000000000..0cfb4eea2a3 --- /dev/null +++ b/spec/lib/gitlab/application_setting_fetcher_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::ApplicationSettingFetcher, feature_category: :cell do + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + + described_class.clear_in_memory_application_settings! + end + + describe '.clear_in_memory_application_settings!' do + subject(:clear_in_memory_application_settings!) { described_class.clear_in_memory_application_settings! } + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true') + + described_class.current_application_settings + end + + it 'will re-initialize settings' do + expect(ApplicationSetting).to receive(:build_from_defaults).and_call_original + + clear_in_memory_application_settings! + described_class.current_application_settings + end + end + + describe '.current_application_settings' do + subject(:current_application_settings) { described_class.current_application_settings } + + context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true') + end + + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) + expect(ApplicationSetting).to receive(:build_from_defaults).and_call_original + + expect(current_application_settings).to be_a(ApplicationSetting) + expect(current_application_settings).not_to be_persisted + end + end + + context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is false' do + context 'and an error is raised' do + before do + allow(ApplicationSetting).to receive(:cached).and_raise(StandardError) + end + + it 'returns nil' do + expect(current_application_settings).to be_nil + end + end + + context 'and settings in cache' do + 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) + end + end + end + end +end diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index df1b12e479f..9f666669c91 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -97,32 +97,23 @@ RSpec.describe Gitlab::CurrentSettings, feature_category: :shared do expect(described_class.metrics_sample_interval).to be(15) end - context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do - before do - stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true') - end - - it 'returns an in-memory ApplicationSetting object' do - expect(ApplicationSetting).not_to receive(:current) + it 'retrieves settings using ApplicationSettingFetcher' do + expect(Gitlab::ApplicationSettingFetcher).to receive(:current_application_settings).and_call_original - expect(described_class.current_application_settings).to be_a(ApplicationSetting) - expect(described_class.current_application_settings).not_to be_persisted - end + 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 - before do - expect(ApplicationSetting).not_to receive(:current) - end - it 'returns a FakeApplicationSettings object' do expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) end diff --git a/spec/requests/api/graphql/mutations/branch_rules/create_spec.rb b/spec/requests/api/graphql/mutations/branch_rules/create_spec.rb new file mode 100644 index 00000000000..85ba3d58ee5 --- /dev/null +++ b/spec/requests/api/graphql/mutations/branch_rules/create_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'BranchRuleCreate', feature_category: :source_code_management do + include GraphqlHelpers + let_it_be(:project) { create(:project, :public) } + let_it_be(:current_user, reload: true) { create(:user) } + + let(:params) do + { + project_path: project.full_path, + name: branch_name + } + end + + let(:branch_name) { 'branch_name/*' } + let(:mutation) { graphql_mutation(:branch_rule_create, params) } + let(:mutation_response) { graphql_mutation_response(:branch_rule_create) } + let(:mutation_errors) { mutation_response['errors'] } + + subject(:post_mutation) { post_graphql_mutation(mutation, current_user: current_user) } + + context 'when the user does not have permission' do + before_all do + project.add_developer(current_user) + end + + it_behaves_like 'a mutation that returns a top-level access error' + + it 'does not create the board' do + expect { post_mutation }.not_to change { ProtectedBranch.count } + end + end + + context 'when the user can create a branch rules' do + before_all do + project.add_maintainer(current_user) + end + + it 'creates the protected branch' do + expect { post_mutation }.to change { ProtectedBranch.count }.by(1) + end + + it 'returns the created branch rule' do + post_mutation + + expect(mutation_response).to have_key('branchRule') + expect(mutation_response['branchRule']['name']).to eq(branch_name) + expect(mutation_errors).to be_empty + end + + context 'when the branch rule already exist' do + let!(:existing_rule) { create :protected_branch, name: branch_name, project: project } + + it 'does not create the protected branch' do + expect { post_mutation }.not_to change { ProtectedBranch.count } + end + + it 'return an error message' do + post_mutation + + expect(mutation_errors).to include 'Name has already been taken' + expect(mutation_response['branchRule']).to be_nil + end + end + end +end diff --git a/spec/requests/api/graphql/organizations/organization_query_spec.rb b/spec/requests/api/graphql/organizations/organization_query_spec.rb index c485e3b170d..73bcfe81d76 100644 --- a/spec/requests/api/graphql/organizations/organization_query_spec.rb +++ b/spec/requests/api/graphql/organizations/organization_query_spec.rb @@ -7,7 +7,6 @@ RSpec.describe 'getting organization information', feature_category: :cell do let(:query) { graphql_query_for(:organization, { id: organization.to_global_id }, organization_fields) } let(:current_user) { user } - let(:groups) { graphql_data_at(:organization, :groups, :nodes) } let(:organization_fields) do <<~FIELDS id @@ -23,24 +22,9 @@ RSpec.describe 'getting organization information', feature_category: :cell do let_it_be(:organization_user) { create(:organization_user) } let_it_be(:organization) { organization_user.organization } let_it_be(:user) { organization_user.user } - let_it_be(:parent_group) { create(:group, name: 'parent-group', organization: organization) } - let_it_be(:public_group) { create(:group, name: 'public-group', parent: parent_group, organization: organization) } - let_it_be(:other_group) { create(:group, name: 'other-group', organization: organization) } - let_it_be(:outside_organization_group) { create(:group) } - - let_it_be(:private_group) do - create(:group, :private, name: 'private-group', organization: organization) - end - - let_it_be(:no_access_group_in_org) do - create(:group, :private, name: 'no-access', organization: organization) - end - - before_all do - private_group.add_developer(user) - public_group.add_developer(user) - other_group.add_developer(user) - outside_organization_group.add_developer(user) + let_it_be(:project) { create(:project, organization: organization) { |p| p.add_developer(user) } } + let_it_be(:other_group) do + create(:group, name: 'other-group', organization: organization) { |g| g.add_developer(user) } end subject(:request_organization) { post_graphql(query, current_user: current_user) } @@ -62,25 +46,6 @@ RSpec.describe 'getting organization information', feature_category: :cell do end end - context 'when resolve_organization_groups feature flag is disabled' do - before do - stub_feature_flags(resolve_organization_groups: false) - end - - it 'returns no groups' do - request_organization - - expect(graphql_data_at(:organization)).not_to be_nil - expect(graphql_data_at(:organization, :groups, :nodes)).to be_empty - end - end - - it 'does not return ancestors of authorized groups' do - request_organization - - expect(groups.pluck('id')).not_to include(parent_group.to_global_id.to_s) - end - context 'when requesting organization user' do let(:organization_fields) do <<~FIELDS @@ -116,6 +81,8 @@ RSpec.describe 'getting organization information', feature_category: :cell do organization_user_2 = create(:organization_user, organization: organization) other_group.add_developer(organization_user_2.user) + organization_user_from_project = create(:organization_user, organization: organization) + project.add_developer(organization_user_from_project.user) expect { run_query }.not_to exceed_query_limit(base_query_count) end @@ -127,62 +94,144 @@ RSpec.describe 'getting organization information', feature_category: :cell do end end - context 'with `search` argument' do - let(:search) { 'oth' } - let(:organization_fields) do - <<~FIELDS - id - path - groups(search: "#{search}") { - nodes { - id - name - } - } - FIELDS + context 'when requesting groups' do + let(:groups) { graphql_data_at(:organization, :groups, :nodes) } + let_it_be(:parent_group) { create(:group, name: 'parent-group', organization: organization) } + let_it_be(:public_group) do + create(:group, name: 'public-group', parent: parent_group, organization: organization) end - it 'filters groups by name' do - request_organization + let_it_be(:private_group) do + create(:group, :private, name: 'private-group', organization: organization) + end - expect(groups).to contain_exactly(a_graphql_entity_for(other_group)) + before_all do + create(:group, :private, name: 'no-access', organization: organization) + private_group.add_developer(user) + public_group.add_developer(user) + create(:group) { |g| g.add_developer(user) } # outside organization end - end - context 'with `sort` argument' do - using RSpec::Parameterized::TableSyntax + context 'when resolve_organization_groups feature flag is disabled' do + before do + stub_feature_flags(resolve_organization_groups: false) + end + + it 'returns no groups' do + request_organization + + expect(graphql_data_at(:organization)).not_to be_nil + expect(graphql_data_at(:organization, :groups, :nodes)).to be_empty + end + end - let(:authorized_groups) { [public_group, private_group, other_group] } + it 'does not return ancestors of authorized groups' do + request_organization - where(:field, :direction, :sorted_groups) do - 'id' | 'asc' | lazy { authorized_groups.sort_by(&:id) } - 'id' | 'desc' | lazy { authorized_groups.sort_by(&:id).reverse } - 'name' | 'asc' | lazy { authorized_groups.sort_by(&:name) } - 'name' | 'desc' | lazy { authorized_groups.sort_by(&:name).reverse } - 'path' | 'asc' | lazy { authorized_groups.sort_by(&:path) } - 'path' | 'desc' | lazy { authorized_groups.sort_by(&:path).reverse } + expect(groups.pluck('id')).not_to include(parent_group.to_global_id.to_s) end - with_them do - let(:sort) { "#{field}_#{direction}".upcase } + context 'with `search` argument' do + let(:search) { 'oth' } let(:organization_fields) do <<~FIELDS id path - groups(sort: #{sort}) { + groups(search: "#{search}") { nodes { id + name } } FIELDS end - it 'sorts the groups' do + it 'filters groups by name' do request_organization - expect(groups.pluck('id')).to eq(sorted_groups.map(&:to_global_id).map(&:to_s)) + expect(groups).to contain_exactly(a_graphql_entity_for(other_group)) end end + + context 'with `sort` argument' do + using RSpec::Parameterized::TableSyntax + + let(:authorized_groups) { [public_group, private_group, other_group] } + + where(:field, :direction, :sorted_groups) do + 'id' | 'asc' | lazy { authorized_groups.sort_by(&:id) } + 'id' | 'desc' | lazy { authorized_groups.sort_by(&:id).reverse } + 'name' | 'asc' | lazy { authorized_groups.sort_by(&:name) } + 'name' | 'desc' | lazy { authorized_groups.sort_by(&:name).reverse } + 'path' | 'asc' | lazy { authorized_groups.sort_by(&:path) } + 'path' | 'desc' | lazy { authorized_groups.sort_by(&:path).reverse } + end + + with_them do + let(:sort) { "#{field}_#{direction}".upcase } + let(:organization_fields) do + <<~FIELDS + id + path + groups(sort: #{sort}) { + nodes { + id + } + } + FIELDS + end + + it 'sorts the groups' do + request_organization + + expect(groups.pluck('id')).to eq(sorted_groups.map(&:to_global_id).map(&:to_s)) + end + end + end + end + + context 'when requesting projects' do + let(:projects) { graphql_data_at(:organization, :projects, :nodes) } + let(:organization_fields) do + <<~FIELDS + projects { + nodes { + id + } + } + FIELDS + end + + before_all do + create(:project) { |p| p.add_developer(user) } # some other project that shouldn't show up in our results + end + + before do + request_organization + end + + it_behaves_like 'a working graphql query' + + it 'returns projects' do + expect(projects).to contain_exactly(a_graphql_entity_for(project)) + end + + it_behaves_like 'sorted paginated query' do + include_context 'no sort argument' + + let_it_be(:another_project) { create(:project, organization: organization) { |p| p.add_developer(user) } } + let_it_be(:another_project2) { create(:project, organization: organization) { |p| p.add_developer(user) } } + let(:first_param) { 2 } + let(:data_path) { [:organization, :projects] } + let(:all_records) { [another_project2, another_project, project].map { |p| global_id_of(p).to_s } } + end + + def pagination_query(params) + graphql_query_for( + :organization, { id: organization.to_global_id }, + query_nodes(:projects, :id, include_pagination_info: true, args: params) + ) + end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7317b512ae4..816dec08b53 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -424,6 +424,7 @@ 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 stub_all_feature_flags diff --git a/spec/support/migration.rb b/spec/support/migration.rb index fc8a4bb12fb..d9db7ae52fe 100644 --- a/spec/support/migration.rb +++ b/spec/support/migration.rb @@ -18,6 +18,7 @@ RSpec.configure do |config| config.after(:context, :migration) do Gitlab::CurrentSettings.clear_in_memory_application_settings! + Gitlab::ApplicationSettingFetcher.clear_in_memory_application_settings! end config.append_after(:context, :migration) do |