Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
23c30c25c9
commit
0cd10fa56d
|
|
@ -5,6 +5,7 @@ stages:
|
|||
- build-images
|
||||
- fixtures
|
||||
- lint
|
||||
- test-frontend
|
||||
- test
|
||||
- post-test
|
||||
- review
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -94,6 +94,14 @@ module CachedIntrospectionQuery
|
|||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -318,7 +318,7 @@ domains:
|
|||
feature_categories:
|
||||
- webhooks
|
||||
|
||||
WebIDE:
|
||||
WebIde:
|
||||
description:
|
||||
feature_categories:
|
||||
- web_ide
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
9170262cb868bc440128752eedfd21e042f4ea69ec61c987a8af89edf9616f93
|
||||
|
|
@ -0,0 +1 @@
|
|||
fa9bc8a810958a939d97f9c1a9d8f8c57c136cd2de2e5e9b9d9fe96de15380d1
|
||||
|
|
@ -0,0 +1 @@
|
|||
2192a39694251af43e0f4312ae4f8ea97e474edca74fe2ae340e3cbb153cced8
|
||||
|
|
@ -0,0 +1 @@
|
|||
a9ba59901844135e10bf0666a106982c2dcd73e01e1dec6869c8783157f3ea18
|
||||
|
|
@ -0,0 +1 @@
|
|||
4ec6ade0a0b8008ed978cba9a530ae6e5b1efbde0281749fd82fc53d12d58ae3
|
||||
|
|
@ -0,0 +1 @@
|
|||
ab12a8cb06d71254d8e56022957eebdf3bca931784ca1ccf8efefd6ade9d5aba
|
||||
|
|
@ -0,0 +1 @@
|
|||
6bb086e637c6559dc6f3a92f9415da3de4ad151fc198343c99348c205e9dd5e2
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -89,6 +89,14 @@ fragment TypeRef on __Type {
|
|||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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) }
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 }
|
||||
}
|
||||
])
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue