Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-07-08 09:25:20 +00:00
parent 23c30c25c9
commit 0cd10fa56d
90 changed files with 1694 additions and 546 deletions

View File

@ -5,6 +5,7 @@ stages:
- build-images
- fixtures
- lint
- test-frontend
- test
- post-test
- review

View File

@ -1,3 +1,11 @@
.with-fixtures-needs:
needs:
- "rspec-all frontend_fixture"
.with-graphql-schema-dump-needs:
needs:
- "graphql-schema-dump"
.compile-assets-base:
extends:
- .default-retry
@ -83,7 +91,6 @@ update-assets-compile-production-cache:
- .update-cache-base
- .assets-compile-cache-push
- .shared:rules:update-cache
stage: prepare
artifacts: {} # This job's purpose is only to update the cache.
update-assets-compile-test-cache:
@ -92,7 +99,6 @@ update-assets-compile-test-cache:
- .update-cache-base
- .assets-compile-cache-push
- .shared:rules:update-cache
stage: prepare
artifacts: {} # This job's purpose is only to update the cache.
update-storybook-yarn-cache:
@ -136,8 +142,14 @@ retrieve-frontend-fixtures:
- .default-before_script
- .ruby-cache
- .use-pg14
- .repo-from-artifacts
stage: fixtures
needs: ["setup-test-env", "retrieve-tests-metadata", "retrieve-frontend-fixtures"]
needs:
- "setup-test-env"
- "retrieve-tests-metadata"
- "retrieve-frontend-fixtures"
# it's ok to wait for the repo artifact as we're waiting for setup-test-env (which takes longer than clone-gitlab-repo) anyway
- !reference [.repo-from-artifacts, needs]
variables:
# Don't add `CRYSTALBALL: "false"` here as we're enabling Crystalball for scheduled pipelines (in `.gitlab-ci.yml`), so that we get coverage data
# for the `frontend fixture RSpec files` that will be added to the Crystalball mapping in `update-tests-metadata`.
@ -180,18 +192,21 @@ upload-frontend-fixtures:
variables:
SETUP_DB: "false"
extends:
- .frontend-fixtures-base
- .default-retry
- .default-before_script
- .repo-from-artifacts
- .frontend:rules:upload-frontend-fixtures
stage: fixtures
needs: ["rspec-all frontend_fixture"]
needs:
# it's ok to wait for the repo artifact as we're waiting for the fixtures (which wait for the repo artifact) anyway
- !reference [.repo-from-artifacts, needs]
- !reference [.with-fixtures-needs, needs]
script:
- source scripts/utils.sh
- source scripts/gitlab_component_helpers.sh
- export_fixtures_sha_for_upload
- 'fixtures_archive_doesnt_exist || { echoinfo "INFO: Exiting early as package exists."; exit 0; }'
- run_timed_command "create_fixtures_package"
- run_timed_command "upload_fixtures_package"
artifacts: {}
graphql-schema-dump:
variables:
@ -225,18 +240,7 @@ graphql-schema-dump:
before_script:
- !reference [.default-before_script, before_script]
- yarn_install_script
stage: test
.vue3:
variables:
VUE_VERSION: 3
NODE_OPTIONS: --max-old-space-size=7680
allow_failure: true
.jest-base:
extends: .frontend-test-base
script:
- run_timed_command "yarn jest:ci:without-fixtures"
stage: test-frontend
jest-build-cache:
extends:
@ -260,37 +264,26 @@ jest-build-cache:
# they exit with 1, so as not to break master and other pipelines.
exit_codes: 1
.vue3:
variables:
VUE_VERSION: 3
NODE_OPTIONS: --max-old-space-size=7680
allow_failure: true
.with-jest-build-cache-vue3-needs:
needs:
- job: jest-build-cache-vue3
optional: true
jest-build-cache-vue3:
extends:
- jest-build-cache
- .frontend:rules:jest-vue3
- .vue3
jest-with-fixtures:
extends:
- .jest-base
- .frontend:rules:jest
needs:
- "rspec-all frontend_fixture"
- job: jest-build-cache
optional: true
artifacts:
name: coverage-frontend
expire_in: 31d
when: always
paths:
- coverage-frontend/
- junit_jest.xml
- tmp/tests/frontend/
reports:
junit: junit_jest.xml
parallel: 2
script:
- run_timed_command "yarn jest:ci:with-fixtures"
jest:
extends:
- .jest-base
- .frontend-test-base
- .frontend:rules:jest
needs:
- job: jest-build-cache
@ -306,16 +299,22 @@ jest:
reports:
junit: junit_jest.xml
parallel: 11
script:
- run_timed_command "yarn jest:ci:without-fixtures"
jest-with-fixtures vue3:
jest-with-fixtures:
extends:
- jest-with-fixtures
- .frontend:rules:jest-vue3
- .vue3
- jest
- .repo-from-artifacts
- .frontend:rules:jest
needs:
- "rspec-all frontend_fixture"
- job: jest-build-cache-vue3
optional: true
- !reference [jest, needs]
# it's ok to wait for the repo artifact as we're waiting for the fixtures (which wait for the repo artifact) anyway
- !reference [.repo-from-artifacts, needs]
- !reference [.with-fixtures-needs, needs]
parallel: 2
script:
- run_timed_command "yarn jest:ci:with-fixtures"
jest vue3:
extends:
@ -323,8 +322,18 @@ jest vue3:
- .frontend:rules:jest-vue3
- .vue3
needs:
- job: jest-build-cache-vue3
optional: true
- !reference [.with-jest-build-cache-vue3-needs, needs]
jest-with-fixtures vue3:
extends:
- jest-with-fixtures
- .frontend:rules:jest-vue3
- .vue3
needs:
- !reference ["jest vue3", needs]
# it's ok to wait for the repo artifact as we're waiting for the fixtures (which wait for the repo artifact) anyway
- !reference [.repo-from-artifacts, needs]
- !reference [.with-fixtures-needs, needs]
jest predictive:
extends:
@ -349,16 +358,25 @@ jest-with-fixtures predictive:
jest-integration:
extends:
- .frontend-test-base
- .repo-from-artifacts
- .frontend:rules:jest-integration
script:
- run_timed_command "yarn jest:integration --ci"
needs: ["rspec-all frontend_fixture", "graphql-schema-dump"]
needs:
# it's ok to wait for the repo artifact as we're waiting for the fixtures (which wait for the repo artifact) anyway
- !reference [.repo-from-artifacts, needs]
- !reference [.with-fixtures-needs, needs]
- !reference [.with-graphql-schema-dump-needs, needs]
jest-snapshot-vue3:
extends:
- .jest-base
- .frontend-test-base
- .repo-from-artifacts
- .frontend:rules:jest-snapshot-vue3
needs: ["rspec-all frontend_fixture"]
needs:
# it's ok to wait for the repo artifact as we're waiting for the fixtures (which wait for the repo artifact) anyway
- !reference [.repo-from-artifacts, needs]
- !reference [.with-fixtures-needs, needs]
variables:
VUE_VERSION: 3
JEST_REPORT: jest-test-report.json
@ -381,7 +399,6 @@ jest-snapshot-vue3:
echo 'All snapshot tests passed! Exiting 0...'
exit 0
fi
artifacts:
name: snapshot_tests
expire_in: 31d
@ -395,8 +412,10 @@ coverage-frontend:
- .default-retry
- .default-utils-before_script
- .yarn-cache
- .repo-from-artifacts
- .frontend:rules:coverage-frontend
needs:
- !reference [.repo-from-artifacts, needs]
- job: "jest"
optional: true
- job: "jest-with-fixtures"
@ -427,9 +446,9 @@ webpack-dev-server:
- .default-retry
- .default-utils-before_script
- .yarn-cache
- .repo-from-artifacts
- .frontend:rules:default-frontend-jobs
stage: test
needs: []
stage: test-frontend
variables:
WEBPACK_MEMORY_TEST: "true"
WEBPACK_VENDOR_DLL: "true"
@ -443,44 +462,24 @@ webpack-dev-server:
paths:
- webpack-dev-server.json
bundle-size-review:
extends:
- .default-retry
- .default-utils-before_script
- .assets-compile-cache
- .frontend:rules:bundle-size-review
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:danger
stage: test
needs: []
script:
- yarn_install_script
- scripts/bundle_size_review
artifacts:
when: always
name: bundle-size-review
expire_in: 31d
paths:
- bundle-size-review/
.compile-storybook-base:
compile-storybook:
extends:
- .frontend-test-base
- .storybook-yarn-cache
script:
- yarn_install_script_storybook
- run_timed_command "yarn run storybook:build"
needs: ["graphql-schema-dump"]
compile-storybook:
extends:
- .compile-storybook-base
- .repo-from-artifacts
- .frontend:rules:compile-storybook
stage: pages
needs:
- !reference [.compile-storybook-base, needs]
- job: "rspec-all frontend_fixture"
# it's ok to wait for the repo artifact as we're waiting for the fixtures (which wait for the repo artifact) anyway
- !reference [.repo-from-artifacts, needs]
- !reference [.with-fixtures-needs, needs]
- !reference [.with-graphql-schema-dump-needs, needs]
artifacts:
name: storybook
expire_in: 31d
when: always
paths:
- storybook/public
script:
- yarn_install_script_storybook
- run_timed_command "yarn run storybook:build"

View File

@ -28,7 +28,8 @@
needs:
# If the job extending this also defines `needs`, make sure to update
# its `needs` to include `clone-gitlab-repo` because it'll be overridden.
- clone-gitlab-repo
- job: clone-gitlab-repo
optional: true # Optional so easier to switch in between
.production:
variables:
@ -321,7 +322,7 @@
.ai-gateway-services:
services:
- name: registry.gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/model-gateway:v1.7.0
- name: registry.gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/model-gateway:v1.8.0
alias: ai-gateway
.use-pg13:

View File

@ -2,6 +2,7 @@ include:
- component: ${CI_SERVER_FQDN}/gitlab-org/components/danger-review/danger-review@1.4.1
inputs:
job_image: "${DEFAULT_CI_IMAGE}"
job_stage: "preflight"
# By default DANGER_DANGERFILE_PREFIX is not defined but allows JiHu to
# use a different prefix.
# See https://jihulab.com/gitlab-cn/gitlab/-/blob/main-jh/jh/.gitlab-ci.yml

View File

@ -115,3 +115,21 @@ pipeline-tier-3:
extends:
- .pipeline-tier-base
- .preflight:rules:pipeline-tier-3
bundle-size-review:
extends:
- .default-retry
- .default-utils-before_script
- .assets-compile-cache
- .repo-from-artifacts
- .frontend:rules:bundle-size-review
stage: preflight
script:
- yarn_install_script
- scripts/bundle_size_review
artifacts:
when: always
name: bundle-size-review
expire_in: 31d
paths:
- bundle-size-review/

View File

@ -103,6 +103,7 @@ qa:metadata-lint:
extends:
- .qa-job-base
- .qa:rules:metadata-lint
stage: lint
variables:
QA_EXPORT_TEST_METRICS: "false"
# Disable warnings in browserslist which can break on backports

View File

@ -1367,7 +1367,6 @@ fail-pipeline-early:
stage: test
needs:
- !reference [.rspec-base-needs, needs]
- job: "compile-test-assets"
- job: "detect-previous-failed-tests"
script:
- !reference [.base-script, script]

View File

@ -68,10 +68,10 @@ include:
.rspec-base-needs:
needs:
- job: "clone-gitlab-repo"
optional: true # Optional so easier to switch in between
- !reference [.repo-from-artifacts, needs]
- job: "setup-test-env"
- job: "retrieve-tests-metadata"
- job: "compile-test-assets"
.rspec-base:
extends:
@ -89,7 +89,6 @@ include:
EVENT_PROF: "sql.active_record"
needs:
- !reference [.rspec-base-needs, needs]
- job: "compile-test-assets"
- job: "detect-tests"
optional: true
script:

View File

