Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml21
-rw-r--r--.gitlab/ci/yaml.gitlab-ci.yml4
-rw-r--r--.rubocop_todo/cop/user_admin.yml1
-rw-r--r--.rubocop_todo/gitlab/feature_available_usage.yml1
-rw-r--r--.rubocop_todo/gitlab/namespaced_class.yml7
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml2
-rw-r--r--.rubocop_todo/layout/line_length.yml9
-rw-r--r--.rubocop_todo/rspec/verified_doubles.yml11
-rw-r--r--.rubocop_todo/style/bare_percent_literals.yml1
-rw-r--r--.rubocop_todo/style/class_and_module_children.yml2
-rw-r--r--.rubocop_todo/style/format_string.yml2
-rw-r--r--.rubocop_todo/style/guard_clause.yml6
-rw-r--r--.rubocop_todo/style/if_inside_else.yml1
-rw-r--r--.rubocop_todo/style/if_unless_modifier.yml12
-rw-r--r--.rubocop_todo/style/next.yml1
-rw-r--r--.rubocop_todo/style/numeric_literal_prefix.yml4
-rw-r--r--.rubocop_todo/style/percent_literal_delimiters.yml11
-rw-r--r--.rubocop_todo/style/redundant_interpolation.yml1
-rw-r--r--.rubocop_todo/style/redundant_regexp_escape.yml1
-rw-r--r--.rubocop_todo/style/redundant_self.yml4
-rw-r--r--.rubocop_todo/style/string_concatenation.yml2
-rw-r--r--.rubocop_todo/style/string_literals_in_interpolation.yml2
-rw-r--r--CHANGELOG.md7
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--app/assets/javascripts/api.js6
-rw-r--r--app/assets/javascripts/behaviors/components/sandboxed_mermaid.vue77
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js7
-rw-r--r--app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue51
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_more_dropdown.vue34
-rw-r--r--app/assets/javascripts/content_editor/components/top_toolbar.vue14
-rw-r--r--app/assets/javascripts/content_editor/components/wrappers/code_block.vue79
-rw-r--r--app/assets/javascripts/content_editor/extensions/diagram.js22
-rw-r--r--app/assets/javascripts/content_editor/services/asset_resolver.js15
-rw-r--r--app/assets/javascripts/content_editor/services/code_block_language_loader.js6
-rw-r--r--app/assets/javascripts/content_editor/services/content_editor.js4
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/description.vue8
-rw-r--r--app/assets/javascripts/lib/utils/color_utils.js2
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue75
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata_loader.vue30
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/composer.vue10
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/conan.vue4
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/maven.vue6
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/nuget.vue14
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/pypi.vue4
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue20
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/constants.js3
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql33
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_metadata.query.graphql39
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue27
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue17
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js4
-rw-r--r--app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/participants/participants.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue4
-rw-r--r--app/assets/javascripts/work_items/components/item_state.vue18
-rw-r--r--app/assets/javascripts/work_items/components/item_title.vue9
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue4
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail_modal.vue13
-rw-r--r--app/assets/stylesheets/framework/diffs.scss2
-rw-r--r--app/assets/stylesheets/framework/feature_highlight.scss2
-rw-r--r--app/assets/stylesheets/framework/header.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss2
-rw-r--r--app/assets/stylesheets/framework/responsive_tables.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss9
-rw-r--r--app/assets/stylesheets/framework/wells.scss2
-rw-r--r--app/assets/stylesheets/highlight/conflict_colors.scss12
-rw-r--r--app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss4
-rw-r--r--app/assets/stylesheets/pages/editor.scss2
-rw-r--r--app/assets/stylesheets/pages/issues.scss6
-rw-r--r--app/assets/stylesheets/pages/search.scss2
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss12
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss2
-rw-r--r--app/assets/stylesheets/startup/startup-signin.scss2
-rw-r--r--app/assets/stylesheets/themes/_dark.scss2
-rw-r--r--app/finders/crm/contacts_finder.rb12
-rw-r--r--app/finders/crm/organizations_finder.rb12
-rw-r--r--app/graphql/mutations/incident_management/timeline_event/create.rb4
-rw-r--r--app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb3
-rw-r--r--app/graphql/resolvers/crm/contacts_resolver.rb7
-rw-r--r--app/graphql/resolvers/crm/organizations_resolver.rb7
-rw-r--r--app/models/projects/build_artifacts_size_refresh.rb4
-rw-r--r--app/models/release.rb5
-rw-r--r--app/services/incident_management/timeline_events/create_service.rb4
-rw-r--r--app/services/incident_management/timeline_events/update_service.rb5
-rw-r--r--app/views/profiles/keys/_form.html.haml2
-rw-r--r--config/feature_flags/development/validate_release_with_author.yml8
-rw-r--r--config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml23
-rw-r--r--config/metrics/counts_7d/20210216174846_p_analytics_pipelines.yml (renamed from config/metrics/counts_all/20210216174846_p_analytics_pipelines.yml)6
-rw-r--r--config/metrics/counts_7d/20210216174850_p_analytics_valuestream.yml (renamed from config/metrics/counts_all/20210216174850_p_analytics_valuestream.yml)6
-rw-r--r--config/metrics/counts_7d/20210216174856_p_analytics_repo.yml (renamed from config/metrics/counts_all/20210216174856_p_analytics_repo.yml)6
-rw-r--r--config/metrics/counts_7d/20210216174858_i_analytics_cohorts.yml (renamed from config/metrics/counts_all/20210216174858_i_analytics_cohorts.yml)6
-rw-r--r--config/metrics/counts_7d/20210216174900_i_analytics_dev_ops_score.yml (renamed from config/metrics/counts_all/20210216174900_i_analytics_dev_ops_score.yml)6
-rw-r--r--config/metrics/counts_7d/20210216174902_g_analytics_merge_request.yml (renamed from config/metrics/counts_all/20210216174902_g_analytics_merge_request.yml)6
-rw-r--r--config/metrics/counts_7d/20210216174906_i_analytics_instance_statistics.yml (renamed from config/metrics/counts_all/20210216174906_i_analytics_instance_statistics.yml)6
-rw-r--r--config/metrics/counts_7d/20210216174908_analytics_unique_visits_for_any_target.yml44
-rw-r--r--config/metrics/counts_7d/20210423005644_i_analytics_dev_ops_adoption.yml (renamed from config/metrics/counts_all/20210423005644_i_analytics_dev_ops_adoption.yml)6
-rw-r--r--config/metrics/counts_7d/20210428142406_users_viewing_analytics_group_devops_adoption.yml (renamed from config/metrics/counts_all/20210428142406_users_viewing_analytics_group_devops_adoption.yml)6
-rw-r--r--config/metrics/counts_7d/20211126090001_p_analytics_ci_cd_pipelines.yml (renamed from config/metrics/counts_all/20211126090001_p_analytics_ci_cd_pipelines.yml)6
-rw-r--r--config/metrics/counts_7d/20211126090002_p_analytics_ci_cd_deployment_frequency.yml (renamed from config/metrics/counts_all/20211126090002_p_analytics_ci_cd_deployment_frequency.yml)6
-rw-r--r--config/metrics/counts_7d/20211126090003_p_analytics_ci_cd_lead_time.yml (renamed from config/metrics/counts_all/20211126090003_p_analytics_ci_cd_lead_time.yml)6
-rw-r--r--config/metrics/counts_all/20210216174908_analytics_unique_visits_for_any_target.yml21
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/development/i18n/proofreader.md1
-rw-r--r--lib/gitlab/analytics/unique_visits.rb8
-rw-r--r--lib/gitlab/usage_data.rb11
-rw-r--r--locale/gitlab.pot59
-rw-r--r--spec/factories/incident_management/timeline_events.rb9
-rw-r--r--spec/factories/releases.rb6
-rw-r--r--spec/finders/crm/contacts_finder_spec.rb8
-rw-r--r--spec/finders/crm/organizations_finder_spec.rb8
-rw-r--r--spec/frontend/admin/signup_restrictions/components/signup_form_spec.js7
-rw-r--r--spec/frontend/admin/signup_restrictions/mock_data.js8
-rw-r--r--spec/frontend/admin/signup_restrictions/utils_spec.js4
-rw-r--r--spec/frontend/api_spec.js6
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/code_block_spec.js48
-rw-r--r--spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js54
-rw-r--r--spec/frontend/content_editor/components/top_toolbar_spec.js29
-rw-r--r--spec/frontend/content_editor/components/wrappers/code_block_spec.js83
-rw-r--r--spec/frontend/content_editor/services/asset_resolver_spec.js10
-rw-r--r--spec/frontend/content_editor/services/code_block_language_loader_spec.js7
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js84
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js10
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js10
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js12
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js32
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js9
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js52
-rw-r--r--spec/frontend/packages_and_registries/package_registry/mock_data.js43
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/details_spec.js41
-rw-r--r--spec/frontend/work_items/components/item_title_spec.js2
-rw-r--r--spec/graphql/mutations/incident_management/timeline_event/create_spec.rb3
-rw-r--r--spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb3
-rw-r--r--spec/graphql/resolvers/crm/contacts_resolver_spec.rb16
-rw-r--r--spec/graphql/resolvers/crm/organizations_resolver_spec.rb18
-rw-r--r--spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb4
-rw-r--r--spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb4
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb40
-rw-r--r--spec/models/customer_relations/contact_spec.rb95
-rw-r--r--spec/models/customer_relations/organization_spec.rb79
-rw-r--r--spec/models/milestone_spec.rb6
-rw-r--r--spec/models/projects/build_artifacts_size_refresh_spec.rb10
-rw-r--r--spec/models/release_spec.rb26
-rw-r--r--spec/presenters/project_presenter_spec.rb5
-rw-r--r--spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/incident_management/timeline_event/destroy_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb2
-rw-r--r--spec/services/incident_management/timeline_events/create_service_spec.rb26
-rw-r--r--spec/services/incident_management/timeline_events/update_service_spec.rb8
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb45
-rw-r--r--spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb1
158 files changed, 1659 insertions, 493 deletions
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index 7f373769ded..68c71b359c2 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -91,7 +91,7 @@ gemnasium-python-dependency_scanning:
yarn-audit-dependency_scanning:
extends: .ds-analyzer
- image: "${REGISTRY_HOST}/${REGISTRY_GROUP}/security-products/analyzers/npm-audit:1.4.1"
+ image: "${REGISTRY_HOST}/${REGISTRY_GROUP}/security-products/analyzers/npm-audit:1"
variables:
TOOL: yarn
rules: !reference [".reports:rules:yarn-audit-dependency_scanning", rules]
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 3a8a8677706..ba64a1d91b1 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -184,6 +184,17 @@
- "*.yml"
- "**/*.yml"
+.lint-pipeline-yaml-patterns: &lint-pipeline-yaml-patterns
+ - ".gitlab-ci.yml"
+ - ".gitlab/ci/**/*.yml"
+ - "lib/gitlab/ci/templates/**/*.yml"
+ - "data/deprecations/**/*.yml"
+ - "data/removals/**/*.yml"
+ - "data/whats_new/**/*.yml"
+
+.lint-metrics-yaml-patterns: &lint-metrics-yaml-patterns
+ - "config/metrics/**/*.yml"
+
.docs-patterns: &docs-patterns
- ".gitlab/route-map.yml"
- "doc/**/*"
@@ -1801,3 +1812,13 @@
rules:
- <<: *if-default-refs
changes: *yaml-lint-patterns
+
+.lint-pipeline-yaml:rules:
+ rules:
+ - <<: *if-default-refs
+ changes: *lint-pipeline-yaml-patterns
+
+.lint-metrics-yaml:rules:
+ rules:
+ - <<: *if-default-refs
+ changes: *lint-metrics-yaml-patterns
diff --git a/.gitlab/ci/yaml.gitlab-ci.yml b/.gitlab/ci/yaml.gitlab-ci.yml
index a0665d712dc..0420f158bbb 100644
--- a/.gitlab/ci/yaml.gitlab-ci.yml
+++ b/.gitlab/ci/yaml.gitlab-ci.yml
@@ -18,7 +18,7 @@ lint-yaml:
lint-pipeline-yaml:
extends:
- .default-retry
- - .yaml-lint:rules
+ - .lint-pipeline-yaml:rules
image: pipelinecomponents/yamllint:latest
stage: lint
needs: []
@@ -30,7 +30,7 @@ lint-pipeline-yaml:
lint-metrics-yaml:
extends:
- .default-retry
- - .yaml-lint:rules
+ - .lint-metrics-yaml:rules
image: pipelinecomponents/yamllint:latest
stage: lint
needs: []
diff --git a/.rubocop_todo/cop/user_admin.yml b/.rubocop_todo/cop/user_admin.yml
index 5f0f7213950..ab5f76a002d 100644
--- a/.rubocop_todo/cop/user_admin.yml
+++ b/.rubocop_todo/cop/user_admin.yml
@@ -26,7 +26,6 @@ Cop/UserAdmin:
- app/models/protected_branch.rb
- app/models/user.rb
- app/policies/note_policy.rb
- - app/serializers/deploy_key_entity.rb
- app/services/auth/container_registry_authentication_service.rb
- app/services/emails/create_service.rb
- app/services/projects/enable_deploy_key_service.rb
diff --git a/.rubocop_todo/gitlab/feature_available_usage.yml b/.rubocop_todo/gitlab/feature_available_usage.yml
index 2a0975a6e9b..168edd94f90 100644
--- a/.rubocop_todo/gitlab/feature_available_usage.yml
+++ b/.rubocop_todo/gitlab/feature_available_usage.yml
@@ -25,7 +25,6 @@ Gitlab/FeatureAvailableUsage:
- ee/app/controllers/projects/subscriptions_controller.rb
- ee/app/finders/autocomplete/vulnerabilities_autocomplete_finder.rb
- ee/app/finders/clusters/agents_finder.rb
- - ee/app/finders/ee/alert_management/alerts_finder.rb
- ee/app/finders/ee/alert_management/http_integrations_finder.rb
- ee/app/graphql/ee/types/group_type.rb
- ee/app/graphql/mutations/dast/profiles/create.rb
diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml
index 0125456954a..23ff86ba289 100644
--- a/.rubocop_todo/gitlab/namespaced_class.yml
+++ b/.rubocop_todo/gitlab/namespaced_class.yml
@@ -500,8 +500,6 @@ Gitlab/NamespacedClass:
- 'app/serializers/current_board_entity.rb'
- 'app/serializers/current_board_serializer.rb'
- 'app/serializers/current_user_entity.rb'
- - 'app/serializers/deploy_key_entity.rb'
- - 'app/serializers/deploy_key_serializer.rb'
- 'app/serializers/deploy_keys_project_entity.rb'
- 'app/serializers/deployment_cluster_entity.rb'
- 'app/serializers/deployment_entity.rb'
@@ -770,7 +768,6 @@ Gitlab/NamespacedClass:
- 'app/workers/error_tracking_issue_link_worker.rb'
- 'app/workers/expire_build_artifacts_worker.rb'
- 'app/workers/expire_job_cache_worker.rb'
- - 'app/workers/expire_pipeline_cache_worker.rb'
- 'app/workers/export_csv_worker.rb'
- 'app/workers/external_service_reactive_caching_worker.rb'
- 'app/workers/file_hook_worker.rb'
@@ -812,7 +809,6 @@ Gitlab/NamespacedClass:
- 'app/workers/post_receive.rb'
- 'app/workers/process_commit_worker.rb'
- 'app/workers/project_cache_worker.rb'
- - 'app/workers/project_daily_statistics_worker.rb'
- 'app/workers/project_destroy_worker.rb'
- 'app/workers/project_export_worker.rb'
- 'app/workers/propagate_integration_group_worker.rb'
@@ -1017,7 +1013,6 @@ Gitlab/NamespacedClass:
- 'ee/app/serializers/group_analytics_serializer.rb'
- 'ee/app/serializers/group_vulnerability_autocomplete_entity.rb'
- 'ee/app/serializers/group_vulnerability_autocomplete_serializer.rb'
- - 'ee/app/serializers/invited_group_entity.rb'
- 'ee/app/serializers/invited_group_serializer.rb'
- 'ee/app/serializers/iteration_serializer.rb'
- 'ee/app/serializers/license_entity.rb'
@@ -1216,7 +1211,6 @@ Gitlab/NamespacedClass:
- 'lib/gitlab/object_hierarchy.rb'
- 'lib/gitlab/omniauth_initializer.rb'
- 'lib/gitlab/otp_key_rotator.rb'
- - 'lib/gitlab/pages_transfer.rb'
- 'lib/gitlab/pipeline_scope_counts.rb'
- 'lib/gitlab/polling_interval.rb'
- 'lib/gitlab/process_memory_cache.rb'
@@ -1270,7 +1264,6 @@ Gitlab/NamespacedClass:
- 'lib/gitlab/unicode.rb'
- 'lib/gitlab/untrusted_regexp.rb'
- 'lib/gitlab/untrusted_regexp/ruby_syntax.rb'
- - 'lib/gitlab/updated_notes_paginator.rb'
- 'lib/gitlab/uploads_transfer.rb'
- 'lib/gitlab/url_blocker.rb'
- 'lib/gitlab/url_builder.rb'
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index 6cbc062aae6..e889a05ad36 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -293,7 +293,6 @@ Layout/ArgumentAlignment:
- 'ee/app/controllers/subscriptions_controller.rb'
- 'ee/app/finders/geo/registry_finder.rb'
- 'ee/app/graphql/ee/mutations/boards/issues/issue_move_list.rb'
- - 'ee/app/graphql/ee/mutations/ci/ci_cd_settings_update.rb'
- 'ee/app/graphql/ee/types/ci/pipeline_type.rb'
- 'ee/app/graphql/ee/types/deprecated_mutations.rb'
- 'ee/app/graphql/mutations/app_sec/fuzzing/api/ci_configuration/create.rb'
@@ -395,7 +394,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/features/uncompleted_learn_gitlab_link_spec.rb'
- 'ee/spec/finders/security/pipeline_vulnerabilities_finder_spec.rb'
- 'ee/spec/frontend/fixtures/search.rb'
- - 'ee/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb'
- 'ee/spec/graphql/mutations/requirements_management/export_requirements_spec.rb'
- 'ee/spec/helpers/billing_plans_helper_spec.rb'
- 'ee/spec/helpers/ee/users/callouts_helper_spec.rb'
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index 4c4e13b0436..16d22183345 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -383,7 +383,6 @@ Layout/LineLength:
- 'app/models/concerns/redis_cacheable.rb'
- 'app/models/concerns/restricted_signup.rb'
- 'app/models/concerns/routable.rb'
- - 'app/models/concerns/sha256_attribute.rb'
- 'app/models/concerns/shardable.rb'
- 'app/models/concerns/sortable.rb'
- 'app/models/concerns/storage/legacy_namespace.rb'
@@ -529,7 +528,6 @@ Layout/LineLength:
- 'app/serializers/base_discussion_entity.rb'
- 'app/serializers/build_details_entity.rb'
- 'app/serializers/ci/pipeline_entity.rb'
- - 'app/serializers/deploy_key_entity.rb'
- 'app/serializers/deployment_cluster_entity.rb'
- 'app/serializers/deployment_entity.rb'
- 'app/serializers/diff_file_base_entity.rb'
@@ -649,7 +647,6 @@ Layout/LineLength:
- 'app/services/members/approve_access_request_service.rb'
- 'app/services/members/destroy_service.rb'
- 'app/services/members/invitation_reminder_email_service.rb'
- - 'app/services/members/mailgun/process_webhook_service.rb'
- 'app/services/members/update_service.rb'
- 'app/services/merge_requests/add_context_service.rb'
- 'app/services/merge_requests/assign_issues_service.rb'
@@ -795,7 +792,6 @@ Layout/LineLength:
- 'app/workers/packages/maven/metadata/sync_worker.rb'
- 'app/workers/personal_access_tokens/expired_notification_worker.rb'
- 'app/workers/pipeline_metrics_worker.rb'
- - 'app/workers/quality/test_data_cleanup_worker.rb'
- 'app/workers/repository_fork_worker.rb'
- 'app/workers/repository_import_worker.rb'
- 'app/workers/ssh_keys/expired_notification_worker.rb'
@@ -831,7 +827,6 @@ Layout/LineLength:
- 'config/initializers/wikicloth_redos_patch.rb'
- 'config/initializers/zz_metrics.rb'
- 'config/object_store_settings.rb'
- - 'config/puma.rb'
- 'config/routes.rb'
- 'config/routes/admin.rb'
- 'config/routes/api.rb'
@@ -1176,7 +1171,6 @@ Layout/LineLength:
- 'ee/app/controllers/projects/requirements_management/requirements_controller.rb'
- 'ee/app/controllers/projects/security/policies_controller.rb'
- 'ee/app/controllers/projects/security/vulnerabilities/notes_controller.rb'
- - 'ee/app/controllers/projects/threat_monitoring_controller.rb'
- 'ee/app/controllers/registrations/groups_controller.rb'
- 'ee/app/controllers/registrations/groups_projects_controller.rb'
- 'ee/app/controllers/subscriptions_controller.rb'
@@ -1324,7 +1318,6 @@ Layout/LineLength:
- 'ee/app/helpers/projects/security/dast_configuration_helper.rb'
- 'ee/app/helpers/projects/security/dast_profiles_helper.rb'
- 'ee/app/helpers/projects/security/discover_helper.rb'
- - 'ee/app/helpers/projects/security/policies_helper.rb'
- 'ee/app/helpers/push_rules_helper.rb'
- 'ee/app/helpers/seats_count_alert_helper.rb'
- 'ee/app/helpers/security_helper.rb'
@@ -1524,7 +1517,6 @@ Layout/LineLength:
- 'ee/app/services/ee/users/update_service.rb'
- 'ee/app/services/elastic/cluster_reindexing_service.rb'
- 'ee/app/services/elastic/indexing_control_service.rb'
- - 'ee/app/services/epic_links/create_service.rb'
- 'ee/app/services/epic_links/list_service.rb'
- 'ee/app/services/epics/issue_promote_service.rb'
- 'ee/app/services/epics/update_service.rb'
@@ -1818,7 +1810,6 @@ Layout/LineLength:
- 'ee/lib/gitlab/geo/jwt_request_decoder.rb'
- 'ee/lib/gitlab/geo/log_cursor/events/repository_created_event.rb'
- 'ee/lib/gitlab/geo/registry_batcher.rb'
- - 'ee/lib/gitlab/geo/replication/base_downloader.rb'
- 'ee/lib/gitlab/geo/replication/base_transfer.rb'
- 'ee/lib/gitlab/geo/replication/blob_downloader.rb'
- 'ee/lib/gitlab/geo/replicator.rb'
diff --git a/.rubocop_todo/rspec/verified_doubles.yml b/.rubocop_todo/rspec/verified_doubles.yml
index 0c920287bfe..a69ab9198b1 100644
--- a/.rubocop_todo/rspec/verified_doubles.yml
+++ b/.rubocop_todo/rspec/verified_doubles.yml
@@ -79,8 +79,6 @@ RSpec/VerifiedDoubles:
- ee/spec/lib/gitlab/geo/log_cursor/lease_spec.rb
- ee/spec/lib/gitlab/geo/oauth/logout_token_spec.rb
- ee/spec/lib/gitlab/geo/oauth/session_spec.rb
- - ee/spec/lib/gitlab/geo/replication/job_artifact_retriever_spec.rb
- - ee/spec/lib/gitlab/geo/replication/job_artifact_transfer_spec.rb
- ee/spec/lib/gitlab/geo/replicator_spec.rb
- ee/spec/lib/gitlab/geo_spec.rb
- ee/spec/lib/gitlab/git_access_spec.rb
@@ -91,7 +89,6 @@ RSpec/VerifiedDoubles:
- ee/spec/lib/gitlab/prometheus/queries/cluster_query_spec.rb
- ee/spec/lib/gitlab/subscription_portal/clients/rest_spec.rb
- ee/spec/lib/sidebars/groups/menus/analytics_menu_spec.rb
- - ee/spec/lib/system_check/app/elasticsearch_check_spec.rb
- ee/spec/lib/system_check/geo/geo_database_configured_check_spec.rb
- ee/spec/models/app_sec/fuzzing/api/ci_configuration_spec.rb
- ee/spec/models/approvable_spec.rb
@@ -111,7 +108,6 @@ RSpec/VerifiedDoubles:
- ee/spec/presenters/group_member_presenter_spec.rb
- ee/spec/presenters/merge_request_approver_presenter_spec.rb
- ee/spec/presenters/project_member_presenter_spec.rb
- - ee/spec/requests/api/alert_management_alerts_spec.rb
- ee/spec/requests/api/geo_spec.rb
- ee/spec/requests/api/graphql/mutations/dast_site_profiles/delete_spec.rb
- ee/spec/requests/api/internal/base_spec.rb
@@ -142,7 +138,6 @@ RSpec/VerifiedDoubles:
- ee/spec/serializers/issuable_sidebar_extras_entity_spec.rb
- ee/spec/serializers/issues/linked_issue_feature_flag_entity_spec.rb
- ee/spec/serializers/linked_feature_flag_issue_entity_spec.rb
- - ee/spec/serializers/merge_request_compliance_entity_spec.rb
- ee/spec/serializers/merge_request_poll_widget_entity_spec.rb
- ee/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
- ee/spec/serializers/merge_request_widget_entity_spec.rb
@@ -173,9 +168,6 @@ RSpec/VerifiedDoubles:
- ee/spec/services/ee/merge_requests/refresh_service_spec.rb
- ee/spec/services/ee/notification_service_spec.rb
- ee/spec/services/ee/post_receive_service_spec.rb
- - ee/spec/services/ee/service_ping/build_payload_service_spec.rb
- - ee/spec/services/ee/service_ping/permit_data_categories_service_spec.rb
- - ee/spec/services/ee/service_ping/service_ping_settings_spec.rb
- ee/spec/services/geo/blob_download_service_spec.rb
- ee/spec/services/geo/graphql_request_service_spec.rb
- ee/spec/services/geo/node_status_request_service_spec.rb
@@ -1040,8 +1032,6 @@ RSpec/VerifiedDoubles:
- spec/services/repositories/changelog_service_spec.rb
- spec/services/search_service_spec.rb
- spec/services/service_ping/build_payload_service_spec.rb
- - spec/services/service_ping/permit_data_categories_service_spec.rb
- - spec/services/service_ping/service_ping_settings_spec.rb
- spec/services/service_ping/submit_service_ping_service_spec.rb
- spec/services/snippets/update_repository_storage_service_spec.rb
- spec/services/spam/akismet_mark_as_spam_service_spec.rb
@@ -1155,7 +1145,6 @@ RSpec/VerifiedDoubles:
- spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
- spec/workers/gitlab_performance_bar_stats_worker_spec.rb
- spec/workers/invalid_gpg_signature_update_worker_spec.rb
- - spec/workers/issue_rebalancing_worker_spec.rb
- spec/workers/issues/rebalancing_worker_spec.rb
- spec/workers/merge_request_mergeability_check_worker_spec.rb
- spec/workers/new_issue_worker_spec.rb
diff --git a/.rubocop_todo/style/bare_percent_literals.yml b/.rubocop_todo/style/bare_percent_literals.yml
index 658c6c22baa..104ead817d5 100644
--- a/.rubocop_todo/style/bare_percent_literals.yml
+++ b/.rubocop_todo/style/bare_percent_literals.yml
@@ -10,7 +10,6 @@ Style/BarePercentLiterals:
- 'app/models/integrations/datadog.rb'
- 'app/services/feature_flags/base_service.rb'
- 'app/services/repositories/base_service.rb'
- - 'app/services/repositories/destroy_rollback_service.rb'
- 'app/services/repositories/destroy_service.rb'
- 'ee/app/services/jira/jql_builder_service.rb'
- 'ee/lib/ee/gitlab/checks/push_rules/file_size_check.rb'
diff --git a/.rubocop_todo/style/class_and_module_children.yml b/.rubocop_todo/style/class_and_module_children.yml
index 14f6ccad44f..fab05667adb 100644
--- a/.rubocop_todo/style/class_and_module_children.yml
+++ b/.rubocop_todo/style/class_and_module_children.yml
@@ -351,9 +351,7 @@ Style/ClassAndModuleChildren:
- 'app/serializers/merge_requests/pipeline_entity.rb'
- 'app/services/projects/branches_by_mode_service.rb'
- 'app/services/repositories/base_service.rb'
- - 'app/services/repositories/destroy_rollback_service.rb'
- 'app/services/repositories/destroy_service.rb'
- - 'app/services/repositories/shell_destroy_service.rb'
- 'app/uploaders/dependency_proxy/file_uploader.rb'
- 'app/uploaders/packages/composer/cache_uploader.rb'
- 'app/uploaders/packages/debian/component_file_uploader.rb'
diff --git a/.rubocop_todo/style/format_string.yml b/.rubocop_todo/style/format_string.yml
index 13e118d6edc..a9065bb5932 100644
--- a/.rubocop_todo/style/format_string.yml
+++ b/.rubocop_todo/style/format_string.yml
@@ -182,7 +182,6 @@ Style/FormatString:
- 'config/initializers/rack_lineprof.rb'
- 'danger/roulette/Dangerfile'
- 'ee/app/components/billing/plan_component.rb'
- - 'ee/app/components/namespaces/preview_free_user_cap_alert_component.rb'
- 'ee/app/controllers/admin/elasticsearch_controller.rb'
- 'ee/app/controllers/admin/geo/application_controller.rb'
- 'ee/app/controllers/admin/geo/projects_controller.rb'
@@ -235,7 +234,6 @@ Style/FormatString:
- 'ee/app/services/merge_requests/create_from_vulnerability_data_service.rb'
- 'ee/app/services/namespaces/check_excess_storage_size_service.rb'
- 'ee/app/services/namespaces/check_storage_size_service.rb'
- - 'ee/app/services/network_policies/responses.rb'
- 'ee/app/services/security/security_orchestration_policies/policy_configuration_validation_service.rb'
- 'ee/app/services/security/security_orchestration_policies/validate_policy_service.rb'
- 'ee/app/services/timebox_report_service.rb'
diff --git a/.rubocop_todo/style/guard_clause.yml b/.rubocop_todo/style/guard_clause.yml
index 008ccb971c4..c7109639677 100644
--- a/.rubocop_todo/style/guard_clause.yml
+++ b/.rubocop_todo/style/guard_clause.yml
@@ -60,7 +60,6 @@ Style/GuardClause:
- 'app/finders/ci/runners_finder.rb'
- 'app/finders/deployments_finder.rb'
- 'app/finders/group_members_finder.rb'
- - 'app/finders/projects/serverless/functions_finder.rb'
- 'app/finders/snippets_finder.rb'
- 'app/graphql/mutations/concerns/mutations/spam_protection.rb'
- 'app/graphql/mutations/design_management/delete.rb'
@@ -73,7 +72,6 @@ Style/GuardClause:
- 'app/graphql/resolvers/concerns/time_frame_arguments.rb'
- 'app/graphql/resolvers/projects/jira_projects_resolver.rb'
- 'app/graphql/types/ci/job_type.rb'
- - 'app/graphql/types/concerns/find_closest.rb'
- 'app/helpers/admin/user_actions_helper.rb'
- 'app/helpers/appearances_helper.rb'
- 'app/helpers/application_helper.rb'
@@ -235,7 +233,6 @@ Style/GuardClause:
- 'app/services/post_receive_service.rb'
- 'app/services/projects/after_rename_service.rb'
- 'app/services/projects/create_service.rb'
- - 'app/services/projects/destroy_rollback_service.rb'
- 'app/services/projects/destroy_service.rb'
- 'app/services/projects/hashed_storage/rollback_service.rb'
- 'app/services/projects/import_export/export_service.rb'
@@ -463,7 +460,6 @@ Style/GuardClause:
- 'ee/lib/gitlab/insights/serializers/chartjs/multi_series_serializer.rb'
- 'ee/lib/gitlab/insights/validators/params_validator.rb'
- 'ee/lib/omni_auth/strategies/group_saml.rb'
- - 'ee/lib/pseudonymizer/dumper.rb'
- 'ee/spec/features/billings/billing_plans_spec.rb'
- 'ee/spec/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules_spec.rb'
- 'ee/spec/workers/update_max_seats_used_for_gitlab_com_subscriptions_worker_spec.rb'
@@ -545,7 +541,6 @@ Style/GuardClause:
- 'lib/gitlab/database/partitioning/sliding_list_strategy.rb'
- 'lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin.rb'
- 'lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb'
- - 'lib/gitlab/diff/custom_diff.rb'
- 'lib/gitlab/diff/file.rb'
- 'lib/gitlab/diff/highlight.rb'
- 'lib/gitlab/diff/highlight_cache.rb'
@@ -580,7 +575,6 @@ Style/GuardClause:
- 'lib/gitlab/graphql/pagination/keyset/conditions/null_condition.rb'
- 'lib/gitlab/graphql/pagination/keyset/order_info.rb'
- 'lib/gitlab/graphql/pagination/keyset/query_builder.rb'
- - 'lib/gitlab/graphql/query_analyzers/recursion_analyzer.rb'
- 'lib/gitlab/i18n/metadata_entry.rb'
- 'lib/gitlab/i18n/po_linter.rb'
- 'lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb'
diff --git a/.rubocop_todo/style/if_inside_else.yml b/.rubocop_todo/style/if_inside_else.yml
index fcddbae74fc..3ce6e5ec704 100644
--- a/.rubocop_todo/style/if_inside_else.yml
+++ b/.rubocop_todo/style/if_inside_else.yml
@@ -14,7 +14,6 @@ Style/IfInsideElse:
- 'app/models/ci/build.rb'
- 'app/models/namespace.rb'
- 'app/presenters/project_presenter.rb'
- - 'app/services/service_ping/build_payload_service.rb'
- 'app/services/system_notes/commit_service.rb'
- 'app/services/task_list_toggle_service.rb'
- 'app/services/user_project_access_changed_service.rb'
diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml
index ddc6a477b85..645fc5f764c 100644
--- a/.rubocop_todo/style/if_unless_modifier.yml
+++ b/.rubocop_todo/style/if_unless_modifier.yml
@@ -8,7 +8,6 @@ Style/IfUnlessModifier:
- 'app/channels/graphql_channel.rb'
- 'app/controllers/admin/application_settings_controller.rb'
- 'app/controllers/admin/projects_controller.rb'
- - 'app/controllers/admin/requests_profiles_controller.rb'
- 'app/controllers/admin/runners_controller.rb'
- 'app/controllers/admin/users_controller.rb'
- 'app/controllers/application_controller.rb'
@@ -379,7 +378,6 @@ Style/IfUnlessModifier:
- 'app/services/projects/update_repository_storage_service.rb'
- 'app/services/resource_access_tokens/create_service.rb'
- 'app/services/resource_events/change_labels_service.rb'
- - 'app/services/service_ping/devops_report_service.rb'
- 'app/services/snippets/create_service.rb'
- 'app/services/snippets/destroy_service.rb'
- 'app/services/snippets/repository_validation_service.rb'
@@ -420,7 +418,6 @@ Style/IfUnlessModifier:
- 'app/workers/concerns/worker_attributes.rb'
- 'app/workers/database/batched_background_migration/single_database_worker.rb'
- 'app/workers/file_hook_worker.rb'
- - 'app/workers/issue_rebalancing_worker.rb'
- 'app/workers/issues/rebalancing_worker.rb'
- 'app/workers/merge_request_mergeability_check_worker.rb'
- 'app/workers/object_storage/migrate_uploads_worker.rb'
@@ -497,7 +494,6 @@ Style/IfUnlessModifier:
- 'ee/app/controllers/projects/push_rules_controller.rb'
- 'ee/app/controllers/projects/settings/slacks_controller.rb'
- 'ee/app/controllers/trials_controller.rb'
- - 'ee/app/finders/ee/alert_management/alerts_finder.rb'
- 'ee/app/finders/iterations_finder.rb'
- 'ee/app/finders/merge_trains_finder.rb'
- 'ee/app/finders/security/pipeline_vulnerabilities_finder.rb'
@@ -610,8 +606,6 @@ Style/IfUnlessModifier:
- 'ee/app/services/ee/users/update_service.rb'
- 'ee/app/services/elastic/indexing_control_service.rb'
- 'ee/app/services/elastic/process_bookkeeping_service.rb'
- - 'ee/app/services/epic_links/create_service.rb'
- - 'ee/app/services/epic_links/update_service.rb'
- 'ee/app/services/epics/base_service.rb'
- 'ee/app/services/epics/create_service.rb'
- 'ee/app/services/epics/update_dates_service.rb'
@@ -719,7 +713,6 @@ Style/IfUnlessModifier:
- 'ee/lib/gitlab/geo/log_cursor/daemon.rb'
- 'ee/lib/gitlab/geo/log_cursor/events/repository_deleted_event.rb'
- 'ee/lib/gitlab/geo/oauth/logout_token.rb'
- - 'ee/lib/gitlab/geo/replication/base_transfer.rb'
- 'ee/lib/gitlab/geo/replication/blob_downloader.rb'
- 'ee/lib/gitlab/geo/replicator.rb'
- 'ee/lib/gitlab/graphql/aggregations/epics/lazy_epic_aggregate.rb'
@@ -735,13 +728,10 @@ Style/IfUnlessModifier:
- 'ee/lib/gitlab/usage/metrics/instrumentations/count_users_creating_ci_builds_metric.rb'
- 'ee/lib/gitlab/usage/metrics/instrumentations/license_metric.rb'
- 'ee/lib/omni_auth/strategies/group_saml.rb'
- - 'ee/lib/pseudonymizer/dumper.rb'
- - 'ee/lib/pseudonymizer/uploader.rb'
- 'ee/lib/sidebars/groups/menus/administration_menu.rb'
- 'ee/lib/sidebars/groups/menus/analytics_menu.rb'
- 'ee/lib/sidebars/groups/menus/security_compliance_menu.rb'
- 'ee/lib/tasks/geo.rake'
- - 'ee/lib/tasks/gitlab/db.rake'
- 'ee/lib/tasks/gitlab/geo.rake'
- 'ee/lib/tasks/gitlab/seed/insights.rake'
- 'ee/spec/controllers/subscriptions_controller_spec.rb'
@@ -945,7 +935,6 @@ Style/IfUnlessModifier:
- 'lib/gitlab/database/reindexing/reindex_concurrently.rb'
- 'lib/gitlab/database/transaction/observer.rb'
- 'lib/gitlab/database/with_lock_retries.rb'
- - 'lib/gitlab/diff/custom_diff.rb'
- 'lib/gitlab/diff/formatters/base_formatter.rb'
- 'lib/gitlab/diff/rendered/notebook/diff_file.rb'
- 'lib/gitlab/elasticsearch/logs/lines.rb'
@@ -1154,7 +1143,6 @@ Style/IfUnlessModifier:
- 'rubocop/cop/ignored_columns.rb'
- 'rubocop/cop/migration/add_limit_to_text_columns.rb'
- 'rubocop/cop/migration/add_reference.rb'
- - 'rubocop/cop/migration/hash_index.rb'
- 'rubocop/cop/migration/remove_column.rb'
- 'rubocop/cop/migration/sidekiq_queue_migrate.rb'
- 'rubocop/cop/performance/ar_exists_and_present_blank.rb'
diff --git a/.rubocop_todo/style/next.yml b/.rubocop_todo/style/next.yml
index e1f9b927db6..ba2bd293696 100644
--- a/.rubocop_todo/style/next.yml
+++ b/.rubocop_todo/style/next.yml
@@ -5,7 +5,6 @@ Style/Next:
# Temporarily disabled due to too many offenses
Enabled: false
Exclude:
- - 'app/finders/projects/serverless/functions_finder.rb'
- 'app/models/preloaders/environments/deployment_preloader.rb'
- 'app/models/route.rb'
- 'app/services/authorized_project_update/find_records_due_for_refresh_service.rb'
diff --git a/.rubocop_todo/style/numeric_literal_prefix.yml b/.rubocop_todo/style/numeric_literal_prefix.yml
index b469a68ff5a..ea0b028d11c 100644
--- a/.rubocop_todo/style/numeric_literal_prefix.yml
+++ b/.rubocop_todo/style/numeric_literal_prefix.yml
@@ -10,16 +10,12 @@ Style/NumericLiteralPrefix:
- 'config/initializers/01_secret_token.rb'
- 'config/initializers/1_settings.rb'
- 'db/post_migrate/20220131000001_schedule_trace_expiry_removal.rb'
- - 'ee/lib/gitlab/geo/replication/base_transfer.rb'
- 'ee/lib/gitlab/geo/replication/blob_downloader.rb'
- 'ee/spec/lib/bulk_imports/groups/pipelines/iterations_pipeline_spec.rb'
- 'ee/spec/lib/gitlab/background_migration/remove_all_trace_expiration_dates_spec.rb'
- 'ee/spec/lib/gitlab/geo/replication/blob_downloader_spec.rb'
- - 'ee/spec/lib/gitlab/geo/replication/file_transfer_spec.rb'
- - 'ee/spec/lib/gitlab/geo/replication/job_artifact_transfer_spec.rb'
- 'ee/spec/migrations/schedule_trace_expiry_removal_spec.rb'
- 'ee/spec/models/analytics/devops_adoption/snapshot_spec.rb'
- - 'ee/spec/models/ci/minutes/quota_spec.rb'
- 'ee/spec/models/ee/group_spec.rb'
- 'ee/spec/models/gitlab/seat_link_data_spec.rb'
- 'ee/spec/services/incident_management/oncall_schedules/update_service_spec.rb'
diff --git a/.rubocop_todo/style/percent_literal_delimiters.yml b/.rubocop_todo/style/percent_literal_delimiters.yml
index 8611181f430..487e0e7cd20 100644
--- a/.rubocop_todo/style/percent_literal_delimiters.yml
+++ b/.rubocop_todo/style/percent_literal_delimiters.yml
@@ -177,7 +177,6 @@ Style/PercentLiteralDelimiters:
- 'app/services/projects/update_service.rb'
- 'app/services/prometheus/proxy_service.rb'
- 'app/services/repositories/base_service.rb'
- - 'app/services/repositories/destroy_rollback_service.rb'
- 'app/services/repositories/destroy_service.rb'
- 'app/services/repository_archive_clean_up_service.rb'
- 'app/services/resource_access_tokens/create_service.rb'
@@ -293,7 +292,6 @@ Style/PercentLiteralDelimiters:
- 'ee/lib/api/managed_licenses.rb'
- 'ee/lib/api/status_checks.rb'
- 'ee/lib/api/visual_review_discussions.rb'
- - 'ee/lib/banzai/pipeline/incident_management/timeline_event_pipeline.rb'
- 'ee/lib/ee/api/helpers/members_helpers.rb'
- 'ee/lib/ee/api/helpers/projects_helpers.rb'
- 'ee/lib/ee/api/search.rb'
@@ -305,7 +303,6 @@ Style/PercentLiteralDelimiters:
- 'ee/lib/ee/gitlab/checks/push_rules/file_size_check.rb'
- 'ee/lib/ee/gitlab/ci/parsers/security/validators/schema_validator.rb'
- 'ee/lib/ee/gitlab/etag_caching/router/rails.rb'
- - 'ee/lib/ee/gitlab/integrations/sti_type.rb'
- 'ee/lib/ee/gitlab/middleware/read_only/controller.rb'
- 'ee/lib/ee/gitlab/path_regex.rb'
- 'ee/lib/ee/gitlab/uploads/migration_helper.rb'
@@ -316,7 +313,6 @@ Style/PercentLiteralDelimiters:
- 'ee/lib/elastic/latest/git_class_proxy.rb'
- 'ee/lib/elastic/latest/project_instance_proxy.rb'
- 'ee/lib/elastic/latest/snippet_class_proxy.rb'
- - 'ee/lib/gitlab/alert_management/payload/cilium.rb'
- 'ee/lib/gitlab/auth/group_saml/auth_hash.rb'
- 'ee/lib/gitlab/ci/parsers/security/formatters/dast.rb'
- 'ee/lib/gitlab/geo.rb'
@@ -368,7 +364,6 @@ Style/PercentLiteralDelimiters:
- 'ee/spec/lib/banzai/filter/references/epic_reference_filter_spec.rb'
- 'ee/spec/lib/banzai/filter/references/iteration_reference_filter_spec.rb'
- 'ee/spec/lib/banzai/filter/references/vulnerability_reference_filters_spec.rb'
- - 'ee/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb'
- 'ee/spec/lib/ee/gitlab/alert_management/payload/generic_spec.rb'
- 'ee/spec/lib/ee/gitlab/auth/ldap/access_levels_spec.rb'
- 'ee/spec/lib/ee/gitlab/auth/ldap/config_spec.rb'
@@ -558,7 +553,6 @@ Style/PercentLiteralDelimiters:
- 'lib/gitlab/gitaly_client/diff.rb'
- 'lib/gitlab/gitaly_client/wiki_page.rb'
- 'lib/gitlab/graphql/pagination/keyset/order_info.rb'
- - 'lib/gitlab/graphql/query_analyzers/recursion_analyzer.rb'
- 'lib/gitlab/hotlinking_detector.rb'
- 'lib/gitlab/import_export/command_line_util.rb'
- 'lib/gitlab/import_export/file_importer.rb'
@@ -645,10 +639,6 @@ Style/PercentLiteralDelimiters:
- 'rubocop/migration_helpers.rb'
- 'scripts/qa/testcases-check'
- 'scripts/regenerate-schema'
- - 'shared/packages/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b/packages/57/files/67/package.gemspec'
- - 'shared/packages/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b/packages/6/files/5/package.gemspec'
- - 'shared/packages/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b/packages/7/files/7/package.gemspec'
- - 'shared/packages/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b/packages/88/files/91/package.gemspec'
- 'sidekiq_cluster/cli.rb'
- 'spec/benchmarks/banzai_benchmark.rb'
- 'spec/commands/sidekiq_cluster/cli_spec.rb'
@@ -894,7 +884,6 @@ Style/PercentLiteralDelimiters:
- 'spec/lib/gitlab/jira_import/issue_serializer_spec.rb'
- 'spec/lib/gitlab/jira_import/labels_importer_spec.rb'
- 'spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb'
- - 'spec/lib/gitlab/kubernetes/network_policy_spec.rb'
- 'spec/lib/gitlab/kubernetes/role_spec.rb'
- 'spec/lib/gitlab/language_data_spec.rb'
- 'spec/lib/gitlab/markup_helper_spec.rb'
diff --git a/.rubocop_todo/style/redundant_interpolation.yml b/.rubocop_todo/style/redundant_interpolation.yml
index fb190ff2100..0d3651eb7b2 100644
--- a/.rubocop_todo/style/redundant_interpolation.yml
+++ b/.rubocop_todo/style/redundant_interpolation.yml
@@ -77,7 +77,6 @@ Style/RedundantInterpolation:
- 'qa/qa/resource/events/base.rb'
- 'qa/qa/service/praefect_manager.rb'
- 'qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb'
- - 'qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb'
- 'qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/1_manage/project/project_templates_spec.rb'
- 'qa/qa/tools/generate_perf_testdata.rb'
diff --git a/.rubocop_todo/style/redundant_regexp_escape.yml b/.rubocop_todo/style/redundant_regexp_escape.yml
index 1a88f62ab64..969a23601ca 100644
--- a/.rubocop_todo/style/redundant_regexp_escape.yml
+++ b/.rubocop_todo/style/redundant_regexp_escape.yml
@@ -35,7 +35,6 @@ Style/RedundantRegexpEscape:
- 'ee/lib/gitlab/geo/git_ssh_proxy.rb'
- 'ee/lib/gitlab/return_to_location.rb'
- 'ee/spec/features/read_only_spec.rb'
- - 'ee/spec/helpers/seats_count_alert_helper_spec.rb'
- 'ee/spec/helpers/vulnerabilities_helper_spec.rb'
- 'ee/spec/lib/ee/gitlab/usage_data_counters/hll_redis_counter_spec.rb'
- 'ee/spec/mailers/notify_spec.rb'
diff --git a/.rubocop_todo/style/redundant_self.yml b/.rubocop_todo/style/redundant_self.yml
index e363f83761b..1aba23c90ae 100644
--- a/.rubocop_todo/style/redundant_self.yml
+++ b/.rubocop_todo/style/redundant_self.yml
@@ -260,7 +260,6 @@ Style/RedundantSelf:
- 'ee/lib/elastic/latest/note_config.rb'
- 'ee/lib/elastic/migration.rb'
- 'ee/lib/gem_extensions/elasticsearch/model/indexing/instance_methods.rb'
- - 'ee/lib/gitlab/alert_management/payload/cilium.rb'
- 'ee/lib/gitlab/analytics/cycle_analytics/summary/base_time.rb'
- 'ee/lib/gitlab/auth/group_saml/response_check.rb'
- 'ee/lib/gitlab/auth/group_saml/user.rb'
@@ -277,7 +276,6 @@ Style/RedundantSelf:
- 'ee/lib/gitlab/geo/oauth/logout_state.rb'
- 'ee/lib/gitlab/geo/replicator.rb'
- 'ee/lib/gitlab/template/custom_template.rb'
- - 'ee/lib/system_check/app/elasticsearch_check.rb'
- 'ee/spec/helpers/ee/feature_flags_helper_spec.rb'
- 'ee/spec/helpers/ee/graph_helper_spec.rb'
- 'ee/spec/models/geo/deleted_project_spec.rb'
@@ -365,8 +363,6 @@ Style/RedundantSelf:
- 'lib/gitlab/instrumentation/redis_base.rb'
- 'lib/gitlab/jira_import.rb'
- 'lib/gitlab/jwt_token.rb'
- - 'lib/gitlab/kubernetes/cilium_network_policy.rb'
- - 'lib/gitlab/kubernetes/network_policy.rb'
- 'lib/gitlab/logger.rb'
- 'lib/gitlab/marker_range.rb'
- 'lib/gitlab/memory/instrumentation.rb'
diff --git a/.rubocop_todo/style/string_concatenation.yml b/.rubocop_todo/style/string_concatenation.yml
index e21fecfb5e3..8c8dbf86b1c 100644
--- a/.rubocop_todo/style/string_concatenation.yml
+++ b/.rubocop_todo/style/string_concatenation.yml
@@ -301,11 +301,9 @@ Style/StringConcatenation:
- 'spec/services/packages/helm/extract_file_metadata_service_spec.rb'
- 'spec/services/packages/helm/process_file_service_spec.rb'
- 'spec/services/projects/create_service_spec.rb'
- - 'spec/services/projects/destroy_rollback_service_spec.rb'
- 'spec/services/projects/destroy_service_spec.rb'
- 'spec/services/projects/download_service_spec.rb'
- 'spec/services/push_event_payload_service_spec.rb'
- - 'spec/services/repositories/destroy_rollback_service_spec.rb'
- 'spec/services/repositories/destroy_service_spec.rb'
- 'spec/services/snippets/bulk_destroy_service_spec.rb'
- 'spec/services/snippets/update_service_spec.rb'
diff --git a/.rubocop_todo/style/string_literals_in_interpolation.yml b/.rubocop_todo/style/string_literals_in_interpolation.yml
index 3f3cb007306..cded81afa7f 100644
--- a/.rubocop_todo/style/string_literals_in_interpolation.yml
+++ b/.rubocop_todo/style/string_literals_in_interpolation.yml
@@ -24,7 +24,6 @@ Style/StringLiteralsInInterpolation:
- 'ee/app/models/license.rb'
- 'ee/app/services/epics/tree_reorder_service.rb'
- 'ee/lib/gitlab/elastic/helper.rb'
- - 'ee/lib/pseudonymizer/pager.rb'
- 'ee/spec/features/admin/admin_settings_spec.rb'
- 'lib/api/helpers/snippets_helpers.rb'
- 'lib/api/validations/validators/check_assignees_count.rb'
@@ -54,7 +53,6 @@ Style/StringLiteralsInInterpolation:
- 'qa/qa/specs/helpers/context_selector.rb'
- 'qa/qa/tools/generate_perf_testdata.rb'
- 'rubocop/cop/migration/prevent_index_creation.rb'
- - 'spec/controllers/projects/serverless/functions_controller_spec.rb'
- 'spec/features/commits_spec.rb'
- 'spec/features/dashboard/merge_requests_spec.rb'
- 'spec/features/users/login_spec.rb'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c8d2417bb4..1c178782ebb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 15.0.3 (2022-06-16)
+
+### Fixed (2 changes)
+
+- [Disconnect alternates when unlinking from a repository pool](gitlab-org/gitlab@a6f4b701af0d5850a10d77feeb4842b1fe017047) ([merge request](gitlab-org/gitlab!90269))
+- [Add GitLab agent image tag to install command](gitlab-org/gitlab@09decb04e391f095139412ed623164cab8023a7c) ([merge request](gitlab-org/gitlab!90269))
+
## 15.0.2 (2022-06-06)
### Added (1 change)
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index e8f59312593..2422b2f9464 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-14.7.3
+14.7.4
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 8d46ea76be1..e7a5f5ecc06 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,4 +1,4 @@
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { __ } from '~/locale';
import axios from './lib/utils/axios_utils';
import { joinPaths } from './lib/utils/url_utility';
@@ -464,7 +464,7 @@ const Api = {
throw error;
}
- createFlash({
+ createAlert({
message: __('Something went wrong while fetching projects'),
});
@@ -654,7 +654,7 @@ const Api = {
})
.then(({ data }) => callback(data))
.catch(() =>
- createFlash({
+ createAlert({
message: __('Something went wrong while fetching projects'),
}),
);
diff --git a/app/assets/javascripts/behaviors/components/sandboxed_mermaid.vue b/app/assets/javascripts/behaviors/components/sandboxed_mermaid.vue
new file mode 100644
index 00000000000..6b4110cff02
--- /dev/null
+++ b/app/assets/javascripts/behaviors/components/sandboxed_mermaid.vue
@@ -0,0 +1,77 @@
+<script>
+import {
+ getSandboxFrameSrc,
+ BUFFER_IFRAME_HEIGHT,
+ SANDBOX_ATTRIBUTES,
+} from '../markdown/render_sandboxed_mermaid';
+
+export default {
+ name: 'SandboxedMermaid',
+
+ props: {
+ source: {
+ type: String,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ iframeHeight: BUFFER_IFRAME_HEIGHT,
+ sandboxFrameSrc: getSandboxFrameSrc(),
+ };
+ },
+
+ watch: {
+ source() {
+ this.updateDiagram();
+ },
+ },
+
+ mounted() {
+ window.addEventListener('message', this.onPostMessage, false);
+ },
+
+ destroyed() {
+ window.removeEventListener('message', this.onPostMessage);
+ },
+
+ methods: {
+ getSandboxFrameSrc,
+
+ onPostMessage(event) {
+ const container = this.$refs.diagramContainer;
+
+ if (event.source === container?.contentWindow) {
+ this.iframeHeight = Number(event.data.h) + BUFFER_IFRAME_HEIGHT;
+ }
+ },
+
+ updateDiagram() {
+ const container = this.$refs.diagramContainer;
+
+ // Potential risk associated with '*' discussed in below thread
+ // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74414#note_735183398
+ container.contentWindow?.postMessage(this.source, '*');
+ container.addEventListener('load', () => {
+ container.contentWindow?.postMessage(this.source, '*');
+ });
+ },
+ },
+
+ sandboxFrameSrc: getSandboxFrameSrc(),
+ sandboxAttributes: SANDBOX_ATTRIBUTES,
+};
+</script>
+<template>
+ <iframe
+ ref="diagramContainer"
+ :src="$options.sandboxFrameSrc"
+ :sandbox="$options.sandboxAttributes"
+ frameborder="0"
+ scrolling="no"
+ width="100%"
+ :height="iframeHeight"
+ >
+ </iframe>
+</template>
diff --git a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
index 543e676e85e..077e96b2fee 100644
--- a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
@@ -32,7 +32,8 @@ const MAX_CHAR_LIMIT = 2000;
const MAX_MERMAID_BLOCK_LIMIT = 50;
// Max # of `&` allowed in Chaining of links syntax
const MAX_CHAINING_OF_LINKS_LIMIT = 30;
-const BUFFER_IFRAME_HEIGHT = 10;
+export const BUFFER_IFRAME_HEIGHT = 10;
+export const SANDBOX_ATTRIBUTES = 'allow-scripts allow-popups';
// Keep a map of mermaid blocks we've already rendered.
const elsProcessingMap = new WeakMap();
let renderedMermaidBlocks = 0;
@@ -56,7 +57,7 @@ function fixElementSource(el) {
return { source };
}
-function getSandboxFrameSrc() {
+export function getSandboxFrameSrc() {
const path = joinPaths(gon.relative_url_root || '', SANDBOX_FRAME_PATH);
if (!darkModeEnabled()) {
return path;
@@ -69,7 +70,7 @@ function renderMermaidEl(el, source) {
const iframeEl = document.createElement('iframe');
setAttributes(iframeEl, {
src: getSandboxFrameSrc(),
- sandbox: 'allow-scripts allow-popups',
+ sandbox: SANDBOX_ATTRIBUTES,
frameBorder: 0,
scrolling: 'no',
width: '100%',
diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue b/app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue
index 4ed89140e5b..6c0ac8e54d2 100644
--- a/app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue
+++ b/app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue
@@ -36,16 +36,19 @@ export default {
directives: {
GlTooltip,
},
- inject: ['tiptapEditor'],
+ inject: ['tiptapEditor', 'contentEditor'],
data() {
return {
codeBlockType: undefined,
- selectedLanguage: {},
filterTerm: '',
filteredLanguages: [],
showCustomLanguageInput: false,
customLanguageType: '',
+
+ selectedLanguage: {},
+ isDiagram: false,
+ showPreview: false,
};
},
watch: {
@@ -61,21 +64,36 @@ export default {
return CODE_BLOCK_NODE_TYPES.some((type) => editor.isActive(type));
},
- updateSelectedLanguage() {
+ async updateCodeBlockInfoToState() {
this.codeBlockType = CODE_BLOCK_NODE_TYPES.find((type) => this.tiptapEditor.isActive(type));
- if (this.codeBlockType) {
- const { language } = this.tiptapEditor.getAttributes(this.codeBlockType);
- this.selectedLanguage = codeBlockLanguageLoader.findOrCreateLanguageBySyntax(language);
- }
+ if (!this.codeBlockType) return;
+
+ const { language, isDiagram, showPreview } = this.tiptapEditor.getAttributes(
+ this.codeBlockType,
+ );
+ this.selectedLanguage = codeBlockLanguageLoader.findOrCreateLanguageBySyntax(
+ language,
+ isDiagram,
+ );
+ this.isDiagram = isDiagram;
+ this.showPreview = showPreview;
},
- copyCodeBlockText() {
+ getCodeBlockText() {
const { view } = this.tiptapEditor;
const { from } = this.tiptapEditor.state.selection;
const node = getParentByTagName(view.domAtPos(from).node, 'pre');
+ return node?.textContent || '';
+ },
- navigator.clipboard.writeText(node?.textContent || '');
+ copyCodeBlockText() {
+ navigator.clipboard.writeText(this.getCodeBlockText());
+ },
+
+ togglePreview() {
+ this.showPreview = !this.showPreview;
+ this.tiptapEditor.commands.updateAttributes(Diagram.name, { showPreview: this.showPreview });
},
async applyLanguage(language) {
@@ -125,7 +143,7 @@ export default {
getReferenceClientRect,
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
>
- <editor-state-observer @transaction="updateSelectedLanguage">
+ <editor-state-observer @transaction="updateCodeBlockInfoToState">
<gl-button-group>
<gl-dropdown
category="tertiary"
@@ -228,6 +246,19 @@ export default {
@click="copyCodeBlockText"
/>
<gl-button
+ v-if="isDiagram"
+ v-gl-tooltip
+ variant="default"
+ category="tertiary"
+ size="medium"
+ :class="{ active: showPreview }"
+ data-testid="preview-diagram"
+ :aria-label="__('Preview diagram')"
+ :title="__('Preview diagram')"
+ icon="eye"
+ @click="togglePreview"
+ />
+ <gl-button
v-gl-tooltip
variant="default"
category="tertiary"
diff --git a/app/assets/javascripts/content_editor/components/toolbar_more_dropdown.vue b/app/assets/javascripts/content_editor/components/toolbar_more_dropdown.vue
new file mode 100644
index 00000000000..ecde593147c
--- /dev/null
+++ b/app/assets/javascripts/content_editor/components/toolbar_more_dropdown.vue
@@ -0,0 +1,34 @@
+<script>
+import { GlDropdown, GlDropdownItem, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ },
+ directives: {
+ GlTooltip,
+ },
+ inject: ['tiptapEditor'],
+ methods: {
+ execute(contentType, attrs) {
+ this.tiptapEditor.chain().focus().setNode(contentType, attrs).run();
+
+ this.$emit('execute', { contentType });
+ },
+ },
+};
+</script>
+<template>
+ <gl-dropdown size="small" category="tertiary" icon="plus">
+ <gl-dropdown-item @click="execute('diagram', { language: 'mermaid' })">
+ {{ __('Mermaid diagram') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item @click="execute('diagram', { language: 'plantuml' })">
+ {{ __('PlantUML diagram') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item @click="execute('horizontalRule')">
+ {{ __('Horizontal rule') }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/content_editor/components/top_toolbar.vue b/app/assets/javascripts/content_editor/components/top_toolbar.vue
index 19e150a4da9..b652e634b0c 100644
--- a/app/assets/javascripts/content_editor/components/top_toolbar.vue
+++ b/app/assets/javascripts/content_editor/components/top_toolbar.vue
@@ -5,6 +5,7 @@ import ToolbarImageButton from './toolbar_image_button.vue';
import ToolbarLinkButton from './toolbar_link_button.vue';
import ToolbarTableButton from './toolbar_table_button.vue';
import ToolbarTextStyleDropdown from './toolbar_text_style_dropdown.vue';
+import ToolbarMoreDropdown from './toolbar_more_dropdown.vue';
export default {
components: {
@@ -13,6 +14,7 @@ export default {
ToolbarLinkButton,
ToolbarTableButton,
ToolbarImageButton,
+ ToolbarMoreDropdown,
},
methods: {
trackToolbarControlExecution({ contentType, value }) {
@@ -117,16 +119,8 @@ export default {
:label="__('Add a collapsible section')"
@execute="trackToolbarControlExecution"
/>
- <toolbar-button
- data-testid="horizontal-rule"
- content-type="horizontalRule"
- icon-name="dash"
- class="gl-mx-2"
- editor-command="setHorizontalRule"
- :label="__('Add a horizontal rule')"
- @execute="trackToolbarControlExecution"
- />
- <toolbar-table-button @execute="trackToolbarControlExecution" />
+ <toolbar-table-button data-testid="table" @execute="trackToolbarControlExecution" />
+ <toolbar-more-dropdown data-testid="more" @execute="trackToolbarControlExecution" />
</div>
</template>
<style>
diff --git a/app/assets/javascripts/content_editor/components/wrappers/code_block.vue b/app/assets/javascripts/content_editor/components/wrappers/code_block.vue
index 647f0798364..81f9b1f0af5 100644
--- a/app/assets/javascripts/content_editor/components/wrappers/code_block.vue
+++ b/app/assets/javascripts/content_editor/components/wrappers/code_block.vue
@@ -1,15 +1,26 @@
<script>
+import { debounce } from 'lodash';
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2';
import { __ } from '~/locale';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import SandboxedMermaid from '~/behaviors/components/sandboxed_mermaid.vue';
import codeBlockLanguageLoader from '../../services/code_block_language_loader';
+import EditorStateObserver from '../editor_state_observer.vue';
export default {
name: 'CodeBlock',
components: {
NodeViewWrapper,
NodeViewContent,
+ EditorStateObserver,
+ SandboxedMermaid,
},
+ inject: ['contentEditor'],
props: {
+ editor: {
+ type: Object,
+ required: true,
+ },
node: {
type: Object,
required: true,
@@ -18,13 +29,48 @@ export default {
type: Function,
required: true,
},
+ selected: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ diagramUrl: '',
+ diagramSource: '',
+ };
},
async mounted() {
+ this.updateDiagramPreview = debounce(
+ this.updateDiagramPreview,
+ DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
+ );
+
const lang = codeBlockLanguageLoader.findOrCreateLanguageBySyntax(this.node.attrs.language);
await codeBlockLanguageLoader.loadLanguage(lang.syntax);
this.updateAttributes({ language: this.node.attrs.language });
},
+ methods: {
+ async updateDiagramPreview() {
+ if (!this.node.attrs.showPreview) {
+ this.diagramSource = '';
+ return;
+ }
+
+ if (!this.editor.isActive('diagram')) return;
+
+ this.diagramSource = this.$refs.nodeViewContent.$el.textContent;
+
+ if (this.node.attrs.language !== 'mermaid') {
+ this.diagramUrl = await this.contentEditor.renderDiagram(
+ this.diagramSource,
+ this.node.attrs.language,
+ );
+ }
+ },
+ },
i18n: {
frontmatter: __('frontmatter'),
},
@@ -32,17 +78,26 @@ export default {
};
</script>
<template>
- <node-view-wrapper
- :class="`content-editor-code-block gl-relative code highlight ${$options.userColorScheme}`"
- as="pre"
- >
- <span
- v-if="node.attrs.isFrontmatter"
- data-testid="frontmatter-label"
- class="gl-absolute gl-top-0 gl-right-3"
- contenteditable="false"
- >{{ $options.i18n.frontmatter }}:{{ node.attrs.language }}</span
+ <editor-state-observer @transaction="updateDiagramPreview">
+ <node-view-wrapper
+ :class="`content-editor-code-block gl-relative code highlight ${$options.userColorScheme}`"
+ as="pre"
>
- <node-view-content as="code" />
- </node-view-wrapper>
+ <div
+ v-if="node.attrs.showPreview"
+ class="gl-mt-n3! gl-ml-n4! gl-mr-n4! gl-mb-3 gl-bg-white! gl-p-4 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
+ >
+ <sandboxed-mermaid v-if="node.attrs.language === 'mermaid'" :source="diagramSource" />
+ <img v-else ref="diagramContainer" :src="diagramUrl" />
+ </div>
+ <span
+ v-if="node.attrs.isFrontmatter"
+ data-testid="frontmatter-label"
+ class="gl-absolute gl-top-0 gl-right-3"
+ contenteditable="false"
+ >{{ $options.i18n.frontmatter }}:{{ node.attrs.language }}</span
+ >
+ <node-view-content ref="nodeViewContent" as="code" />
+ </node-view-wrapper>
+ </editor-state-observer>
</template>
diff --git a/app/assets/javascripts/content_editor/extensions/diagram.js b/app/assets/javascripts/content_editor/extensions/diagram.js
index f9dfeb92e9a..c59ca8a28b8 100644
--- a/app/assets/javascripts/content_editor/extensions/diagram.js
+++ b/app/assets/javascripts/content_editor/extensions/diagram.js
@@ -1,6 +1,10 @@
+import { textblockTypeInputRule } from '@tiptap/core';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
+import languageLoader from '../services/code_block_language_loader';
import CodeBlockHighlight from './code_block_highlight';
+const backtickInputRegex = /^```(mermaid|plantuml)[\s\n]$/;
+
export default CodeBlockHighlight.extend({
name: 'diagram',
@@ -17,6 +21,9 @@ export default CodeBlockHighlight.extend({
isDiagram: {
default: true,
},
+ showPreview: {
+ default: true,
+ },
};
},
@@ -24,6 +31,11 @@ export default CodeBlockHighlight.extend({
return [
{
priority: PARSE_HTML_PRIORITY_HIGHEST,
+ tag: 'pre[lang="mermaid"]',
+ getAttrs: () => ({ language: 'mermaid' }),
+ },
+ {
+ priority: PARSE_HTML_PRIORITY_HIGHEST,
tag: '[data-diagram]',
getContent(element, schema) {
const source = atob(element.dataset.diagramSrc.replace('data:text/plain;base64,', ''));
@@ -54,6 +66,14 @@ export default CodeBlockHighlight.extend({
},
addInputRules() {
- return [];
+ const getAttributes = (match) => languageLoader?.loadLanguageFromInputRule(match) || {};
+
+ return [
+ textblockTypeInputRule({
+ find: backtickInputRegex,
+ type: this.type,
+ getAttributes,
+ }),
+ ];
},
});
diff --git a/app/assets/javascripts/content_editor/services/asset_resolver.js b/app/assets/javascripts/content_editor/services/asset_resolver.js
index 942457b9664..c0bcddbe58d 100644
--- a/app/assets/javascripts/content_editor/services/asset_resolver.js
+++ b/app/assets/javascripts/content_editor/services/asset_resolver.js
@@ -1,13 +1,24 @@
import { memoize } from 'lodash';
+const parser = new DOMParser();
+
export default ({ renderMarkdown }) => ({
resolveUrl: memoize(async (canonicalSrc) => {
const html = await renderMarkdown(`[link](${canonicalSrc})`);
if (!html) return canonicalSrc;
- const parser = new DOMParser();
const { body } = parser.parseFromString(html, 'text/html');
-
return body.querySelector('a').getAttribute('href');
}),
+
+ renderDiagram: memoize(async (code, language) => {
+ const backticks = '`'.repeat(4);
+ const html = await renderMarkdown(`${backticks}${language}\n${code}\n${backticks}`);
+
+ const { body } = parser.parseFromString(html, 'text/html');
+ const img = body.querySelector('img');
+ if (!img) return '';
+
+ return img.dataset.src || img.getAttribute('src');
+ }),
});
diff --git a/app/assets/javascripts/content_editor/services/code_block_language_loader.js b/app/assets/javascripts/content_editor/services/code_block_language_loader.js
index fa0549d4183..b7cf1bb087c 100644
--- a/app/assets/javascripts/content_editor/services/code_block_language_loader.js
+++ b/app/assets/javascripts/content_editor/services/code_block_language_loader.js
@@ -8,7 +8,7 @@ const codeBlockLanguageLoader = {
allLanguages: CODE_BLOCK_LANGUAGES,
- findOrCreateLanguageBySyntax(value) {
+ findOrCreateLanguageBySyntax(value, isDiagram) {
const lowercaseValue = value?.toLowerCase() || 'plaintext';
return (
this.allLanguages.find(
@@ -16,7 +16,9 @@ const codeBlockLanguageLoader = {
syntax === lowercaseValue || variants?.toLowerCase().split(', ').includes(lowercaseValue),
) || {
syntax: lowercaseValue,
- label: sprintf(__(`Custom (%{language})`), { language: lowercaseValue }),
+ label: sprintf(isDiagram ? __(`Diagram (%{language})`) : __(`Custom (%{language})`), {
+ language: lowercaseValue,
+ }),
}
);
},
diff --git a/app/assets/javascripts/content_editor/services/content_editor.js b/app/assets/javascripts/content_editor/services/content_editor.js
index 52dacb84153..06757e7a280 100644
--- a/app/assets/javascripts/content_editor/services/content_editor.js
+++ b/app/assets/javascripts/content_editor/services/content_editor.js
@@ -52,6 +52,10 @@ export class ContentEditor {
return this._assetResolver.resolveUrl(canonicalSrc);
}
+ renderDiagram(code, language) {
+ return this._assetResolver.renderDiagram(code, language);
+ }
+
async setSerializedContent(serializedContent) {
const { _tiptapEditor: editor, _eventHub: eventHub } = this;
const { doc, tr } = editor.state;
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index fc69dca73a7..54b648e8d03 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -121,7 +121,7 @@ export default {
<div class="d-flex float-left align-items-center align-self-start">
<input
v-if="isSelectable"
- class="mr-2"
+ class="gl-mr-3"
type="checkbox"
:checked="checked"
@change="$emit('handleCheckboxChange', $event.target.checked)"
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index f926169549d..b8b7cbb5bdc 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -453,13 +453,7 @@ export default {
>
</textarea>
- <gl-modal
- ref="modal"
- modal-id="create-task-modal"
- :title="s__('WorkItem|New Task')"
- hide-footer
- body-class="gl-p-0!"
- >
+ <gl-modal ref="modal" size="lg" modal-id="create-task-modal" hide-footer body-class="gl-p-0!">
<create-work-item
is-modal
:initial-title="activeTask.title"
diff --git a/app/assets/javascripts/lib/utils/color_utils.js b/app/assets/javascripts/lib/utils/color_utils.js
index 66d52051905..3d8df4fde05 100644
--- a/app/assets/javascripts/lib/utils/color_utils.js
+++ b/app/assets/javascripts/lib/utils/color_utils.js
@@ -67,7 +67,7 @@ export function darkModeEnabled() {
const ideDarkThemes = ['dark', 'solarized-dark', 'monokai'];
// eslint-disable-next-line @gitlab/require-i18n-strings
- const isWebIde = document.body.dataset.page.startsWith('ide:');
+ const isWebIde = document.body.dataset.page?.startsWith('ide:');
if (isWebIde) {
return ideDarkThemes.includes(window.gon?.user_color_scheme);
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index f397a86a368..e7ac27c5e3e 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -416,7 +416,7 @@ export default {
<gl-form-checkbox
v-if="internalNotesEnabled && canSetInternalNote"
v-model="noteIsInternal"
- class="gl-mb-6"
+ class="gl-mb-2"
data-testid="internal-note-checkbox"
>
{{ $options.i18n.internal }}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue
index 74c0cb44c51..a3bbd569f41 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue
@@ -1,30 +1,68 @@
<script>
+import { GlAlert } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
+import { s__ } from '~/locale';
import Composer from '~/packages_and_registries/package_registry/components/details/metadata/composer.vue';
import Conan from '~/packages_and_registries/package_registry/components/details/metadata/conan.vue';
import Maven from '~/packages_and_registries/package_registry/components/details/metadata/maven.vue';
import Nuget from '~/packages_and_registries/package_registry/components/details/metadata/nuget.vue';
import Pypi from '~/packages_and_registries/package_registry/components/details/metadata/pypi.vue';
import {
+ FETCH_PACKAGE_METADATA_ERROR_MESSAGE,
PACKAGE_TYPE_COMPOSER,
PACKAGE_TYPE_CONAN,
PACKAGE_TYPE_MAVEN,
PACKAGE_TYPE_NUGET,
PACKAGE_TYPE_PYPI,
} from '~/packages_and_registries/package_registry/constants';
+import getPackageMetadataQuery from '../../graphql/queries/get_package_metadata.query.graphql';
+import AdditionalMetadataLoader from './additional_metadata_loader.vue';
export default {
components: {
Composer,
Conan,
+ GlAlert,
Maven,
Nuget,
Pypi,
+ AdditionalMetadataLoader,
},
props: {
- packageEntity: {
- type: Object,
+ packageId: {
+ type: String,
required: true,
},
+ packageType: {
+ type: String,
+ required: true,
+ },
+ },
+ apollo: {
+ packageMetadata: {
+ query: getPackageMetadataQuery,
+ context: {
+ isSingleRequest: true,
+ },
+ variables() {
+ return {
+ id: this.packageId,
+ };
+ },
+ update(data) {
+ return data.package?.metadata || null;
+ },
+ error(error) {
+ this.fetchPackageMetadataError = true;
+ Sentry.captureException(error);
+ },
+ },
+ },
+ data() {
+ return {
+ packageMetadata: null,
+ fetchPackageMetadataError: false,
+ };
},
computed: {
metadataComponent() {
@@ -34,22 +72,43 @@ export default {
[PACKAGE_TYPE_MAVEN]: Maven,
[PACKAGE_TYPE_NUGET]: Nuget,
[PACKAGE_TYPE_PYPI]: Pypi,
- }[this.packageEntity.packageType];
+ }[this.packageType];
},
showMetadata() {
- return this.metadataComponent && this.packageEntity.metadata;
+ return this.metadataComponent && this.packageMetadata;
+ },
+ isLoading() {
+ return this.$apollo.queries.packageMetadata.loading;
},
},
+ i18n: {
+ componentTitle: s__('PackageRegistry|Additional metadata'),
+ fetchPackageMetadataErrorMessage: FETCH_PACKAGE_METADATA_ERROR_MESSAGE,
+ },
};
</script>
<template>
- <div v-if="showMetadata">
- <h3 class="gl-font-lg" data-testid="title">{{ __('Additional Metadata') }}</h3>
- <div class="gl-bg-gray-50 gl-inset-border-1-gray-100 gl-rounded-base" data-testid="main">
+ <div>
+ <h3 v-if="isLoading || showMetadata" class="gl-font-lg" data-testid="title">
+ {{ $options.i18n.componentTitle }}
+ </h3>
+ <gl-alert
+ v-if="fetchPackageMetadataError"
+ variant="danger"
+ @dismiss="fetchPackageMetadataError = false"
+ >
+ {{ $options.i18n.fetchPackageMetadataErrorMessage }}
+ </gl-alert>
+ <additional-metadata-loader v-if="isLoading" />
+ <div
+ v-if="showMetadata"
+ class="gl-bg-gray-50 gl-inset-border-1-gray-100 gl-rounded-base"
+ data-testid="main"
+ >
<component
:is="metadataComponent"
- :package-entity="packageEntity"
+ :package-metadata="packageMetadata"
data-testid="component-is"
/>
</div>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata_loader.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata_loader.vue
new file mode 100644
index 00000000000..628cf441831
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata_loader.vue
@@ -0,0 +1,30 @@
+<script>
+import { GlSkeletonLoader } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlSkeletonLoader,
+ },
+ loader: {
+ width: 302,
+ height: 16,
+ repeat: 2,
+ },
+};
+</script>
+
+<template>
+ <div class="gl-bg-gray-50 gl-inset-border-1-gray-100 gl-rounded-base">
+ <div
+ v-for="index in $options.loader.repeat"
+ :key="index"
+ class="gl-display-flex gl-align-items-center gl-p-4 gl-border-gray-100 gl-border-b-1"
+ >
+ <div class="gl-md-max-w-30p">
+ <gl-skeleton-loader :width="$options.loader.width" :height="$options.loader.height">
+ <rect :width="$options.loader.width" :height="$options.loader.height" rx="4" />
+ </gl-skeleton-loader>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/composer.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/composer.vue
index b6a36a0b00f..e3edaa3e45e 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/composer.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/composer.vue
@@ -18,7 +18,7 @@ export default {
ClipboardButton,
},
props: {
- packageEntity: {
+ packageMetadata: {
type: Object,
required: true,
},
@@ -31,10 +31,10 @@ export default {
<details-row icon="information-o" padding="gl-p-4" dashed data-testid="composer-target-sha">
<gl-sprintf :message="$options.i18n.targetSha">
<template #sha>
- <strong>{{ packageEntity.metadata.targetSha }}</strong>
+ <strong>{{ packageMetadata.targetSha }}</strong>
<clipboard-button
:title="$options.i18n.targetShaCopyButton"
- :text="packageEntity.metadata.targetSha"
+ :text="packageMetadata.targetSha"
category="tertiary"
css-class="gl-p-0!"
/>
@@ -44,10 +44,10 @@ export default {
<details-row icon="information-o" padding="gl-p-4" data-testid="composer-json">
<gl-sprintf :message="$options.i18n.composerJson">
<template #license>
- <strong>{{ packageEntity.metadata.composerJson.license }}</strong>
+ <strong>{{ packageMetadata.composerJson.license }}</strong>
</template>
<template #version>
- <strong>{{ packageEntity.metadata.composerJson.version }}</strong>
+ <strong>{{ packageMetadata.composerJson.version }}</strong>
</template>
</gl-sprintf>
</details-row>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/conan.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/conan.vue
index 10797d74acf..de7c1bc4cd3 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/conan.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/conan.vue
@@ -13,7 +13,7 @@ export default {
GlSprintf,
},
props: {
- packageEntity: {
+ packageMetadata: {
type: Object,
required: true,
},
@@ -25,7 +25,7 @@ export default {
<div>
<details-row icon="information-o" padding="gl-p-4" data-testid="conan-recipe">
<gl-sprintf :message="$options.i18n.recipeText">
- <template #recipe>{{ packageEntity.metadata.recipe }}</template>
+ <template #recipe>{{ packageMetadata.recipe }}</template>
</gl-sprintf>
</details-row>
</div>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/maven.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/maven.vue
index fd9fb49a9f2..7c3eb476a99 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/maven.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/maven.vue
@@ -14,7 +14,7 @@ export default {
GlSprintf,
},
props: {
- packageEntity: {
+ packageMetadata: {
type: Object,
required: true,
},
@@ -27,14 +27,14 @@ export default {
<details-row icon="information-o" padding="gl-p-4" dashed data-testid="maven-app">
<gl-sprintf :message="$options.i18n.appName">
<template #name>
- <strong>{{ packageEntity.metadata.appName }}</strong>
+ <strong>{{ packageMetadata.appName }}</strong>
</template>
</gl-sprintf>
</details-row>
<details-row icon="information-o" padding="gl-p-4" data-testid="maven-group">
<gl-sprintf :message="$options.i18n.appGroup">
<template #group>
- <strong>{{ packageEntity.metadata.appGroup }}</strong>
+ <strong>{{ packageMetadata.appGroup }}</strong>
</template>
</gl-sprintf>
</details-row>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/nuget.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/nuget.vue
index 1360b03856f..1ddd419a639 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/nuget.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/nuget.vue
@@ -14,7 +14,7 @@ export default {
GlSprintf,
},
props: {
- packageEntity: {
+ packageMetadata: {
type: Object,
required: true,
},
@@ -25,7 +25,7 @@ export default {
<template>
<div>
<details-row
- v-if="packageEntity.metadata.projectUrl"
+ v-if="packageMetadata.projectUrl"
icon="project"
padding="gl-p-4"
dashed
@@ -33,22 +33,22 @@ export default {
>
<gl-sprintf :message="$options.i18n.sourceText">
<template #link>
- <gl-link :href="packageEntity.metadata.projectUrl" target="_blank">{{
- packageEntity.metadata.projectUrl
+ <gl-link :href="packageMetadata.projectUrl" target="_blank">{{
+ packageMetadata.projectUrl
}}</gl-link>
</template>
</gl-sprintf>
</details-row>
<details-row
- v-if="packageEntity.metadata.licenseUrl"
+ v-if="packageMetadata.licenseUrl"
icon="license"
padding="gl-p-4"
data-testid="nuget-license"
>
<gl-sprintf :message="$options.i18n.licenseText">
<template #link>
- <gl-link :href="packageEntity.metadata.licenseUrl" target="_blank">{{
- packageEntity.metadata.licenseUrl
+ <gl-link :href="packageMetadata.licenseUrl" target="_blank">{{
+ packageMetadata.licenseUrl
}}</gl-link>
</template>
</gl-sprintf>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/pypi.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/pypi.vue
index 6534eef532c..ef35349c228 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/pypi.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/pypi.vue
@@ -13,7 +13,7 @@ export default {
GlSprintf,
},
props: {
- packageEntity: {
+ packageMetadata: {
type: Object,
required: true,
},
@@ -26,7 +26,7 @@ export default {
<details-row icon="information-o" padding="gl-p-4" data-testid="pypi-required-python">
<gl-sprintf :message="$options.i18n.requiredPython">
<template #pythonVersion>
- <strong>{{ packageEntity.metadata.requiredPython }}</strong>
+ <strong>{{ packageMetadata.requiredPython }}</strong>
</template>
</gl-sprintf>
</details-row>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
index 8b3d781b2df..96b82a20364 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
@@ -1,7 +1,7 @@
<script>
-import { GlLink, GlSprintf } from '@gitlab/ui';
+import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import { first } from 'lodash';
-import createFlash from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { truncateSha } from '~/lib/utils/text_utility';
import { s__, n__ } from '~/locale';
@@ -27,8 +27,10 @@ export default {
combinedUpdateText: s__(
'PackageRegistry|Package updated by commit %{link} on branch %{branch}, built by pipeline %{pipeline}, and published to the registry %{datetime}',
),
+ fetchPackagePipelinesErrorMessage: FETCH_PACKAGE_PIPELINES_ERROR_MESSAGE,
},
components: {
+ GlAlert,
GlLink,
GlSprintf,
HistoryItem,
@@ -54,15 +56,16 @@ export default {
update(data) {
return data.package?.pipelines?.nodes || [];
},
- error() {
- createFlash({ message: FETCH_PACKAGE_PIPELINES_ERROR_MESSAGE });
+ error(error) {
+ this.fetchPackagePipelinesError = true;
+ Sentry.captureException(error);
},
},
},
data() {
return {
pipelines: [],
- showDescription: false,
+ fetchPackagePipelinesError: false,
};
},
computed: {
@@ -109,6 +112,13 @@ export default {
<template>
<div class="issuable-discussion">
<h3 class="gl-font-lg" data-testid="title">{{ __('History') }}</h3>
+ <gl-alert
+ v-if="fetchPackagePipelinesError"
+ variant="danger"
+ @dismiss="fetchPackagePipelinesError = false"
+ >
+ {{ $options.i18n.fetchPackagePipelinesErrorMessage }}
+ </gl-alert>
<package-history-loader v-if="isLoading" />
<ul v-else class="timeline main-notes-list notes gl-mb-4" data-testid="timeline">
<history-item icon="clock" data-testid="created-on">
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
index 65930d94f3e..3c090951b7d 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
@@ -75,6 +75,9 @@ export const FETCH_PACKAGE_DETAILS_ERROR_MESSAGE = s__(
export const FETCH_PACKAGE_PIPELINES_ERROR_MESSAGE = s__(
'PackageRegistry|Something went wrong while fetching the package history.',
);
+export const FETCH_PACKAGE_METADATA_ERROR_MESSAGE = s__(
+ 'PackageRegistry|Something went wrong while fetching the package metadata.',
+);
export const DELETE_PACKAGE_SUCCESS_MESSAGE = s__('PackageRegistry|Package deleted successfully');
export const PACKAGE_REGISTRY_TITLE = __('Package Registry');
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
index 651bd2b4d63..5574020c9e4 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
@@ -27,6 +27,17 @@ query getPackageDetails($id: PackagesPackageID!) {
name
}
}
+ pipelines(first: 1) {
+ nodes {
+ ref
+ id
+ project {
+ id
+ name
+ webUrl
+ }
+ }
+ }
packageFiles(first: 100) {
nodes {
id
@@ -72,37 +83,15 @@ query getPackageDetails($id: PackagesPackageID!) {
}
}
metadata {
- ... on ComposerMetadata {
- targetSha
- composerJson {
- license
- version
- }
- }
- ... on PypiMetadata {
- id
- requiredPython
- }
- ... on ConanMetadata {
- id
- packageChannel
- packageUsername
- recipe
- recipePath
- }
... on MavenMetadata {
id
appName
appGroup
appVersion
- path
}
-
... on NugetMetadata {
id
iconUrl
- licenseUrl
- projectUrl
}
}
}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_metadata.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_metadata.query.graphql
new file mode 100644
index 00000000000..fc8b39b37ab
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_metadata.query.graphql
@@ -0,0 +1,39 @@
+query getPackageMetadata($id: PackagesPackageID!) {
+ package(id: $id) {
+ id
+ packageType
+ metadata {
+ ... on ComposerMetadata {
+ targetSha
+ composerJson {
+ license
+ version
+ }
+ }
+ ... on PypiMetadata {
+ id
+ requiredPython
+ }
+ ... on ConanMetadata {
+ id
+ packageChannel
+ packageUsername
+ recipe
+ recipePath
+ }
+ ... on MavenMetadata {
+ id
+ appName
+ appGroup
+ appVersion
+ path
+ }
+ ... on NugetMetadata {
+ id
+ iconUrl
+ licenseUrl
+ projectUrl
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
index 162b420a784..768c8d6478b 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
@@ -27,6 +27,9 @@ import DeletePackage from '~/packages_and_registries/package_registry/components
import {
PACKAGE_TYPE_NUGET,
PACKAGE_TYPE_COMPOSER,
+ PACKAGE_TYPE_CONAN,
+ PACKAGE_TYPE_MAVEN,
+ PACKAGE_TYPE_PYPI,
DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
@@ -122,6 +125,9 @@ export default {
packageFiles() {
return this.packageEntity.packageFiles?.nodes;
},
+ packageType() {
+ return this.packageEntity.packageType;
+ },
isLoading() {
return this.$apollo.queries.packageEntity.loading;
},
@@ -130,7 +136,7 @@ export default {
},
tracking() {
return {
- category: packageTypeToTrackCategory(this.packageEntity.packageType),
+ category: packageTypeToTrackCategory(this.packageType),
};
},
hasVersions() {
@@ -140,10 +146,19 @@ export default {
return this.packageEntity.dependencyLinks?.nodes || [];
},
showDependencies() {
- return this.packageEntity.packageType === PACKAGE_TYPE_NUGET;
+ return this.packageType === PACKAGE_TYPE_NUGET;
},
showFiles() {
- return this.packageEntity.packageType !== PACKAGE_TYPE_COMPOSER;
+ return this.packageType !== PACKAGE_TYPE_COMPOSER;
+ },
+ showMetadata() {
+ return [
+ PACKAGE_TYPE_COMPOSER,
+ PACKAGE_TYPE_CONAN,
+ PACKAGE_TYPE_MAVEN,
+ PACKAGE_TYPE_NUGET,
+ PACKAGE_TYPE_PYPI,
+ ].includes(this.packageType);
},
},
methods: {
@@ -262,7 +277,11 @@ export default {
<installation-commands :package-entity="packageEntity" />
- <additional-metadata :package-entity="packageEntity" />
+ <additional-metadata
+ v-if="showMetadata"
+ :package-id="packageEntity.id"
+ :package-type="packageType"
+ />
</div>
<package-files
diff --git a/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue
index 5ecacb84d65..ccb449f96e1 100644
--- a/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue
+++ b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue
@@ -12,6 +12,7 @@ import {
import { toSafeInteger } from 'lodash';
import csrf from '~/lib/utils/csrf';
import { __, n__, s__, sprintf } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import SignupCheckbox from './signup_checkbox.vue';
const DENYLIST_TYPE_RAW = 'raw';
@@ -31,7 +32,12 @@ export default {
GlLink,
SignupCheckbox,
GlModal,
+ PasswordComplexityCheckboxGroup: () =>
+ import(
+ 'ee_component/pages/admin/application_settings/general/components/password_complexity_checkbox_group.vue'
+ ),
},
+ mixins: [glFeatureFlagMixin()],
inject: [
'host',
'settingsPath',
@@ -178,6 +184,9 @@ export default {
this.submitForm();
},
+ setPasswordComplexity({ name, value }) {
+ this.$set(this.form, name, value);
+ },
submitForm() {
this.$refs.form.submit();
},
@@ -291,9 +300,7 @@ export default {
<template #description>
<gl-sprintf
:message="
- s__(
- 'ApplicationSettings|See GitLab\'s %{linkStart}Password Policy Guidelines%{linkEnd}.',
- )
+ s__('ApplicationSettings|See %{linkStart}password policy guidelines%{linkEnd}.')
"
>
<template #link="{ content }">
@@ -305,6 +312,10 @@ export default {
</template>
</gl-form-group>
+ <password-complexity-checkbox-group
+ v-if="glFeatures.passwordComplexity"
+ @set-password-complexity="setPasswordComplexity"
+ />
<gl-form-group
:description="$options.i18n.domainAllowListDescription"
:label="$options.i18n.domainAllowListLabel"
diff --git a/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js b/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js
index a50d8de0e88..0d5c55cb87b 100644
--- a/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js
+++ b/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js
@@ -18,6 +18,10 @@ export default function initSignupRestrictions(elementSelector = '#js-signup-for
'domainDenylistEnabled',
'denylistTypeRawSelected',
'emailRestrictionsEnabled',
+ 'passwordNumberRequired',
+ 'passwordLowercaseRequired',
+ 'passwordUppercaseRequired',
+ 'passwordSymbolRequired',
],
});
diff --git a/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue b/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue
index be7a89c2869..ef99d540c86 100644
--- a/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue
+++ b/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue
@@ -274,7 +274,7 @@ export default {
<template #collapsed>
<div v-gl-tooltip.viewport.left :title="dateLabel" class="sidebar-collapsed-icon">
<gl-icon :size="16" name="calendar" />
- <span class="collapse-truncated-title">{{ formattedDate }}</span>
+ <span class="gl-pt-2 gl-px-3 gl-font-sm">{{ formattedDate }}</span>
</div>
<sidebar-inherit-date
v-if="canInherit && !initialLoading"
diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue
index 77e41648e9b..b8804de653f 100644
--- a/app/assets/javascripts/sidebar/components/participants/participants.vue
+++ b/app/assets/javascripts/sidebar/components/participants/participants.vue
@@ -99,7 +99,9 @@ export default {
>
<gl-icon name="users" />
<gl-loading-icon v-if="loading" size="sm" />
- <span v-else data-testid="collapsed-count"> {{ participantCount }} </span>
+ <span v-else data-testid="collapsed-count" class="gl-pt-2 gl-px-3 gl-font-sm">
+ {{ participantCount }}
+ </span>
</div>
<div
v-if="showParticipantLabel"
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
index 897cab45fe4..3d8a2cd847c 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
@@ -322,7 +322,7 @@ export default {
class="sidebar-collapsed-icon"
>
<gl-icon :aria-label="attributeTypeTitle" :name="attributeTypeIcon" />
- <span class="collapse-truncated-title">
+ <span class="collapse-truncated-title gl-pt-2 gl-px-3 gl-font-sm">
{{ attributeTitle }}
</span>
</div>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
index 7b67c34ded6..465f971717f 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
@@ -58,7 +58,7 @@ export default {
} else if (this.showEstimateOnlyState || this.showSpentOnlyState) {
return 'bold';
} else if (this.showNoTimeTrackingState) {
- return 'no-value';
+ return 'no-value collapse-truncated-title gl-pt-2 gl-px-3 gl-font-sm';
}
return '';
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
index 914d146fcc3..178c57a5666 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
@@ -126,7 +126,7 @@ export default {
<div class="gl-display-flex gl-align-items-center">
<span
:style="{ backgroundColor: label.color }"
- class="gl-display-inline-block mr-2 p-2"
+ class="gl-display-inline-block gl-mr-3 gl-p-3"
></span>
<div>{{ getLabelName(label) }}</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue
index 57ee816c4c7..57e3ee4aaa5 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue
@@ -92,7 +92,9 @@ export default {
@click="handleCollapsedClick"
>
<gl-icon name="labels" />
- <span class="gl-font-base gl-line-height-24">{{ selectedLabels.length }}</span>
+ <span class="collapse-truncated-title gl-pt-2 gl-px-3 gl-font-sm">{{
+ selectedLabels.length
+ }}</span>
</div>
<span
v-if="!selectedLabels.length"
diff --git a/app/assets/javascripts/work_items/components/item_state.vue b/app/assets/javascripts/work_items/components/item_state.vue
index 0b6c1a75bb2..69670d3471c 100644
--- a/app/assets/javascripts/work_items/components/item_state.vue
+++ b/app/assets/javascripts/work_items/components/item_state.vue
@@ -49,14 +49,28 @@ export default {
</script>
<template>
- <gl-form-group :label="$options.i18n.status" :label-for="$options.labelId">
+ <gl-form-group
+ :label="$options.i18n.status"
+ :label-for="$options.labelId"
+ label-cols="3"
+ label-cols-lg="2"
+ label-class="gl-pb-0!"
+ class="gl-align-items-center"
+ >
<gl-form-select
:id="$options.labelId"
:value="state"
:options="$options.states"
:disabled="loading"
- class="gl-w-auto"
+ class="gl-w-auto hide-select-decoration"
@change="setState"
/>
</gl-form-group>
</template>
+
+<style>
+.hide-select-decoration:not(:focus, :hover) {
+ background-image: none;
+ box-shadow: none;
+}
+</style>
diff --git a/app/assets/javascripts/work_items/components/item_title.vue b/app/assets/javascripts/work_items/components/item_title.vue
index 232510b108d..ce2fa158596 100644
--- a/app/assets/javascripts/work_items/components/item_title.vue
+++ b/app/assets/javascripts/work_items/components/item_title.vue
@@ -40,18 +40,18 @@ export default {
<template>
<h2
- class="gl-font-weight-normal gl-sm-font-weight-bold gl-my-5 gl-display-inline-block"
+ class="gl-font-weight-normal gl-sm-font-weight-bold gl-my-5 gl-w-full"
:class="{ 'gl-cursor-not-allowed': disabled }"
aria-labelledby="item-title"
>
- <span
+ <div
id="item-title"
ref="titleEl"
role="textbox"
:aria-label="__('Title')"
:data-placeholder="placeholder"
:contenteditable="!disabled"
- class="gl-pseudo-placeholder"
+ class="gl-pseudo-placeholder gl-px-4 gl-py-3 gl-ml-n4 gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base"
@blur="handleBlur"
@keyup="handleInput"
@keydown.enter.exact="handleSubmit"
@@ -59,7 +59,8 @@ export default {
@keydown.meta.u.prevent
@keydown.ctrl.b.prevent
@keydown.meta.b.prevent
- >{{ title }}</span
>
+ {{ title }}
+ </div>
</h2>
</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index d9e92ead86c..da8fe222b6a 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -115,7 +115,7 @@ export default {
</gl-skeleton-loader>
</div>
<template v-else>
- <div class="gl-display-flex">
+ <div class="gl-display-flex gl-align-items-start">
<work-item-title
:work-item-id="workItem.id"
:work-item-title="workItem.title"
@@ -127,7 +127,7 @@ export default {
<work-item-actions
:work-item-id="workItem.id"
:can-delete="canDelete"
- class="gl-ml-auto gl-mt-5"
+ class="gl-ml-auto gl-mt-6"
@deleteWorkItem="$emit('deleteWorkItem')"
@error="error = $event"
/>
diff --git a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
index 425b8290d44..d1c8022ac57 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
@@ -98,7 +98,15 @@ export default {
</script>
<template>
- <gl-modal ref="modal" hide-footer size="lg" modal-id="work-item-detail-modal" @hide="closeModal">
+ <gl-modal
+ ref="modal"
+ hide-footer
+ size="lg"
+ modal-id="work-item-detail-modal"
+ header-class="gl-p-0 gl-pb-2!"
+ body-class="gl-pb-6!"
+ @hide="closeModal"
+ >
<gl-alert v-if="error" variant="danger" @dismiss="error = false">
{{ error }}
</gl-alert>
@@ -106,6 +114,7 @@ export default {
<work-item-detail
:work-item-parent-id="issueGid"
:work-item-id="workItemId"
+ class="gl-p-5 gl-mt-n3"
@deleteWorkItem="deleteWorkItem"
/>
</gl-modal>
@@ -114,7 +123,7 @@ export default {
<style>
/* hide the existing modal header
*/
-#work-item-detail-modal .modal-header {
+#work-item-detail-modal .modal-header * {
display: none;
}
</style>
diff --git a/app/assets/stylesheets/framework/diffs.scss b/app/assets/stylesheets/framework/diffs.scss
index 904d041fdc9..47471310ef3 100644
--- a/app/assets/stylesheets/framework/diffs.scss
+++ b/app/assets/stylesheets/framework/diffs.scss
@@ -82,7 +82,7 @@
}
&:hover {
- background-color: $gray-normal;
+ background-color: $gray-10;
}
svg {
diff --git a/app/assets/stylesheets/framework/feature_highlight.scss b/app/assets/stylesheets/framework/feature_highlight.scss
index 36f1b1f2903..0a01b14a37d 100644
--- a/app/assets/stylesheets/framework/feature_highlight.scss
+++ b/app/assets/stylesheets/framework/feature_highlight.scss
@@ -27,7 +27,7 @@
background-color: $indigo-50;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
- border-bottom: 1px solid darken($gray-normal, 8%);
+ border-bottom: 1px solid darken($gray-10, 8%);
}
.feature-highlight-popover {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index ced62926218..cbecae24031 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -464,7 +464,7 @@
float: left;
margin-right: 5px;
border-radius: 50%;
- border: 1px solid $gray-normal;
+ border: 1px solid $gray-10;
}
.notification-dot {
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 2cea3b96ff7..0d1196851e0 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -452,7 +452,7 @@
@mixin avatar-counter($border-radius: 1em) {
background-color: $gray-darkest;
color: $white;
- border: 1px solid $gray-normal;
+ border: 1px solid $gray-10;
border-radius: $border-radius;
font-family: $regular-font;
font-size: 9px;
diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss
index f57d906e73c..98808c25c8e 100644
--- a/app/assets/stylesheets/framework/responsive_tables.scss
+++ b/app/assets/stylesheets/framework/responsive_tables.scss
@@ -95,7 +95,7 @@
display: block;
align-self: stretch;
min-height: 0;
- background-color: $gray-normal;
+ background-color: $gray-10;
border-top: 1px solid $border-color;
.table-action-buttons {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index c9bc1c9189e..76c1f4c2093 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -92,7 +92,6 @@ $white-transparent: rgba($white, 0.8) !default;
$gray-lightest: #fdfdfd !default;
$gray-light: #fafafa !default;
$gray-lighter: #f9f9f9 !default;
-$gray-normal: #f5f5f5 !default;
$gray-dark: darken($gray-light, $darken-dark-factor) !default;
$gray-darker: #eee !default;
$gray-darkest: #c4c4c4 !default;
@@ -169,7 +168,7 @@ $purple-800: #453894 !default;
$purple-900: #2f2a6b !default;
$purple-950: #232150 !default;
-$gray-10: #fafafa !default;
+$gray-10: #f5f5f5 !default;
$gray-50: #f0f0f0 !default;
$gray-100: #dbdbdb !default;
$gray-200: #bfbfbf !default;
@@ -351,13 +350,13 @@ $border-white-light: darken($white, $darken-border-factor) !default;
$border-white-normal: darken($white-normal, $darken-border-factor) !default;
$border-gray-light: darken($gray-light, $darken-border-factor);
-$border-gray-normal: darken($gray-normal, $darken-border-factor);
-$border-gray-normal-dashed: darken($gray-normal, $darken-border-dashed-factor);
+$border-gray-normal: darken($gray-10, $darken-border-factor);
+$border-gray-normal-dashed: darken($gray-10, $darken-border-dashed-factor);
/*
* UI elements
*/
-$contextual-sidebar-bg-color: #f5f5f5;
+$contextual-sidebar-bg-color: $gray-10;
$contextual-sidebar-border-color: #e9e9e9;
$border-color: $gray-100;
$shadow-color: $t-gray-a-08;
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index cfd215b81b8..3885a5c835d 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -76,7 +76,7 @@
}
.dark-well {
- background-color: $gray-normal;
+ background-color: $gray-10;
}
.card.card-body-centered {
diff --git a/app/assets/stylesheets/highlight/conflict_colors.scss b/app/assets/stylesheets/highlight/conflict_colors.scss
index 910dc6f4ad6..d5e278ae4b4 100644
--- a/app/assets/stylesheets/highlight/conflict_colors.scss
+++ b/app/assets/stylesheets/highlight/conflict_colors.scss
@@ -95,17 +95,17 @@ $conflict-colors: (
solarized_dark_header_not_chosen : rgba(#839496, 0.25),
solarized_dark_line_not_chosen : rgba(#839496, 0.15),
- none_header_head_neutral : $gray-normal,
- none_line_head_neutral : $gray-normal,
- none_button_head_neutral : $gray-normal,
+ none_header_head_neutral : $gray-10,
+ none_line_head_neutral : $gray-10,
+ none_button_head_neutral : $gray-10,
none_header_head_chosen : $gray-darker,
none_line_head_chosen : $gray-darker,
none_button_head_chosen : $gray-darker,
- none_header_origin_neutral : $gray-normal,
- none_line_origin_neutral : $gray-normal,
- none_button_origin_neutral : $gray-normal,
+ none_header_origin_neutral : $gray-10,
+ none_line_origin_neutral : $gray-10,
+ none_button_origin_neutral : $gray-10,
none_header_origin_chosen : $gray-darker,
none_line_origin_chosen : $gray-darker,
diff --git a/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss b/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss
index 25a565ce2ba..b13d7997769 100644
--- a/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss
+++ b/app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss
@@ -140,7 +140,7 @@
}
hr {
- border-color: var(--ide-border-color, darken($gray-normal, 8%));
+ border-color: var(--ide-border-color, darken($gray-10, 8%));
}
.md h1,
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index a4a82fdcef3..10d5ce54573 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -93,7 +93,7 @@ $ide-commit-header-height: 48px;
display: flex;
align-items: center;
padding: $grid-size $gl-padding;
- background-color: var(--ide-background-hover, $gray-normal);
+ background-color: var(--ide-background-hover, $gray-10);
border-right: 1px solid var(--ide-border-color, $white-dark);
border-bottom: 1px solid var(--ide-border-color, $white-dark);
@@ -135,7 +135,7 @@ $ide-commit-header-height: 48px;
box-shadow: none !important;
font-weight: normal !important;
- background-color: var(--ide-background-hover, $gray-normal);
+ background-color: var(--ide-background-hover, $gray-10);
border-right: 1px solid var(--ide-border-color, $white-dark);
border-bottom: 1px solid var(--ide-border-color, $white-dark);
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index c177d0b74a2..e8519157d98 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -5,7 +5,7 @@
border-left: 1px solid $border-color;
border-bottom: 0;
border-radius: $border-radius-small $border-radius-small 0 0;
- background: $gray-normal;
+ background: $gray-10;
}
#editor,
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 04e0ef6631e..10a81365a1b 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -17,14 +17,14 @@
.issue-token:hover &,
.issue-token-link:focus > & {
- background-color: $gray-normal;
+ background-color: $gray-10;
color: $blue-800;
text-decoration: none;
}
}
.issue-token-title {
- background-color: $gray-normal;
+ background-color: $gray-10;
transition: background $general-hover-transition-duration $general-hover-transition-curve;
.issue-token:hover &,
@@ -34,7 +34,7 @@
}
.issue-token-remove-button {
- background-color: $gray-normal;
+ background-color: $gray-10;
transition: background $general-hover-transition-duration $general-hover-transition-curve;
&:hover,
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 8755db83d35..b74166fd723 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -276,7 +276,7 @@ input[type='checkbox']:hover {
width: $search-avatar-size;
height: $search-avatar-size;
border-radius: 50%;
- border: 1px solid $gray-normal;
+ border: 1px solid $gray-10;
}
}
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index 4cefa60b12a..b958418e916 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -40,7 +40,7 @@ body {
line-height: 1.5;
color: #fafafa;
text-align: left;
- background-color: #1f1f1f;
+ background-color: #333;
}
ul {
margin-top: 0;
@@ -430,7 +430,7 @@ a.gl-badge.badge-warning:active {
.gl-form-input:not(.form-control-plaintext):not([type="color"]):read-only,
.gl-form-input.form-control:disabled,
.gl-form-input.form-control:not(.form-control-plaintext):not([type="color"]):read-only {
- background-color: #1f1f1f;
+ background-color: #333;
box-shadow: inset 0 0 0 1px #404040;
}
.gl-form-input:disabled,
@@ -1034,7 +1034,7 @@ input {
z-index: 600;
width: 256px;
top: var(--header-height, 48px);
- background-color: #f5f5f5;
+ background-color: #333;
border-right: 1px solid #e9e9e9;
transform: translate3d(0, 0, 0);
}
@@ -1402,7 +1402,7 @@ input {
color: #999;
display: flex;
align-items: center;
- background-color: #f5f5f5;
+ background-color: #333;
position: fixed;
bottom: 0;
width: 255px;
@@ -1698,7 +1698,7 @@ svg.s16 {
border-radius: 4px;
}
body.gl-dark {
- --gray-10: #1f1f1f;
+ --gray-10: #333;
--gray-50: #303030;
--gray-100: #404040;
--gray-200: #525252;
@@ -1939,7 +1939,7 @@ body.gl-dark .navbar-gitlab .search form .search-input {
}
body.gl-dark {
- --gray-10: #1f1f1f;
+ --gray-10: #333;
--gray-50: #303030;
--gray-100: #404040;
--gray-200: #525252;
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index 6753f0cb46b..cb3c97f18a3 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -415,7 +415,7 @@ a.gl-badge.badge-warning:active {
.gl-form-input:not(.form-control-plaintext):not([type="color"]):read-only,
.gl-form-input.form-control:disabled,
.gl-form-input.form-control:not(.form-control-plaintext):not([type="color"]):read-only {
- background-color: #fafafa;
+ background-color: #f5f5f5;
box-shadow: inset 0 0 0 1px #dbdbdb;
}
.gl-form-input:disabled,
diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss
index 7ac1a187bb8..3090edfb123 100644
--- a/app/assets/stylesheets/startup/startup-signin.scss
+++ b/app/assets/stylesheets/startup/startup-signin.scss
@@ -297,7 +297,7 @@ fieldset:disabled a.btn {
.gl-form-input:not(.form-control-plaintext):not([type="color"]):read-only,
.gl-form-input.form-control:disabled,
.gl-form-input.form-control:not(.form-control-plaintext):not([type="color"]):read-only {
- background-color: #fafafa;
+ background-color: #f5f5f5;
box-shadow: inset 0 0 0 1px #dbdbdb;
}
.gl-form-input:disabled,
diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss
index fe8a5aec1b3..ff36257e3cf 100644
--- a/app/assets/stylesheets/themes/_dark.scss
+++ b/app/assets/stylesheets/themes/_dark.scss
@@ -86,7 +86,7 @@ $purple-950: #f4f0ff;
$gray-lightest: #222;
$gray-light: $gray-50;
$gray-lighter: #303030;
-$gray-normal: #333;
+$gray-10: #333;
$gray-dark: $gray-100;
$gray-darker: #4f4f4f;
$gray-darkest: #c4c4c4;
diff --git a/app/finders/crm/contacts_finder.rb b/app/finders/crm/contacts_finder.rb
index f7c2625f788..29f3d6f0f16 100644
--- a/app/finders/crm/contacts_finder.rb
+++ b/app/finders/crm/contacts_finder.rb
@@ -8,6 +8,7 @@
# group: Group, required
# search: String, optional
# state: CustomerRelations::ContactStateEnum, optional
+# ids: int[], optional
module Crm
class ContactsFinder
include Gitlab::Allowable
@@ -24,6 +25,7 @@ module Crm
return CustomerRelations::Contact.none unless root_group
contacts = root_group.contacts
+ contacts = by_ids(contacts)
contacts = by_state(contacts)
contacts = by_search(contacts)
contacts.sort_by_name
@@ -53,6 +55,12 @@ module Crm
contacts.search_by_state(params[:state])
end
+ def by_ids(contacts)
+ return contacts unless ids?
+
+ contacts.id_in(params[:ids])
+ end
+
def search?
params[:search].present?
end
@@ -60,5 +68,9 @@ module Crm
def state?
params[:state].present?
end
+
+ def ids?
+ params[:ids].present?
+ end
end
end
diff --git a/app/finders/crm/organizations_finder.rb b/app/finders/crm/organizations_finder.rb
index 1a3df05aa11..5a8ab148ef3 100644
--- a/app/finders/crm/organizations_finder.rb
+++ b/app/finders/crm/organizations_finder.rb
@@ -8,6 +8,7 @@
# group: Group, required
# search: String, optional
# state: CustomerRelations::OrganizationStateEnum, optional
+# ids: int[], optional
module Crm
class OrganizationsFinder
include Gitlab::Allowable
@@ -24,6 +25,7 @@ module Crm
return CustomerRelations::Organization.none unless root_group
organizations = root_group.organizations
+ organizations = by_ids(organizations)
organizations = by_search(organizations)
organizations = by_state(organizations)
organizations.sort_by_name
@@ -53,6 +55,12 @@ module Crm
organizations.search_by_state(params[:state])
end
+ def by_ids(organizations)
+ return organizations unless ids?
+
+ organizations.id_in(params[:ids])
+ end
+
def search?
params[:search].present?
end
@@ -60,5 +68,9 @@ module Crm
def state?
params[:state].present?
end
+
+ def ids?
+ params[:ids].present?
+ end
end
end
diff --git a/app/graphql/mutations/incident_management/timeline_event/create.rb b/app/graphql/mutations/incident_management/timeline_event/create.rb
index cbc708a2530..1907954cada 100644
--- a/app/graphql/mutations/incident_management/timeline_event/create.rb
+++ b/app/graphql/mutations/incident_management/timeline_event/create.rb
@@ -23,7 +23,9 @@ module Mutations
authorize!(incident)
- response ::IncidentManagement::TimelineEvents::CreateService.new(incident, current_user, args).execute
+ response ::IncidentManagement::TimelineEvents::CreateService.new(
+ incident, current_user, args.merge(editable: true)
+ ).execute
end
private
diff --git a/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb b/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
index 73a20b8a380..31ae29d896b 100644
--- a/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
+++ b/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
@@ -21,7 +21,8 @@ module Mutations
current_user,
promoted_from_note: note,
note: note.note,
- occurred_at: note.created_at
+ occurred_at: note.created_at,
+ editable: true
).execute
end
diff --git a/app/graphql/resolvers/crm/contacts_resolver.rb b/app/graphql/resolvers/crm/contacts_resolver.rb
index 9e235669657..58d0e2ce13d 100644
--- a/app/graphql/resolvers/crm/contacts_resolver.rb
+++ b/app/graphql/resolvers/crm/contacts_resolver.rb
@@ -4,6 +4,7 @@ module Resolvers
module Crm
class ContactsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
+ include ResolvesIds
authorize :read_crm_contact
@@ -17,7 +18,13 @@ module Resolvers
required: false,
description: 'State of the contacts to search for.'
+ argument :ids, [::Types::GlobalIDType[CustomerRelations::Contact]],
+ required: false,
+ description: 'Filter contacts by IDs.'
+
def resolve(**args)
+ args[:ids] = resolve_ids(args.delete(:ids))
+
::Crm::ContactsFinder.new(current_user, { group: group }.merge(args)).execute
end
diff --git a/app/graphql/resolvers/crm/organizations_resolver.rb b/app/graphql/resolvers/crm/organizations_resolver.rb
index ef0c930a94a..ca0a908ee22 100644
--- a/app/graphql/resolvers/crm/organizations_resolver.rb
+++ b/app/graphql/resolvers/crm/organizations_resolver.rb
@@ -4,6 +4,7 @@ module Resolvers
module Crm
class OrganizationsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
+ include ResolvesIds
authorize :read_crm_organization
@@ -17,7 +18,13 @@ module Resolvers
required: false,
description: 'State of the organization to search for.'
+ argument :ids, [Types::GlobalIDType[CustomerRelations::Organization]],
+ required: false,
+ description: 'Filter organizations by IDs.'
+
def resolve(**args)
+ args[:ids] = resolve_ids(args.delete(:ids))
+
::Crm::OrganizationsFinder.new(current_user, { group: group }.merge(args)).execute
end
diff --git a/app/models/projects/build_artifacts_size_refresh.rb b/app/models/projects/build_artifacts_size_refresh.rb
index c92898068c3..dee4afdefa6 100644
--- a/app/models/projects/build_artifacts_size_refresh.rb
+++ b/app/models/projects/build_artifacts_size_refresh.rb
@@ -50,6 +50,7 @@ module Projects
scope :stale, -> { with_state(:running).where('updated_at < ?', STALE_WINDOW.ago) }
scope :remaining, -> { with_state(:created, :pending).or(stale) }
+ scope :processing_queue, -> { remaining.order(state: :desc) }
def self.enqueue_refresh(projects)
now = Time.zone.now
@@ -65,8 +66,7 @@ module Projects
next_refresh = nil
transaction do
- next_refresh = remaining
- .order(:state, :updated_at)
+ next_refresh = processing_queue
.lock('FOR UPDATE SKIP LOCKED')
.take
diff --git a/app/models/release.rb b/app/models/release.rb
index 655a2ba4709..ee5d7bab190 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -31,6 +31,7 @@ class Release < ApplicationRecord
validates :description, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }, if: :description_changed?
validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") }
validates :links, nested_attributes_duplicates: { scope: :release, child_attributes: %i[name url filepath] }
+ validates :author_id, presence: true, on: :create, if: :validate_release_with_author?
scope :sorted, -> { order(released_at: :desc) }
scope :preloaded, -> {
@@ -117,6 +118,10 @@ class Release < ApplicationRecord
end
end
+ def validate_release_with_author?
+ Feature.enabled?(:validate_release_with_author, self.project)
+ end
+
def set_released_at
self.released_at ||= created_at
end
diff --git a/app/services/incident_management/timeline_events/create_service.rb b/app/services/incident_management/timeline_events/create_service.rb
index 7d287e1bd82..3a7e8d2509d 100644
--- a/app/services/incident_management/timeline_events/create_service.rb
+++ b/app/services/incident_management/timeline_events/create_service.rb
@@ -3,6 +3,7 @@
module IncidentManagement
module TimelineEvents
DEFAULT_ACTION = 'comment'
+ DEFAULT_EDITABLE = false
class CreateService < TimelineEvents::BaseService
def initialize(incident, user, params)
@@ -23,7 +24,8 @@ module IncidentManagement
action: params.fetch(:action, DEFAULT_ACTION),
note_html: params[:note_html].presence || params[:note],
occurred_at: params[:occurred_at],
- promoted_from_note: params[:promoted_from_note]
+ promoted_from_note: params[:promoted_from_note],
+ editable: params.fetch(:editable, DEFAULT_EDITABLE)
}
timeline_event = IncidentManagement::TimelineEvent.new(timeline_event_params)
diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb
index fe8b4879561..347c6cd3e05 100644
--- a/app/services/incident_management/timeline_events/update_service.rb
+++ b/app/services/incident_management/timeline_events/update_service.rb
@@ -17,6 +17,7 @@ module IncidentManagement
end
def execute
+ return error_non_editable unless timeline_event.editable?
return error_no_permissions unless allowed?
if timeline_event.update(update_params)
@@ -56,6 +57,10 @@ module IncidentManagement
:none
end
+
+ def error_non_editable
+ error(_('You cannot edit this timeline event.'))
+ end
end
end
end
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index db13a7f7ba4..a749fbd1eec 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -1,7 +1,7 @@
- max_date = ::Gitlab::CurrentSettings.max_ssh_key_lifetime_from_now.to_date if ssh_key_expiration_policy_enabled?
%div
= form_for [:profile, @key], html: { class: 'js-requires-input' } do |f|
- = form_errors(@key)
+ = form_errors(@key, pajamas_alert: true)
.form-group
= f.label :key, s_('Profiles|Key'), class: 'label-bold'
diff --git a/config/feature_flags/development/validate_release_with_author.yml b/config/feature_flags/development/validate_release_with_author.yml
new file mode 100644
index 00000000000..45102103183
--- /dev/null
+++ b/config/feature_flags/development/validate_release_with_author.yml
@@ -0,0 +1,8 @@
+---
+name: validate_release_with_author
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89694
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365350
+milestone: '15.1'
+type: development
+group: group::release
+default_enabled: false
diff --git a/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml b/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml
index f1bff756c5f..791cad7b6e2 100644
--- a/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml
+++ b/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml
@@ -10,6 +10,29 @@ value_type: number
status: active
time_frame: 28d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - users_viewing_analytics_group_devops_adoption
+ - i_analytics_dev_ops_adoption
+ - i_analytics_dev_ops_score
+ - p_analytics_merge_request
+ - i_analytics_instance_statistics
+ - g_analytics_contribution
+ - g_analytics_insights
+ - g_analytics_issues
+ - g_analytics_productivity
+ - g_analytics_valuestream
+ - p_analytics_pipelines
+ - p_analytics_code_reviews
+ - p_analytics_valuestream
+ - p_analytics_insights
+ - p_analytics_issues
+ - p_analytics_repo
+ - i_analytics_cohorts
+ - p_analytics_ci_cd_pipelines
+ - p_analytics_ci_cd_deployment_frequency
+ - p_analytics_ci_cd_lead_time
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216174846_p_analytics_pipelines.yml b/config/metrics/counts_7d/20210216174846_p_analytics_pipelines.yml
index c34f5644053..8c491bc24b1 100644
--- a/config/metrics/counts_all/20210216174846_p_analytics_pipelines.yml
+++ b/config/metrics/counts_7d/20210216174846_p_analytics_pipelines.yml
@@ -8,8 +8,12 @@ product_group: group::optimize
product_category:
value_type: number
status: active
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - p_analytics_pipelines
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216174850_p_analytics_valuestream.yml b/config/metrics/counts_7d/20210216174850_p_analytics_valuestream.yml
index 671cd9a10b3..0cb77ed4933 100644
--- a/config/metrics/counts_all/20210216174850_p_analytics_valuestream.yml
+++ b/config/metrics/counts_7d/20210216174850_p_analytics_valuestream.yml
@@ -8,8 +8,12 @@ product_group: group::optimize
product_category:
value_type: number
status: active
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - p_analytics_valuestream
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216174856_p_analytics_repo.yml b/config/metrics/counts_7d/20210216174856_p_analytics_repo.yml
index 5e6e308d8d2..fc1fb3470db 100644
--- a/config/metrics/counts_all/20210216174856_p_analytics_repo.yml
+++ b/config/metrics/counts_7d/20210216174856_p_analytics_repo.yml
@@ -8,8 +8,12 @@ product_group: group::optimize
product_category:
value_type: number
status: active
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - p_analytics_repo
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216174858_i_analytics_cohorts.yml b/config/metrics/counts_7d/20210216174858_i_analytics_cohorts.yml
index 9a06f4c44df..705cfefc5b5 100644
--- a/config/metrics/counts_all/20210216174858_i_analytics_cohorts.yml
+++ b/config/metrics/counts_7d/20210216174858_i_analytics_cohorts.yml
@@ -8,8 +8,12 @@ product_group: group::optimize
product_category:
value_type: number
status: active
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - i_analytics_cohorts
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216174900_i_analytics_dev_ops_score.yml b/config/metrics/counts_7d/20210216174900_i_analytics_dev_ops_score.yml
index 9130eb837e7..72db511d1e7 100644
--- a/config/metrics/counts_all/20210216174900_i_analytics_dev_ops_score.yml
+++ b/config/metrics/counts_7d/20210216174900_i_analytics_dev_ops_score.yml
@@ -8,8 +8,12 @@ product_group: group::optimize
product_category:
value_type: number
status: active
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - i_analytics_dev_ops_score
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216174902_g_analytics_merge_request.yml b/config/metrics/counts_7d/20210216174902_g_analytics_merge_request.yml
index af9338f028a..0e726cfceae 100644
--- a/config/metrics/counts_all/20210216174902_g_analytics_merge_request.yml
+++ b/config/metrics/counts_7d/20210216174902_g_analytics_merge_request.yml
@@ -8,8 +8,12 @@ product_group: group::optimize
product_category:
value_type: number
status: removed
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - g_analytics_merge_request
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216174906_i_analytics_instance_statistics.yml b/config/metrics/counts_7d/20210216174906_i_analytics_instance_statistics.yml
index 5ee75f27529..06123553499 100644
--- a/config/metrics/counts_all/20210216174906_i_analytics_instance_statistics.yml
+++ b/config/metrics/counts_7d/20210216174906_i_analytics_instance_statistics.yml
@@ -8,8 +8,12 @@ product_group: group::optimize
product_category:
value_type: number
status: active
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - i_analytics_instance_statistics
distribution:
- ce
- ee
diff --git a/config/metrics/counts_7d/20210216174908_analytics_unique_visits_for_any_target.yml b/config/metrics/counts_7d/20210216174908_analytics_unique_visits_for_any_target.yml
new file mode 100644
index 00000000000..cdc7ee79752
--- /dev/null
+++ b/config/metrics/counts_7d/20210216174908_analytics_unique_visits_for_any_target.yml
@@ -0,0 +1,44 @@
+---
+data_category: optional
+key_path: analytics_unique_visits.analytics_unique_visits_for_any_target
+description: Unique visitors to any analytics feature by week
+product_section: dev
+product_stage: manage
+product_group: group::optimize
+product_category:
+value_type: number
+status: active
+time_frame: 7d
+data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - users_viewing_analytics_group_devops_adoption
+ - i_analytics_dev_ops_adoption
+ - i_analytics_dev_ops_score
+ - p_analytics_merge_request
+ - i_analytics_instance_statistics
+ - g_analytics_contribution
+ - g_analytics_insights
+ - g_analytics_issues
+ - g_analytics_productivity
+ - g_analytics_valuestream
+ - p_analytics_pipelines
+ - p_analytics_code_reviews
+ - p_analytics_valuestream
+ - p_analytics_insights
+ - p_analytics_issues
+ - p_analytics_repo
+ - i_analytics_cohorts
+ - p_analytics_ci_cd_pipelines
+ - p_analytics_ci_cd_deployment_frequency
+ - p_analytics_ci_cd_lead_time
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+performance_indicator_type: []
+milestone: "<13.9"
diff --git a/config/metrics/counts_all/20210423005644_i_analytics_dev_ops_adoption.yml b/config/metrics/counts_7d/20210423005644_i_analytics_dev_ops_adoption.yml
index 7a524dee720..b79b021901c 100644
--- a/config/metrics/counts_all/20210423005644_i_analytics_dev_ops_adoption.yml
+++ b/config/metrics/counts_7d/20210423005644_i_analytics_dev_ops_adoption.yml
@@ -9,8 +9,12 @@ product_category:
value_type: number
status: active
milestone: "13.11"
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - i_analytics_dev_ops_adoption
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210428142406_users_viewing_analytics_group_devops_adoption.yml b/config/metrics/counts_7d/20210428142406_users_viewing_analytics_group_devops_adoption.yml
index b8ae877b0be..2f5415c7802 100644
--- a/config/metrics/counts_all/20210428142406_users_viewing_analytics_group_devops_adoption.yml
+++ b/config/metrics/counts_7d/20210428142406_users_viewing_analytics_group_devops_adoption.yml
@@ -10,8 +10,12 @@ product_category:
value_type: number
status: active
milestone: "13.12"
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - users_viewing_analytics_group_devops_adoption
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20211126090001_p_analytics_ci_cd_pipelines.yml b/config/metrics/counts_7d/20211126090001_p_analytics_ci_cd_pipelines.yml
index 7e6820b0d24..ec686425d7c 100644
--- a/config/metrics/counts_all/20211126090001_p_analytics_ci_cd_pipelines.yml
+++ b/config/metrics/counts_7d/20211126090001_p_analytics_ci_cd_pipelines.yml
@@ -10,8 +10,12 @@ value_type: number
status: active
milestone: '14.6'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - p_analytics_ci_cd_pipelines
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20211126090002_p_analytics_ci_cd_deployment_frequency.yml b/config/metrics/counts_7d/20211126090002_p_analytics_ci_cd_deployment_frequency.yml
index 134f43b1b27..bee4b88889e 100644
--- a/config/metrics/counts_all/20211126090002_p_analytics_ci_cd_deployment_frequency.yml
+++ b/config/metrics/counts_7d/20211126090002_p_analytics_ci_cd_deployment_frequency.yml
@@ -10,8 +10,12 @@ value_type: number
status: active
milestone: '14.6'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - p_analytics_ci_cd_deployment_frequency
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20211126090003_p_analytics_ci_cd_lead_time.yml b/config/metrics/counts_7d/20211126090003_p_analytics_ci_cd_lead_time.yml
index a167a380432..3c1f6082049 100644
--- a/config/metrics/counts_all/20211126090003_p_analytics_ci_cd_lead_time.yml
+++ b/config/metrics/counts_7d/20211126090003_p_analytics_ci_cd_lead_time.yml
@@ -10,8 +10,12 @@ value_type: number
status: active
milestone: '14.6'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
-time_frame: all
+time_frame: 7d
data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - p_analytics_ci_cd_lead_time
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216174908_analytics_unique_visits_for_any_target.yml b/config/metrics/counts_all/20210216174908_analytics_unique_visits_for_any_target.yml
deleted file mode 100644
index 488bb48be0e..00000000000
--- a/config/metrics/counts_all/20210216174908_analytics_unique_visits_for_any_target.yml
+++ /dev/null
@@ -1,21 +0,0 @@
----
-data_category: optional
-key_path: analytics_unique_visits.analytics_unique_visits_for_any_target
-description: Unique visitors to any analytics feature by week
-product_section: dev
-product_stage: manage
-product_group: group::optimize
-product_category:
-value_type: number
-status: active
-time_frame: all
-data_source: redis_hll
-distribution:
-- ce
-- ee
-tier:
-- free
-- premium
-- ultimate
-performance_indicator_type: []
-milestone: "<13.9"
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 15cf2f4d6c0..62369551055 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -11739,6 +11739,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| <a id="groupcontactsids"></a>`ids` | [`[CustomerRelationsContactID!]`](#customerrelationscontactid) | Filter contacts by IDs. |
| <a id="groupcontactssearch"></a>`search` | [`String`](#string) | Search term to find contacts with. |
| <a id="groupcontactsstate"></a>`state` | [`CustomerRelationsContactState`](#customerrelationscontactstate) | State of the contacts to search for. |
@@ -12095,6 +12096,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| <a id="grouporganizationsids"></a>`ids` | [`[CustomerRelationsOrganizationID!]`](#customerrelationsorganizationid) | Filter organizations by IDs. |
| <a id="grouporganizationssearch"></a>`search` | [`String`](#string) | Search term used to find organizations with. |
| <a id="grouporganizationsstate"></a>`state` | [`CustomerRelationsOrganizationState`](#customerrelationsorganizationstate) | State of the organization to search for. |
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index c50c8aef793..8231cf4316b 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -60,6 +60,7 @@ are very appreciative of the work done by translators and proofreaders!
- German
- Michael Hahnle - [GitLab](https://gitlab.com/mhah), [Crowdin](https://crowdin.com/profile/mhah)
- Katrin Leinweber - [GitLab](https://gitlab.com/katrinleinweber), [Crowdin](https://crowdin.com/profile/katrinleinweber)
+ - Justman10000 - [GitLab](https://gitlab.com/Justman10000), [Crowdin](https://crowdin.com/profile/Justman10000)
- Greek
- Proofreaders needed.
- Hebrew
diff --git a/lib/gitlab/analytics/unique_visits.rb b/lib/gitlab/analytics/unique_visits.rb
index 3546a7e3ddb..7fcfe60a3e7 100644
--- a/lib/gitlab/analytics/unique_visits.rb
+++ b/lib/gitlab/analytics/unique_visits.rb
@@ -10,9 +10,7 @@ module Gitlab
# @param [ActiveSupport::TimeWithZone] end_date end of time frame
# @return [Integer] number of unique visitors
def unique_visits_for(targets:, start_date: 7.days.ago, end_date: start_date + 1.week)
- events = if targets == :analytics
- self.class.analytics_events
- elsif targets == :compliance
+ events = if targets == :compliance
self.class.compliance_events
else
Array(targets)
@@ -22,10 +20,6 @@ module Gitlab
end
class << self
- def analytics_events
- Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category('analytics')
- end
-
def compliance_events
Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category('compliance')
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index f86394971e7..f623e207fb9 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -643,16 +643,6 @@ module Gitlab
}
end
- def analytics_unique_visits_data
- results = ::Gitlab::Analytics::UniqueVisits.analytics_events.each_with_object({}) do |target, hash|
- hash[target] = redis_usage_data { unique_visit_service.unique_visits_for(targets: target) }
- end
- results['analytics_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) }
- results['analytics_unique_visits_for_any_target_monthly'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics, **monthly_time_range) }
-
- { analytics_unique_visits: results }
- end
-
def compliance_unique_visits_data
results = ::Gitlab::Analytics::UniqueVisits.compliance_events.each_with_object({}) do |target, hash|
hash[target] = redis_usage_data { unique_visit_service.unique_visits_for(targets: target) }
@@ -710,7 +700,6 @@ module Gitlab
.merge(topology_usage_data)
.merge(usage_activity_by_stage)
.merge(usage_activity_by_stage(:usage_activity_by_stage_monthly, monthly_time_range_db_params))
- .merge(analytics_unique_visits_data)
.merge(compliance_unique_visits_data)
.merge(redis_hll_counters)
.deep_merge(aggregated_metrics_data)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c15522c123f..d82e155eeaa 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2103,9 +2103,6 @@ msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
-msgid "Add a horizontal rule"
-msgstr ""
-
msgid "Add a new issue"
msgstr ""
@@ -2358,9 +2355,6 @@ msgstr ""
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
-msgid "Additional Metadata"
-msgstr ""
-
msgid "Additional minutes"
msgstr ""
@@ -4527,13 +4521,25 @@ msgstr ""
msgid "ApplicationSettings|Require admin approval for new sign-ups"
msgstr ""
+msgid "ApplicationSettings|Require lowercase letters"
+msgstr ""
+
+msgid "ApplicationSettings|Require numbers"
+msgstr ""
+
+msgid "ApplicationSettings|Require symbols"
+msgstr ""
+
+msgid "ApplicationSettings|Require uppercase letters"
+msgstr ""
+
msgid "ApplicationSettings|Restricts sign-ups for email addresses that match the given regex. %{linkStart}What is the supported syntax?%{linkEnd}"
msgstr ""
msgid "ApplicationSettings|Save changes"
msgstr ""
-msgid "ApplicationSettings|See GitLab's %{linkStart}Password Policy Guidelines%{linkEnd}."
+msgid "ApplicationSettings|See %{linkStart}password policy guidelines%{linkEnd}."
msgstr ""
msgid "ApplicationSettings|Send confirmation email on sign-up"
@@ -4560,6 +4566,18 @@ msgstr ""
msgid "ApplicationSettings|Users with e-mail addresses that match these domain(s) cannot sign up. Wildcards allowed. Use separate lines or commas for multiple entries."
msgstr ""
+msgid "ApplicationSettings|When enabled, new passwords must contain at least one lowercase letter (a-z)."
+msgstr ""
+
+msgid "ApplicationSettings|When enabled, new passwords must contain at least one number (0-9)."
+msgstr ""
+
+msgid "ApplicationSettings|When enabled, new passwords must contain at least one symbol (!@#$%%^&())."
+msgstr ""
+
+msgid "ApplicationSettings|When enabled, new passwords must contain at least one uppercase letter (A-Z)."
+msgstr ""
+
msgid "ApplicationSettings|domain.com"
msgstr ""
@@ -13173,6 +13191,9 @@ msgstr ""
msgid "DevopsReport|Your usage"
msgstr ""
+msgid "Diagram (%{language})"
+msgstr ""
+
msgid "Did not delete the source branch."
msgstr ""
@@ -18992,6 +19013,9 @@ msgstr ""
msgid "Hook was successfully updated."
msgstr ""
+msgid "Horizontal rule"
+msgstr ""
+
msgid "Hostname"
msgstr ""
@@ -24301,6 +24325,9 @@ msgstr ""
msgid "Merging immediately isn't recommended as it may negatively impact the existing merge train. Read the %{docsLinkStart}documentation%{docsLinkEnd} for more information."
msgstr ""
+msgid "Mermaid diagram"
+msgstr ""
+
msgid "Message"
msgstr ""
@@ -27039,6 +27066,9 @@ msgstr ""
msgid "PackageRegistry|Add composer registry"
msgstr ""
+msgid "PackageRegistry|Additional metadata"
+msgstr ""
+
msgid "PackageRegistry|Allow duplicates"
msgstr ""
@@ -27305,6 +27335,9 @@ msgstr ""
msgid "PackageRegistry|Something went wrong while fetching the package history."
msgstr ""
+msgid "PackageRegistry|Something went wrong while fetching the package metadata."
+msgstr ""
+
msgid "PackageRegistry|Sorry, your filter produced no results"
msgstr ""
@@ -28520,6 +28553,9 @@ msgstr ""
msgid "PlantUML URL"
msgstr ""
+msgid "PlantUML diagram"
+msgstr ""
+
msgid "Play"
msgstr ""
@@ -28943,6 +28979,9 @@ msgstr ""
msgid "Preview changes"
msgstr ""
+msgid "Preview diagram"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -43265,9 +43304,6 @@ msgstr ""
msgid "WorkItem|Expand child items"
msgstr ""
-msgid "WorkItem|New Task"
-msgstr ""
-
msgid "WorkItem|No child items are currently assigned. Use child items to prioritize tasks that your team should complete in order to accomplish your goals!"
msgstr ""
@@ -43672,6 +43708,9 @@ msgstr ""
msgid "You cannot combine replace_ids with add_ids or remove_ids"
msgstr ""
+msgid "You cannot edit this timeline event."
+msgstr ""
+
msgid "You cannot impersonate a blocked user"
msgstr ""
diff --git a/spec/factories/incident_management/timeline_events.rb b/spec/factories/incident_management/timeline_events.rb
index e2e216d24b8..831f78369b7 100644
--- a/spec/factories/incident_management/timeline_events.rb
+++ b/spec/factories/incident_management/timeline_events.rb
@@ -10,5 +10,14 @@ FactoryBot.define do
note { 'timeline created' }
note_html { '<strong>timeline created</strong>' }
action { 'comment' }
+ editable
+ end
+
+ trait :editable do
+ editable { true }
+ end
+
+ trait :non_editable do
+ editable { false }
end
end
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index 52a9341b955..a07d4ef6c2e 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -14,7 +14,11 @@ FactoryBot.define do
trait :legacy do
sha { nil }
- author { nil }
+
+ # Legacy releases which are created during tags creation have empty users.
+ after(:create) do |release, _|
+ release.update_column(:author_id, nil)
+ end
end
trait :with_evidence do
diff --git a/spec/finders/crm/contacts_finder_spec.rb b/spec/finders/crm/contacts_finder_spec.rb
index 14f838812a6..fee4f049123 100644
--- a/spec/finders/crm/contacts_finder_spec.rb
+++ b/spec/finders/crm/contacts_finder_spec.rb
@@ -143,6 +143,14 @@ RSpec.describe Crm::ContactsFinder do
expect(finder.execute).to match_array([search_test_b])
end
end
+
+ context 'when searching for contacts ids' do
+ it 'returns the expected contacts' do
+ finder = described_class.new(user, group: search_test_group, ids: [search_test_b.id])
+
+ expect(finder.execute).to match_array([search_test_b])
+ end
+ end
end
end
end
diff --git a/spec/finders/crm/organizations_finder_spec.rb b/spec/finders/crm/organizations_finder_spec.rb
index 94b5d9e5874..807c9f36484 100644
--- a/spec/finders/crm/organizations_finder_spec.rb
+++ b/spec/finders/crm/organizations_finder_spec.rb
@@ -129,6 +129,14 @@ RSpec.describe Crm::OrganizationsFinder do
expect(finder.execute).to match_array([search_test_b])
end
end
+
+ context 'when searching for organizations ids' do
+ it 'returns the expected organizations' do
+ finder = described_class.new(user, group: search_test_group, ids: [search_test_a.id])
+
+ expect(finder.execute).to match_array([search_test_a])
+ end
+ end
end
end
end
diff --git a/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js
index 5b4f954b672..6a859873a9d 100644
--- a/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js
+++ b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js
@@ -16,6 +16,9 @@ describe('Signup Form', () => {
wrapper = extendedWrapper(
mountFn(SignupForm, {
provide: {
+ glFeatures: {
+ passwordComplexity: true,
+ },
...mockData,
...injectedProps,
},
@@ -58,6 +61,10 @@ describe('Signup Form', () => {
${'minimumPasswordLength'} | ${mockData.minimumPasswordLength} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'value'} | ${mockData.minimumPasswordLength}
${'minimumPasswordLengthMin'} | ${mockData.minimumPasswordLengthMin} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'min'} | ${mockData.minimumPasswordLengthMin}
${'minimumPasswordLengthMax'} | ${mockData.minimumPasswordLengthMax} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'max'} | ${mockData.minimumPasswordLengthMax}
+ ${'passwordNumberRequired'} | ${mockData.passwordNumberRequired} | ${'[name="application_setting[password_number_required]"]'} | ${'prop'} | ${'value'} | ${mockData.passwordNumberRequired}
+ ${'passwordLowercaseRequired'} | ${mockData.passwordLowercaseRequired} | ${'[name="application_setting[password_lowercase_required]"]'} | ${'prop'} | ${'value'} | ${mockData.passwordLowercaseRequired}
+ ${'passwordUppercaseRequired'} | ${mockData.passwordUppercaseRequired} | ${'[name="application_setting[password_uppercase_required]"]'} | ${'prop'} | ${'value'} | ${mockData.passwordUppercaseRequired}
+ ${'passwordSymbolRequired'} | ${mockData.passwordSymbolRequired} | ${'[name="application_setting[password_symbol_required]"]'} | ${'prop'} | ${'value'} | ${mockData.passwordSymbolRequired}
${'domainAllowlistRaw'} | ${mockData.domainAllowlistRaw} | ${'[name="application_setting[domain_allowlist_raw]"]'} | ${'value'} | ${'value'} | ${mockData.domainAllowlistRaw}
${'domainDenylistEnabled'} | ${mockData.domainDenylistEnabled} | ${'[name="application_setting[domain_denylist_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.domainDenylistEnabled}
${'denylistTypeRawSelected'} | ${mockData.denylistTypeRawSelected} | ${'[name="denylist_type"]'} | ${'attribute'} | ${'checked'} | ${'raw'}
diff --git a/spec/frontend/admin/signup_restrictions/mock_data.js b/spec/frontend/admin/signup_restrictions/mock_data.js
index 135fc8caae0..9e001e122a4 100644
--- a/spec/frontend/admin/signup_restrictions/mock_data.js
+++ b/spec/frontend/admin/signup_restrictions/mock_data.js
@@ -18,6 +18,10 @@ export const rawMockData = {
emailRestrictions: 'user1@domain.com, user2@domain.com',
afterSignUpText: 'Congratulations on your successful sign-up!',
pendingUserCount: '0',
+ passwordNumberRequired: 'true',
+ passwordLowercaseRequired: 'true',
+ passwordUppercaseRequired: 'true',
+ passwordSymbolRequired: 'true',
};
export const mockData = {
@@ -40,4 +44,8 @@ export const mockData = {
emailRestrictions: 'user1@domain.com, user2@domain.com',
afterSignUpText: 'Congratulations on your successful sign-up!',
pendingUserCount: '0',
+ passwordNumberRequired: true,
+ passwordLowercaseRequired: true,
+ passwordUppercaseRequired: true,
+ passwordSymbolRequired: true,
};
diff --git a/spec/frontend/admin/signup_restrictions/utils_spec.js b/spec/frontend/admin/signup_restrictions/utils_spec.js
index fd5c4c3317b..f07e14430f9 100644
--- a/spec/frontend/admin/signup_restrictions/utils_spec.js
+++ b/spec/frontend/admin/signup_restrictions/utils_spec.js
@@ -14,6 +14,10 @@ describe('utils', () => {
'domainDenylistEnabled',
'denylistTypeRawSelected',
'emailRestrictionsEnabled',
+ 'passwordNumberRequired',
+ 'passwordLowercaseRequired',
+ 'passwordUppercaseRequired',
+ 'passwordSymbolRequired',
],
}),
).toEqual(mockData);
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 5f162f498c4..9526277f06b 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
import Api, { DEFAULT_PER_PAGE } from '~/api';
import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
jest.mock('~/flash');
@@ -622,8 +622,8 @@ describe('Api', () => {
const query = 'dummy query';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`;
const flashCallback = (callCount) => {
- expect(createFlash).toHaveBeenCalledTimes(callCount);
- createFlash.mockClear();
+ expect(createAlert).toHaveBeenCalledTimes(callCount);
+ createAlert.mockClear();
};
mock.onGet(expectedUrl).reply(500, null);
diff --git a/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js b/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js
index f19bd02443f..646d068e795 100644
--- a/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js
@@ -12,6 +12,7 @@ import { stubComponent } from 'helpers/stub_component';
import CodeBlockBubbleMenu from '~/content_editor/components/bubble_menus/code_block.vue';
import eventHubFactory from '~/helpers/event_hub_factory';
import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
+import Diagram from '~/content_editor/extensions/diagram';
import codeBlockLanguageLoader from '~/content_editor/services/code_block_language_loader';
import { createTestEditor, emitEditorEvent } from '../../test_utils';
@@ -20,11 +21,13 @@ const createFakeEvent = () => ({ preventDefault: jest.fn(), stopPropagation: jes
describe('content_editor/components/bubble_menus/code_block', () => {
let wrapper;
let tiptapEditor;
+ let contentEditor;
let bubbleMenu;
let eventHub;
const buildEditor = () => {
- tiptapEditor = createTestEditor({ extensions: [CodeBlockHighlight] });
+ tiptapEditor = createTestEditor({ extensions: [CodeBlockHighlight, Diagram] });
+ contentEditor = { renderDiagram: jest.fn() };
eventHub = eventHubFactory();
};
@@ -32,6 +35,7 @@ describe('content_editor/components/bubble_menus/code_block', () => {
wrapper = mountExtended(CodeBlockBubbleMenu, {
provide: {
tiptapEditor,
+ contentEditor,
eventHub,
},
stubs: {
@@ -85,6 +89,15 @@ describe('content_editor/components/bubble_menus/code_block', () => {
expect(wrapper.findComponent(GlDropdown).props('text')).toBe('Javascript');
});
+ it('selects diagram sytnax for mermaid', async () => {
+ tiptapEditor.commands.insertContent('<pre lang="mermaid">test</pre>');
+ bubbleMenu = wrapper.findComponent(BubbleMenu);
+
+ await emitEditorEvent({ event: 'transaction', tiptapEditor });
+
+ expect(wrapper.findComponent(GlDropdown).props('text')).toBe('Diagram (mermaid)');
+ });
+
it("selects Custom (syntax) if the language doesn't exist in the list", async () => {
tiptapEditor.commands.insertContent('<pre lang="nomnoml">test</pre>');
bubbleMenu = wrapper.findComponent(BubbleMenu);
@@ -116,6 +129,39 @@ describe('content_editor/components/bubble_menus/code_block', () => {
});
});
+ describe('preview button', () => {
+ it('does not appear for a regular code block', async () => {
+ tiptapEditor.commands.insertContent('<pre lang="javascript">var a = 2;</pre>');
+
+ expect(wrapper.findByTestId('preview-diagram').exists()).toBe(false);
+ });
+
+ it.each`
+ diagramType | diagramCode
+ ${'mermaid'} | ${'<pre lang="mermaid">graph TD;\n A-->B;</pre>'}
+ ${'nomnoml'} | ${'<img data-diagram="nomnoml" data-diagram-src="data:text/plain;base64,WzxmcmFtZT5EZWNvcmF0b3IgcGF0dGVybl0=">'}
+ `('toggles preview for a $diagramType diagram', async ({ diagramType, diagramCode }) => {
+ tiptapEditor.commands.insertContent(diagramCode);
+
+ await nextTick();
+ await wrapper.findByTestId('preview-diagram').vm.$emit('click');
+
+ expect(tiptapEditor.getAttributes(Diagram.name)).toEqual({
+ isDiagram: true,
+ language: diagramType,
+ showPreview: false,
+ });
+
+ await wrapper.findByTestId('preview-diagram').vm.$emit('click');
+
+ expect(tiptapEditor.getAttributes(Diagram.name)).toEqual({
+ isDiagram: true,
+ language: diagramType,
+ showPreview: true,
+ });
+ });
+ });
+
describe('when opened and search is changed', () => {
beforeEach(async () => {
tiptapEditor.commands.insertContent('<pre lang="javascript">var a = 2;</pre>');
diff --git a/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js b/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js
new file mode 100644
index 00000000000..0334a18c9a1
--- /dev/null
+++ b/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js
@@ -0,0 +1,54 @@
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import ToolbarMoreDropdown from '~/content_editor/components/toolbar_more_dropdown.vue';
+import Diagram from '~/content_editor/extensions/diagram';
+import HorizontalRule from '~/content_editor/extensions/horizontal_rule';
+import { createTestEditor, mockChainedCommands } from '../test_utils';
+
+describe('content_editor/components/toolbar_more_dropdown', () => {
+ let wrapper;
+ let tiptapEditor;
+
+ const buildEditor = () => {
+ tiptapEditor = createTestEditor({
+ extensions: [Diagram, HorizontalRule],
+ });
+ };
+
+ const buildWrapper = (propsData = {}) => {
+ wrapper = mountExtended(ToolbarMoreDropdown, {
+ provide: {
+ tiptapEditor,
+ },
+ propsData,
+ });
+ };
+
+ beforeEach(() => {
+ buildEditor();
+ buildWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe.each`
+ label | contentType | data
+ ${'Mermaid diagram'} | ${'diagram'} | ${{ language: 'mermaid' }}
+ ${'PlantUML diagram'} | ${'diagram'} | ${{ language: 'plantuml' }}
+ ${'Horizontal rule'} | ${'horizontalRule'} | ${undefined}
+ `('when option $label is clicked', ({ label, contentType, data }) => {
+ it(`inserts a ${contentType}`, async () => {
+ const commands = mockChainedCommands(tiptapEditor, ['setNode', 'focus', 'run']);
+
+ const btn = wrapper.findByRole('menuitem', { name: label });
+ await btn.trigger('click');
+
+ expect(commands.focus).toHaveBeenCalled();
+ expect(commands.setNode).toHaveBeenCalledWith(contentType, data);
+ expect(commands.run).toHaveBeenCalled();
+
+ expect(wrapper.emitted('execute')).toEqual([[{ contentType }]]);
+ });
+ });
+});
diff --git a/spec/frontend/content_editor/components/top_toolbar_spec.js b/spec/frontend/content_editor/components/top_toolbar_spec.js
index ec58877470c..d98a9a52aff 100644
--- a/spec/frontend/content_editor/components/top_toolbar_spec.js
+++ b/spec/frontend/content_editor/components/top_toolbar_spec.js
@@ -23,20 +23,21 @@ describe('content_editor/components/top_toolbar', () => {
});
describe.each`
- testId | controlProps
- ${'bold'} | ${{ contentType: 'bold', iconName: 'bold', label: 'Bold text', editorCommand: 'toggleBold' }}
- ${'italic'} | ${{ contentType: 'italic', iconName: 'italic', label: 'Italic text', editorCommand: 'toggleItalic' }}
- ${'strike'} | ${{ contentType: 'strike', iconName: 'strikethrough', label: 'Strikethrough', editorCommand: 'toggleStrike' }}
- ${'code'} | ${{ contentType: 'code', iconName: 'code', label: 'Code', editorCommand: 'toggleCode' }}
- ${'blockquote'} | ${{ contentType: 'blockquote', iconName: 'quote', label: 'Insert a quote', editorCommand: 'toggleBlockquote' }}
- ${'bullet-list'} | ${{ contentType: 'bulletList', iconName: 'list-bulleted', label: 'Add a bullet list', editorCommand: 'toggleBulletList' }}
- ${'ordered-list'} | ${{ contentType: 'orderedList', iconName: 'list-numbered', label: 'Add a numbered list', editorCommand: 'toggleOrderedList' }}
- ${'details'} | ${{ contentType: 'details', iconName: 'details-block', label: 'Add a collapsible section', editorCommand: 'toggleDetails' }}
- ${'horizontal-rule'} | ${{ contentType: 'horizontalRule', iconName: 'dash', label: 'Add a horizontal rule', editorCommand: 'setHorizontalRule' }}
- ${'code-block'} | ${{ contentType: 'codeBlock', iconName: 'doc-code', label: 'Insert a code block', editorCommand: 'toggleCodeBlock' }}
- ${'text-styles'} | ${{}}
- ${'link'} | ${{}}
- ${'image'} | ${{}}
+ testId | controlProps
+ ${'bold'} | ${{ contentType: 'bold', iconName: 'bold', label: 'Bold text', editorCommand: 'toggleBold' }}
+ ${'italic'} | ${{ contentType: 'italic', iconName: 'italic', label: 'Italic text', editorCommand: 'toggleItalic' }}
+ ${'strike'} | ${{ contentType: 'strike', iconName: 'strikethrough', label: 'Strikethrough', editorCommand: 'toggleStrike' }}
+ ${'code'} | ${{ contentType: 'code', iconName: 'code', label: 'Code', editorCommand: 'toggleCode' }}
+ ${'blockquote'} | ${{ contentType: 'blockquote', iconName: 'quote', label: 'Insert a quote', editorCommand: 'toggleBlockquote' }}
+ ${'bullet-list'} | ${{ contentType: 'bulletList', iconName: 'list-bulleted', label: 'Add a bullet list', editorCommand: 'toggleBulletList' }}
+ ${'ordered-list'} | ${{ contentType: 'orderedList', iconName: 'list-numbered', label: 'Add a numbered list', editorCommand: 'toggleOrderedList' }}
+ ${'details'} | ${{ contentType: 'details', iconName: 'details-block', label: 'Add a collapsible section', editorCommand: 'toggleDetails' }}
+ ${'code-block'} | ${{ contentType: 'codeBlock', iconName: 'doc-code', label: 'Insert a code block', editorCommand: 'toggleCodeBlock' }}
+ ${'text-styles'} | ${{}}
+ ${'link'} | ${{}}
+ ${'image'} | ${{}}
+ ${'table'} | ${{}}
+ ${'more'} | ${{}}
`('given a $testId toolbar control', ({ testId, controlProps }) => {
beforeEach(() => {
buildWrapper();
diff --git a/spec/frontend/content_editor/components/wrappers/code_block_spec.js b/spec/frontend/content_editor/components/wrappers/code_block_spec.js
index 2e59cd9714a..17a365e12bb 100644
--- a/spec/frontend/content_editor/components/wrappers/code_block_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/code_block_spec.js
@@ -1,8 +1,14 @@
import { nextTick } from 'vue';
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2';
-import { shallowMount } from '@vue/test-utils';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { stubComponent } from 'helpers/stub_component';
+import eventHubFactory from '~/helpers/event_hub_factory';
+import SandboxedMermaid from '~/behaviors/components/sandboxed_mermaid.vue';
+import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
+import Diagram from '~/content_editor/extensions/diagram';
import CodeBlockWrapper from '~/content_editor/components/wrappers/code_block.vue';
import codeBlockLanguageLoader from '~/content_editor/services/code_block_language_loader';
+import { emitEditorEvent, createTestEditor } from '../../test_utils';
jest.mock('~/content_editor/services/code_block_language_loader');
@@ -10,21 +16,42 @@ describe('content/components/wrappers/code_block', () => {
const language = 'yaml';
let wrapper;
let updateAttributesFn;
+ let tiptapEditor;
+ let contentEditor;
+ let eventHub;
+
+ const buildEditor = () => {
+ tiptapEditor = createTestEditor({ extensions: [CodeBlockHighlight, Diagram] });
+ contentEditor = { renderDiagram: jest.fn().mockResolvedValue('url/to/some/diagram') };
+ eventHub = eventHubFactory();
+ };
const createWrapper = async (nodeAttrs = { language }) => {
updateAttributesFn = jest.fn();
- wrapper = shallowMount(CodeBlockWrapper, {
+ wrapper = mountExtended(CodeBlockWrapper, {
propsData: {
+ editor: tiptapEditor,
node: {
attrs: nodeAttrs,
},
updateAttributes: updateAttributesFn,
},
+ stubs: {
+ NodeViewContent: stubComponent(NodeViewContent),
+ NodeViewWrapper: stubComponent(NodeViewWrapper),
+ },
+ provide: {
+ contentEditor,
+ tiptapEditor,
+ eventHub,
+ },
});
};
beforeEach(() => {
+ buildEditor();
+
codeBlockLanguageLoader.findOrCreateLanguageBySyntax.mockReturnValue({ syntax: language });
});
@@ -68,4 +95,56 @@ describe('content/components/wrappers/code_block', () => {
expect(updateAttributesFn).toHaveBeenCalledWith({ language });
});
+
+ describe('diagrams', () => {
+ beforeEach(() => {
+ jest.spyOn(tiptapEditor, 'isActive').mockReturnValue(true);
+ });
+
+ it('does not render a preview if showPreview: false', async () => {
+ createWrapper({ language: 'plantuml', isDiagram: true, showPreview: false });
+
+ expect(wrapper.find({ ref: 'diagramContainer' }).exists()).toBe(false);
+ });
+
+ it('does not update preview when diagram is not active', async () => {
+ createWrapper({ language: 'plantuml', isDiagram: true, showPreview: true });
+
+ await emitEditorEvent({ event: 'transaction', tiptapEditor });
+ await nextTick();
+
+ expect(wrapper.find('img').attributes('src')).toBe('url/to/some/diagram');
+
+ jest.spyOn(tiptapEditor, 'isActive').mockReturnValue(false);
+
+ const alternateUrl = 'url/to/another/diagram';
+
+ contentEditor.renderDiagram.mockResolvedValue(alternateUrl);
+
+ await emitEditorEvent({ event: 'transaction', tiptapEditor });
+ await nextTick();
+
+ expect(wrapper.find('img').attributes('src')).toBe('url/to/some/diagram');
+ });
+
+ it('renders an image with preview for a plantuml/kroki diagram', async () => {
+ createWrapper({ language: 'plantuml', isDiagram: true, showPreview: true });
+
+ await emitEditorEvent({ event: 'transaction', tiptapEditor });
+ await nextTick();
+
+ expect(wrapper.find('img').attributes('src')).toBe('url/to/some/diagram');
+ expect(wrapper.find(SandboxedMermaid).exists()).toBe(false);
+ });
+
+ it('renders an iframe with preview for a mermaid diagram', async () => {
+ createWrapper({ language: 'mermaid', isDiagram: true, showPreview: true });
+
+ await emitEditorEvent({ event: 'transaction', tiptapEditor });
+ await nextTick();
+
+ expect(wrapper.find(SandboxedMermaid).props('source')).toBe('');
+ expect(wrapper.find('img').exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/content_editor/services/asset_resolver_spec.js b/spec/frontend/content_editor/services/asset_resolver_spec.js
index f4e7d9bf881..0a99f823be3 100644
--- a/spec/frontend/content_editor/services/asset_resolver_spec.js
+++ b/spec/frontend/content_editor/services/asset_resolver_spec.js
@@ -20,4 +20,14 @@ describe('content_editor/services/asset_resolver', () => {
);
});
});
+
+ describe('renderDiagram', () => {
+ it('resolves a diagram code to a url containing the diagram image', async () => {
+ renderMarkdown.mockResolvedValue(
+ '<p><img data-diagram="nomnoml" src="url/to/some/diagram"></p>',
+ );
+
+ expect(await assetResolver.renderDiagram('test')).toBe('url/to/some/diagram');
+ });
+ });
});
diff --git a/spec/frontend/content_editor/services/code_block_language_loader_spec.js b/spec/frontend/content_editor/services/code_block_language_loader_spec.js
index 9b2600f85d9..795f5219a3f 100644
--- a/spec/frontend/content_editor/services/code_block_language_loader_spec.js
+++ b/spec/frontend/content_editor/services/code_block_language_loader_spec.js
@@ -35,6 +35,13 @@ describe('content_editor/services/code_block_language_loader', () => {
});
});
+ it('returns Diagram (syntax) if the language does not exist, and isDiagram = true', () => {
+ expect(languageLoader.findOrCreateLanguageBySyntax('foobar', true)).toMatchObject({
+ syntax: 'foobar',
+ label: 'Diagram (foobar)',
+ });
+ });
+
it('returns plaintext if no syntax is passed', () => {
expect(languageLoader.findOrCreateLanguageBySyntax('')).toMatchObject({
syntax: 'plaintext',
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js
index 7a71a1cea0f..4f3d780b149 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js
@@ -1,4 +1,9 @@
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlAlert } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
import {
conanMetadata,
mavenMetadata,
@@ -6,9 +11,11 @@ import {
packageData,
composerMetadata,
pypiMetadata,
+ packageMetadataQuery,
} from 'jest/packages_and_registries/package_registry/mock_data';
import component from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue';
import {
+ FETCH_PACKAGE_METADATA_ERROR_MESSAGE,
PACKAGE_TYPE_NUGET,
PACKAGE_TYPE_CONAN,
PACKAGE_TYPE_MAVEN,
@@ -16,6 +23,9 @@ import {
PACKAGE_TYPE_COMPOSER,
PACKAGE_TYPE_PYPI,
} from '~/packages_and_registries/package_registry/constants';
+import AdditionalMetadataLoader from '~/packages_and_registries/package_registry/components/details/additional_metadata_loader.vue';
+import waitForPromises from 'helpers/wait_for_promises';
+import getPackageMetadata from '~/packages_and_registries/package_registry/graphql/queries/get_package_metadata.query.graphql';
const mavenPackage = { packageType: PACKAGE_TYPE_MAVEN, metadata: mavenMetadata() };
const conanPackage = { packageType: PACKAGE_TYPE_CONAN, metadata: conanMetadata() };
@@ -24,16 +34,26 @@ const composerPackage = { packageType: PACKAGE_TYPE_COMPOSER, metadata: composer
const pypiPackage = { packageType: PACKAGE_TYPE_PYPI, metadata: pypiMetadata() };
const npmPackage = { packageType: PACKAGE_TYPE_NPM, metadata: {} };
-describe('Package Additional Metadata', () => {
+Vue.use(VueApollo);
+
+describe('Package Additional metadata', () => {
let wrapper;
+ let apolloProvider;
+
const defaultProps = {
- packageEntity: {
- ...packageData(mavenPackage),
- },
+ packageId: packageData().id,
+ packageType: PACKAGE_TYPE_MAVEN,
};
- const mountComponent = (props) => {
+ const mountComponent = ({
+ props = {},
+ resolver = jest.fn().mockResolvedValue(packageMetadataQuery(mavenPackage)),
+ } = {}) => {
+ const requestHandlers = [[getPackageMetadata, resolver]];
+ apolloProvider = createMockApollo(requestHandlers);
+
wrapper = shallowMountExtended(component, {
+ apolloProvider,
propsData: { ...defaultProps, ...props },
stubs: {
component: { template: '<div data-testid="component-is"></div>' },
@@ -41,6 +61,10 @@ describe('Package Additional Metadata', () => {
});
};
+ beforeEach(() => {
+ jest.spyOn(Sentry, 'captureException').mockImplementation();
+ });
+
afterEach(() => {
wrapper.destroy();
wrapper = null;
@@ -49,6 +73,22 @@ describe('Package Additional Metadata', () => {
const findTitle = () => wrapper.findByTestId('title');
const findMainArea = () => wrapper.findByTestId('main');
const findComponentIs = () => wrapper.findByTestId('component-is');
+ const findAdditionalMetadataLoader = () => wrapper.findComponent(AdditionalMetadataLoader);
+ const findPackageMetadataAlert = () => wrapper.findComponent(GlAlert);
+
+ it('renders the loading container when loading', () => {
+ mountComponent();
+
+ expect(findAdditionalMetadataLoader().exists()).toBe(true);
+ });
+
+ it('does not render the loading container once resolved', async () => {
+ mountComponent();
+ await waitForPromises();
+
+ expect(findAdditionalMetadataLoader().exists()).toBe(false);
+ expect(Sentry.captureException).not.toHaveBeenCalled();
+ });
it('has the correct title', () => {
mountComponent();
@@ -56,7 +96,25 @@ describe('Package Additional Metadata', () => {
const title = findTitle();
expect(title.exists()).toBe(true);
- expect(title.text()).toBe('Additional Metadata');
+ expect(title.text()).toMatchInterpolatedText(component.i18n.componentTitle);
+ });
+
+ it('does not render gl-alert', () => {
+ mountComponent();
+
+ expect(findPackageMetadataAlert().exists()).toBe(false);
+ });
+
+ it('renders gl-alert if load fails', async () => {
+ mountComponent({ resolver: jest.fn().mockRejectedValue() });
+
+ await waitForPromises();
+
+ expect(findPackageMetadataAlert().exists()).toBe(true);
+ expect(findPackageMetadataAlert().text()).toMatchInterpolatedText(
+ FETCH_PACKAGE_METADATA_ERROR_MESSAGE,
+ );
+ expect(Sentry.captureException).toHaveBeenCalled();
});
it.each`
@@ -68,16 +126,22 @@ describe('Package Additional Metadata', () => {
${pypiPackage} | ${true} | ${PACKAGE_TYPE_PYPI}
${npmPackage} | ${false} | ${PACKAGE_TYPE_NPM}
`(
- `It is $visible that the component is visible when the package is $packageType`,
- ({ packageEntity, visible }) => {
- mountComponent({ packageEntity });
+ `component visibility is $visible when the package is $packageType`,
+ async ({ packageEntity, visible, packageType }) => {
+ const resolved = packageMetadataQuery(packageType);
+ const resolver = jest.fn().mockResolvedValue(resolved);
+
+ mountComponent({ props: { packageType }, resolver });
+
+ await waitForPromises();
+ await nextTick();
expect(findTitle().exists()).toBe(visible);
expect(findMainArea().exists()).toBe(visible);
expect(findComponentIs().exists()).toBe(visible);
if (visible) {
- expect(findComponentIs().props('packageEntity')).toEqual(packageEntity);
+ expect(findComponentIs().props('packageMetadata')).toEqual(packageEntity.metadata);
}
},
);
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js
index e744680cb9a..bb6846d354f 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js
@@ -1,22 +1,16 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import {
- packageData,
- composerMetadata,
-} from 'jest/packages_and_registries/package_registry/mock_data';
+import { composerMetadata } from 'jest/packages_and_registries/package_registry/mock_data';
import component from '~/packages_and_registries/package_registry/components/details/metadata/composer.vue';
-import { PACKAGE_TYPE_COMPOSER } from '~/packages_and_registries/package_registry/constants';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
-const composerPackage = { packageType: PACKAGE_TYPE_COMPOSER, metadata: composerMetadata() };
-
describe('Composer Metadata', () => {
let wrapper;
const mountComponent = () => {
wrapper = shallowMountExtended(component, {
- propsData: { packageEntity: packageData(composerPackage) },
+ propsData: { packageMetadata: composerMetadata() },
stubs: {
DetailsRow,
GlSprintf,
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js
index 46593047f1f..e7e47401aa1 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js
@@ -1,22 +1,16 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import {
- conanMetadata,
- packageData,
-} from 'jest/packages_and_registries/package_registry/mock_data';
+import { conanMetadata } from 'jest/packages_and_registries/package_registry/mock_data';
import component from '~/packages_and_registries/package_registry/components/details/metadata/conan.vue';
-import { PACKAGE_TYPE_CONAN } from '~/packages_and_registries/package_registry/constants';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
-const conanPackage = { packageType: PACKAGE_TYPE_CONAN, metadata: conanMetadata() };
-
describe('Conan Metadata', () => {
let wrapper;
const mountComponent = () => {
wrapper = shallowMountExtended(component, {
propsData: {
- packageEntity: packageData(conanPackage),
+ packageMetadata: conanMetadata(),
},
stubs: {
DetailsRow,
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js
index bc54cf1cb98..8680d983042 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js
@@ -1,24 +1,16 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import {
- mavenMetadata,
- packageData,
-} from 'jest/packages_and_registries/package_registry/mock_data';
+import { mavenMetadata } from 'jest/packages_and_registries/package_registry/mock_data';
import component from '~/packages_and_registries/package_registry/components/details/metadata/maven.vue';
-import { PACKAGE_TYPE_MAVEN } from '~/packages_and_registries/package_registry/constants';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
-const mavenPackage = { packageType: PACKAGE_TYPE_MAVEN, metadata: mavenMetadata() };
-
describe('Maven Metadata', () => {
let wrapper;
const mountComponent = () => {
wrapper = shallowMountExtended(component, {
propsData: {
- packageEntity: {
- ...packageData(mavenPackage),
- },
+ packageMetadata: mavenMetadata(),
},
stubs: {
DetailsRow,
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js
index f759fe7a81c..af3692023f0 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js
@@ -1,25 +1,17 @@
import { GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import {
- nugetMetadata,
- packageData,
-} from 'jest/packages_and_registries/package_registry/mock_data';
+import { nugetMetadata } from 'jest/packages_and_registries/package_registry/mock_data';
import component from '~/packages_and_registries/package_registry/components/details/metadata/nuget.vue';
-import { PACKAGE_TYPE_NUGET } from '~/packages_and_registries/package_registry/constants';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
describe('Nuget Metadata', () => {
- let nugetPackage = { packageType: PACKAGE_TYPE_NUGET, metadata: nugetMetadata() };
+ let nugetPackageMetadata = { ...nugetMetadata() };
let wrapper;
- const mountComponent = () => {
+ const mountComponent = (props) => {
wrapper = shallowMountExtended(component, {
- propsData: {
- packageEntity: {
- ...packageData(nugetPackage),
- },
- },
+ propsData: { ...props },
stubs: {
DetailsRow,
GlSprintf,
@@ -37,7 +29,7 @@ describe('Nuget Metadata', () => {
const findElementLink = (container) => container.findComponent(GlLink);
beforeEach(() => {
- mountComponent({ packageEntity: nugetPackage });
+ mountComponent({ packageMetadata: nugetPackageMetadata });
});
it.each`
@@ -49,14 +41,14 @@ describe('Nuget Metadata', () => {
expect(element.exists()).toBe(true);
expect(element.text()).toBe(text);
expect(element.props('icon')).toBe(icon);
- expect(findElementLink(element).attributes('href')).toBe(nugetPackage.metadata[link]);
+ expect(findElementLink(element).attributes('href')).toBe(nugetPackageMetadata[link]);
});
describe('without source', () => {
beforeAll(() => {
- nugetPackage = {
- packageType: PACKAGE_TYPE_NUGET,
- metadata: { iconUrl: 'iconUrl', licenseUrl: 'licenseUrl' },
+ nugetPackageMetadata = {
+ iconUrl: 'iconUrl',
+ licenseUrl: 'licenseUrl',
};
});
@@ -67,9 +59,9 @@ describe('Nuget Metadata', () => {
describe('without license', () => {
beforeAll(() => {
- nugetPackage = {
- packageType: PACKAGE_TYPE_NUGET,
- metadata: { iconUrl: 'iconUrl', projectUrl: 'projectUrl' },
+ nugetPackageMetadata = {
+ iconUrl: 'iconUrl',
+ projectUrl: 'projectUrl',
};
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js
index c4481c3f20b..d7c6ea8379d 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js
@@ -1,22 +1,17 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { packageData, pypiMetadata } from 'jest/packages_and_registries/package_registry/mock_data';
+import { pypiMetadata } from 'jest/packages_and_registries/package_registry/mock_data';
import component from '~/packages_and_registries/package_registry/components/details/metadata/pypi.vue';
-import { PACKAGE_TYPE_PYPI } from '~/packages_and_registries/package_registry/constants';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
-const pypiPackage = { packageType: PACKAGE_TYPE_PYPI, metadata: pypiMetadata() };
-
describe('Package Additional Metadata', () => {
let wrapper;
const mountComponent = () => {
wrapper = shallowMountExtended(component, {
propsData: {
- packageEntity: {
- ...packageData(pypiPackage),
- },
+ packageMetadata: pypiMetadata(),
},
stubs: {
DetailsRow,
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
index e68916ecb39..f4e6d43812d 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { GlLink, GlSprintf } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import { stubComponent } from 'helpers/stub_component';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -11,7 +11,6 @@ import {
packagePipelinesQuery,
} from 'jest/packages_and_registries/package_registry/mock_data';
import { HISTORY_PIPELINES_LIMIT } from '~/packages_and_registries/shared/constants';
-import { FETCH_PACKAGE_PIPELINES_ERROR_MESSAGE } from '~/packages_and_registries/package_registry/constants';
import component from '~/packages_and_registries/package_registry/components/details/package_history.vue';
import PackageHistoryLoader from '~/packages_and_registries/package_registry/components/details/package_history_loader.vue';
import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
@@ -19,7 +18,8 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import waitForPromises from 'helpers/wait_for_promises';
import getPackagePipelines from '~/packages_and_registries/package_registry/graphql/queries/get_package_pipelines.query.graphql';
-jest.mock('~/flash');
+Vue.use(VueApollo);
+
describe('Package History', () => {
let wrapper;
let apolloProvider;
@@ -34,12 +34,10 @@ describe('Package History', () => {
const createPipelines = (amount) =>
[...Array(amount)].map((x, index) => packagePipelines({ id: index + 1 })[0]);
- const mountComponent = (
- props,
+ const mountComponent = ({
+ props = {},
resolver = jest.fn().mockResolvedValue(packagePipelinesQuery()),
- ) => {
- Vue.use(VueApollo);
-
+ } = {}) => {
const requestHandlers = [[getPackagePipelines, resolver]];
apolloProvider = createMockApollo(requestHandlers);
@@ -55,14 +53,20 @@ describe('Package History', () => {
});
};
+ beforeEach(() => {
+ jest.spyOn(Sentry, 'captureException').mockImplementation();
+ });
+
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
const findPackageHistoryLoader = () => wrapper.findComponent(PackageHistoryLoader);
const findHistoryElement = (testId) => wrapper.findByTestId(testId);
const findElementLink = (container) => container.findComponent(GlLink);
const findElementTimeAgo = (container) => container.findComponent(TimeAgoTooltip);
+ const findPackageHistoryAlert = () => wrapper.findComponent(GlAlert);
const findTitle = () => wrapper.findByTestId('title');
const findTimeline = () => wrapper.findByTestId('timeline');
@@ -77,6 +81,7 @@ describe('Package History', () => {
await waitForPromises();
expect(findPackageHistoryLoader().exists()).toBe(false);
+ expect(Sentry.captureException).not.toHaveBeenCalled();
});
it('has the correct title', async () => {
@@ -101,16 +106,22 @@ describe('Package History', () => {
);
});
- it('calls createFlash function if load fails', async () => {
- mountComponent({}, jest.fn().mockRejectedValue());
+ it('does not render gl-alert', () => {
+ mountComponent();
+
+ expect(findPackageHistoryAlert().exists()).toBe(false);
+ });
+
+ it('renders gl-alert if load fails', async () => {
+ mountComponent({ resolver: jest.fn().mockRejectedValue() });
await waitForPromises();
- expect(createFlash).toHaveBeenCalledWith(
- expect.objectContaining({
- message: FETCH_PACKAGE_PIPELINES_ERROR_MESSAGE,
- }),
+ expect(findPackageHistoryAlert().exists()).toBe(true);
+ expect(findPackageHistoryAlert().text()).toEqual(
+ 'Something went wrong while fetching the package history.',
);
+ expect(Sentry.captureException).toHaveBeenCalled();
});
describe.each`
@@ -132,13 +143,16 @@ describe('Package History', () => {
const pipelinesResolver = jest
.fn()
.mockResolvedValue(packagePipelinesQuery(createPipelines(amount)));
- mountComponent(
- {
+
+ mountComponent({
+ props: {
packageEntity,
},
- pipelinesResolver,
- );
+ resolver: pipelinesResolver,
+ });
+
await waitForPromises();
+
element = findHistoryElement(name);
});
diff --git a/spec/frontend/packages_and_registries/package_registry/mock_data.js b/spec/frontend/packages_and_registries/package_registry/mock_data.js
index 3dfcec37ea7..d40feee582f 100644
--- a/spec/frontend/packages_and_registries/package_registry/mock_data.js
+++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js
@@ -148,6 +148,8 @@ export const conanMetadata = () => ({
recipePath: 'package-8/1.0.0/gitlab-org+gitlab-test/stable',
});
+const conanMetadataQuery = () => ({ ...conanMetadata(), __typename: 'ConanMetadata' });
+
export const composerMetadata = () => ({
targetSha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0',
composerJson: {
@@ -156,23 +158,45 @@ export const composerMetadata = () => ({
},
});
+const composerMetadataQuery = () => ({
+ ...composerMetadata(),
+ __typename: 'ComposerMetadata',
+});
+
export const pypiMetadata = () => ({
+ id: 'pypi-1',
requiredPython: '1.0.0',
});
+const pypiMetadataQuery = () => ({ ...pypiMetadata(), __typename: 'PypiMetadata' });
+
export const mavenMetadata = () => ({
+ id: 'maven-1',
appName: 'appName',
appGroup: 'appGroup',
appVersion: 'appVersion',
path: 'path',
});
+const mavenMetadataQuery = () => ({ ...mavenMetadata(), __typename: 'MavenMetadata' });
+
export const nugetMetadata = () => ({
+ id: 'nuget-1',
iconUrl: 'iconUrl',
licenseUrl: 'licenseUrl',
projectUrl: 'projectUrl',
});
+const nugetMetadataQuery = () => ({ ...nugetMetadata(), __typename: 'NugetMetadata' });
+
+const packageTypeMetadataQueryMapping = {
+ CONAN: conanMetadataQuery,
+ COMPOSER: composerMetadataQuery,
+ PYPI: pypiMetadataQuery,
+ MAVEN: mavenMetadataQuery,
+ NUGET: nugetMetadataQuery,
+};
+
export const pagination = (extend) => ({
endCursor: 'eyJpZCI6IjIwNSIsIm5hbWUiOiJteS9jb21wYW55L2FwcC9teS1hcHAifQ',
hasNextPage: true,
@@ -202,6 +226,10 @@ export const packageDetailsQuery = (extendPackage) => ({
nodes: packageTags(),
__typename: 'PackageTagConnection',
},
+ pipelines: {
+ nodes: packagePipelines(),
+ __typename: 'PipelineConnection',
+ },
packageFiles: {
nodes: packageFiles(),
__typename: 'PackageFileConnection',
@@ -240,6 +268,21 @@ export const emptyPackageDetailsQuery = () => ({
},
});
+export const packageMetadataQuery = (packageType) => {
+ return {
+ data: {
+ package: {
+ id: 'gid://gitlab/Packages::Package/111',
+ packageType,
+ metadata: {
+ ...(packageTypeMetadataQueryMapping[packageType]?.() ?? {}),
+ },
+ __typename: 'PackageDetailsType',
+ },
+ },
+ };
+};
+
export const packageDestroyMutation = () => ({
data: {
destroyPackage: {
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
index a7e31d42c9e..3cadb001c58 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
@@ -23,6 +23,10 @@ import {
DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
DELETE_PACKAGE_FILE_ERROR_MESSAGE,
PACKAGE_TYPE_NUGET,
+ PACKAGE_TYPE_MAVEN,
+ PACKAGE_TYPE_CONAN,
+ PACKAGE_TYPE_PYPI,
+ PACKAGE_TYPE_NPM,
} from '~/packages_and_registries/package_registry/constants';
import destroyPackageFileMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql';
@@ -160,15 +164,38 @@ describe('PackagesApp', () => {
});
});
- it('renders additional metadata and has the right props', async () => {
- createComponent();
+ describe('additional metadata', () => {
+ it.each`
+ packageType | visible
+ ${PACKAGE_TYPE_MAVEN} | ${true}
+ ${PACKAGE_TYPE_CONAN} | ${true}
+ ${PACKAGE_TYPE_NUGET} | ${true}
+ ${PACKAGE_TYPE_COMPOSER} | ${true}
+ ${PACKAGE_TYPE_PYPI} | ${true}
+ ${PACKAGE_TYPE_NPM} | ${false}
+ `(
+ `It is $visible that the component is visible when the package is $packageType`,
+ async ({ packageType, visible }) => {
+ createComponent({
+ resolver: jest.fn().mockResolvedValue(
+ packageDetailsQuery({
+ packageType,
+ }),
+ ),
+ });
- await waitForPromises();
+ await waitForPromises();
- expect(findAdditionalMetadata().exists()).toBe(true);
- expect(findAdditionalMetadata().props()).toMatchObject({
- packageEntity: expect.objectContaining(packageWithoutTypename),
- });
+ expect(findAdditionalMetadata().exists()).toBe(visible);
+
+ if (visible) {
+ expect(findAdditionalMetadata().props()).toMatchObject({
+ packageId: packageWithoutTypename.id,
+ packageType,
+ });
+ }
+ },
+ );
});
it('renders installation commands and has the right props', async () => {
diff --git a/spec/frontend/work_items/components/item_title_spec.js b/spec/frontend/work_items/components/item_title_spec.js
index 0d85df25b4f..2c3f6ef8634 100644
--- a/spec/frontend/work_items/components/item_title_spec.js
+++ b/spec/frontend/work_items/components/item_title_spec.js
@@ -15,7 +15,7 @@ const createComponent = ({ title = 'Sample title', disabled = false } = {}) =>
describe('ItemTitle', () => {
let wrapper;
const mockUpdatedTitle = 'Updated title';
- const findInputEl = () => wrapper.find('span#item-title');
+ const findInputEl = () => wrapper.find('[aria-label="Title"]');
beforeEach(() => {
wrapper = createComponent();
diff --git a/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb b/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb
index 63faecad5d5..ea74e427dd6 100644
--- a/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb
+++ b/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb
@@ -22,7 +22,8 @@ RSpec.describe Mutations::IncidentManagement::TimelineEvent::Create do
occurred_at: args[:occurred_at].to_s,
incident: incident,
author: current_user,
- promoted_from_note: nil
+ promoted_from_note: nil,
+ editable: true
)
end
diff --git a/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb b/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb
index 598ee496cf1..4541f8af7d3 100644
--- a/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb
+++ b/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb
@@ -27,7 +27,8 @@ RSpec.describe Mutations::IncidentManagement::TimelineEvent::PromoteFromNote do
occurred_at: comment.created_at.to_s,
incident: incident,
author: current_user,
- promoted_from_note: comment
+ promoted_from_note: comment,
+ editable: true
)
end
diff --git a/spec/graphql/resolvers/crm/contacts_resolver_spec.rb b/spec/graphql/resolvers/crm/contacts_resolver_spec.rb
index eba26c8c71f..98da4aeac28 100644
--- a/spec/graphql/resolvers/crm/contacts_resolver_spec.rb
+++ b/spec/graphql/resolvers/crm/contacts_resolver_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe Resolvers::Crm::ContactsResolver do
end
context 'with authorized user' do
- it 'does not rise an error and returns all contacts' do
+ it 'does not rise an error and returns all contacts in the correct order' do
group.add_reporter(user)
expect { resolve_contacts(group) }.not_to raise_error
@@ -61,20 +61,26 @@ RSpec.describe Resolvers::Crm::ContactsResolver do
end
context 'when no filter is provided' do
- it 'returns all the contacts' do
- expect(resolve_contacts(group)).to match_array([contact_a, contact_b])
+ it 'returns all the contacts in the correct order' do
+ expect(resolve_contacts(group)).to eq([contact_a, contact_b])
end
end
context 'when search term is provided' do
it 'returns the correct contacts' do
- expect(resolve_contacts(group, { search: "x@test.com" })).to match_array([contact_b])
+ expect(resolve_contacts(group, { search: "x@test.com" })).to contain_exactly(contact_b)
end
end
context 'when state is provided' do
it 'returns the correct contacts' do
- expect(resolve_contacts(group, { state: :inactive })).to match_array([contact_a])
+ expect(resolve_contacts(group, { state: :inactive })).to contain_exactly(contact_a)
+ end
+ end
+
+ context 'when ids are provided' do
+ it 'returns the correct contacts' do
+ expect(resolve_contacts(group, { ids: [contact_a.to_global_id] })).to contain_exactly(contact_a)
end
end
end
diff --git a/spec/graphql/resolvers/crm/organizations_resolver_spec.rb b/spec/graphql/resolvers/crm/organizations_resolver_spec.rb
index c80caf91f90..323f134ffc3 100644
--- a/spec/graphql/resolvers/crm/organizations_resolver_spec.rb
+++ b/spec/graphql/resolvers/crm/organizations_resolver_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe Resolvers::Crm::OrganizationsResolver do
end
context 'with authorized user' do
- it 'does not rise an error and returns all organizations' do
+ it 'does not rise an error and returns all organizations in the correct order' do
group.add_reporter(user)
expect { resolve_organizations(group) }.not_to raise_error
@@ -55,20 +55,28 @@ RSpec.describe Resolvers::Crm::OrganizationsResolver do
end
context 'when no filter is provided' do
- it 'returns all the organizations' do
- expect(resolve_organizations(group)).to match_array([organization_a, organization_b])
+ it 'returns all the organizations in the correct order' do
+ expect(resolve_organizations(group)).to eq([organization_a, organization_b])
end
end
context 'when search term is provided' do
it 'returns the correct organizations' do
- expect(resolve_organizations(group, { search: "def" })).to match_array([organization_b])
+ expect(resolve_organizations(group, { search: "def" })).to contain_exactly(organization_b)
end
end
context 'when state is provided' do
it 'returns the correct organizations' do
- expect(resolve_organizations(group, { state: :inactive })).to match_array([organization_a])
+ expect(resolve_organizations(group, { state: :inactive })).to contain_exactly(organization_a)
+ end
+ end
+
+ context 'when ids are provided' do
+ it 'returns the correct organizations' do
+ expect(resolve_organizations(group, {
+ ids: [organization_b.to_global_id]
+ })).to contain_exactly(organization_b)
end
end
end
diff --git a/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb
index 85c25938fcc..2633598b48d 100644
--- a/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb
@@ -31,7 +31,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do
'created_at' => '2019-12-26T10:17:14.621Z',
'updated_at' => '2019-12-26T10:17:14.621Z',
'released_at' => '2019-12-26T10:17:14.615Z',
- 'sha' => '901de3a8bd5573f4a049b1457d28bc1592ba6bf9'
+ 'sha' => '901de3a8bd5573f4a049b1457d28bc1592ba6bf9',
+ 'author_id' => user.id
}.merge(attributes)
end
@@ -62,6 +63,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do
expect(imported_release.updated_at.to_s).to eq('2019-12-26 10:17:14 UTC')
expect(imported_release.released_at.to_s).to eq('2019-12-26 10:17:14 UTC')
expect(imported_release.sha).to eq(release['sha'])
+ expect(imported_release.author_id).to eq(release['author_id'])
end
end
diff --git a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb b/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
index f81ad9b193d..8cdfa28d40a 100644
--- a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
@@ -77,8 +77,8 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
context 'for redis metrics' do
it_behaves_like 'name suggestion' do
- # corresponding metric is collected with redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) }
- let(:key_path) { 'analytics_unique_visits.analytics_unique_visits_for_any_target' }
+ # corresponding metric is collected with redis_usage_data { unique_visit_service.unique_visits_for(targets: :compliance) }
+ let(:key_path) { 'compliance_unique_visits.compliance_unique_visits_for_any_target' }
let(:name_suggestion) { /<please fill metric name, suggested format is: {subject}_{verb}{ing|ed}_{object} eg: users_creating_epics or merge_requests_viewed_in_single_file_mode>/ }
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 9d7f464756d..a0a1a216cb6 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -1204,46 +1204,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '.analytics_unique_visits_data' do
- subject { described_class.analytics_unique_visits_data }
-
- it 'returns the number of unique visits to pages with analytics features' do
- ::Gitlab::Analytics::UniqueVisits.analytics_events.each do |target|
- expect_any_instance_of(::Gitlab::Analytics::UniqueVisits).to receive(:unique_visits_for).with(targets: target).and_return(123)
- end
-
- expect_any_instance_of(::Gitlab::Analytics::UniqueVisits).to receive(:unique_visits_for).with(targets: :analytics).and_return(543)
- expect_any_instance_of(::Gitlab::Analytics::UniqueVisits).to receive(:unique_visits_for).with(targets: :analytics, start_date: 4.weeks.ago.to_date, end_date: Date.current).and_return(987)
-
- expect(subject).to eq({
- analytics_unique_visits: {
- 'g_analytics_contribution' => 123,
- 'g_analytics_insights' => 123,
- 'g_analytics_issues' => 123,
- 'g_analytics_productivity' => 123,
- 'g_analytics_valuestream' => 123,
- 'p_analytics_pipelines' => 123,
- 'p_analytics_code_reviews' => 123,
- 'p_analytics_valuestream' => 123,
- 'p_analytics_insights' => 123,
- 'p_analytics_issues' => 123,
- 'p_analytics_repo' => 123,
- 'i_analytics_cohorts' => 123,
- 'i_analytics_dev_ops_score' => 123,
- 'i_analytics_instance_statistics' => 123,
- 'p_analytics_ci_cd_deployment_frequency' => 123,
- 'p_analytics_ci_cd_lead_time' => 123,
- 'p_analytics_ci_cd_pipelines' => 123,
- 'p_analytics_merge_request' => 123,
- 'i_analytics_dev_ops_adoption' => 123,
- 'users_viewing_analytics_group_devops_adoption' => 123,
- 'analytics_unique_visits_for_any_target' => 543,
- 'analytics_unique_visits_for_any_target_monthly' => 987
- }
- })
- end
- end
-
describe '.compliance_unique_visits_data' do
subject { described_class.compliance_unique_visits_data }
diff --git a/spec/models/customer_relations/contact_spec.rb b/spec/models/customer_relations/contact_spec.rb
index 86f868b269e..f91546f5240 100644
--- a/spec/models/customer_relations/contact_spec.rb
+++ b/spec/models/customer_relations/contact_spec.rb
@@ -142,4 +142,99 @@ RSpec.describe CustomerRelations::Contact, type: :model do
expect(issue_contact2.reload.contact_id).to eq(dupe_contact1.id)
end
end
+
+ describe '.search' do
+ let_it_be(:contact_a) do
+ create(
+ :contact,
+ group: group,
+ first_name: "ABC",
+ last_name: "DEF",
+ email: "ghi@test.com",
+ description: "LMNO",
+ state: "inactive"
+ )
+ end
+
+ let_it_be(:contact_b) do
+ create(
+ :contact,
+ group: group,
+ first_name: "PQR",
+ last_name: "STU",
+ email: "vwx@test.com",
+ description: "YZ",
+ state: "active"
+ )
+ end
+
+ subject(:found_contacts) { group.contacts.search(search_term) }
+
+ context 'when search term is empty' do
+ let(:search_term) { "" }
+
+ it 'returns all group contacts' do
+ expect(found_contacts).to contain_exactly(contact_a, contact_b)
+ end
+ end
+
+ context 'when search term is not empty' do
+ context 'when searching for first name ignoring casing' do
+ let(:search_term) { "aBc" }
+
+ it { is_expected.to contain_exactly(contact_a) }
+ end
+
+ context 'when searching for last name ignoring casing' do
+ let(:search_term) { "StU" }
+
+ it { is_expected.to contain_exactly(contact_b) }
+ end
+
+ context 'when searching for email' do
+ let(:search_term) { "ghi" }
+
+ it { is_expected.to contain_exactly(contact_a) }
+ end
+
+ context 'when searching description ignoring casing' do
+ let(:search_term) { "Yz" }
+
+ it { is_expected.to contain_exactly(contact_b) }
+ end
+
+ context 'when fuzzy searching for email and last name' do
+ let(:search_term) { "s" }
+
+ it { is_expected.to contain_exactly(contact_a, contact_b) }
+ end
+ end
+ end
+
+ describe '.search_by_state' do
+ let_it_be(:contact_a) { create(:contact, group: group, state: "inactive") }
+ let_it_be(:contact_b) { create(:contact, group: group, state: "active") }
+
+ context 'when searching for contacts state' do
+ it 'returns only inactive contacts' do
+ expect(group.contacts.search_by_state(:inactive)).to contain_exactly(contact_a)
+ end
+
+ it 'returns only active contacts' do
+ expect(group.contacts.search_by_state(:active)).to contain_exactly(contact_b)
+ end
+ end
+ end
+
+ describe '.sort_by_name' do
+ let_it_be(:contact_a) { create(:contact, group: group, first_name: "c", last_name: "d") }
+ let_it_be(:contact_b) { create(:contact, group: group, first_name: "a", last_name: "b") }
+ let_it_be(:contact_c) { create(:contact, group: group, first_name: "e", last_name: "d") }
+
+ context 'when sorting the contacts' do
+ it 'sorts them by last name then first name in ascendent order' do
+ expect(group.contacts.sort_by_name).to eq([contact_b, contact_a, contact_c])
+ end
+ end
+ end
end
diff --git a/spec/models/customer_relations/organization_spec.rb b/spec/models/customer_relations/organization_spec.rb
index 06ba9c5b7ad..1833fcf5385 100644
--- a/spec/models/customer_relations/organization_spec.rb
+++ b/spec/models/customer_relations/organization_spec.rb
@@ -78,4 +78,83 @@ RSpec.describe CustomerRelations::Organization, type: :model do
expect(contact2.reload.organization_id).to eq(dupe_organization1.id)
end
end
+
+ describe '.search' do
+ let_it_be(:organization_a) do
+ create(
+ :organization,
+ group: group,
+ name: "DEF",
+ description: "ghi_st",
+ state: "inactive"
+ )
+ end
+
+ let_it_be(:organization_b) do
+ create(
+ :organization,
+ group: group,
+ name: "ABC_st",
+ description: "JKL",
+ state: "active"
+ )
+ end
+
+ subject(:found_organizations) { group.organizations.search(search_term) }
+
+ context 'when search term is empty' do
+ let(:search_term) { "" }
+
+ it 'returns all group organizations' do
+ expect(found_organizations).to contain_exactly(organization_a, organization_b)
+ end
+ end
+
+ context 'when search term is not empty' do
+ context 'when searching for name' do
+ let(:search_term) { "aBc" }
+
+ it { is_expected.to contain_exactly(organization_b) }
+ end
+
+ context 'when searching for description' do
+ let(:search_term) { "ghI" }
+
+ it { is_expected.to contain_exactly(organization_a) }
+ end
+
+ context 'when searching for name and description' do
+ let(:search_term) { "_st" }
+
+ it { is_expected.to contain_exactly(organization_a, organization_b) }
+ end
+ end
+ end
+
+ describe '.search_by_state' do
+ let_it_be(:organization_a) { create(:organization, group: group, state: "inactive") }
+ let_it_be(:organization_b) { create(:organization, group: group, state: "active") }
+
+ context 'when searching for organizations state' do
+ it 'returns only inactive organizations' do
+ expect(group.organizations.search_by_state(:inactive)).to contain_exactly(organization_a)
+ end
+
+ it 'returns only active organizations' do
+ expect(group.organizations.search_by_state(:active)).to contain_exactly(organization_b)
+ end
+ end
+ end
+
+ describe '.sort_by_name' do
+ let_it_be(:organization_a) { create(:organization, group: group, name: "c") }
+ let_it_be(:organization_b) { create(:organization, group: group, name: "a") }
+ let_it_be(:organization_c) { create(:organization, group: group, name: "b") }
+
+ context 'when sorting the organizations' do
+ it 'sorts them by name in ascendent order' do
+ expect(group.organizations.sort_by_name).to eq([organization_b, organization_c, organization_a])
+ end
+ end
+ end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 06044cf53cc..72a57b6076a 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -82,14 +82,16 @@ RSpec.describe Milestone do
context 'when it is tied to a release for another project' do
it 'creates a validation error' do
other_project = create(:project)
- milestone.releases << build(:release, project: other_project)
+ milestone.releases << build(:release,
+ project: other_project, author_id: other_project.members.first.user_id)
expect(milestone).not_to be_valid
end
end
context 'when it is tied to a release for the same project' do
it 'is valid' do
- milestone.releases << build(:release, project: project)
+ milestone.releases << build(:release,
+ project: project, author_id: project.members.first.user_id)
expect(milestone).to be_valid
end
end
diff --git a/spec/models/projects/build_artifacts_size_refresh_spec.rb b/spec/models/projects/build_artifacts_size_refresh_spec.rb
index f8cd8fb5c76..052e654af76 100644
--- a/spec/models/projects/build_artifacts_size_refresh_spec.rb
+++ b/spec/models/projects/build_artifacts_size_refresh_spec.rb
@@ -30,6 +30,12 @@ RSpec.describe Projects::BuildArtifactsSizeRefresh, type: :model do
expect(described_class.remaining).to match_array([refresh_1, refresh_3, refresh_4])
end
end
+
+ describe 'processing_queue' do
+ it 'prioritizes pending -> stale -> created' do
+ expect(described_class.processing_queue).to eq([refresh_3, refresh_1, refresh_4])
+ end
+ end
end
describe 'state machine', :clean_gitlab_redis_shared_state do
@@ -165,15 +171,13 @@ RSpec.describe Projects::BuildArtifactsSizeRefresh, type: :model do
end
describe '.process_next_refresh!' do
- let!(:refresh_running) { create(:project_build_artifacts_size_refresh, :running) }
let!(:refresh_created) { create(:project_build_artifacts_size_refresh, :created) }
- let!(:refresh_stale) { create(:project_build_artifacts_size_refresh, :stale) }
let!(:refresh_pending) { create(:project_build_artifacts_size_refresh, :pending) }
subject(:processed_refresh) { described_class.process_next_refresh! }
it 'picks the first record from the remaining work' do
- expect(processed_refresh).to eq(refresh_created)
+ expect(processed_refresh).to eq(refresh_pending)
expect(processed_refresh.reload).to be_running
end
end
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 4ae1927dcca..83d7596ff51 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -66,6 +66,32 @@ RSpec.describe Release do
expect { release.milestones << milestone }.to change { MilestoneRelease.count }.by(1)
end
end
+
+ context 'when creating new release' do
+ subject { build(:release, project: project, name: 'Release 1.0') }
+
+ it { is_expected.to validate_presence_of(:author_id) }
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(validate_release_with_author: false)
+ end
+
+ it { is_expected.not_to validate_presence_of(:author_id) }
+ end
+ end
+
+ # Mimic releases created before 11.7
+ # See: https://gitlab.com/gitlab-org/gitlab/-/blob/8e5a110b01f842d8b6a702197928757a40ce9009/app/models/release.rb#L14
+ context 'when updating existing release without author' do
+ let(:release) { create(:release, :legacy) }
+
+ it 'updates successfully' do
+ release.description += 'Update'
+
+ expect { release.save! }.not_to raise_error
+ end
+ end
end
describe '#assets_count' do
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index 7ff020f05e8..df3e4b985ab 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -73,8 +73,6 @@ RSpec.describe ProjectPresenter do
context 'when repository is not empty' do
let_it_be(:project) { create(:project, :public, :repository) }
- let(:release) { create(:release, project: project, author: user) }
-
it 'returns files and readme if user has repository access' do
allow(presenter).to receive(:can?).with(nil, :download_code, project).and_return(true)
@@ -98,6 +96,9 @@ RSpec.describe ProjectPresenter do
end
it 'returns releases anchor' do
+ user = create(:user)
+ release = create(:release, project: project, author: user)
+
expect(release).to be_truthy
expect(presenter.releases_anchor_data).to have_attributes(
is_link: true,
diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb
index 3ea8b38e20f..923e12a3c06 100644
--- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb
@@ -53,7 +53,7 @@ RSpec.describe 'Creating an incident timeline event' do
},
'note' => note,
'action' => 'comment',
- 'editable' => false,
+ 'editable' => true,
'occurredAt' => event_occurred_at.iso8601
)
end
diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/destroy_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/destroy_spec.rb
index faff3bfe23a..85208869ad9 100644
--- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/destroy_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe 'Removing an incident timeline event' do
},
'note' => timeline_event.note,
'noteHtml' => timeline_event.note_html,
- 'editable' => false,
+ 'editable' => true,
'action' => timeline_event.action,
'occurredAt' => timeline_event.occurred_at.iso8601,
'createdAt' => timeline_event.created_at.iso8601,
diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb
index b92f6af1d3d..9272e218172 100644
--- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe 'Promote an incident timeline event from a comment' do
},
'note' => comment.note,
'action' => 'comment',
- 'editable' => false,
+ 'editable' => true,
'occurredAt' => comment.created_at.iso8601
)
end
diff --git a/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb b/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb
index 708fa96986c..31fef75f679 100644
--- a/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb
+++ b/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb
@@ -94,7 +94,7 @@ RSpec.describe 'getting incident timeline events' do
'id' => promoted_from_note.to_global_id.to_s,
'body' => promoted_from_note.note
},
- 'editable' => false,
+ 'editable' => true,
'action' => timeline_event.action,
'occurredAt' => timeline_event.occurred_at.iso8601,
'createdAt' => timeline_event.created_at.iso8601,
diff --git a/spec/services/incident_management/timeline_events/create_service_spec.rb b/spec/services/incident_management/timeline_events/create_service_spec.rb
index 38ce15e74f1..a88a9400832 100644
--- a/spec/services/incident_management/timeline_events/create_service_spec.rb
+++ b/spec/services/incident_management/timeline_events/create_service_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do
}
end
+ let(:editable) { false }
let(:current_user) { user_with_permissions }
let(:service) { described_class.new(incident, current_user, args) }
@@ -45,6 +46,7 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do
expect(result.project).to eq(project)
expect(result.note).to eq(args[:note])
expect(result.promoted_from_note).to eq(comment)
+ expect(result.editable).to eq(editable)
end
end
@@ -90,6 +92,30 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do
end
end
+ context 'with editable param' do
+ let(:args) do
+ {
+ note: 'note',
+ occurred_at: Time.current,
+ action: 'new comment',
+ promoted_from_note: comment,
+ editable: editable
+ }
+ end
+
+ context 'when editable is true' do
+ let(:editable) { true }
+
+ it_behaves_like 'success response'
+ end
+
+ context 'when editable is false' do
+ let(:editable) { false }
+
+ it_behaves_like 'success response'
+ end
+ end
+
it 'successfully creates a database record', :aggregate_failures do
expect { execute }.to change { ::IncidentManagement::TimelineEvent.count }.by(1)
end
diff --git a/spec/services/incident_management/timeline_events/update_service_spec.rb b/spec/services/incident_management/timeline_events/update_service_spec.rb
index 8bc0e5ce0ed..b1699e993d5 100644
--- a/spec/services/incident_management/timeline_events/update_service_spec.rb
+++ b/spec/services/incident_management/timeline_events/update_service_spec.rb
@@ -135,6 +135,14 @@ RSpec.describe IncidentManagement::TimelineEvents::UpdateService do
execute
end
end
+
+ context 'when timeline event is non-editable' do
+ let!(:timeline_event) do
+ create(:incident_management_timeline_event, :non_editable, project: project, incident: incident)
+ end
+
+ it_behaves_like 'error response', 'You cannot edit this timeline event.'
+ end
end
context 'when user does not have permissions' do
diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb
index 2fd1b2e9048..591f7973454 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -76,4 +76,49 @@ RSpec.shared_examples 'edits content using the content editor' do
expect(find('[data-testid="code-block-bubble-menu"]')).to have_text('Custom (nomnoml)')
end
end
+
+ describe 'mermaid diagram' do
+ before do
+ find(content_editor_testid).send_keys [:enter, :enter]
+
+ find(content_editor_testid).send_keys '```mermaid '
+ find(content_editor_testid).send_keys ['graph TD;', :enter, ' JohnDoe12 --> HelloWorld34']
+ end
+
+ it 'renders and updates the diagram correctly in a sandboxed iframe' do
+ iframe = find(content_editor_testid).find('iframe')
+ expect(iframe['src']).to include('/-/sandbox/mermaid')
+
+ within_frame(iframe) do
+ expect(find('svg').text).to include('JohnDoe12')
+ expect(find('svg').text).to include('HelloWorld34')
+ end
+
+ expect(iframe['height'].to_i).to be > 100
+
+ find(content_editor_testid).send_keys [:enter, ' JaneDoe34 --> HelloWorld56']
+
+ within_frame(iframe) do
+ page.has_content?('JaneDoe34')
+
+ expect(find('svg').text).to include('JaneDoe34')
+ expect(find('svg').text).to include('HelloWorld56')
+ end
+ end
+
+ it 'toggles the diagram when preview button is clicked' do
+ find('[data-testid="preview-diagram"]').click
+
+ expect(find(content_editor_testid)).not_to have_selector('iframe')
+
+ find('[data-testid="preview-diagram"]').click
+
+ iframe = find(content_editor_testid).find('iframe')
+
+ within_frame(iframe) do
+ expect(find('svg').text).to include('JohnDoe12')
+ expect(find('svg').text).to include('HelloWorld34')
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb
index b989dbc6524..cd591248ff6 100644
--- a/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb
@@ -21,6 +21,7 @@ RSpec.shared_examples 'creating an incident timeline event' do
expect(timeline_event.occurred_at.to_s).to eq(expected_timeline_event.occurred_at)
expect(timeline_event.incident).to eq(expected_timeline_event.incident)
expect(timeline_event.author).to eq(expected_timeline_event.author)
+ expect(timeline_event.editable).to eq(expected_timeline_event.editable)
end
end