diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 8db03227a81..f03a34b9064 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -3193622a3bea75c0eb55a4a03718890894305b7e +25020130cc7c734d56bc729cba1134f7de89cb7c diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js index b9512fdf8d5..204734515af 100644 --- a/app/assets/javascripts/environments/constants.js +++ b/app/assets/javascripts/environments/constants.js @@ -124,6 +124,12 @@ export const SYNC_STATUS_BADGES = { text: s__('Environment|Reconciling'), popoverText: s__('Deployment|Flux sync reconciling'), }, + suspended: { + variant: 'warning', + icon: 'status-paused', + text: __('Paused'), + popoverText: s__('Deployment|Flux sync is suspended'), + }, stalled: { variant: 'warning', icon: 'status-paused', diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue index 845d5950293..a6b69652150 100644 --- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue +++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue @@ -158,7 +158,10 @@ export default { return Object.values(this.failedState).some((item) => item); }, fluxResourceStatus() { - return this.fluxKustomization.conditions || this.fluxHelmRelease.conditions; + const conditions = this.fluxKustomization.conditions || this.fluxHelmRelease.conditions || []; + const spec = this.fluxKustomization.spec || this.fluxHelmRelease.spec || false; + + return { conditions, suspend: spec?.suspend }; }, fluxNamespace() { return ( @@ -185,7 +188,7 @@ export default { return { name: item.metadata.name, namespace: item.metadata.namespace, - status: fluxSyncStatus(item.status.conditions).status, + status: fluxSyncStatus({ conditions: item.status.conditions }).status, labels: item.metadata.labels, annotations: item.metadata.annotations, kind: item.kind, diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue index 8d422d30ff3..73af9a4c002 100644 --- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue +++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue @@ -60,9 +60,9 @@ export default { required: true, }, fluxResourceStatus: { - type: Array, + type: Object, required: false, - default: () => [], + default: () => {}, }, fluxNamespace: { type: String, @@ -144,6 +144,8 @@ export default { const fluxStatus = fluxSyncStatus(this.fluxResourceStatus); switch (fluxStatus.status) { + case 'suspended': + return SYNC_STATUS_BADGES.suspended; case 'failed': return { ...SYNC_STATUS_BADGES.failed, @@ -171,7 +173,7 @@ export default { return Boolean(this.fluxConnectionParams.resourceType); }, fluxResourcePresent() { - return Boolean(this.fluxResourceStatus?.length); + return Boolean(this.fluxResourceStatus?.conditions?.length); }, fluxBadgeHref() { return this.fluxResourcePresent ? '#' : null; diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue index 012d3cdeac8..497fdbf26a0 100644 --- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue +++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue @@ -72,7 +72,7 @@ export default { fluxKustomizationStatus() { if (!this.fluxKustomization.conditions?.length) return ''; - return fluxSyncStatus(this.fluxKustomization.conditions).status; + return fluxSyncStatus({ conditions: this.fluxKustomization.conditions }).status; }, fluxInventory() { return ( diff --git a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js index 576859fb9e2..b2659fae7e0 100644 --- a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js +++ b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js @@ -56,7 +56,12 @@ const fluxAnyFailed = (fluxConditions) => { }); }; -export const fluxSyncStatus = (fluxConditions) => { +export const fluxSyncStatus = (fluxResourceStatus) => { + const fluxConditions = fluxResourceStatus.conditions; + + if (fluxResourceStatus.suspend) { + return { status: 'suspended' }; + } if (fluxAnyFailed(fluxConditions)) { return { status: 'failed', message: fluxAnyFailed(fluxConditions).message }; } diff --git a/app/assets/javascripts/projects/new/components/deployment_target_select.vue b/app/assets/javascripts/projects/new/components/deployment_target_select.vue index 9c8581a0b1e..4f39cf8c58d 100644 --- a/app/assets/javascripts/projects/new/components/deployment_target_select.vue +++ b/app/assets/javascripts/projects/new/components/deployment_target_select.vue @@ -42,7 +42,7 @@ export default { }, computed: { isK8sOptionSelected() { - return this.selectedTarget === K8S_OPTION; + return this.selectedTarget === K8S_OPTION.value; }, }, mounted() { diff --git a/app/assets/javascripts/projects/new/constants.js b/app/assets/javascripts/projects/new/constants.js index 7b6b2cfc7ca..daecfecca96 100644 --- a/app/assets/javascripts/projects/new/constants.js +++ b/app/assets/javascripts/projects/new/constants.js @@ -1,22 +1,66 @@ import { s__ } from '~/locale'; -export const K8S_OPTION = s__('DeploymentTarget|Kubernetes (GKE, EKS, OpenShift, and so on)'); +export const K8S_OPTION = { + value: 'kubernetes', + text: s__('DeploymentTarget|Kubernetes (GKE, EKS, OpenShift, and so on)'), +}; export const DEPLOYMENT_TARGET_SELECTIONS = [ K8S_OPTION, - s__('DeploymentTarget|Managed container runtime (Fargate, Cloud Run, DigitalOcean App)'), - s__('DeploymentTarget|Self-managed container runtime (Podman, Docker Swarm, Docker Compose)'), - s__('DeploymentTarget|Heroku'), - s__('DeploymentTarget|Virtual machine (for example, EC2)'), - s__('DeploymentTarget|Mobile app store'), - s__('DeploymentTarget|Registry (package or container)'), - s__('DeploymentTarget|Infrastructure provider (Terraform, Cloudformation, and so on)'), - s__('DeploymentTarget|Serverless backend (Lambda, Cloud functions)'), - s__('DeploymentTarget|Edge Computing (e.g. Cloudflare Workers)'), - s__('DeploymentTarget|Web Deployment Platform (Netlify, Vercel, Gatsby)'), - s__('DeploymentTarget|GitLab Pages'), - s__('DeploymentTarget|Other hosting service'), - s__('DeploymentTarget|No deployment planned'), + { + value: 'managed_container_runtime', + text: s__('DeploymentTarget|Managed container runtime (Fargate, Cloud Run, DigitalOcean App)'), + }, + { + value: 'self_managed_container_runtime', + text: s__( + 'DeploymentTarget|Self-managed container runtime (Podman, Docker Swarm, Docker Compose)', + ), + }, + { + value: 'heroku', + text: s__('DeploymentTarget|Heroku'), + }, + { + value: 'virtual_machine', + text: s__('DeploymentTarget|Virtual machine (for example, EC2)'), + }, + { + value: 'mobile_app_store', + text: s__('DeploymentTarget|Mobile app store'), + }, + { + value: 'registry', + text: s__('DeploymentTarget|Registry (package or container)'), + }, + { + value: 'infrastructure_provider', + text: s__('DeploymentTarget|Infrastructure provider (Terraform, Cloudformation, and so on)'), + }, + { + value: 'serverless_backend', + text: s__('DeploymentTarget|Serverless backend (Lambda, Cloud functions)'), + }, + { + value: 'edge_computing', + text: s__('DeploymentTarget|Edge Computing (e.g. Cloudflare Workers)'), + }, + { + value: 'web_deployment_platform', + text: s__('DeploymentTarget|Web Deployment Platform (Netlify, Vercel, Gatsby)'), + }, + { + value: 'gitlab_pages', + text: s__('DeploymentTarget|GitLab Pages'), + }, + { + value: 'other_hosting_service', + text: s__('DeploymentTarget|Other hosting service'), + }, + { + value: 'no_deployment', + text: s__('DeploymentTarget|No deployment planned'), + }, ]; export const NEW_PROJECT_FORM = 'new_project'; diff --git a/app/assets/javascripts/repository/components/collapsible_commit_info.vue b/app/assets/javascripts/repository/components/collapsible_commit_info.vue new file mode 100644 index 00000000000..ab587ca40a0 --- /dev/null +++ b/app/assets/javascripts/repository/components/collapsible_commit_info.vue @@ -0,0 +1,147 @@ + + + diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue index 7afda86185f..35eea946851 100644 --- a/app/assets/javascripts/repository/components/last_commit.vue +++ b/app/assets/javascripts/repository/components/last_commit.vue @@ -12,10 +12,12 @@ import projectPathQuery from '../queries/project_path.query.graphql'; import eventHub from '../event_hub'; import { FORK_UPDATED_EVENT } from '../constants'; import CommitInfo from './commit_info.vue'; +import CollapsibleCommitInfo from './collapsible_commit_info.vue'; export default { components: { CommitInfo, + CollapsibleCommitInfo, ClipboardButton, SignatureBadge, CiIcon, @@ -114,34 +116,41 @@ export default { diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index bc5d63bfaec..33127a165f8 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -1,10 +1,8 @@ -import { GlButton } from '@gitlab/ui'; import Vue from 'vue'; // eslint-disable-next-line no-restricted-imports import Vuex from 'vuex'; import { parseBoolean } from '~/lib/utils/common_utils'; import { joinPaths, visitUrl } from '~/lib/utils/url_utility'; -import { __ } from '~/locale'; import initWebIdeLink from '~/pages/projects/shared/web_ide_link'; import PerformancePlugin from '~/performance/vue_performance_plugin'; import createStore from '~/code_navigation/store'; @@ -269,35 +267,6 @@ export default function setupVueRepositoryList() { }); } - const treeHistoryLinkEl = document.getElementById('js-tree-history-link'); - if (treeHistoryLinkEl) { - const { historyLink } = treeHistoryLinkEl.dataset; - // eslint-disable-next-line no-new - new Vue({ - el: treeHistoryLinkEl, - router, - render(h) { - const url = generateHistoryUrl( - historyLink, - this.$route.params.path, - this.$route.meta.refType || this.$route.query.ref_type, - ); - return h( - GlButton, - { - attrs: { - href: url.href, - // Ideally passing this class to `props` should work - // But it doesn't work here. :( - class: 'btn btn-default btn-md gl-button', - }, - }, - [__('History')], - ); - }, - }); - } - initWebIdeLink({ el: document.getElementById('js-tree-web-ide-link'), router }); // eslint-disable-next-line no-new diff --git a/app/models/namespace.rb b/app/models/namespace.rb index d8cb6a53d51..84264907dee 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -221,6 +221,7 @@ class Namespace < ApplicationRecord scope :without_deleted, -> { joins(:namespace_details).where(namespace_details: { deleted_at: nil }) } scope :user_namespaces, -> { where(type: Namespaces::UserNamespace.sti_name) } scope :group_namespaces, -> { where(type: Group.sti_name) } + scope :project_namespaces, -> { where(type: Namespaces::ProjectNamespace.sti_name) } scope :without_project_namespaces, -> { where(Namespace.arel_table[:type].not_eq(Namespaces::ProjectNamespace.sti_name)) } scope :sort_by_type, -> { order(arel_table[:type].asc.nulls_first) } scope :include_route, -> { includes(:route) } diff --git a/app/services/ml/increment_version_service.rb b/app/services/ml/increment_version_service.rb index ef3cbf5269b..6695fb14ed9 100644 --- a/app/services/ml/increment_version_service.rb +++ b/app/services/ml/increment_version_service.rb @@ -7,7 +7,7 @@ module Ml class IncrementVersionService def initialize(version, increment_type = nil) @version = version - @increment_type = increment_type || :major + @increment_type = increment_type || :patch @parsed_version = Packages::SemVer.parse(@version.to_s) raise "Version must be in a valid SemVer format" unless @parsed_version || @version.nil? diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml index a4923942298..6c36bdc9c36 100644 --- a/app/views/projects/_files.html.haml +++ b/app/views/projects/_files.html.haml @@ -10,7 +10,7 @@ #tree-holder.tree-holder.clearfix.js-per-page.gl-mt-5{ data: { blame_per_page: Gitlab::Git::BlamePagination::PAGINATION_PER_PAGE } } - if params[:common_repository_blob_header_app] == 'true' - #js-repository-blob-header-app{ data: { project_id: @project.id, ref: ref, ref_type: @ref_type.to_s, history_link: project_commits_path(@project, @ref), breadcrumbs: breadcrumb_data_attributes, project_root_path: project_path(@project), project_path: project.full_path, compare_path: compare_path, escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref) } } + #js-repository-blob-header-app{ data: { project_id: @project.id, ref: ref, ref_type: @ref_type.to_s, breadcrumbs: breadcrumb_data_attributes, project_root_path: project_path(@project), project_path: project.full_path, compare_path: compare_path, escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref) } } - else .nav-block.gl-flex.gl-flex-col.sm:gl-flex-row.gl-items-stretch @@ -19,10 +19,10 @@ - if project.forked? #js-fork-info{ data: vue_fork_divergence_data(project, ref) } - .info-well.gl-hidden.sm:gl-flex.project-last-commit.gl-flex-col.gl-mt-5 + .info-well.project-last-commit.gl-flex-col.gl-mt-5 #js-last-commit.gl-m-auto{ data: {ref_type: @ref_type.to_s, history_link: project_commits_path(@project, @ref)} } = gl_loading_icon(size: 'md') - if project.licensed_feature_available?(:code_owners) - #js-code-owners{ data: { branch: @ref, can_view_branch_rules: can_view_branch_rules?, branch_rules_path: branch_rules_path } } + #js-code-owners.gl-hidden.sm:gl-flex{ data: { branch: @ref, can_view_branch_rules: can_view_branch_rules?, branch_rules_path: branch_rules_path } } #js-tree-list{ data: vue_file_list_data(project, ref) } diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index 08dc61e5694..fea04813828 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -7,7 +7,7 @@ - if (readme = @repository.readme) && readme.rich_viewer .tree-holder.gl-mt-5 - if params[:common_repository_blob_header_app] == 'true' - #js-repository-blob-header-app{ data: { project_id: @project.id, ref: ref, ref_type: @ref_type.to_s, history_link: project_commits_path(@project, @ref), breadcrumbs: breadcrumb_data_attributes, project_root_path: project_path(@project), project_path: project.full_path, compare_path: compare_path, escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref) } } + #js-repository-blob-header-app{ data: { project_id: @project.id, ref: ref, ref_type: @ref_type.to_s, breadcrumbs: breadcrumb_data_attributes, project_root_path: project_path(@project), project_path: project.full_path, compare_path: compare_path, escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref) } } - else .nav-block.mt-0 diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 2b4d5c92658..8d353c4c4a7 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -10,9 +10,6 @@ = render_if_exists 'projects/tree/lock_link' = render 'projects/buttons/compare', project: @project, ref: @ref, root_ref: @repository&.root_ref - .gl-block.sm:gl-hidden.gl-w-full - #js-tree-history-link{ data: { history_link: project_commits_path(@project, @ref) } } - = render 'projects/find_file_link' = render 'shared/web_ide_button', blob: nil, css_classes: 'gl-w-full sm:gl-w-auto' diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb index 9a4d1a38086..b527b75ba83 100644 --- a/app/workers/prune_old_events_worker.rb +++ b/app/workers/prune_old_events_worker.rb @@ -13,11 +13,16 @@ class PruneOldEventsWorker # rubocop:disable Scalability/IdempotentWorker feature_category :user_profile DELETE_LIMIT = 10_000 + # Contribution calendar shows maximum 12 months of events, we retain 3 years for data integrity. + CUTOFF_DATE = (3.years + 1.day).freeze + + def self.pruning_enabled? + Feature.enabled?(:ops_prune_old_events, type: :ops) + end def perform - if Feature.enabled?(:ops_prune_old_events, type: :ops) - # Contribution calendar shows maximum 12 months of events, we retain 3 years for data integrity. - cutoff_date = (3.years + 1.day).ago + if self.class.pruning_enabled? + cutoff_date = CUTOFF_DATE.ago Event.unscoped.created_before(cutoff_date).delete_with_limit(DELETE_LIMIT) else diff --git a/config/feature_flags/development/handle_structured_gitaly_errors.yml b/config/feature_flags/development/handle_structured_gitaly_errors.yml deleted file mode 100644 index 26a8082dec4..00000000000 --- a/config/feature_flags/development/handle_structured_gitaly_errors.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: handle_structured_gitaly_errors -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128366 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/420865 -milestone: '16.3' -type: development -group: group::source code -default_enabled: false diff --git a/doc/ci/runners/img/project_runner_ip_address_v17_6.png b/doc/ci/runners/img/project_runner_ip_address_v17_6.png new file mode 100644 index 00000000000..f89c3466451 Binary files /dev/null and b/doc/ci/runners/img/project_runner_ip_address_v17_6.png differ diff --git a/doc/ci/runners/runners_scope.md b/doc/ci/runners/runners_scope.md index 631cc2ad830..9a8bc5aa02d 100644 --- a/doc/ci/runners/runners_scope.md +++ b/doc/ci/runners/runners_scope.md @@ -75,9 +75,10 @@ the authentication token is stored in the `config.toml`. ### Create an instance runner with a registration token (deprecated) WARNING: -The ability to pass a runner registration token, and support for certain configuration arguments was -[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) in GitLab 15.6 and will be removed in GitLab 18.0. Runner authentication tokens -should be used instead. For more information, see [Migrating to the new runner registration workflow](new_creation_workflow.md). +The option to pass a runner registration token and support for certain configuration arguments was +[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) in GitLab 15.6. They are scheduled for removal +in GitLab 18.0. Use runner authentication tokens instead. For more information, see +[Migrating to the new runner registration workflow](new_creation_workflow.md). Prerequisites: @@ -172,7 +173,7 @@ To disable instance runners for a project: Instance runners are automatically disabled for a project: - If the instance runners setting for the parent group is disabled, and -- If overriding this setting is not permitted at the project level. +- If overriding this setting is not permitted for projects. ### Disable instance runners for a group @@ -274,9 +275,10 @@ The runner authentication token displays in the UI for only a short period of ti > - Path changed from **Settings > CI/CD > Runners**. WARNING: -The ability to pass a runner registration token, and support for certain configuration arguments was -[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) in GitLab 15.6 and will be removed in GitLab 18.0. Authentication tokens -should be used instead. For more information, see [Migrating to the new runner registration workflow](new_creation_workflow.md). +The option to pass a runner registration token and support for certain configuration arguments was +[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) in GitLab 15.6. They are scheduled for removal +in GitLab 18.0. Use runner authentication tokens instead. For more information, see +[Migrating to the new runner registration workflow](new_creation_workflow.md). Prerequisites: @@ -476,9 +478,10 @@ The runner authentication token displays in the UI for only a short period of ti ### Create a project runner with a registration token (deprecated) WARNING: -The ability to pass a runner registration token, and support for certain configuration arguments was -[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) in GitLab 15.6 and will be removed in GitLab 18.0. Runner authentication tokens -should be used instead. For more information, see [Migrating to the new runner registration workflow](new_creation_workflow.md). +The option to pass a runner registration token and support for certain configuration arguments was +[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) in GitLab 15.6. They are scheduled for removal +in GitLab 18.0. Use runner authentication tokens instead. For more information, see +[Migrating to the new runner registration workflow](new_creation_workflow.md). Prerequisites: @@ -587,7 +590,7 @@ A runner can have one of the following statuses. | Status | Description | |---------|-------------| -| `online` | The runner has contacted GitLab within the last 2 hours and is available to run jobs. | +| `online` | The runner has contacted GitLab in the last 2 hours and is available to run jobs. | | `offline` | The runner has not contacted GitLab in more than 2 hours and is not available to run jobs. Check the runner to see if you can bring it online. | | `stale` | The runner has not contacted GitLab in more than 7 days. If the runner was created more than 7 days ago, but it never contacted the instance, it is also considered **stale**. | | `never_contacted` | The runner has never contacted GitLab. To make the runner contact GitLab, run `gitlab-runner run`. | @@ -606,8 +609,8 @@ The **Median job queued time** value is calculated by sampling the queue duratio most recent 100 jobs that were run by Instance runners. Jobs from only the latest 5000 runners are considered. -The median is a value that falls into the 50th percentile: half of the jobs -queued for longer than the median value, and half of the jobs queued for less than the +The median is a value that falls into the 50th percentile. Half of the jobs +queue longer than the median value, and half queue for less time than the median value. To view runner statistics: @@ -646,11 +649,10 @@ To determine which runners need to be upgraded: ## Determine the IP address of a runner -It may be useful to know the IP address of a runner so you can troubleshoot -issues with that runner. GitLab stores and displays the IP address by viewing -the source of the HTTP requests it makes to GitLab when polling for jobs. The -IP address is always kept up to date so if the runner IP changes it -automatically updates in GitLab. +To troubleshoot runner issues, you might need to know the runner's IP address. +GitLab stores and displays the IP address by viewing the source of the +HTTP requests when the runner polls for jobs. +GitLab automatically updates the runner's IP address whenever it is updated. The IP address for instance runners and project runners can be found in different places. @@ -678,24 +680,26 @@ project. 1. Go to the project's **Settings > CI/CD** and expand the **Runners** section. 1. Select the runner name and find the **IP Address** row. -![Project runner IP address](img/project_runner_ip_address_v10_7.png) +![Project runner IP address](img/project_runner_ip_address_v17_6.png) ## Enable use of runner registration tokens in projects and groups > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148557) in GitLab 16.11 WARNING: -The ability to pass a runner registration token, and support for certain configuration arguments was deprecated in GitLab 15.6 and will be removed in GitLab 18.0. Runner authentication tokens should be used instead. For more information, see [Migrating to the new runner registration workflow](../../ci/runners/new_creation_workflow.md). +The option to pass a runner registration token and support for certain configuration arguments was +[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) in GitLab 15.6. They are scheduled for removal +in GitLab 18.0. Use runner authentication tokens instead. For more information, see +[Migrating to the new runner registration workflow](new_creation_workflow.md). -In GitLab 17.0, the use of runner registration tokens to create runners will be disabled in all GitLab instances. -Users must use runner authentication tokens instead. -If you have not yet [migrated to the use of runner authentication tokens](../../ci/runners/new_creation_workflow.md), -you can enable runner registration tokens for projects and groups. This setting and support for runner registration tokens will be removed in GitLab 18.0. +In GitLab 17.0, the use of runner registration tokens is disabled in all GitLab instances. Prerequisites: - Runner registration tokens must be [enabled](../../administration/settings/continuous_integration.md#allow-runner-registrations-tokens) in the **Admin** area. +To enable the use of runner registration token in project and groups: + 1. On the left sidebar, select **Search or go to** and find your group. 1. Select **Settings > CI/CD**. 1. Expand **Runners**. diff --git a/doc/user/analytics/dora_metrics.md b/doc/user/analytics/dora_metrics.md index 639415cb9b4..85799302ac9 100644 --- a/doc/user/analytics/dora_metrics.md +++ b/doc/user/analytics/dora_metrics.md @@ -156,8 +156,8 @@ DETAILS: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96561) in GitLab 15.4 [with a flag](../../administration/feature_flags.md) named `dora_configuration`. Disabled by default. This feature is an [experiment](../../policy/development_stages_support.md). FLAG: -On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, an administrator can [enable the feature flag](../../administration/feature_flags.md) named `dora_configuration`. -On GitLab.com and GitLab Dedicated, this feature is not available. +The availability of this feature is controlled by a feature flag. +For more information, see the history. This feature is an [experiment](../../policy/development_stages_support.md). To join the list of users testing this feature, [here is a suggested test flow](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96561#steps-to-check-on-localhost). diff --git a/doc/user/enterprise_user/index.md b/doc/user/enterprise_user/index.md index 00b86eefd31..a9073bc57b4 100644 --- a/doc/user/enterprise_user/index.md +++ b/doc/user/enterprise_user/index.md @@ -194,8 +194,12 @@ To disable 2FA: ### Enable the extension marketplace for the Web IDE and workspaces +DETAILS: +**Status:** Beta + > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161819) as a [beta](../../policy/development_stages_support.md#beta) in GitLab 17.0 [with flags](../../administration/feature_flags.md) named `web_ide_oauth` and `web_ide_extensions_marketplace`. Disabled by default. -> - Feature flag `web_ide_oauth` [enabled on GitLab.com, self-managed, and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163181) and feature flag `web_ide_extensions_marketplace` [enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/459028) in GitLab 17.4. +> - Feature flag `web_ide_oauth` [enabled on GitLab.com, self-managed, and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163181) in GitLab 17.4. +> - Feature flag `web_ide_extensions_marketplace` [enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/459028) in GitLab 17.4. > - Feature flag `web_ide_oauth` [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167464) in GitLab 17.5. FLAG: diff --git a/doc/user/okrs.md b/doc/user/okrs.md index 03b7b0829d8..f2a834309c8 100644 --- a/doc/user/okrs.md +++ b/doc/user/okrs.md @@ -16,10 +16,9 @@ OKRs are an [experiment](../policy/development_stages_support.md#experiment). For the OKR feature roadmap, see [epic 7864](https://gitlab.com/groups/gitlab-org/-/epics/7864). FLAG: -On self-managed GitLab, by default this feature is not available. To make it available per project, an administrator can [enable the featured flag](../administration/feature_flags.md) named `okrs_mvc`. -On GitLab.com, by default this feature is not available, but can be configured by GitLab.com administrators. -On GitLab Dedicated, this feature is not available. -This feature is not ready for production use. +The availability of this feature is controlled by a feature flag. +For more information, see the history. +This feature is available for testing, but not ready for production use. [Objectives and key results](https://en.wikipedia.org/wiki/OKR) (OKRs) are a framework for setting and tracking goals that are aligned with your organization's overall strategy and vision. @@ -113,6 +112,7 @@ To edit an OKR: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378949) in GitLab 15.7 [with a flag](../administration/feature_flags.md) named `work_items_mvc_2`. Disabled by default. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/378949) to feature flag named `work_items_mvc` in GitLab 15.8. Disabled by default. +> - Feature flag [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144141) from `work_items_mvc` to `work_items_beta` in GitLab 16.10. > - Changing activity sort order [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378949) in GitLab 15.8. > - Filtering activity [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/389971) in GitLab 15.10. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/334812) in GitLab 15.10. @@ -382,9 +382,9 @@ To reorder them, drag them around. > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. 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 `okr_checkin_reminders`. -On GitLab.com and GitLab Dedicated, this feature is not available. -This feature is not ready for production use. +The availability of this feature is controlled by a feature flag. +For more information, see the history. +This feature is available for testing, but not ready for production use. Schedule check-in reminders to remind your team to provide status updates on the key results you care about. @@ -525,9 +525,9 @@ system note in the OKR's comments, for example: > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. 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 `work_items_beta`. -On GitLab.com and GitLab Dedicated, this feature is not available. -This feature is not ready for production use. +The availability of this feature is controlled by a feature flag. +For more information, see the history. +This feature is available for testing, but not ready for production use. You can prevent public comments in an OKR. When you do, only project members can add and edit comments. diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index e0f331d1c81..70651b018e4 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -96,10 +96,6 @@ module API params ] end - - def rescue_not_found? - Feature.disabled?(:handle_structured_gitaly_errors) - end end desc 'Get a project repository tree' do @@ -128,7 +124,7 @@ module API end end get ':id/repository/tree', urgency: :low do - tree_finder = ::Repositories::TreeFinder.new(user_project, declared_params(include_missing: false).merge(rescue_not_found: rescue_not_found?)) + tree_finder = ::Repositories::TreeFinder.new(user_project, declared_params(include_missing: false).merge(rescue_not_found: false)) not_found!("Tree") unless tree_finder.commit_exists? diff --git a/lib/gitlab/github_import/importer/events/base_importer.rb b/lib/gitlab/github_import/importer/events/base_importer.rb index 534e6adcbb7..95a1b96ec51 100644 --- a/lib/gitlab/github_import/importer/events/base_importer.rb +++ b/lib/gitlab/github_import/importer/events/base_importer.rb @@ -42,6 +42,12 @@ module Gitlab issue_event.issuable_type == MergeRequest.name end + # `PruneOldEventsWorker` deletes Event records older than a cutoff date. + # Before importing Events, check if they would be pruned. + def event_outside_cutoff?(issue_event) + issue_event.created_at < PruneOldEventsWorker::CUTOFF_DATE.ago && PruneOldEventsWorker.pruning_enabled? + end + def resource_event_belongs_to(issue_event) belongs_to_key = merge_request_event?(issue_event) ? :merge_request_id : :issue_id { belongs_to_key => issuable_db_id(issue_event) } diff --git a/lib/gitlab/github_import/importer/events/closed.rb b/lib/gitlab/github_import/importer/events/closed.rb index f99d8e58e23..3a757728a57 100644 --- a/lib/gitlab/github_import/importer/events/closed.rb +++ b/lib/gitlab/github_import/importer/events/closed.rb @@ -13,6 +13,8 @@ module Gitlab private def create_event(issue_event) + return if event_outside_cutoff?(issue_event) + created_event = Event.create!( project_id: project.id, author_id: author_id(issue_event), diff --git a/lib/gitlab/github_import/importer/events/merged.rb b/lib/gitlab/github_import/importer/events/merged.rb index bc04ab4cd48..9522dcfbcfa 100644 --- a/lib/gitlab/github_import/importer/events/merged.rb +++ b/lib/gitlab/github_import/importer/events/merged.rb @@ -14,6 +14,8 @@ module Gitlab private def create_event(issue_event) + return if event_outside_cutoff?(issue_event) + event = Event.create!( project_id: project.id, author_id: author_id(issue_event), diff --git a/lib/gitlab/github_import/importer/events/reopened.rb b/lib/gitlab/github_import/importer/events/reopened.rb index 5721b2dc5e5..060f509958c 100644 --- a/lib/gitlab/github_import/importer/events/reopened.rb +++ b/lib/gitlab/github_import/importer/events/reopened.rb @@ -13,6 +13,8 @@ module Gitlab private def create_event(issue_event) + return if event_outside_cutoff?(issue_event) + created_event = Event.create!( project_id: project.id, author_id: author_id(issue_event), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index bcfa667331a..2dc61e496f1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18951,6 +18951,9 @@ msgstr "" msgid "Deployment|Flux sync failed" msgstr "" +msgid "Deployment|Flux sync is suspended" +msgstr "" + msgid "Deployment|Flux sync reconciled successfully" msgstr "" diff --git a/rubocop/cop/migration/create_table_with_foreign_keys.rb b/rubocop/cop/migration/create_table_with_foreign_keys.rb index e3039ac76a9..093e054950e 100644 --- a/rubocop/cop/migration/create_table_with_foreign_keys.rb +++ b/rubocop/cop/migration/create_table_with_foreign_keys.rb @@ -9,7 +9,8 @@ module RuboCop include MigrationHelpers MSG = 'Creating a table with more than one foreign key at once violates our migration style guide. ' \ - 'For more details check the https://docs.gitlab.com/ee/development/migration_style_guide.html#examples' + 'For more details check the ' \ + 'https://docs.gitlab.com/ee/development/migration_style_guide.html#creating-a-new-table-when-we-have-two-foreign-keys' def_node_matcher :create_table_with_block?, <<~PATTERN (block diff --git a/scripts/frontend/quarantined_vue3_specs.txt b/scripts/frontend/quarantined_vue3_specs.txt index 2a53bcd9842..3303e4d1c89 100644 --- a/scripts/frontend/quarantined_vue3_specs.txt +++ b/scripts/frontend/quarantined_vue3_specs.txt @@ -170,7 +170,6 @@ spec/frontend/ci/pipelines_page/components/pipeline_multi_actions_spec.js spec/frontend/ci/pipelines_page/components/pipelines_artifacts_spec.js spec/frontend/ci/runner/components/runner_details_spec.js spec/frontend/ci/runner/components/runner_form_fields_spec.js -spec/frontend/ci/runner/components/runner_managers_table_spec.js spec/frontend/ci/runner/components/runner_platforms_radio_spec.js spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js spec/frontend/ci_settings_pipeline_triggers/components/edit_trigger_modal_spec.js diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index 8b611a0f8ed..59bf3922865 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -61,7 +61,9 @@ RSpec.describe "User browses files", :js, feature_category: :source_code_managem click_link("README.md") end - click_link("History") + page.within(".commit-actions") do + click_link("History") + end history_path = project_commits_path(project, "master/README.md") expect(page).to have_current_path(history_path) diff --git a/spec/features/projects/settings/secure_files_spec.rb b/spec/features/projects/settings/secure_files_spec.rb index 13a001de286..c9412824a1c 100644 --- a/spec/features/projects/settings/secure_files_spec.rb +++ b/spec/features/projects/settings/secure_files_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Secure Files', :js, feature_category: :source_code_management do +RSpec.describe 'Secure Files', :js, feature_category: :secrets_management do let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js index 96e8d3a2a45..909a165ef36 100644 --- a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js +++ b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js @@ -162,7 +162,7 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov namespace: kubernetesNamespace, resourceType: k8sResourceType.k8sPods, fluxApiError: '', - fluxResourceStatus: [], + fluxResourceStatus: { conditions: [] }, }); }); @@ -240,7 +240,7 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov }); it('provides empty `fluxResourceStatus` to KubernetesStatusBar', () => { - expect(findKubernetesStatusBar().props('fluxResourceStatus')).toEqual([]); + expect(findKubernetesStatusBar().props('fluxResourceStatus')).toEqual({ conditions: [] }); }); it('provides empty `fluxKustomization` to KubernetesTabs', () => { @@ -305,9 +305,9 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov await waitForPromises(); }); it('provides correct `fluxResourceStatus` to KubernetesStatusBar', () => { - expect(findKubernetesStatusBar().props('fluxResourceStatus')).toEqual( - fluxResourceStatus, - ); + expect(findKubernetesStatusBar().props('fluxResourceStatus')).toEqual({ + conditions: fluxResourceStatus, + }); }); it('provides correct `fluxNamespace` to KubernetesStatusBar', () => { diff --git a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_status_bar_spec.js b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_status_bar_spec.js index c36f3d93927..643939f20e1 100644 --- a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_status_bar_spec.js +++ b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_status_bar_spec.js @@ -43,7 +43,7 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_st const createWrapper = ({ clusterHealthStatus = '', fluxResourcePath = '', - fluxResourceStatus = [], + fluxResourceStatus = { conditions: [] }, fluxApiError = '', namespace = kubernetesNamespace, resourceType = k8sResourceType.k8sPods, @@ -189,14 +189,16 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_st 'renders sync status as $statusText when status is $status, type is $type, and reason is $reason', ({ status, type, reason, statusText, statusPopover }) => { createWrapper({ - fluxResourceStatus: [ - { - status, - type, - reason, - message, - }, - ], + fluxResourceStatus: { + conditions: [ + { + status, + type, + reason, + message, + }, + ], + }, }); expect(findSyncBadge().text()).toBe(statusText); @@ -206,7 +208,7 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_st it('renders a clickable badge', () => { createWrapper({ - fluxResourceStatus: [{ status: 'True', type: 'Ready' }], + fluxResourceStatus: { conditions: [{ status: 'True', type: 'Ready' }] }, }); expect(findSyncBadge().attributes('href')).toBe('#'); @@ -214,7 +216,7 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_st it('emits `show-flux-resource-details` event when badge is clicked', () => { createWrapper({ - fluxResourceStatus: [{ status: 'True', type: 'Ready' }], + fluxResourceStatus: { conditions: [{ status: 'True', type: 'Ready' }] }, }); findSyncBadge().trigger('click'); diff --git a/spec/frontend/environments/helpers/k8s_integration_helper_spec.js b/spec/frontend/environments/helpers/k8s_integration_helper_spec.js index 4d1d5224bed..02f4cb823f5 100644 --- a/spec/frontend/environments/helpers/k8s_integration_helper_spec.js +++ b/spec/frontend/environments/helpers/k8s_integration_helper_spec.js @@ -67,16 +67,17 @@ describe('k8s_integration_helper', () => { let fluxConditions; it.each` - status | type | reason | statusText | statusMessage - ${STATUS_TRUE} | ${'Stalled'} | ${''} | ${'stalled'} | ${{ message }} - ${STATUS_TRUE} | ${'Reconciling'} | ${''} | ${'reconciling'} | ${''} - ${STATUS_UNKNOWN} | ${'Ready'} | ${REASON_PROGRESSING} | ${'reconcilingWithBadConfig'} | ${{ message }} - ${STATUS_TRUE} | ${'Ready'} | ${''} | ${'reconciled'} | ${''} - ${STATUS_FALSE} | ${'Ready'} | ${''} | ${'failed'} | ${{ message }} - ${STATUS_UNKNOWN} | ${'Ready'} | ${''} | ${'unknown'} | ${''} + suspended | status | type | reason | statusText | statusMessage + ${false} | ${STATUS_TRUE} | ${'Stalled'} | ${''} | ${'stalled'} | ${{ message }} + ${false} | ${STATUS_TRUE} | ${'Reconciling'} | ${''} | ${'reconciling'} | ${''} + ${false} | ${STATUS_UNKNOWN} | ${'Ready'} | ${REASON_PROGRESSING} | ${'reconcilingWithBadConfig'} | ${{ message }} + ${false} | ${STATUS_TRUE} | ${'Ready'} | ${''} | ${'reconciled'} | ${''} + ${false} | ${STATUS_FALSE} | ${'Ready'} | ${''} | ${'failed'} | ${''} + ${true} | ${STATUS_FALSE} | ${'Ready'} | ${''} | ${'suspended'} | ${''} + ${false} | ${STATUS_UNKNOWN} | ${'Ready'} | ${''} | ${'unknown'} | ${''} `( 'renders sync status as $statusText when status is $status, type is $type, and reason is $reason', - ({ status, type, reason, statusText, statusMessage }) => { + ({ suspended, status, type, reason, statusText, statusMessage }) => { fluxConditions = [ { status, @@ -86,7 +87,9 @@ describe('k8s_integration_helper', () => { }, ]; - expect(fluxSyncStatus(fluxConditions)).toMatchObject({ + const fluxResourceStatus = { conditions: fluxConditions, suspend: suspended }; + + expect(fluxSyncStatus(fluxResourceStatus)).toMatchObject({ status: statusText, ...statusMessage, }); diff --git a/spec/frontend/projects/new/components/deployment_target_select_spec.js b/spec/frontend/projects/new/components/deployment_target_select_spec.js index 9f6d1c45c6a..d5fac294ca4 100644 --- a/spec/frontend/projects/new/components/deployment_target_select_spec.js +++ b/spec/frontend/projects/new/components/deployment_target_select_spec.js @@ -60,10 +60,10 @@ describe('Deployment target select', () => { }); describe.each` - selectedTarget | formSubmitted | eventSent - ${null} | ${true} | ${false} - ${DEPLOYMENT_TARGET_SELECTIONS[0]} | ${false} | ${false} - ${DEPLOYMENT_TARGET_SELECTIONS[0]} | ${true} | ${true} + selectedTarget | formSubmitted | eventSent + ${null} | ${true} | ${false} + ${DEPLOYMENT_TARGET_SELECTIONS[0].value} | ${false} | ${false} + ${DEPLOYMENT_TARGET_SELECTIONS[0].value} | ${true} | ${true} `('Snowplow tracking event', ({ selectedTarget, formSubmitted, eventSent }) => { beforeEach(() => { findSelect().vm.$emit('input', selectedTarget); @@ -89,10 +89,10 @@ describe('Deployment target select', () => { }); describe.each` - selectedTarget | isTextShown - ${null} | ${false} - ${DEPLOYMENT_TARGET_SELECTIONS[0]} | ${true} - ${DEPLOYMENT_TARGET_SELECTIONS[1]} | ${false} + selectedTarget | isTextShown + ${null} | ${false} + ${DEPLOYMENT_TARGET_SELECTIONS[0].value} | ${true} + ${DEPLOYMENT_TARGET_SELECTIONS[1].value} | ${false} `('K8s education text', ({ selectedTarget, isTextShown }) => { beforeEach(() => { findSelect().vm.$emit('input', selectedTarget); @@ -105,7 +105,7 @@ describe('Deployment target select', () => { describe('when user clicks on the docs link', () => { beforeEach(async () => { - findSelect().vm.$emit('input', K8S_OPTION); + findSelect().vm.$emit('input', K8S_OPTION.value); await nextTick(); findLink().trigger('click'); diff --git a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap index 49f25ac69cc..23650093bbe 100644 --- a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap +++ b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap @@ -1,60 +1,68 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Repository last commit component renders commit widget 1`] = ` - -
+
- -
- +
+ +
+ + + 12345678 + + + - 12345678 + History - -
- - History - -
-
+ + + + `; diff --git a/spec/frontend/repository/components/collapsible_commit_info_spec.js b/spec/frontend/repository/components/collapsible_commit_info_spec.js new file mode 100644 index 00000000000..f9fca7b7ddd --- /dev/null +++ b/spec/frontend/repository/components/collapsible_commit_info_spec.js @@ -0,0 +1,142 @@ +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import CollapsibleCommitInfo from '~/repository/components/collapsible_commit_info.vue'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; +import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; + +describe('CollapsibleCommitInfo', () => { + let wrapper; + const defaultProps = { + sha: '1234567890abcdef', + authorName: 'John Doe', + authoredDate: '2023-06-01T12:00:00Z', + titleHtml: 'Initial commit', + message: 'Commit message', + descriptionHtml: ' This is a commit description', + webPath: '/commit/1234567890abcdef', + author: { + webPath: '/users/johndoe', + avatarUrl: 'https://example.com/avatar.jpg', + name: 'John Doe', + }, + }; + + const createComponent = async (props = {}) => { + wrapper = shallowMountExtended(CollapsibleCommitInfo, { + propsData: { commit: { ...defaultProps, ...props }, historyUrl: '/project/history' }, + }); + await nextTick(); + }; + + beforeEach(() => { + createComponent(); + }); + + const findCommitterAvatar = () => wrapper.findComponent(UserAvatarLink); + const findDefaultAvatar = () => wrapper.findComponent(UserAvatarImage); + const findCommitTimeAgo = () => wrapper.findComponent(TimeagoTooltip); + const findCommitId = () => wrapper.findByText('12345678'); + const findHistoryButton = () => wrapper.findByTestId('collapsible-commit-history'); + const findTextExpander = () => wrapper.findByTestId('text-expander'); + const findCommitRowDescription = () => wrapper.find('pre'); + const findCommitTitle = () => wrapper.findByText('Initial commit'); + const findCommitterName = () => wrapper.findByText('John Doe'); + + describe('renders user avatar correctly', () => { + it('renders avatar link if user has a custom profile photo', () => { + expect(findCommitterAvatar().exists()).toBe(true); + expect(findDefaultAvatar().exists()).toBe(false); + expect(findCommitterAvatar().props()).toStrictEqual({ + imgAlt: "John Doe's avatar", + imgCssClasses: '', + imgCssWrapperClasses: '', + imgSize: 32, + imgSrc: 'https://example.com/avatar.jpg', + lazy: false, + linkHref: '/users/johndoe', + popoverUserId: '', + popoverUsername: '', + tooltipPlacement: 'top', + tooltipText: '', + username: '', + }); + }); + + it('renders default avatar image if user does not have a custom profile photo', () => { + createComponent({ + sha: '1234567890abcdef', + authorName: 'John Doe', + author: null, + authoredDate: '2023-06-01T12:00:00Z', + titleHtml: 'Initial commit', + descriptionHtml: ' This is a commit description', + webPath: '/commit/1234567890abcdef', + }); + expect(findCommitterAvatar().exists()).toBe(false); + expect(findDefaultAvatar().exists()).toBe(true); + expect(findDefaultAvatar().props()).toStrictEqual({ + cssClasses: '', + imgAlt: 'user avatar', + imgSrc: 'file-mock', + pseudo: false, + size: 32, + lazy: false, + tooltipPlacement: 'top', + tooltipText: '', + }); + }); + }); + + it('renders commit details correctly', () => { + expect(findCommitTimeAgo().props().time).toBe('2023-06-01T12:00:00Z'); + expect(findCommitId().exists()).toBe(true); + expect(findHistoryButton().exists()).toBe(true); + expect(findHistoryButton().attributes('href')).toBe('/project/history'); + }); + + describe('text expander', () => { + it('renders commit details collapsed by default', () => { + expect(findTextExpander().exists()).toBe(true); + expect(findCommitTitle().exists()).toBe(false); + expect(findCommitterName().exists()).toBe(false); + expect(findCommitRowDescription().exists()).toBe(false); + }); + + it('shows commit details when clicking the expander button', async () => { + await findTextExpander().vm.$emit('click'); + await nextTick(); + + expect(findTextExpander().classes('open')).toBe(true); + expect(findTextExpander().props('selected')).toBe(true); + expect(findCommitTitle().exists()).toBe(true); + expect(findCommitterName().exists()).toBe(true); + expect(findCommitRowDescription().html()).toBe( + '
This is a commit description
', + ); + }); + + it('sets correct CSS class if the commit message is empty', async () => { + createComponent({ message: '' }); + await findTextExpander().vm.$emit('click'); + await nextTick(); + expect(findCommitTitle().classes()).toContain('gl-italic'); + }); + + it('does not render description when description is null', async () => { + createComponent({ + sha: '1234567890abcdef', + authorName: 'John Doe', + authoredDate: '2023-06-01T12:00:00Z', + titleHtml: 'Initial commit', + descriptionHtml: null, + webPath: '/commit/1234567890abcdef', + }); + + await findTextExpander().vm.$emit('click'); + await nextTick(); + + expect(findCommitRowDescription().exists()).toBe(false); + }); + }); +}); diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 8efdd08df0a..e6374dd28c5 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -41,14 +41,8 @@ RSpec.describe Gitlab::Git::Tree, feature_category: :source_code_management do context 'with an invalid ref' do let(:sha) { 'foobar-does-not-exist' } - context 'when handle_structured_gitaly_errors feature is disabled' do - before do - stub_feature_flags(handle_structured_gitaly_errors: false) - end - - it { expect(entries).to eq([]) } - it { expect(cursor).to be_nil } - end + it { expect(entries).to eq([]) } + it { expect(cursor).to be_nil } end context 'when path is provided' do diff --git a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb index 820cf3e279e..c391523db7c 100644 --- a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb +++ b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb @@ -11,6 +11,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed, feature_category: let(:client) { instance_double('Gitlab::GithubImport::Client') } let(:issuable) { create(:issue, project: project) } let(:commit_id) { nil } + let(:created_at) { 1.month.ago } let(:issue_event) do Gitlab::GithubImport::Representation::IssueEvent.from_json_hash( @@ -19,7 +20,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed, feature_category: 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486', 'actor' => { 'id' => user.id, 'login' => user.username }, 'event' => 'closed', - 'created_at' => '2022-04-26 18:30:53 UTC', + 'created_at' => created_at.iso8601, 'commit_id' => commit_id, 'issue' => { 'number' => issuable.iid, pull_request: issuable.is_a?(MergeRequest) } ) @@ -57,6 +58,29 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed, feature_category: .to include expected_state_event_attrs end + context 'when event is outside the cutoff date and would be pruned' do + let(:created_at) { (PruneOldEventsWorker::CUTOFF_DATE + 1.minute).ago } + + it 'does not create the event, but does create the state event' do + importer.execute(issue_event) + + expect(issuable.events.count).to eq 0 + expect(issuable.resource_state_events.count).to eq 1 + end + + context 'when pruning events is disabled' do + before do + stub_feature_flags(ops_prune_old_events: false) + end + + it 'creates the event' do + importer.execute(issue_event) + + expect(issuable.events.count).to eq 1 + end + end + end + context 'when closed by commit' do let!(:closing_commit) { create(:commit, project: project) } let(:commit_id) { closing_commit.id } diff --git a/spec/lib/gitlab/github_import/importer/events/merged_spec.rb b/spec/lib/gitlab/github_import/importer/events/merged_spec.rb index 1e1b19c7fc3..15e37599a89 100644 --- a/spec/lib/gitlab/github_import/importer/events/merged_spec.rb +++ b/spec/lib/gitlab/github_import/importer/events/merged_spec.rb @@ -11,6 +11,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Merged, feature_category: let(:client) { instance_double('Gitlab::GithubImport::Client') } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:commit_id) { nil } + let(:created_at) { 1.month.ago } let(:issue_event) do Gitlab::GithubImport::Representation::IssueEvent.from_json_hash( @@ -19,7 +20,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Merged, feature_category: 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486', 'actor' => { 'id' => user.id, 'login' => user.username }, 'event' => 'merged', - 'created_at' => '2022-04-26 18:30:53 UTC', + 'created_at' => created_at.iso8601, 'commit_id' => commit_id, 'issue' => { 'number' => merge_request.iid, pull_request: true } ) @@ -106,6 +107,29 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Merged, feature_category: ) end + context 'when event is outside the cutoff date and would be pruned' do + let(:created_at) { (PruneOldEventsWorker::CUTOFF_DATE + 1.minute).ago } + + it 'does not create the event, but does create the state event' do + importer.execute(issue_event) + + expect(merge_request.events.count).to eq 0 + expect(merge_request.resource_state_events.count).to eq 1 + end + + context 'when pruning events is disabled' do + before do + stub_feature_flags(ops_prune_old_events: false) + end + + it 'creates the event' do + importer.execute(issue_event) + + expect(merge_request.events.count).to eq 1 + end + end + end + context 'when commit ID is present' do let!(:commit) { create(:commit, project: project) } let(:commit_id) { commit.id } diff --git a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb index a887699622f..5441d05c8dd 100644 --- a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb +++ b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb @@ -10,6 +10,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail let(:client) { instance_double('Gitlab::GithubImport::Client') } let(:issuable) { create(:issue, project: project) } + let(:created_at) { 1.month.ago } let(:issue_event) do Gitlab::GithubImport::Representation::IssueEvent.from_json_hash( @@ -18,7 +19,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486', 'actor' => { 'id' => user.id, 'login' => user.username }, 'event' => 'reopened', - 'created_at' => '2022-04-26 18:30:53 UTC', + 'created_at' => created_at.iso8601, 'issue' => { 'number' => issuable.iid, pull_request: issuable.is_a?(MergeRequest) } ) end @@ -59,6 +60,29 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail end end + context 'when event is outside the cutoff date and would be pruned' do + let(:created_at) { (PruneOldEventsWorker::CUTOFF_DATE + 1.minute).ago } + + it 'does not create the event, but does create the state event' do + importer.execute(issue_event) + + expect(issuable.events.count).to eq 0 + expect(issuable.resource_state_events.count).to eq 1 + end + + context 'when pruning events is disabled' do + before do + stub_feature_flags(ops_prune_old_events: false) + end + + it 'creates the event' do + importer.execute(issue_event) + + expect(issuable.events.count).to eq 1 + end + end + end + shared_examples 'push placeholder references' do it 'pushes the references' do expect(subject) diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 1cc9c9ad16e..d0bf0b0c00c 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -620,6 +620,19 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do end end + describe '.project_namespaces' do + let_it_be(:user_namespace) { create(:user_namespace) } + let_it_be(:project) { create(:project) } + let_it_be(:project_namespace) { project.project_namespace } + let_it_be(:group_namespace) { create(:group) } + + it 'only returns project namespaces' do + project_namespaces = described_class.project_namespaces + expect(project_namespaces).to include(project_namespace) + expect(project_namespaces).not_to include(group_namespace, user_namespace) + end + end + describe '.without_project_namespaces' do let_it_be(:user_namespace) { create(:user_namespace) } let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index eeec669ee80..24e72995716 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -40,46 +40,9 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do context 'when path does not exist' do let(:path) { 'bogus' } - context 'when handle_structured_gitaly_errors feature is disabled' do - before do - stub_feature_flags(handle_structured_gitaly_errors: false) - end - - it 'returns an empty array' do - get api("#{route}?path=#{path}", current_user) - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to include_pagination_headers - expect(json_response).to be_an(Array) - expect(json_response).to be_an_empty - end - end - - context 'when handle_structured_gitaly_errors feature is enabled' do - before do - stub_feature_flags(handle_structured_gitaly_errors: true) - end - - it_behaves_like '404 response' do - let(:request) { get api("#{route}?path=#{path}", current_user) } - let(:message) { '404 invalid revision or path Not Found' } - end - end - end - - context 'when path is empty directory ' do - context 'when handle_structured_gitaly_errors feature is disabled' do - before do - stub_feature_flags(handle_structured_gitaly_errors: false) - end - - it 'returns an empty array' do - get api(route, current_user) - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to include_pagination_headers - expect(json_response).to be_an(Array) - end + it_behaves_like '404 response' do + let(:request) { get api("#{route}?path=#{path}", current_user) } + let(:message) { '404 invalid revision or path Not Found' } end end diff --git a/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb b/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb index feea5cb3958..95b41b0a860 100644 --- a/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb +++ b/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb @@ -78,7 +78,7 @@ RSpec.describe RuboCop::Cop::Migration::CreateTableWithForeignKeys do context 'with more than one foreign keys' do let(:offense) do 'Creating a table with more than one foreign key at once violates our migration style guide. ' \ - 'For more details check the https://docs.gitlab.com/ee/development/migration_style_guide.html#examples' + 'For more details check the https://docs.gitlab.com/ee/development/migration_style_guide.html#creating-a-new-table-when-we-have-two-foreign-keys' end shared_examples 'target to high traffic table' do |dsl_method, table_name| diff --git a/spec/services/ml/create_model_version_service_spec.rb b/spec/services/ml/create_model_version_service_spec.rb index dbfbdbc79cf..932069142c6 100644 --- a/spec/services/ml/create_model_version_service_spec.rb +++ b/spec/services/ml/create_model_version_service_spec.rb @@ -53,13 +53,13 @@ RSpec.describe ::Ml::CreateModelVersionService, feature_category: :mlops do context 'when a version exist and no value is passed for version' do before do - create(:ml_model_versions, model: model, version: '3.0.0') + create(:ml_model_versions, model: model, version: '1.2.3') model.reload end it 'creates another model version and increments the version number' do expect { service }.to change { Ml::ModelVersion.count }.by(1).and change { Ml::Candidate.count }.by(1) - expect(model.reload.latest_version.version).to eq('4.0.0') + expect(model.reload.latest_version.version).to eq('1.2.4') expect(service).to be_success expect(Gitlab::InternalEvents).to have_received(:track_event).with( diff --git a/spec/services/ml/increment_version_service_spec.rb b/spec/services/ml/increment_version_service_spec.rb index 7e8bf153e63..bae5fb25bd3 100644 --- a/spec/services/ml/increment_version_service_spec.rb +++ b/spec/services/ml/increment_version_service_spec.rb @@ -39,8 +39,8 @@ RSpec.describe ::Ml::IncrementVersionService, feature_category: :mlops do where(:version, :increment_type, :result) do nil | nil | '1.0.0' - '0.0.1' | nil | '1.0.1' - '1.0.0' | nil | '2.0.0' + '0.0.1' | nil | '0.0.2' + '1.0.0' | nil | '1.0.1' '1.0.0' | :major | '2.0.0' '1.0.0' | :minor | '1.1.0' '1.0.0' | :patch | '1.0.1' diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb index 8046fff2cf3..aee22fdaada 100644 --- a/spec/workers/prune_old_events_worker_spec.rb +++ b/spec/workers/prune_old_events_worker_spec.rb @@ -43,4 +43,18 @@ RSpec.describe PruneOldEventsWorker, feature_category: :user_profile do end end end + + describe '.pruning_enabled?' do + subject(:pruning_enabled) { described_class.pruning_enabled? } + + it { is_expected.to be(true) } + + context 'with ops_prune_old_events FF disabled' do + before do + stub_feature_flags(ops_prune_old_events: false) + end + + it { is_expected.to be(false) } + end + end end