@ -91,7 +91,7 @@ verify-tests-yml:
extends:
- .setup:rules:verify-tests-yml
image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}ruby:${RUBY_VERSION}-alpine3.20
stage: test
stage: preflight
needs: []
script:
- source scripts/utils.sh

View File

@ -593,7 +593,6 @@ Gitlab/StrongMemoizeAttr:
- 'lib/gitlab/tracking/destinations/snowplow_micro.rb'
- 'lib/gitlab/usage_data.rb'
- 'lib/gitlab/web_hooks/rate_limiter.rb'
- 'lib/gitlab/web_ide/config/entry/terminal.rb'
- 'lib/gitlab/webpack/graphql_known_operations.rb'
- 'lib/gitlab/webpack/manifest.rb'
- 'lib/gitlab/wiki_pages/front_matter_parser.rb'

View File

@ -3696,8 +3696,6 @@ Layout/LineLength:
- 'spec/lib/gitlab/utils/measuring_spec.rb'
- 'spec/lib/gitlab/utils/nokogiri_spec.rb'
- 'spec/lib/gitlab/utils/usage_data_spec.rb'
- 'spec/lib/gitlab/web_ide/config/entry/global_spec.rb'
- 'spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb'
- 'spec/lib/gitlab/webpack/manifest_spec.rb'
- 'spec/lib/gitlab/word_diff/parser_spec.rb'
- 'spec/lib/gitlab/workhorse_spec.rb'

View File

@ -344,7 +344,6 @@ Lint/RedundantCopDisableDirective:
- 'lib/gitlab/usage_data.rb'
- 'lib/gitlab/usage_data_queries.rb'
- 'lib/gitlab/verify/ci_secure_files.rb'
- 'lib/gitlab/web_ide/extensions_marketplace.rb'
- 'lib/gitlab/x509/signature.rb'
- 'lib/tasks/gitlab/cleanup.rake'
- 'lib/tasks/gitlab/seed/group_seed.rake'

View File

@ -472,7 +472,6 @@ Lint/UnusedMethodArgument:
- 'lib/gitlab/utils/usage_data.rb'
- 'lib/gitlab/verify/batch_verifier.rb'
- 'lib/gitlab/view/presenter/base.rb'
- 'lib/gitlab/web_ide/config.rb'
- 'lib/gitlab/work_items/work_item_hierarchy.rb'
- 'lib/kramdown/parser/atlassian_document_format.rb'
- 'lib/tasks/gems.rake'

View File

@ -78,7 +78,6 @@ Naming/HeredocDelimiterNaming:
- 'spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb'
- 'spec/lib/gitlab/patch/database_config_spec.rb'
- 'spec/lib/gitlab/quick_actions/substitution_definition_spec.rb'
- 'spec/lib/gitlab/web_ide/config_spec.rb'
- 'spec/lib/gitlab/webpack/file_loader_spec.rb'
- 'spec/lib/gitlab/webpack/graphql_known_operations_spec.rb'
- 'spec/lib/gitlab/webpack/manifest_spec.rb'

View File

@ -1896,7 +1896,6 @@ RSpec/ContextWording:
- 'spec/lib/gitlab/view/presenter/base_spec.rb'
- 'spec/lib/gitlab/visibility_level_checker_spec.rb'
- 'spec/lib/gitlab/visibility_level_spec.rb'
- 'spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb'
- 'spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb'
- 'spec/lib/gitlab/workhorse_spec.rb'
- 'spec/lib/gitlab/x509/certificate_spec.rb'

View File

@ -531,7 +531,6 @@ RSpec/FeatureCategory:
- 'ee/spec/lib/ee/gitlab/usage_data_non_sql_metrics_spec.rb'
- 'ee/spec/lib/ee/gitlab/verify/lfs_objects_spec.rb'
- 'ee/spec/lib/ee/gitlab/verify/uploads_spec.rb'
- 'ee/spec/lib/ee/gitlab/web_ide/config/entry/global_spec.rb'
- 'ee/spec/lib/ee/service_ping/service_ping_settings_spec.rb'
- 'ee/spec/lib/ee/sidebars/groups/menus/issues_menu_spec.rb'
- 'ee/spec/lib/ee/sidebars/projects/menus/ci_cd_menu_spec.rb'
@ -734,10 +733,6 @@ RSpec/FeatureCategory:
- 'ee/spec/lib/gitlab/usage_data_counters/streaming_audit_event_type_counter_spec.rb'
- 'ee/spec/lib/gitlab/user_access_spec.rb'
- 'ee/spec/lib/gitlab/visibility_level_spec.rb'
- 'ee/spec/lib/gitlab/web_ide/config/entry/schema/match_spec.rb'
- 'ee/spec/lib/gitlab/web_ide/config/entry/schema/uri_spec.rb'
- 'ee/spec/lib/gitlab/web_ide/config/entry/schema_spec.rb'
- 'ee/spec/lib/gitlab/web_ide/config/entry/schemas_spec.rb'
- 'ee/spec/lib/gitlab_subscriptions/upcoming_reconciliation_entity_spec.rb'
- 'ee/spec/lib/incident_management/oncall_shift_generator_spec.rb'
- 'ee/spec/lib/omni_auth/strategies/group_saml_spec.rb'
@ -3348,9 +3343,6 @@ RSpec/FeatureCategory:
- 'spec/lib/gitlab/visibility_level_spec.rb'
- 'spec/lib/gitlab/web_hooks/rate_limiter_spec.rb'
- 'spec/lib/gitlab/web_hooks/recursion_detection_spec.rb'
- 'spec/lib/gitlab/web_ide/config/entry/global_spec.rb'
- 'spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb'
- 'spec/lib/gitlab/web_ide/config_spec.rb'
- 'spec/lib/gitlab/webpack/file_loader_spec.rb'
- 'spec/lib/gitlab/webpack/graphql_known_operations_spec.rb'
- 'spec/lib/gitlab/webpack/manifest_spec.rb'

View File

@ -321,6 +321,8 @@ class GraphqlController < ApplicationController
end
def execute_introspection_query
context[:introspection] = true
if introspection_query_can_use_cache?
# Context for caching: https://gitlab.com/gitlab-org/gitlab/-/issues/409448
Rails.cache.fetch(

View File

@ -27,11 +27,11 @@ class IdeController < ApplicationController
end
def oauth_redirect
return render_404 unless ::Gitlab::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
return render_404 unless ::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
# TODO - It's **possible** we end up here and no oauth application has been set up.
# We need to have better handling of these edge cases. Here's a follow-up issue:
# https://gitlab.com/gitlab-org/gitlab/-/issues/433322
return render_404 unless ::Gitlab::WebIde::DefaultOauthApplication.oauth_application
return render_404 unless ::WebIde::DefaultOauthApplication.oauth_application
render layout: 'fullscreen', locals: { minimal: true }
end
@ -43,9 +43,9 @@ class IdeController < ApplicationController
end
def ensure_web_ide_oauth_application!
return unless ::Gitlab::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
return unless ::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
::Gitlab::WebIde::DefaultOauthApplication.ensure_oauth_application!
::WebIde::DefaultOauthApplication.ensure_oauth_application!
end
def fork_info(project, branch)

View File

@ -4,6 +4,7 @@ module Organizations
class GroupsFinder < GroupsFinder
def execute
groups = find_union(filtered_groups, Group)
groups = groups.without_deleted if Feature.enabled?(:filter_deleted_groups, current_user)
unless default_organization?
cte = Gitlab::SQL::CTE.new(:filtered_groups_cte, groups, materialized: false)

View File

@ -94,6 +94,14 @@ module CachedIntrospectionQuery
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}

View File

@ -1,11 +1,12 @@
# frozen_string_literal: true
class GitlabSchema < GraphQL::Schema
# Currently an IntrospectionQuery has a complexity of 179.
# These values will evolve over time.
DEFAULT_MAX_COMPLEXITY = 200
AUTHENTICATED_MAX_COMPLEXITY = 250
ADMIN_MAX_COMPLEXITY = 300
# Current GraphiQL introspection query has complexity of 217.
# As we cache this specific query we allow it to have a higher complexity.
INTROSPECTION_MAX_COMPLEXITY = 217
DEFAULT_MAX_DEPTH = 15
AUTHENTICATED_MAX_DEPTH = 20
@ -156,11 +157,14 @@ class GitlabSchema < GraphQL::Schema
def max_query_complexity(ctx)
current_user = ctx&.fetch(:current_user, nil)
introspection = ctx&.fetch(:introspection, false)
if current_user&.admin
ADMIN_MAX_COMPLEXITY
elsif current_user
AUTHENTICATED_MAX_COMPLEXITY
elsif introspection
INTROSPECTION_MAX_COMPLEXITY
else
DEFAULT_MAX_COMPLEXITY
end

View File

@ -22,15 +22,15 @@ module IdeHelper
end
def show_web_ide_oauth_callback_mismatch_callout?
return false unless ::Gitlab::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
return false unless ::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
callback_urls = ::Gitlab::WebIde::DefaultOauthApplication.oauth_application_callback_urls
callback_urls = ::WebIde::DefaultOauthApplication.oauth_application_callback_urls
callback_url_domains = callback_urls.map { |url| URI.parse(url).origin }
callback_url_domains.any? && callback_url_domains.exclude?(request.base_url)
end
def web_ide_oauth_application_id
::Gitlab::WebIde::DefaultOauthApplication.oauth_application_id
::WebIde::DefaultOauthApplication.oauth_application_id
end
def use_new_web_ide?
@ -66,11 +66,11 @@ module IdeHelper
end
def new_ide_oauth_data
return {} unless ::Gitlab::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
return {} unless ::Gitlab::WebIde::DefaultOauthApplication.oauth_application
return {} unless ::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
return {} unless ::WebIde::DefaultOauthApplication.oauth_application
client_id = ::Gitlab::WebIde::DefaultOauthApplication.oauth_application.uid
callback_urls = ::Gitlab::WebIde::DefaultOauthApplication.oauth_application_callback_urls
client_id = ::WebIde::DefaultOauthApplication.oauth_application.uid
callback_urls = ::WebIde::DefaultOauthApplication.oauth_application_callback_urls
{
'client-id' => client_id,
@ -121,6 +121,6 @@ module IdeHelper
end
def extensions_gallery_settings
Gitlab::WebIde::ExtensionsMarketplace.webide_extensions_gallery_settings(user: current_user).to_json
WebIde::ExtensionsMarketplace.webide_extensions_gallery_settings(user: current_user).to_json
end
end

View File

@ -132,7 +132,7 @@ module PreferencesHelper
[].tap do |views|
views << { name: 'gitpod', message: gitpod_enable_description, message_url: gitpod_url_placeholder, help_link: help_page_path('integration/gitpod') } if Gitlab::CurrentSettings.gitpod_enabled
views << { name: 'sourcegraph', message: sourcegraph_url_message, message_url: Gitlab::CurrentSettings.sourcegraph_url, help_link: help_page_path('user/profile/preferences', anchor: 'sourcegraph') } if Gitlab::Sourcegraph.feature_available? && Gitlab::CurrentSettings.sourcegraph_enabled
views << extensions_marketplace_view if Gitlab::WebIde::ExtensionsMarketplace.feature_enabled?(user: current_user)
views << extensions_marketplace_view if WebIde::ExtensionsMarketplace.feature_enabled?(user: current_user)
end
end
@ -140,14 +140,14 @@ module PreferencesHelper
def extensions_marketplace_view
# We handle the linkStart / linkEnd inside of a Vue sprintf
extensions_marketplace_home = "%{linkStart}#{::Gitlab::WebIde::ExtensionsMarketplace.marketplace_home_url}%{linkEnd}"
extensions_marketplace_home = "%{linkStart}#{::WebIde::ExtensionsMarketplace.marketplace_home_url}%{linkEnd}"
message = format(s_('PreferencesIntegrations|Uses %{extensions_marketplace_home} as the extension marketplace for the Web IDE.'), extensions_marketplace_home: extensions_marketplace_home)
{
name: 'extensions_marketplace',
message: message,
message_url: Gitlab::WebIde::ExtensionsMarketplace.marketplace_home_url,
help_link: Gitlab::WebIde::ExtensionsMarketplace.help_preferences_url
message_url: WebIde::ExtensionsMarketplace.marketplace_home_url,
help_link: WebIde::ExtensionsMarketplace.help_preferences_url
}
end

View File

@ -3,9 +3,40 @@
module AuditEvents
class GroupAuditEvent < ApplicationRecord
self.table_name = "group_audit_events"
include PartitionedTable
self.primary_key = :id
partitioned_by :created_at, strategy: :monthly
include AuditEvents::CommonModel
include ::Gitlab::Utils::StrongMemoize
validates :group_id, presence: true
scope :by_group, ->(group_id) { where(group_id: group_id) }
attr_accessor :root_group_entity_id
attr_writer :group
def group
lazy_group
end
strong_memoize_attr :group
def root_group_entity
return ::Group.find_by(id: root_group_entity_id) if root_group_entity_id.present?
return if group.nil?
root_group_entity = group.root_ancestor
self.root_group_entity_id = root_group_entity.id
root_group_entity
end
strong_memoize_attr :root_group_entity
private
def lazy_group
BatchLoader.for(group_id)
.batch(default_value: ::Gitlab::Audit::NullEntity.new
) do |ids, loader|
::Group.where(id: ids).find_each { |record| loader.call(record.id, record) }
end
end
end
end

View File

@ -3,9 +3,7 @@
module AuditEvents
class InstanceAuditEvent < ApplicationRecord
self.table_name = "instance_audit_events"
include PartitionedTable
self.primary_key = :id
partitioned_by :created_at, strategy: :monthly
include AuditEvents::CommonModel
end
end

View File

@ -3,9 +3,40 @@
module AuditEvents
class ProjectAuditEvent < ApplicationRecord
self.table_name = "project_audit_events"
include PartitionedTable
self.primary_key = :id
partitioned_by :created_at, strategy: :monthly
include AuditEvents::CommonModel
include ::Gitlab::Utils::StrongMemoize
validates :project_id, presence: true
scope :by_project, ->(project_id) { where(project_id: project_id) }
attr_accessor :root_group_entity_id
attr_writer :project
def project
lazy_project
end
strong_memoize_attr :project
def root_group_entity
return ::Group.find_by(id: root_group_entity_id) if root_group_entity_id.present?
return if project.nil?
root_group_entity = project.group&.root_ancestor
self.root_group_entity_id = root_group_entity&.id
root_group_entity
end
strong_memoize_attr :root_group_entity
private
def lazy_project
BatchLoader.for(project_id)
.batch(default_value: ::Gitlab::Audit::NullEntity.new
) do |ids, loader|
::Project.where(id: ids).find_each { |record| loader.call(record.id, record) }
end
end
end
end

View File

@ -3,9 +3,12 @@
module AuditEvents
class UserAuditEvent < ApplicationRecord
self.table_name = "user_audit_events"
include PartitionedTable
self.primary_key = :id
partitioned_by :created_at, strategy: :monthly
include AuditEvents::CommonModel
validates :user_id, presence: true
scope :by_user, ->(user_id) { where(user_id: user_id) }
scope :by_username, ->(username) { where(user_id: find_user_id(username)) }
end
end

View File

@ -0,0 +1,155 @@
# frozen_string_literal: true
module AuditEvents
module CommonModel
extend ActiveSupport::Concern
PARALLEL_PERSISTENCE_COLUMNS = [
:author_name,
:entity_path,
:target_details,
:target_type,
:target_id
].freeze
TRUNCATED_FIELDS = {
entity_path: 5_500,
target_details: 5_500
}.freeze
included do
include AfterCommitQueue
include CreatedAtFilterable
include BulkInsertSafe
include EachBatch
include PartitionedTable
self.primary_key = :id
partitioned_by :created_at, strategy: :monthly
serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize -- We need this to serialize details stored in audit event.
belongs_to :user, foreign_key: :author_id, inverse_of: :audit_events
validates :author_id, presence: true
validates :ip_address, ip_address: true
scope :by_author_id, ->(author_id) { where(author_id: author_id) }
scope :by_author_username, ->(username) { where(author_id: find_user_id(username)) }
after_initialize :initialize_details
before_validation :sanitize_message
before_validation :truncate_fields
after_validation :parallel_persist
end
class_methods do
def supported_keyset_orderings
{ id: [:desc] }
end
def order_by(method)
case method.to_s
when 'created_asc'
order(id: :asc)
else
order(id: :desc)
end
end
def find_user_id(username)
User.find_by_username(username)&.id
end
end
def initialize_details
return unless has_attribute?(:details)
self.details = {} if details&.nil?
end
def author_name
author&.name
end
def formatted_details
details
.merge(details.slice(:from, :to).transform_values(&:to_s))
.merge(author_email: author.try(:email))
end
def author
lazy_author&.itself.presence || default_author_value
end
def lazy_author
BatchLoader.for(author_id).batch do |author_ids, loader|
User.select(:id, :name, :username, :email).where(id: author_ids).find_each do |user|
loader.call(user.id, user)
end
end
end
def as_json(options = {})
super(options).tap do |json|
json['ip_address'] = ip_address.to_s
end
end
def target_type
super || details[:target_type]
end
def target_id
details[:target_id]
end
def target_details
super || details[:target_details]
end
def entity_path
super || details[:entity_path]
end
def ip_address
super&.to_s || details[:ip_address]
end
private
def sanitize_message
message = details[:custom_message]
return unless message
self.details = details.merge(custom_message: Sanitize.clean(message))
end
def default_author_value
::Gitlab::Audit::NullAuthor.for(author_id, self)
end
def parallel_persist
PARALLEL_PERSISTENCE_COLUMNS.each do |name|
original = self[name] || details[name]
next unless original
self[name] = details[name] = original
end
end
def truncate_fields
TRUNCATED_FIELDS.each do |name, limit|
original = self[name] || details[name]
next unless original
self[name] = details[name] = String(original).truncate(limit)
end
end
end
end

View File

@ -178,7 +178,8 @@ class Namespace < ApplicationRecord
delegate :math_rendering_limits_enabled?,
:lock_math_rendering_limits_enabled?,
to: :namespace_settings
delegate :add_creator, to: :namespace_details
delegate :add_creator, :pending_delete, :pending_delete=,
to: :namespace_details
before_create :sync_share_with_group_lock_with_parent
before_update :sync_share_with_group_lock_with_parent, if: :parent_changed?
@ -196,6 +197,7 @@ class Namespace < ApplicationRecord
saved_change_to_name?) || saved_change_to_path? || saved_change_to_parent_id?
}
scope :without_deleted, -> { joins(:namespace_details).where(namespace_details: { pending_delete: false }) }
scope :user_namespaces, -> { where(type: Namespaces::UserNamespace.sti_name) }
scope :group_namespaces, -> { where(type: Group.sti_name) }
scope :without_project_namespaces, -> { where(Namespace.arel_table[:type].not_eq(Namespaces::ProjectNamespace.sti_name)) }

View File

@ -5,6 +5,8 @@ module Groups
DestroyError = Class.new(StandardError)
def async_execute
mark_pending_delete
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
Gitlab::AppLogger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
end
@ -14,6 +16,8 @@ module Groups
# TODO - add a policy check here https://gitlab.com/gitlab-org/gitlab/-/issues/353082
raise DestroyError, "You can't delete this group because you're blocked." if current_user.blocked?
mark_pending_delete
group.projects.includes(:project_feature).each do |project|
# Execute the destruction of the models immediately to ensure atomic cleanup.
success = ::Projects::DestroyService.new(project, current_user).execute
@ -46,11 +50,22 @@ module Groups
publish_event
group
rescue Exception # rubocop:disable Lint/RescueException -- Namespace.transaction can raise Exception
unmark_pending_delete
raise
end
# rubocop: enable CodeReuse/ActiveRecord
private
def mark_pending_delete
group.update_attribute(:pending_delete, true)
end
def unmark_pending_delete
group.update_attribute(:pending_delete, false)
end
def any_groups_shared_with_this_group?
group.shared_group_links.any?
end

View File

@ -41,12 +41,12 @@ module Ide
end
def load_config!
@config = Gitlab::WebIde::Config.new(config_content)
@config = WebIde::Config.new(config_content)
unless @config.valid?
raise ValidationError, @config.errors.first
end
rescue Gitlab::WebIde::Config::ConfigError => e
rescue WebIde::Config::ConfigError => e
raise ValidationError, e.message
end

View File

@ -8,7 +8,7 @@
- fluid_help_text = s_('Preferences|Content will span %{percentage} of the page width.').html_safe % { percentage: '100%' }
- @color_modes = Gitlab::ColorModes::available_modes.to_json
- @themes = Gitlab::Themes::available_themes.to_json
- extensions_marketplace_url = ::Gitlab::WebIde::ExtensionsMarketplace.marketplace_home_url
- extensions_marketplace_url = ::WebIde::ExtensionsMarketplace.marketplace_home_url
- data_attributes = { color_modes: @color_modes, themes: @themes, integration_views: integration_views.to_json, user_fields: user_fields, body_classes: Gitlab::Themes.body_classes, profile_preferences_path: profile_preferences_path, extensions_marketplace_url: extensions_marketplace_url }
- @force_desktop_expanded_sidebar = true

View File

@ -318,7 +318,7 @@ domains:
feature_categories:
- webhooks
WebIDE:
WebIde:
description:
feature_categories:
- web_ide

View File

