From 1efcb3739be3eba62e07505ecaf7dc075f8d7280 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 10 May 2024 09:18:47 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/test-on-cng/main.gitlab-ci.yml | 4 +- .rubocop_todo/rspec/context_wording.yml | 1 - .rubocop_todo/rspec/feature_category.yml | 1 - .rubocop_todo/search/namespaced_class.yml | 1 - app/assets/javascripts/main.js | 17 +++ .../javascripts/organizations/mock_data.js | 53 ------- .../components/profile_preferences.vue | 10 +- .../components/states/commit_edit.vue | 2 +- app/controllers/search_controller.rb | 5 +- app/helpers/preferences_helper.rb | 4 + app/models/ci/bridge.rb | 6 - .../concerns/packages/debian/component.rb | 2 + .../packages/debian/component_file.rb | 2 + app/models/packages/nuget/symbol.rb | 2 +- .../packages/composer/cache_uploader.rb | 1 + .../debian/component_file_uploader.rb | 1 + .../distribution_release_file_uploader.rb | 1 + .../packages/gcs_signed_url_metadata.rb | 32 ++++ .../packages/npm/metadata_cache_uploader.rb | 1 + .../packages/nuget/symbol_uploader.rb | 1 + .../packages/package_file_uploader.rb | 1 + .../packages/rpm/repository_file_uploader.rb | 1 + app/views/layouts/_head.html.haml | 7 + config/events/perform_navbar_search.yml | 18 +++ config/events/perform_search.yml | 18 +++ ...file_var_expansion_downstream_pipeline.yml | 8 - .../augment_gcs_signed_url_with_metadata.yml | 9 ++ .../20210216180413_all_searches.yml | 9 +- .../20210216180414_navbar_searches.yml | 9 +- doc/.vale/gitlab/spelling-exceptions.txt | 2 + doc/subscriptions/subscription-add-ons.md | 2 +- doc/user/ai_data_usage.md | 76 ++++++++++ doc/user/ai_features.md | 61 -------- doc/user/ai_features_enable.md | 59 ++++---- doc/user/gitlab_duo_chat.md | 6 +- .../repository/code_suggestions/data.md | 61 +------- .../repository/code_suggestions/index.md | 28 +++- gems/gitlab-cng/lib/gitlab/cng/cli.rb | 44 ++---- .../lib/gitlab/cng/commands/_command.rb | 13 ++ .../lib/gitlab/cng/commands/create.rb | 30 ++++ .../lib/gitlab/cng/commands/doctor.rb | 6 +- .../lib/gitlab/cng/commands/version.rb | 4 +- .../lib/gitlab/cng/helpers/output.rb | 88 ----------- .../lib/gitlab/cng/helpers/shell.rb | 35 ----- .../lib/gitlab/cng/lib/helpers/output.rb | 61 ++++++++ .../lib/gitlab/cng/lib/helpers/shell.rb | 38 +++++ .../gitlab/cng/{ => lib}/helpers/spinner.rb | 2 +- .../lib/gitlab/cng/lib/helpers/thor.rb | 31 ++++ .../lib/gitlab/cng/lib/kind/cluster.rb | 81 +++++++++++ .../lib/gitlab/cng/lib/kind/configs.rb | 102 +++++++++++++ gems/gitlab-cng/spec/command_helper.rb | 23 +++ gems/gitlab-cng/spec/integration/cng_spec.rb | 7 +- .../spec/integration/gitlab/cng/cli_spec.rb | 55 +++++++ .../integration/lib/gitlab/cng/cli_spec.rb | 36 ----- gems/gitlab-cng/spec/spec_helper.rb | 3 +- .../unit/gitlab/cng/commands/create_spec.rb | 32 ++++ .../gitlab/cng/commands/doctor_spec.rb | 15 +- .../gitlab/cng/commands/version_spec.rb | 8 +- .../gitlab/cng/helpers/spinner_spec.rb | 0 .../spec/unit/gitlab/cng/kind/cluster_spec.rb | 137 ++++++++++++++++++ lib/api/search.rb | 2 +- .../ci/variables/downstream/generator.rb | 2 +- lib/gitlab/color_modes.rb | 4 +- lib/gitlab/git/keep_around.rb | 5 +- lib/gitlab/gon_helper.rb | 1 + lib/gitlab/usage_data_counters.rb | 1 - .../usage_data_counters/search_counter.rb | 24 --- .../total_counter_redis_key_overrides.yml | 2 + locale/gitlab.pot | 5 +- .../file_variable_downstream_pipeline_spec.rb | 7 - scripts/qa/cng_deploy/cng-kind.sh | 11 -- spec/controllers/search_controller_spec.rb | 23 +++ spec/factories/packages/npm/metadata_cache.rb | 13 +- spec/factories/packages/nuget/symbol.rb | 13 +- .../index/components/app_spec.js | 12 +- spec/frontend/fixtures/organizations.rb | 39 +++++ .../index/components/app_spec.js | 21 +-- spec/frontend/organizations/mock_data.js | 23 +++ .../shared/components/groups_view_spec.js | 46 +----- .../list/organizations_list_item_spec.js | 30 ++-- .../list/organizations_list_spec.js | 29 ++-- .../components/organizations_view_spec.js | 10 +- .../shared/components/projects_view_spec.js | 46 +----- .../components/profile_preferences_spec.js | 15 ++ .../frontend/profile/preferences/mock_data.js | 2 + .../components/organization_switcher_spec.js | 43 +++--- .../entity_select/organization_select_spec.js | 14 +- .../ci/variables/downstream/generator_spec.rb | 42 ++---- .../instrumentations/redis_metric_spec.rb | 14 -- .../search_counter_spec.rb | 31 ---- spec/models/ci/bridge_spec.rb | 35 ----- .../packages/composer/cache_file_spec.rb | 2 +- .../debian/group_component_file_spec.rb | 2 +- .../packages/debian/group_component_spec.rb | 2 +- .../debian/project_component_file_spec.rb | 2 +- .../packages/debian/project_component_spec.rb | 2 +- spec/models/packages/nuget/symbol_spec.rb | 1 + spec/models/packages/package_file_spec.rb | 2 +- spec/requests/api/search_spec.rb | 61 ++------ spec/support/rspec_order_todo.yml | 1 - .../debian/component_file_shared_example.rb | 4 + .../debian/component_shared_examples.rb | 4 + ...gcs_signed_url_metadata_shared_examples.rb | 73 ++++++++++ .../packages/composer/cache_uploader_spec.rb | 12 +- .../debian/component_file_uploader_spec.rb | 14 +- ...distribution_release_file_uploader_spec.rb | 14 +- .../npm/metadata_cache_uploader_spec.rb | 18 ++- .../packages/nuget/symbol_uploader_spec.rb | 18 ++- .../packages/package_file_uploader_spec.rb | 10 +- .../rpm/repository_file_uploader_spec.rb | 8 +- 110 files changed, 1353 insertions(+), 835 deletions(-) create mode 100644 app/uploaders/packages/gcs_signed_url_metadata.rb create mode 100644 config/events/perform_navbar_search.yml create mode 100644 config/events/perform_search.yml delete mode 100644 config/feature_flags/development/ci_prevent_file_var_expansion_downstream_pipeline.yml create mode 100644 config/feature_flags/gitlab_com_derisk/augment_gcs_signed_url_with_metadata.yml create mode 100644 doc/user/ai_data_usage.md create mode 100644 gems/gitlab-cng/lib/gitlab/cng/commands/create.rb delete mode 100644 gems/gitlab-cng/lib/gitlab/cng/helpers/output.rb delete mode 100644 gems/gitlab-cng/lib/gitlab/cng/helpers/shell.rb create mode 100644 gems/gitlab-cng/lib/gitlab/cng/lib/helpers/output.rb create mode 100644 gems/gitlab-cng/lib/gitlab/cng/lib/helpers/shell.rb rename gems/gitlab-cng/lib/gitlab/cng/{ => lib}/helpers/spinner.rb (97%) create mode 100644 gems/gitlab-cng/lib/gitlab/cng/lib/helpers/thor.rb create mode 100644 gems/gitlab-cng/lib/gitlab/cng/lib/kind/cluster.rb create mode 100644 gems/gitlab-cng/lib/gitlab/cng/lib/kind/configs.rb create mode 100644 gems/gitlab-cng/spec/command_helper.rb create mode 100644 gems/gitlab-cng/spec/integration/gitlab/cng/cli_spec.rb delete mode 100644 gems/gitlab-cng/spec/integration/lib/gitlab/cng/cli_spec.rb create mode 100644 gems/gitlab-cng/spec/unit/gitlab/cng/commands/create_spec.rb rename gems/gitlab-cng/spec/unit/{lib => }/gitlab/cng/commands/doctor_spec.rb (67%) rename gems/gitlab-cng/spec/unit/{lib => }/gitlab/cng/commands/version_spec.rb (56%) rename gems/gitlab-cng/spec/unit/{lib => }/gitlab/cng/helpers/spinner_spec.rb (100%) create mode 100644 gems/gitlab-cng/spec/unit/gitlab/cng/kind/cluster_spec.rb delete mode 100644 lib/gitlab/usage_data_counters/search_counter.rb create mode 100644 spec/frontend/organizations/mock_data.js delete mode 100644 spec/lib/gitlab/usage_data_counters/search_counter_spec.rb create mode 100644 spec/support/shared_examples/uploaders/gcs_signed_url_metadata_shared_examples.rb diff --git a/.gitlab/ci/test-on-cng/main.gitlab-ci.yml b/.gitlab/ci/test-on-cng/main.gitlab-ci.yml index 7652efb6ade..83395a5922d 100644 --- a/.gitlab/ci/test-on-cng/main.gitlab-ci.yml +++ b/.gitlab/ci/test-on-cng/main.gitlab-ci.yml @@ -51,9 +51,9 @@ workflow: - source scripts/utils.sh - source scripts/rspec_helpers.sh - source scripts/qa/cng_deploy/cng-kind.sh - - run_timed_command "setup_cluster scripts/qa/cng_deploy/config/kind-config.yml" - - run_timed_command "deploy ${GITLAB_DOMAIN}" - cd qa && bundle install + - bundle exec cng create cluster --ci + - deploy ${GITLAB_DOMAIN} script: - export QA_COMMAND="bundle exec bin/qa ${QA_SCENARIO:=Test::Instance::All} $QA_GITLAB_URL -- --force-color --order random --format documentation" - echo "Running - '$QA_COMMAND'" diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml index 9d1e854df62..f357c829348 100644 --- a/.rubocop_todo/rspec/context_wording.yml +++ b/.rubocop_todo/rspec/context_wording.yml @@ -1939,7 +1939,6 @@ RSpec/ContextWording: - 'spec/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/kubernetes_agent_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb' - - 'spec/lib/gitlab/usage_data_counters/search_counter_spec.rb' - 'spec/lib/gitlab/usage_data_spec.rb' - 'spec/lib/gitlab/utils/lazy_attributes_spec.rb' - 'spec/lib/gitlab/utils/markdown_spec.rb' diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml index 77678225139..839134c070d 100644 --- a/.rubocop_todo/rspec/feature_category.yml +++ b/.rubocop_todo/rspec/feature_category.yml @@ -3974,7 +3974,6 @@ RSpec/FeatureCategory: - 'spec/lib/gitlab/usage_data_counters/package_event_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb' - - 'spec/lib/gitlab/usage_data_counters/search_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb' diff --git a/.rubocop_todo/search/namespaced_class.yml b/.rubocop_todo/search/namespaced_class.yml index 00015b06e3c..5a605af434b 100644 --- a/.rubocop_todo/search/namespaced_class.yml +++ b/.rubocop_todo/search/namespaced_class.yml @@ -219,7 +219,6 @@ Search/NamespacedClass: - 'lib/gitlab/slash_commands/issue_search.rb' - 'lib/gitlab/slash_commands/presenters/issue_search.rb' - 'lib/gitlab/snippet_search_results.rb' - - 'lib/gitlab/usage_data_counters/search_counter.rb' - 'lib/peek/views/elasticsearch.rb' - 'lib/peek/views/zoekt.rb' - 'qa/qa/ee/page/admin/settings/component/elasticsearch.rb' diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index edafab8dddc..fcc2f064866 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -49,6 +49,23 @@ if (process.env.NODE_ENV !== 'production' && gon?.test_env) { import(/* webpackMode: "eager" */ './test_utils'); } +if (gon?.user_color_mode === 'gl-system') { + const root = document.documentElement; + // eslint-disable-next-line @gitlab/require-i18n-strings + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + root.classList.add('gl-dark'); + } + + // eslint-disable-next-line @gitlab/require-i18n-strings + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { + if (e.matches) { + root.classList.add('gl-dark'); + } else { + root.classList.remove('gl-dark'); + } + }); +} + document.addEventListener('beforeunload', () => { // Unbind scroll events // eslint-disable-next-line @gitlab/no-global-event-off diff --git a/app/assets/javascripts/organizations/mock_data.js b/app/assets/javascripts/organizations/mock_data.js index df67f60e9cf..754d52f6173 100644 --- a/app/assets/javascripts/organizations/mock_data.js +++ b/app/assets/javascripts/organizations/mock_data.js @@ -16,35 +16,6 @@ export const defaultOrganization = { avatar_url: null, }; -export const organizations = [ - { - id: 'gid://gitlab/Organizations::Organization/1', - name: 'My First Organization', - descriptionHtml: - '

This is where an organization can be explained in detail

', - avatarUrl: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61', - webUrl: '/-/organizations/default', - __typename: 'Organization', - }, - { - id: 'gid://gitlab/Organizations::Organization/2', - name: 'Vegetation Co.', - descriptionHtml: - '

Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt

', - avatarUrl: null, - webUrl: '/-/organizations/default', - __typename: 'Organization', - }, - { - id: 'gid://gitlab/Organizations::Organization/3', - name: 'Dude where is my car?', - descriptionHtml: null, - avatarUrl: null, - webUrl: '/-/organizations/default', - __typename: 'Organization', - }, -]; - export const organizationCreateResponse = { data: { organizationCreate: { @@ -87,27 +58,3 @@ export const organizationUpdateResponseWithErrors = { }, }, }; - -export const pageInfo = { - endCursor: 'eyJpZCI6IjEwNTMifQ', - hasNextPage: true, - hasPreviousPage: true, - startCursor: 'eyJpZCI6IjEwNzIifQ', - __typename: 'PageInfo', -}; - -export const pageInfoOnePage = { - endCursor: 'eyJpZCI6IjEwNTMifQ', - hasNextPage: false, - hasPreviousPage: false, - startCursor: 'eyJpZCI6IjEwNzIifQ', - __typename: 'PageInfo', -}; - -export const pageInfoEmpty = { - endCursor: null, - hasNextPage: false, - hasPreviousPage: false, - startCursor: null, - __typename: 'PageInfo', -}; diff --git a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue index f42ca4cf569..0ba83a649fd 100644 --- a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue +++ b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue @@ -58,7 +58,7 @@ export default { return { isSubmitEnabled: true, - darkModeOnCreate: null, + colorModeOnCreate: null, schemeOnCreate: null, integrationValues, }; @@ -72,7 +72,7 @@ export default { this.formEl.addEventListener('ajax:beforeSend', this.handleLoading); this.formEl.addEventListener('ajax:success', this.handleSuccess); this.formEl.addEventListener('ajax:error', this.handleError); - this.darkModeOnCreate = this.darkModeSelected(); + this.colorModeOnCreate = this.getSelectedColorMode(); this.schemeOnCreate = this.getSelectedScheme(); }, beforeDestroy() { @@ -81,10 +81,6 @@ export default { this.formEl.removeEventListener('ajax:error', this.handleError); }, methods: { - darkModeSelected() { - const mode = this.getSelectedColorMode(); - return mode ? mode.css_class === 'gl-dark' : null; - }, getSelectedColorMode() { const modeId = new FormData(this.formEl).get('user[color_mode_id]'); const mode = this.colorModes.find((item) => item.id === Number(modeId)); @@ -105,7 +101,7 @@ export default { // Reload the page if the theme has changed from light to dark mode or vice versa // or if color scheme has changed to correctly load all required styles. if ( - this.darkModeOnCreate !== this.darkModeSelected() || + this.colorModeOnCreate !== this.getSelectedColorMode() || this.schemeOnCreate !== this.getSelectedScheme() ) { window.location.reload(); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue index 315e5b21f92..65a34423c27 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue @@ -41,7 +41,7 @@ export default { { where(package_type: :nuget) }, inverse_of: :nuget_symbols - delegate :project_id, to: :package + delegate :project_id, :project, to: :package validates :package, :file, :file_path, :signature, :object_storage_key, :size, presence: true validates :signature, uniqueness: { scope: :file_path } diff --git a/app/uploaders/packages/composer/cache_uploader.rb b/app/uploaders/packages/composer/cache_uploader.rb index ef581b5d6a1..b0170ac7421 100644 --- a/app/uploaders/packages/composer/cache_uploader.rb +++ b/app/uploaders/packages/composer/cache_uploader.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Packages::Composer::CacheUploader < GitlabUploader include ObjectStorage::Concern + include Packages::GcsSignedUrlMetadata storage_location :packages diff --git a/app/uploaders/packages/debian/component_file_uploader.rb b/app/uploaders/packages/debian/component_file_uploader.rb index b1ed8d853f1..9f110fe18ef 100644 --- a/app/uploaders/packages/debian/component_file_uploader.rb +++ b/app/uploaders/packages/debian/component_file_uploader.rb @@ -2,6 +2,7 @@ class Packages::Debian::ComponentFileUploader < GitlabUploader extend Workhorse::UploadPath include ObjectStorage::Concern + include Packages::GcsSignedUrlMetadata storage_location :packages diff --git a/app/uploaders/packages/debian/distribution_release_file_uploader.rb b/app/uploaders/packages/debian/distribution_release_file_uploader.rb index fe10861b77f..48946eb6663 100644 --- a/app/uploaders/packages/debian/distribution_release_file_uploader.rb +++ b/app/uploaders/packages/debian/distribution_release_file_uploader.rb @@ -2,6 +2,7 @@ class Packages::Debian::DistributionReleaseFileUploader < GitlabUploader extend Workhorse::UploadPath include ObjectStorage::Concern + include Packages::GcsSignedUrlMetadata storage_location :packages diff --git a/app/uploaders/packages/gcs_signed_url_metadata.rb b/app/uploaders/packages/gcs_signed_url_metadata.rb new file mode 100644 index 00000000000..10f68abeab4 --- /dev/null +++ b/app/uploaders/packages/gcs_signed_url_metadata.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# This module is used to augment the GCS signed URL with metadata that is used for auditing purposes. +# The uploader that includes this module should respond to `group` or `project` methods. +# If the underlying model instance does not have the `size` stored in the DB, we will make a stats request +# to fetch the file size from the object storage. +module Packages + module GcsSignedUrlMetadata + def url(*args, **kwargs) + return super if ::Feature.disabled?(:augment_gcs_signed_url_with_metadata, Feature.current_request) + return super unless fog_credentials[:provider] == 'Google' && Gitlab.com? # rubocop:disable Gitlab/AvoidGitlabInstanceChecks -- As per https://gitlab.com/groups/gitlab-org/-/epics/8834, we are only interested in egress traffic on Gitlab.com + + project = model.try(:project) + root_namespace = project&.root_namespace || model.try(:group)&.root_ancestor + + metadata_params = { + 'x-goog-custom-audit-gitlab-project' => project&.id, + 'x-goog-custom-audit-gitlab-namespace' => root_namespace&.id, + 'x-goog-custom-audit-gitlab-size-bytes' => model.try(:size) || size + }.compact.transform_values(&:to_s) + + super(*args, **kwargs.merge(query: metadata_params)) + rescue StandardError => e + Gitlab::ErrorTracking.track_exception( + e, + model_class: model.class.name, + model_id: model.id + ) + super + end + end +end diff --git a/app/uploaders/packages/npm/metadata_cache_uploader.rb b/app/uploaders/packages/npm/metadata_cache_uploader.rb index 75a3a94c0b4..3e20eb3d531 100644 --- a/app/uploaders/packages/npm/metadata_cache_uploader.rb +++ b/app/uploaders/packages/npm/metadata_cache_uploader.rb @@ -4,6 +4,7 @@ module Packages module Npm class MetadataCacheUploader < GitlabUploader include ObjectStorage::Concern + include Packages::GcsSignedUrlMetadata FILENAME = 'metadata.json' diff --git a/app/uploaders/packages/nuget/symbol_uploader.rb b/app/uploaders/packages/nuget/symbol_uploader.rb index 1d6ec9a8de8..bb25335e03e 100644 --- a/app/uploaders/packages/nuget/symbol_uploader.rb +++ b/app/uploaders/packages/nuget/symbol_uploader.rb @@ -4,6 +4,7 @@ module Packages module Nuget class SymbolUploader < GitlabUploader include ObjectStorage::Concern + include Packages::GcsSignedUrlMetadata storage_location :packages diff --git a/app/uploaders/packages/package_file_uploader.rb b/app/uploaders/packages/package_file_uploader.rb index 57feee9f19d..1ca7405477b 100644 --- a/app/uploaders/packages/package_file_uploader.rb +++ b/app/uploaders/packages/package_file_uploader.rb @@ -2,6 +2,7 @@ class Packages::PackageFileUploader < GitlabUploader extend Workhorse::UploadPath include ObjectStorage::Concern + include Packages::GcsSignedUrlMetadata storage_location :packages diff --git a/app/uploaders/packages/rpm/repository_file_uploader.rb b/app/uploaders/packages/rpm/repository_file_uploader.rb index 399e9fa07d5..8fa5adf87d3 100644 --- a/app/uploaders/packages/rpm/repository_file_uploader.rb +++ b/app/uploaders/packages/rpm/repository_file_uploader.rb @@ -3,6 +3,7 @@ module Packages module Rpm class RepositoryFileUploader < GitlabUploader include ObjectStorage::Concern + include Packages::GcsSignedUrlMetadata storage_location :packages diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index ea770a076f3..f54019a2a87 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -30,6 +30,13 @@ = stylesheet_link_tag_defer "application_dark" = yield :page_specific_styles = stylesheet_link_tag_defer "application_utilities_dark" + - elsif user_application_system_mode? + %meta{ name: 'color-scheme', content: 'light dark' } + = stylesheet_link_tag "application", media: "(prefers-color-scheme: light)" + = stylesheet_link_tag "application_dark", media: "(prefers-color-scheme: dark)" + = yield :page_specific_styles + = stylesheet_link_tag "application_utilities", media: "(prefers-color-scheme: light)" + = stylesheet_link_tag "application_utilities_dark", media: "(prefers-color-scheme: dark)" - else = stylesheet_link_tag_defer "application" = yield :page_specific_styles diff --git a/config/events/perform_navbar_search.yml b/config/events/perform_navbar_search.yml new file mode 100644 index 00000000000..50b6d92f0f9 --- /dev/null +++ b/config/events/perform_navbar_search.yml @@ -0,0 +1,18 @@ +--- +description: Basic Search and Advanced Search requests using the navbar. +internal_events: true +action: perform_navbar_search +identifiers: +- user +product_section: core_platform +product_stage: data_stores +product_group: global_search +milestone: '17.0' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151599 +distributions: +- ce +- ee +tiers: +- free +- premium +- ultimate diff --git a/config/events/perform_search.yml b/config/events/perform_search.yml new file mode 100644 index 00000000000..a7228c50f24 --- /dev/null +++ b/config/events/perform_search.yml @@ -0,0 +1,18 @@ +--- +description: All Basic Search and Advanced Search requests. +internal_events: true +action: perform_search +identifiers: +- user +product_section: core_platform +product_stage: data_stores +product_group: global_search +milestone: '17.0' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151599 +distributions: +- ce +- ee +tiers: +- free +- premium +- ultimate diff --git a/config/feature_flags/development/ci_prevent_file_var_expansion_downstream_pipeline.yml b/config/feature_flags/development/ci_prevent_file_var_expansion_downstream_pipeline.yml deleted file mode 100644 index f0bcacbe2bd..00000000000 --- a/config/feature_flags/development/ci_prevent_file_var_expansion_downstream_pipeline.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_prevent_file_var_expansion_downstream_pipeline -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124320 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414583 -milestone: '16.3' -type: development -group: group::pipeline security -default_enabled: false diff --git a/config/feature_flags/gitlab_com_derisk/augment_gcs_signed_url_with_metadata.yml b/config/feature_flags/gitlab_com_derisk/augment_gcs_signed_url_with_metadata.yml new file mode 100644 index 00000000000..09ebe4a6167 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/augment_gcs_signed_url_with_metadata.yml @@ -0,0 +1,9 @@ +--- +name: augment_gcs_signed_url_with_metadata +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/443335 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147207 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/458729 +milestone: '17.0' +group: group::package registry +type: gitlab_com_derisk +default_enabled: false diff --git a/config/metrics/counts_all/20210216180413_all_searches.yml b/config/metrics/counts_all/20210216180413_all_searches.yml index 09d3873cb93..7a5ab7e3d7d 100644 --- a/config/metrics/counts_all/20210216180413_all_searches.yml +++ b/config/metrics/counts_all/20210216180413_all_searches.yml @@ -9,12 +9,9 @@ product_group: global_search value_type: number status: active time_frame: all -data_source: redis -instrumentation_class: RedisMetric -options: - prefix: null - event: all_searches_count - include_usage_prefix: false +data_source: internal_events +events: + - name: perform_search distribution: - ce - ee diff --git a/config/metrics/counts_all/20210216180414_navbar_searches.yml b/config/metrics/counts_all/20210216180414_navbar_searches.yml index 08c3afc5817..6b59141842d 100644 --- a/config/metrics/counts_all/20210216180414_navbar_searches.yml +++ b/config/metrics/counts_all/20210216180414_navbar_searches.yml @@ -9,12 +9,9 @@ product_group: global_search value_type: number status: active time_frame: all -data_source: redis -instrumentation_class: RedisMetric -options: - prefix: null - event: navbar_searches_count - include_usage_prefix: false +data_source: internal_events +events: + - name: perform_navbar_search distribution: - ce - ee diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt index 34acfe0c713..dffd2aa5752 100644 --- a/doc/.vale/gitlab/spelling-exceptions.txt +++ b/doc/.vale/gitlab/spelling-exceptions.txt @@ -533,6 +533,8 @@ ldapsearch Lefthook Leiningen Lemmy +LLM +LLMs libFuzzer Libgcrypt Libravatar diff --git a/doc/subscriptions/subscription-add-ons.md b/doc/subscriptions/subscription-add-ons.md index bec74ac8a12..0dd053f7521 100644 --- a/doc/subscriptions/subscription-add-ons.md +++ b/doc/subscriptions/subscription-add-ons.md @@ -67,7 +67,7 @@ Prerequisites: #### Configure network and proxy settings For self-managed instances, to enable GitLab Duo features, -You must [enable network connectivity](../user/ai_features_enable.md#enable-gitlab-duo-on-self-managed-instances). +You must [enable network connectivity](../user/ai_features_enable.md#configure-gitlab-duo-on-a-self-managed-instance). ## Assign and remove seats in bulk diff --git a/doc/user/ai_data_usage.md b/doc/user/ai_data_usage.md new file mode 100644 index 00000000000..0a13c952ab3 --- /dev/null +++ b/doc/user/ai_data_usage.md @@ -0,0 +1,76 @@ +--- +stage: AI-powered +group: AI Model Validation +description: AI-powered features and functionality. +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# GitLab Duo data usage + +GitLab Duo uses generative AI to help increase your velocity and make you more productive. Each AI-powered feature operates independently and is not required for other features to function. + +GitLab uses best-in-class large language models (LLMs) for specific tasks. These LLMs are +[Google Vertex AI Models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview#genai-models) +and [Anthropic Claude](https://www.anthropic.com/product). + +## Progressive enhancement + +GitLab Duo AI-powered features are designed as a progressive enhancement to existing GitLab features across the DevSecOps platform. These features are designed to fail gracefully and should not prevent the core functionality of the underlying feature. You should note each feature is subject to its expected functionality as defined by the relevant [feature support policy](../policy/experiment-beta-support.md). + +## Stability and performance + +GitLab Duo AI-powered features are in a variety of [feature support levels](../policy/experiment-beta-support.md#beta). Due to the nature of these features, there may be high demand for usage which may cause degraded performance or unexpected downtime of the feature. We have built these features to gracefully degrade and have controls in place to allow us to mitigate abuse or misuse. GitLab may disable Beta and Experiment features for any or all customers at any time at our discretion. + +## Data privacy + +GitLab Duo AI-powered features are powered by a generative AI model. The processing of any personal data is in accordance with our [Privacy Statement](https://about.gitlab.com/privacy/). You may also visit the [Sub-Processors page](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors) to see the list of Sub-Processors we use to provide these features. + +## Data retention + +The below reflects the current retention periods of GitLab AI model [Sub-Processors](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors): + +- Anthropic discards model input and output data immediately after the output is provided. Anthropic currently does not store data for abuse monitoring. Model input and output is not used to train models. +- Google discards model input and output data immediately after the output is provided. Google currently does not store data for abuse monitoring. Model input and output is not used to train models. + +All of these AI providers are under data protection agreements with GitLab that prohibit the use of Customer Content for their own purposes, except to perform their independent legal obligations. + +GitLab retains input and output for up to 30 days for the purpose of troubleshooting, debugging, and addressing latency issues. + +## Training data + +GitLab does not train generative AI models based on private (non-public) data. The vendors we work with also do not train models based on private data. + +For more information on our AI [sub-processors](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors), see: + +- Google Vertex AI Models APIs [data governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance), [responsible AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/responsible-ai), and [release notes](https://cloud.google.com/vertex-ai/docs/release-notes). +- Anthropic Claude's [constitution](https://www.anthropic.com/news/claudes-constitution), training data [FAQ](https://support.anthropic.com/en/articles/7996885-how-do-you-use-personal-data-in-model-training), and [data recency article](https://support.anthropic.com/en/articles/8114494-how-up-to-date-is-claude-s-training-data). + +## Telemetry + +GitLab Duo collects aggregated or de-identified first-party usage data through a [Snowplow collector](https://handbook.gitlab.com/handbook/business-technology/data-team/platform/snowplow/). This usage data includes the following metrics: + +- Number of unique users +- Number of unique instances +- Prompt and suffix lengths +- Model used +- Status code responses +- API responses times +- Code Suggestions also collects: + - Language the suggestion was in (for example, Python) + - Editor being used (for example, VS Code) + - Number of suggestions shown, accepted, rejected, or that had errors + - Duration of time that a suggestion was shown + +## Model accuracy and quality + +Generative AI may produce unexpected results that may be: + +- Low-quality +- Incoherent +- Incomplete +- Produce failed pipelines +- Insecure code +- Offensive or insensitive +- Out of date information + +GitLab is actively iterating on all our AI-assisted capabilities to improve the quality of the generated content. We improve the quality through prompt engineering, evaluating new AI/ML models to power these features, and through novel heuristics built into these features directly. diff --git a/doc/user/ai_features.md b/doc/user/ai_features.md index bb17fda35d2..559f07be2f8 100644 --- a/doc/user/ai_features.md +++ b/doc/user/ai_features.md @@ -62,64 +62,3 @@ Disable GitLab Duo features by [following these instructions](ai_features_enable | [Root cause analysis](ai_experiments.md#root-cause-analysis) | Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text) | | [Value stream forecasting](ai_experiments.md#forecast-deployment-frequency-with-value-stream-forecasting) | Statistical forecasting | | [Product analytics](analytics/analytics_dashboards.md#generate-a-custom-visualization-with-gitlab-duo) | Vertex AI Codey [`codechat-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/code-chat) | - -## Data usage - -GitLab AI features leverage generative AI to help increase velocity and aim to help make you more productive. Each feature operates independently of other features and is not required for other features to function. GitLab selects the best-in-class large-language models for specific tasks. We use [Google Vertex AI Models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview#genai-models) and [Anthropic Claude](https://www.anthropic.com/product). - -### Progressive enhancement - -These features are designed as a progressive enhancement to existing GitLab features across our DevSecOps platform. They are designed to fail gracefully and should not prevent the core functionality of the underlying feature. You should note each feature is subject to its expected functionality as defined by the relevant [feature support policy](../policy/experiment-beta-support.md). - -### Stability and performance - -These features are in a variety of [feature support levels](../policy/experiment-beta-support.md#beta). Due to the nature of these features, there may be high demand for usage which may cause degraded performance or unexpected downtime of the feature. We have built these features to gracefully degrade and have controls in place to allow us to mitigate abuse or misuse. GitLab may disable **beta and experimental** features for any or all customers at any time at our discretion. - -### Data privacy - -GitLab Duo AI features are powered by a generative AI models. The processing of any personal data is in accordance with our [Privacy Statement](https://about.gitlab.com/privacy/). You may also visit the [Sub-Processors page](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors) to see the list of our Sub-Processors that we use to provide these features. - -### Data retention - -The below reflects the current retention periods of GitLab AI model [Sub-Processors](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors): - -- Anthropic discards model input and output data immediately after the output is provided. Anthropic currently does not store data for abuse monitoring. Model input and output is not used to train models. -- Google discards model input and output data immediately after the output is provided. Google currently does not store data for abuse monitoring. Model input and output is not used to train models. - -All of these AI providers are under data protection agreements with GitLab that prohibit the use of Customer Content for their own purposes, except to perform their independent legal obligations. - -GitLab retains input and output for up to 30 days for the purpose of troubleshooting, debugging, and addressing latency issues. - -### Training data - -GitLab does not train generative AI models based on private (non-public) data. The vendors we work with also do not train models based on private data. - -For more information on our AI [sub-processors](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors), see: - -- Google Vertex AI Models APIs [data governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance), [responsible AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/responsible-ai), and [release notes](https://cloud.google.com/vertex-ai/docs/release-notes). -- Anthropic Claude's [constitution](https://www.anthropic.com/news/claudes-constitution), training data [FAQ](https://support.anthropic.com/en/articles/7996885-how-do-you-use-personal-data-in-model-training), and [data recency article](https://support.anthropic.com/en/articles/8114494-how-up-to-date-is-claude-s-training-data). - -### Telemetry - -GitLab Duo collects aggregated or de-identified first-party usage data through our [Snowplow collector](https://handbook.gitlab.com/handbook/business-technology/data-team/platform/snowplow/). This usage data includes the following metrics: - -- Number of unique users -- Number of unique instances -- Prompt lengths -- Model used -- Status code responses -- API responses times - -### Model accuracy and quality - -Generative AI may produce unexpected results that may be: - -- Low-quality -- Incoherent -- Incomplete -- Produce failed pipelines -- Insecure code -- Offensive or insensitive -- Out of date information - -GitLab is actively iterating on all our AI-assisted capabilities to improve the quality of the generated content. We improve the quality through prompt engineering, evaluating new AI/ML models to power these features, and through novel heuristics built into these features directly. diff --git a/doc/user/ai_features_enable.md b/doc/user/ai_features_enable.md index 7cb28937189..87e12adc44f 100644 --- a/doc/user/ai_features_enable.md +++ b/doc/user/ai_features_enable.md @@ -10,33 +10,33 @@ DETAILS: **Tier:** Premium, Ultimate **Offering:** GitLab.com, Self-managed, GitLab Dedicated -> - [Settings to disable AI features introduced](https://gitlab.com/groups/gitlab-org/-/epics/12404) in GitLab 16.10. -> - [Settings to disable AI features added to the UI](https://gitlab.com/gitlab-org/gitlab/-/issues/441489) in GitLab 16.11. +> - [Settings to turn off AI features introduced](https://gitlab.com/groups/gitlab-org/-/epics/12404) in GitLab 16.10. +> - [Settings to turn off AI features added to the UI](https://gitlab.com/gitlab-org/gitlab/-/issues/441489) in GitLab 16.11. -GitLab Duo features that are Generally Available are automatically enabled for all users that have access. +GitLab Duo features that are Generally Available are automatically turned on for all users that have access. In addition: - If you have self-managed GitLab, you must - [enable connectivity](#enable-gitlab-duo-on-self-managed-instances). + [allow connectivity](#configure-gitlab-duo-on-a-self-managed-instance). - For some Generally Available features, like [Code Suggestions](project/repository/code_suggestions/index.md), [you must assign seats](../subscriptions/subscription-add-ons.md#assign-gitlab-duo-pro-seats) to the users you want to have access. -GitLab Duo features that are Experiment or Beta are disabled by default -and [must be enabled](#enable-beta-and-experimental-features). +GitLab Duo features that are Experiment or Beta are turned off by default +and [must be turned on](#turn-on-beta-and-experimental-features). -## Enable GitLab Duo on self-managed instances +## Configure GitLab Duo on a self-managed instance -To enable GitLab Duo on a self-managed instance, you must ensure connectivity exists. +To use GitLab Duo on a self-managed instance, you must ensure connectivity exists. -### Enable outbound connections from GitLab instances +### Allow outbound connections from the GitLab instance - Your firewalls and HTTP/S proxy servers must allow outbound connections to `cloud.gitlab.com` and `customers.gitlab.com` on port `443` both with `https://`. - To use an HTTP/S proxy, both `gitLab_workhorse` and `gitLab_rails` must have the necessary [web proxy environment variables](https://docs.gitlab.com/omnibus/settings/environment-variables.html) set. -### Enable inbound connections from clients to GitLab instances +### Allow inbound connections from clients to the GitLab instance - GitLab instances must allow inbound connections from Duo clients (IDEs, Code Editors, and GitLab Web Frontend) on port 443 with `https://` and `wss://`. @@ -46,13 +46,14 @@ To enable GitLab Duo on a self-managed instance, you must ensure connectivity ex Network policy restrictions on `wss://` traffic can cause issues with some GitLab Duo Chat services. Consider policy updates to allow these services. -## Disable GitLab Duo features +## Turn off GitLab Duo features -You can turn off GitLab Duo for a group, project, or instance. (Note, GitLab Duo includes Code Suggestions.) +You can turn off GitLab Duo for a group, project, or instance. When GitLab Duo is turned off for a group, project, or instance: - GitLab Duo features that access resources, like code, issues, and vulnerabilities, are not available. +- Code Suggestions are not available. However, GitLab Duo Chat works differently. When you turn off GitLab Duo: @@ -64,15 +65,15 @@ However, GitLab Duo Chat works differently. When you turn off GitLab Duo: - For an instance: - The **GitLab Duo Chat** button is not available anywhere in the UI. -### Disable for a group +### Turn off for a group -You can disable GitLab Duo for a group. +You can turn off GitLab Duo for a group. Prerequisites: - You must have the Owner role for the group or project. -To disable GitLab Duo for a group: +To turn off GitLab Duo for a group: 1. On the left sidebar, select **Search or go to** and find your group. @@ -90,33 +91,33 @@ An [issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/448709) for maki setting cascade to all groups and projects. Right now the lower-level projects and groups do not display the setting of the top-level group. -### Disable for a project +### Turn off for a project -You can disable GitLab Duo for a project. +You can turn off GitLab Duo for a project. Prerequisites: - You must have the Owner role for the project. -To disable GitLab Duo for a project: +To turn off GitLab Duo for a project: 1. Use the [GitLab GraphQL API](../api/graphql/getting_started.md) `projectSettingsUpdate` mutation. -1. Disable GitLab Duo for the project by setting the `duo_features_enabled` setting to `false`. +1. Turn off GitLab Duo for the project by setting the `duo_features_enabled` setting to `false`. (The default is `true`.) -### Disable for an instance +### Turn off for an instance DETAILS: **Offering:** Self-managed -You can disable GitLab Duo for the instance. +You can turn off GitLab Duo for the instance. Prerequisites: - You must be an administrator. -To disable GitLab Duo for an instance: +To turn off GitLab Duo for an instance: 1. On the left sidebar, at the bottom, select **Admin Area**. @@ -131,21 +132,21 @@ NOTE: An [issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/441532) to allow administrators to override the setting for specific groups or projects. -## Enable Beta and Experimental features +## Turn on Beta and Experimental features -Features listed as Experiment and Beta are disabled by default. +Features listed as Experiment and Beta are turned off by default. These features are subject to the [Testing Agreement](https://handbook.gitlab.com/handbook/legal/testing-agreement/). -### Enable on GitLab.com +### On GitLab.com -You can enable Experiment and Beta features for your group on GitLab.com. +You can turn on Experiment and Beta features for your group on GitLab.com. Prerequisites: - You must have the Owner role in the top-level group. -To enable Beta and Experimental GitLab Duo features, use the [Experiment and Beta features checkbox](group/manage.md#enable-experiment-and-beta-features). +To turn on Beta and Experimental GitLab Duo features, use the [Experiment and Beta features checkbox](group/manage.md#enable-experiment-and-beta-features). -### Enable on self-managed +### On self-managed -To enable Beta and Experimental GitLab Duo features for GitLab versions where GitLab Duo Chat is not yet generally available, see the [GitLab Duo Chat documentation](gitlab_duo_chat.md#for-self-managed). +To turn on Beta and Experimental GitLab Duo features for GitLab versions where GitLab Duo Chat is not yet generally available, see the [GitLab Duo Chat documentation](gitlab_duo_chat.md#for-self-managed). diff --git a/doc/user/gitlab_duo_chat.md b/doc/user/gitlab_duo_chat.md index 3773cc12070..55af6632984 100644 --- a/doc/user/gitlab_duo_chat.md +++ b/doc/user/gitlab_duo_chat.md @@ -269,7 +269,7 @@ Prerequisites: - You must have GitLab version 16.8 or later. You should use the latest GitLab version to benefit from the latest improvements to GitLab Duo Chat. The generally available version of GitLab Duo Chat in GitLab 16.11 has significant improvements in the quality of the answers. - You must have a Premium or Ultimate subscription that is [synchronized with GitLab](https://about.gitlab.com/pricing/licensing-faq/cloud-licensing/). To make sure GitLab Duo Chat works immediately, administrators can [manually synchronize your subscription](#manually-synchronize-your-subscription). -- You must have [enabled network connectivity](ai_features_enable.md#enable-gitlab-duo-on-self-managed-instances). +- You must have [enabled network connectivity](ai_features_enable.md#configure-gitlab-duo-on-a-self-managed-instance). - All of the users in your instance must have the latest version of their IDE extension. Then, depending on the version of GitLab you have, you can enable GitLab Duo Chat. @@ -294,7 +294,7 @@ In GitLab 16.8, 16.9, and 16.10, GitLab Duo Chat is available in Beta. To enable NOTE: Usage of GitLab Duo Chat Beta is governed by the [GitLab Testing Agreement](https://handbook.gitlab.com/handbook/legal/testing-agreement/). -Learn about [data usage when using GitLab Duo Chat](ai_features.md#data-usage). +Learn about [data usage when using GitLab Duo Chat](ai_data_usage.md). #### Manually synchronize your subscription @@ -315,7 +315,7 @@ In GitLab 16.8, 16.9, and 16.10, on GitLab Dedicated, GitLab Duo Chat is availab ## Disable GitLab Duo Chat To limit the data that Duo Chat has access to, follow the instructions for -[disabling GitLab Duo features](ai_features_enable.md#disable-gitlab-duo-features). +[disabling GitLab Duo features](ai_features_enable.md#turn-off-gitlab-duo-features). ## Use GitLab Duo Chat in the GitLab UI diff --git a/doc/user/project/repository/code_suggestions/data.md b/doc/user/project/repository/code_suggestions/data.md index 08eaf0d19dc..23ad418cd40 100644 --- a/doc/user/project/repository/code_suggestions/data.md +++ b/doc/user/project/repository/code_suggestions/data.md @@ -1,58 +1,11 @@ --- -stage: Create -group: Code Creation -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +redirect_to: '../../../ai_data_usage.md' +remove_date: '2024-08-08' --- -# Code Suggestions data usage +This document was moved to [another location](../../../ai_data_usage.md). -DETAILS: -**Tier:** Premium or Ultimate with GitLab Duo Pro -**Offering:** GitLab.com, Self-managed, GitLab Dedicated - -Code Suggestions is powered by a generative AI model. - -Your personal access token enables a secure API connection to GitLab.com or to your GitLab instance. -This API connection securely transmits a context window from your IDE/editor to the [GitLab AI Gateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist), a GitLab hosted service. The [gateway](../../../../development/ai_architecture.md) calls the large language model APIs, and then the generated suggestion is transmitted back to your IDE/editor. - -GitLab selects the best-in-class large-language models for specific tasks. We use [Google Vertex AI Code Models](https://cloud.google.com/vertex-ai/generative-ai/docs/code/code-models-overview) and [Anthropic Claude](https://www.anthropic.com/product) for Code Suggestions. - -[View data retention policies](../../../ai_features.md#data-retention). - -## Telemetry - -For self-managed instances that have enabled Code Suggestions and for SaaS accounts, we collect aggregated or de-identified first-party usage data through our [Snowplow collector](https://handbook.gitlab.com/handbook/business-technology/data-team/platform/snowplow/). This usage data includes the following metrics: - -- Language the code suggestion was in (for example, Python) -- Editor being used (for example, VS Code) -- Number of suggestions shown, accepted, rejected, or that had errors -- Duration of time that a suggestion was shown -- Prompt and suffix lengths -- Model used -- Number of unique users -- Number of unique instances - -## Inference window context - -Code Suggestions inferences against the currently opened file, the content before and after the cursor, the filename, and the extension type. For more information on possible future context expansion to improve the quality of suggestions, see [epic 11669](https://gitlab.com/groups/gitlab-org/-/epics/11669). - -### Truncation of file content - -Because of LLM limits and performance reasons, the content of the currently -opened file is truncated: - -- **For code completion**: to 2048 tokens (roughly 8192 characters). -- **For code generation**: to 50,000 characters. - -Content above the cursor is prioritized over content below the cursor. The content -above the cursor is truncated from the left side, and content below the cursor -is truncated from the right side. - -## Training data - -GitLab does not train generative AI models based on private (non-public) data. The vendors we work with also do not train models based on private data. - -For more information on GitLab Code Suggestions data [sub-processors](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors), see: - -- Google Vertex AI Codey APIs [data governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance) and [responsible AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/responsible-ai). -- Anthropic Claude's [constitution](https://www.anthropic.com/news/claudes-constitution). + + + + diff --git a/doc/user/project/repository/code_suggestions/index.md b/doc/user/project/repository/code_suggestions/index.md index 7d7f7095a86..3284726db31 100644 --- a/doc/user/project/repository/code_suggestions/index.md +++ b/doc/user/project/repository/code_suggestions/index.md @@ -86,11 +86,32 @@ For use cases and best practices, follow the [GitLab Duo examples documentation] ## Response time +Code Suggestions is powered by a generative AI model. + +Your personal access token enables a secure API connection to GitLab.com or to your GitLab instance. +This API connection securely transmits a context window from your IDE/editor to the [GitLab AI Gateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist), a GitLab hosted service. The [gateway](../../../../development/ai_architecture.md) calls the large language model APIs, and then the generated suggestion is transmitted back to your IDE/editor. + - Code completion suggestions are usually low latency. - For code generation: - Algorithms or large code blocks might take more than 10 seconds to generate. - Streaming of code generation responses is supported in VS Code, leading to faster average response times. Other supported IDEs offer slower response times and will return the generated code in a single block. +## Inference window context + +Code Suggestions inferences against the currently opened file, the content before and after the cursor, the filename, and the extension type. For more information on possible future context expansion to improve the quality of suggestions, see [epic 11669](https://gitlab.com/groups/gitlab-org/-/epics/11669). + +## Truncation of file content + +Because of LLM limits and performance reasons, the content of the currently +opened file is truncated: + +- For code completion: to 2048 tokens (roughly 8192 characters). +- For code generation: to 50,000 characters. + +Content above the cursor is prioritized over content below the cursor. The content +above the cursor is truncated from the left side, and content below the cursor +is truncated from the right side. + ## Accuracy of results We are continuing to work on the accuracy of overall generated content. @@ -104,13 +125,6 @@ However, Code Suggestions might generate suggestions that are: When using Code Suggestions, [code review best practice](../../../../development/code_review.md) still applies. -## Progressive enhancement - -This feature is designed as a progressive enhancement to developer IDEs. -Code Suggestions offer a completion if a suitable recommendation is provided to the user in a timely manner. -In the event of a connection issue or model inference failure, the feature gracefully degrades. -Code Suggestions do not prevent you from writing code in your IDE. - ## Disable Code Suggestions To disable Code Suggestions, disable the feature in your IDE editor extension. diff --git a/gems/gitlab-cng/lib/gitlab/cng/cli.rb b/gems/gitlab-cng/lib/gitlab/cng/cli.rb index 04519d6bbca..578ff8f7e61 100644 --- a/gems/gitlab-cng/lib/gitlab/cng/cli.rb +++ b/gems/gitlab-cng/lib/gitlab/cng/cli.rb @@ -3,51 +3,31 @@ require "thor" require "require_all" -require_rel "helpers/**/*.rb" +require_rel "lib/**/*.rb" require_rel "commands/**/*.rb" module Gitlab module Cng # Main CLI class handling all commands # - class CLI < Thor + class CLI < Commands::Command + extend Helpers::Thor + # Error raised by this runner Error = Class.new(StandardError) - # Fail if unknown option is passed - check_unknown_options! - - class << self - # Exit with non 0 status code if any command fails - # - # @return [Boolean] - def exit_on_failure? - true - end - - # Register all public methods of Thor class as top level commands - # - # @param [Thor] klass - # @return [void] - def register_commands(klass) - raise Error, "#{klass} is not a Thor class" unless klass < Thor - - klass.commands.each do |name, command| - raise Error, "Tried to register command '#{name}' but the command already exists" if commands[name] - - # check if the method takes arguments - pass_args = klass.new.method(name).arity != 0 - - commands[name] = command - define_method(name) do |*args| - pass_args ? invoke(klass, name, *args) : invoke(klass, name) - end - end - end + # Exit with non 0 status code if any command fails + # + # @return [Boolean] + def self.exit_on_failure? + true end register_commands(Commands::Version) register_commands(Commands::Doctor) + + desc "create [SUBCOMMAND]", "Manage deployment related object creation" + subcommand "create", Commands::Create end end end diff --git a/gems/gitlab-cng/lib/gitlab/cng/commands/_command.rb b/gems/gitlab-cng/lib/gitlab/cng/commands/_command.rb index fd0f449e6ef..ab9c521fa93 100644 --- a/gems/gitlab-cng/lib/gitlab/cng/commands/_command.rb +++ b/gems/gitlab-cng/lib/gitlab/cng/commands/_command.rb @@ -3,8 +3,21 @@ module Gitlab module Cng module Commands + # Thor command base class + # class Command < Thor include Helpers::Output + + check_unknown_options! + + private + + # Options hash with symbolized keys + # + # @return [Hash] + def symbolized_options + @symbolized_options ||= options.transform_keys(&:to_sym) + end end end end diff --git a/gems/gitlab-cng/lib/gitlab/cng/commands/create.rb b/gems/gitlab-cng/lib/gitlab/cng/commands/create.rb new file mode 100644 index 00000000000..1b55108d531 --- /dev/null +++ b/gems/gitlab-cng/lib/gitlab/cng/commands/create.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Gitlab + module Cng + module Commands + # Create command composed of subcommands that create various resources needed for CNG deployment + # + class Create < Command + desc "cluster", "Create kind cluster for local deployments" + option :name, + desc: "Cluster name", + default: "gitlab", + type: :string, + aliases: "-n" + option :ci, + desc: "Use CI specific configuration", + default: false, + type: :boolean, + aliases: "-c" + option :docker_hostname, + desc: "Custom docker hostname if remote docker instance is used, like docker-in-docker", + type: :string, + aliases: "-d" + def cluster + Kind::Cluster.new(**symbolized_options).create + end + end + end + end +end diff --git a/gems/gitlab-cng/lib/gitlab/cng/commands/doctor.rb b/gems/gitlab-cng/lib/gitlab/cng/commands/doctor.rb index 4262a25e4f9..4390de2ce63 100644 --- a/gems/gitlab-cng/lib/gitlab/cng/commands/doctor.rb +++ b/gems/gitlab-cng/lib/gitlab/cng/commands/doctor.rb @@ -5,12 +5,14 @@ require "tty-which" module Gitlab module Cng module Commands + # Command to check system dependencies + # class Doctor < Command TOOLS = %w[docker kind kubectl helm].freeze desc "doctor", "Validate presence of all required system dependencies" def doctor - log_info "Checking system dependencies", bright: true + log "Checking system dependencies", :info, bright: true missing_tools = TOOLS.filter_map do |tool| Helpers::Spinner.spin("Checking if #{tool} is installed") do raise "#{tool} not found in PATH" unless TTY::Which.exist?(tool) @@ -18,7 +20,7 @@ module Gitlab rescue StandardError tool end - return log_success "All system dependencies are present", bright: true if missing_tools.empty? + return log "All system dependencies are present", :success, bright: true if missing_tools.empty? exit_with_error "The following system dependencies are missing: #{missing_tools.join(', ')}" end diff --git a/gems/gitlab-cng/lib/gitlab/cng/commands/version.rb b/gems/gitlab-cng/lib/gitlab/cng/commands/version.rb index 3e1643236e1..f62a4c9072e 100644 --- a/gems/gitlab-cng/lib/gitlab/cng/commands/version.rb +++ b/gems/gitlab-cng/lib/gitlab/cng/commands/version.rb @@ -3,8 +3,10 @@ module Gitlab module Cng module Commands + # Basic command to print the version of cng orchestrator + # class Version < Command - desc "version", "Prints cng orchestrator version" + desc "version", "Print cng orchestrator version" def version puts Cng::VERSION end diff --git a/gems/gitlab-cng/lib/gitlab/cng/helpers/output.rb b/gems/gitlab-cng/lib/gitlab/cng/helpers/output.rb deleted file mode 100644 index b3ba6d13a29..00000000000 --- a/gems/gitlab-cng/lib/gitlab/cng/helpers/output.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -require "rainbow" - -module Gitlab - module Cng - module Helpers - # Console output helpers to include in command implementations - # - module Output - private - - # Print base output without specific color - # - # @param [String] message - # @param [Boolean] bright - # @return [void] - def log(message, bright: false) - puts colorize(message, nil, bright: bright) - end - - # Print info in magenta color - # - # @param [String] message - # @param [Boolean] bright - # @return [nil] - def log_info(message, bright: false) - puts colorize(message, :magenta, bright: bright) - end - - # Print success message in green color - # - # @param [String] message - # @param [Boolean] bright - # @return [nil] - def log_success(message, bright: false) - puts colorize(message, :green, bright: bright) - end - - # Print warning message in yellow color - # - # @param [String] message - # @param [Boolean] bright - # @return [nil] - def log_warn(message, bright: false) - puts colorize(message, :yellow, bright: bright) - end - - # Print error message in red color - # - # @param [String] message - # @param [Boolean] bright - # @return [nil] - def log_error(message, bright: false) - puts colorize(message, :red, bright: bright) - end - - # Exit with non zero exit code and print error message - # - # @param [String] message - # @return [void] - def exit_with_error(message) - log_error(message, bright: true) - exit 1 - end - - # Colorize message string and output to stdout - # - # @param [String] message - # @param [] color - # @param [Boolean] bright - # @return [String] - def colorize(message, color, bright: false) - rainbow.wrap(message) - .then { |m| bright ? m.bright : m } - .then { |m| color ? m.color(color) : m } - end - - # Instance of rainbow colorization class - # - # @return [Rainbow] - def rainbow - @rainbow ||= Rainbow.new - end - end - end - end -end diff --git a/gems/gitlab-cng/lib/gitlab/cng/helpers/shell.rb b/gems/gitlab-cng/lib/gitlab/cng/helpers/shell.rb deleted file mode 100644 index 40483007439..00000000000 --- a/gems/gitlab-cng/lib/gitlab/cng/helpers/shell.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require "open3" - -module Gitlab - module Cng - module Helpers - # Wrapper for shell command execution - # - module Shell - CommandFailure = Class.new(StandardError) - - # Execute shell command - # - # @param [String] command - # @return [String] output - def self.execute_shell(command) - out, err, status = Open3.capture3(command) - - cmd_output = [] - cmd_output << "Out: #{out}" unless out.empty? - cmd_output << "Err: #{err}" unless err.empty? - output = cmd_output.join("\n") - - unless status.success? - err_msg = "Command '#{command}' failed!\n#{output}" - raise(CommandFailure, err_msg) - end - - output - end - end - end - end -end diff --git a/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/output.rb b/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/output.rb new file mode 100644 index 00000000000..2b89aca3949 --- /dev/null +++ b/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/output.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "rainbow" + +module Gitlab + module Cng + module Helpers + # Console output helpers to include in command implementations + # + module Output + LOG_COLOR = { + default: nil, + info: :magenta, + success: :green, + warn: :yellow, + error: :red + }.freeze + + private + + # Print colorized log message to stdout + # + # @param [String] message + # @param [Symbol] type + # @param [Boolean] bright + # @return [void] + def log(message, type = :default, bright: false) + puts colorize(message, LOG_COLOR.fetch(type), bright: bright) + end + + # Exit with non zero exit code and print error message + # + # @param [String] message + # @return [void] + def exit_with_error(message) + log(message, :error, bright: true) + exit 1 + end + + # Colorize message string and output to stdout + # + # @param [String] message + # @param [] color + # @param [Boolean] bright + # @return [String] + def colorize(message, color, bright: false) + rainbow.wrap(message) + .then { |m| bright ? m.bright : m } + .then { |m| color ? m.color(color) : m } + end + + # Instance of rainbow colorization class + # + # @return [Rainbow] + def rainbow + @rainbow ||= Rainbow.new + end + end + end + end +end diff --git a/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/shell.rb b/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/shell.rb new file mode 100644 index 00000000000..4a6001c0d73 --- /dev/null +++ b/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/shell.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "open3" + +module Gitlab + module Cng + module Helpers + # Wrapper for shell command execution + # + module Shell + CommandFailure = Class.new(StandardError) + + # Execute shell command + # + # @param [Array] command + # @param [Boolean] raise_on_failure + # @param [Hash] env + # @return [] return command output and status if raise_on_failure is false + def execute_shell(cmd, raise_on_failure: true, env: {}) + raise "System commands must be given as an array of strings" unless cmd.is_a?(Array) + + if cmd.one? && cmd.first.match?(/\s/) + raise "System commands must be split into an array of space-separated values" + end + + out, status = Open3.capture2e(env, *cmd) + + if raise_on_failure && !status.success? + err_msg = "Command '#{cmd}' failed!\n#{out}" + raise(CommandFailure, err_msg) + end + + raise_on_failure ? out : [out, status] + end + end + end + end +end diff --git a/gems/gitlab-cng/lib/gitlab/cng/helpers/spinner.rb b/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/spinner.rb similarity index 97% rename from gems/gitlab-cng/lib/gitlab/cng/helpers/spinner.rb rename to gems/gitlab-cng/lib/gitlab/cng/lib/helpers/spinner.rb index 38a42a6e0b5..c723bf4ddfe 100644 --- a/gems/gitlab-cng/lib/gitlab/cng/helpers/spinner.rb +++ b/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/spinner.rb @@ -5,7 +5,7 @@ require "tty-spinner" module Gitlab module Cng module Helpers - # Spinner helper class + # Spinner helper class for wrapping long running tasks in progress spinner # class Spinner include Output diff --git a/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/thor.rb b/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/thor.rb new file mode 100644 index 00000000000..fa396058e5b --- /dev/null +++ b/gems/gitlab-cng/lib/gitlab/cng/lib/helpers/thor.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Gitlab + module Cng + module Helpers + # Thor command specific helpers + # + module Thor + # Register all public methods of Thor class inside another Thor class as commands + # + # @param [Thor] klass + # @return [void] + def register_commands(klass) + raise "#{klass} is not a Thor class" unless klass < ::Thor + + klass.commands.each do |name, command| + raise "Tried to register command '#{name}' but the command already exists" if commands[name] + + # check if the method takes arguments + pass_args = klass.new.method(name).arity != 0 + + commands[name] = command + define_method(name) do |*args| + pass_args ? invoke(klass, name, *args) : invoke(klass, name) + end + end + end + end + end + end +end diff --git a/gems/gitlab-cng/lib/gitlab/cng/lib/kind/cluster.rb b/gems/gitlab-cng/lib/gitlab/cng/lib/kind/cluster.rb new file mode 100644 index 00000000000..a5f23330f3e --- /dev/null +++ b/gems/gitlab-cng/lib/gitlab/cng/lib/kind/cluster.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require "uri" + +require_relative "configs" + +module Gitlab + module Cng + module Kind + # Class responsible for creating kind cluster + # + class Cluster + include Helpers::Output + include Helpers::Shell + include Configs + + def initialize(ci:, name:, docker_hostname: nil) + @ci = ci + @name = name + @docker_hostname = ci ? docker_hostname || "docker" : docker_hostname + end + + def create + log "Creating cluster '#{name}'", :info + return log " cluster '#{name}' already exists, skipping!" if cluster_exists? + + create_cluster + update_server_url + log "Cluster '#{name}' created", :success + rescue Helpers::Shell::CommandFailure + # Exit cleanly without stacktrace if shell command fails + exit(1) + end + + private + + attr_reader :ci, :name, :docker_hostname + + # Check if cluster exists + # + # @return [Boolean] + def cluster_exists? + execute_shell(%w[kind get clusters]).include?(name) + end + + # Create kind cluster + # + # @return [void] + def create_cluster + Helpers::Spinner.spin("performing cluster creation") do + execute_shell([ + "kind", + "create", + "cluster", + "--name", name, + "--wait", "10s", + "--config", ci ? ci_config(docker_hostname) : default_config(docker_hostname) + ]) + end + end + + # Update server url in kubeconfig for kubectl to work correctly with remote docker + # + # @return [void] + def update_server_url + return unless docker_hostname + + Helpers::Spinner.spin("updating kind cluster server url") do + cluster_name = "kind-#{name}" + server = execute_shell([ + "kubectl", "config", "view", + "-o", "jsonpath={.clusters[?(@.name == \"#{cluster_name}\")].cluster.server}" + ]) + uri = URI.parse(server).tap { |uri| uri.host = docker_hostname } + execute_shell(%W[kubectl config set-cluster #{cluster_name} --server=#{uri}]) + end + end + end + end + end +end diff --git a/gems/gitlab-cng/lib/gitlab/cng/lib/kind/configs.rb b/gems/gitlab-cng/lib/gitlab/cng/lib/kind/configs.rb new file mode 100644 index 00000000000..7406b4bb614 --- /dev/null +++ b/gems/gitlab-cng/lib/gitlab/cng/lib/kind/configs.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require "tmpdir" +require "erb" + +module Gitlab + module Cng + module Kind + # Kind configuration file templates + # + module Configs + # Temporary ci specific kind configuration file + # + # @param [String] docker_hostname + # @return [String] file path + def ci_config(docker_hostname) + config_yml = <<~YML + apiVersion: kind.x-k8s.io/v1alpha4 + kind: Cluster + networking: + apiServerAddress: "0.0.0.0" + nodes: + - role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + - | + kind: ClusterConfiguration + apiServer: + certSANs: + - "#{docker_hostname}" + extraPortMappings: + # containerPort below must match the values file: + # nginx-ingress.controller.service.nodePorts.http + - containerPort: 32080 + hostPort: 80 + listenAddress: "0.0.0.0" + # containerPort below must match the values file: + # nginx-ingress.controller.service.nodePorts.gitlab-shell + - containerPort: 32022 + hostPort: 22 + listenAddress: "0.0.0.0" + YML + + tmp_config_file(config_yml) + end + + # Temporary kind configuration file + # + # @param [String, nil] docker_hostname + # @return [String] file path + def default_config(docker_hostname) + template = ERB.new(<<~YML, trim_mode: "-") + kind: Cluster + apiVersion: kind.x-k8s.io/v1alpha4 + nodes: + - role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + <% if docker_hostname -%> + - | + kind: ClusterConfiguration + apiServer: + certSANs: + - "<%= docker_hostname %>" + <% end -%> + extraPortMappings: + # containerPort below must match the values file: + # nginx-ingress.controller.service.nodePorts.http + - containerPort: 32080 + hostPort: 32080 + listenAddress: "0.0.0.0" + # containerPort below must match the values file: + # nginx-ingress.controller.service.nodePorts.ssh + - containerPort: 32022 + hostPort: 32022 + listenAddress: "0.0.0.0" + YML + + tmp_config_file(template.result(binding)) + end + + # Create temporary kind config file + # + # @param [String] config_yml + # @return [String] + def tmp_config_file(config_yml) + File.join(Dir.tmpdir, "kind-config.yml").tap do |path| + File.write(path, config_yml) + end + end + end + end + end +end diff --git a/gems/gitlab-cng/spec/command_helper.rb b/gems/gitlab-cng/spec/command_helper.rb new file mode 100644 index 00000000000..493b4e5dce9 --- /dev/null +++ b/gems/gitlab-cng/spec/command_helper.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +RSpec.shared_context "with command testing helper" do + let(:command_instance) { described_class.new } + + # Invoke command with args + # + # @param [String] command + # @param [Array] args + # @return [void] + def invoke_command(command, args = [], options = {}) + command_instance.invoke(command, args, options) + end + + # Expect command to have attributes + # + # @param [String] command + # @param [Hash] attributes + # @return [void] + def expect_command_to_include_attributes(command, attributes) + expect(described_class.commands[command].to_h).to include(attributes) + end +end diff --git a/gems/gitlab-cng/spec/integration/cng_spec.rb b/gems/gitlab-cng/spec/integration/cng_spec.rb index 5a12f844375..8a33eb85bc2 100644 --- a/gems/gitlab-cng/spec/integration/cng_spec.rb +++ b/gems/gitlab-cng/spec/integration/cng_spec.rb @@ -4,9 +4,10 @@ RSpec.describe "cng" do let(:usage) do <<~USAGE Commands: - cng doctor # Validate presence of all required system dependencies - cng help [COMMAND] # Describe available commands or one specific command - cng version # Prints cng orchestrator version + cng create [SUBCOMMAND] # Manage deployment related object creation + cng doctor # Validate presence of all required system dependencies + cng help [COMMAND] # Describe available commands or one specific command + cng version # Print cng orchestrator version USAGE end diff --git a/gems/gitlab-cng/spec/integration/gitlab/cng/cli_spec.rb b/gems/gitlab-cng/spec/integration/gitlab/cng/cli_spec.rb new file mode 100644 index 00000000000..1fe400dd4a7 --- /dev/null +++ b/gems/gitlab-cng/spec/integration/gitlab/cng/cli_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +RSpec.describe Gitlab::Cng::CLI do + shared_examples "command with help" do |args, help_output| + it "shows help" do + expect { cli.invoke(*args) }.to output(/#{help_output}/).to_stdout + end + end + + subject(:cli) { described_class.new } + + describe "version command" do + it_behaves_like "command with help", [:help, ["version"]], /Print cng orchestrator version/ + + it "executes version command" do + expect { cli.invoke(:version) }.to output(/#{Gitlab::Cng::VERSION}/o).to_stdout + end + end + + describe "doctor command" do + let(:command_instance) { Gitlab::Cng::Commands::Doctor.new } + + before do + allow(Gitlab::Cng::Commands::Doctor).to receive(:new).and_return(command_instance) + allow(command_instance).to receive(:doctor) + end + + it_behaves_like "command with help", [:help, ["doctor"]], /Validate presence of all required system dependencies/ + + it "invokes doctor command" do + cli.invoke(:doctor) + + expect(command_instance).to have_received(:doctor) + end + end + + describe "create command" do + context "with cluster subcommand" do + let(:cluster_instance) { instance_double(Gitlab::Cng::Kind::Cluster, create: nil) } + + before do + allow(Gitlab::Cng::Kind::Cluster).to receive(:new).and_return(cluster_instance) + end + + it_behaves_like "command with help", [:help, %w[create cluster]], /Create kind cluster for local deployments/ + + it "invokes cluster create command" do + cli.invoke(:create, %w[cluster]) + + expect(Gitlab::Cng::Kind::Cluster).to have_received(:new).with(ci: false, name: "gitlab") + expect(cluster_instance).to have_received(:create) + end + end + end +end diff --git a/gems/gitlab-cng/spec/integration/lib/gitlab/cng/cli_spec.rb b/gems/gitlab-cng/spec/integration/lib/gitlab/cng/cli_spec.rb deleted file mode 100644 index 04f5ca5953f..00000000000 --- a/gems/gitlab-cng/spec/integration/lib/gitlab/cng/cli_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe Gitlab::Cng::CLI do - let(:cli) { described_class.new } - - describe "version command" do - it "shows version command help" do - expect { cli.invoke(:help, ["version"]) }.to output(/Prints cng orchestrator version/).to_stdout - end - - it "executes version command" do - expect { cli.invoke(:version) }.to output(/#{Gitlab::Cng::VERSION}/o).to_stdout - end - end - - describe "doctor command" do - let(:command_instance) { Gitlab::Cng::Commands::Doctor.new } - - before do - allow(Gitlab::Cng::Commands::Doctor).to receive(:new).and_return(command_instance) - allow(command_instance).to receive(:doctor) - end - - it "shows doctor command help" do - expect { cli.invoke(:help, ["doctor"]) }.to output( - /Validate presence of all required system dependencies/ - ).to_stdout - end - - it "invokes doctor command" do - cli.invoke(:doctor) - - expect(command_instance).to have_received(:doctor) - end - end -end diff --git a/gems/gitlab-cng/spec/spec_helper.rb b/gems/gitlab-cng/spec/spec_helper.rb index 64f319a3c7d..888c6f5603d 100644 --- a/gems/gitlab-cng/spec/spec_helper.rb +++ b/gems/gitlab-cng/spec/spec_helper.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true require "rspec" - require "gitlab/cng/cli" +require_relative "command_helper" + RSpec.configure do |config| # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! diff --git a/gems/gitlab-cng/spec/unit/gitlab/cng/commands/create_spec.rb b/gems/gitlab-cng/spec/unit/gitlab/cng/commands/create_spec.rb new file mode 100644 index 00000000000..b94342c7832 --- /dev/null +++ b/gems/gitlab-cng/spec/unit/gitlab/cng/commands/create_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +RSpec.describe Gitlab::Cng::Commands::Create do + include_context "with command testing helper" + + describe "cluster command" do + let(:command_name) { "cluster" } + let(:kind_cluster) { instance_double(Gitlab::Cng::Kind::Cluster, create: nil) } + + before do + allow(Gitlab::Cng::Kind::Cluster).to receive(:new).and_return(kind_cluster) + end + + it "defines cluster command" do + expect_command_to_include_attributes(command_name, { + description: "Create kind cluster for local deployments", + name: command_name, + usage: command_name + }) + end + + it "invokes kind cluster creation with correct arguments" do + invoke_command(command_name, [], { ci: true, name: "test-cluster" }) + + expect(kind_cluster).to have_received(:create) + expect(Gitlab::Cng::Kind::Cluster).to have_received(:new).with({ + ci: true, + name: "test-cluster" + }) + end + end +end diff --git a/gems/gitlab-cng/spec/unit/lib/gitlab/cng/commands/doctor_spec.rb b/gems/gitlab-cng/spec/unit/gitlab/cng/commands/doctor_spec.rb similarity index 67% rename from gems/gitlab-cng/spec/unit/lib/gitlab/cng/commands/doctor_spec.rb rename to gems/gitlab-cng/spec/unit/gitlab/cng/commands/doctor_spec.rb index f017f25a5a1..4524194d1b2 100644 --- a/gems/gitlab-cng/spec/unit/lib/gitlab/cng/commands/doctor_spec.rb +++ b/gems/gitlab-cng/spec/unit/gitlab/cng/commands/doctor_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe Gitlab::Cng::Commands::Doctor do - subject(:command) { described_class.new } + include_context "with command testing helper" let(:spinner) { instance_double(Gitlab::Cng::Helpers::Spinner) } let(:command_name) { "doctor" } @@ -11,13 +11,12 @@ RSpec.describe Gitlab::Cng::Commands::Doctor do before do allow(Gitlab::Cng::Helpers::Spinner).to receive(:new) { spinner } allow(spinner).to receive(:spin).and_yield - allow(command).to receive(:puts) tools.each { |tool| allow(TTY::Which).to receive(:exist?).with(tool).and_return(tools_present) } end it "defines a doctor command" do - expect(described_class.commands[command_name].to_h).to include({ + expect_command_to_include_attributes(command_name, { description: "Validate presence of all required system dependencies", long_description: nil, name: command_name, @@ -28,8 +27,9 @@ RSpec.describe Gitlab::Cng::Commands::Doctor do context "with all tools present" do it "does not raise an error", :aggregate_failures do - expect { command.doctor }.not_to raise_error - expect(command).to have_received(:puts).with(/All system dependencies are present/) + expect do + expect { invoke_command(command_name) }.not_to raise_error + end.to output(/All system dependencies are present/).to_stdout end end @@ -37,8 +37,9 @@ RSpec.describe Gitlab::Cng::Commands::Doctor do let(:tools_present) { false } it "exits and prints missing dependencies error", :aggregate_failures do - expect { command.doctor }.to raise_error(SystemExit) - expect(command).to have_received(:puts).with(/The following system dependencies are missing: #{tools.join(', ')}/) + expect do + expect { invoke_command(command_name) }.to raise_error(SystemExit) + end.to output(/The following system dependencies are missing: #{tools.join(', ')}/).to_stdout end end end diff --git a/gems/gitlab-cng/spec/unit/lib/gitlab/cng/commands/version_spec.rb b/gems/gitlab-cng/spec/unit/gitlab/cng/commands/version_spec.rb similarity index 56% rename from gems/gitlab-cng/spec/unit/lib/gitlab/cng/commands/version_spec.rb rename to gems/gitlab-cng/spec/unit/gitlab/cng/commands/version_spec.rb index 58e13883603..f0f4e97af1a 100644 --- a/gems/gitlab-cng/spec/unit/lib/gitlab/cng/commands/version_spec.rb +++ b/gems/gitlab-cng/spec/unit/gitlab/cng/commands/version_spec.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true RSpec.describe Gitlab::Cng::Commands::Version do + include_context "with command testing helper" + let(:version) { Gitlab::Cng::VERSION } it "defines a version command" do - expect(described_class.commands["version"].to_h).to include({ - description: "Prints cng orchestrator version", + expect_command_to_include_attributes("version", { + description: "Print cng orchestrator version", long_description: nil, name: "version", options: {}, @@ -14,6 +16,6 @@ RSpec.describe Gitlab::Cng::Commands::Version do end it "prints the version" do - expect { described_class.new.version }.to output(/#{version}/).to_stdout + expect { invoke_command("version") }.to output(/#{version}/).to_stdout end end diff --git a/gems/gitlab-cng/spec/unit/lib/gitlab/cng/helpers/spinner_spec.rb b/gems/gitlab-cng/spec/unit/gitlab/cng/helpers/spinner_spec.rb similarity index 100% rename from gems/gitlab-cng/spec/unit/lib/gitlab/cng/helpers/spinner_spec.rb rename to gems/gitlab-cng/spec/unit/gitlab/cng/helpers/spinner_spec.rb diff --git a/gems/gitlab-cng/spec/unit/gitlab/cng/kind/cluster_spec.rb b/gems/gitlab-cng/spec/unit/gitlab/cng/kind/cluster_spec.rb new file mode 100644 index 00000000000..760bd9b5923 --- /dev/null +++ b/gems/gitlab-cng/spec/unit/gitlab/cng/kind/cluster_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +RSpec.describe Gitlab::Cng::Kind::Cluster do + subject(:cluster) { described_class.new(ci: ci, name: name, docker_hostname: docker_hostname) } + + let(:ci) { false } + let(:name) { "gitlab" } + let(:docker_hostname) { nil } + let(:tmp_config_path) { File.join(Dir.tmpdir, "kind-config.yml") } + let(:command_status) { instance_double(Process::Status, success?: true) } + let(:clusters) { "kind" } + + before do + allow(Gitlab::Cng::Helpers::Spinner).to receive(:spin).and_yield + allow(File).to receive(:write).with(tmp_config_path, kind_config_content) + + allow(Open3).to receive(:capture2e).with({}, *%w[ + kind get clusters + ]).and_return([clusters, command_status]) + allow(Open3).to receive(:capture2e).with({}, *[ + "kind", + "create", + "cluster", + "--name", name, + "--wait", "10s", + "--config", tmp_config_path + ]).and_return(["", command_status]) + end + + context "with ci specific setup" do + let(:ci) { true } + let(:docker_hostname) { "docker" } + + let(:kind_config_content) do + <<~YML + apiVersion: kind.x-k8s.io/v1alpha4 + kind: Cluster + networking: + apiServerAddress: "0.0.0.0" + nodes: + - role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + - | + kind: ClusterConfiguration + apiServer: + certSANs: + - "#{docker_hostname}" + extraPortMappings: + # containerPort below must match the values file: + # nginx-ingress.controller.service.nodePorts.http + - containerPort: 32080 + hostPort: 80 + listenAddress: "0.0.0.0" + # containerPort below must match the values file: + # nginx-ingress.controller.service.nodePorts.gitlab-shell + - containerPort: 32022 + hostPort: 22 + listenAddress: "0.0.0.0" + YML + end + + context "without existing cluster" do + before do + allow(Open3).to receive(:capture2e).with({}, *[ + "kubectl", "config", "view", "-o", "jsonpath={.clusters[?(@.name == \"kind-#{name}\")].cluster.server}" + ]).and_return(["https://127.0.0.1:6443", command_status]) + allow(Open3).to receive(:capture2e).with({}, *%W[ + kubectl config set-cluster kind-#{name} --server=https://#{docker_hostname}:6443 + ]).and_return(["", command_status]) + end + + it "creates cluster with ci specific configuration" do + expect { cluster.create }.to output(/Cluster '#{name}' created/).to_stdout + end + end + end + + context "without ci specific setup" do + let(:ci) { false } + let(:docker_hostname) { nil } + + let(:kind_config_content) do + <<~YML + kind: Cluster + apiVersion: kind.x-k8s.io/v1alpha4 + nodes: + - role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + extraPortMappings: + # containerPort below must match the values file: + # nginx-ingress.controller.service.nodePorts.http + - containerPort: 32080 + hostPort: 32080 + listenAddress: "0.0.0.0" + # containerPort below must match the values file: + # nginx-ingress.controller.service.nodePorts.ssh + - containerPort: 32022 + hostPort: 32022 + listenAddress: "0.0.0.0" + YML + end + + context "with already created cluster" do + let(:clusters) { "kind\n#{name}" } + + it "skips clusters creation" do + expect { cluster.create }.to output(/cluster '#{name}' already exists, skipping!/).to_stdout + end + end + + context "without existing cluster" do + it "creates cluster with default config" do + expect { cluster.create }.to output(/Cluster '#{name}' created/).to_stdout + end + end + + context "with command failure" do + let(:command_status) { instance_double(Process::Status, success?: false) } + + it "exits on command failures" do + expect do + expect { cluster.create }.to output.to_stdout + end.to raise_error(SystemExit) + end + end + end +end diff --git a/lib/api/search.rb b/lib/api/search.rb index 2a20adf6058..87c09dc6d90 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -86,7 +86,7 @@ module API search_scope: search_scope ) - Gitlab::UsageDataCounters::SearchCounter.count(:all_searches) + Gitlab::InternalEvents.track_event('perform_search', category: 'API::Search', user: current_user) paginate(@results) diff --git a/lib/gitlab/ci/variables/downstream/generator.rb b/lib/gitlab/ci/variables/downstream/generator.rb index e1fd8200dd6..6b1824f49eb 100644 --- a/lib/gitlab/ci/variables/downstream/generator.rb +++ b/lib/gitlab/ci/variables/downstream/generator.rb @@ -10,7 +10,7 @@ module Gitlab def initialize(bridge) @bridge = bridge - context = Context.new(all_bridge_variables: bridge.variables, expand_file_refs: bridge.expand_file_refs?) + context = Context.new(all_bridge_variables: bridge.variables, expand_file_refs: false) @raw_variable_generator = RawVariableGenerator.new(context) @expandable_variable_generator = ExpandableVariableGenerator.new(context) diff --git a/lib/gitlab/color_modes.rb b/lib/gitlab/color_modes.rb index cba984519da..0114c44f4cd 100644 --- a/lib/gitlab/color_modes.rb +++ b/lib/gitlab/color_modes.rb @@ -7,6 +7,7 @@ module Gitlab # Color mode ID used when no `default_color_mode` configuration setting is provided. APPLICATION_DEFAULT = 1 APPLICATION_DARK = 2 + APPLICATION_SYSTEM = 3 # Struct class representing a single Mode Mode = Struct.new(:id, :name, :css_class) @@ -14,7 +15,8 @@ module Gitlab def self.available_modes [ Mode.new(APPLICATION_DEFAULT, s_('ColorMode|Light'), 'gl-light'), - Mode.new(APPLICATION_DARK, s_('ColorMode|Dark (Experiment)'), 'gl-dark') + Mode.new(APPLICATION_DARK, s_('ColorMode|Dark (Experiment)'), 'gl-dark'), + Mode.new(APPLICATION_SYSTEM, s_('ColorMode|Auto (Experiment)'), 'gl-system') ] end diff --git a/lib/gitlab/git/keep_around.rb b/lib/gitlab/git/keep_around.rb index 4d2ad99d2f5..4e741a7c9e2 100644 --- a/lib/gitlab/git/keep_around.rb +++ b/lib/gitlab/git/keep_around.rb @@ -35,6 +35,7 @@ module Gitlab next unless sha.present? && commit_by(oid: sha) @keeparound_requested_counter.increment(labels) + Gitlab::AppLogger.info(message: 'Requesting keep-around reference', object_id: sha) next if kept_around?(sha) @@ -42,8 +43,10 @@ module Gitlab raw_repository.write_ref(keep_around_ref_name(sha), sha) @keeparound_created_counter.increment(labels) + Gitlab::AppLogger.info(message: 'Created keep-around reference', object_id: sha) + rescue Gitlab::Git::CommandError => ex - Gitlab::AppLogger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}" + Gitlab::ErrorTracking.track_exception(ex, object_id: sha) end end diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index e9d91b71722..4e71792ab90 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -13,6 +13,7 @@ module Gitlab gon.asset_host = ActionController::Base.asset_host gon.webpack_public_path = webpack_public_path gon.relative_url_root = Gitlab.config.gitlab.relative_url_root + gon.user_color_mode = Gitlab::ColorModes.for_user(current_user).css_class gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class gon.markdown_surround_selection = current_user&.markdown_surround_selection gon.markdown_automatic_lists = current_user&.markdown_automatic_lists diff --git a/lib/gitlab/usage_data_counters.rb b/lib/gitlab/usage_data_counters.rb index 401c7887604..03ddf79eeb0 100644 --- a/lib/gitlab/usage_data_counters.rb +++ b/lib/gitlab/usage_data_counters.rb @@ -9,7 +9,6 @@ module Gitlab DiffsCounter, KubernetesAgentCounter, NoteCounter, - SearchCounter, WebIdeCounter, SourceCodeCounter, MergeRequestWidgetExtensionCounter diff --git a/lib/gitlab/usage_data_counters/search_counter.rb b/lib/gitlab/usage_data_counters/search_counter.rb deleted file mode 100644 index 31d8485042e..00000000000 --- a/lib/gitlab/usage_data_counters/search_counter.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module UsageDataCounters - class SearchCounter < BaseCounter - KNOWN_EVENTS = %w[all_searches navbar_searches].freeze - PREFIX = nil - - class << self - def redis_key(event) - require_known_event(event) - - "#{event}_COUNT".upcase - end - - private - - def counter_key(event) - event.to_s.to_sym - end - end - end - end -end diff --git a/lib/gitlab/usage_data_counters/total_counter_redis_key_overrides.yml b/lib/gitlab/usage_data_counters/total_counter_redis_key_overrides.yml index 0c639c5bdef..f30f32d6629 100644 --- a/lib/gitlab/usage_data_counters/total_counter_redis_key_overrides.yml +++ b/lib/gitlab/usage_data_counters/total_counter_redis_key_overrides.yml @@ -26,3 +26,5 @@ '{event_counters}_view_wiki_page': USAGE_WIKI_PAGES_VIEW '{event_counters}_click_full_report_license_compliance': USAGE_USERS_VISITING_TESTING_LICENSE_COMPLIANCE_FULL_REPORT '{event_counters}_click_external_link_license_compliance': USAGE_USERS_CLICKING_LICENSE_TESTING_VISITING_EXTERNAL_WEBSITE +'{event_counters}_perform_search': ALL_SEARCHES_COUNT +'{event_counters}_perform_navbar_search': NAVBAR_SEARCHES_COUNT diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f09bfe523a6..f8c57413a0f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -12657,6 +12657,9 @@ msgstr "" msgid "Color" msgstr "" +msgid "ColorMode|Auto (Experiment)" +msgstr "" + msgid "ColorMode|Dark (Experiment)" msgstr "" @@ -26111,7 +26114,7 @@ msgstr "" msgid "IdentityVerification|Before you can create additional groups, we need to verify your account." msgstr "" -msgid "IdentityVerification|Before you can run concurrent pipelines, we need to verify your account." +msgid "IdentityVerification|Before you can run pipelines, we need to verify your account." msgstr "" msgid "IdentityVerification|Before you finish creating your account, we need to verify your identity. On the verification page, enter the following code." diff --git a/qa/qa/specs/features/api/4_verify/file_variable_downstream_pipeline_spec.rb b/qa/qa/specs/features/api/4_verify/file_variable_downstream_pipeline_spec.rb index f0707420b3b..be10caa75fa 100644 --- a/qa/qa/specs/features/api/4_verify/file_variable_downstream_pipeline_spec.rb +++ b/qa/qa/specs/features/api/4_verify/file_variable_downstream_pipeline_spec.rb @@ -2,7 +2,6 @@ module QA RSpec.describe 'Verify', :runner, product_group: :pipeline_security, - feature_flag: { name: 'ci_prevent_file_var_expansion_downstream_pipeline', scope: :project }, only: { subdomain: 'staging-canary' } do # Runs this test only in staging-canary to debug flakiness https://gitlab.com/gitlab-org/gitlab/-/issues/424903 # We need to collect failure data, please don't quarantine for the time being @@ -104,12 +103,6 @@ module QA ] end - around do |example| - Runtime::Feature.enable(:ci_prevent_file_var_expansion_downstream_pipeline, project: upstream_project) - example.run - Runtime::Feature.disable(:ci_prevent_file_var_expansion_downstream_pipeline, project: upstream_project) - end - before do add_file_variables_to_upstream_project add_ci_file(downstream_project, downstream_project_file) diff --git a/scripts/qa/cng_deploy/cng-kind.sh b/scripts/qa/cng_deploy/cng-kind.sh index ed7b89a6d59..b53420631e9 100644 --- a/scripts/qa/cng_deploy/cng-kind.sh +++ b/scripts/qa/cng_deploy/cng-kind.sh @@ -185,17 +185,6 @@ EOF log "success!" } -function setup_cluster() { - local kind_config=$1 - - log_with_header "Create kind kubernetes cluster" - kind create cluster --config "$kind_config" - sed -i -E -e "s/localhost|0\.0\.0\.0/docker/g" "$KUBECONFIG" - - log_with_header "Print cluster info" - kubectl cluster-info -} - function deploy() { local domain=$1 local values=$(chart_values $domain) diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 7f5b2653fa5..bdefa892007 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -62,6 +62,29 @@ RSpec.describe SearchController, feature_category: :global_search do end end + it_behaves_like 'internal event tracking' do + let(:params) { { search: 'foobar' } } + let(:event) { 'perform_search' } + let(:category) { described_class.to_s } + let(:namespace) { nil } + let(:project) { nil } + + subject(:tracked_event) { get :show, params: params } + end + + context 'for navbar search' do + let(:params) { { search: 'foobar', nav_source: 'navbar' } } + let(:category) { described_class.to_s } + let(:namespace) { nil } + let(:project) { nil } + + it_behaves_like 'internal event tracking' do + let(:event) { 'perform_navbar_search' } + + subject(:tracked_event) { get :show, params: params } + end + end + it_behaves_like 'with external authorization service enabled', :show, { search: 'hello' } it_behaves_like 'support for active record query timeouts', :show, { search: 'hello' }, :search_objects, :html it_behaves_like 'metadata is set', :show diff --git a/spec/factories/packages/npm/metadata_cache.rb b/spec/factories/packages/npm/metadata_cache.rb index 4fe1930d03e..56a59465ba3 100644 --- a/spec/factories/packages/npm/metadata_cache.rb +++ b/spec/factories/packages/npm/metadata_cache.rb @@ -4,9 +4,16 @@ FactoryBot.define do factory :npm_metadata_cache, class: 'Packages::Npm::MetadataCache' do project sequence(:package_name) { |n| "@#{project.root_namespace.path}/package-#{n}" } - file { fixture_file_upload('spec/fixtures/packages/npm/metadata.json') } size { 401.bytes } + transient do + file_fixture { 'spec/fixtures/packages/npm/metadata.json' } + end + + after(:build) do |entry, evaluator| + entry.file = fixture_file_upload(evaluator.file_fixture) + end + trait :processing do status { 'processing' } end @@ -16,5 +23,9 @@ FactoryBot.define do entry.update_attribute(:project_id, nil) end end + + trait(:object_storage) do + file_store { Packages::Npm::MetadataCacheUploader::Store::REMOTE } + end end end diff --git a/spec/factories/packages/nuget/symbol.rb b/spec/factories/packages/nuget/symbol.rb index 77d53ed1439..8b064c4511d 100644 --- a/spec/factories/packages/nuget/symbol.rb +++ b/spec/factories/packages/nuget/symbol.rb @@ -3,16 +3,27 @@ FactoryBot.define do factory :nuget_symbol, class: 'Packages::Nuget::Symbol' do package { association(:nuget_package) } - file { fixture_file_upload('spec/fixtures/packages/nuget/symbol/package.pdb') } file_path { 'lib/net7.0/package.pdb' } size { 100.bytes } sequence(:signature) { |n| "b91a152048fc4b3883bf3cf73fbc03f#{n}FFFFFFFF" } file_sha256 { 'dd1aaf26c557685cc37f93f53a2b6befb2c2e679f5ace6ec7a26d12086f358be' } + transient do + file_fixture { 'spec/fixtures/packages/nuget/symbol/package.pdb' } + end + + after(:build) do |symbol, evaluator| + symbol.file = fixture_file_upload(evaluator.file_fixture) + end + trait :stale do after(:create) do |entry| entry.update_attribute(:package_id, nil) end end + + trait(:object_storage) do + file_store { Packages::Nuget::SymbolUploader::Store::REMOTE } + end end end diff --git a/spec/frontend/admin/organizations/index/components/app_spec.js b/spec/frontend/admin/organizations/index/components/app_spec.js index 72339303e25..e7014b35d2f 100644 --- a/spec/frontend/admin/organizations/index/components/app_spec.js +++ b/spec/frontend/admin/organizations/index/components/app_spec.js @@ -1,16 +1,17 @@ import { GlButton } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/alert'; import { DEFAULT_PER_PAGE } from '~/api'; -import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data'; import organizationsQuery from '~/admin/organizations/index/graphql/queries/organizations.query.graphql'; import OrganizationsIndexApp from '~/admin/organizations/index/components/app.vue'; import OrganizationsView from '~/organizations/shared/components/organizations_view.vue'; import { MOCK_NEW_ORG_URL } from 'jest/organizations/shared/mock_data'; +import { pageInfoEmpty } from 'jest/organizations/mock_data'; jest.mock('~/alert'); @@ -20,10 +21,11 @@ describe('AdminOrganizationsIndexApp', () => { let wrapper; let mockApollo; - const organizations = { - nodes, - pageInfo, - }; + const { + data: { + currentUser: { organizations }, + }, + } = organizationsGraphQlResponse; const organizationEmpty = { nodes: [], diff --git a/spec/frontend/fixtures/organizations.rb b/spec/frontend/fixtures/organizations.rb index 68bfd3416cc..d5894622eb2 100644 --- a/spec/frontend/fixtures/organizations.rb +++ b/spec/frontend/fixtures/organizations.rb @@ -26,3 +26,42 @@ RSpec.describe Organizations::GroupsController, '(JavaScript fixtures)', type: : expect(response).to be_successful end end + +RSpec.describe 'Organizations (GraphQL fixtures)', feature_category: :cell do + describe GraphQL::Query, type: :request do + include GraphqlHelpers + include JavaScriptFixturesHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:organizations) { create_list(:organization, 3) } + let_it_be(:organization_users) do + organizations.map do |organization| + create(:organization_user, organization: organization, user: current_user) + end + end + + let_it_be(:organization_details) do + organizations.map do |organization| + create(:organization_detail, organization: organization) + end + end + + before do + sign_in(current_user) + end + + describe 'organizations' do + base_input_path = 'organizations/shared/graphql/queries/' + base_output_path = 'graphql/organizations/' + query_name = 'organizations.query.graphql' + + it "#{base_output_path}#{query_name}.json" do + query = get_graphql_query_as_string("#{base_input_path}#{query_name}") + + post_graphql(query, current_user: current_user, variables: { search: '', first: 3 }) + + expect_graphql_errors_to_be_empty + end + end + end +end diff --git a/spec/frontend/organizations/index/components/app_spec.js b/spec/frontend/organizations/index/components/app_spec.js index 0b607cc5902..0b101f4820a 100644 --- a/spec/frontend/organizations/index/components/app_spec.js +++ b/spec/frontend/organizations/index/components/app_spec.js @@ -1,15 +1,16 @@ import { GlButton } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/alert'; import { DEFAULT_PER_PAGE } from '~/api'; -import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data'; import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql'; import OrganizationsIndexApp from '~/organizations/index/components/app.vue'; import OrganizationsView from '~/organizations/shared/components/organizations_view.vue'; +import { pageInfoEmpty } from 'jest/organizations/mock_data'; import { MOCK_NEW_ORG_URL } from '../../shared/mock_data'; jest.mock('~/alert'); @@ -20,24 +21,18 @@ describe('OrganizationsIndexApp', () => { let wrapper; let mockApollo; - const organizations = { - nodes, - pageInfo, - }; + const { + data: { + currentUser: { organizations }, + }, + } = organizationsGraphQlResponse; const organizationEmpty = { nodes: [], pageInfo: pageInfoEmpty, }; - const successHandler = jest.fn().mockResolvedValue({ - data: { - currentUser: { - id: 'gid://gitlab/User/1', - organizations, - }, - }, - }); + const successHandler = jest.fn().mockResolvedValue(organizationsGraphQlResponse); const createComponent = (handler = successHandler) => { mockApollo = createMockApollo([[organizationsQuery, handler]]); diff --git a/spec/frontend/organizations/mock_data.js b/spec/frontend/organizations/mock_data.js new file mode 100644 index 00000000000..37498075609 --- /dev/null +++ b/spec/frontend/organizations/mock_data.js @@ -0,0 +1,23 @@ +export const pageInfoMultiplePages = { + endCursor: 'eyJpZCI6IjEwNTMifQ', + hasNextPage: true, + hasPreviousPage: true, + startCursor: 'eyJpZCI6IjEwNzIifQ', + __typename: 'PageInfo', +}; + +export const pageInfoOnePage = { + endCursor: 'eyJpZCI6IjEwNTMifQ', + hasNextPage: false, + hasPreviousPage: false, + startCursor: 'eyJpZCI6IjEwNzIifQ', + __typename: 'PageInfo', +}; + +export const pageInfoEmpty = { + endCursor: null, + hasNextPage: false, + hasPreviousPage: false, + startCursor: null, + __typename: 'PageInfo', +}; diff --git a/spec/frontend/organizations/shared/components/groups_view_spec.js b/spec/frontend/organizations/shared/components/groups_view_spec.js index 1ee57a65e04..942e8db247f 100644 --- a/spec/frontend/organizations/shared/components/groups_view_spec.js +++ b/spec/frontend/organizations/shared/components/groups_view_spec.js @@ -19,12 +19,12 @@ import { DEFAULT_PER_PAGE } from '~/api'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; +import { organizationGroups as nodes } from '~/organizations/mock_data'; import { - organizationGroups as nodes, - pageInfo, + pageInfoMultiplePages, pageInfoEmpty, pageInfoOnePage, -} from '~/organizations/mock_data'; +} from 'jest/organizations/mock_data'; const MOCK_DELETE_PARAMS = { testParam: true, @@ -59,7 +59,7 @@ describe('GroupsView', () => { const groups = { nodes, - pageInfo, + pageInfo: pageInfoMultiplePages, }; const successHandler = jest.fn().mockResolvedValue({ @@ -203,24 +203,9 @@ describe('GroupsView', () => { describe('when there is a next page of groups', () => { const mockEndCursor = 'mockEndCursor'; - const handler = jest.fn().mockResolvedValue({ - data: { - organization: { - id: defaultProvide.organizationGid, - groups: { - nodes, - pageInfo: { - ...pageInfo, - hasNextPage: true, - hasPreviousPage: false, - }, - }, - }, - }, - }); beforeEach(async () => { - createComponent({ handler }); + createComponent(); await waitForPromises(); }); @@ -249,7 +234,7 @@ describe('GroupsView', () => { }); it('calls query with correct variables', () => { - expect(handler).toHaveBeenCalledWith({ + expect(successHandler).toHaveBeenCalledWith({ after: mockEndCursor, before: null, first: DEFAULT_PER_PAGE, @@ -264,24 +249,9 @@ describe('GroupsView', () => { describe('when there is a previous page of groups', () => { const mockStartCursor = 'mockStartCursor'; - const handler = jest.fn().mockResolvedValue({ - data: { - organization: { - id: defaultProvide.organizationGid, - groups: { - nodes, - pageInfo: { - ...pageInfo, - hasNextPage: false, - hasPreviousPage: true, - }, - }, - }, - }, - }); beforeEach(async () => { - createComponent({ handler }); + createComponent(); await waitForPromises(); }); @@ -312,7 +282,7 @@ describe('GroupsView', () => { }); it('calls query with correct variables', () => { - expect(handler).toHaveBeenCalledWith({ + expect(successHandler).toHaveBeenCalledWith({ after: null, before: mockStartCursor, first: null, diff --git a/spec/frontend/organizations/shared/components/list/organizations_list_item_spec.js b/spec/frontend/organizations/shared/components/list/organizations_list_item_spec.js index 9c9c7bd9d7b..dc1b2162781 100644 --- a/spec/frontend/organizations/shared/components/list/organizations_list_item_spec.js +++ b/spec/frontend/organizations/shared/components/list/organizations_list_item_spec.js @@ -1,16 +1,24 @@ import { GlAvatarLabeled } from '@gitlab/ui'; +import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import OrganizationsListItem from '~/organizations/shared/components/list/organizations_list_item.vue'; -import { organizations } from '~/organizations/mock_data'; - -const MOCK_ORGANIZATION = organizations[0]; describe('OrganizationsListItem', () => { let wrapper; + const { + data: { + currentUser: { + organizations: { + nodes: [organization], + }, + }, + }, + } = organizationsGraphQlResponse; + const defaultProps = { - organization: MOCK_ORGANIZATION, + organization, }; const createComponent = (props = {}) => { @@ -33,11 +41,11 @@ describe('OrganizationsListItem', () => { it('renders GlAvatarLabeled with correct data', () => { expect(findGlAvatarLabeled().attributes()).toMatchObject({ - 'entity-id': getIdFromGraphQLId(MOCK_ORGANIZATION.id).toString(), - 'entity-name': MOCK_ORGANIZATION.name, - src: MOCK_ORGANIZATION.avatarUrl, - label: MOCK_ORGANIZATION.name, - labellink: MOCK_ORGANIZATION.webUrl, + 'entity-id': getIdFromGraphQLId(organization.id).toString(), + 'entity-name': organization.name, + src: organization.avatarUrl, + label: organization.name, + labellink: organization.webUrl, }); }); }); @@ -47,7 +55,7 @@ describe('OrganizationsListItem', () => { describe('is a HTML description', () => { beforeEach(() => { - createComponent({ organization: { ...MOCK_ORGANIZATION, descriptionHtml } }); + createComponent({ organization: { ...organization, descriptionHtml } }); }); it('renders HTML description', () => { @@ -58,7 +66,7 @@ describe('OrganizationsListItem', () => { describe('is not a HTML description', () => { beforeEach(() => { createComponent({ - organization: { ...MOCK_ORGANIZATION, descriptionHtml: null }, + organization: { ...organization, descriptionHtml: null }, }); }); diff --git a/spec/frontend/organizations/shared/components/list/organizations_list_spec.js b/spec/frontend/organizations/shared/components/list/organizations_list_spec.js index 612848a1212..a80c219d2be 100644 --- a/spec/frontend/organizations/shared/components/list/organizations_list_spec.js +++ b/spec/frontend/organizations/shared/components/list/organizations_list_spec.js @@ -1,20 +1,24 @@ import { GlKeysetPagination } from '@gitlab/ui'; import { omit } from 'lodash'; import { shallowMount } from '@vue/test-utils'; +import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json'; import OrganizationsList from '~/organizations/shared/components/list/organizations_list.vue'; import OrganizationsListItem from '~/organizations/shared/components/list/organizations_list_item.vue'; -import { organizations as nodes, pageInfo, pageInfoOnePage } from '~/organizations/mock_data'; +import { pageInfoMultiplePages, pageInfoOnePage } from 'jest/organizations/mock_data'; describe('OrganizationsList', () => { let wrapper; + const { + data: { + currentUser: { organizations }, + }, + } = organizationsGraphQlResponse; + const createComponent = ({ propsData = {} } = {}) => { wrapper = shallowMount(OrganizationsList, { propsData: { - organizations: { - nodes, - pageInfo, - }, + organizations, ...propsData, }, }); @@ -27,7 +31,7 @@ describe('OrganizationsList', () => { it('renders a list item for each organization', () => { createComponent(); - expect(findAllOrganizationsListItem()).toHaveLength(nodes.length); + expect(findAllOrganizationsListItem()).toHaveLength(organizations.nodes.length); }); describe('when there is one page of organizations', () => { @@ -35,7 +39,7 @@ describe('OrganizationsList', () => { createComponent({ propsData: { organizations: { - nodes, + ...organizations, pageInfo: pageInfoOnePage, }, }, @@ -49,11 +53,18 @@ describe('OrganizationsList', () => { describe('when there are multiple pages of organizations', () => { beforeEach(() => { - createComponent(); + createComponent({ + propsData: { + organizations: { + ...organizations, + pageInfo: pageInfoMultiplePages, + }, + }, + }); }); it('renders pagination', () => { - expect(findPagination().props()).toMatchObject(omit(pageInfo, '__typename')); + expect(findPagination().props()).toMatchObject(omit(pageInfoMultiplePages, '__typename')); }); describe('when `GlKeysetPagination` emits `next` event', () => { diff --git a/spec/frontend/organizations/shared/components/organizations_view_spec.js b/spec/frontend/organizations/shared/components/organizations_view_spec.js index 270998eba1f..c6f66d371d6 100644 --- a/spec/frontend/organizations/shared/components/organizations_view_spec.js +++ b/spec/frontend/organizations/shared/components/organizations_view_spec.js @@ -1,6 +1,6 @@ import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { organizations } from '~/organizations/mock_data'; +import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json'; import OrganizationsView from '~/organizations/shared/components/organizations_view.vue'; import OrganizationsList from '~/organizations/shared/components/list/organizations_list.vue'; import { MOCK_NEW_ORG_URL, MOCK_ORG_EMPTY_STATE_SVG } from '../mock_data'; @@ -8,6 +8,14 @@ import { MOCK_NEW_ORG_URL, MOCK_ORG_EMPTY_STATE_SVG } from '../mock_data'; describe('OrganizationsView', () => { let wrapper; + const { + data: { + currentUser: { + organizations: { nodes: organizations }, + }, + }, + } = organizationsGraphQlResponse; + const createComponent = (props = {}) => { wrapper = shallowMount(OrganizationsView, { propsData: { diff --git a/spec/frontend/organizations/shared/components/projects_view_spec.js b/spec/frontend/organizations/shared/components/projects_view_spec.js index 76bf9a81825..15c430e05d8 100644 --- a/spec/frontend/organizations/shared/components/projects_view_spec.js +++ b/spec/frontend/organizations/shared/components/projects_view_spec.js @@ -19,12 +19,12 @@ import { DEFAULT_PER_PAGE } from '~/api'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; +import { organizationProjects as nodes } from '~/organizations/mock_data'; import { - organizationProjects as nodes, - pageInfo, + pageInfoMultiplePages, pageInfoEmpty, pageInfoOnePage, -} from '~/organizations/mock_data'; +} from 'jest/organizations/mock_data'; jest.mock('~/alert'); jest.mock('~/api/projects_api'); @@ -60,7 +60,7 @@ describe('ProjectsView', () => { const projects = { nodes, - pageInfo, + pageInfo: pageInfoMultiplePages, }; const successHandler = jest.fn().mockResolvedValue({ @@ -206,24 +206,9 @@ describe('ProjectsView', () => { describe('when there is a next page of projects', () => { const mockEndCursor = 'mockEndCursor'; - const handler = jest.fn().mockResolvedValue({ - data: { - organization: { - id: defaultProvide.organizationGid, - projects: { - nodes, - pageInfo: { - ...pageInfo, - hasNextPage: true, - hasPreviousPage: false, - }, - }, - }, - }, - }); beforeEach(async () => { - createComponent({ handler }); + createComponent(); await waitForPromises(); }); @@ -253,7 +238,7 @@ describe('ProjectsView', () => { }); it('calls query with correct variables', () => { - expect(handler).toHaveBeenCalledWith({ + expect(successHandler).toHaveBeenCalledWith({ after: mockEndCursor, before: null, first: DEFAULT_PER_PAGE, @@ -268,24 +253,9 @@ describe('ProjectsView', () => { describe('when there is a previous page of projects', () => { const mockStartCursor = 'mockStartCursor'; - const handler = jest.fn().mockResolvedValue({ - data: { - organization: { - id: defaultProvide.organizationGid, - projects: { - nodes, - pageInfo: { - ...pageInfo, - hasNextPage: false, - hasPreviousPage: true, - }, - }, - }, - }, - }); beforeEach(async () => { - createComponent({ handler }); + createComponent(); await waitForPromises(); }); @@ -315,7 +285,7 @@ describe('ProjectsView', () => { }); it('calls query with correct variables', () => { - expect(handler).toHaveBeenCalledWith({ + expect(successHandler).toHaveBeenCalledWith({ after: null, before: mockStartCursor, first: null, diff --git a/spec/frontend/profile/preferences/components/profile_preferences_spec.js b/spec/frontend/profile/preferences/components/profile_preferences_spec.js index 67f766ad116..cd3ab06e4bc 100644 --- a/spec/frontend/profile/preferences/components/profile_preferences_spec.js +++ b/spec/frontend/profile/preferences/components/profile_preferences_spec.js @@ -19,6 +19,7 @@ import { colorModes, lightColorModeId, darkColorModeId, + autoColorModeId, themes, themeId1, } from '../mock_data'; @@ -247,6 +248,20 @@ describe('ProfilePreferences component', () => { expect(window.location.reload).toHaveBeenCalledTimes(1); }); + + it('reloads the page when switching from auto to light mode', async () => { + selectColorModeId(autoColorModeId); + setupWrapper(); + + selectColorModeId(lightColorModeId); + dispatchBeforeSendEvent(); + await nextTick(); + + dispatchSuccessEvent(); + await nextTick(); + + expect(window.location.reload).toHaveBeenCalledTimes(1); + }); }); describe('with extensions marketplace integration view', () => { diff --git a/spec/frontend/profile/preferences/mock_data.js b/spec/frontend/profile/preferences/mock_data.js index 2eabba49243..98a28df363f 100644 --- a/spec/frontend/profile/preferences/mock_data.js +++ b/spec/frontend/profile/preferences/mock_data.js @@ -21,10 +21,12 @@ export const bodyClasses = 'ui-light-indigo ui-light gl-dark'; export const lightColorModeId = 1; export const darkColorModeId = 2; +export const autoColorModeId = 3; export const colorModes = [ { id: lightColorModeId, css_class: 'gl-light' }, { id: darkColorModeId, css_class: 'gl-dark' }, + { id: autoColorModeId, css_class: 'gl-system' }, ]; export const themes = [ diff --git a/spec/frontend/super_sidebar/components/organization_switcher_spec.js b/spec/frontend/super_sidebar/components/organization_switcher_spec.js index 921bc2c7644..0fe2e852e95 100644 --- a/spec/frontend/super_sidebar/components/organization_switcher_spec.js +++ b/spec/frontend/super_sidebar/components/organization_switcher_spec.js @@ -2,14 +2,11 @@ import { GlAvatar, GlDisclosureDropdown, GlLoadingIcon, GlLink } from '@gitlab/u import VueApollo from 'vue-apollo'; import Vue from 'vue'; +import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import OrganizationSwitcher from '~/super_sidebar/components/organization_switcher.vue'; -import { - defaultOrganization as currentOrganization, - organizations as nodes, - pageInfo, - pageInfoEmpty, -} from '~/organizations/mock_data'; +import { defaultOrganization as currentOrganization } from '~/organizations/mock_data'; +import { pageInfoEmpty } from 'jest/organizations/mock_data'; import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { helpPagePath } from '~/helpers/help_page_helper'; @@ -22,7 +19,15 @@ describe('OrganizationSwitcher', () => { let wrapper; let mockApollo; - const [, secondOrganization, thirdOrganization] = nodes; + const { + data: { + currentUser: { + organizations: { nodes, pageInfo }, + }, + }, + } = organizationsGraphQlResponse; + + const [firstOrganization, secondOrganization] = nodes; const organizations = { nodes, @@ -97,25 +102,25 @@ describe('OrganizationSwitcher', () => { await waitForPromises(); - expect(findDropdownItemByIndex(1).text()).toContain(secondOrganization.name); + expect(findDropdownItemByIndex(1).text()).toContain(firstOrganization.name); expect(findDropdownItemByIndex(1).element.firstChild.getAttribute('href')).toBe( - secondOrganization.webUrl, + firstOrganization.webUrl, ); expect(findDropdownItemByIndex(1).findComponent(GlAvatar).props()).toMatchObject({ + src: firstOrganization.avatarUrl, + entityId: getIdFromGraphQLId(firstOrganization.id), + entityName: firstOrganization.name, + }); + + expect(findDropdownItemByIndex(2).text()).toContain(secondOrganization.name); + expect(findDropdownItemByIndex(2).element.firstChild.getAttribute('href')).toBe( + secondOrganization.webUrl, + ); + expect(findDropdownItemByIndex(2).findComponent(GlAvatar).props()).toMatchObject({ src: secondOrganization.avatarUrl, entityId: getIdFromGraphQLId(secondOrganization.id), entityName: secondOrganization.name, }); - - expect(findDropdownItemByIndex(2).text()).toContain(thirdOrganization.name); - expect(findDropdownItemByIndex(2).element.firstChild.getAttribute('href')).toBe( - thirdOrganization.webUrl, - ); - expect(findDropdownItemByIndex(2).findComponent(GlAvatar).props()).toMatchObject({ - src: thirdOrganization.avatarUrl, - entityId: getIdFromGraphQLId(thirdOrganization.id), - entityName: thirdOrganization.name, - }); }); describe('when there are no organizations to switch to', () => { diff --git a/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js b/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js index b4f31bf7dc1..84db8c4172e 100644 --- a/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js +++ b/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js @@ -2,6 +2,7 @@ import VueApollo from 'vue-apollo'; import Vue from 'vue'; import { GlCollapsibleListbox, GlAlert } from '@gitlab/ui'; import { chunk } from 'lodash'; +import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import OrganizationSelect from '~/vue_shared/components/entity_select/organization_select.vue'; import EntitySelect from '~/vue_shared/components/entity_select/entity_select.vue'; @@ -14,7 +15,7 @@ import { } from '~/vue_shared/components/entity_select/constants'; import getCurrentUserOrganizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql'; import getOrganizationQuery from '~/organizations/shared/graphql/queries/organization.query.graphql'; -import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data'; +import { pageInfoMultiplePages, pageInfoEmpty } from 'jest/organizations/mock_data'; import waitForPromises from 'helpers/wait_for_promises'; import createMockApollo from 'helpers/mock_apollo_helper'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; @@ -26,6 +27,13 @@ describe('OrganizationSelect', () => { let mockApollo; // Mocks + const { + data: { + currentUser: { + organizations: { nodes, pageInfo }, + }, + }, + } = organizationsGraphQlResponse; const [organization] = nodes; const organizations = { nodes, @@ -147,7 +155,7 @@ describe('OrganizationSelect', () => { currentUser: { id: 'gid://gitlab/User/1', __typename: 'CurrentUser', - organizations: { nodes: firstPage, pageInfo }, + organizations: { nodes: firstPage, pageInfo: pageInfoMultiplePages }, }, }, }) @@ -178,7 +186,7 @@ describe('OrganizationSelect', () => { it('calls graphQL query correct `after` variable', () => { expect(getCurrentUserOrganizationsQueryMultiplePagesHandler).toHaveBeenCalledWith({ search: '', - after: pageInfo.endCursor, + after: pageInfoMultiplePages.endCursor, first: DEFAULT_PER_PAGE, }); expect(findListbox().props('infiniteScroll')).toBe(false); diff --git a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb index f5845e492bc..221d116eabf 100644 --- a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb +++ b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb @@ -54,7 +54,6 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: : variables: bridge_variables, forward_yaml_variables?: true, forward_pipeline_variables?: true, - expand_file_refs?: false, yaml_variables: yaml_variables, pipeline_variables: pipeline_variables, pipeline_schedule_variables: pipeline_schedule_variables, @@ -125,39 +124,16 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: : [{ key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }] end - context 'when expand_file_refs is true' do - before do - allow(bridge).to receive(:expand_file_refs?).and_return(true) - end + it 'does not expand file variables and adds file variables' do + expected = [ + { key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' }, + { key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' }, + { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' }, + { key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' }, + { key: 'FILE_REF3', value: 'ref 3', variable_type: :file } + ] - it 'expands file variables' do - expected = [ - { key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' }, - { key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' }, - { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' }, - { key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' } - ] - - expect(generator.calculate).to contain_exactly(*expected) - end - end - - context 'when expand_file_refs is false' do - before do - allow(bridge).to receive(:expand_file_refs?).and_return(false) - end - - it 'does not expand file variables and adds file variables' do - expected = [ - { key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' }, - { key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' }, - { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' }, - { key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' }, - { key: 'FILE_REF3', value: 'ref 3', variable_type: :file } - ] - - expect(generator.calculate).to contain_exactly(*expected) - end + expect(generator.calculate).to contain_exactly(*expected) end end end diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb index 90568f4731e..b7d93bc3499 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb @@ -67,18 +67,4 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisMetric, :clean_git time_frame: 'all' } end - - context "with prefix disabled" do - let(:expected_value) { 3 } - - before do - 3.times do - Gitlab::UsageDataCounters::SearchCounter.count(:all_searches) - end - end - - it_behaves_like 'a correct instrumented metric value', { - options: { event: 'all_searches_count', prefix: nil, include_usage_prefix: false }, time_frame: 'all' - } - end end diff --git a/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb deleted file mode 100644 index 17188a75ccb..00000000000 --- a/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::UsageDataCounters::SearchCounter, :clean_gitlab_redis_shared_state do - shared_examples_for 'usage counter with totals' do |counter| - it 'increments counter and returns total count' do - expect(described_class.read(counter)).to eq(0) - - 2.times { described_class.count(counter) } - - expect(described_class.read(counter)).to eq(2) - end - end - - context 'all_searches counter' do - it_behaves_like 'usage counter with totals', :all_searches - end - - context 'navbar_searches counter' do - it_behaves_like 'usage counter with totals', :navbar_searches - end - - describe '.fetch_supported_event' do - subject { described_class.fetch_supported_event(event_name) } - - let(:event_name) { 'all_searches' } - - it { is_expected.to eq 'all_searches' } - end -end diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 202286645b1..409b5b8804b 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -343,16 +343,6 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do expect(bridge.downstream_variables).to contain_exactly(*expected_vars) end - - context 'and feature flag is disabled' do - before do - stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false) - end - - it 'expands the file variable' do - expect(bridge.downstream_variables).to contain_exactly({ key: 'EXPANDED_FILE', value: 'test-file-value' }) - end - end end context 'when recursive interpolation has been used' do @@ -455,21 +445,6 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do expect(bridge.downstream_variables).to contain_exactly(*expected_vars) end - - context 'and feature flag is disabled' do - before do - stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false) - end - - it 'expands the file variable' do - expected_vars = [ - { key: 'FILE_VAR', value: 'project file' }, - { key: 'YAML_VAR', value: 'project file' } - ] - - expect(bridge.downstream_variables).to contain_exactly(*expected_vars) - end - end end context 'when the pipeline runs from a pipeline schedule' do @@ -537,16 +512,6 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do expect(bridge.downstream_variables).to contain_exactly(*expected_vars) end - - context 'and feature flag is disabled' do - before do - stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false) - end - - it 'expands the file variable' do - expect(bridge.downstream_variables).to contain_exactly({ key: 'schedule_var_key', value: 'project file' }) - end - end end context 'when using raw variables' do diff --git a/spec/models/packages/composer/cache_file_spec.rb b/spec/models/packages/composer/cache_file_spec.rb index a03b89ca2f5..745bc1f5c19 100644 --- a/spec/models/packages/composer/cache_file_spec.rb +++ b/spec/models/packages/composer/cache_file_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Composer::CacheFile, type: :model do +RSpec.describe Packages::Composer::CacheFile, type: :model, feature_category: :package_registry do describe 'relationships' do it { is_expected.to belong_to(:group) } it { is_expected.to belong_to(:namespace) } diff --git a/spec/models/packages/debian/group_component_file_spec.rb b/spec/models/packages/debian/group_component_file_spec.rb index bf33ca138c3..63f57bc3710 100644 --- a/spec/models/packages/debian/group_component_file_spec.rb +++ b/spec/models/packages/debian/group_component_file_spec.rb @@ -2,6 +2,6 @@ require 'spec_helper' -RSpec.describe Packages::Debian::GroupComponentFile do +RSpec.describe Packages::Debian::GroupComponentFile, feature_category: :package_registry do it_behaves_like 'Debian Component File', :group, false end diff --git a/spec/models/packages/debian/group_component_spec.rb b/spec/models/packages/debian/group_component_spec.rb index f288ebbe5df..c17c7bdabad 100644 --- a/spec/models/packages/debian/group_component_spec.rb +++ b/spec/models/packages/debian/group_component_spec.rb @@ -2,6 +2,6 @@ require 'spec_helper' -RSpec.describe Packages::Debian::GroupComponent do +RSpec.describe Packages::Debian::GroupComponent, feature_category: :package_registry do it_behaves_like 'Debian Distribution Component', :debian_group_component, :group, false end diff --git a/spec/models/packages/debian/project_component_file_spec.rb b/spec/models/packages/debian/project_component_file_spec.rb index 5dfc47c14c0..1e2110c4bdc 100644 --- a/spec/models/packages/debian/project_component_file_spec.rb +++ b/spec/models/packages/debian/project_component_file_spec.rb @@ -2,6 +2,6 @@ require 'spec_helper' -RSpec.describe Packages::Debian::ProjectComponentFile do +RSpec.describe Packages::Debian::ProjectComponentFile, feature_category: :package_registry do it_behaves_like 'Debian Component File', :project, true end diff --git a/spec/models/packages/debian/project_component_spec.rb b/spec/models/packages/debian/project_component_spec.rb index 4b041068b8d..cc462c1b52e 100644 --- a/spec/models/packages/debian/project_component_spec.rb +++ b/spec/models/packages/debian/project_component_spec.rb @@ -2,6 +2,6 @@ require 'spec_helper' -RSpec.describe Packages::Debian::ProjectComponent do +RSpec.describe Packages::Debian::ProjectComponent, feature_category: :package_registry do it_behaves_like 'Debian Distribution Component', :debian_project_component, :project, true end diff --git a/spec/models/packages/nuget/symbol_spec.rb b/spec/models/packages/nuget/symbol_spec.rb index 42a873e140f..f4b4dd95230 100644 --- a/spec/models/packages/nuget/symbol_spec.rb +++ b/spec/models/packages/nuget/symbol_spec.rb @@ -25,6 +25,7 @@ RSpec.describe Packages::Nuget::Symbol, type: :model, feature_category: :package describe 'delegations' do it { is_expected.to delegate_method(:project_id).to(:package) } + it { is_expected.to delegate_method(:project).to(:package) } end describe 'scopes' do diff --git a/spec/models/packages/package_file_spec.rb b/spec/models/packages/package_file_spec.rb index 0732a50c8e8..e7b875c34fd 100644 --- a/spec/models/packages/package_file_spec.rb +++ b/spec/models/packages/package_file_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::PackageFile, type: :model do +RSpec.describe Packages::PackageFile, type: :model, feature_category: :package_registry do using RSpec::Parameterized::TableSyntax let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index db02d93bb5b..795d16ce9d3 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -19,14 +19,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it { expect(json_response.size).to eq(size) } end - shared_examples 'ping counters' do |scope:, search: ''| - it 'increases usage ping searches counter' do - expect(Gitlab::UsageDataCounters::SearchCounter).to receive(:count).with(:all_searches) - - get api(endpoint, user), params: { scope: scope, search: search } - end - end - shared_examples 'apdex recorded' do |scope:, level:, search: ''| it 'increments the custom search sli apdex' do expect(Gitlab::Metrics::GlobalSearchSlis).to receive(:record_apdex).with( @@ -190,6 +182,21 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: end context 'with correct params' do + [:issues, :merge_requests, :projects, :milestones, :users, :snippet_titles].each do |scope| + context "with correct params for scope #{scope}" do + it_behaves_like 'internal event tracking' do + let(:event) { 'perform_search' } + let(:category) { described_class.to_s } + let(:project) { nil } + let(:namespace) { nil } + + subject(:tracked_event) do + get api(endpoint, user), params: { scope: scope, search: 'foobar' } + end + end + end + end + context 'for projects scope' do before do get api(endpoint, user), params: { scope: 'projects', search: 'awesome' } @@ -199,8 +206,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'pagination', scope: :projects - it_behaves_like 'ping counters', scope: :projects - it_behaves_like 'apdex recorded', scope: 'projects', level: 'global' end @@ -214,8 +219,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/issues' - it_behaves_like 'ping counters', scope: :issues - it_behaves_like 'apdex recorded', scope: 'issues', level: 'global' it_behaves_like 'issues orderable by created_at' @@ -278,8 +281,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests' - it_behaves_like 'ping counters', scope: :merge_requests - it_behaves_like 'apdex recorded', scope: 'merge_requests', level: 'global' it_behaves_like 'merge_requests orderable by created_at' @@ -325,8 +326,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' - it_behaves_like 'ping counters', scope: :milestones - it_behaves_like 'apdex recorded', scope: 'milestones', level: 'global' describe 'pagination' do @@ -365,8 +364,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'pagination', scope: :users - it_behaves_like 'ping counters', scope: :users - it_behaves_like 'apdex recorded', scope: 'users', level: 'global' end @@ -379,8 +376,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/snippets' - it_behaves_like 'ping counters', scope: :snippet_titles - it_behaves_like 'apdex recorded', scope: 'snippet_titles', level: 'global' describe 'pagination' do @@ -545,8 +540,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'pagination', scope: :projects - it_behaves_like 'ping counters', scope: :projects - it_behaves_like 'apdex recorded', scope: 'projects', level: 'group' end @@ -559,8 +552,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/issues' - it_behaves_like 'ping counters', scope: :issues - it_behaves_like 'apdex recorded', scope: 'issues', level: 'group' it_behaves_like 'issues orderable by created_at' @@ -583,8 +574,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests' - it_behaves_like 'ping counters', scope: :merge_requests - it_behaves_like 'apdex recorded', scope: 'merge_requests', level: 'group' it_behaves_like 'merge_requests orderable by created_at' @@ -607,8 +596,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' - it_behaves_like 'ping counters', scope: :milestones - it_behaves_like 'apdex recorded', scope: 'milestones', level: 'group' describe 'pagination' do @@ -642,8 +629,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics' - it_behaves_like 'ping counters', scope: :users - it_behaves_like 'apdex recorded', scope: 'users', level: 'group' describe 'pagination' do @@ -756,8 +741,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/issues' - it_behaves_like 'ping counters', scope: :issues - it_behaves_like 'issues orderable by created_at' it_behaves_like 'apdex recorded', scope: 'issues', level: 'project' @@ -790,8 +773,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests' - it_behaves_like 'ping counters', scope: :merge_requests - it_behaves_like 'merge_requests orderable by created_at' it_behaves_like 'apdex recorded', scope: 'merge_requests', level: 'project' @@ -817,8 +798,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' - it_behaves_like 'ping counters', scope: :milestones - it_behaves_like 'apdex recorded', scope: 'milestones', level: 'project' describe 'pagination' do @@ -856,8 +835,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics' - it_behaves_like 'ping counters', scope: :users - it_behaves_like 'apdex recorded', scope: 'users', level: 'project' describe 'pagination' do @@ -878,8 +855,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/notes' - it_behaves_like 'ping counters', scope: :notes - it_behaves_like 'apdex recorded', scope: 'notes', level: 'project' describe 'pagination' do @@ -903,8 +878,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'response is correct', schema: 'public_api/v4/wiki_blobs' - it_behaves_like 'ping counters', scope: :wiki_blobs - it_behaves_like 'apdex recorded', scope: 'wiki_blobs', level: 'project' describe 'pagination' do @@ -927,8 +900,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'pagination', scope: :commits, search: 'merge' - it_behaves_like 'ping counters', scope: :commits - it_behaves_like 'apdex recorded', scope: 'commits', level: 'project' describe 'pipeline visibility' do @@ -1043,8 +1014,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category: it_behaves_like 'pagination', scope: :blobs, search: 'monitors' - it_behaves_like 'ping counters', scope: :blobs - it_behaves_like 'apdex recorded', scope: 'blobs', level: 'project' context 'filters' do diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index fa4d5b37b8d..c12d0c5bbc1 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -6617,7 +6617,6 @@ - './spec/lib/gitlab/usage_data_counters/package_event_counter_spec.rb' - './spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb' - './spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb' -- './spec/lib/gitlab/usage_data_counters/search_counter_spec.rb' - './spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb' - './spec/lib/gitlab/usage_data_counters_spec.rb' - './spec/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter_spec.rb' diff --git a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb index c2c123277ee..2e8dbd09a47 100644 --- a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb +++ b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb @@ -49,6 +49,10 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| end end + describe 'delegations' do + it { is_expected.to delegate_method(container_type).to(:component) } + end + describe 'validations' do describe "#component" do before do diff --git a/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb index 635d45f40e5..2b4fe943b07 100644 --- a/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb +++ b/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb @@ -14,6 +14,10 @@ RSpec.shared_examples 'Debian Distribution Component' do |factory, container, ca it { is_expected.to have_many(:files).class_name("Packages::Debian::#{container.capitalize}ComponentFile").inverse_of(:component) } end + describe 'delegations' do + it { is_expected.to delegate_method(container).to(:distribution) } + end + describe 'validations' do describe "#distribution" do it { is_expected.to validate_presence_of(:distribution) } diff --git a/spec/support/shared_examples/uploaders/gcs_signed_url_metadata_shared_examples.rb b/spec/support/shared_examples/uploaders/gcs_signed_url_metadata_shared_examples.rb new file mode 100644 index 00000000000..10333dfc406 --- /dev/null +++ b/spec/support/shared_examples/uploaders/gcs_signed_url_metadata_shared_examples.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'augmenting GCS signed URL with metadata' do + let(:project) { uploader.model.try(:project) } + let(:root_namespace) { project&.root_namespace || uploader.model.try(:group).root_ancestor } + let(:size) { uploader.model.try(:size) || uploader.model.file.size } + let(:has_project?) { true } + let(:connection) do + { + provider: 'Google', + google_storage_access_key_id: 'test-access-id', + google_storage_secret_access_key: 'secret' + } + end + + subject { uploader.model.file.url } + + context 'when the fog provider is not Google' do + it { is_expected.not_to include('x-goog-custom-audit-gitlab-') } + end + + context 'when the fog provider is Google' do + before do + stub_object_storage_uploader( + config: Gitlab.config.packages.object_store.merge(connection: connection), + uploader: described_class + ) + end + + context 'when on GitLab.com', :saas do + it do + is_expected.to include( + "x-goog-custom-audit-gitlab-namespace=#{root_namespace.id}", + "x-goog-custom-audit-gitlab-size-bytes=#{size}" + ) + end + + it { is_expected.to include("x-goog-custom-audit-gitlab-project=#{project.id}") if has_project? } + + context 'when an error occurs' do + before do + allow(uploader.model).to receive(:project).and_raise(StandardError) + allow(::Gitlab::ErrorTracking).to receive(:track_exception) + end + + it { expect { subject }.not_to raise_error } + it { is_expected.not_to include('x-goog-custom-audit-gitlab-') } + + it 'tracks the error' do + subject + + expect(::Gitlab::ErrorTracking).to have_received(:track_exception).with( + an_instance_of(StandardError), + model_class: uploader.model.class.name, + model_id: uploader.model.id + ) + end + end + end + + context 'when not on GitLab.com' do + it { is_expected.not_to include('x-goog-custom-audit-gitlab-') } + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(augment_gcs_signed_url_with_metadata: false) + end + + it { is_expected.not_to include('x-goog-custom-audit-gitlab-') } + end + end +end diff --git a/spec/uploaders/packages/composer/cache_uploader_spec.rb b/spec/uploaders/packages/composer/cache_uploader_spec.rb index 56e8b28ef36..733889ed8e5 100644 --- a/spec/uploaders/packages/composer/cache_uploader_spec.rb +++ b/spec/uploaders/packages/composer/cache_uploader_spec.rb @@ -1,13 +1,15 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Composer::CacheUploader do - let(:cache_file) { create(:composer_cache_file) } # rubocop:disable Rails/SaveBang +RSpec.describe Packages::Composer::CacheUploader, feature_category: :package_registry do + let_it_be(:cache_file) { create(:composer_cache_file) } let(:uploader) { described_class.new(cache_file, :file) } let(:path) { Gitlab.config.packages.storage_path } subject { uploader } + it { is_expected.to include_module(Packages::GcsSignedUrlMetadata) } + it_behaves_like "builds correct paths", store_dir: %r[^\h{2}/\h{2}/\h{64}/packages/composer_cache/\d+$], cache_dir: %r{/packages/tmp/cache}, @@ -33,11 +35,13 @@ RSpec.describe Packages::Composer::CacheUploader do end it 'can store file remotely' do - cache_file - expect(cache_file.file_store).to eq(described_class::Store::REMOTE) expect(cache_file.file.path).not_to be_blank end + + it_behaves_like 'augmenting GCS signed URL with metadata' do + let(:has_project?) { false } + end end end end diff --git a/spec/uploaders/packages/debian/component_file_uploader_spec.rb b/spec/uploaders/packages/debian/component_file_uploader_spec.rb index ffc5d8085fa..cf63b23e590 100644 --- a/spec/uploaders/packages/debian/component_file_uploader_spec.rb +++ b/spec/uploaders/packages/debian/component_file_uploader_spec.rb @@ -1,16 +1,18 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Debian::ComponentFileUploader do +RSpec.describe Packages::Debian::ComponentFileUploader, feature_category: :package_registry do [:project, :group].each do |container_type| context "Packages::Debian::#{container_type.capitalize}ComponentFile" do - let(:factory) { "debian_#{container_type}_component_file" } - let(:component_file) { create(factory) } # rubocop:disable Rails/SaveBang + let_it_be(:factory) { "debian_#{container_type}_component_file" } + let_it_be(:component_file) { create(:"#{factory}") } let(:uploader) { described_class.new(component_file, :file) } let(:path) { Gitlab.config.packages.storage_path } subject { uploader } + it { is_expected.to include_module(Packages::GcsSignedUrlMetadata) } + it_behaves_like "builds correct paths", store_dir: %r[^\h{2}/\h{2}/\h{64}/debian_#{container_type}_component_file/\d+$], cache_dir: %r{/packages/tmp/cache$}, @@ -38,11 +40,13 @@ RSpec.describe Packages::Debian::ComponentFileUploader do end it 'can store file remotely' do - component_file - expect(component_file.file_store).to eq(described_class::Store::REMOTE) expect(component_file.file.path).not_to be_blank end + + it_behaves_like 'augmenting GCS signed URL with metadata' do + let(:has_project?) { container_type == :project } + end end end end diff --git a/spec/uploaders/packages/debian/distribution_release_file_uploader_spec.rb b/spec/uploaders/packages/debian/distribution_release_file_uploader_spec.rb index 2086ab5966c..885b4b7ba4f 100644 --- a/spec/uploaders/packages/debian/distribution_release_file_uploader_spec.rb +++ b/spec/uploaders/packages/debian/distribution_release_file_uploader_spec.rb @@ -1,16 +1,18 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Debian::DistributionReleaseFileUploader do +RSpec.describe Packages::Debian::DistributionReleaseFileUploader, feature_category: :package_registry do [:project, :group].each do |container_type| context "Packages::Debian::#{container_type.capitalize}Distribution" do - let(:factory) { "debian_#{container_type}_distribution" } - let(:distribution) { create(factory, :with_file) } + let_it_be(:factory) { "debian_#{container_type}_distribution" } + let_it_be(:distribution) { create(factory, :with_file) } let(:uploader) { described_class.new(distribution, :file) } let(:path) { Gitlab.config.packages.storage_path } subject { uploader } + it { is_expected.to include_module(Packages::GcsSignedUrlMetadata) } + it_behaves_like "builds correct paths", store_dir: %r[^\h{2}/\h{2}/\h{64}/debian_#{container_type}_distribution/\d+$], cache_dir: %r{/packages/tmp/cache$}, @@ -38,11 +40,13 @@ RSpec.describe Packages::Debian::DistributionReleaseFileUploader do end it 'can store file remotely' do - distribution - expect(distribution.file_store).to eq(described_class::Store::REMOTE) expect(distribution.file.path).not_to be_blank end + + it_behaves_like 'augmenting GCS signed URL with metadata' do + let(:has_project?) { container_type == :project } + end end end diff --git a/spec/uploaders/packages/npm/metadata_cache_uploader_spec.rb b/spec/uploaders/packages/npm/metadata_cache_uploader_spec.rb index 0bcf05932a5..68b2a454dfc 100644 --- a/spec/uploaders/packages/npm/metadata_cache_uploader_spec.rb +++ b/spec/uploaders/packages/npm/metadata_cache_uploader_spec.rb @@ -5,8 +5,11 @@ require 'spec_helper' RSpec.describe Packages::Npm::MetadataCacheUploader, feature_category: :package_registry do let(:object_storage_key) { 'object/storage/key' } let(:npm_metadata_cache) { build_stubbed(:npm_metadata_cache, object_storage_key: object_storage_key) } + let(:uploader) { described_class.new(npm_metadata_cache, :file) } - subject { described_class.new(npm_metadata_cache, :file) } + subject { uploader } + + it { is_expected.to include_module(Packages::GcsSignedUrlMetadata) } describe '#filename' do it 'returns metadata.json' do @@ -31,4 +34,17 @@ RSpec.describe Packages::Npm::MetadataCacheUploader, feature_category: :package_ end end end + + context 'with object storage enabled' do + let(:npm_metadata_cache) { create(:npm_metadata_cache, :object_storage) } + + before do + stub_object_storage_uploader( + config: Gitlab.config.packages.object_store, + uploader: described_class + ) + end + + it_behaves_like 'augmenting GCS signed URL with metadata' + end end diff --git a/spec/uploaders/packages/nuget/symbol_uploader_spec.rb b/spec/uploaders/packages/nuget/symbol_uploader_spec.rb index 1a93871d94a..c3a01864b5d 100644 --- a/spec/uploaders/packages/nuget/symbol_uploader_spec.rb +++ b/spec/uploaders/packages/nuget/symbol_uploader_spec.rb @@ -6,8 +6,11 @@ RSpec.describe Packages::Nuget::SymbolUploader, feature_category: :package_regis let(:file_path) { 'file/Path.pdb' } let(:object_storage_key) { 'object/storage/key' } let(:symbol) { build_stubbed(:nuget_symbol, object_storage_key: object_storage_key, file_path: file_path) } + let(:uploader) { described_class.new(symbol, :symbol_file) } - subject { described_class.new(symbol, :file) } + subject { uploader } + + it { is_expected.to include_module(Packages::GcsSignedUrlMetadata) } describe '#store_dir' do it 'uses the object_storage_key' do @@ -26,4 +29,17 @@ RSpec.describe Packages::Nuget::SymbolUploader, feature_category: :package_regis end end end + + context 'with object storage enabled' do + let(:symbol) { create(:nuget_symbol, :object_storage) } + + before do + stub_object_storage_uploader( + config: Gitlab.config.packages.object_store, + uploader: described_class + ) + end + + it_behaves_like 'augmenting GCS signed URL with metadata' + end end diff --git a/spec/uploaders/packages/package_file_uploader_spec.rb b/spec/uploaders/packages/package_file_uploader_spec.rb index 36acb681669..31caae933e1 100644 --- a/spec/uploaders/packages/package_file_uploader_spec.rb +++ b/spec/uploaders/packages/package_file_uploader_spec.rb @@ -1,13 +1,15 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::PackageFileUploader do - let(:package_file) { create(:package_file) } +RSpec.describe Packages::PackageFileUploader, feature_category: :package_registry do + let_it_be(:package_file) { create(:package_file) } let(:uploader) { described_class.new(package_file, :file) } let(:path) { Gitlab.config.packages.storage_path } subject { uploader } + it { is_expected.to include_module(Packages::GcsSignedUrlMetadata) } + it_behaves_like "builds correct paths", store_dir: %r[^\h{2}/\h{2}/\h{64}/packages/\d+/files/\d+$], cache_dir: %r{/packages/tmp/cache}, @@ -33,11 +35,11 @@ RSpec.describe Packages::PackageFileUploader do end it 'can store file remotely' do - package_file - expect(package_file.file_store).to eq(described_class::Store::REMOTE) expect(package_file.file.path).not_to be_blank end + + it_behaves_like 'augmenting GCS signed URL with metadata' end end end diff --git a/spec/uploaders/packages/rpm/repository_file_uploader_spec.rb b/spec/uploaders/packages/rpm/repository_file_uploader_spec.rb index a36a035fde3..66f9cddec29 100644 --- a/spec/uploaders/packages/rpm/repository_file_uploader_spec.rb +++ b/spec/uploaders/packages/rpm/repository_file_uploader_spec.rb @@ -1,13 +1,15 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Rpm::RepositoryFileUploader do +RSpec.describe Packages::Rpm::RepositoryFileUploader, feature_category: :package_registry do let_it_be(:repository_file) { create(:rpm_repository_file) } let(:uploader) { described_class.new(repository_file, :file) } let(:path) { Gitlab.config.packages.storage_path } subject { uploader } + it { is_expected.to include_module(Packages::GcsSignedUrlMetadata) } + it_behaves_like 'builds correct paths', store_dir: %r[^\h{2}/\h{2}/\h{64}/projects/\d+/rpm/repository_files/\d+$], cache_dir: %r{/packages/tmp/cache}, @@ -33,11 +35,11 @@ RSpec.describe Packages::Rpm::RepositoryFileUploader do end it 'can store file remotely' do - repository_file - expect(repository_file.file_store).to eq(described_class::Store::REMOTE) expect(repository_file.file.path).not_to be_blank end + + it_behaves_like 'augmenting GCS signed URL with metadata' end end end