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 @@
+
+
+
+
+
+
+
+ {{ $options.VerificationLevel[verificationLevel].badgeText }}
+
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+ {{ $options.i18n.verificationLevelPopoverLink }}
+
+
+
+
+
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 @@
+
+
+
+
+
+ {{ $options.i18n.body }}
+
+
+
+
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,
},
};
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ copyProjectId }}
-
-
-
-
-
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ copyProjectId }}
+
+
+
+
+
+
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