@ -0,0 +1,9 @@
---
name: filter_deleted_groups
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/455871
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158309
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/470628
milestone: '17.2'
group: group::tenant scale
type: gitlab_com_derisk
default_enabled: false

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddPendingDeleteToNamespaceDetails < Gitlab::Database::Migration[2.2]
milestone '17.2'
def change
add_column :namespace_details, :pending_delete, :boolean, default: false, null: false
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
class FkToCiPipelinesFromCiPipelineArtifactsOnPartitionIdAndPipelineId < Gitlab::Database::Migration[2.2]
milestone '17.2'
disable_ddl_transaction!
SOURCE_TABLE_NAME = :ci_pipeline_artifacts
TARGET_TABLE_NAME = :ci_pipelines
COLUMN = :pipeline_id
TARGET_COLUMN = :id
FK_NAME = :fk_rails_a9e811a466_p
PARTITION_COLUMN = :partition_id
def up
add_concurrent_foreign_key(
SOURCE_TABLE_NAME,
TARGET_TABLE_NAME,
column: [PARTITION_COLUMN, COLUMN],
target_column: [PARTITION_COLUMN, TARGET_COLUMN],
validate: false,
reverse_lock_order: true,
on_update: :cascade,
on_delete: :cascade,
name: FK_NAME
)
end
def down
with_lock_retries do
remove_foreign_key_if_exists(
SOURCE_TABLE_NAME,
TARGET_TABLE_NAME,
name: FK_NAME,
reverse_lock_order: true
)
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class ValidateAsyncFkOnCiPipelineArtifactsPartitionIdAndPipelineId < Gitlab::Database::Migration[2.2]
milestone '17.2'
TABLE_NAME = :ci_pipeline_artifacts
FK_NAME = :fk_rails_a9e811a466_p
COLUMNS = [:partition_id, :pipeline_id]
def up
prepare_async_foreign_key_validation(TABLE_NAME, COLUMNS, name: FK_NAME)
end
def down
unprepare_async_foreign_key_validation(TABLE_NAME, COLUMNS, name: FK_NAME)
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
class FkCiPipelinesCiDailyBuildGroupReportResultsOnParIdLastPipelineId < Gitlab::Database::Migration[2.2]
milestone '17.2'
disable_ddl_transaction!
SOURCE_TABLE_NAME = :ci_daily_build_group_report_results
TARGET_TABLE_NAME = :ci_pipelines
COLUMN = :last_pipeline_id
TARGET_COLUMN = :id
FK_NAME = :fk_rails_ee072d13b3_p
PARTITION_COLUMN = :partition_id
def up
add_concurrent_foreign_key(
SOURCE_TABLE_NAME,
TARGET_TABLE_NAME,
column: [PARTITION_COLUMN, COLUMN],
target_column: [PARTITION_COLUMN, TARGET_COLUMN],
validate: false,
reverse_lock_order: true,
on_update: :cascade,
on_delete: :cascade,
name: FK_NAME
)
end
def down
with_lock_retries do
remove_foreign_key_if_exists(
SOURCE_TABLE_NAME,
TARGET_TABLE_NAME,
name: FK_NAME,
reverse_lock_order: true
)
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class ValidateAsyncFkCiDailyBuildGroupReportResultsParIdLastPipelineId < Gitlab::Database::Migration[2.2]
milestone '17.2'
TABLE_NAME = :ci_daily_build_group_report_results
FK_NAME = :fk_rails_ee072d13b3_p
COLUMNS = [:partition_id, :last_pipeline_id]
def up
prepare_async_foreign_key_validation(TABLE_NAME, COLUMNS, name: FK_NAME)
end
def down
unprepare_async_foreign_key_validation(TABLE_NAME, COLUMNS, name: FK_NAME)
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
class FkToCiPipelinesFromCiSourcesProjectsOnPartitionIdAndPipelineId < Gitlab::Database::Migration[2.2]
milestone '17.2'
disable_ddl_transaction!
SOURCE_TABLE_NAME = :ci_sources_projects
TARGET_TABLE_NAME = :ci_pipelines
COLUMN = :pipeline_id
TARGET_COLUMN = :id
FK_NAME = :fk_rails_10a1eb379a_p
PARTITION_COLUMN = :partition_id
def up
add_concurrent_foreign_key(
SOURCE_TABLE_NAME,
TARGET_TABLE_NAME,
column: [PARTITION_COLUMN, COLUMN],
target_column: [PARTITION_COLUMN, TARGET_COLUMN],
validate: true,
reverse_lock_order: true,
on_update: :cascade,
on_delete: :cascade,
name: FK_NAME
)
end
def down
with_lock_retries do
remove_foreign_key_if_exists(
SOURCE_TABLE_NAME,
TARGET_TABLE_NAME,
name: FK_NAME,
reverse_lock_order: true
)
end
end
end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
class RemoveFkToCiPipelinesCiSourcesProjectsOnPipelineId < Gitlab::Database::Migration[2.2]
milestone '17.2'
disable_ddl_transaction!
SOURCE_TABLE_NAME = :ci_sources_projects
TARGET_TABLE_NAME = :ci_pipelines
COLUMN = :pipeline_id
TARGET_COLUMN = :id
FK_NAME = :fk_rails_10a1eb379a
def up
with_lock_retries do
remove_foreign_key_if_exists(
SOURCE_TABLE_NAME,
TARGET_TABLE_NAME,
name: FK_NAME,
reverse_lock_order: true
)
end
end
def down
add_concurrent_foreign_key(
SOURCE_TABLE_NAME,
TARGET_TABLE_NAME,
column: COLUMN,
target_column: TARGET_COLUMN,
validate: true,
reverse_lock_order: true,
on_delete: :cascade,
name: FK_NAME
)
end
end

View File

@ -0,0 +1 @@
9170262cb868bc440128752eedfd21e042f4ea69ec61c987a8af89edf9616f93

View File

@ -0,0 +1 @@
fa9bc8a810958a939d97f9c1a9d8f8c57c136cd2de2e5e9b9d9fe96de15380d1

View File

@ -0,0 +1 @@
2192a39694251af43e0f4312ae4f8ea97e474edca74fe2ae340e3cbb153cced8

View File

@ -0,0 +1 @@
a9ba59901844135e10bf0666a106982c2dcd73e01e1dec6869c8783157f3ea18

View File

@ -0,0 +1 @@
4ec6ade0a0b8008ed978cba9a530ae6e5b1efbde0281749fd82fc53d12d58ae3

View File

@ -0,0 +1 @@
ab12a8cb06d71254d8e56022957eebdf3bca931784ca1ccf8efefd6ade9d5aba

View File

@ -0,0 +1 @@
6bb086e637c6559dc6f3a92f9415da3de4ad151fc198343c99348c205e9dd5e2

View File

@ -13212,7 +13212,8 @@ CREATE TABLE namespace_details (
cached_markdown_version integer,
description text,
description_html text,
creator_id bigint
creator_id bigint,
pending_delete boolean DEFAULT false NOT NULL
);
CREATE TABLE namespace_ldap_settings (
@ -33392,7 +33393,7 @@ ALTER TABLE ONLY audit_events_streaming_headers
ADD CONSTRAINT fk_rails_109fcf96e2 FOREIGN KEY (external_audit_event_destination_id) REFERENCES audit_events_external_audit_event_destinations(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_sources_projects
ADD CONSTRAINT fk_rails_10a1eb379a FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ADD CONSTRAINT fk_rails_10a1eb379a_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY zoom_meetings
ADD CONSTRAINT fk_rails_1190f0e0fa FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@ -34528,6 +34529,9 @@ ALTER TABLE ONLY saved_replies
ALTER TABLE ONLY ci_pipeline_artifacts
ADD CONSTRAINT fk_rails_a9e811a466 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_pipeline_artifacts
ADD CONSTRAINT fk_rails_a9e811a466_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
ALTER TABLE ONLY merge_request_user_mentions
ADD CONSTRAINT fk_rails_aa1b2961b1 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
@ -35041,6 +35045,9 @@ ALTER TABLE ONLY packages_debian_group_distributions
ALTER TABLE ONLY ci_daily_build_group_report_results
ADD CONSTRAINT fk_rails_ee072d13b3 FOREIGN KEY (last_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_daily_build_group_report_results
ADD CONSTRAINT fk_rails_ee072d13b3_p FOREIGN KEY (partition_id, last_pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
ALTER TABLE ONLY import_source_users
ADD CONSTRAINT fk_rails_ee30e569be FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;

View File

@ -218,8 +218,7 @@ Prerequisites:
- You have configured DNS setup
[without a wildcard](#for-namespace-in-url-path-without-wildcard-dns).
1. In `/etc/gitlab/gitlab.rb`, set the external URL for GitLab Pages, and enable
the feature flag:
1. In `/etc/gitlab/gitlab.rb`, set the external URL for GitLab Pages, and enable the feature:
```ruby
# External_url here is only for reference
@ -309,8 +308,7 @@ In this configuration, NGINX proxies all requests to the daemon. The GitLab Page
daemon doesn't listen to the outside world:
1. Add your TLS certificate and key as mentioned in the prerequisites into `/etc/gitlab/ssl`.
1. In `/etc/gitlab/gitlab.rb`, set the external URL for GitLab Pages, and enable
the feature flag:
1. In `/etc/gitlab/gitlab.rb`, set the external URL for GitLab Pages, and enable the feature:
```ruby
# The external_url field is here only for reference.

View File

@ -694,6 +694,7 @@ listed in the descriptions of the relevant settings.
| `duo_features_enabled` | boolean | no | Indicates whether GitLab Duo features are enabled for this instance. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144931) in GitLab 16.10. Self-managed, Premium and Ultimate only. |
| `lock_duo_features_enabled` | boolean | no | Indicates whether the GitLab Duo features enabled setting is enforced for all subgroups. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144931) in GitLab 16.10. Self-managed, Premium and Ultimate only. |
| `nuget_skip_metadata_url_validation` | boolean | no | Indicates whether to skip metadata URL validation for the NuGet package. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145887) in GitLab 17.0. |
| `require_admin_two_factor_authentication` | boolean | no | Allow administrators to require 2FA for all administrators on the instance. |
### Configure inactive project deletion

View File

@ -177,12 +177,22 @@ must meet the following system requirements:
These requirements have been tested on Debian 10.13 and Ubuntu 20.04.
For more information, see the [VS Code documentation](https://code.visualstudio.com/docs/remote/linux).
## Workspace add-ons
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385157) in GitLab 17.2.
The GitLab Workflow extension for VS Code is configured by default in workspaces.
With this extension, you can view issues, create and review merge requests, and manage CI/CD pipelines.
The extension also powers AI features like GitLab Duo Code Suggestions and GitLab Duo Chat.
For more information, see [GitLab Workflow extension for VS Code](https://gitlab.com/gitlab-org/gitlab-vscode-extension).
## Personal access token
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129715) in GitLab 16.4.
> - `api` permission [added](https://gitlab.com/gitlab-org/gitlab/-/issues/385157) in GitLab 17.2.
When you [create a workspace](configuration.md#create-a-workspace), you get a personal access token with `write_repository` permission.
This token is used to initially clone the project while starting the workspace.
When you [create a workspace](configuration.md#create-a-workspace), you get a personal access token with `write_repository` and `api` permissions.
This token is used to initially clone the project while starting the workspace and to configure the GitLab Workflow extension for VS Code.
Any Git operation you perform in the workspace uses this token for authentication and authorization.
When you terminate the workspace, the token is revoked.

View File

@ -1,48 +0,0 @@
# frozen_string_literal: true
module Gitlab
module WebIde
#
# Base GitLab WebIde Configuration facade
#
class Config
ConfigError = Class.new(StandardError)
def initialize(config, opts = {})
@config = build_config(config, opts)
@global = Entry::Global.new(@config,
with_image_ports: true)
@global.compose!
rescue Gitlab::Config::Loader::FormatError => e
raise Config::ConfigError, e.message
end
def valid?
@global.valid?
end
def errors
@global.errors
end
def to_hash
@config
end
def terminal_value
@global.terminal_value
end
def schemas_value
@global.schemas_value
end
private
def build_config(config, opts = {})
Gitlab::Config::Loader::Yaml.new(config).load!
end
end
end
end

View File

@ -1,33 +0,0 @@
# frozen_string_literal: true
module Gitlab
module WebIde
class Config
module Entry
##
# This class represents a global entry - root Entry for entire
# GitLab WebIde Configuration file.
#
class Global < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Attributable
def self.allowed_keys
%i[terminal].freeze
end
validations do
validates :config, allowed_keys: Global.allowed_keys
end
attributes allowed_keys
entry :terminal, Entry::Terminal,
description: 'Configuration of the webide terminal.'
end
end
end
end
end
::Gitlab::WebIde::Config::Entry::Global.prepend_mod_with('Gitlab::WebIde::Config::Entry::Global')

View File

@ -1,80 +0,0 @@
# frozen_string_literal: true
module Gitlab
module WebIde
class Config
module Entry
##
# Entry that represents a concrete CI/CD job.
#
class Terminal < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Attributable
include Gitlab::Utils::StrongMemoize
# By default the build will finish in a few seconds, not giving the webide
# enough time to connect to the terminal. This default script provides
# those seconds blocking the build from finishing inmediately.
DEFAULT_SCRIPT = ['sleep 60'].freeze
ALLOWED_KEYS = %i[image services tags before_script script variables].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
validates :config, job_port_unique: { data: ->(record) { record.ports } }
with_options allow_nil: true do
validates :tags, array_of_strings: true
end
end
entry :before_script, ::Gitlab::Ci::Config::Entry::Commands,
description: 'Global before script overridden in this job.'
entry :script, ::Gitlab::Ci::Config::Entry::Commands,
description: 'Commands that will be executed in this job.'
entry :image, ::Gitlab::Ci::Config::Entry::Image,
description: 'Image that will be used to execute this job.'
entry :services, ::Gitlab::Ci::Config::Entry::Services,
description: 'Services that will be used to execute this job.'
entry :variables, ::Gitlab::Ci::Config::Entry::Variables,
description: 'Environment variables available for this job.'
attributes :tags
def value
to_hash.compact
end
private
def to_hash
{
tag_list: tags || [],
job_variables: yaml_variables,
options: {
image: image_value,
services: services_value,
before_script: before_script_value,
script: script_value || DEFAULT_SCRIPT
}.compact
}.compact
end
def yaml_variables
strong_memoize(:yaml_variables) do
next unless variables_value
variables_value.map do |key, value|
{ key: key.to_s, value: value, public: true }
end
end
end
end
end
end
end
end

View File

@ -1,67 +0,0 @@
# frozen_string_literal: true
module Gitlab
module WebIde
module DefaultOauthApplication
class << self
def feature_enabled?(current_user)
Feature.enabled?(:vscode_web_ide, current_user) && Feature.enabled?(:web_ide_oauth, current_user)
end
def oauth_application
application_settings.web_ide_oauth_application
end
def oauth_callback_url
Gitlab::Routing.url_helpers.ide_oauth_redirect_url
end
def oauth_application_id
oauth_application ? oauth_application.id : nil
end
def oauth_application_callback_urls
return [] unless oauth_application
URI.extract(oauth_application.redirect_uri, %w[http https]).uniq
end
def ensure_oauth_application!
return if oauth_application
should_expire_cache = false
application_settings.transaction do
# note: This should run very rarely and should be safe for us to do a lock
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132496#note_1587293087
application_settings.lock!
# note: `lock!`` breaks applicaiton_settings cache and will trigger another query.
# We need to double check here so that requests previously waiting on the lock can
# now just skip.
next if oauth_application
application = Doorkeeper::Application.new(
name: 'GitLab Web IDE',
redirect_uri: oauth_callback_url,
scopes: ['api'],
trusted: true,
confidential: false)
application.save!
application_settings.update!(web_ide_oauth_application: application)
should_expire_cache = true
end
# note: This needs to happen outside the transaction, but only if we actually changed something
::Gitlab::CurrentSettings.expire_current_application_settings if should_expire_cache
end
private
def application_settings
::Gitlab::CurrentSettings.current_application_settings
end
end
end
end
end

View File

@ -1,120 +0,0 @@
# frozen_string_literal: true
module Gitlab
module WebIde
module ExtensionsMarketplace
# NOTE: These `disabled_reason` enumeration values are also referenced/consumed in
# the "gitlab-web-ide" and "gitlab-web-ide-vscode-fork" projects
# (https://gitlab.com/gitlab-org/gitlab-web-ide & https://gitlab.com/gitlab-org/gitlab-web-ide-vscode-fork),
# so we must ensure that any changes made here are also reflected in those projects.
DISABLED_REASONS =
%i[
no_user
no_flag
instance_disabled
opt_in_unset
opt_in_disabled
].to_h { |reason| [reason, reason] }.freeze
class << self
def feature_enabled?(user:)
# TODO: Add instance-level setting for this https://gitlab.com/gitlab-org/gitlab/-/issues/451871
# note: OAuth **must** be enabled for us to use the extension marketplace
::Gitlab::WebIde::DefaultOauthApplication.feature_enabled?(user) &&
Feature.enabled?(:web_ide_extensions_marketplace, user)
end
def vscode_settings
# TODO: Add instance-level setting for this https://gitlab.com/gitlab-org/gitlab/-/issues/451871
# TODO: We need to harmonize this with `lib/remote_development/settings/defaults_initializer.rb`
# https://gitlab.com/gitlab-org/gitlab/-/issues/460515
{
item_url: 'https://open-vsx.org/vscode/item',
service_url: 'https://open-vsx.org/vscode/gallery',
resource_url_template:
'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}',
control_url: '',
nls_base_url: '',
publisher_url: ''
}
end
# This value is used when the end-user is accepting the third-party extension marketplace integration.
def marketplace_home_url
"https://open-vsx.org"
end
def help_url
::Gitlab::Routing.url_helpers.help_page_url('user/project/web_ide/index', anchor: 'extension-marketplace')
end
def help_preferences_url
::Gitlab::Routing.url_helpers.help_page_url('user/profile/preferences',
anchor: 'integrate-with-the-extension-marketplace')
end
def user_preferences_url
::Gitlab::Routing.url_helpers.profile_preferences_url(anchor: 'integrations')
end
# This returns a value to be used in the Web IDE config `extensionsGallerySettings`
# It should match the type expected by the Web IDE:
#
# - https://gitlab.com/gitlab-org/gitlab-web-ide/-/blob/51f9e91f890752596e7a3ef51f436fea07885eff/packages/web-ide-types/src/config.ts#L109
#
# @return [Hash]
def webide_extensions_gallery_settings(user:)
flag_enabled = feature_enabled?(user: user)
metadata = metadata_for_user(user: user, flag_enabled: flag_enabled)
return { enabled: true, vscode_settings: vscode_settings } if metadata.fetch(:enabled)
disabled_reason = metadata.fetch(:disabled_reason, nil)
result = { enabled: false, reason: disabled_reason, help_url: help_url }
if disabled_reason == :opt_in_unset || disabled_reason == :opt_in_disabled
result[:user_preferences_url] = user_preferences_url
end
result
end
# @param [User, nil] user
# @param [Boolean, nil] flag_enabled
# @return [Hash]
def metadata_for_user(user:, flag_enabled:)
return metadata_disabled(:no_user) unless user
return metadata_disabled(:no_flag) if flag_enabled.nil?
return metadata_disabled(:instance_disabled) unless flag_enabled
# noinspection RubyNilAnalysis -- RubyMine doesn't realize user can't be nil because of guard clause above
opt_in_status = user.extensions_marketplace_opt_in_status.to_sym
case opt_in_status
when :enabled
return metadata_enabled
when :unset
return metadata_disabled(:opt_in_unset)
when :disabled
return metadata_disabled(:opt_in_disabled)
end
# This is an internal bug due to an enumeration mismatch/inconsistency with the model
raise "Invalid user.extensions_marketplace_opt_in_status: '#{opt_in_status}'. " \
"Supported statuses are: #{Enums::WebIde::ExtensionsMarketplaceOptInStatus.statuses.keys}." # rubocop:disable Layout/LineEndStringConcatenationIndentation -- This is already changed in the next version of gitlab-styles
end
private
def metadata_enabled
{ enabled: true }
end
def metadata_disabled(reason)
{ enabled: false, disabled_reason: DISABLED_REASONS.fetch(reason) }
end
end
end
end
end

View File

@ -16,7 +16,7 @@ module RemoteDevelopment
extensions_marketplace_feature_flag_enabled
}
extensions_gallery_metadata = ::Gitlab::WebIde::ExtensionsMarketplace.metadata_for_user(
extensions_gallery_metadata = ::WebIde::ExtensionsMarketplace.metadata_for_user(
user: user,
flag_enabled: extensions_marketplace_feature_flag_enabled
)

46
lib/web_ide/config.rb Normal file
View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
module WebIde
#
# Base GitLab WebIde Configuration facade
#
class Config
ConfigError = Class.new(StandardError)
def initialize(config, opts = {})
@config = build_config(config, opts)
@global = Entry::Global.new(@config,
with_image_ports: true)
@global.compose!
rescue Gitlab::Config::Loader::FormatError => e
raise Config::ConfigError, e.message
end
def valid?
@global.valid?
end
def errors
@global.errors
end
def to_hash
@config
end
def terminal_value
@global.terminal_value
end
def schemas_value
@global.schemas_value
end
private
def build_config(config, _opts = {})
Gitlab::Config::Loader::Yaml.new(config).load!
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module WebIde
class Config
module Entry
##
# This class represents a global entry - root Entry for entire
# GitLab WebIde Configuration file.
#
class Global < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Attributable
def self.allowed_keys
%i[terminal].freeze
end
validations do
validates :config, allowed_keys: Global.allowed_keys
end
attributes allowed_keys
entry :terminal, Entry::Terminal,
description: 'Configuration of the webide terminal.'
end
end
end
end
::WebIde::Config::Entry::Global.prepend_mod_with('WebIde::Config::Entry::Global')

View File

@ -0,0 +1,78 @@
# frozen_string_literal: true
module WebIde
class Config
module Entry
##
# Entry that represents a concrete CI/CD job.
#
class Terminal < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Attributable
include Gitlab::Utils::StrongMemoize
# By default the build will finish in a few seconds, not giving the webide
# enough time to connect to the terminal. This default script provides
# those seconds blocking the build from finishing inmediately.
DEFAULT_SCRIPT = ['sleep 60'].freeze
ALLOWED_KEYS = %i[image services tags before_script script variables].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
validates :config, job_port_unique: { data: ->(record) { record.ports } }
with_options allow_nil: true do
validates :tags, array_of_strings: true
end
end
entry :before_script, ::Gitlab::Ci::Config::Entry::Commands,
description: 'Global before script overridden in this job.'
entry :script, ::Gitlab::Ci::Config::Entry::Commands,
description: 'Commands that will be executed in this job.'
entry :image, ::Gitlab::Ci::Config::Entry::Image,
description: 'Image that will be used to execute this job.'
entry :services, ::Gitlab::Ci::Config::Entry::Services,
description: 'Services that will be used to execute this job.'
entry :variables, ::Gitlab::Ci::Config::Entry::Variables,
description: 'Environment variables available for this job.'
attributes :tags
def value
to_hash.compact
end
private
def to_hash
{
tag_list: tags || [],
job_variables: yaml_variables,
options: {
image: image_value,
services: services_value,
before_script: before_script_value,
script: script_value || DEFAULT_SCRIPT
}.compact
}.compact
end
def yaml_variables
strong_memoize(:yaml_variables) do # rubocop:todo Gitlab/StrongMemoizeAttr -- legacy Web IDE is deprecated, not fixing this because. Also not sure if strong_memoize_attr will change behavior
next unless variables_value
variables_value.map do |key, value|
{ key: key.to_s, value: value, public: true }
end
end
end
end
end
end
end

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
module WebIde
module DefaultOauthApplication
class << self
def feature_enabled?(current_user)
Feature.enabled?(:vscode_web_ide, current_user) && Feature.enabled?(:web_ide_oauth, current_user)
end
def oauth_application
application_settings.web_ide_oauth_application
end
def oauth_callback_url
Gitlab::Routing.url_helpers.ide_oauth_redirect_url
end
def oauth_application_id
oauth_application ? oauth_application.id : nil
end
def oauth_application_callback_urls
return [] unless oauth_application
URI.extract(oauth_application.redirect_uri, %w[http https]).uniq
end
def ensure_oauth_application!
return if oauth_application
should_expire_cache = false
application_settings.transaction do
# note: This should run very rarely and should be safe for us to do a lock
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132496#note_1587293087
application_settings.lock!
# note: `lock!`` breaks applicaiton_settings cache and will trigger another query.
# We need to double check here so that requests previously waiting on the lock can
# now just skip.
next if oauth_application
application = Doorkeeper::Application.new(
name: 'GitLab Web IDE',
redirect_uri: oauth_callback_url,
scopes: ['api'],
trusted: true,
confidential: false)
application.save!
application_settings.update!(web_ide_oauth_application: application)
should_expire_cache = true
end
# note: This needs to happen outside the transaction, but only if we actually changed something
::Gitlab::CurrentSettings.expire_current_application_settings if should_expire_cache
end
private
def application_settings
::Gitlab::CurrentSettings.current_application_settings
end
end
end
end

View File

@ -0,0 +1,118 @@
# frozen_string_literal: true
module WebIde
module ExtensionsMarketplace
# NOTE: These `disabled_reason` enumeration values are also referenced/consumed in
# the "gitlab-web-ide" and "gitlab-web-ide-vscode-fork" projects
# (https://gitlab.com/gitlab-org/gitlab-web-ide & https://gitlab.com/gitlab-org/gitlab-web-ide-vscode-fork),
# so we must ensure that any changes made here are also reflected in those projects.
DISABLED_REASONS =
%i[
no_user
no_flag
instance_disabled
opt_in_unset
opt_in_disabled
].to_h { |reason| [reason, reason] }.freeze
class << self
def feature_enabled?(user:)
# TODO: Add instance-level setting for this https://gitlab.com/gitlab-org/gitlab/-/issues/451871
# note: OAuth **must** be enabled for us to use the extension marketplace
::WebIde::DefaultOauthApplication.feature_enabled?(user) &&
Feature.enabled?(:web_ide_extensions_marketplace, user)
end
def vscode_settings
# TODO: Add instance-level setting for this https://gitlab.com/gitlab-org/gitlab/-/issues/451871
# TODO: We need to harmonize this with `lib/remote_development/settings/defaults_initializer.rb`
# https://gitlab.com/gitlab-org/gitlab/-/issues/460515
{
item_url: 'https://open-vsx.org/vscode/item',
service_url: 'https://open-vsx.org/vscode/gallery',
resource_url_template:
'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}',
control_url: '',
nls_base_url: '',
publisher_url: ''
}
end
# This value is used when the end-user is accepting the third-party extension marketplace integration.
def marketplace_home_url
"https://open-vsx.org"
end
def help_url
::Gitlab::Routing.url_helpers.help_page_url('user/project/web_ide/index', anchor: 'extension-marketplace')
end
def help_preferences_url
::Gitlab::Routing.url_helpers.help_page_url('user/profile/preferences',
anchor: 'integrate-with-the-extension-marketplace')
end
def user_preferences_url
::Gitlab::Routing.url_helpers.profile_preferences_url(anchor: 'integrations')
end
# This returns a value to be used in the Web IDE config `extensionsGallerySettings`
# It should match the type expected by the Web IDE:
#
# - https://gitlab.com/gitlab-org/gitlab-web-ide/-/blob/51f9e91f890752596e7a3ef51f436fea07885eff/packages/web-ide-types/src/config.ts#L109
#
# @return [Hash]
def webide_extensions_gallery_settings(user:)
flag_enabled = feature_enabled?(user: user)
metadata = metadata_for_user(user: user, flag_enabled: flag_enabled)
return { enabled: true, vscode_settings: vscode_settings } if metadata.fetch(:enabled)
disabled_reason = metadata.fetch(:disabled_reason, nil)
result = { enabled: false, reason: disabled_reason, help_url: help_url }
if disabled_reason == :opt_in_unset || disabled_reason == :opt_in_disabled
result[:user_preferences_url] = user_preferences_url
end
result
end
# @param [User, nil] user
# @param [Boolean, nil] flag_enabled
# @return [Hash]
def metadata_for_user(user:, flag_enabled:)
return metadata_disabled(:no_user) unless user
return metadata_disabled(:no_flag) if flag_enabled.nil?
return metadata_disabled(:instance_disabled) unless flag_enabled
# noinspection RubyNilAnalysis -- RubyMine doesn't realize user can't be nil because of guard clause above
opt_in_status = user.extensions_marketplace_opt_in_status.to_sym
case opt_in_status
when :enabled
metadata_enabled
when :unset
metadata_disabled(:opt_in_unset)
when :disabled
metadata_disabled(:opt_in_disabled)
else
# This is an internal bug due to an enumeration mismatch/inconsistency with the model
raise "Invalid user.extensions_marketplace_opt_in_status: '#{opt_in_status}'. " \
"Supported statuses are: #{Enums::WebIde::ExtensionsMarketplaceOptInStatus.statuses.keys}." # rubocop:disable Layout/LineEndStringConcatenationIndentation -- This is already changed in the next version of gitlab-styles
end
end
private
def metadata_enabled
{ enabled: true }
end
def metadata_disabled(reason)
{ enabled: false, disabled_reason: DISABLED_REASONS.fetch(reason) }
end
end
end
end

View File

@ -18,6 +18,9 @@ RSpec.describe 'Database schema', feature_category: :database do
slack_integrations_scopes: [%w[slack_api_scope_id]],
notes: %w[namespace_id], # this index is added in an async manner, hence it needs to be ignored in the first phase.
users: [%w[accepted_term_id]],
ci_pipeline_artifacts: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient
ci_sources_projects: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient
ci_daily_build_group_report_results: [%w[partition_id last_pipeline_id]], # index on last_pipeline_id is sufficient
ci_builds: [%w[partition_id stage_id], %w[partition_id execution_config_id], %w[partition_id upstream_pipeline_id], %w[auto_canceled_by_partition_id auto_canceled_by_id], %w[partition_id commit_id]], # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142804#note_1745483081
ci_pipeline_variables: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient
ci_pipelines_config: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
FactoryBot.define do
factory :audit_events_group_audit_event, class: 'AuditEvents::GroupAuditEvent' do
user
transient { target_group { association(:group) } }
group_id { target_group.id }
entity_path { target_group.full_path }
target_details { target_group.name }
ip_address { IPAddr.new '127.0.0.1' }
details do
{
change: 'project_creation_level',
from: nil,
to: 'Developers + Maintainers',
author_name: 'Jane Doe',
target_id: target_group.id,
target_type: 'Group',
target_details: target_group.name,
ip_address: '127.0.0.1',
entity_path: target_group.full_path
}
end
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
FactoryBot.define do
factory :audit_events_instance_audit_event, class: 'AuditEvents::InstanceAuditEvent' do
user
entity_path { "gitlab_instance" }
target_details { "Default project visibility" }
ip_address { IPAddr.new '127.0.0.1' }
author_name { 'Jane Doe' }
details do
{
change: "default_project_visibility",
from: 0,
to: 10,
target_details: "Default project visibility",
event_name: "application_setting_updated",
author_name: 'Jane Doe',
target_id: 1,
target_type: "ApplicationSetting",
custom_message: "Changed default_project_visibility from 0 to 10",
ip_address: "127.0.0.1",
entity_path: "gitlab_instance"
}
end
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
FactoryBot.define do
factory :audit_events_project_audit_event, class: 'AuditEvents::ProjectAuditEvent' do
user
transient { target_project { association(:project) } }
project_id { target_project.id }
entity_path { target_project.full_path }
target_details { target_project.name }
ip_address { IPAddr.new '127.0.0.1' }
details do
{
change: 'packages_enabled',
from: true,
to: false,
author_name: user.name,
target_id: target_project.id,
target_type: 'Project',
target_details: target_project.name,
ip_address: '127.0.0.1',
entity_path: target_project.full_path
}
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
FactoryBot.define do
factory :audit_events_user_audit_event, class: 'AuditEvents::UserAuditEvent' do
user
user_id { user.id }
entity_path { user.full_path }
target_details { user.name }
ip_address { IPAddr.new '127.0.0.1' }
author_name { 'Jane Doe' }
details do
{
change: 'email address',
from: 'admin@gitlab.com',
to: 'maintainer@gitlab.com',
author_name: 'Jane Doe',
target_id: user.id,
target_type: 'User',
target_details: user.name,
ip_address: '127.0.0.1',
entity_path: user.full_path
}
end
end
end

View File

@ -7,10 +7,15 @@ RSpec.describe Organizations::GroupsFinder, feature_category: :groups_and_projec
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(:public_group) { create(:group, name: 'public-group', organization: organization) }
let_it_be(:outside_organization_group) { create(:group) }
let_it_be(:private_group) { create(:group, :private, name: 'private-group', organization: organization) }
let_it_be(:no_access_group_in_org) { create(:group, :private, name: 'no-access', organization: organization) }
let_it_be_with_reload(:public_group) { create(:group, name: 'public-group', organization: organization) }
let_it_be_with_reload(:outside_organization_group) { create(:group) }
let_it_be_with_reload(:private_group) do
create(:group, :private, name: 'private-group', organization: organization)
end
let_it_be_with_reload(:no_access_group_in_org) do
create(:group, :private, name: 'no-access', organization: organization)
end
let(:current_user) { user }
let(:params) { { organization: organization } }
@ -59,5 +64,23 @@ RSpec.describe Organizations::GroupsFinder, feature_category: :groups_and_projec
result
end
end
it 'filters deleted groups' do
public_group.namespace_details.update!(pending_delete: true)
expect(result).not_to include(public_group)
end
context 'when filter_deleted_groups feature flag is disabled' do
before do
stub_feature_flags(filter_deleted_groups: false)
end
it 'includes deleted groups' do
public_group.namespace_details.update!(pending_delete: true)
expect(result).to include(public_group)
end
end
end
end

View File

@ -89,6 +89,14 @@ fragment TypeRef on __Type {
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}

View File

@ -21,9 +21,12 @@ class CustomEnvironment extends TestEnvironment {
const { error: originalErrorFn } = context.console;
Object.assign(context.console, {
error(...args) {
const firstError = args?.[0];
if (
args?.[0]?.includes('[Vue warn]: Missing required prop') ||
args?.[0]?.includes('[Vue warn]: Invalid prop')
typeof firstError === 'string' &&
['[Vue warn]: Missing required prop', '[Vue warn]: Invalid prop'].some((line) =>
firstError.startsWith(line),
)
) {
originalErrorFn.apply(context.console, args);
return;

View File

@ -84,7 +84,7 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
end
it 'includes extensions gallery settings' do
expect(Gitlab::WebIde::ExtensionsMarketplace).to receive(:webide_extensions_gallery_settings)
expect(WebIde::ExtensionsMarketplace).to receive(:webide_extensions_gallery_settings)
.with(user: user).and_return({ enabled: false })
actual = helper.ide_data(project: nil, fork_info: fork_info, params: params)

View File

@ -280,7 +280,7 @@ RSpec.describe PreferencesHelper do
context 'when WebIdeExtensionsMarketplace is enabled' do
before do
allow(Gitlab::WebIde::ExtensionsMarketplace).to receive(:feature_enabled?).with(user: user).and_return(true)
allow(WebIde::ExtensionsMarketplace).to receive(:feature_enabled?).with(user: user).and_return(true)
end
it 'includes extension marketplace integration' do

View File

@ -7,7 +7,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillPartitionIdCiDailyBuildGroup
let(:ci_daily_build_group_report_results_table) { table(:ci_daily_build_group_report_results, database: :ci) }
let!(:pipeline_1) { ci_pipelines_table.create!(id: 1, partition_id: 100) }
let!(:pipeline_2) { ci_pipelines_table.create!(id: 2, partition_id: 101) }
let!(:pipeline_3) { ci_pipelines_table.create!(id: 3, partition_id: 101) }
let!(:pipeline_3) { ci_pipelines_table.create!(id: 3, partition_id: 100) }
let!(:ci_daily_build_group_report_results_100) do
ci_daily_build_group_report_results_table.create!(
date: Date.yesterday,
@ -43,7 +43,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillPartitionIdCiDailyBuildGroup
data: { 'coverage' => 77.0 },
default_branch: true,
last_pipeline_id: pipeline_3.id,
partition_id: pipeline_1.partition_id
partition_id: pipeline_3.partition_id
)
end
@ -55,18 +55,29 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillPartitionIdCiDailyBuildGroup
batch_column: :id,
sub_batch_size: 1,
pause_ms: 0,
connection: Ci::ApplicationRecord.connection
connection: connection
}
end
let!(:migration) { described_class.new(**migration_attrs) }
let(:connection) { Ci::ApplicationRecord.connection }
around do |example|
connection.transaction do
connection.execute(<<~SQL)
ALTER TABLE ci_pipelines DISABLE TRIGGER ALL;
SQL
example.run
connection.execute(<<~SQL)
ALTER TABLE ci_pipelines ENABLE TRIGGER ALL;
SQL
end
end
describe '#perform' do
context 'when second partition does not exist' do
before do
pipeline_3.update!(partition_id: 100)
end
it 'does not execute the migration' do
expect { migration.perform }
.not_to change { invalid_ci_daily_build_group_report_results.reload.partition_id }
@ -74,6 +85,10 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillPartitionIdCiDailyBuildGroup
end
context 'when second partition exists' do
before do
pipeline_3.update!(partition_id: 101)
end
it 'fixes invalid records in the wrong the partition' do
expect { migration.perform }
.to not_change { ci_daily_build_group_report_results_100.reload.partition_id }

View File

@ -8,7 +8,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillPartitionIdCiPipelineArtifac
let(:ci_pipeline_artifacts_table) { table(:ci_pipeline_artifacts, database: :ci) }
let!(:pipeline_100) { ci_pipelines_table.create!(id: 1, partition_id: 100) }
let!(:pipeline_101) { ci_pipelines_table.create!(id: 2, partition_id: 101) }
let!(:pipeline_102) { ci_pipelines_table.create!(id: 3, partition_id: 101) }
let!(:pipeline_102) { ci_pipelines_table.create!(id: 3, partition_id: 100) }
let!(:ci_pipeline_artifact_100) do
ci_pipeline_artifacts_table.create!(
id: 1,
@ -50,7 +50,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillPartitionIdCiPipelineArtifac
file: fixture_file_upload(
Rails.root.join('spec/fixtures/pipeline_artifacts/code_coverage.json'), 'application/json'
),
partition_id: pipeline_100.partition_id
partition_id: pipeline_102.partition_id
)
end
@ -62,11 +62,26 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillPartitionIdCiPipelineArtifac
batch_column: :id,
sub_batch_size: 1,
pause_ms: 0,
connection: Ci::ApplicationRecord.connection
connection: connection
}
end
let!(:migration) { described_class.new(**migration_attrs) }
let(:connection) { Ci::ApplicationRecord.connection }
around do |example|
connection.transaction do
connection.execute(<<~SQL)
ALTER TABLE ci_pipelines DISABLE TRIGGER ALL;
SQL
example.run
connection.execute(<<~SQL)
ALTER TABLE ci_pipelines ENABLE TRIGGER ALL;
SQL
end
end
describe '#perform' do
context 'when second partition does not exist' do
@ -79,6 +94,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillPartitionIdCiPipelineArtifac
context 'when second partition exists' do
before do
allow(migration).to receive(:uses_multiple_partitions?).and_return(true)
pipeline_102.update!(partition_id: 101)
end
it 'fixes invalid records in the wrong the partition' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::WebIde::Config::Entry::Global do
RSpec.describe WebIde::Config::Entry::Global, feature_category: :web_ide do
let(:global) { described_class.new(hash) }
describe '.nodes' do
@ -38,7 +38,7 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Global do
it 'creates node object using valid class' do
expect(global.descendants.first)
.to be_an_instance_of Gitlab::WebIde::Config::Entry::Terminal
.to be_an_instance_of WebIde::Config::Entry::Terminal
end
it 'sets correct description for nodes' do
@ -108,7 +108,9 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Global do
describe '#errors' do
it 'reports errors about missing script' do
expect(global.errors)
.to include "terminal:before_script config should be a string or a nested array of strings up to 10 levels deep"
.to include(
"terminal:before_script config should be a string or a nested array of strings up to 10 levels deep"
)
end
end
end
@ -148,7 +150,7 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Global do
context 'when entry exists' do
it 'returns correct entry' do
expect(global[:terminal])
.to be_an_instance_of Gitlab::WebIde::Config::Entry::Terminal
.to be_an_instance_of WebIde::Config::Entry::Terminal
expect(global[:terminal][:before_script].value).to eq ['ls']
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::WebIde::Config::Entry::Terminal do
RSpec.describe WebIde::Config::Entry::Terminal, feature_category: :web_ide do
let(:entry) { described_class.new(config, with_image_ports: true) }
describe '.nodes' do
@ -35,7 +35,9 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Terminal do
let(:config) do
{
image: { name: "ruby", ports: [80] },
services: [{ name: "mysql", alias: "service1", ports: [81] }, { name: "mysql", alias: "service2", ports: [82] }]
services: [
{ name: "mysql", alias: "service1", ports: [81] }, { name: "mysql", alias: "service2", ports: [82] }
]
}
end
@ -63,7 +65,7 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Terminal do
end
context 'when entry value is not correct' do
context 'incorrect config value type' do
context 'when incorrect config value type' do
let(:config) { ['incorrect'] }
describe '#errors' do

View File

@ -2,19 +2,19 @@
require 'spec_helper'
RSpec.describe Gitlab::WebIde::Config do
RSpec.describe WebIde::Config, feature_category: :web_ide do
let(:config) do
described_class.new(yml)
end
context 'when config is valid' do
let(:yml) do
<<-EOS
<<-YAML
terminal:
image: image:1.0
before_script:
- gem install rspec
EOS
YAML
end
describe '#to_hash' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::WebIde::DefaultOauthApplication, feature_category: :web_ide do
RSpec.describe WebIde::DefaultOauthApplication, feature_category: :web_ide do
let_it_be(:current_user) { create(:user) }
let_it_be(:oauth_application) { create(:oauth_application, owner: nil) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::WebIde::ExtensionsMarketplace, feature_category: :web_ide do
RSpec.describe WebIde::ExtensionsMarketplace, feature_category: :web_ide do
using RSpec::Parameterized::TableSyntax
let_it_be_with_reload(:current_user) { create(:user) }
@ -25,7 +25,7 @@ RSpec.describe Gitlab::WebIde::ExtensionsMarketplace, feature_category: :web_ide
with_them do
it 'returns the expected value' do
stub_feature_flags(web_ide_extensions_marketplace: web_ide_extensions_marketplace)
expect(::Gitlab::WebIde::DefaultOauthApplication).to receive(:feature_enabled?)
expect(::WebIde::DefaultOauthApplication).to receive(:feature_enabled?)
.with(current_user).and_return(web_ide_oauth)
expect(described_class.feature_enabled?(user: current_user)).to be(expectation)

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::AuditEvents::GroupAuditEvent, feature_category: :audit_events do
it_behaves_like 'includes ::AuditEvents::CommonModel concern' do
let_it_be(:audit_event_symbol) { :audit_events_group_audit_event }
let_it_be(:audit_event_class) { described_class }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:group_id) }
end
describe '.by_group' do
let_it_be(:group_audit_event_1) { create(:audit_events_group_audit_event) }
let_it_be(:group_audit_event_2) { create(:audit_events_group_audit_event) }
subject(:event) { described_class.by_group(group_audit_event_1.group_id) }
it 'returns the correct audit event' do
expect(event).to contain_exactly(group_audit_event_1)
end
end
describe '#root_group_entity' do
let_it_be(:root_group) { create(:group) }
let_it_be(:group) { create(:group, parent: root_group) }
context 'when root_group_entity_id is set' do
subject(:event) { described_class.new(root_group_entity_id: root_group.id) }
it "return root_group_entity through root_group_entity_id" do
expect(event.root_group_entity).to eq(root_group)
end
end
context "when group is nil" do
subject(:event) { described_class.new(group: nil) }
it "return nil" do
expect(event.root_group_entity).to eq(nil)
end
end
subject(:event) { described_class.new(group: group) }
it "return root_group and set root_group_entity_id" do
expect(event.root_group_entity).to eq(root_group)
expect(event.root_group_entity_id).to eq(root_group.id)
end
end
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::AuditEvents::InstanceAuditEvent, feature_category: :audit_events do
it_behaves_like 'includes ::AuditEvents::CommonModel concern' do
let_it_be(:audit_event_symbol) { :audit_events_instance_audit_event }
let_it_be(:audit_event_class) { described_class }
end
end

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::AuditEvents::ProjectAuditEvent, feature_category: :audit_events do
it_behaves_like 'includes ::AuditEvents::CommonModel concern' do
let_it_be(:audit_event_symbol) { :audit_events_project_audit_event }
let_it_be(:audit_event_class) { described_class }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:project_id) }
end
describe '.by_project' do
let_it_be(:project_audit_event_1) { create(:audit_events_project_audit_event) }
let_it_be(:project_audit_event_2) { create(:audit_events_project_audit_event) }
subject(:event) { described_class.by_project(project_audit_event_1.project_id) }
it 'returns the correct audit event' do
expect(event).to contain_exactly(project_audit_event_1)
end
end
describe '#root_group_entity' do
let_it_be(:root_group) { create(:group) }
let_it_be(:project) { create(:project, group: root_group) }
context 'when root_group_entity_id is set' do
subject(:event) { described_class.new(root_group_entity_id: root_group.id) }
it "return root_group_entity through root_group_entity_id" do
expect(event.root_group_entity).to eq(root_group)
end
end
context "when project is nil" do
subject(:event) { described_class.new(project: nil) }
it "return nil" do
expect(event.root_group_entity).to eq(nil)
end
end
subject(:event) { described_class.new(project: project) }
it "return root_group and set root_group_entity_id" do
expect(event.root_group_entity).to eq(root_group)
expect(event.root_group_entity_id).to eq(root_group.id)
end
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::AuditEvents::UserAuditEvent, feature_category: :audit_events do
it_behaves_like 'includes ::AuditEvents::CommonModel concern' do
let_it_be(:audit_event_symbol) { :audit_events_user_audit_event }
let_it_be(:audit_event_class) { described_class }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:user_id) }
end
describe '.by_user' do
let_it_be(:user_audit_event_1) { create(:audit_events_user_audit_event) }
let_it_be(:user_audit_event_2) { create(:audit_events_user_audit_event) }
subject(:event) { described_class.by_user(user_audit_event_1.user_id) }
it 'returns the correct audit event' do
expect(event).to contain_exactly(user_audit_event_1)
end
end
describe '.by_username' do
let_it_be(:user_audit_event_1) { create(:audit_events_user_audit_event) }
let_it_be(:user_audit_event_2) { create(:audit_events_user_audit_event) }
subject(:event) { described_class.by_username(user_audit_event_1.user.name) }
before do
allow(User).to receive(:find_by_username).and_return(user_audit_event_1.user)
end
it 'returns the correct audit event' do
expect(event).to contain_exactly(user_audit_event_1)
end
end
end

View File

@ -51,19 +51,19 @@ RSpec.describe Ci::DailyBuildGroupReportResult, feature_category: :continuous_in
project_id: rspec_coverage.project_id,
ref_path: rspec_coverage.ref_path,
last_pipeline_id: new_pipeline.id,
partition_id: new_pipeline.partition_id,
date: rspec_coverage.date,
group_name: 'rspec',
data: { 'coverage' => 81.0 },
partition_id: 100
data: { 'coverage' => 81.0 }
},
{
project_id: rspec_coverage.project_id,
ref_path: rspec_coverage.ref_path,
last_pipeline_id: new_pipeline.id,
partition_id: new_pipeline.partition_id,
date: rspec_coverage.date,
group_name: 'karma',
data: { 'coverage' => 87.0 },
partition_id: 100
data: { 'coverage' => 87.0 }
}
])

