From a3b88e15d85d5ef66057d96060e9e646fd33b3c8 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 4 Apr 2024 21:13:26 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/setup.gitlab-ci.yml | 19 ++- .../components/details/ci_resource_header.vue | 23 ++- .../list/ci_resources_list_item.vue | 14 +- .../pages/ci_resource_details_page.vue | 12 +- .../shared/ci_verification_badge.vue | 74 +++++++++ .../javascripts/ci/catalog/constants.js | 14 ++ .../catalog_resource.fragment.graphql | 5 +- ..._ci_catalog_resource_details.query.graphql | 22 --- .../components/details_feedback.vue | 35 ++++ .../components/show_deployment.vue | 3 + .../home_panel/components/home_panel.vue | 77 +-------- .../components/home_panel_actions.vue | 84 ++++++++++ app/models/namespace.rb | 7 - app/models/project.rb | 11 -- app/models/users/callout.rb | 3 +- app/services/projects/after_rename_service.rb | 1 - app/services/projects/create_service.rb | 5 - app/services/projects/transfer_service.rb | 7 +- .../development/ml_experiment_tracking.yml | 2 +- danger/master_pipeline_status/Dangerfile | 3 + danger/plugins/master_pipeline_status.rb | 9 ++ ...eplication_and_failover_troubleshooting.md | 4 +- doc/api/graphql/reference/index.md | 1 + doc/api/merge_request_approvals.md | 1 + doc/api/tags.md | 1 + doc/ci/components/index.md | 11 ++ .../documentation/feature_flags.md | 82 ++++++---- doc/topics/git/how_to_install_git/index.md | 1 + doc/topics/git/unstage.md | 1 + .../merge_requests/approvals/settings.md | 1 + .../merge_when_pipeline_succeeds.md | 1 + .../merge_requests/reviews/suggestions.md | 1 + doc/user/project/remote_development/index.md | 1 + doc/user/snippets.md | 1 + lib/gitlab/git/repository.rb | 15 -- .../gitaly_client/repository_service.rb | 27 ---- locale/gitlab.pot | 30 +++- qa/qa/resource/project.rb | 4 + .../ci_catalog_sorting_spec.rb | 2 +- .../run_component_in_project_pipeline_spec.rb | 9 +- .../details/ci_resource_header_spec.js | 52 +++--- .../list/ci_resources_list_item_spec.js | 32 ++++ .../pages/ci_resource_details_page_spec.js | 4 +- .../shared/ci_verification_badge_spec.js | 85 ++++++++++ spec/frontend/ci/catalog/mock.js | 58 +++---- .../components/details_feedback_spec.js | 53 +++++++ .../components/show_deployment_spec.js | 5 + .../components/home_panel_actions_spec.js | 80 ++++++++++ .../home_panel/components/home_panel_spec.js | 77 +-------- spec/lib/gitlab/git/repository_spec.rb | 46 ------ .../gitaly_client/repository_service_spec.rb | 25 --- spec/models/project_spec.rb | 25 --- spec/services/projects/create_service_spec.rb | 4 - .../helpers/user_with_namespace_shim.yml | 1 - .../danger/master_pipeline_status_spec.rb | 150 ++++++++++++++++++ tooling/danger/master_pipeline_status.rb | 77 +++++++++ tooling/danger/project_helper.rb | 1 + 57 files changed, 920 insertions(+), 479 deletions(-) create mode 100644 app/assets/javascripts/ci/catalog/components/shared/ci_verification_badge.vue create mode 100644 app/assets/javascripts/deployments/components/details_feedback.vue create mode 100644 app/assets/javascripts/pages/projects/home_panel/components/home_panel_actions.vue create mode 100644 danger/master_pipeline_status/Dangerfile create mode 100644 danger/plugins/master_pipeline_status.rb create mode 100644 spec/frontend/ci/catalog/components/shared/ci_verification_badge_spec.js create mode 100644 spec/frontend/deployments/components/details_feedback_spec.js create mode 100644 spec/frontend/home_panel/components/home_panel_actions_spec.js create mode 100644 spec/tooling/danger/master_pipeline_status_spec.rb create mode 100644 tooling/danger/master_pipeline_status.rb diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml index cdad52233de..7b06ccd5888 100644 --- a/.gitlab/ci/setup.gitlab-ci.yml +++ b/.gitlab/ci/setup.gitlab-ci.yml @@ -160,10 +160,21 @@ detect-tests: filter_rspec_matched_foss_tests ${RSPEC_MATCHING_TESTS_PATH} ${RSPEC_MATCHING_TESTS_FOSS_PATH}; filter_rspec_matched_ee_tests ${RSPEC_MATCHING_TESTS_PATH} ${RSPEC_MATCHING_TESTS_EE_PATH}; - echoinfo "Changed files: $(cat $RSPEC_CHANGED_FILES_PATH)"; - echoinfo "Related FOSS RSpec tests: $(cat $RSPEC_MATCHING_TESTS_FOSS_PATH)"; - echoinfo "Related EE RSpec tests: $(cat $RSPEC_MATCHING_TESTS_EE_PATH)"; - echoinfo "Related JS files: $(cat $RSPEC_MATCHING_JS_FILES_PATH)"; + echoinfo 'Changed files:' + echoinfo "$(tr ' ' '\n' < $RSPEC_CHANGED_FILES_PATH)" + echo "" + + echoinfo 'Related FOSS RSpec tests:' + echoinfo "$(tr ' ' '\n' < $RSPEC_MATCHING_TESTS_FOSS_PATH)" + echo "" + + echoinfo 'Related EE RSpec tests:' + echoinfo "$(tr ' ' '\n' < $RSPEC_MATCHING_TESTS_EE_PATH)" + echo "" + + echoinfo 'Related JS files:' + echoinfo "$(tr ' ' '\n' < $RSPEC_MATCHING_JS_FILES_PATH)" + echo "" fi artifacts: expire_in: 7d diff --git a/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue b/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue index 072b863c52b..b6fc8ae5d7d 100644 --- a/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue +++ b/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue @@ -10,8 +10,8 @@ import { import { __ } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { cleanLeadingSeparator } from '~/lib/utils/url_utility'; -import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; +import CiVerificationBadge from '../shared/ci_verification_badge.vue'; import CiResourceAbout from './ci_resource_about.vue'; import CiResourceHeaderSkeletonLoader from './ci_resource_header_skeleton_loader.vue'; @@ -22,9 +22,9 @@ export default { }, components: { AbuseCategorySelector, - CiIcon, CiResourceAbout, CiResourceHeaderSkeletonLoader, + CiVerificationBadge, GlAvatar, GlAvatarLink, GlDisclosureDropdown, @@ -54,11 +54,6 @@ export default { required: false, default: 0, }, - pipelineStatus: { - type: Object, - required: false, - default: () => ({}), - }, resource: { type: Object, required: true, @@ -81,8 +76,8 @@ export default { hasLatestVersion() { return this.latestVersion?.name; }, - hasPipelineStatus() { - return this.pipelineStatus?.text; + isVerified() { + return this.resource?.verificationLevel !== 'UNVERIFIED'; }, latestVersion() { return this.resource?.versions?.nodes[0] || {}; @@ -139,11 +134,11 @@ export default { {{ versionBadgeText }} - diff --git a/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue b/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue index 8363159122c..57538da65de 100644 --- a/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue +++ b/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue @@ -14,6 +14,7 @@ import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; import { toNounSeriesText } from '~/lib/utils/grammar'; import { cleanLeadingSeparator } from '~/lib/utils/url_utility'; import { CI_RESOURCE_DETAILS_PAGE_NAME } from '../../router/constants'; +import CiVerificationBadge from '../shared/ci_verification_badge.vue'; export default { i18n: { @@ -22,6 +23,7 @@ export default { releasedMessage: s__('CiCatalog|Released %{timeAgo} by %{author}'), }, components: { + CiVerificationBadge, GlAvatar, GlBadge, GlButton, @@ -80,6 +82,9 @@ export default { hasReleasedVersion() { return Boolean(this.latestVersion?.createdAt); }, + isVerified() { + return this.resource?.verificationLevel !== 'UNVERIFIED'; + }, latestVersion() { return this.resource?.versions?.nodes[0] || []; }, @@ -143,7 +148,14 @@ export default { @click="navigateToDetailsPage" />
- {{ webPath }} +
+ {{ webPath }} + +
diff --git a/app/assets/javascripts/ci/catalog/components/shared/ci_verification_badge.vue b/app/assets/javascripts/ci/catalog/components/shared/ci_verification_badge.vue new file mode 100644 index 00000000000..8d999470db1 --- /dev/null +++ b/app/assets/javascripts/ci/catalog/components/shared/ci_verification_badge.vue @@ -0,0 +1,74 @@ + + + diff --git a/app/assets/javascripts/ci/catalog/constants.js b/app/assets/javascripts/ci/catalog/constants.js index 2767c9d91ec..086a3531a34 100644 --- a/app/assets/javascripts/ci/catalog/constants.js +++ b/app/assets/javascripts/ci/catalog/constants.js @@ -1,4 +1,5 @@ import { helpPagePath } from '~/helpers/help_page_helper'; +import { s__ } from '~/locale'; export const CATALOG_FEEDBACK_DISMISSED_KEY = 'catalog_feedback_dismissed'; @@ -7,6 +8,19 @@ export const SCOPE = { namespaces: 'NAMESPACES', }; +export const VerificationLevel = { + GITLAB: { + badgeText: s__('CiCatalog|GitLab-maintained'), + icon: 'tanuki-verified', + popoverText: s__('CiCatalog|Created and maintained by %{boldStart}GitLab%{boldEnd}'), + }, + PARTNER: { + badgeText: s__('CiCatalog|Partner'), + icon: 'partner-verified', + popoverText: s__('CiCatalog|Created and maintained by a %{boldStart}GitLab Partner%{boldEnd}'), + }, +}; + export const SORT_OPTION_CREATED = 'CREATED'; export const SORT_OPTION_RELEASED = 'LATEST_RELEASED_AT'; export const SORT_ASC = 'ASC'; diff --git a/app/assets/javascripts/ci/catalog/graphql/fragments/catalog_resource.fragment.graphql b/app/assets/javascripts/ci/catalog/graphql/fragments/catalog_resource.fragment.graphql index ded14b18664..1a8b6f5858c 100644 --- a/app/assets/javascripts/ci/catalog/graphql/fragments/catalog_resource.fragment.graphql +++ b/app/assets/javascripts/ci/catalog/graphql/fragments/catalog_resource.fragment.graphql @@ -1,11 +1,11 @@ fragment CatalogResourceFields on CiCatalogResource { id - webPath + description icon name - description starCount starrersPath + verificationLevel versions(first: 1) { nodes { id @@ -26,4 +26,5 @@ fragment CatalogResourceFields on CiCatalogResource { } } } + webPath } diff --git a/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resource_details.query.graphql b/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resource_details.query.graphql index 79a3e46b822..cd711ba806f 100644 --- a/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resource_details.query.graphql +++ b/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resource_details.query.graphql @@ -4,27 +4,5 @@ query getCiCatalogResourceDetails($fullPath: ID!) { webPath openIssuesCount openMergeRequestsCount - versions(first: 1) { - nodes { - id - commit { - id - pipelines(first: 1) { - nodes { - id - detailedStatus { - id - detailsPath - icon - text - group - } - } - } - } - name - createdAt - } - } } } diff --git a/app/assets/javascripts/deployments/components/details_feedback.vue b/app/assets/javascripts/deployments/components/details_feedback.vue new file mode 100644 index 00000000000..ff98457e1fa --- /dev/null +++ b/app/assets/javascripts/deployments/components/details_feedback.vue @@ -0,0 +1,35 @@ + + diff --git a/app/assets/javascripts/deployments/components/show_deployment.vue b/app/assets/javascripts/deployments/components/show_deployment.vue index 6e62925ced1..b5abfe241e5 100644 --- a/app/assets/javascripts/deployments/components/show_deployment.vue +++ b/app/assets/javascripts/deployments/components/show_deployment.vue @@ -8,6 +8,7 @@ import environmentQuery from '../graphql/queries/environment.query.graphql'; import DeploymentHeader from './deployment_header.vue'; import DeploymentAside from './deployment_aside.vue'; import DeploymentDeployBlock from './deployment_deploy_block.vue'; +import DetailsFeedback from './details_feedback.vue'; const DEPLOYMENT_QUERY_POLLING_INTERVAL = 3000; @@ -18,6 +19,7 @@ export default { DeploymentHeader, DeploymentAside, DeploymentDeployBlock, + DetailsFeedback, DeploymentApprovals: () => import('ee_component/deployments/components/deployment_approvals.vue'), DeploymentTimeline: () => import('ee_component/deployments/components/deployment_timeline.vue'), @@ -99,6 +101,7 @@ export default { :environment="environment" :loading="$apollo.queries.deployment.loading" /> + -import { GlButton, GlTooltipDirective } from '@gitlab/ui'; -import { __, s__, sprintf } from '~/locale'; -import { isLoggedIn } from '~/lib/utils/common_utils'; -import ForksButton from '~/forks/components/forks_button.vue'; -import MoreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue'; -import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue'; -import StarCount from '~/stars/components/star_count.vue'; +import HomePanelActions from './home_panel_actions.vue'; export default { components: { - ForksButton, - GlButton, - MoreActionsDropdown, - NotificationsDropdown, - StarCount, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - inject: { - adminPath: { - default: '', - }, - canReadProject: { - default: false, - }, - isProjectEmpty: { - default: false, - }, - projectId: { - default: '', - }, - }, - data() { - return { - isLoggedIn: isLoggedIn(), - }; - }, - computed: { - canForkProject() { - return !this.isProjectEmpty && isLoggedIn() && this.canReadProject; - }, - copyProjectId() { - return sprintf(s__('ProjectPage|Project ID: %{id}'), { id: this.projectId }); - }, - }, - i18n: { - adminButtonTooltip: __('View project in admin area'), + HomePanelActions, }, }; diff --git a/app/assets/javascripts/pages/projects/home_panel/components/home_panel_actions.vue b/app/assets/javascripts/pages/projects/home_panel/components/home_panel_actions.vue new file mode 100644 index 00000000000..40427f3e4b0 --- /dev/null +++ b/app/assets/javascripts/pages/projects/home_panel/components/home_panel_actions.vue @@ -0,0 +1,84 @@ + + + diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 4c2b799cba0..bc186986984 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -830,13 +830,6 @@ class Namespace < ApplicationRecord Rails.cache.delete_multi(keys) end - def write_projects_repository_config - all_projects.find_each do |project| - project.set_full_path - project.track_project_repository - end - end - def enforce_minimum_path_length? path_changed? && !project_namespace? end diff --git a/app/models/project.rb b/app/models/project.rb index 76dbd0547bb..ec77ab7011a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2283,16 +2283,6 @@ class Project < ApplicationRecord ensure_pages_metadatum.update!(onboarding_complete: true) end - def set_full_path(gl_full_path: full_path) - # We'd need to keep track of project full path otherwise directory tree - # created with hashed storage enabled cannot be usefully imported using - # the import rake task. - repository.raw_repository.set_full_path(full_path: gl_full_path) - rescue Gitlab::Git::Repository::NoRepository => e - Gitlab::AppLogger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.") - nil - end - def after_import repository.expire_content_cache repository.remove_prohibited_branches @@ -2315,7 +2305,6 @@ class Project < ApplicationRecord after_create_default_branch join_pool_repository refresh_markdown_cache! - set_full_path end def update_project_counter_caches diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb index 5c472d4c344..140339e1bb0 100644 --- a/app/models/users/callout.rb +++ b/app/models/users/callout.rb @@ -85,7 +85,8 @@ module Users joining_a_project_alert: 83, # EE-only transition_to_jihu_callout: 84, summarize_code_changes: 85, # EE-only - duo_pro_trial_alert: 86 # EE-only + duo_pro_trial_alert: 86, # EE-only + deployment_details_feedback: 87 } validates :feature_name, diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb index 5cd30689faf..3f079a14b65 100644 --- a/app/services/projects/after_rename_service.rb +++ b/app/services/projects/after_rename_service.rb @@ -97,7 +97,6 @@ module Projects def update_repository_configuration project.reload_repository! - project.set_full_path project.track_project_repository end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 6f29c72e25a..50f0db3728a 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -112,11 +112,6 @@ module Projects if @project.import? Gitlab::Tracking.event(self.class.name, 'import_project', user: current_user) - else - # Skip writing the config for project imports/forks because it - # will always fail since the Git directory doesn't exist until - # a background job creates it (see Project#add_import_job). - @project.set_full_path end unless @project.gitlab_project_import? diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 49648216808..b128b8a4c5b 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -133,7 +133,7 @@ module Projects project.old_path_with_namespace = @old_path - update_repository_configuration(@new_path) + update_repository_configuration remove_issue_contacts @@ -196,8 +196,7 @@ module Projects project.visibility_level = to_namespace.visibility_level unless project.visibility_level_allowed_by_group? end - def update_repository_configuration(full_path) - project.set_full_path(gl_full_path: full_path) + def update_repository_configuration project.track_project_repository end @@ -233,7 +232,7 @@ module Projects def rollback_side_effects project.reset update_namespace_and_visibility(@old_namespace) - update_repository_configuration(@old_path) + update_repository_configuration end def execute_system_hooks diff --git a/config/feature_flags/development/ml_experiment_tracking.yml b/config/feature_flags/development/ml_experiment_tracking.yml index b39c7395bbc..fae71e6f0d1 100644 --- a/config/feature_flags/development/ml_experiment_tracking.yml +++ b/config/feature_flags/development/ml_experiment_tracking.yml @@ -4,6 +4,6 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95689 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/371669 milestone: '15.6' type: development -group: group::incubation +group: group::mlops default_enabled: true log_state_changes: true diff --git a/danger/master_pipeline_status/Dangerfile b/danger/master_pipeline_status/Dangerfile new file mode 100644 index 00000000000..a88a61604a4 --- /dev/null +++ b/danger/master_pipeline_status/Dangerfile @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +master_pipeline_status.check! diff --git a/danger/plugins/master_pipeline_status.rb b/danger/plugins/master_pipeline_status.rb new file mode 100644 index 00000000000..70c99f55f6e --- /dev/null +++ b/danger/plugins/master_pipeline_status.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require_relative '../../tooling/danger/master_pipeline_status' + +module Danger + class MasterPipelineStatus < ::Danger::Plugin + include Tooling::Danger::MasterPipelineStatus + end +end diff --git a/doc/administration/postgresql/replication_and_failover_troubleshooting.md b/doc/administration/postgresql/replication_and_failover_troubleshooting.md index 7a4e70e0809..1ac35d4973e 100644 --- a/doc/administration/postgresql/replication_and_failover_troubleshooting.md +++ b/doc/administration/postgresql/replication_and_failover_troubleshooting.md @@ -95,8 +95,8 @@ If a replica cannot start or rejoin the cluster, or when it lags behind and cann sudo gitlab-ctl patroni reinitialize-replica --member gitlab-database-2.example.com ``` - This can be run on any Patroni node, but be aware that `sudo gitlab-ctl patroni - reinitialize-replica` without `--member` restarts the server it is run on. + This can be run on any Patroni node, but be aware that `sudo gitlab-ctl patroni reinitialize-replica` + without `--member` restarts the server it is run on. You should run it locally on the broken server to reduce the risk of unintended data loss. 1. Monitor the logs: diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index cadafab80b6..efd7353e506 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -33342,6 +33342,7 @@ Name of the feature that the callout is for. | `CI_DEPRECATION_WARNING_FOR_TYPES_KEYWORD` | Callout feature name for ci_deprecation_warning_for_types_keyword. | | `CLOUD_LICENSING_SUBSCRIPTION_ACTIVATION_BANNER` | Callout feature name for cloud_licensing_subscription_activation_banner. | | `CLUSTER_SECURITY_WARNING` | Callout feature name for cluster_security_warning. | +| `DEPLOYMENT_DETAILS_FEEDBACK` | Callout feature name for deployment_details_feedback. | | `DUO_CHAT_CALLOUT` | Callout feature name for duo_chat_callout. | | `DUO_PRO_TRIAL_ALERT` | Callout feature name for duo_pro_trial_alert. | | `FEATURE_FLAGS_NEW_VERSION` | Callout feature name for feature_flags_new_version. | diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md index 7438575aeb3..e86c4f7f440 100644 --- a/doc/api/merge_request_approvals.md +++ b/doc/api/merge_request_approvals.md @@ -2,6 +2,7 @@ stage: Create group: Code Review info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments" +description: "Documentation for the REST API for merge request approvals in GitLab." --- # Merge request approvals API diff --git a/doc/api/tags.md b/doc/api/tags.md index d3bb0ed3598..2080567f24a 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -2,6 +2,7 @@ stage: Create group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +description: "Documentation for the REST API for Git tags in GitLab." --- # Tags API diff --git a/doc/ci/components/index.md b/doc/ci/components/index.md index 6defe5d32ab..8777d9cbd15 100644 --- a/doc/ci/components/index.md +++ b/doc/ci/components/index.md @@ -292,6 +292,17 @@ in the catalog. The project and its repository still exist, but are not visible To publish the component project in the catalog again, you need to [publish a new release](#publish-a-new-release). +### Verified component creators + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/433443) in GitLab 16.11 + +Some CI/CD components are badged with an icon to show that the component was created +and is maintained by users verified by GitLab: + +- GitLab-maintained (**{tanuki-verified}**): Components that are created and maintained by GitLab. +- GitLab Partner (**{partner-verified}**): Components that are created and maintained by + a GitLab-verified partner. + ## Best practices This section describes some best practices for creating high quality component projects. diff --git a/doc/development/documentation/feature_flags.md b/doc/development/documentation/feature_flags.md index 3fb61334317..54696a0db47 100644 --- a/doc/development/documentation/feature_flags.md +++ b/doc/development/documentation/feature_flags.md @@ -15,9 +15,9 @@ When the state of a feature flag changes, the developer who made the change ## When to document features behind a feature flag -Every feature introduced to the codebase, even if it's behind a disabled feature flag, +Every feature introduced to the codebase, even if it's behind a disabled flag, must be documented. For more information, see -[the discussion that led to this decision](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47917#note_459984428). [Experiment or Beta](../../policy/experiment-beta-support.md) features are usually behind a feature flag, and must also be documented. For more information, see [Document Experiment or Beta features](experiment_beta.md). +[the discussion that led to this decision](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47917#note_459984428). [Experiment or Beta](../../policy/experiment-beta-support.md) features are usually behind a feature flag and must also be documented. For more information, see [Document Experiment or Beta features](experiment_beta.md). When the feature is [implemented in multiple merge requests](../feature_flags/index.md#feature-flags-in-gitlab-development), discuss the plan with your technical writer. @@ -39,7 +39,7 @@ even when the feature is not fully functional or otherwise documented. When you document feature flags, you must: - [Add history text](#add-history-text). -- [Add a note at the start of the topic](#use-a-note-to-describe-the-state-of-the-feature-flag). +- [Use a note to describe the state of the feature flag](#use-a-note-to-describe-the-state-of-the-feature-flag). ## Add history text @@ -50,9 +50,9 @@ Possible history entries are: ```markdown > - [Introduced](issue-link) in GitLab X.X [with a flag](../../administration/feature_flags.md) named `flag_name`. Disabled by default. -> - [Enabled on GitLab.com](issue-link) in GitLab X.X. -> - [Enabled on GitLab.com](issue-link) in GitLab X.X. Available to GitLab.com administrators only. > - [Enabled on self-managed](issue-link) in GitLab X.X. +> - [Enabled on GitLab.com](issue-link) in GitLab X.X. +> - [Enabled on GitLab Dedicated](issue-link) in GitLab X.X. > - [Generally available](issue-link) in GitLab X.Y. Feature flag `flag_name` removed. ``` @@ -60,7 +60,8 @@ Possible history entries are: Information about feature flags should be in a `FLAG` note at the start of the topic (just below the history). -The note has three parts, and follows this structure: +The note has three required parts and one optional part. +The note follows this exact structure and order: ```markdown FLAG: @@ -73,7 +74,8 @@ FLAG: A `FLAG` note renders on the GitLab documentation site as: FLAG: -On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../../administration/feature_flags.md) named `example_flag`. +On self-managed GitLab, by default this feature is not available. +To make it available, an administrator can [enable the feature flag](../../administration/feature_flags.md) named `example_flag`. On GitLab.com and GitLab Dedicated, this feature is not available. This feature is not ready for production use. @@ -95,65 +97,78 @@ This feature is not ready for production use. | If the feature is... | Use this text | |---------------------------------------------|---------------| -| Available | `On GitLab.com, this feature is available. On GitLab Dedicated, this feature is not available.` | +| Available | `On GitLab.com, this feature is available.` | | Available to GitLab.com administrators only | `On GitLab.com, this feature is available but can be configured by GitLab.com administrators only.` | -| Unavailable | `On GitLab.com and GitLab Dedicated, this feature is not available.`| +| Unavailable | `On GitLab.com, this feature is not available.` | ### GitLab Dedicated availability information | If the feature is... | Use this text | |---------------------------------------------|---------------| | Available | `On GitLab Dedicated, this feature is available.` | -| Unavailable | `On GitLab Dedicated, this feature is not available.`| +| Unavailable | `On GitLab Dedicated, this feature is not available.` | - You can combine GitLab.com and GitLab Dedicated like this: `On GitLab.com and GitLab Dedicated, this feature is not available.` -- If the feature is behind a feature flag that is disabled for self-managed, +- If the feature is behind a flag that is disabled for self-managed GitLab, the feature is not available for GitLab Dedicated. ### Optional information If needed, you can add this sentence: -`The feature is not ready for production use.` +`This feature is not ready for production use.` ## Feature flag documentation examples -The following examples show the progression of a feature flag. +The following examples show the progression of a feature flag. Update the history and the `FLAG` note with every change: ```markdown -> - Introduced in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default. +> - [Introduced](issue-link) in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default. FLAG: -On self-managed GitLab, by default this feature is not available. To make it available, -an administrator can [enable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`. -The feature is not ready for production use. On GitLab.com and GitLab Dedicated, this feature is not available. +On self-managed GitLab, by default this feature is not available. +To make it available, an administrator can [enable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`. +On GitLab.com and GitLab Dedicated, this feature is not available. ``` -When the feature is enabled in production, you can update the history: +When the feature is enabled by default on self-managed GitLab: ```markdown -> - Introduced in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default. -> - [Enabled on self-managed](https://gitlab.com/issue/etc) GitLab 13.8. +> - [Introduced](issue-link) in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default. +> - [Enabled on self-managed and GitLab Dedicated](issue-link) in GitLab 13.8. FLAG: -On self-managed GitLab, by default this feature is available. To hide the feature per user, -an administrator can [disable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`. +On self-managed GitLab, by default this feature is available. +To hide the feature, an administrator can [disable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`. +On GitLab.com, this feature is not available. On GitLab Dedicated, this feature is available. +``` + +When the feature is enabled by default for all offerings: + +```markdown +> - [Introduced](issue-link) in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default. +> - [Enabled on self-managed and GitLab Dedicated](issue-link) in GitLab 13.8. +> - [Enabled on GitLab.com](issue-link) in GitLab 13.9. + +FLAG: +On self-managed GitLab, by default this feature is available. +To hide the feature, an administrator can [disable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`. On GitLab.com and GitLab Dedicated, this feature is available. ``` -And, when the feature is done and fully available to all users: +When the flag is removed, add the `Generally available` entry and delete the `FLAG` note: ```markdown -> - Introduced in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default. -> - [Enabled on self-managed](https://gitlab.com/issue/etc) in GitLab 13.8. -> - [Enabled on GitLab.com](https://gitlab.com/issue/etc) in GitLab 13.9. +> - [Introduced](issue-link) in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default. +> - [Enabled on self-managed and GitLab Dedicated](issue-link) in GitLab 13.8. +> - [Enabled on GitLab.com](issue-link) in GitLab 13.9. > - [Generally available](issue-link) in GitLab 14.0. Feature flag `forti_token_cloud` removed. ``` ## Simplify long history -The history can get long, but you can sometimes simplify or remove entries. +The history can get long, but you can sometimes simplify or delete entries. Combine entries if they happened in the same release: @@ -161,8 +176,8 @@ Combine entries if they happened in the same release: ```markdown > - [Introduced](issue-link) in GitLab 14.2 [with a flag](../../administration/feature_flags.md) named `ci_include_rules`. Disabled by default. - > - [Enabled on GitLab.com](issue-link) in GitLab 14.3. > - [Enabled on self-managed](issue-link) in GitLab 14.3. + > - [Enabled on GitLab.com](issue-link) in GitLab 14.3. > - [Enabled on GitLab Dedicated](issue-link) in GitLab 14.3. ``` @@ -170,22 +185,23 @@ Combine entries if they happened in the same release: ```markdown > - [Introduced](issue-link) in GitLab 14.2 [with a flag](../../administration/feature_flags.md) named `ci_include_rules`. Disabled by default. - > - [Enabled on GitLab.com, self-managed, and GitLab Dedicated](issue-link) in GitLab 14.3. + > - [Enabled on self-managed, GitLab.com, and GitLab Dedicated](issue-link) in GitLab 14.3. ``` -Remove `Enabled on GitLab.com` entries when the feature is enabled by default for all offerings: +Delete `Enabled` entries when the feature is enabled by default for all offerings and the flag is removed: - Before: ```markdown > - [Introduced](issue-link) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default. - > - [Enabled on GitLab.com](issue-link) in GitLab 15.9. - > - [Generally available](issue-link) in GitLab 15.10. Feature flag `ci_hooks_pre_get_sources_script` removed. + > - [Enabled on self-managed and GitLab Dedicated](issue-link) in GitLab 15.7. + > - [Enabled on GitLab.com](issue-link) in GitLab 15.8. + > - [Generally available](issue-link) in GitLab 15.9. Feature flag `ci_hooks_pre_get_sources_script` removed. ``` - After: ```markdown > - [Introduced](issue-link) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default. - > - [Generally available](issue-link) in GitLab 15.10. Feature flag `ci_hooks_pre_get_sources_script` removed. + > - [Generally available](issue-link) in GitLab 15.9. Feature flag `ci_hooks_pre_get_sources_script` removed. ``` diff --git a/doc/topics/git/how_to_install_git/index.md b/doc/topics/git/how_to_install_git/index.md index 8e1636bc491..c00900e6dec 100644 --- a/doc/topics/git/how_to_install_git/index.md +++ b/doc/topics/git/how_to_install_git/index.md @@ -2,6 +2,7 @@ stage: Create group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +description: "How to install Git on your local machine." --- # Installing Git diff --git a/doc/topics/git/unstage.md b/doc/topics/git/unstage.md index f65ca18378f..da1d28ec9f7 100644 --- a/doc/topics/git/unstage.md +++ b/doc/topics/git/unstage.md @@ -2,6 +2,7 @@ stage: Create group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +description: "Use the unstage command in Git to stop tracking your changes to a file." --- # Unstage a file in Git diff --git a/doc/user/project/merge_requests/approvals/settings.md b/doc/user/project/merge_requests/approvals/settings.md index b1f4438cfbb..3858acc67d5 100644 --- a/doc/user/project/merge_requests/approvals/settings.md +++ b/doc/user/project/merge_requests/approvals/settings.md @@ -2,6 +2,7 @@ stage: Create group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +description: "Define approval rules and limits in GitLab with merge request approval settings. Options include preventing author approval, requiring re-authentication, and removing approvals on new commits." --- # Merge request approval settings diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md index d991f0179e7..fed6c5d6a3f 100644 --- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md +++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md @@ -2,6 +2,7 @@ stage: Create group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +description: "Set auto-merge on a merge request when you have reviewed its content, so it can merge without intervention when all merge checks pass." --- # Auto-merge diff --git a/doc/user/project/merge_requests/reviews/suggestions.md b/doc/user/project/merge_requests/reviews/suggestions.md index c924939dd40..fa6a820863a 100644 --- a/doc/user/project/merge_requests/reviews/suggestions.md +++ b/doc/user/project/merge_requests/reviews/suggestions.md @@ -2,6 +2,7 @@ stage: Create group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +description: "Suggest improvements to the code in a merge request, and commit those improvements to the merge request directly from your browser." --- # Suggest changes diff --git a/doc/user/project/remote_development/index.md b/doc/user/project/remote_development/index.md index 15fdd61a49b..a39227a4664 100644 --- a/doc/user/project/remote_development/index.md +++ b/doc/user/project/remote_development/index.md @@ -2,6 +2,7 @@ stage: Create group: IDE info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +description: "Write and compile code in your browser, using a secure cloud-based environment." --- # Remote development diff --git a/doc/user/snippets.md b/doc/user/snippets.md index 09fae140ab8..59a8e998b78 100644 --- a/doc/user/snippets.md +++ b/doc/user/snippets.md @@ -2,6 +2,7 @@ stage: Create group: Source Code info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments" +description: "Use snippets to store and share code, text, and files from your browser. Snippets support version control, commenting, and embedding." --- # Snippets diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 524737c6a72..5238bd9b142 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1038,21 +1038,6 @@ module Gitlab end # rubocop:enable Metrics/ParameterLists - def set_full_path(full_path:) - return unless full_path.present? - - # This guard avoids Gitaly log/error spam - raise NoRepository, 'repository does not exist' unless exists? - - gitaly_repository_client.set_full_path(full_path) - end - - def full_path - wrapped_gitaly_errors do - gitaly_repository_client.full_path - end - end - def disconnect_alternates wrapped_gitaly_errors do gitaly_repository_client.disconnect_alternates diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 60d14d18f62..cdd5168d25b 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -251,33 +251,6 @@ module Gitlab gitaly_client_call(@storage, :repository_service, :write_ref, request, timeout: GitalyClient.fast_timeout) end - def set_full_path(path) - gitaly_client_call( - @storage, - :repository_service, - :set_full_path, - Gitaly::SetFullPathRequest.new( - repository: @gitaly_repo, - path: path - ), - timeout: GitalyClient.fast_timeout - ) - - nil - end - - def full_path - response = gitaly_client_call( - @storage, - :repository_service, - :full_path, - Gitaly::FullPathRequest.new(repository: @gitaly_repo), - timeout: GitalyClient.fast_timeout - ) - - response.path.presence - end - def find_license request = Gitaly::FindLicenseRequest.new(repository: @gitaly_repo) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a359e0f6191..164812b7f08 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10774,6 +10774,12 @@ msgstr "" msgid "CiCatalog|Create a pipeline component repository and make reusing pipeline configurations faster and easier." msgstr "" +msgid "CiCatalog|Created and maintained by %{boldStart}GitLab%{boldEnd}" +msgstr "" + +msgid "CiCatalog|Created and maintained by a %{boldStart}GitLab Partner%{boldEnd}" +msgstr "" + msgid "CiCatalog|Discover CI/CD components that can improve your pipeline with additional functionality." msgstr "" @@ -10783,12 +10789,18 @@ msgstr "" msgid "CiCatalog|Get started with the CI/CD Catalog" msgstr "" +msgid "CiCatalog|GitLab-maintained" +msgstr "" + msgid "CiCatalog|Go to the project" msgstr "" msgid "CiCatalog|How do I publish a component?" msgstr "" +msgid "CiCatalog|Learn more about designated creators" +msgstr "" + msgid "CiCatalog|No component available" msgstr "" @@ -10798,6 +10810,9 @@ msgstr "" msgid "CiCatalog|No result found" msgstr "" +msgid "CiCatalog|Partner" +msgstr "" + msgid "CiCatalog|Publish the CI/CD components in this project to the CI/CD Catalog" msgstr "" @@ -17633,6 +17648,9 @@ msgstr "" msgid "DeploymentApproval| Current approvals: %{current}" msgstr "" +msgid "DeploymentApproval|After deployment #%{deploymentIid} has the required approvals, you can run the manual job. Rejecting will fail the manual job." +msgstr "" + msgid "DeploymentApproval|Approval options" msgstr "" @@ -17645,9 +17663,6 @@ msgstr "" msgid "DeploymentApproval|Approved by you %{time}" msgstr "" -msgid "DeploymentApproval|Approving will run the manual job from deployment #%{deploymentIid}. Rejecting will fail the manual job." -msgstr "" - msgid "DeploymentApproval|Deployment approved" msgstr "" @@ -17785,6 +17800,9 @@ msgstr "" msgid "Deployment|Latest Deployed" msgstr "" +msgid "Deployment|Leave feedback" +msgstr "" + msgid "Deployment|Needs Approval" msgstr "" @@ -17850,6 +17868,12 @@ msgstr "" msgid "Deployment|Waiting to be deployed." msgstr "" +msgid "Deployment|We know this page is a bit empty at the moment, but we're working hard to bring you a great experience! In the meantime, we'd love to hear your feedback." +msgstr "" + +msgid "Deployment|What would you like to see here?" +msgstr "" + msgid "Deprecated API rate limits" msgstr "" diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 01786d3d4b6..0763bfba329 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -468,6 +468,10 @@ module QA api_post_to(api_releases_path, tag_name: tag, ref: ref, **params) end + def has_release?(tag) + releases.any? { |release| release[:tag_name] == tag } + end + def protected_branches response = api_get_from(api_protected_branches_path) parse_body(response) diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb index f8f4e64719c..ed31fe5bcb9 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :skip_live_env, product_group: :pipeline_authoring do + RSpec.describe 'Verify', product_group: :pipeline_authoring do describe 'CI catalog' do let(:project_count) { 3 } diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb index 7f7addf482d..bdda2d16d12 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :runner, :skip_live_env, product_group: :pipeline_authoring do + RSpec.describe 'Verify', :runner, product_group: :pipeline_authoring do describe 'CI component' do let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" } let(:tag) { '1.0.0' } @@ -66,6 +66,9 @@ module QA add_ci_file(component_project, 'templates/new-component.yml', component_content) component_project.create_release(tag) + QA::Runtime::Logger.info("Waiting for #{component_project.name}'s release #{tag} to be available") + Support::Waiter.wait_until { component_project.has_release?(tag) } + test_project.visit! add_ci_file(test_project, '.gitlab-ci.yml', ci_yml_content) end @@ -76,9 +79,11 @@ module QA it 'runs in project pipeline with correct inputs', :aggregate_failures, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/451582' do - Flow::Pipeline.visit_latest_pipeline(status: 'Passed') + Flow::Pipeline.visit_latest_pipeline Page::Project::Pipeline::Show.perform do |show| + Support::Waiter.wait_until { show.has_passed? } + expect(show).to have_stage(test_stage), "Expected pipeline to have stage #{test_stage} but not found." end diff --git a/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js b/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js index e4d1e2b1aa0..06e85f277a9 100644 --- a/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js +++ b/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js @@ -4,7 +4,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; import CiResourceHeader from '~/ci/catalog/components/details/ci_resource_header.vue'; import CiResourceAbout from '~/ci/catalog/components/details/ci_resource_about.vue'; -import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; +import CiVerificationBadge from '~/ci/catalog/components/shared/ci_verification_badge.vue'; import { catalogSharedDataMock, catalogAdditionalDetailsMock } from '../../mock'; describe('CiResourceHeader', () => { @@ -26,8 +26,8 @@ describe('CiResourceHeader', () => { const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector); const findAvatar = () => wrapper.findComponent(GlAvatar); const findAvatarLink = () => wrapper.findComponent(GlAvatarLink); + const findVerificationBadge = () => wrapper.findComponent(CiVerificationBadge); const findVersionBadge = () => wrapper.findComponent(GlBadge); - const findPipelineStatusBadge = () => wrapper.findComponent(CiIcon); const createComponent = ({ props = {} } = {}) => { wrapper = shallowMountExtended(CiResourceHeader, { @@ -102,42 +102,32 @@ describe('CiResourceHeader', () => { }); }); - describe('when the project has a release', () => { - const pipelineStatus = { - detailsPath: 'path/to/pipeline', - icon: 'status_success', - text: 'passed', - group: 'success', - }; + describe('verification badge', () => { + describe('when the resource is not verified', () => { + beforeEach(() => { + createComponent(); + }); + + it('does not render the verification badge', () => { + expect(findVerificationBadge().exists()).toBe(false); + }); + }); describe.each` - hasPipelineBadge | describeText | testText | status - ${true} | ${'is'} | ${'renders'} | ${pipelineStatus} - ${false} | ${'is not'} | ${'does not render'} | ${{}} - `('and there $describeText a pipeline', ({ hasPipelineBadge, testText, status }) => { + verificationLevel | describeText + ${'GITLAB'} | ${'GitLab'} + ${'PARTNER'} | ${'partner'} + `('when the resource is $describeText maintained', ({ verificationLevel }) => { beforeEach(() => { - createComponent({ - props: { - pipelineStatus: status, - latestVersion: { name: '1.0.0', path: 'path/to/release' }, - }, - }); + createComponent({ props: { resource: { ...resource, verificationLevel } } }); }); - it('renders the version badge', () => { - expect(findVersionBadge().exists()).toBe(true); + it('renders the verification badge', () => { + expect(findVerificationBadge().exists()).toBe(true); }); - it(`${testText} the pipeline status badge`, () => { - expect(findPipelineStatusBadge().exists()).toBe(hasPipelineBadge); - if (hasPipelineBadge) { - expect(findPipelineStatusBadge().props()).toEqual({ - showStatusText: true, - status: pipelineStatus, - showTooltip: true, - useLink: true, - }); - } + it('displays the correct badge', () => { + expect(findVerificationBadge().props('verificationLevel')).toBe(verificationLevel); }); }); }); diff --git a/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js b/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js index 735087e70bb..5fbab85ca01 100644 --- a/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js +++ b/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js @@ -7,6 +7,7 @@ import { cleanLeadingSeparator } from '~/lib/utils/url_utility'; import { createRouter } from '~/ci/catalog/router/index'; import CiResourcesListItem from '~/ci/catalog/components/list/ci_resources_list_item.vue'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import CiVerificationBadge from '~/ci/catalog/components/shared/ci_verification_badge.vue'; import { catalogSinglePageResponse } from '../../mock'; Vue.use(VueRouter); @@ -48,6 +49,7 @@ describe('CiResourcesListItem', () => { const findResourceName = () => wrapper.findByTestId('ci-resource-link'); const findResourceDescription = () => wrapper.findByText(defaultProps.resource.description); const findUserLink = () => wrapper.findByTestId('user-link'); + const findVerificationBadge = () => wrapper.findComponent(CiVerificationBadge); const findTimeAgoMessage = () => wrapper.findComponent(GlSprintf); const findFavorites = () => wrapper.findByTestId('stats-favorites'); @@ -139,6 +141,36 @@ describe('CiResourcesListItem', () => { }); }); + describe('verification badge', () => { + describe('when the resource is not verified', () => { + beforeEach(() => { + createComponent(); + }); + + it('does not render the verification badge', () => { + expect(findVerificationBadge().exists()).toBe(false); + }); + }); + + describe.each` + verificationLevel | describeText + ${'GITLAB'} | ${'GitLab'} + ${'PARTNER'} | ${'partner'} + `('when the resource is $describeText maintained', ({ verificationLevel }) => { + beforeEach(() => { + createComponent({ props: { resource: { ...resource, verificationLevel } } }); + }); + + it('renders the verification badge', () => { + expect(findVerificationBadge().exists()).toBe(true); + }); + + it('displays the correct badge', () => { + expect(findVerificationBadge().props('verificationLevel')).toBe(verificationLevel); + }); + }); + }); + describe('release time', () => { describe('when there is no release data', () => { beforeEach(() => { diff --git a/spec/frontend/ci/catalog/components/pages/ci_resource_details_page_spec.js b/spec/frontend/ci/catalog/components/pages/ci_resource_details_page_spec.js index 49fff799efa..2951c8b3658 100644 --- a/spec/frontend/ci/catalog/components/pages/ci_resource_details_page_spec.js +++ b/spec/frontend/ci/catalog/components/pages/ci_resource_details_page_spec.js @@ -164,8 +164,6 @@ describe('CiResourceDetailsPage', () => { isLoadingSharedData: false, openIssuesCount: defaultAdditionalData.openIssuesCount, openMergeRequestsCount: defaultAdditionalData.openMergeRequestsCount, - pipelineStatus: - defaultAdditionalData.versions.nodes[0].commit.pipelines.nodes[0].detailedStatus, resource: defaultSharedData, }); }); @@ -179,7 +177,7 @@ describe('CiResourceDetailsPage', () => { it('passes expected props', () => { expect(findDetailsComponent().props()).toEqual({ resourcePath: cleanLeadingSeparator(defaultSharedData.webPath), - version: defaultAdditionalData.versions.nodes[0].name, + version: defaultSharedData.versions.nodes[0].name, }); }); }); diff --git a/spec/frontend/ci/catalog/components/shared/ci_verification_badge_spec.js b/spec/frontend/ci/catalog/components/shared/ci_verification_badge_spec.js new file mode 100644 index 00000000000..fd6fc763831 --- /dev/null +++ b/spec/frontend/ci/catalog/components/shared/ci_verification_badge_spec.js @@ -0,0 +1,85 @@ +import { GlIcon, GlLink } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import CiVerificationBadge from '~/ci/catalog/components/shared/ci_verification_badge.vue'; +import { VerificationLevel } from '~/ci/catalog/constants'; + +describe('Catalog Verification Badge', () => { + let wrapper; + + const defaultProps = { + resourceId: 'gid://gitlab/Ci::Catalog::Resource/36', + showText: true, + verificationLevel: 'GITLAB', + }; + + const findVerificationIcon = () => wrapper.findComponent(GlIcon); + const findLink = () => wrapper.findComponent(GlLink); + const findVerificationText = () => wrapper.findByTestId('verification-badge-text'); + + const createComponent = (props = defaultProps) => { + wrapper = extendedWrapper( + shallowMount(CiVerificationBadge, { + propsData: { + ...props, + }, + }), + ); + }; + + describe('when the badge is rendered', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders an icon', () => { + expect(findVerificationIcon().exists()).toBe(true); + }); + + it('renders a link', () => { + expect(findLink().exists()).toBe(true); + }); + }); + + describe('badge text', () => { + describe('when showText is true', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders badge text', () => { + expect(findVerificationText().exists()).toBe(true); + }); + }); + + describe('when showText is false', () => { + beforeEach(() => { + createComponent({ ...defaultProps, showText: false }); + }); + + it('does not render badge text', () => { + expect(findVerificationText().exists()).toBe(false); + }); + }); + }); + + describe.each` + verificationLevel | describeText + ${'GITLAB'} | ${'GitLab'} + ${'PARTNER'} | ${'partner'} + `('when the resource is $describeText maintained', ({ verificationLevel }) => { + beforeEach(() => { + createComponent({ ...defaultProps, verificationLevel }); + }); + + it('renders the correct icon', () => { + expect(findVerificationIcon().props('name')).toBe(VerificationLevel[verificationLevel].icon); + }); + + it('displays the correct badge text', () => { + expect(findVerificationText().text()).toContain( + VerificationLevel[verificationLevel].badgeText, + ); + }); + }); +}); diff --git a/spec/frontend/ci/catalog/mock.js b/spec/frontend/ci/catalog/mock.js index 9b8c3a9ab04..249c054ebc5 100644 --- a/spec/frontend/ci/catalog/mock.js +++ b/spec/frontend/ci/catalog/mock.js @@ -96,6 +96,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-42/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -110,6 +111,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-41/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -124,6 +126,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-40/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -138,6 +141,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-39/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -152,6 +156,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-38/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -166,6 +171,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-37/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -180,6 +186,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-36/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -194,6 +201,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-35/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -208,6 +216,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-34/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -222,6 +231,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-33/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -236,6 +246,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-32/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -250,6 +261,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-31/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -264,6 +276,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-30/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -278,6 +291,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-29/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -292,6 +306,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-28/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -306,6 +321,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-27/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -320,6 +336,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-26/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -334,6 +351,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-25/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -348,6 +366,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-24/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -362,6 +381,7 @@ export const catalogResponseBody = { description: 'A simple component', starCount: 0, starrersPath: '/frontend-fixtures/project-23/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -394,6 +414,7 @@ export const catalogSinglePageResponse = { name: 'Project-45 Name', description: 'A simple component', starCount: 0, + verificationLevel: 'UNVERIFIED', versions: { __typename: 'CiCatalogResourceVersionConnection', nodes: [ @@ -424,6 +445,7 @@ export const catalogSinglePageResponse = { name: 'Project-44 Name', description: 'A simple component', starCount: 0, + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -437,6 +459,7 @@ export const catalogSinglePageResponse = { name: 'Project-43 Name', description: 'A simple component', starCount: 0, + verificationLevel: 'UNVERIFIED', versions: { nodes: [], __typename: 'CiCatalogResourceVersionConnection', @@ -460,6 +483,7 @@ export const catalogSharedDataMock = { name: 'Ruby', starCount: 1, starrersPath: '/path/to/project/-/starrers', + verificationLevel: 'UNVERIFIED', versions: { __typename: 'CiCatalogResourceVersionConnection', nodes: [ @@ -494,40 +518,6 @@ export const catalogAdditionalDetailsMock = { openIssuesCount: 4, openMergeRequestsCount: 10, readmeHtml: '

Hello world

', - versions: { - __typename: 'CiCatalogResourceVersionConnection', - nodes: [ - { - __typename: 'CiCatalogResourceVersion', - id: 'gid://gitlab/Release/3', - commit: { - __typename: 'Commit', - id: 'gid://gitlab/CommitPresenter/afa936495f20e08c26ed4a67130ee2166f94fa6e', - pipelines: { - __typename: 'PipelineConnection', - nodes: [ - { - __typename: 'Pipeline', - id: 'gid://gitlab/Ci::Pipeline/583', - detailedStatus: { - __typename: 'DetailedStatus', - id: 'success-583-583', - detailsPath: '/root/cicd-circular/-/pipelines/583', - icon: 'status_success', - text: 'passed', - group: 'success', - }, - }, - ], - }, - }, - name: '1.0.2', - path: '/path/to/release', - author: { __typename: 'UserCore', id: 1, webUrl: 'profile/1', name: 'username' }, - createdAt: '2022-08-23T17:19:09Z', - }, - ], - }, }, }, }; diff --git a/spec/frontend/deployments/components/details_feedback_spec.js b/spec/frontend/deployments/components/details_feedback_spec.js new file mode 100644 index 00000000000..6b4627ab86f --- /dev/null +++ b/spec/frontend/deployments/components/details_feedback_spec.js @@ -0,0 +1,53 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlAlert } from '@gitlab/ui'; +import DetailsFeedback from '~/deployments/components/details_feedback.vue'; +import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; + +jest.mock('~/sentry/sentry_browser_wrapper'); +jest.mock('~/graphql_shared/utils'); + +describe('~/deployments/components/details_feedback.vue', () => { + let wrapper; + let dismiss; + let dismisserComponent; + + const createComponent = ({ shouldShowCallout = true } = {}) => { + dismiss = jest.fn(); + dismisserComponent = makeMockUserCalloutDismisser({ + dismiss, + shouldShowCallout, + }); + wrapper = shallowMount(DetailsFeedback, { + stubs: { + UserCalloutDismisser: dismisserComponent, + }, + }); + }; + + const findAlert = () => wrapper.findComponent(GlAlert); + + it('shows an alert', () => { + createComponent(); + expect(findAlert().exists()).toBe(true); + }); + + it('calls dismiss when the alert is dismissed', () => { + createComponent(); + findAlert().vm.$emit('dismiss'); + expect(dismiss).toHaveBeenCalled(); + }); + + it('links to the feedback issue', () => { + createComponent(); + expect(findAlert().props()).toMatchObject({ + title: 'What would you like to see here?', + primaryButtonText: 'Leave feedback', + primaryButtonLink: 'https://gitlab.com/gitlab-org/gitlab/-/issues/450700', + }); + }); + + it('hides the alert if already dismissed', () => { + createComponent({ shouldShowCallout: false }); + expect(findAlert().exists()).toBe(false); + }); +}); diff --git a/spec/frontend/deployments/components/show_deployment_spec.js b/spec/frontend/deployments/components/show_deployment_spec.js index f76967a5027..bd92e620ca8 100644 --- a/spec/frontend/deployments/components/show_deployment_spec.js +++ b/spec/frontend/deployments/components/show_deployment_spec.js @@ -9,6 +9,7 @@ import { toggleQueryPollingByVisibility } from '~/graphql_shared/utils'; import ShowDeployment from '~/deployments/components/show_deployment.vue'; import DeploymentHeader from '~/deployments/components/deployment_header.vue'; import DeploymentDeployBlock from '~/deployments/components/deployment_deploy_block.vue'; +import DetailsFeedback from '~/deployments/components/details_feedback.vue'; import deploymentQuery from '~/deployments/graphql/queries/deployment.query.graphql'; import environmentQuery from '~/deployments/graphql/queries/environment.query.graphql'; import waitForPromises from 'helpers/wait_for_promises'; @@ -106,6 +107,10 @@ describe('~/deployments/components/show_deployment.vue', () => { }); }); + it('shows an alert asking for feedback', () => { + expect(wrapper.findComponent(DetailsFeedback).exists()).toBe(true); + }); + it('shows the deployment block if the deployment job is manual', () => { expect(wrapper.findComponent(DeploymentDeployBlock).props()).toEqual({ deployment: mockDeploymentFixture.data.project.deployment, diff --git a/spec/frontend/home_panel/components/home_panel_actions_spec.js b/spec/frontend/home_panel/components/home_panel_actions_spec.js new file mode 100644 index 00000000000..f47eac8c198 --- /dev/null +++ b/spec/frontend/home_panel/components/home_panel_actions_spec.js @@ -0,0 +1,80 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import HomePanelActions from '~/pages/projects/home_panel/components/home_panel_actions.vue'; +import ForksButton from '~/forks/components/forks_button.vue'; +import MoreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue'; +import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue'; +import StarCount from '~/stars/components/star_count.vue'; + +describe('HomePanelActions', () => { + let wrapper; + + const createComponent = ({ isLoggedIn = false, provide = {} } = {}) => { + if (isLoggedIn) { + window.gon.current_user_id = 1; + } + + wrapper = shallowMountExtended(HomePanelActions, { + provide: { + ...provide, + }, + }); + }; + + const findAdminButton = () => wrapper.find('[data-testid="admin-button"]'); + const findForksButton = () => wrapper.findComponent(ForksButton); + const findMoreActionsDropdown = () => wrapper.findComponent(MoreActionsDropdown); + const findNotificationsDropdown = () => wrapper.findComponent(NotificationsDropdown); + const findStarCount = () => wrapper.findComponent(StarCount); + + describe.each` + isLoggedIn | canReadProject | isProjectEmpty | adminPath | isForkButtonVisible | isMoreActionsDropdownVisible | isNotificationDropdownVisible | isStarCountVisible | isAdminButtonVisible + ${true} | ${true} | ${true} | ${undefined} | ${false} | ${true} | ${true} | ${true} | ${false} + ${true} | ${true} | ${true} | ${null} | ${false} | ${true} | ${true} | ${true} | ${false} + ${true} | ${true} | ${true} | ${''} | ${false} | ${true} | ${true} | ${true} | ${false} + ${true} | ${true} | ${false} | ${''} | ${true} | ${true} | ${true} | ${true} | ${false} + ${true} | ${false} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} + ${true} | ${false} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} + ${true} | ${true} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${true} | ${true} | ${true} + ${true} | ${true} | ${false} | ${'project/admin/path'} | ${true} | ${true} | ${true} | ${true} | ${true} + ${true} | ${false} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} + ${true} | ${false} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} + ${false} | ${true} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} + ${false} | ${true} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} + ${false} | ${false} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} + ${false} | ${false} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} + ${false} | ${true} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} + ${false} | ${true} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} + ${false} | ${false} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} + ${false} | ${false} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} + `( + 'renders components', + ({ + isLoggedIn, + canReadProject, + isProjectEmpty, + adminPath, + isForkButtonVisible, + isMoreActionsDropdownVisible, + isNotificationDropdownVisible, + isStarCountVisible, + isAdminButtonVisible, + }) => { + it('as expected', () => { + createComponent({ + isLoggedIn, + provide: { + adminPath, + canReadProject, + isProjectEmpty, + }, + }); + + expect(findForksButton().exists()).toBe(isForkButtonVisible); + expect(findMoreActionsDropdown().exists()).toBe(isMoreActionsDropdownVisible); + expect(findNotificationsDropdown().exists()).toBe(isNotificationDropdownVisible); + expect(findStarCount().exists()).toBe(isStarCountVisible); + expect(findAdminButton().exists()).toBe(isAdminButtonVisible); + }); + }, + ); +}); diff --git a/spec/frontend/home_panel/components/home_panel_spec.js b/spec/frontend/home_panel/components/home_panel_spec.js index 619238d2325..e0f874b6479 100644 --- a/spec/frontend/home_panel/components/home_panel_spec.js +++ b/spec/frontend/home_panel/components/home_panel_spec.js @@ -1,80 +1,19 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import HomePanel from '~/pages/projects/home_panel/components/home_panel.vue'; -import ForksButton from '~/forks/components/forks_button.vue'; -import MoreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue'; -import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue'; -import StarCount from '~/stars/components/star_count.vue'; +import HomePanelActions from '~/pages/projects/home_panel/components/home_panel_actions.vue'; describe('HomePanel', () => { let wrapper; - const createComponent = ({ isLoggedIn = false, provide = {} } = {}) => { - if (isLoggedIn) { - window.gon.current_user_id = 1; - } - - wrapper = shallowMountExtended(HomePanel, { - provide: { - ...provide, - }, - }); + const createComponent = () => { + wrapper = shallowMountExtended(HomePanel); }; - const findAdminButton = () => wrapper.find('[data-testid="admin-button"]'); - const findForksButton = () => wrapper.findComponent(ForksButton); - const findMoreActionsDropdown = () => wrapper.findComponent(MoreActionsDropdown); - const findNotificationsDropdown = () => wrapper.findComponent(NotificationsDropdown); - const findStarCount = () => wrapper.findComponent(StarCount); + const findHomePanelActions = () => wrapper.findComponent(HomePanelActions); - describe.each` - isLoggedIn | canReadProject | isProjectEmpty | adminPath | isForkButtonVisible | isMoreActionsDropdownVisible | isNotificationDropdownVisible | isStarCountVisible | isAdminButtonVisible - ${true} | ${true} | ${true} | ${undefined} | ${false} | ${true} | ${true} | ${true} | ${false} - ${true} | ${true} | ${true} | ${null} | ${false} | ${true} | ${true} | ${true} | ${false} - ${true} | ${true} | ${true} | ${''} | ${false} | ${true} | ${true} | ${true} | ${false} - ${true} | ${true} | ${false} | ${''} | ${true} | ${true} | ${true} | ${true} | ${false} - ${true} | ${false} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} - ${true} | ${false} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} - ${true} | ${true} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${true} | ${true} | ${true} - ${true} | ${true} | ${false} | ${'project/admin/path'} | ${true} | ${true} | ${true} | ${true} | ${true} - ${true} | ${false} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} - ${true} | ${false} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} - ${false} | ${true} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} - ${false} | ${true} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} - ${false} | ${false} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} - ${false} | ${false} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false} - ${false} | ${true} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} - ${false} | ${true} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} - ${false} | ${false} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} - ${false} | ${false} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true} - `( - 'renders components', - ({ - isLoggedIn, - canReadProject, - isProjectEmpty, - adminPath, - isForkButtonVisible, - isMoreActionsDropdownVisible, - isNotificationDropdownVisible, - isStarCountVisible, - isAdminButtonVisible, - }) => { - it('as expected', () => { - createComponent({ - isLoggedIn, - provide: { - adminPath, - canReadProject, - isProjectEmpty, - }, - }); + it('renders components as expected', () => { + createComponent(); - expect(findForksButton().exists()).toBe(isForkButtonVisible); - expect(findMoreActionsDropdown().exists()).toBe(isMoreActionsDropdownVisible); - expect(findNotificationsDropdown().exists()).toBe(isNotificationDropdownVisible); - expect(findStarCount().exists()).toBe(isStarCountVisible); - expect(findAdminButton().exists()).toBe(isAdminButtonVisible); - }); - }, - ); + expect(findHomePanelActions().exists()).toBe(true); + }); }); diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index acd80b49894..932c557eab3 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -2242,52 +2242,6 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen end end - describe '#set_full_path' do - let(:full_path) { 'some/path' } - - before do - repository.set_full_path(full_path: full_path) - end - - it 'writes full_path to gitaly' do - repository.set_full_path(full_path: "not-the/real-path.git") - - expect(repository.full_path).to eq('not-the/real-path.git') - end - - context 'it is given an empty path' do - it 'does not write it to disk' do - repository.set_full_path(full_path: "") - - expect(repository.full_path).to eq(full_path) - end - end - - context 'repository does not exist' do - it 'raises NoRepository and does not call SetFullPath' do - repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '', 'group/project') - - expect(repository.gitaly_repository_client).not_to receive(:set_full_path) - - expect do - repository.set_full_path(full_path: 'foo/bar.git') - end.to raise_error(Gitlab::Git::Repository::NoRepository) - end - end - end - - describe '#full_path' do - let(:full_path) { 'some/path' } - - before do - repository.set_full_path(full_path: full_path) - end - - it 'returns the full path' do - expect(repository.full_path).to eq(full_path) - end - end - describe '#merge_to_ref' do let(:repository) { mutable_repository } let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index 1e17033c18b..5a4c6e4c3a7 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -451,31 +451,6 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService, feature_category: :gital end end - describe '#set_full_path' do - let(:path) { 'repo/path' } - - it 'sends a set_full_path message' do - expect_any_instance_of(Gitaly::RepositoryService::Stub) - .to receive(:set_full_path) - .with(gitaly_request_with_params(path: path), kind_of(Hash)) - .and_return(double) - - client.set_full_path(path) - end - end - - describe '#full_path' do - let(:path) { 'repo/path' } - - it 'sends a full_path message' do - expect_any_instance_of(Gitaly::RepositoryService::Stub) - .to receive(:full_path) - .and_return(double(path: path)) - - expect(client.full_path).to eq(path) - end - end - describe "#find_license" do it 'sends a find_license request with medium timeout' do expect_any_instance_of(Gitaly::RepositoryService::Stub) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c4f98d6581f..5a3d5c147e5 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -6022,7 +6022,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:repository_size, :wiki_size]) expect(DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id) expect(AuthorizedProjectUpdate::ProjectRecalculateWorker).to receive(:perform_async).with(project.id) - expect(project).to receive(:set_full_path) project.after_import end @@ -6175,30 +6174,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr end end - describe '#set_full_path' do - let_it_be(:project) { create(:project, :repository) } - - let(:repository) { project.repository.raw } - - it 'writes full path in .git/config when key is missing' do - project.set_full_path - - expect(repository.full_path).to eq project.full_path - end - - it 'updates full path in .git/config when key is present' do - project.set_full_path(gl_full_path: 'old/path') - - expect { project.set_full_path }.to change { repository.full_path }.from('old/path').to(project.full_path) - end - - it 'does not raise an error with an empty repository' do - project = create(:project_empty_repo) - - expect { project.set_full_path }.not_to raise_error - end - end - describe '#default_branch' do context 'with default_branch_name' do let_it_be_with_refind(:root_group) { create(:group) } diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 61d80fcb014..a328cc905c5 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -461,10 +461,6 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :groups_an let(:imported_project) { create_project(user, { name: 'test', import_url: 'http://import-url', import_data: import_data }) } it 'does not write repository config' do - expect_next_instance_of(Project) do |project| - expect(project).not_to receive(:set_full_path) - end - imported_project expect(imported_project.project_namespace).to be_in_sync_with_project(imported_project) end diff --git a/spec/support/helpers/user_with_namespace_shim.yml b/spec/support/helpers/user_with_namespace_shim.yml index d2ad52365cd..00b77daad97 100644 --- a/spec/support/helpers/user_with_namespace_shim.yml +++ b/spec/support/helpers/user_with_namespace_shim.yml @@ -449,7 +449,6 @@ - spec/features/profiles/list_users_comment_template_spec.rb - spec/features/profiles/oauth_applications_spec.rb - spec/features/profiles/password_spec.rb -- spec/features/profiles/personal_access_tokens_spec.rb - spec/features/profiles/two_factor_auths_spec.rb - spec/features/profiles/user_changes_notified_of_own_activity_spec.rb - spec/features/profiles/user_creates_comment_template_spec.rb diff --git a/spec/tooling/danger/master_pipeline_status_spec.rb b/spec/tooling/danger/master_pipeline_status_spec.rb new file mode 100644 index 00000000000..9689d97ae70 --- /dev/null +++ b/spec/tooling/danger/master_pipeline_status_spec.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require 'gitlab/dangerfiles/spec_helper' +require 'fast_spec_helper' + +require_relative '../../../tooling/danger/master_pipeline_status' + +RSpec.describe Tooling::Danger::MasterPipelineStatus, feature_category: :tooling do + include_context 'with dangerfile' + + let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) } + let(:statuses) do + [ + { + 'name' => 'rubocop', + 'stage' => 'linter', + 'status' => 'failed', + 'last_finished_at' => '2016-08-12 15:23:28 UTC', + 'last_failed' => { + 'web_url' => "rubocop_failed_web_url", + 'finished_at' => "2024-03-25T18:49:52.259Z" + } + }, + { + 'name' => 'rspec', + 'stage' => 'test', + 'status' => 'failed', + 'last_finished_at' => '2016-08-12 15:23:28 UTC', + 'last_failed' => { + 'web_url' => "rubocop_failed_web_url", + 'finished_at' => "2024-03-25T18:49:52.259Z" + } + }, + { + 'name' => 'eslint', + 'stage' => 'test', + 'status' => 'success', + 'last_finished_at' => '2016-08-12 15:23:28 UTC' + } + ] + end + + let(:jobs) do + [ + { 'name' => 'rubocop', 'stage' => 'linter', 'allow_failure' => false }, + { 'name' => 'rspec', 'stage' => 'test', 'allow_failure' => false }, + { 'name' => 'eslint', 'stage' => 'linter', 'allow_failure' => false } + ] + end + + let(:file_contents_response) { JSON.pretty_generate(statuses) } # rubocop:disable Gitlab/Json -- JSON is sufficient + let(:pipeline_jobs_response) { double('PipelineJobsResponse', auto_paginate: jobs) } # rubocop:disable RSpec/VerifiedDoubles -- type is not relevant + + subject(:master_pipeline_status) { fake_danger.new(helper: fake_helper) } + + before do + allow(master_pipeline_status).to receive_message_chain(:gitlab, :api, :pipeline_jobs) do + pipeline_jobs_response + end + + allow(master_pipeline_status).to receive_message_chain(:gitlab, :api, :file_contents) do + file_contents_response + end + + allow(fake_helper).to receive(:ci?).and_return(ci_env) + end + + describe 'check!' do + context 'when not in ci environment' do + let(:ci_env) { false } + + it 'does not add the warnings' do + expect(master_pipeline_status).not_to receive(:warn) + + master_pipeline_status.check! + end + end + + context 'when in ci environment' do + let(:ci_env) { true } + + context 'when all tests are reported as passed in status page' do + let(:statuses) do + [ + { + 'name' => 'rubocop', + 'stage' => 'linter', + 'status' => 'success', + 'last_finished_at' => '2024-03-25T18:49:52.259Z' + } + ] + end + + it 'does not raise any warning' do + expect(master_pipeline_status).not_to receive(:warn) + + master_pipeline_status.check! + end + end + + context 'when rubocop is reproted to have failed in the pipeline status page' do + it 'raises warnings for rubocop' do + expect(master_pipeline_status).to receive(:warn).with( + <<~MSG + The [master pipeline status page](#{described_class::STATUS_PAGE_URL}) reported failures in + + * [rubocop](rubocop_failed_web_url) + * [rspec](rubocop_failed_web_url) + + If these jobs fail in your merge request with the same errors, then they are not caused by your changes. + Please check for any on-going incidents in the [incident issue tracker](#{described_class::INCIDENT_TRACKER_URL}) or in the `#master-broken` Slack channel. + MSG + ) + + master_pipeline_status.check! + end + end + + context 'when api returns error when fetching pipeline status' do + let(:file_contents_response) { raise StandardError, 'Failed to fetch file_contents' } + + it 'does not raise any warning' do + expect(master_pipeline_status).not_to receive(:warn) + + master_pipeline_status.check! + end + end + + context 'when status file does not contain a valid JSON' do + let(:file_contents_response) { '{' } + + it 'does not raise any warning' do + expect(master_pipeline_status).not_to receive(:warn) + + master_pipeline_status.check! + end + end + + context 'when api returns error when fetching pipeline jobs' do + let(:pipeline_jobs_response) { raise StandardError, 'Failed to fetch pipeline jobs' } + + it 'does not raise any warning' do + expect(master_pipeline_status).not_to receive(:warn) + + master_pipeline_status.check! + end + end + end + end +end diff --git a/tooling/danger/master_pipeline_status.rb b/tooling/danger/master_pipeline_status.rb new file mode 100644 index 00000000000..266ad7a04b6 --- /dev/null +++ b/tooling/danger/master_pipeline_status.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'gitlab' + +module Tooling + module Danger + module MasterPipelineStatus + MASTER_PIPELINE_STATUS_PROJECT_ID = '40549124' # https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents + MASTER_PIPELINE_STATUS_BRANCH = 'master-pipeline-status' + MASTER_PIPELINE_STATUS_FILE_NAME = 'canonical-gitlab-master-pipeline-status.json' + STATUS_PAGE_URL = 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/raw/master-pipeline-status/canonical-gitlab-master-pipeline-status.json' + INCIDENT_TRACKER_URL = 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues' + + def check! + return unless helper.ci? + + failed_master_pipeline_jobs = master_pipeline_jobs_statuses.select { |job| job['status'] == 'failed' } + return if failed_master_pipeline_jobs.empty? + + pipeline_jobs = pipeline_jobs(ENV['CI_PROJECT_ID'], ENV['CI_PIPELINE_ID']) + return if pipeline_jobs.empty? + + job_statuses_to_warn = impacted_job_statuses(pipeline_jobs, failed_master_pipeline_jobs) + warn(construct_message(job_statuses_to_warn)) if job_statuses_to_warn.any? + end + + private + + def master_pipeline_jobs_statuses + status_file_content = begin + gitlab.api.file_contents( + MASTER_PIPELINE_STATUS_PROJECT_ID, + MASTER_PIPELINE_STATUS_FILE_NAME, + MASTER_PIPELINE_STATUS_BRANCH + ) + rescue StandardError + '[]' + end + + JSON.parse(status_file_content) + rescue JSON::ParserError + [] + end + + def pipeline_jobs(project_id, pipeline_id) + gitlab.api.pipeline_jobs(project_id, pipeline_id).auto_paginate + rescue StandardError + [] + end + + def impacted_job_statuses(pipeline_jobs, failed_jobs_statuses) + failed_jobs_statuses.select do |failed_job_status| + pipeline_jobs.any? do |job| + failed_job_status['name'] == job['name'] && + failed_job_status['stage'] == job['stage'] && + !job['allow_failure'] + end + end + end + + def construct_message(job_statuses_to_warn) + job_list = job_statuses_to_warn.map do |job| + "* [#{job['name']}](#{job.dig('last_failed', 'web_url')})" + end.join("\n") + + <<~MSG + The [master pipeline status page](#{STATUS_PAGE_URL}) reported failures in + + #{job_list} + + If these jobs fail in your merge request with the same errors, then they are not caused by your changes. + Please check for any on-going incidents in the [incident issue tracker](#{INCIDENT_TRACKER_URL}) or in the `#master-broken` Slack channel. + MSG + end + end + end +end diff --git a/tooling/danger/project_helper.rb b/tooling/danger/project_helper.rb index dd5ae6be8e5..257c68729cf 100644 --- a/tooling/danger/project_helper.rb +++ b/tooling/danger/project_helper.rb @@ -8,6 +8,7 @@ module Tooling ci_templates datateam feature_flag + master_pipeline_status roulette sidekiq_queues specialization_labels