From 80b22a4413679216b470c7a4e9fefd0eb928add5 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 24 Nov 2023 18:09:57 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .rubocop_todo/capybara/testid_finders.yml | 22 -- .rubocop_todo/gitlab/namespaced_class.yml | 1 - .../layout/empty_line_after_magic_comment.yml | 1 - .rubocop_todo/rspec/context_wording.yml | 1 - .rubocop_todo/rspec/feature_category.yml | 2 - .rubocop_todo/rspec/named_subject.yml | 4 - .../style/inline_disable_annotation.yml | 1 - .../components/list/catalog_header.vue | 7 +- app/assets/javascripts/clone_panel.js | 2 +- .../folder/environments_folder_app.vue | 21 ++ .../folder/environments_folder_bundle.js | 27 ++- .../graphql/resolvers/kubernetes.js | 7 +- app/assets/javascripts/gfm_auto_complete.js | 35 +++ .../graphql/helpers/resolver_helpers.js | 6 +- .../components/notes_activity_header.vue | 4 +- .../search/sidebar/components/app.vue | 5 +- .../stylesheets/page_bundles/project.scss | 2 +- .../projects/environments_controller.rb | 4 + app/models/product_analytics_event.rb | 37 ---- app/models/project.rb | 4 - app/policies/user_policy.rb | 1 + .../build_activity_graph_service.rb | 13 -- .../product_analytics/build_graph_service.rb | 33 --- app/views/projects/_activity.html.haml | 4 +- .../{_clone.html.haml => _code.html.haml} | 16 +- .../projects/buttons/_download.html.haml | 20 +- .../buttons/_download_links.html.haml | 9 +- .../buttons/_download_menu_items.html.haml | 21 ++ app/views/projects/empty.html.haml | 6 +- .../projects/tree/_tree_header.html.haml | 10 +- app/views/shared/_clone_panel.html.haml | 4 +- .../shared/_mobile_clone_panel.html.haml | 4 +- .../development/ci_retry_on_exit_codes.yml | 8 + .../environments_folder_new_look.yml | 8 + config/initializers/circuitbox.rb | 8 +- .../27_product_analytics_events.rb | 56 ----- ...ake_profile_private_application_setting.rb | 9 + ...smtp_columns_from_service_desk_settings.rb | 32 +++ db/schema_migrations/20231029142649 | 1 + db/schema_migrations/20231122100006 | 1 + db/structure.sql | 10 +- doc/administration/logs/index.md | 2 +- doc/administration/monitoring/health_check.md | 2 +- doc/administration/monitoring/index.md | 2 +- .../performance/gitlab_configuration.md | 2 +- .../performance/grafana_configuration.md | 2 +- doc/administration/reply_by_email.md | 2 +- .../incident_management_rate_limits.md | 2 +- doc/api/alert_management_alerts.md | 2 +- doc/api/group_ssh_certificates.md | 3 +- doc/api/metrics_dashboard_annotations.md | 2 +- doc/api/metrics_user_starred_dashboards.md | 2 +- doc/development/distributed_tracing.md | 2 +- doc/development/logging.md | 2 +- doc/development/migration_style_guide.md | 7 +- doc/development/prometheus_metrics.md | 2 +- doc/development/runner_fleet_dashboard.md | 1 + doc/operations/incident_management/alerts.md | 2 +- .../escalation_policies.md | 2 +- .../incident_timeline_events.md | 2 +- .../incident_management/incidents.md | 2 +- doc/operations/incident_management/index.md | 2 +- .../incident_management/integrations.md | 2 +- .../incident_management/linked_resources.md | 2 +- .../incident_management/manage_incidents.md | 2 +- .../incident_management/oncall_schedules.md | 2 +- doc/operations/incident_management/paging.md | 2 +- doc/operations/incident_management/slack.md | 2 +- .../incident_management/status_page.md | 2 +- doc/operations/index.md | 2 +- doc/user/crm/index.md | 2 +- doc/user/group/ssh_certificates.md | 83 ++++++++ doc/user/project/service_desk/configure.md | 2 +- doc/user/project/service_desk/index.md | 2 +- .../service_desk/using_service_desk.md | 2 +- lib/gitlab/ci/config/entry/retry.rb | 14 +- lib/gitlab/circuit_breaker.rb | 36 ++++ lib/gitlab/circuit_breaker/notifier.rb | 28 +++ lib/gitlab/circuit_breaker/store.rb | 51 +++++ .../quick_actions/merge_request_actions.rb | 17 +- lib/product_analytics/event_params.rb | 56 ----- qa/qa/page/component/clone_panel.rb | 2 +- rubocop/rubocop-code_reuse.yml | 1 + spec/factories/environments.rb | 8 + spec/factories/product_analytics_event.rb | 24 --- spec/features/dashboard/projects_spec.rb | 4 +- spec/features/dashboard/todos/todos_spec.rb | 10 +- .../environments/environments_folder_spec.rb | 46 ++++ spec/features/groups/board_sidebar_spec.rb | 2 +- spec/features/groups/board_spec.rb | 2 +- spec/features/groups/clusters/user_spec.rb | 4 +- spec/features/groups/dependency_proxy_spec.rb | 2 +- spec/features/groups/group_settings_spec.rb | 8 +- .../groups/members/leave_group_spec.rb | 2 +- .../groups/members/manage_groups_spec.rb | 2 +- ...r_adds_member_with_expiration_date_spec.rb | 2 +- .../groups/members/search_members_spec.rb | 2 +- .../groups/members/sort_members_spec.rb | 2 +- spec/features/groups/members/tabs_spec.rb | 4 +- spec/features/groups/merge_requests_spec.rb | 2 +- .../environments/environments_spec.rb | 16 -- .../projects/user_sees_sidebar_spec.rb | 2 +- .../components/list/catalog_header_spec.js | 14 ++ .../folder/environments_folder_app_spec.js | 23 ++ spec/frontend/gfm_auto_complete_spec.js | 72 +++++-- .../search/sidebar/components/app_spec.js | 5 - spec/initializers/circuitbox_spec.rb | 7 +- spec/lib/gitlab/ci/config/entry/job_spec.rb | 89 +++++++- spec/lib/gitlab/ci/config/entry/retry_spec.rb | 86 +++++++- .../gitlab/circuit_breaker/notifier_spec.rb | 37 ++++ spec/lib/gitlab/circuit_breaker/store_spec.rb | 201 ++++++++++++++++++ spec/lib/gitlab/circuit_breaker_spec.rb | 120 +++++++++++ .../product_analytics/event_params_spec.rb | 59 ----- spec/models/product_analytics_event_spec.rb | 52 ----- .../build_activity_graph_service_spec.rb | 33 --- .../build_graph_service_spec.rb | 27 --- .../quick_actions/interpret_service_spec.rb | 30 +++ spec/support/rspec_order_todo.yml | 4 - .../projects/tags/index.html.haml_spec.rb | 5 +- 119 files changed, 1215 insertions(+), 619 deletions(-) create mode 100644 app/assets/javascripts/environments/folder/environments_folder_app.vue delete mode 100644 app/models/product_analytics_event.rb delete mode 100644 app/services/product_analytics/build_activity_graph_service.rb delete mode 100644 app/services/product_analytics/build_graph_service.rb rename app/views/projects/buttons/{_clone.html.haml => _code.html.haml} (83%) create mode 100644 app/views/projects/buttons/_download_menu_items.html.haml create mode 100644 config/feature_flags/development/ci_retry_on_exit_codes.yml create mode 100644 config/feature_flags/development/environments_folder_new_look.yml delete mode 100644 db/fixtures/development/27_product_analytics_events.rb create mode 100644 db/migrate/20231029142649_add_make_profile_private_application_setting.rb create mode 100644 db/post_migrate/20231122100006_remove_custom_email_smtp_columns_from_service_desk_settings.rb create mode 100644 db/schema_migrations/20231029142649 create mode 100644 db/schema_migrations/20231122100006 create mode 100644 doc/user/group/ssh_certificates.md create mode 100644 lib/gitlab/circuit_breaker.rb create mode 100644 lib/gitlab/circuit_breaker/notifier.rb create mode 100644 lib/gitlab/circuit_breaker/store.rb delete mode 100644 lib/product_analytics/event_params.rb delete mode 100644 spec/factories/product_analytics_event.rb create mode 100644 spec/features/environments/environments_folder_spec.rb create mode 100644 spec/frontend/environments/folder/environments_folder_app_spec.js create mode 100644 spec/lib/gitlab/circuit_breaker/notifier_spec.rb create mode 100644 spec/lib/gitlab/circuit_breaker/store_spec.rb create mode 100644 spec/lib/gitlab/circuit_breaker_spec.rb delete mode 100644 spec/lib/product_analytics/event_params_spec.rb delete mode 100644 spec/models/product_analytics_event_spec.rb delete mode 100644 spec/services/product_analytics/build_activity_graph_service_spec.rb delete mode 100644 spec/services/product_analytics/build_graph_service_spec.rb diff --git a/.rubocop_todo/capybara/testid_finders.yml b/.rubocop_todo/capybara/testid_finders.yml index f78470479e916..07bea3ea04f88 100644 --- a/.rubocop_todo/capybara/testid_finders.yml +++ b/.rubocop_todo/capybara/testid_finders.yml @@ -1,26 +1,11 @@ --- Capybara/TestidFinders: Exclude: - - 'spec/features/dashboard/projects_spec.rb' - - 'spec/features/dashboard/todos/todos_spec.rb' - - 'spec/features/groups/board_sidebar_spec.rb' - - 'spec/features/groups/board_spec.rb' - - 'spec/features/groups/clusters/user_spec.rb' - - 'spec/features/groups/dependency_proxy_spec.rb' - - 'spec/features/groups/group_settings_spec.rb' - - 'spec/features/groups/members/leave_group_spec.rb' - - 'spec/features/groups/members/manage_groups_spec.rb' - - 'spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb' - - 'spec/features/groups/members/search_members_spec.rb' - - 'spec/features/groups/members/sort_members_spec.rb' - - 'spec/features/groups/members/tabs_spec.rb' - - 'spec/features/groups/merge_requests_spec.rb' - 'spec/features/issues/issue_sidebar_spec.rb' - 'spec/features/issues/issue_state_spec.rb' - 'spec/features/issues/user_creates_issue_spec.rb' - 'spec/features/issues/user_edits_issue_spec.rb' - 'spec/features/issues/user_resets_their_incoming_email_token_spec.rb' - - 'spec/features/issues/user_sees_live_update_spec.rb' - 'spec/features/issues/user_sees_sidebar_updates_in_realtime_spec.rb' - 'spec/features/issues/user_toggles_subscription_spec.rb' - 'spec/features/labels_hierarchy_spec.rb' @@ -38,7 +23,6 @@ Capybara/TestidFinders: - 'spec/features/merge_request/user_merges_immediately_spec.rb' - 'spec/features/merge_request/user_posts_notes_spec.rb' - 'spec/features/merge_request/user_resolves_conflicts_spec.rb' - - 'spec/features/merge_request/user_reverts_merge_request_spec.rb' - 'spec/features/merge_request/user_sees_merge_widget_spec.rb' - 'spec/features/merge_request/user_sees_pipelines_spec.rb' - 'spec/features/merge_request/user_sees_suggest_pipeline_spec.rb' @@ -47,7 +31,6 @@ Capybara/TestidFinders: - 'spec/features/merge_request/user_toggles_whitespace_changes_spec.rb' - 'spec/features/merge_request/user_views_open_merge_request_spec.rb' - 'spec/features/milestone_spec.rb' - - 'spec/features/nav/new_nav_callout_spec.rb' - 'spec/features/nav/pinned_nav_items_spec.rb' - 'spec/features/populate_new_pipeline_vars_with_params_spec.rb' - 'spec/features/profile_spec.rb' @@ -61,7 +44,6 @@ Capybara/TestidFinders: - 'spec/features/profiles/user_edit_profile_spec.rb' - 'spec/features/profiles/user_updates_comment_template_spec.rb' - 'spec/features/project_group_variables_spec.rb' - - 'spec/features/project_variables_spec.rb' - 'spec/features/projects/blobs/blame_spec.rb' - 'spec/features/projects/branches/user_deletes_branch_spec.rb' - 'spec/features/projects/branches_spec.rb' @@ -76,7 +58,6 @@ Capybara/TestidFinders: - 'spec/features/projects/environments/environment_spec.rb' - 'spec/features/projects/environments/environments_spec.rb' - 'spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb' - - 'spec/features/projects/features_visibility_spec.rb' - 'spec/features/projects/fork_spec.rb' - 'spec/features/projects/integrations/user_activates_jira_spec.rb' - 'spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb' @@ -111,7 +92,6 @@ Capybara/TestidFinders: - 'spec/features/projects/terraform_spec.rb' - 'spec/features/projects/tree/create_directory_spec.rb' - 'spec/features/projects/tree/create_file_spec.rb' - - 'spec/features/projects/user_uses_shortcuts_spec.rb' - 'spec/features/projects/work_items/work_item_children_spec.rb' - 'spec/features/projects/work_items/work_item_spec.rb' - 'spec/features/protected_branches_spec.rb' @@ -121,10 +101,8 @@ Capybara/TestidFinders: - 'spec/features/search/user_searches_for_merge_requests_spec.rb' - 'spec/features/search/user_searches_for_milestones_spec.rb' - 'spec/features/search/user_searches_for_wiki_pages_spec.rb' - - 'spec/features/search/user_uses_header_search_field_spec.rb' - 'spec/features/search/user_uses_search_filters_spec.rb' - 'spec/features/tags/developer_deletes_tag_spec.rb' - 'spec/features/tags/maintainer_deletes_protected_tag_spec.rb' - 'spec/features/triggers_spec.rb' - - 'spec/features/uploads/user_uploads_avatar_to_profile_spec.rb' - 'spec/features/user_sees_revert_modal_spec.rb' diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml index 8b59571249c21..ea2e52b028a97 100644 --- a/.rubocop_todo/gitlab/namespaced_class.yml +++ b/.rubocop_todo/gitlab/namespaced_class.yml @@ -250,7 +250,6 @@ Gitlab/NamespacedClass: - 'app/models/plan.rb' - 'app/models/plan_limits.rb' - 'app/models/pool_repository.rb' - - 'app/models/product_analytics_event.rb' - 'app/models/programming_language.rb' - 'app/models/project.rb' - 'app/models/project_authorization.rb' diff --git a/.rubocop_todo/layout/empty_line_after_magic_comment.yml b/.rubocop_todo/layout/empty_line_after_magic_comment.yml index 6d996ff2f4ae9..b0ddd74796d09 100644 --- a/.rubocop_todo/layout/empty_line_after_magic_comment.yml +++ b/.rubocop_todo/layout/empty_line_after_magic_comment.yml @@ -581,7 +581,6 @@ Layout/EmptyLineAfterMagicComment: - 'spec/models/packages/rpm/repository_file_spec.rb' - 'spec/models/packages/rubygems/metadatum_spec.rb' - 'spec/models/packages/tag_spec.rb' - - 'spec/models/product_analytics_event_spec.rb' - 'spec/policies/design_management/design_policy_spec.rb' - 'spec/requests/api/composer_packages_spec.rb' - 'spec/requests/api/conan_project_packages_spec.rb' diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml index d7fb387b1a228..850672f4f6508 100644 --- a/.rubocop_todo/rspec/context_wording.yml +++ b/.rubocop_todo/rspec/context_wording.yml @@ -1994,7 +1994,6 @@ RSpec/ContextWording: - 'spec/lib/object_storage/direct_upload_spec.rb' - 'spec/lib/omni_auth/strategies/jwt_spec.rb' - 'spec/lib/peek/views/bullet_detailed_spec.rb' - - 'spec/lib/product_analytics/event_params_spec.rb' - 'spec/lib/safe_zip/entry_spec.rb' - 'spec/lib/security/ci_configuration/container_scanning_build_action_spec.rb' - 'spec/lib/security/ci_configuration/sast_build_action_spec.rb' diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml index 174fda7107fe5..b41381c9afda0 100644 --- a/.rubocop_todo/rspec/feature_category.yml +++ b/.rubocop_todo/rspec/feature_category.yml @@ -4223,7 +4223,6 @@ RSpec/FeatureCategory: - 'spec/lib/peek/views/external_http_spec.rb' - 'spec/lib/peek/views/memory_spec.rb' - 'spec/lib/peek/views/redis_detailed_spec.rb' - - 'spec/lib/product_analytics/event_params_spec.rb' - 'spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb' - 'spec/lib/prometheus/pid_provider_spec.rb' - 'spec/lib/quality/seeders/issues_spec.rb' @@ -4709,7 +4708,6 @@ RSpec/FeatureCategory: - 'spec/models/preloaders/project_root_ancestor_preloader_spec.rb' - 'spec/models/preloaders/user_max_access_level_in_groups_preloader_spec.rb' - 'spec/models/preloaders/user_max_access_level_in_projects_preloader_spec.rb' - - 'spec/models/product_analytics_event_spec.rb' - 'spec/models/programming_language_spec.rb' - 'spec/models/project_authorization_spec.rb' - 'spec/models/project_auto_devops_spec.rb' diff --git a/.rubocop_todo/rspec/named_subject.yml b/.rubocop_todo/rspec/named_subject.yml index 230c292834313..80bb6ee92880c 100644 --- a/.rubocop_todo/rspec/named_subject.yml +++ b/.rubocop_todo/rspec/named_subject.yml @@ -473,7 +473,6 @@ RSpec/NamedSubject: - 'ee/spec/lib/gitlab/llm/chat_message_spec.rb' - 'ee/spec/lib/gitlab/llm/chat_storage_spec.rb' - 'ee/spec/lib/gitlab/llm/completions/chat_spec.rb' - - 'ee/spec/lib/gitlab/llm/concerns/circuit_breaker_spec.rb' - 'ee/spec/lib/gitlab/llm/concerns/exponential_backoff_spec.rb' - 'ee/spec/lib/gitlab/llm/graphql_subscription_response_service_spec.rb' - 'ee/spec/lib/gitlab/llm/templates/categorize_question_spec.rb' @@ -2503,7 +2502,6 @@ RSpec/NamedSubject: - 'spec/lib/peek/views/external_http_spec.rb' - 'spec/lib/peek/views/memory_spec.rb' - 'spec/lib/peek/views/redis_detailed_spec.rb' - - 'spec/lib/product_analytics/event_params_spec.rb' - 'spec/lib/quality/seeders/issues_spec.rb' - 'spec/lib/release_highlights/validator/entry_spec.rb' - 'spec/lib/release_highlights/validator_spec.rb' @@ -3437,8 +3435,6 @@ RSpec/NamedSubject: - 'spec/services/personal_access_tokens/last_used_service_spec.rb' - 'spec/services/personal_access_tokens/revoke_service_spec.rb' - 'spec/services/post_receive_service_spec.rb' - - 'spec/services/product_analytics/build_activity_graph_service_spec.rb' - - 'spec/services/product_analytics/build_graph_service_spec.rb' - 'spec/services/projects/alerting/notify_service_spec.rb' - 'spec/services/projects/all_issues_count_service_spec.rb' - 'spec/services/projects/all_merge_requests_count_service_spec.rb' diff --git a/.rubocop_todo/style/inline_disable_annotation.yml b/.rubocop_todo/style/inline_disable_annotation.yml index e49171b0362e0..a9c02aa579645 100644 --- a/.rubocop_todo/style/inline_disable_annotation.yml +++ b/.rubocop_todo/style/inline_disable_annotation.yml @@ -2081,7 +2081,6 @@ Style/InlineDisableAnnotation: - 'ee/spec/lib/gitlab/import_export/project/relation_factory_spec.rb' - 'ee/spec/lib/gitlab/llm/chain/agents/zero_shot/executor_real_requests_spec.rb' - 'ee/spec/lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb' - - 'ee/spec/lib/gitlab/llm/concerns/circuit_breaker_spec.rb' - 'ee/spec/lib/gitlab/mirror_spec.rb' - 'ee/spec/lib/gitlab/patch/database_config_spec.rb' - 'ee/spec/lib/gitlab/sitemaps/sitemap_file_spec.rb' diff --git a/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue b/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue index 64229f5490418..3a9ec34178968 100644 --- a/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue +++ b/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue @@ -2,6 +2,7 @@ import { GlBanner, GlLink } from '@gitlab/ui'; import { __, s__ } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; +import BetaBadge from '~/vue_shared/components/badges/beta_badge.vue'; import { CATALOG_FEEDBACK_DISMISSED_KEY } from '../../constants'; const defaultTitle = __('CI/CD Catalog'); @@ -11,6 +12,7 @@ const defaultDescription = s__( export default { components: { + BetaBadge, GlBanner, GlLink, }, @@ -58,7 +60,10 @@ export default { {{ $options.i18n.banner.description }}

-

{{ pageTitle }}

+
+

{{ pageTitle }}

+ +

{{ pageDescription }} {{ diff --git a/app/assets/javascripts/clone_panel.js b/app/assets/javascripts/clone_panel.js index 79280c13f0f4c..d2a5d5a9db7c1 100644 --- a/app/assets/javascripts/clone_panel.js +++ b/app/assets/javascripts/clone_panel.js @@ -14,7 +14,7 @@ export default function initClonePanel() { $(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active'); } - $('a', $cloneOptions).on('click', (e) => { + $('.js-clone-links a', $cloneOptions).on('click', (e) => { const $this = $(e.currentTarget); const url = $this.attr('href'); if ( diff --git a/app/assets/javascripts/environments/folder/environments_folder_app.vue b/app/assets/javascripts/environments/folder/environments_folder_app.vue new file mode 100644 index 0000000000000..a963ca9b14405 --- /dev/null +++ b/app/assets/javascripts/environments/folder/environments_folder_app.vue @@ -0,0 +1,21 @@ + + diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js index 1a32de30de006..beaf8041c39ee 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js @@ -2,7 +2,8 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; import Translate from '~/vue_shared/translate'; -import EnvironmentsFolderApp from './environments_folder_view.vue'; +import EnvironmentsFolderView from './environments_folder_view.vue'; +import EnvironmentsFolderApp from './environments_folder_app.vue'; Vue.use(Translate); Vue.use(VueApollo); @@ -13,19 +14,35 @@ const apolloProvider = new VueApollo({ export default () => { const el = document.getElementById('environments-folder-list-view'); + const environmentsData = el.dataset; + if (gon.features.environmentsFolderNewLook) { + const folderName = environmentsData.environmentsDataFolderName; + + return new Vue({ + el, + components: { + EnvironmentsFolderApp, + }, + render(createElement) { + return createElement('environments-folder-app', { + props: { + folderName, + }, + }); + }, + }); + } return new Vue({ el, components: { - EnvironmentsFolderApp, + EnvironmentsFolderView, }, apolloProvider, provide: { projectPath: el.dataset.projectPath, }, data() { - const environmentsData = el.dataset; - return { endpoint: environmentsData.environmentsDataEndpoint, folderName: environmentsData.environmentsDataFolderName, @@ -33,7 +50,7 @@ export default () => { }; }, render(createElement) { - return createElement('environments-folder-app', { + return createElement('environments-folder-view', { props: { endpoint: this.endpoint, folderName: this.folderName, diff --git a/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js b/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js index 9111dc9c86cac..eab25298c3648 100644 --- a/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js +++ b/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js @@ -10,6 +10,7 @@ import produce from 'immer'; import { getK8sPods, handleClusterError, + buildWatchPath, } from '~/kubernetes_dashboard/graphql/helpers/resolver_helpers'; import { humanizeClusterErrors } from '../../helpers/k8s_integration_helper'; import k8sPodsQuery from '../queries/k8s_pods.query.graphql'; @@ -62,9 +63,7 @@ const mapWorkloadItems = (items, kind) => { const watchWorkloadItems = ({ kind, apiVersion, configuration, namespace, client }) => { const itemKind = kind.toLowerCase().replace('list', 's'); - const path = namespace - ? `/apis/${apiVersion}/namespaces/${namespace}/${itemKind}` - : `/apis/${apiVersion}/${itemKind}`; + const path = buildWatchPath({ resource: itemKind, api: `apis/${apiVersion}`, namespace }); const config = new Configuration(configuration); const watcherApi = new WatchApi(config); @@ -113,7 +112,7 @@ const mapServicesItems = (items) => { }; const watchServices = ({ configuration, namespace, client }) => { - const path = namespace ? `/api/v1/namespaces/${namespace}/services` : '/api/v1/services'; + const path = buildWatchPath({ resource: 'services', namespace }); const config = new Configuration(configuration); const watcherApi = new WatchApi(config); diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 39a8b1d0a9cd3..1babccff4253e 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -262,6 +262,38 @@ class GfmAutoComplete { }); } + // eslint-disable-next-line class-methods-use-this + setSubmitReviewStates($input) { + if (!window.gon.features?.mrRequestChanges) return; + + const REVIEW_STATES = { + reviewed: { + header: __('Comment'), + description: __('Submit general feedback without explicit approval.'), + }, + approve: { + header: __('Approve'), + description: __('Submit feedback and approve these changes.'), + }, + requested_changes: { + header: __('Request changes'), + description: __('Submit feedback that should be addressed before merging.'), + }, + }; + + $input.filter('[data-supports-quick-actions="true"]').atwho({ + // Always keep the trailing space otherwise the command won't display correctly + at: '/submit_review ', + alias: 'submit_review', + data: Object.keys(REVIEW_STATES), + displayTpl({ name }) { + const reviewState = REVIEW_STATES[name]; + + return `

  • ${reviewState.header}${reviewState.description}
  • `; + }, + }); + } + setupEmoji($input) { const fetchData = this.fetchData.bind(this); @@ -851,6 +883,9 @@ class GfmAutoComplete { } else if (dataSource) { AjaxCache.retrieve(dataSource, true) .then((data) => { + if (data.some((c) => c.name === 'submit_review')) { + this.setSubmitReviewStates($input); + } this.loadData($input, at, data); }) .catch(() => { diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js b/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js index 22ddd7c6a6100..4eec31cfe3725 100644 --- a/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js +++ b/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js @@ -9,8 +9,12 @@ export const handleClusterError = async (err) => { throw errorData; }; +export const buildWatchPath = ({ resource, api = 'api/v1', namespace = '' }) => { + return namespace ? `/${api}/namespaces/${namespace}/${resource}` : `/${api}/${resource}`; +}; + export const watchPods = ({ client, query, configuration, namespace }) => { - const path = namespace ? `/api/v1/namespaces/${namespace}/pods` : '/api/v1/pods'; + const path = buildWatchPath({ resource: 'pods', namespace }); const config = new Configuration(configuration); const watcherApi = new WatchApi(config); diff --git a/app/assets/javascripts/notes/components/notes_activity_header.vue b/app/assets/javascripts/notes/components/notes_activity_header.vue index 23b2ae74e41c1..be9c768ae6043 100644 --- a/app/assets/javascripts/notes/components/notes_activity_header.vue +++ b/app/assets/javascripts/notes/components/notes_activity_header.vue @@ -38,9 +38,7 @@ export default { }, computed: { showAiActions() { - return ( - this.resourceGlobalId && this.glFeatures.aiGlobalSwitch && this.glFeatures.summarizeNotes - ); + return this.resourceGlobalId && this.glFeatures.summarizeNotes; }, }, }; diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue index e0c49412d5654..307be0b0aa025 100644 --- a/app/assets/javascripts/search/sidebar/components/app.vue +++ b/app/assets/javascripts/search/sidebar/components/app.vue @@ -67,10 +67,7 @@ export default { return this.currentScope === SCOPE_MILESTONES; }, showWikiBlobsFilters() { - return ( - this.currentScope === SCOPE_WIKI_BLOBS && - this.glFeatures?.searchProjectWikisHideArchivedProjects - ); + return this.currentScope === SCOPE_WIKI_BLOBS; }, }, methods: { diff --git a/app/assets/stylesheets/page_bundles/project.scss b/app/assets/stylesheets/page_bundles/project.scss index 8d8da10268a6d..504f14051482d 100644 --- a/app/assets/stylesheets/page_bundles/project.scss +++ b/app/assets/stylesheets/page_bundles/project.scss @@ -53,7 +53,7 @@ } } - .project-clone-holder { + .project-code-holder { display: inline-block; margin: $gl-padding 0 0; diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 4b2749dc7169b..8cdd6efa7c565 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -14,6 +14,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController push_frontend_feature_flag(:k8s_watch_api, project) end + before_action only: [:folder] do + push_frontend_feature_flag(:environments_folder_new_look, project) + end + before_action :authorize_read_environment! before_action :authorize_create_environment!, only: [:new, :create] before_action :authorize_stop_environment!, only: [:stop] diff --git a/app/models/product_analytics_event.rb b/app/models/product_analytics_event.rb deleted file mode 100644 index 52baa3be6c448..0000000000000 --- a/app/models/product_analytics_event.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -class ProductAnalyticsEvent < ApplicationRecord - self.table_name = 'product_analytics_events_experimental' - - # Ignore that the partition key :project_id is part of the formal primary key - self.primary_key = :id - - belongs_to :project - - validates :event_id, :project_id, :v_collector, :v_etl, presence: true - - # There is no default Rails timestamps in the table. - # collector_tstamp is a timestamp when a collector recorded an event. - scope :order_by_time, -> { order(collector_tstamp: :desc) } - - # If we decide to change this scope to use date_trunc('day', collector_tstamp), - # we should remember that a btree index on collector_tstamp will be no longer effective. - scope :timerange, ->(duration, today = Time.zone.today) { - where('collector_tstamp BETWEEN ? AND ? ', today - duration + 1, today + 1) - } - - def self.count_by_graph(graph, days) - group(graph).timerange(days).count - end - - def self.count_collector_tstamp_by_day(days) - group("DATE_TRUNC('day', collector_tstamp)") - .reorder('date_trunc_day_collector_tstamp') - .timerange(days) - .count - end - - def as_json_wo_empty - as_json.compact - end -end diff --git a/app/models/project.rb b/app/models/project.rb index 57c127965f76c..5788885498cb0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -468,10 +468,6 @@ def self.integration_association_name(name) # rubocop:enable Cop/ActiveRecordDependent has_many :active_pages_deployments, -> { active }, class_name: 'PagesDeployment', inverse_of: :project - # Can be too many records. We need to implement delete_all in batches. - # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/228637 - has_many :product_analytics_events, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :operations_feature_flags, class_name: 'Operations::FeatureFlag' has_one :operations_feature_flags_client, class_name: 'Operations::FeatureFlagsClient' has_many :operations_feature_flags_user_lists, class_name: 'Operations::FeatureFlags::UserList' diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 04fbc8467c9ef..ccab3d9f02d5d 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -33,6 +33,7 @@ class UserPolicy < BasePolicy enable :read_saved_replies enable :read_user_email_address enable :admin_user_email_address + enable :make_profile_private end rule { default }.enable :read_user_profile diff --git a/app/services/product_analytics/build_activity_graph_service.rb b/app/services/product_analytics/build_activity_graph_service.rb deleted file mode 100644 index 63108d76afd99..0000000000000 --- a/app/services/product_analytics/build_activity_graph_service.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module ProductAnalytics - class BuildActivityGraphService < BuildGraphService - def execute - timerange = @params[:timerange].days - - results = product_analytics_events.count_collector_tstamp_by_day(timerange) - - format_results('collector_tstamp', results.transform_keys(&:to_date)) - end - end -end diff --git a/app/services/product_analytics/build_graph_service.rb b/app/services/product_analytics/build_graph_service.rb deleted file mode 100644 index da54ad4de0eb7..0000000000000 --- a/app/services/product_analytics/build_graph_service.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module ProductAnalytics - class BuildGraphService - def initialize(project, params) - @project = project - @params = params - end - - def execute - graph = @params[:graph].to_sym - timerange = @params[:timerange].days - - results = product_analytics_events.count_by_graph(graph, timerange) - - format_results(graph, results) - end - - private - - def format_results(name, results) - { - id: name, - keys: results.keys, - values: results.values - } - end - - def product_analytics_events - @project.product_analytics_events - end - end -end diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 00da6c73081cf..68e73a017a740 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -5,8 +5,8 @@ .controls.gl-display-flex = link_button_to nil, project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'd-none d-sm-inline-flex has-tooltip', icon: 'rss' - if is_project_overview && can?(current_user, :download_code, @project) - .project-clone-holder.d-none.d-md-inline-flex.gl-ml-2 - = render "projects/buttons/clone", dropdown_class: 'dropdown-menu-right' + .project-code-holder.d-none.d-md-inline-flex.gl-ml-2 + = render "projects/buttons/code", dropdown_class: 'dropdown-menu-right', ref: @ref .content_list.project-activity{ :"data-href" => activity_project_path(@project) } .loading diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_code.html.haml similarity index 83% rename from app/views/projects/buttons/_clone.html.haml rename to app/views/projects/buttons/_code.html.haml index 0e645eda678c1..0b2a527025edb 100644 --- a/app/views/projects/buttons/_clone.html.haml +++ b/app/views/projects/buttons/_code.html.haml @@ -1,15 +1,16 @@ - project = project || @project - dropdown_class = local_assigns.fetch(:dropdown_class, '') +- ref = local_assigns.fetch(:ref) - if can?(current_user, :download_code, @project) .git-clone-holder.js-git-clone-holder = render Pajamas::ButtonComponent.new(variant: :confirm, button_options: { id: 'clone-dropdown', class: 'clone-dropdown-btn', data: { toggle: 'dropdown', qa_selector: 'clone_dropdown' } }) do - %span.gl-mr-2.js-clone-dropdown-label - = _('Clone') + %span.js-clone-dropdown-label + = _('Code') = sprite_icon("chevron-down", css_class: "icon") - %ul.dropdown-menu.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown{ class: dropdown_class, data: { qa_selector: 'clone_dropdown_content' } } + %ul.dropdown-menu.dropdown-menu-large.clone-options-dropdown{ class: dropdown_class, data: { qa_selector: 'clone_dropdown_content' } } - if ssh_enabled? - %li{ class: 'gl-px-4!' } + %li.gl-dropdown-item.js-clone-links{ class: 'gl-px-4!' } %label.label-bold = _('Clone with SSH') .input-group.btn-group @@ -18,7 +19,7 @@ = clipboard_button(target: '#ssh_project_clone', title: _("Copy URL"), category: :primary, size: :medium) = render_if_exists 'projects/buttons/geo' - if http_enabled? - %li.pt-2{ class: 'gl-px-4!' } + %li.pt-2.gl-dropdown-item.js-clone-links{ class: 'gl-px-4!' } %label.label-bold = _('Clone with %{http_label}') % { http_label: gitlab_config.protocol.upcase } .input-group.btn-group @@ -28,7 +29,7 @@ = render_if_exists 'projects/buttons/geo' = render_if_exists 'projects/buttons/kerberos_clone_field' %li.divider.mt-2 - %li.pt-2.gl-dropdown-item + %li.pt-2.gl-dropdown-item.js-clone-links %label.label-bold{ class: 'gl-px-4!' } = _('Open in your IDE') - if ssh_enabled? @@ -53,3 +54,6 @@ %a.dropdown-item.open-with-link{ href: xcode_uri_to_repo(@project) } .gl-dropdown-item-text-wrapper = _("Xcode") + - if !project.empty_repo? && can?(current_user, :download_code, project) + %li.divider.mt-2 + = render 'projects/buttons/download_menu_items', project: project, ref: ref diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index b3282742407f6..946057a2e2fe0 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,27 +1,13 @@ - project = local_assigns.fetch(:project) - ref = local_assigns.fetch(:ref) -- pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) } +- pipeline = local_assigns.fetch(:pipeline, nil) - css_class = local_assigns.fetch(:css_class, '') - if !project.empty_repo? && can?(current_user, :download_code, project) - - archive_prefix = "#{project.path}-#{ref.tr('/', '-')}" .project-action-button.dropdown.gl-dropdown.inline{ class: css_class }> = render Pajamas::ButtonComponent.new(button_options: { class: 'dropdown-toggle gl-dropdown-toggle dropdown-icon-only has-tooltip', title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download'), 'data-display' => 'static', data: { testid: 'download-source-code-button' } }) do = sprite_icon('download', css_class: 'gl-icon dropdown-icon') %span.sr-only= _('Select Archive Format') = sprite_icon('chevron-down', css_class: 'gl-icon dropdown-chevron') - .dropdown-menu.dropdown-menu-right{ role: 'menu' } - %section - %h5.m-0.dropdown-bold-header= _('Download source code') - .dropdown-menu-content - = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: nil - .js-directory-downloads{ data: { links: directory_download_links(project, ref, archive_prefix).to_json } } - - if pipeline && pipeline.latest_builds_with_artifacts.any? - %section.border-top.pt-1.mt-1 - %h5.m-0.dropdown-bold-header= _('Download artifacts') - - unless pipeline.latest? - %span.unclickable= ci_status_for_statuseable(project.latest_pipeline(ref)) - %h6.m-0.dropdown-header= _('Previous Artifacts') - %ul - - pipeline.latest_builds_with_artifacts.each do |job| - %li= link_to job.name, latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' + %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' } + = render 'projects/buttons/download_menu_items', project: project, ref: ref, pipeline: pipeline diff --git a/app/views/projects/buttons/_download_links.html.haml b/app/views/projects/buttons/_download_links.html.haml index 31185fc153215..7035f3b37927f 100644 --- a/app/views/projects/buttons/_download_links.html.haml +++ b/app/views/projects/buttons/_download_links.html.haml @@ -1,4 +1,5 @@ -.btn-group.ml-0.w-100 - - Gitlab::Workhorse::ARCHIVE_FORMATS.each_with_index do |fmt, index| - - archive_path = project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: fmt) - = link_button_to fmt, external_storage_url_or_path(archive_path), rel: 'nofollow', download: '', variant: index == 0 ? :confirm : :default, size: :small +- Gitlab::Workhorse::ARCHIVE_FORMATS.each_with_index do |fmt, index| + - archive_path = project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: fmt) + + %a.dropdown-item.open-with-link{ href: external_storage_url_or_path(archive_path), rel: 'nofollow', download: '' } + .gl-dropdown-item-text-wrapper= fmt diff --git a/app/views/projects/buttons/_download_menu_items.html.haml b/app/views/projects/buttons/_download_menu_items.html.haml new file mode 100644 index 0000000000000..e4fcae1815c85 --- /dev/null +++ b/app/views/projects/buttons/_download_menu_items.html.haml @@ -0,0 +1,21 @@ +- project = local_assigns.fetch(:project) +- ref = local_assigns.fetch(:ref) +- pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) } +- archive_prefix = "#{project.path}-#{ref.tr('/', '-')}" + +%li.gl-dropdown-item + %h5.m-0.dropdown-bold-header= _('Download source code') + = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: nil +.js-directory-downloads{ data: { links: directory_download_links(project, ref, archive_prefix).to_json } } +- if pipeline && pipeline.latest_builds_with_artifacts.any? + %li.divider.mt-2 + %li.gl-dropdown-item + %h5.m-0.dropdown-bold-header= _('Download artifacts') + - unless pipeline.latest? + %span.gl-ml-3.unclickable= ci_status_for_statuseable(project.latest_pipeline(ref)) + %li.divider.mt-2 + %li.gl-dropdown-item + %h5.m-0.dropdown-bold-header= _('Previous Artifacts') + - pipeline.latest_builds_with_artifacts.each do |job| + = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), class: 'dropdown-item open-with-link', rel: 'nofollow', download: '' do + .gl-dropdown-item-text-wrapper= job.name diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 60b5ab376ecaa..ca9900a646e4d 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -19,11 +19,11 @@ = _('You can get started by cloning the repository or start adding files to it with one of the following options.') .project-buttons{ data: { testid: 'quick-actions-container' } } - .project-clone-holder.d-block.d-md-none.gl-mt-3.gl-mr-3 + .project-code-holder.d-block.d-md-none.gl-mt-3.gl-mr-3 = render "shared/mobile_clone_panel" - .project-clone-holder.d-none.d-md-inline-block.gl-mb-3.gl-mr-3.float-left - = render "projects/buttons/clone" + .project-code-holder.d-none.d-md-inline-block.gl-mb-3.gl-mr-3.float-left + = render "projects/buttons/code", ref: @ref = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons, project_buttons: true - if can?(current_user, :push_code, @project) diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index bed37d9cb63da..8eb0bb85e660d 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -16,10 +16,10 @@ = render 'projects/find_file_link' = render 'shared/web_ide_button', blob: nil - = render 'projects/buttons/download', project: @project, ref: @ref - .project-clone-holder.d-none.d-md-inline-block> - = render "projects/buttons/clone", dropdown_class: 'dropdown-menu-right' + .project-code-holder.d-none.d-md-inline-block> + = render "projects/buttons/code", dropdown_class: 'dropdown-menu-right', ref: @ref - .project-clone-holder.d-block.d-md-none.mt-sm-2.mt-md-0.ml-md-2> - = render "shared/mobile_clone_panel" + .project-code-holder.d-block.d-md-none.mt-sm-2.mt-md-0.ml-md-2> + = render 'projects/buttons/download', project: @project, ref: @ref + = render "shared/mobile_clone_panel", ref: @ref diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 4b39ec5283757..7dce8737eb490 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -10,9 +10,9 @@ = default_clone_protocol.upcase = sprite_icon('chevron-down', css_class: 'gl-icon') %ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown{ data: { testid: 'clone-dropdown-content' } } - %li + %li.js-clone-links = ssh_clone_button(container) - %li + %li.js-clone-links = http_clone_button(container) = render_if_exists 'shared/kerberos_clone_button', container: container diff --git a/app/views/shared/_mobile_clone_panel.html.haml b/app/views/shared/_mobile_clone_panel.html.haml index c594cee326ebb..2f7fb348c17db 100644 --- a/app/views/shared/_mobile_clone_panel.html.haml +++ b/app/views/shared/_mobile_clone_panel.html.haml @@ -8,9 +8,9 @@ = sprite_icon("chevron-down", css_class: "dropdown-btn-icon icon") %ul.dropdown-menu.dropdown-menu-selectable.dropdown-menu-right.clone-options-dropdown{ data: { dropdown: true } } - if ssh_enabled? - %li + %li.js-clone-links = dropdown_item_with_description(ssh_copy_label, ssh_clone_url_to_repo(project), href: ssh_clone_url_to_repo(project), data: { clone_type: 'ssh' }, default: true) - if http_enabled? - %li + %li.js-clone-links = dropdown_item_with_description(http_copy_label, http_clone_url_to_repo(project), href: http_clone_url_to_repo(project), data: { clone_type: 'http' }) = render_if_exists 'shared/mobile_kerberos_clone' diff --git a/config/feature_flags/development/ci_retry_on_exit_codes.yml b/config/feature_flags/development/ci_retry_on_exit_codes.yml new file mode 100644 index 0000000000000..eb5f4abb618ca --- /dev/null +++ b/config/feature_flags/development/ci_retry_on_exit_codes.yml @@ -0,0 +1,8 @@ +--- +name: ci_retry_on_exit_codes +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135430 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/430037 +milestone: '16.7' +type: development +group: group::pipeline authoring +default_enabled: false diff --git a/config/feature_flags/development/environments_folder_new_look.yml b/config/feature_flags/development/environments_folder_new_look.yml new file mode 100644 index 0000000000000..218ea2cf44842 --- /dev/null +++ b/config/feature_flags/development/environments_folder_new_look.yml @@ -0,0 +1,8 @@ +--- +name: environments_folder_new_look +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137046 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431928 +milestone: '16.7' +type: development +group: group::environments +default_enabled: false diff --git a/config/initializers/circuitbox.rb b/config/initializers/circuitbox.rb index 966320265aa5e..55c06cf868b91 100644 --- a/config/initializers/circuitbox.rb +++ b/config/initializers/circuitbox.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -Gitlab.ee do - Circuitbox.configure do |config| - config.default_circuit_store = Gitlab::CircuitBreaker::Store.new - config.default_notifier = Gitlab::CircuitBreaker::Notifier.new - end +Circuitbox.configure do |config| + config.default_circuit_store = Gitlab::CircuitBreaker::Store.new + config.default_notifier = Gitlab::CircuitBreaker::Notifier.new end diff --git a/db/fixtures/development/27_product_analytics_events.rb b/db/fixtures/development/27_product_analytics_events.rb deleted file mode 100644 index 19237afd8eab3..0000000000000 --- a/db/fixtures/development/27_product_analytics_events.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -Gitlab::Seeder.quiet do - # The data set takes approximately 2 minutes to load, - # so its put behind the flag. To seed this data use the flag and the filter: - # SEED_PRODUCT_ANALYTICS_EVENTS=1 FILTER=product_analytics_events rake db:seed_fu - flag = 'SEED_PRODUCT_ANALYTICS_EVENTS' - - if ENV[flag] - Project.all.sample(2).each do |project| - # Let's generate approx a week of events from now into the past with 1 minute step. - # To add some differentiation we add a random offset of up to 45 seconds. - 10000.times do |i| - dvce_created_tstamp = DateTime.now - i.minute - rand(45).seconds - - # Add a random delay to collector timestamp. Up to 2 seconds. - collector_tstamp = dvce_created_tstamp + rand(3).second - - ProductAnalyticsEvent.create!( - project_id: project.id, - platform: ["web", "mob", "mob", "app"].sample, - collector_tstamp: collector_tstamp, - dvce_created_tstamp: dvce_created_tstamp, - event: nil, - event_id: SecureRandom.uuid, - name_tracker: "sp", - v_tracker: "js-2.14.0", - v_collector: Gitlab::VERSION, - v_etl: Gitlab::VERSION, - domain_userid: SecureRandom.uuid, - domain_sessionidx: 4, - page_url: "#{project.web_url}/-/product_analytics/test", - page_title: 'Test page', - page_referrer: "#{project.web_url}/-/product_analytics/test", - br_lang: ["en-US", "en-US", "en-GB", "nl", "fi"].sample, # https://www.andiamo.co.uk/resources/iso-language-codes/ - br_features_pdf: true, - br_cookies: [true, true, true, false].sample, - br_colordepth: ["24", "24", "16", "8"].sample, - os_timezone: ["America/Los_Angeles", "America/Los_Angeles", "America/Lima", "Asia/Dubai", "Africa/Bangui"].sample, # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - doc_charset: ["UTF-8", "UTF-8", "UTF-8", "DOS", "EUC"].sample, - domain_sessionid: SecureRandom.uuid - ) - end - - unless Feature.enabled?(:product_analytics, project) - if Feature.enable(:product_analytics, project) - puts "Product analytics feature was enabled for #{project.full_path}" - end - end - - puts "10K events added to #{project.full_path}" - end - else - puts "Skipped. Use the `#{flag}` environment variable to enable." - end -end diff --git a/db/migrate/20231029142649_add_make_profile_private_application_setting.rb b/db/migrate/20231029142649_add_make_profile_private_application_setting.rb new file mode 100644 index 0000000000000..ff946d56e7201 --- /dev/null +++ b/db/migrate/20231029142649_add_make_profile_private_application_setting.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddMakeProfilePrivateApplicationSetting < Gitlab::Database::Migration[2.2] + milestone '16.7' + + def change + add_column(:application_settings, :make_profile_private, :boolean, default: true, null: false) + end +end diff --git a/db/post_migrate/20231122100006_remove_custom_email_smtp_columns_from_service_desk_settings.rb b/db/post_migrate/20231122100006_remove_custom_email_smtp_columns_from_service_desk_settings.rb new file mode 100644 index 0000000000000..98bb15dd76e79 --- /dev/null +++ b/db/post_migrate/20231122100006_remove_custom_email_smtp_columns_from_service_desk_settings.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class RemoveCustomEmailSmtpColumnsFromServiceDeskSettings < Gitlab::Database::Migration[2.2] + MAXIMUM_LIMIT = 255 + + milestone '16.7' + + disable_ddl_transaction! + + def up + with_lock_retries do + remove_column :service_desk_settings, :custom_email_smtp_address + remove_column :service_desk_settings, :custom_email_smtp_port + remove_column :service_desk_settings, :custom_email_smtp_username + remove_column :service_desk_settings, :encrypted_custom_email_smtp_password + remove_column :service_desk_settings, :encrypted_custom_email_smtp_password_iv + end + end + + def down + with_lock_retries do + add_column :service_desk_settings, :custom_email_smtp_address, :text + add_column :service_desk_settings, :custom_email_smtp_port, :integer + add_column :service_desk_settings, :custom_email_smtp_username, :text + add_column :service_desk_settings, :encrypted_custom_email_smtp_password, :binary + add_column :service_desk_settings, :encrypted_custom_email_smtp_password_iv, :binary + end + + add_text_limit :service_desk_settings, :custom_email_smtp_address, MAXIMUM_LIMIT + add_text_limit :service_desk_settings, :custom_email_smtp_username, MAXIMUM_LIMIT + end +end diff --git a/db/schema_migrations/20231029142649 b/db/schema_migrations/20231029142649 new file mode 100644 index 0000000000000..8e9924fb32e9a --- /dev/null +++ b/db/schema_migrations/20231029142649 @@ -0,0 +1 @@ +6b8021c293e630af13479afe8f49ef3d28861d963942b481a3113266ff59fccf \ No newline at end of file diff --git a/db/schema_migrations/20231122100006 b/db/schema_migrations/20231122100006 new file mode 100644 index 0000000000000..5cec39e6f6031 --- /dev/null +++ b/db/schema_migrations/20231122100006 @@ -0,0 +1 @@ +cf9a4cbefa65c11d5066134ff82615453aaf63af3f6f871d532038439ada6d22 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index b915344d7cb0b..0fd47d9b0d4e9 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12196,6 +12196,7 @@ CREATE TABLE application_settings ( project_jobs_api_rate_limit integer DEFAULT 600 NOT NULL, math_rendering_limits_enabled boolean DEFAULT true NOT NULL, service_access_tokens_expiration_enforced boolean DEFAULT true NOT NULL, + make_profile_private boolean DEFAULT true NOT NULL, enable_artifact_external_redirect_warning_page boolean DEFAULT true NOT NULL, allow_project_creation_for_guest_and_below boolean DEFAULT true NOT NULL, update_namespace_name_rate_limit smallint DEFAULT 120 NOT NULL, @@ -23437,16 +23438,9 @@ CREATE TABLE service_desk_settings ( file_template_project_id bigint, custom_email_enabled boolean DEFAULT false NOT NULL, custom_email text, - custom_email_smtp_address text, - custom_email_smtp_port integer, - custom_email_smtp_username text, - encrypted_custom_email_smtp_password bytea, - encrypted_custom_email_smtp_password_iv bytea, service_desk_enabled boolean DEFAULT true NOT NULL, add_external_participants_from_cc boolean DEFAULT false NOT NULL, - CONSTRAINT check_57a79552e1 CHECK ((char_length(custom_email) <= 255)), - CONSTRAINT check_b283637a9e CHECK ((char_length(custom_email_smtp_address) <= 255)), - CONSTRAINT check_e3535d46ee CHECK ((char_length(custom_email_smtp_username) <= 255)) + CONSTRAINT check_57a79552e1 CHECK ((char_length(custom_email) <= 255)) ); CREATE TABLE shards ( diff --git a/doc/administration/logs/index.md b/doc/administration/logs/index.md index b32d19f174fd7..b61970b6fa522 100644 --- a/doc/administration/logs/index.md +++ b/doc/administration/logs/index.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/health_check.md b/doc/administration/monitoring/health_check.md index 4dbbdf6f3c9dc..029143564d1e3 100644 --- a/doc/administration/monitoring/health_check.md +++ b/doc/administration/monitoring/health_check.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/index.md b/doc/administration/monitoring/index.md index 622a772a63894..ef4d40a7fca8c 100644 --- a/doc/administration/monitoring/index.md +++ b/doc/administration/monitoring/index.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/performance/gitlab_configuration.md b/doc/administration/monitoring/performance/gitlab_configuration.md index a32a38a11e564..858c94f3671a8 100644 --- a/doc/administration/monitoring/performance/gitlab_configuration.md +++ b/doc/administration/monitoring/performance/gitlab_configuration.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md index a69eb67e77e78..15ccc2094c82f 100644 --- a/doc/administration/monitoring/performance/grafana_configuration.md +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md index b632108b10353..f965288611c5c 100644 --- a/doc/administration/reply_by_email.md +++ b/doc/administration/reply_by_email.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/administration/settings/incident_management_rate_limits.md b/doc/administration/settings/incident_management_rate_limits.md index 5a8b1b96c6d79..ef646b5e219a7 100644 --- a/doc/administration/settings/incident_management_rate_limits.md +++ b/doc/administration/settings/incident_management_rate_limits.md @@ -1,6 +1,6 @@ --- type: reference -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/api/alert_management_alerts.md b/doc/api/alert_management_alerts.md index 5da77d0860543..049754d28878a 100644 --- a/doc/api/alert_management_alerts.md +++ b/doc/api/alert_management_alerts.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/api/group_ssh_certificates.md b/doc/api/group_ssh_certificates.md index 3d99bdcaa3a91..d906bbefa8e45 100644 --- a/doc/api/group_ssh_certificates.md +++ b/doc/api/group_ssh_certificates.md @@ -4,12 +4,11 @@ group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Group SSH certificates API **(PREMIUM ALL)** +# Group SSH certificates API **(PREMIUM SAAS)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/421915) in GitLab 16.4 [with a flag](../user/feature_flags.md) named `ssh_certificates_rest_endpoints`. Disabled by default. FLAG: -On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../administration/feature_flags.md) named `ssh_certificates_rest_endpoints`. On GitLab.com, this feature is not available. Use this API to create, read and delete SSH certificates for a group. diff --git a/doc/api/metrics_dashboard_annotations.md b/doc/api/metrics_dashboard_annotations.md index 37d19860e6a42..56bd1066e2e6a 100644 --- a/doc/api/metrics_dashboard_annotations.md +++ b/doc/api/metrics_dashboard_annotations.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments type: concepts, howto diff --git a/doc/api/metrics_user_starred_dashboards.md b/doc/api/metrics_user_starred_dashboards.md index 6e839e75ae946..2c358e080c200 100644 --- a/doc/api/metrics_user_starred_dashboards.md +++ b/doc/api/metrics_user_starred_dashboards.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments type: concepts, howto diff --git a/doc/development/distributed_tracing.md b/doc/development/distributed_tracing.md index b5b049346b6af..86732c3a8acc0 100644 --- a/doc/development/distributed_tracing.md +++ b/doc/development/distributed_tracing.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- diff --git a/doc/development/logging.md b/doc/development/logging.md index cd4b9f2cd8c6a..123c5c46f9210 100644 --- a/doc/development/logging.md +++ b/doc/development/logging.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 681f345fb8687..25d48632bd656 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -1019,6 +1019,10 @@ end ## Dropping a database table +NOTE: +After a table has been dropped, it should be added to the database dictionary, following the +steps in the [database dictionary guide](database/database_dictionary.md#dropping-tables). + Dropping a database table is uncommon, and the `drop_table` method provided by Rails is generally considered safe. Before dropping the table, please consider the following: @@ -1105,9 +1109,6 @@ class DroppingTableMigrationClass < Gitlab::Database::Migration[2.1] end ``` -After a table has been dropped, it should be added to the database dictionary, following the -steps in the [database dictionary guide](database/database_dictionary.md#dropping-tables). - ## Dropping a sequence > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88387) in GitLab 15.1. diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md index d7779232d3e72..6a4a85f14ffa7 100644 --- a/doc/development/prometheus_metrics.md +++ b/doc/development/prometheus_metrics.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- diff --git a/doc/development/runner_fleet_dashboard.md b/doc/development/runner_fleet_dashboard.md index 9629dc973c565..737ee751b7f3c 100644 --- a/doc/development/runner_fleet_dashboard.md +++ b/doc/development/runner_fleet_dashboard.md @@ -59,6 +59,7 @@ To setup ClickHouse as the GitLab data storage: 1. [Run ClickHouse Cluster and configure database](#run-and-configure-clickhouse). 1. [Configure GitLab connection to Clickhouse](#configure-the-gitlab-connection-to-clickhouse). +1. [Run ClickHouse migrations](#run-clickhouse-migrations). 1. [Enable the feature flags](#enable-feature-flags). ### Run and configure ClickHouse diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md index f92739580bdfd..2f2cb91c51150 100644 --- a/doc/operations/incident_management/alerts.md +++ b/doc/operations/incident_management/alerts.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/escalation_policies.md b/doc/operations/incident_management/escalation_policies.md index 3e1585c930dc1..10dce56024e79 100644 --- a/doc/operations/incident_management/escalation_policies.md +++ b/doc/operations/incident_management/escalation_policies.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/incident_timeline_events.md b/doc/operations/incident_management/incident_timeline_events.md index f5322e0a7ccd3..96827183086b0 100644 --- a/doc/operations/incident_management/incident_timeline_events.md +++ b/doc/operations/incident_management/incident_timeline_events.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md index 43df69a3b39fa..29a7be1e6c9f3 100644 --- a/doc/operations/incident_management/incidents.md +++ b/doc/operations/incident_management/incidents.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/index.md b/doc/operations/incident_management/index.md index 1cae066ce32af..377073a9e8a6f 100644 --- a/doc/operations/incident_management/index.md +++ b/doc/operations/incident_management/index.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/integrations.md b/doc/operations/incident_management/integrations.md index 8691d78c4c983..a2006aedad0d9 100644 --- a/doc/operations/incident_management/integrations.md +++ b/doc/operations/incident_management/integrations.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/linked_resources.md b/doc/operations/incident_management/linked_resources.md index 57fcb31e3ba7c..0f2fccce00c8d 100644 --- a/doc/operations/incident_management/linked_resources.md +++ b/doc/operations/incident_management/linked_resources.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/manage_incidents.md b/doc/operations/incident_management/manage_incidents.md index 1b48de9e478d7..9ab9a313a5155 100644 --- a/doc/operations/incident_management/manage_incidents.md +++ b/doc/operations/incident_management/manage_incidents.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/oncall_schedules.md b/doc/operations/incident_management/oncall_schedules.md index e885cbd458955..314a801c58013 100644 --- a/doc/operations/incident_management/oncall_schedules.md +++ b/doc/operations/incident_management/oncall_schedules.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/paging.md b/doc/operations/incident_management/paging.md index 845c17d974a2f..7247a490d4f36 100644 --- a/doc/operations/incident_management/paging.md +++ b/doc/operations/incident_management/paging.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/slack.md b/doc/operations/incident_management/slack.md index 100aaa0d9bcc1..41e731267ca3d 100644 --- a/doc/operations/incident_management/slack.md +++ b/doc/operations/incident_management/slack.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/status_page.md b/doc/operations/incident_management/status_page.md index 3835ea3078a99..05c6fd9e17e95 100644 --- a/doc/operations/incident_management/status_page.md +++ b/doc/operations/incident_management/status_page.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/operations/index.md b/doc/operations/index.md index 766047e1f29ff..a4cd58e69867c 100644 --- a/doc/operations/index.md +++ b/doc/operations/index.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/crm/index.md b/doc/user/crm/index.md index 46e395af5bc5d..9292c271fd229 100644 --- a/doc/user/crm/index.md +++ b/doc/user/crm/index.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/group/ssh_certificates.md b/doc/user/group/ssh_certificates.md new file mode 100644 index 0000000000000..84ac79bc7736f --- /dev/null +++ b/doc/user/group/ssh_certificates.md @@ -0,0 +1,83 @@ +--- +stage: Create +group: Source Code +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Manage group's SSH certificates **(PREMIUM SAAS)** + +Manage Git access to the projects by sharing public Certified Authority (`CA`) files in your organization's top-level group. + +Git access control options on GitLab SaaS (SSH, HTTPS) rely on credentials (such as access tokens and SSH keys) +setup in the user profile and are out of control of the organization. +To temporarily grant Git access to your projects, you can use SSH certificates. + +## Add a CA certificate to a top-level group + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/421915) in GitLab 16.4 [with a flag](../feature_flags.md) named `ssh_certificates_rest_endpoints`. Disabled by default. + +FLAG: +On GitLab.com, this feature is not available. +Prerequisites: + +- You must have the Owner role for the group. +- The group must be a top-level group, not a subgroup. + +To add a CA certificate to a group: + +1. Generate an SSH key pair to be used as a Certified Authority file: + + ```plaintext + ssh-keygen -f CA + ``` + +1. Add the public key to the top-level group using [Group SSH certificates API](../../api/group_ssh_certificates.md#create-ssh-certificate) + to grant access to the projects of the group and its subgroups. + +## Issue CA certificates for users + +Prerequisites: + +- You must have the Owner role for the group. +- The user certificates can only be used to access the projects within the top-level group and its subgroups. +- A user's username or primary email (`user` or `user@example.com`) must be specified to associate a + GitLab user with the user certificate. +- The user must be an [Enterprise User](../enterprise_user/index.md). + +To issue user certificates, use the private key from the pair you [created earlier](#add-a-ca-certificate-to-a-top-level-group): + +```shell +ssh-keygen -s CA -I user@example.com -V +1d user-key.pub +``` + +The (`user-key.pub`) key is the public key from an SSH key pair that is used by a user for SSH authentication. +The SSH key pair is either generated by a user or provisioned by the group owner infrastructure along with the SSH certificate. + +The expiration date (`+1d`) identifies how long the SSH certificate can be used to access the group projects. + +The user certificates can only be used to access the projects within the top-level group. + +## Enforce SSH certificates + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/421915) in GitLab 16.7 [with a flag](../feature_flags.md) named `enforce_ssh_certificates_via_settings`. Disabled by default. + +FLAG: +On GitLab.com, this feature is not available. + +You can enforce usage of SSH certificates and forbid users from authenticating using SSH +keys and access tokens. + +When SSH certificates are enforced, only individual user accounts are affected. +It does not apply to service accounts, deploy keys, and other types of internal accounts. + +Prerequisites: + +- You must have the Owner role for the group. + +To enforce using SSH certificates: + +1. On the left sidebar, select **Search or go to** and find your group. +1. Select **Settings > General**. +1. Expand the **Permissions and group features** section. +1. Select the **Enforce SSH Certificates** checkbox. +1. Select **Save changes**. diff --git a/doc/user/project/service_desk/configure.md b/doc/user/project/service_desk/configure.md index 8a9e28a6d0d04..a3d2528f69164 100644 --- a/doc/user/project/service_desk/configure.md +++ b/doc/user/project/service_desk/configure.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/project/service_desk/index.md b/doc/user/project/service_desk/index.md index e87a0bb29439e..9539fe5eb88ca 100644 --- a/doc/user/project/service_desk/index.md +++ b/doc/user/project/service_desk/index.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/project/service_desk/using_service_desk.md b/doc/user/project/service_desk/using_service_desk.md index 5f3c725b83b6b..8c7677a0cd443 100644 --- a/doc/user/project/service_desk/using_service_desk.md +++ b/doc/user/project/service_desk/using_service_desk.md @@ -1,5 +1,5 @@ --- -stage: Monitor +stage: Service Management group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/lib/gitlab/ci/config/entry/retry.rb b/lib/gitlab/ci/config/entry/retry.rb index e9cbcb31e21c2..f225ed4caf42a 100644 --- a/lib/gitlab/ci/config/entry/retry.rb +++ b/lib/gitlab/ci/config/entry/retry.rb @@ -35,8 +35,8 @@ class FullRetry < ::Gitlab::Config::Entry::Node include ::Gitlab::Config::Entry::Validatable include ::Gitlab::Config::Entry::Attributable - ALLOWED_KEYS = %i[max when].freeze - attributes :max, :when + ALLOWED_KEYS = %i[max when exit_codes].freeze + attributes ALLOWED_KEYS validations do validates :config, allowed_keys: ALLOWED_KEYS @@ -53,6 +53,7 @@ class FullRetry < ::Gitlab::Config::Entry::Node validates :when, inclusion: { in: FullRetry.possible_retry_when_values }, if: -> (config) { config.when.is_a?(String) } + validates :exit_codes, array_of_integers_or_integer: true end end @@ -62,9 +63,14 @@ def self.possible_retry_when_values def value super.tap do |config| - # make sure that `when` is an array, because we allow it to - # be passed as a String in config for simplicity + # make sure that `when` and `exit_codes` are arrays, because we allow them to + # be passed as a String/Integer in config for simplicity config[:when] = Array.wrap(config[:when]) if config[:when] + if config[:exit_codes] && Feature.enabled?(:ci_retry_on_exit_codes, Feature.current_request) + config[:exit_codes] = Array.wrap(config[:exit_codes]) + else + config.delete(:exit_codes) + end end end diff --git a/lib/gitlab/circuit_breaker.rb b/lib/gitlab/circuit_breaker.rb new file mode 100644 index 0000000000000..2b3a6187f273b --- /dev/null +++ b/lib/gitlab/circuit_breaker.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# A configurable circuit breaker to protect the application from external service failures. +# The circuit measures the amount of failures and if the threshold is exceeded, stops sending requests. +module Gitlab + module CircuitBreaker + InternalServerError = Class.new(StandardError) + + DEFAULT_ERROR_THRESHOLD = 50 + DEFAULT_VOLUME_THRESHOLD = 10 + + class << self + include ::Gitlab::Utils::StrongMemoize + + # @param [String] unique name for the circuit + # @param options [Hash] an options hash setting optional values per circuit + def run_with_circuit(service_name, options = {}, &block) + circuit(service_name, options).run(exception: false, &block) + end + + private + + def circuit(service_name, options) + strong_memoize_with(:circuit, service_name, options) do + circuit_options = { + exceptions: [InternalServerError], + error_threshold: DEFAULT_ERROR_THRESHOLD, + volume_threshold: DEFAULT_VOLUME_THRESHOLD + }.merge(options) + + Circuitbox.circuit(service_name, circuit_options) + end + end + end + end +end diff --git a/lib/gitlab/circuit_breaker/notifier.rb b/lib/gitlab/circuit_breaker/notifier.rb new file mode 100644 index 0000000000000..b555158ee48b6 --- /dev/null +++ b/lib/gitlab/circuit_breaker/notifier.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Gitlab + module CircuitBreaker + class Notifier + CircuitBreakerError = Class.new(RuntimeError) + + def notify(service_name, event) + return unless event == 'failure' + + exception = CircuitBreakerError.new("Service #{service_name}: #{event}") + exception.set_backtrace(Gitlab::BacktraceCleaner.clean_backtrace(caller)) + + Gitlab::ErrorTracking.track_exception(exception) + end + + def notify_warning(_service_name, _message) + # no-op + end + + def notify_run(_service_name, &_block) + # This gets called by Circuitbox::CircuitBreaker#run to actually execute + # the block passed. + yield + end + end + end +end diff --git a/lib/gitlab/circuit_breaker/store.rb b/lib/gitlab/circuit_breaker/store.rb new file mode 100644 index 0000000000000..0ba4f08d5e17a --- /dev/null +++ b/lib/gitlab/circuit_breaker/store.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Gitlab + module CircuitBreaker + class Store + def key?(key) + with { |redis| redis.exists?(key) } + end + + def store(key, value, opts = {}) + with do |redis| + redis.set(key, value, ex: opts[:expires]) + value + end + end + + def increment(key, amount = 1, opts = {}) + expires = opts[:expires] + + with do |redis| + redis.multi do |multi| + multi.incrby(key, amount) + multi.expire(key, expires) if expires + end + end + end + + def load(key, _opts = {}) + with { |redis| redis.get(key) } + end + + def values_at(*keys, **_opts) + keys.map! { |key| load(key) } + end + + def delete(key) + with { |redis| redis.del(key) } + end + + private + + def with(&block) + Gitlab::Redis::RateLimiting.with(&block) + rescue ::Redis::BaseConnectionError + # Do not raise an error if we cannot connect to Redis. If + # Redis::RateLimiting is unavailable it should not take the site down. + nil + end + end + end +end diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb index 72bec159226f2..fe18bc8e133be 100644 --- a/lib/gitlab/quick_actions/merge_request_actions.rb +++ b/lib/gitlab/quick_actions/merge_request_actions.rb @@ -161,10 +161,25 @@ module MergeRequestActions condition do quick_action_target.persisted? end - command :submit_review do + command :submit_review do |state = "reviewed"| next if params[:review_id] result = DraftNotes::PublishService.new(quick_action_target, current_user).execute + + if Feature.enabled?(:mr_request_changes, current_user) + reviewer_state = state.strip.presence + + if reviewer_state === 'approve' + ::MergeRequests::ApprovalService + .new(project: quick_action_target.project, current_user: current_user) + .execute(quick_action_target) + elsif MergeRequestReviewer.states.key?(reviewer_state) + ::MergeRequests::UpdateReviewerStateService + .new(project: quick_action_target.project, current_user: current_user) + .execute(quick_action_target, reviewer_state) + end + end + @execution_message[:submit_review] = if result[:status] == :success _('Submitted the current review.') else diff --git a/lib/product_analytics/event_params.rb b/lib/product_analytics/event_params.rb deleted file mode 100644 index 6cb3d462384c6..0000000000000 --- a/lib/product_analytics/event_params.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -module ProductAnalytics - # Converts params from Snowplow tracker to one compatible with - # GitLab ProductAnalyticsEvent model. The field naming corresponds - # with snowplow event model. Only project_id is GitLab specific. - # - # For information on what each field is you can check next resources: - # * Snowplow tracker protocol: https://github.com/snowplow/snowplow/wiki/snowplow-tracker-protocol - # * Canonical event model: https://github.com/snowplow/snowplow/wiki/canonical-event-model - class EventParams - def self.parse_event_params(params) - { - project_id: params['aid'], - platform: params['p'], - collector_tstamp: Time.zone.now, - event_id: params['eid'], - v_tracker: params['tv'], - v_collector: Gitlab::VERSION, - v_etl: Gitlab::VERSION, - os_timezone: params['tz'], - name_tracker: params['tna'], - br_lang: params['lang'], - doc_charset: params['cs'], - br_features_pdf: Gitlab::Utils.to_boolean(params['f_pdf']), - br_features_flash: Gitlab::Utils.to_boolean(params['f_fla']), - br_features_java: Gitlab::Utils.to_boolean(params['f_java']), - br_features_director: Gitlab::Utils.to_boolean(params['f_dir']), - br_features_quicktime: Gitlab::Utils.to_boolean(params['f_qt']), - br_features_realplayer: Gitlab::Utils.to_boolean(params['f_realp']), - br_features_windowsmedia: Gitlab::Utils.to_boolean(params['f_wma']), - br_features_gears: Gitlab::Utils.to_boolean(params['f_gears']), - br_features_silverlight: Gitlab::Utils.to_boolean(params['f_ag']), - br_colordepth: params['cd'], - br_cookies: Gitlab::Utils.to_boolean(params['cookie']), - dvce_created_tstamp: params['dtm'], - br_viewheight: params['vp'], - domain_sessionidx: params['vid'], - domain_sessionid: params['sid'], - domain_userid: params['duid'], - user_fingerprint: params['fp'], - page_referrer: params['refr'], - page_url: params['url'], - se_category: params['se_ca'], - se_action: params['se_ac'], - se_label: params['se_la'], - se_property: params['se_pr'], - se_value: params['se_va'] - } - end - - def self.has_required_params?(params) - params['aid'].present? && params['eid'].present? - end - end -end diff --git a/qa/qa/page/component/clone_panel.rb b/qa/qa/page/component/clone_panel.rb index 3ea29ff63da24..feb57a1cbf6a0 100644 --- a/qa/qa/page/component/clone_panel.rb +++ b/qa/qa/page/component/clone_panel.rb @@ -9,7 +9,7 @@ module ClonePanel def self.included(base) super - base.view 'app/views/projects/buttons/_clone.html.haml' do + base.view 'app/views/projects/buttons/_code.html.haml' do element :clone_dropdown element :clone_dropdown_content element :ssh_clone_url_content diff --git a/rubocop/rubocop-code_reuse.yml b/rubocop/rubocop-code_reuse.yml index 6f9d7902dc681..cf81aa2db5e6d 100644 --- a/rubocop/rubocop-code_reuse.yml +++ b/rubocop/rubocop-code_reuse.yml @@ -26,6 +26,7 @@ CodeReuse/ActiveRecord: - lib/banzai/**/*.rb - lib/click_house/migration_support/**/*.rb - lib/gitlab/background_migration/**/*.rb + - lib/gitlab/circuit_breaker/store.rb - lib/gitlab/cycle_analytics/**/*.rb - lib/gitlab/counters/**/*.rb - lib/gitlab/database/**/*.rb diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb index 6f2cd4bf59693..c4b318a5aeef4 100644 --- a/spec/factories/environments.rb +++ b/spec/factories/environments.rb @@ -35,6 +35,14 @@ name { 'development' } end + trait :with_folders do |environment| + sequence(:name) { |n| "#{folder}/environment#{n}" } + + transient do + folder { 'folder' } + end + end + trait :with_review_app do |environment| sequence(:name) { |n| "review/#{n}" } diff --git a/spec/factories/product_analytics_event.rb b/spec/factories/product_analytics_event.rb deleted file mode 100644 index 168b255f6ca88..0000000000000 --- a/spec/factories/product_analytics_event.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -FactoryBot.define do - factory :product_analytics_event do - project - platform { 'web' } - collector_tstamp { DateTime.now } - dvce_created_tstamp { DateTime.now } - event_id { SecureRandom.uuid } - name_tracker { 'sp' } - v_tracker { 'js-2.14.0' } - v_collector { 'GitLab 13.1.0-pre' } - v_etl { 'GitLab 13.1.0-pre' } - domain_userid { SecureRandom.uuid } - domain_sessionidx { 4 } - page_url { 'http://localhost:3333/products/123' } - br_lang { 'en-US' } - br_cookies { true } - br_colordepth { '24' } - os_timezone { 'America/Los_Angeles' } - doc_charset { 'UTF-8' } - domain_sessionid { SecureRandom.uuid } - end -end diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 5379dabc71361..e9b55ab2900d7 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -151,7 +151,7 @@ it 'shows that the last pipeline passed' do visit dashboard_projects_path - page.within('[data-testid="project_controls"]') do + within_testid('project_controls') do expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']") expect(page).to have_css("[data-testid='ci-icon']") expect(page).to have_css('[data-testid="status_success_borderless-icon"]') @@ -163,7 +163,7 @@ it 'does not show the pipeline status' do visit dashboard_projects_path - page.within('[data-testid="project_controls"]') do + within_testid('project_controls') do expect(page).not_to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']") expect(page).not_to have_css("[data-testid='ci-icon']") expect(page).not_to have_css('[data-testid="status_success_borderless-icon"]') diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 59ce873905a4e..21dfa1cbc0bca 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -95,7 +95,7 @@ shared_examples 'deleting the todo' do before do within first('.todo') do - find('[data-testid="check-icon"]').click + find_by_testid('check-icon').click end end @@ -121,9 +121,9 @@ shared_examples 'deleting and restoring the todo' do before do within first('.todo') do - find('[data-testid="check-icon"]').click + find_by_testid('check-icon').click wait_for_requests - find('[data-testid="redo-icon"]').click + find_by_testid('redo-icon').click end end @@ -301,7 +301,7 @@ describe 'restoring the todo' do before do within first('.todo') do - find('[data-testid="todo-add-icon"]').click + find_by_testid('todo-add-icon').click end end @@ -407,7 +407,7 @@ context 'User has deleted a todo' do before do within first('.todo') do - find('[data-testid="check-icon"]').click + find_by_testid('check-icon').click end end diff --git a/spec/features/environments/environments_folder_spec.rb b/spec/features/environments/environments_folder_spec.rb new file mode 100644 index 0000000000000..ed5e3e9833816 --- /dev/null +++ b/spec/features/environments/environments_folder_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Environments Folder page', :js, feature_category: :environment_management do + let(:folder_name) { 'folder' } + + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let!(:envs) { create_list(:environment, 4, :with_folders, project: project, folder: folder_name) } + + before_all do + project.add_role(user, :developer) + end + + before do + create(:environment, :production, project: project) + end + + describe 'new folders page' do + before do + sign_in(user) + visit folder_project_environments_path(project, folder_name) + wait_for_requests + end + + it 'renders the header with a folder name' do + expect(page).to have_content("Environments / #{folder_name}") + end + end + + describe 'legacy folders page' do + before do + stub_feature_flags(environments_folder_new_look: false) + sign_in(user) + visit folder_project_environments_path(project, folder_name) + wait_for_requests + end + + it 'user opens folder view' do + expect(page).to have_content("Environments / #{folder_name}") + expect(page).not_to have_content('production') + envs.each { |env| expect(page).to have_content(env.name.split('/').last) } + end + end +end diff --git a/spec/features/groups/board_sidebar_spec.rb b/spec/features/groups/board_sidebar_spec.rb index 3fe520ea2ea13..d03fd5323271b 100644 --- a/spec/features/groups/board_sidebar_spec.rb +++ b/spec/features/groups/board_sidebar_spec.rb @@ -34,7 +34,7 @@ wait_for_requests - page.within('[data-testid="dropdown-content"]') do + within_testid('dropdown-content') do expect(page).to have_content(project_1_label.title) expect(page).to have_content(group_label.title) expect(page).not_to have_content(project_2_label.title) diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb index e6dc6055e27a2..1a9a53a7421ce 100644 --- a/spec/features/groups/board_spec.rb +++ b/spec/features/groups/board_spec.rb @@ -32,7 +32,7 @@ fill_in 'issue_title', with: issue_title - page.within("[data-testid='project-select-dropdown']") do + within_testid('project-select-dropdown') do find('button.gl-new-dropdown-toggle').click find('.gl-new-dropdown-item').click diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb index d876a5804bd6e..ca0687134b643 100644 --- a/spec/features/groups/clusters/user_spec.rb +++ b/spec/features/groups/clusters/user_spec.rb @@ -112,9 +112,9 @@ context 'when user destroys the cluster' do before do click_link 'Advanced Settings' - find('[data-testid="remove-integration-button"]').click + find_by_testid('remove-integration-button').click fill_in 'confirm_cluster_name_input', with: cluster.name - find('[data-testid="remove-integration-modal-button"]').click + find_by_testid('remove-integration-modal-button').click end it 'user sees creation form with the successful message' do diff --git a/spec/features/groups/dependency_proxy_spec.rb b/spec/features/groups/dependency_proxy_spec.rb index 12c480a46b006..136c1ff0335f1 100644 --- a/spec/features/groups/dependency_proxy_spec.rb +++ b/spec/features/groups/dependency_proxy_spec.rb @@ -62,7 +62,7 @@ visit settings_path wait_for_requests - proxy_toggle = find('[data-testid="dependency-proxy-setting-toggle"]') + proxy_toggle = find_by_testid('dependency-proxy-setting-toggle') proxy_toggle_button = proxy_toggle.find('button') expect(proxy_toggle).to have_css("button.is-checked") diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 0437e5df6e9e0..a7710ea04ab3b 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -141,8 +141,8 @@ end describe 'transfer group', :js do - let(:namespace_select) { page.find('[data-testid="transfer-group-namespace-select"]') } - let(:confirm_modal) { page.find('[data-testid="confirm-danger-modal"]') } + let(:namespace_select) { find_by_testid('transfer-group-namespace-select') } + let(:confirm_modal) { find_by_testid('confirm-danger-modal') } shared_examples 'can transfer the group' do before do @@ -154,7 +154,7 @@ visit edit_group_path(selected_group) - page.within('[data-testid="transfer-locations-dropdown"]') do + within_testid('transfer-locations-dropdown') do click_button _('Select parent group') fill_in _('Search'), with: target_group&.name || '' wait_for_requests @@ -170,7 +170,7 @@ click_button 'Confirm' end - within('[data-testid="breadcrumb-links"]') do + within_testid('breadcrumb-links') do expect(page).to have_content(target_group.name) if target_group expect(page).to have_content(selected_group.name) end diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb index d864852e0d46c..27ceec05fe3ab 100644 --- a/spec/features/groups/members/leave_group_spec.rb +++ b/spec/features/groups/members/leave_group_spec.rb @@ -79,7 +79,7 @@ visit group_path(group, leave: 1) - expect(find('[data-testid="alert-danger"]')).to have_content 'You do not have permission to leave this group' + expect(find_by_testid('alert-danger')).to have_content 'You do not have permission to leave this group' end def left_group_message(group) diff --git a/spec/features/groups/members/manage_groups_spec.rb b/spec/features/groups/members/manage_groups_spec.rb index 87de0e2e46b79..9531ebd3c35fd 100644 --- a/spec/features/groups/members/manage_groups_spec.rb +++ b/spec/features/groups/members/manage_groups_spec.rb @@ -106,7 +106,7 @@ page.within first_row do expect(page).to have_field('Expiration date', with: expiration_date) - find('[data-testid="clear-button"]').click + find_by_testid('clear-button').click wait_for_requests diff --git a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb index c2eedfb40632f..38eb226690c62 100644 --- a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb @@ -48,7 +48,7 @@ page.within second_row do expect(page).to have_field('Expiration date', with: expiration_date) - find('[data-testid="clear-button"]').click + find_by_testid('clear-button').click wait_for_requests diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb index ed2e0cd7b09f9..5f4ff8023c68a 100644 --- a/spec/features/groups/members/search_members_spec.rb +++ b/spec/features/groups/members/search_members_spec.rb @@ -21,7 +21,7 @@ end it 'renders member users' do - page.within '[data-testid="members-filtered-search-bar"]' do + within_testid('members-filtered-search-bar') do find_field('Filter members').click find('input').native.send_keys(member.name) click_button 'Search' diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb index ea6f3ae1966fd..1cc9862ff3b91 100644 --- a/spec/features/groups/members/sort_members_spec.rb +++ b/spec/features/groups/members/sort_members_spec.rb @@ -17,7 +17,7 @@ end def expect_sort_by(text, sort_direction) - within('[data-testid="members-sort-dropdown"]') do + within_testid('members-sort-dropdown') do expect(page).to have_css('button[aria-haspopup="menu"]', text: text) expect(page).to have_button("Sort direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}") end diff --git a/spec/features/groups/members/tabs_spec.rb b/spec/features/groups/members/tabs_spec.rb index cc97b36731372..b96aa9293f221 100644 --- a/spec/features/groups/members/tabs_spec.rb +++ b/spec/features/groups/members/tabs_spec.rb @@ -62,7 +62,7 @@ click_link 'Invited' - page.within '[data-testid="members-filtered-search-bar"]' do + within_testid('members-filtered-search-bar') do find_field('Search invited').click find('input').native.send_keys('email') click_button 'Search' @@ -75,7 +75,7 @@ before do click_link 'Members' - page.within '[data-testid="members-filtered-search-bar"]' do + within_testid 'members-filtered-search-bar' do find_field('Filter members').click find('input').native.send_keys('test') click_button 'Search' diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index 0a830e6715c95..84a6bc96df0a6 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -81,7 +81,7 @@ it 'shows projects only with merge requests feature enabled', :js do click_button 'Select project to create merge request' - page.within('[data-testid="new-resource-dropdown"]') do + within_testid('new-resource-dropdown') do expect(page).to have_content(project.name_with_namespace) expect(page).not_to have_content(project_with_merge_requests_disabled.name_with_namespace) end diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 0a54f5923f2c9..c7e0fdb3fc8d1 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -461,22 +461,6 @@ def upcoming_deployment_content_selector end end - describe 'environments folders view' do - before do - create(:environment, project: project, name: 'staging.review/review-1', state: :available) - create(:environment, project: project, name: 'staging.review/review-2', state: :available) - end - - it 'user opens folder view' do - visit folder_project_environments_path(project, 'staging.review') - wait_for_requests - - expect(page).to have_content('Environments / staging.review') - expect(page).to have_content('review-1') - expect(page).to have_content('review-2') - end - end - def have_terminal_button have_link(_('Terminal'), href: terminal_project_environment_path(project, environment)) end diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb index 61225b457605e..88c16ab4c9f40 100644 --- a/spec/features/projects/user_sees_sidebar_spec.rb +++ b/spec/features/projects/user_sees_sidebar_spec.rb @@ -207,7 +207,7 @@ visit project_path(project) within('.project-repo-buttons') do - expect(page).not_to have_selector '.project-clone-holder' + expect(page).not_to have_selector '.project-code-holder' end end diff --git a/spec/frontend/ci/catalog/components/list/catalog_header_spec.js b/spec/frontend/ci/catalog/components/list/catalog_header_spec.js index 294b5302ec0c0..e9d2e68c1a347 100644 --- a/spec/frontend/ci/catalog/components/list/catalog_header_spec.js +++ b/spec/frontend/ci/catalog/components/list/catalog_header_spec.js @@ -1,6 +1,7 @@ import { GlBanner, GlButton } from '@gitlab/ui'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import BetaBadge from '~/vue_shared/components/badges/beta_badge.vue'; import CatalogHeader from '~/ci/catalog/components/list/catalog_header.vue'; import { CATALOG_FEEDBACK_DISMISSED_KEY } from '~/ci/catalog/constants'; @@ -16,6 +17,7 @@ describe('CatalogHeader', () => { }; const findBanner = () => wrapper.findComponent(GlBanner); + const findBetaBadge = () => wrapper.findComponent(BetaBadge); const findFeedbackButton = () => findBanner().findComponent(GlButton); const findTitle = () => wrapper.find('h1'); const findDescription = () => wrapper.findByTestId('page-description'); @@ -33,6 +35,16 @@ describe('CatalogHeader', () => { }); }; + describe('Default view', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders a Beta Badge', () => { + expect(findBetaBadge().exists()).toBe(true); + }); + }); + describe('title and description', () => { describe('when there are no values provided', () => { beforeEach(() => { @@ -46,6 +58,7 @@ describe('CatalogHeader', () => { ); }); }); + describe('when custom values are provided', () => { beforeEach(() => { createComponent({ provide: customProvide }); @@ -57,6 +70,7 @@ describe('CatalogHeader', () => { }); }); }); + describe('Feedback banner', () => { describe('when user has never dismissed', () => { beforeEach(() => { diff --git a/spec/frontend/environments/folder/environments_folder_app_spec.js b/spec/frontend/environments/folder/environments_folder_app_spec.js new file mode 100644 index 0000000000000..6ed279517bd21 --- /dev/null +++ b/spec/frontend/environments/folder/environments_folder_app_spec.js @@ -0,0 +1,23 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import EnvironmentsFolderAppComponent from '~/environments/folder/environments_folder_app.vue'; + +describe('EnvironmentsFolderAppComponent', () => { + let wrapper; + const mockFolderName = 'folders'; + + const createWrapper = () => { + wrapper = shallowMountExtended(EnvironmentsFolderAppComponent, { + propsData: { + folderName: mockFolderName, + }, + }); + }; + + const findHeader = () => wrapper.findByTestId('folder-name'); + + it('should render a header with the folder name', () => { + createWrapper(); + + expect(findHeader().text()).toMatchInterpolatedText(`Environments / ${mockFolderName}`); + }); +}); diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js index da465552db3a3..6cea75036bc0f 100644 --- a/spec/frontend/gfm_auto_complete_spec.js +++ b/spec/frontend/gfm_auto_complete_spec.js @@ -45,6 +45,16 @@ describe('GfmAutoComplete', () => { let sorterValue; let filterValue; + const triggerDropdown = ($textarea, text) => { + $textarea + .trigger('focus') + .val($textarea.val() + text) + .caret('pos', -1); + $textarea.trigger('keyup'); + + jest.runOnlyPendingTimers(); + }; + describe('DefaultOptions.filter', () => { let items; @@ -786,13 +796,6 @@ describe('GfmAutoComplete', () => { resetHTMLFixture(); }); - const triggerDropdown = (text) => { - $textarea.trigger('focus').val(text).caret('pos', -1); - $textarea.trigger('keyup'); - - jest.runOnlyPendingTimers(); - }; - const getDropdownItems = () => { const dropdown = document.getElementById('at-view-labels'); const items = dropdown.getElementsByTagName('li'); @@ -800,7 +803,7 @@ describe('GfmAutoComplete', () => { }; const expectLabels = ({ input, output }) => { - triggerDropdown(input); + triggerDropdown($textarea, input); expect(getDropdownItems()).toEqual(output.map((label) => label.title)); }; @@ -860,6 +863,50 @@ describe('GfmAutoComplete', () => { }); }); + describe('submit_review', () => { + let autocomplete; + let $textarea; + + const getDropdownItems = () => { + const dropdown = document.getElementById('at-view-submit_review'); + + return dropdown.getElementsByTagName('li'); + }; + + beforeEach(() => { + jest + .spyOn(AjaxCache, 'retrieve') + .mockReturnValue(Promise.resolve([{ name: 'submit_review' }])); + + window.gon = { features: { mrRequestChanges: true } }; + + setHTMLFixture(''); + autocomplete = new GfmAutoComplete({ + commands: `${TEST_HOST}/autocomplete_sources/commands`, + }); + $textarea = $('textarea'); + autocomplete.setup($textarea, {}); + }); + + afterEach(() => { + autocomplete.destroy(); + resetHTMLFixture(); + }); + + it('renders submit review options', async () => { + triggerDropdown($textarea, '/'); + + await waitForPromises(); + + triggerDropdown($textarea, 'submit_review '); + + expect(getDropdownItems()).toHaveLength(3); + expect(getDropdownItems()[0].textContent).toContain('Comment'); + expect(getDropdownItems()[1].textContent).toContain('Approve'); + expect(getDropdownItems()[2].textContent).toContain('Request changes'); + }); + }); + describe('emoji', () => { const mockItem = { 'atwho-at': ':', @@ -951,13 +998,6 @@ describe('GfmAutoComplete', () => { resetHTMLFixture(); }); - const triggerDropdown = (text) => { - $textarea.trigger('focus').val(text).caret('pos', -1); - $textarea.trigger('keyup'); - - jest.runOnlyPendingTimers(); - }; - const getDropdownItems = () => { const dropdown = document.getElementById('at-view-contacts'); const items = dropdown.getElementsByTagName('li'); @@ -965,7 +1005,7 @@ describe('GfmAutoComplete', () => { }; const expectContacts = ({ input, output }) => { - triggerDropdown(input); + triggerDropdown($textarea, input); expect(getDropdownItems()).toEqual( output.map((contact) => `${contact.first_name} ${contact.last_name} ${contact.email}`), diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js index 9413927ac260f..f607d40253f0b 100644 --- a/spec/frontend/search/sidebar/components/app_spec.js +++ b/spec/frontend/search/sidebar/components/app_spec.js @@ -43,11 +43,6 @@ describe('GlobalSearchSidebar', () => { wrapper = shallowMount(GlobalSearchSidebar, { store, - provide: { - glFeatures: { - searchProjectWikisHideArchivedProjects: true, - }, - }, }); }; diff --git a/spec/initializers/circuitbox_spec.rb b/spec/initializers/circuitbox_spec.rb index 64e384e4d2240..d0a6d6c9f9152 100644 --- a/spec/initializers/circuitbox_spec.rb +++ b/spec/initializers/circuitbox_spec.rb @@ -3,12 +3,7 @@ require 'spec_helper' RSpec.describe 'circuitbox', feature_category: :shared do - it 'does not configure Circuitbox', unless: Gitlab.ee? do - expect(Circuitbox.default_circuit_store).to be_a(Circuitbox::MemoryStore) - expect(Circuitbox.default_notifier).to be_a(Circuitbox::Notifier::ActiveSupport) - end - - it 'configures Circuitbox', if: Gitlab.ee? do + it 'configures Circuitbox' do expect(Circuitbox.default_circuit_store).to be_a(Gitlab::CircuitBreaker::Store) expect(Circuitbox.default_notifier).to be_a(Gitlab::CircuitBreaker::Notifier) end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 24d3cac66169c..073d8feaadd32 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -789,7 +789,7 @@ hooks: { pre_get_sources_script: 'echo hello' } } end - it 'returns correct value' do + it 'returns correct values' do expect(entry.value).to eq( name: :rspec, before_script: %w[ls pwd], @@ -806,6 +806,93 @@ ) end end + + context 'with retry present in the config' do + let(:config) do + { + script: 'rspec', + retry: { max: 1, when: "always" } + } + end + + it 'returns correct values' do + expect(entry.value) + .to eq(name: :rspec, + script: %w[rspec], + stage: 'test', + ignore: false, + retry: { max: 1, when: %w[always] }, + only: { refs: %w[branches tags] }, + job_variables: {}, + root_variables_inheritance: true, + scheduling_type: :stage + ) + end + + context 'when ci_retry_on_exit_codes feature flag is disabled' do + before do + stub_feature_flags(ci_retry_on_exit_codes: false) + end + + it 'returns correct values' do + expect(entry.value) + .to eq(name: :rspec, + script: %w[rspec], + stage: 'test', + ignore: false, + retry: { max: 1, when: %w[always] }, + only: { refs: %w[branches tags] }, + job_variables: {}, + root_variables_inheritance: true, + scheduling_type: :stage + ) + end + end + + context 'with exit_codes present' do + let(:config) do + { + script: 'rspec', + retry: { max: 1, when: "always", exit_codes: 255 } + } + end + + it 'returns correct values' do + expect(entry.value) + .to eq(name: :rspec, + script: %w[rspec], + stage: 'test', + ignore: false, + retry: { max: 1, when: %w[always], exit_codes: [255] }, + only: { refs: %w[branches tags] }, + job_variables: {}, + root_variables_inheritance: true, + scheduling_type: :stage + ) + end + + context 'when ci_retry_on_exit_codes feature flag is disabled' do + before do + stub_feature_flags(ci_retry_on_exit_codes: false) + end + + it 'returns correct values' do + expect(entry.value) + .to eq(name: :rspec, + script: %w[rspec], + stage: 'test', + ignore: false, + # Shouldn't include exit_codes + retry: { max: 1, when: %w[always] }, + only: { refs: %w[branches tags] }, + job_variables: {}, + root_variables_inheritance: true, + scheduling_type: :stage + ) + end + end + end + end end context 'when job is using tags' do diff --git a/spec/lib/gitlab/ci/config/entry/retry_spec.rb b/spec/lib/gitlab/ci/config/entry/retry_spec.rb index 84ef5344a8b37..e01b50c5fbd1d 100644 --- a/spec/lib/gitlab/ci/config/entry/retry_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/retry_spec.rb @@ -11,8 +11,9 @@ end shared_context 'when retry value is a hash', :hash do - let(:config) { { max: max, when: public_send(:when) }.compact } + let(:config) { { max: max, when: public_send(:when), exit_codes: public_send(:exit_codes) }.compact } let(:when) {} + let(:exit_codes) {} let(:max) {} end @@ -43,6 +44,44 @@ expect(value).to eq(when: %w[unknown_failure runner_system_failure]) end end + + context 'and `exit_codes` is an integer' do + let(:exit_codes) { 255 } + + it 'returns an array of exit_codes' do + expect(value).to eq(exit_codes: [255]) + end + end + + context 'and `exit_codes` is an array' do + let(:exit_codes) { [255, 142] } + + it 'returns an array of exit_codes' do + expect(value).to eq(exit_codes: [255, 142]) + end + end + end + + context 'when ci_retry_on_exit_codes feature flag is disabled', :hash do + before do + stub_feature_flags(ci_retry_on_exit_codes: false) + end + + context 'when `exit_codes` is an integer' do + let(:exit_codes) { 255 } + + it 'deletes the attribute exit_codes' do + expect(value).to eq({}) + end + end + + context 'when `exit_codes` is an array' do + let(:exit_codes) { [255, 137] } + + it 'deletes the attribute exit_codes' do + expect(value).to eq({}) + end + end end end @@ -65,6 +104,22 @@ end end + context 'with numeric exit_codes' do + let(:exit_codes) { 255 } + + it 'is valid' do + expect(entry).to be_valid + end + end + + context 'with hash values exit_codes' do + let(:exit_codes) { [255, 142] } + + it 'is valid' do + expect(entry).to be_valid + end + end + context 'with string when' do let(:when) { 'unknown_failure' } @@ -202,7 +257,7 @@ end end - context 'iwth max too high' do + context 'with max too high' do let(:max) { 10 } it 'returns error about value too high' do @@ -211,6 +266,33 @@ end end + context 'with exit_codes in wrong format' do + let(:exit_codes) { true } + + it 'raises an error' do + expect(entry).not_to be_valid + expect(entry.errors).to include 'retry exit codes should be an array of integers or an integer' + end + end + + context 'with exit_codes in wrong array format' do + let(:exit_codes) { ['string 1', 'string 2'] } + + it 'raises an error' do + expect(entry).not_to be_valid + expect(entry.errors).to include 'retry exit codes should be an array of integers or an integer' + end + end + + context 'with exit_codes in wrong mixed array format' do + let(:exit_codes) { [255, '155'] } + + it 'raises an error' do + expect(entry).not_to be_valid + expect(entry.errors).to include 'retry exit codes should be an array of integers or an integer' + end + end + context 'with when in wrong format' do let(:when) { true } diff --git a/spec/lib/gitlab/circuit_breaker/notifier_spec.rb b/spec/lib/gitlab/circuit_breaker/notifier_spec.rb new file mode 100644 index 0000000000000..1640ebb99f967 --- /dev/null +++ b/spec/lib/gitlab/circuit_breaker/notifier_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::CircuitBreaker::Notifier, feature_category: :shared do + subject(:instance) { described_class.new } + + describe '#notify' do + context 'when event is failure' do + it 'sends an exception to Gitlab::ErrorTracking' do + expect(Gitlab::ErrorTracking).to receive(:track_exception) + + instance.notify('test_service', 'failure') + end + end + + context 'when event is not failure' do + it 'does not send an exception to Gitlab::ErrorTracking' do + expect(Gitlab::ErrorTracking).not_to receive(:track_exception) + + instance.notify('test_service', 'test_event') + end + end + end + + describe '#notify_warning' do + it do + expect { instance.notify_warning('test_service', 'test_message') }.not_to raise_error + end + end + + describe '#notify_run' do + it do + expect { instance.notify_run('test_service') { puts 'test block' } }.not_to raise_error + end + end +end diff --git a/spec/lib/gitlab/circuit_breaker/store_spec.rb b/spec/lib/gitlab/circuit_breaker/store_spec.rb new file mode 100644 index 0000000000000..1b1983d4b529e --- /dev/null +++ b/spec/lib/gitlab/circuit_breaker/store_spec.rb @@ -0,0 +1,201 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::CircuitBreaker::Store, :clean_gitlab_redis_rate_limiting, feature_category: :ai_abstraction_layer do + let(:key) { 'key-1' } + let(:value) { 'value' } + let(:circuit_store) { described_class.new } + + shared_examples 'reliable circuit breaker store method' do + it 'does not raise an error when Redis::BaseConnectionError is encountered' do + allow(Gitlab::Redis::RateLimiting) + .to receive(:with) + .and_raise(Redis::BaseConnectionError) + + expect { subject }.not_to raise_error + end + end + + describe '#key?' do + subject(:key?) { circuit_store.key?(key) } + + it_behaves_like 'reliable circuit breaker store method' + + context 'when key exists' do + before do + circuit_store.store(key, value) + end + + it { is_expected.to eq(true) } + end + + context 'when key does not exist' do + it { is_expected.to eq(false) } + end + end + + describe '#store' do + let(:options) { {} } + + subject(:store) { circuit_store.store(key, value, options) } + + it_behaves_like 'reliable circuit breaker store method' + + it 'stores value for specified key without expiry by default' do + expect(store).to eq(value) + + with_redis do |redis| + expect(redis.get(key)).to eq(value) + expect(redis.ttl(key)).to eq(-1) + end + end + + context 'when expires option is set' do + let(:options) { { expires: 10 } } + + it 'stores value for specified key with expiry' do + expect(store).to eq(value) + + with_redis do |redis| + expect(redis.get(key)).to eq(value) + expect(redis.ttl(key)).to eq(10) + end + end + end + end + + describe '#increment' do + let(:options) { {} } + + subject(:increment) { circuit_store.increment(key, 1, options) } + + it_behaves_like 'reliable circuit breaker store method' + + context 'when key does not exist' do + it 'sets key and increments value' do + increment + + with_redis do |redis| + expect(redis.get(key).to_i).to eq(1) + expect(redis.ttl(key)).to eq(-1) + end + end + + context 'with expiry' do + let(:options) { { expires: 10 } } + + it 'sets key and increments value with expiration' do + increment + + with_redis do |redis| + expect(redis.get(key).to_i).to eq(1) + expect(redis.ttl(key)).to eq(10) + end + end + end + end + + context 'when key exists' do + before do + circuit_store.store(key, 1) + end + + it 'increments value' do + increment + + with_redis do |redis| + expect(redis.get(key).to_i).to eq(2) + expect(redis.ttl(key)).to eq(-1) + end + end + + context 'with expiry' do + let(:options) { { expires: 10 } } + + it 'increments value with expiration' do + increment + + with_redis do |redis| + expect(redis.get(key).to_i).to eq(2) + expect(redis.ttl(key)).to eq(10) + end + end + end + end + end + + describe '#load' do + subject(:load) { circuit_store.load(key) } + + it_behaves_like 'reliable circuit breaker store method' + + context 'when key exists' do + before do + circuit_store.store(key, value) + end + + it 'returns the value of the key' do + expect(load).to eq(value) + end + end + + context 'when key does not exist' do + it 'returns nil' do + expect(load).to be_nil + end + end + end + + describe '#values_at' do + let(:other_key) { 'key-2' } + let(:other_value) { 'value-2' } + + subject(:values_at) { circuit_store.values_at(key, other_key) } + + it_behaves_like 'reliable circuit breaker store method' + + context 'when keys exist' do + before do + circuit_store.store(key, value) + circuit_store.store(other_key, other_value) + end + + it 'returns values of keys' do + expect(values_at).to match_array([value, other_value]) + end + end + + context 'when some keys do not exist' do + before do + circuit_store.store(key, value) + end + + it 'returns values of keys with nil for non-existing ones' do + expect(values_at).to match_array([value, nil]) + end + end + end + + describe '#delete' do + subject(:delete) { circuit_store.delete(key) } + + before do + circuit_store.store(key, value) + end + + it_behaves_like 'reliable circuit breaker store method' + + it 'deletes key' do + delete + + with_redis do |redis| + expect(redis.exists?(key)).to eq(false) + end + end + end + + def with_redis(&block) + Gitlab::Redis::RateLimiting.with(&block) + end +end diff --git a/spec/lib/gitlab/circuit_breaker_spec.rb b/spec/lib/gitlab/circuit_breaker_spec.rb new file mode 100644 index 0000000000000..4cd2f41869eee --- /dev/null +++ b/spec/lib/gitlab/circuit_breaker_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::CircuitBreaker, :clean_gitlab_redis_rate_limiting, feature_category: :shared do + let(:service_name) { 'DummyService' } + let(:volume_threshold) { 5 } + let(:circuit) do + Circuitbox.circuit(service_name, + { volume_threshold: volume_threshold, exceptions: [Gitlab::CircuitBreaker::InternalServerError] }) + end + + let(:dummy_class) do + Class.new do + def dummy_method + Gitlab::CircuitBreaker.run_with_circuit('DummyService') do + raise Gitlab::CircuitBreaker::InternalServerError + end + end + + def another_dummy_method + Gitlab::CircuitBreaker.run_with_circuit('DummyService') do + # Do nothing but successful. + end + end + end + end + + subject(:instance) { dummy_class.new } + + before do + stub_const(service_name, dummy_class) + allow(Circuitbox).to receive(:circuit).and_return(circuit) + end + + # rubocop: disable RSpec/AnyInstanceOf -- the instance is defined by an initializer + describe '#circuit' do + it 'returns nil value' do + expect(instance.dummy_method).to be_nil + end + + it 'does not raise an error' do + expect { instance.dummy_method }.not_to raise_error + end + + context 'when failed multiple times below volume threshold' do + it 'does not open the circuit' do + expect_any_instance_of(Gitlab::CircuitBreaker::Notifier).to receive(:notify) + .with(anything, 'failure') + .exactly(4).times + + 4.times do + instance.dummy_method + end + + expect(circuit).not_to be_open + end + end + + context 'when failed multiple times over volume threshold' do + it 'allows the call 5 times, then opens the circuit and skips subsequent calls' do + expect_any_instance_of(Gitlab::CircuitBreaker::Notifier).to receive(:notify) + .with(anything, 'failure') + .exactly(5).times + + expect_any_instance_of(Gitlab::CircuitBreaker::Notifier).to receive(:notify) + .with(anything, 'open') + .once + + expect_any_instance_of(Gitlab::CircuitBreaker::Notifier).to receive(:notify) + .with(anything, 'skipped') + .once + + 6.times do + instance.dummy_method + end + + expect(circuit).to be_open + end + end + + context 'when circuit is previously open' do + before do + # Opens the circuit + 6.times do + instance.dummy_method + end + + # Deletes the open key + circuit.try_close_next_time + end + + context 'when does not fail again' do + it 'closes the circuit' do + instance.another_dummy_method + + expect(circuit).not_to be_open + end + end + + context 'when fails again' do + it 'opens the circuit' do + instance.dummy_method + + expect(circuit).to be_open + end + end + end + end + # rubocop: enable RSpec/AnyInstanceOf + + describe '#run_with_circuit' do + let(:block) { proc {} } + + it 'runs the code block within the Circuitbox circuit' do + expect(circuit).to receive(:run).with(exception: false, &block) + described_class.run_with_circuit('service', &block) + end + end +end diff --git a/spec/lib/product_analytics/event_params_spec.rb b/spec/lib/product_analytics/event_params_spec.rb deleted file mode 100644 index e560fd10dfdce..0000000000000 --- a/spec/lib/product_analytics/event_params_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ProductAnalytics::EventParams do - describe '.parse_event_params' do - subject { described_class.parse_event_params(raw_event) } - - let(:raw_event) { Gitlab::Json.parse(fixture_file('product_analytics/event.json')) } - - it 'extracts all params from raw event' do - expected_params = { - project_id: '1', - platform: 'web', - name_tracker: 'sp', - v_tracker: 'js-2.14.0', - event_id: 'fbf14096-74ee-47e4-883c-8a0d6cb72e37', - domain_userid: '79543c31-cfc3-4479-a737-fafb9333c8ba', - domain_sessionid: '54f6d3f3-f4f9-4fdc-87e0-a2c775234c1b', - domain_sessionidx: 4, - page_url: 'http://example.com/products/1', - page_referrer: 'http://example.com/products/1', - br_lang: 'en-US', - br_cookies: true, - os_timezone: 'America/Los_Angeles', - doc_charset: 'UTF-8', - se_category: 'category', - se_action: 'action', - se_label: 'label', - se_property: 'property', - se_value: 12.34 - } - - expect(subject).to include(expected_params) - end - end - - describe '.has_required_params?' do - subject { described_class.has_required_params?(params) } - - context 'aid and eid are present' do - let(:params) { { 'aid' => 1, 'eid' => 2 } } - - it { expect(subject).to be_truthy } - end - - context 'aid and eid are missing' do - let(:params) { {} } - - it { expect(subject).to be_falsey } - end - - context 'eid is missing' do - let(:params) { { 'aid' => 1 } } - - it { expect(subject).to be_falsey } - end - end -end diff --git a/spec/models/product_analytics_event_spec.rb b/spec/models/product_analytics_event_spec.rb deleted file mode 100644 index 801e6dd5e10b6..0000000000000 --- a/spec/models/product_analytics_event_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' - -RSpec.describe ProductAnalyticsEvent, type: :model do - it { is_expected.to belong_to(:project) } - it { expect(described_class).to respond_to(:order_by_time) } - - describe 'validations' do - it { is_expected.to validate_presence_of(:project_id) } - it { is_expected.to validate_presence_of(:event_id) } - it { is_expected.to validate_presence_of(:v_collector) } - it { is_expected.to validate_presence_of(:v_etl) } - end - - describe '.timerange' do - let_it_be(:event_1) { create(:product_analytics_event, collector_tstamp: Time.zone.now - 1.day) } - let_it_be(:event_2) { create(:product_analytics_event, collector_tstamp: Time.zone.now - 5.days) } - let_it_be(:event_3) { create(:product_analytics_event, collector_tstamp: Time.zone.now - 15.days) } - - it { expect(described_class.timerange(3.days)).to match_array([event_1]) } - it { expect(described_class.timerange(7.days)).to match_array([event_1, event_2]) } - it { expect(described_class.timerange(30.days)).to match_array([event_1, event_2, event_3]) } - end - - describe '.count_by_graph' do - let_it_be(:events) do - [ - create(:product_analytics_event, platform: 'web'), - create(:product_analytics_event, platform: 'web'), - create(:product_analytics_event, platform: 'app'), - create(:product_analytics_event, platform: 'mobile', collector_tstamp: Time.zone.now - 10.days) - ] - end - - it { expect(described_class.count_by_graph('platform', 7.days)).to eq({ 'app' => 1, 'web' => 2 }) } - it { expect(described_class.count_by_graph('platform', 30.days)).to eq({ 'app' => 1, 'mobile' => 1, 'web' => 2 }) } - end - - describe '.count_collector_tstamp_by_day' do - let_it_be(:time_now) { Time.zone.now } - let_it_be(:time_ago) { Time.zone.now - 5.days } - - let_it_be(:events) do - create_list(:product_analytics_event, 3, collector_tstamp: time_now) + - create_list(:product_analytics_event, 2, collector_tstamp: time_ago) - end - - subject { described_class.count_collector_tstamp_by_day(7.days) } - - it { is_expected.to eq({ time_now.beginning_of_day => 3, time_ago.beginning_of_day => 2 }) } - end -end diff --git a/spec/services/product_analytics/build_activity_graph_service_spec.rb b/spec/services/product_analytics/build_activity_graph_service_spec.rb deleted file mode 100644 index 2eb35523da779..0000000000000 --- a/spec/services/product_analytics/build_activity_graph_service_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ProductAnalytics::BuildActivityGraphService, feature_category: :product_analytics_data_management do - let_it_be(:project) { create(:project) } - let_it_be(:time_now) { Time.zone.now } - let_it_be(:time_ago) { Time.zone.now - 5.days } - - let_it_be(:events) do - [ - create(:product_analytics_event, project: project, collector_tstamp: time_now), - create(:product_analytics_event, project: project, collector_tstamp: time_now), - create(:product_analytics_event, project: project, collector_tstamp: time_now), - create(:product_analytics_event, project: project, collector_tstamp: time_ago), - create(:product_analytics_event, project: project, collector_tstamp: time_ago) - ] - end - - let(:params) { { timerange: 7 } } - - subject { described_class.new(project, params).execute } - - it 'returns a valid graph hash' do - expected_hash = { - id: 'collector_tstamp', - keys: [time_ago.to_date, time_now.to_date], - values: [2, 3] - } - - expect(subject).to eq(expected_hash) - end -end diff --git a/spec/services/product_analytics/build_graph_service_spec.rb b/spec/services/product_analytics/build_graph_service_spec.rb deleted file mode 100644 index a850d69e53cd9..0000000000000 --- a/spec/services/product_analytics/build_graph_service_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ProductAnalytics::BuildGraphService, feature_category: :product_analytics_data_management do - let_it_be(:project) { create(:project) } - - let_it_be(:events) do - [ - create(:product_analytics_event, project: project, platform: 'web'), - create(:product_analytics_event, project: project, platform: 'web'), - create(:product_analytics_event, project: project, platform: 'app'), - create(:product_analytics_event, project: project, platform: 'mobile'), - create(:product_analytics_event, project: project, platform: 'mobile', collector_tstamp: Time.zone.now - 60.days) - ] - end - - let(:params) { { graph: 'platform', timerange: 5 } } - - subject { described_class.new(project, params).execute } - - it 'returns a valid graph hash' do - expect(subject[:id]).to eq(:platform) - expect(subject[:keys]).to eq(%w[app mobile web]) - expect(subject[:values]).to eq([1, 1, 2]) - end -end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 0b09bb353d553..90229f54dec42 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -2185,6 +2185,36 @@ expect(message).to eq('Submitted the current review.') end end + + context 'when parameters are passed' do + context 'with approve parameter' do + it 'calls MergeRequests::ApprovalService service' do + expect_next_instance_of( + MergeRequests::ApprovalService, project: merge_request.project, current_user: current_user + ) do |service| + expect(service).to receive(:execute).with(merge_request) + end + + _, _, message = service.execute('/submit_review approve', merge_request) + + expect(message).to eq('Submitted the current review.') + end + end + + context 'with review state parameter' do + it 'calls MergeRequests::UpdateReviewerStateService service' do + expect_next_instance_of( + MergeRequests::UpdateReviewerStateService, project: merge_request.project, current_user: current_user + ) do |service| + expect(service).to receive(:execute).with(merge_request, 'requested_changes') + end + + _, _, message = service.execute('/submit_review requested_changes', merge_request) + + expect(message).to eq('Submitted the current review.') + end + end + end end context 'request_changes command' do diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 17dc7fa6dbf81..13858360a6c3b 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -6920,7 +6920,6 @@ - './spec/lib/peek/views/external_http_spec.rb' - './spec/lib/peek/views/memory_spec.rb' - './spec/lib/peek/views/redis_detailed_spec.rb' -- './spec/lib/product_analytics/event_params_spec.rb' - './spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb' - './spec/lib/prometheus/pid_provider_spec.rb' - './spec/lib/quality/seeders/issues_spec.rb' @@ -7510,7 +7509,6 @@ - './spec/models/preloaders/merge_request_diff_preloader_spec.rb' - './spec/models/preloaders/user_max_access_level_in_groups_preloader_spec.rb' - './spec/models/preloaders/user_max_access_level_in_projects_preloader_spec.rb' -- './spec/models/product_analytics_event_spec.rb' - './spec/models/programming_language_spec.rb' - './spec/models/project_authorization_spec.rb' - './spec/models/project_auto_devops_spec.rb' @@ -8989,8 +8987,6 @@ - './spec/services/personal_access_tokens/revoke_service_spec.rb' - './spec/services/post_receive_service_spec.rb' - './spec/services/preview_markdown_service_spec.rb' -- './spec/services/product_analytics/build_activity_graph_service_spec.rb' -- './spec/services/product_analytics/build_graph_service_spec.rb' - './spec/services/projects/after_rename_service_spec.rb' - './spec/services/projects/alerting/notify_service_spec.rb' - './spec/services/projects/all_issues_count_service_spec.rb' diff --git a/spec/views/projects/tags/index.html.haml_spec.rb b/spec/views/projects/tags/index.html.haml_spec.rb index 0ac5efa2e6d4a..6cfc85287cd6d 100644 --- a/spec/views/projects/tags/index.html.haml_spec.rb +++ b/spec/views/projects/tags/index.html.haml_spec.rb @@ -92,13 +92,12 @@ render expect(page.find('.tags .content-list li', text: tag)).to have_css '[data-testid="status_success_borderless-icon"]' - expect(page.all('.tags .content-list li')).to all(have_css('svg.s16')) end it 'shows no build status or placeholder when no pipelines present' do render - expect(page.all('.tags .content-list li')).not_to have_css 'svg.s16' + expect(page.find('.tags .content-list li', text: tag)).not_to have_css '[data-testid="status_success_borderless-icon"]' end it 'shows no build status or placeholder when pipelines are private' do @@ -107,7 +106,7 @@ render - expect(page.all('.tags .content-list li')).not_to have_css 'svg.s16' + expect(page.find('.tags .content-list li', text: tag)).not_to have_css '[data-testid="status_success_borderless-icon"]' end end