View File

@ -435,6 +435,16 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
let_it_be(:namespace1sub) { create(:group, name: 'Sub Namespace', path: 'sub-namespace', parent: namespace1) }
let_it_be(:namespace2sub) { create(:group, name: 'Sub Namespace', path: 'sub-namespace', parent: namespace2) }
describe '.without_deleted' do
before do
namespace1.namespace_details.update!(pending_delete: true)
end
it 'does not include namespace marked as deleted' do
expect(described_class.without_deleted).to contain_exactly(namespace, namespace2, namespace1sub, namespace2sub)
end
end
describe '.by_parent' do
it 'includes correct namespaces' do
expect(described_class.by_parent(namespace1.id)).to match_array([namespace1sub])
@ -550,6 +560,8 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
it { is_expected.to delegate_method(:math_rendering_limits_enabled?).to(:namespace_settings) }
it { is_expected.to delegate_method(:lock_math_rendering_limits_enabled?).to(:namespace_settings) }
it { is_expected.to delegate_method(:add_creator).to(:namespace_details) }
it { is_expected.to delegate_method(:pending_delete).to(:namespace_details) }
it { is_expected.to delegate_method(:pending_delete=).to(:namespace_details).with_arguments(:args) }
it do
is_expected.to delegate_method(:prevent_sharing_groups_outside_hierarchy=).to(:namespace_settings)

View File

@ -196,8 +196,8 @@ RSpec.describe 'GitlabSchema configurations', feature_category: :integrations do
hash_including(
trace_type: 'execute_query',
"query_analysis.duration_s" => 7,
"query_analysis.complexity" => 181,
"query_analysis.depth" => 13,
"query_analysis.complexity" => 217,
"query_analysis.depth" => 15,
"query_analysis.used_deprecated_fields" => an_instance_of(Array),
"query_analysis.used_deprecated_arguments" => an_instance_of(Array),
"query_analysis.used_fields" => an_instance_of(Array)

View File

@ -86,8 +86,17 @@ RSpec.describe Groups::DestroyService, feature_category: :groups_and_projects do
end
end
shared_examples 'marks the group as pending delete' do |async|
specify do
expect(group).to receive(:update_attribute).with(:pending_delete, true)
destroy_group(group, user, async)
end
end
describe 'asynchronous delete' do
it_behaves_like 'group destruction', true
it_behaves_like 'marks the group as pending delete', true
context 'Sidekiq fake' do
before do
@ -105,6 +114,19 @@ RSpec.describe Groups::DestroyService, feature_category: :groups_and_projects do
describe 'synchronous delete' do
it_behaves_like 'group destruction', false
it_behaves_like 'marks the group as pending delete', false
context 'when destroying the group throws an error' do
before do
allow(group).to receive(:destroy).and_raise(StandardError)
end
it 'unmarks the group as pending delete' do
expect { destroy_group(group, user, false) }.to raise_error(StandardError)
expect(group.pending_delete).to be_falsey
end
end
end
context 'projects in pending_delete' do

View File

@ -1092,7 +1092,7 @@
- './ee/spec/lib/ee/gitlab/verify/lfs_objects_spec.rb'
- './ee/spec/lib/ee/gitlab/verify/uploads_spec.rb'
- './ee/spec/lib/ee/gitlab/web_hooks/rate_limiter_spec.rb'
- './ee/spec/lib/ee/gitlab/web_ide/config/entry/global_spec.rb'
- './ee/spec/lib/ee/web_ide/config/entry/global_spec.rb'
- './ee/spec/lib/ee/service_ping/build_payload_spec.rb'
- './ee/spec/lib/ee/service_ping/permit_data_categories_spec.rb'
- './ee/spec/lib/ee/service_ping/service_ping_settings_spec.rb'
@ -1411,10 +1411,10 @@
- './ee/spec/lib/gitlab/usage/metrics/instrumentations/user_cap_setting_enabled_metric_spec.rb'
- './ee/spec/lib/gitlab/user_access_spec.rb'
- './ee/spec/lib/gitlab/visibility_level_spec.rb'
- './ee/spec/lib/gitlab/web_ide/config/entry/schema/match_spec.rb'
- './ee/spec/lib/gitlab/web_ide/config/entry/schema_spec.rb'
- './ee/spec/lib/gitlab/web_ide/config/entry/schemas_spec.rb'
- './ee/spec/lib/gitlab/web_ide/config/entry/schema/uri_spec.rb'
- './ee/spec/lib/web_ide/config/entry/schema/match_spec.rb'
- './ee/spec/lib/web_ide/config/entry/schema_spec.rb'
- './ee/spec/lib/web_ide/config/entry/schemas_spec.rb'
- './ee/spec/lib/web_ide/config/entry/schema/uri_spec.rb'
- './ee/spec/lib/omni_auth/strategies/group_saml_spec.rb'
- './ee/spec/lib/omni_auth/strategies/kerberos_spec.rb'
- './ee/spec/lib/peek/views/elasticsearch_spec.rb'
@ -6067,9 +6067,9 @@
- './spec/lib/gitlab/visibility_level_spec.rb'
- './spec/lib/gitlab/web_hooks/rate_limiter_spec.rb'
- './spec/lib/gitlab/web_hooks/recursion_detection_spec.rb'
- './spec/lib/gitlab/web_ide/config/entry/global_spec.rb'
- './spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb'
- './spec/lib/gitlab/web_ide/config_spec.rb'
- './spec/lib/web_ide/config/entry/global_spec.rb'
- './spec/lib/web_ide/config/entry/terminal_spec.rb'
- './spec/lib/web_ide/config_spec.rb'
- './spec/lib/gitlab/webpack/file_loader_spec.rb'
- './spec/lib/gitlab/webpack/graphql_known_operations_spec.rb'
- './spec/lib/gitlab/webpack/manifest_spec.rb'

View File

@ -0,0 +1,310 @@
# frozen_string_literal: true
RSpec.shared_examples 'includes ::AuditEvents::CommonModel concern' do
describe 'associations' do
it { is_expected.to belong_to(:user).with_foreign_key(:author_id).inverse_of(:audit_events) }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:author_id) }
include_examples 'validates IP address' do
let(:attribute) { :ip_address }
let(:object) { create(audit_event_symbol) } # rubocop:disable Rails/SaveBang -- Method not available
end
end
describe 'callbacks' do
describe '#truncate_fields' do
shared_examples 'a truncated field' do
context 'when values are provided' do
using RSpec::Parameterized::TableSyntax
where(:database_column, :details_value, :expected_value) do
:long | nil | :truncated
:short | nil | :short
nil | :long | :truncated
nil | :short | :short
:long | :short | :truncated
end
with_them do
let(:values) do
{
long: 'a' * (field_limit + 1),
short: 'a' * field_limit,
truncated: "#{'a' * (field_limit - 3)}..."
}
end
let(:audit_event) do
create(audit_event_symbol,
field_name => values[database_column],
details: { field_name => values[details_value] }
)
end
it 'sets both values to be the same', :aggregate_failures do
expect(audit_event.send(field_name)).to eq(values[expected_value])
expect(audit_event.details[field_name]).to eq(values[expected_value])
end
end
end
context 'when values are not provided' do
let(:audit_event) do
create(:audit_event, field_name => nil, details: {})
end
it 'does not set', :aggregate_failures do
expect(audit_event.send(field_name)).to be_nil
expect(audit_event.details).not_to have_key(field_name)
end
end
end
context 'for entity_path' do
let(:field_name) { :entity_path }
let(:field_limit) { 5_500 }
it_behaves_like 'a truncated field'
end
context 'for target_details' do
let(:field_name) { :target_details }
let(:field_limit) { 5_500 }
it_behaves_like 'a truncated field'
end
end
describe '#parallel_persist' do
shared_examples 'a parallel persisted field' do
using RSpec::Parameterized::TableSyntax
where(:column, :details, :expected_value) do
:value | nil | :value
nil | :value | :value
:value | :another_value | :value
nil | nil | nil
end
with_them do
let(:values) { { value: value, another_value: "#{value}88" } }
let(:audit_event) do
build(audit_event_symbol, name => values[column], details: { name => values[details] })
end
it 'sets both values to be the same', :aggregate_failures do
audit_event.validate
expect(audit_event[name]).to eq(values[expected_value])
expect(audit_event.details[name]).to eq(values[expected_value])
end
end
end
context 'with author_name' do
let(:name) { :author_name }
let(:value) { 'Mary Poppins' }
it_behaves_like 'a parallel persisted field'
end
context 'with target_details' do
let(:name) { :target_details }
let(:value) { 'gitlab-org/gitlab' }
it_behaves_like 'a parallel persisted field'
end
context 'with target_type' do
let(:name) { :target_type }
let(:value) { 'Project' }
it_behaves_like 'a parallel persisted field'
end
context 'with target_id' do
let(:name) { :target_id }
let(:value) { 8 }
it_behaves_like 'a parallel persisted field'
end
end
end
describe '.order_by' do
let_it_be(:event_1) { create(audit_event_symbol) } # rubocop:disable Rails/SaveBang -- Method not available
let_it_be(:event_2) { create(audit_event_symbol) } # rubocop:disable Rails/SaveBang -- Method not available
let_it_be(:event_3) { create(audit_event_symbol) } # rubocop:disable Rails/SaveBang -- Method not available
subject(:events) { audit_event_class.order_by(method) }
context 'when sort by created_at in ascending order' do
let(:method) { 'created_asc' }
it 'sorts results by id in ascending order' do
expect(events).to eq([event_1, event_2, event_3])
end
end
context 'when it is default' do
let(:method) { nil }
it 'sorts results by id in descending order' do
expect(events.count).to eq(3)
expect(events).to eq([event_3, event_2, event_1])
end
end
end
it 'sanitizes custom_message in the details hash' do
audit_event = create(audit_event_symbol, details: { target_id: 678, custom_message: '<strong>Arnold</strong>' })
expect(audit_event.details).to include(
target_id: 678,
custom_message: 'Arnold'
)
end
describe '#as_json' do
context 'for ip_address' do
subject { build(audit_event_symbol, ip_address: '192.168.1.1').as_json }
it 'overrides the ip_address with its string value' do
expect(subject['ip_address']).to eq('192.168.1.1')
end
end
end
describe '#author_name' do
context 'when user exists' do
let(:user) { create(:user, name: 'John Doe') }
subject(:event) { audit_event_class.new(user: user) }
it 'returns user name' do
expect(event.author_name).to eq 'John Doe'
end
end
context 'when user does not exist anymore' do
context 'when database contains author_name' do
subject(:event) { build(audit_event_symbol, author_id: non_existing_record_id, author_name: 'Jane Doe') }
it 'returns author_name' do
expect(event.author_name).to eq 'Jane Doe'
end
end
context 'when details contains author_name' do
subject(:event) do
build(audit_event_symbol, author_id: non_existing_record_id, author_name: nil,
details: { author_name: 'John Doe' })
end
it 'returns author_name' do
expect(event.author_name).to eq 'John Doe'
end
end
context 'when details does not contains author_name' do
subject(:event) do
build(audit_event_symbol, author_name: nil,
details: {})
end
it 'returns nil' do
expect(subject.author_name).to eq nil
end
end
end
context 'when authored by an unauthenticated user' do
subject(:event) { build(audit_event_symbol, author_name: nil, details: {}, author_id: -1) }
it 'returns `An unauthenticated user`' do
expect(subject.author_name).to eq('An unauthenticated user')
end
end
end
describe '#ip_address' do
context 'when ip_address exists in both details hash and ip_address column' do
subject(:event) do
build(audit_event_symbol, ip_address: '10.2.1.1', details: { ip_address: '192.168.0.1' })
end
it 'returns the value from ip_address column' do
expect(event.ip_address).to eq('10.2.1.1')
end
end
context 'when ip_address exists in details hash but not in ip_address column' do
subject(:event) { build(audit_event_symbol, ip_address: nil, details: { ip_address: '192.168.0.1' }) }
it 'returns the value from details hash' do
expect(event.ip_address).to eq('192.168.0.1')
end
end
end
describe '#entity_path' do
context 'when entity_path exists in both details hash and entity_path column' do
subject(:event) do
build(audit_event_symbol, entity_path: 'gitlab-org/gitlab', details: { entity_path: 'gitlab-org/gitlab-foss' })
end
it 'returns the value from entity_path column' do
expect(event.entity_path).to eq('gitlab-org/gitlab')
end
end
context 'when entity_path exists in details hash but not in entity_path column' do
subject(:event) do
build(audit_event_symbol, entity_path: nil, details: { entity_path: 'gitlab-org/gitlab-foss' })
end
it 'returns the value from details hash' do
expect(event.entity_path).to eq('gitlab-org/gitlab-foss')
end
end
end
describe '#target_type' do
context 'when target_type exists in both details hash and target_type column' do
subject(:event) do
build(audit_event_symbol, target_type: 'Group', details: { target_type: 'Project' })
end
it 'returns the value from target_type column' do
expect(event.target_type).to eq('Group')
end
end
context 'when target_type exists in details hash but not in target_type column' do
subject(:event) { build(audit_event_symbol, details: { target_type: 'Project' }) }
it 'returns the value from details hash' do
expect(event.target_type).to eq('Project')
end
end
end
describe '#formatted_details' do
subject(:event) do
create(audit_event_symbol, details: { change: 'membership_lock', from: false, to: true, ip_address: '127.0.0.1' })
end
it 'includes the author\'s email' do
expect(event.formatted_details[:author_email]).to eq(event.author.email)
end
it 'converts value of `to` and `from` in `details` to string' do
expect(event.formatted_details[:to]).to eq('true')
expect(event.formatted_details[:from]).to eq('false')
end
end
end