diff --git a/.gitlab/ci/qa-common/variables.gitlab-ci.yml b/.gitlab/ci/qa-common/variables.gitlab-ci.yml
index 3d567382f47..dcef0aa6774 100644
--- a/.gitlab/ci/qa-common/variables.gitlab-ci.yml
+++ b/.gitlab/ci/qa-common/variables.gitlab-ci.yml
@@ -18,4 +18,4 @@ variables:
# Retry failed specs in separate process
QA_RETRY_FAILED_SPECS: "true"
# helm chart ref used by test-on-cng pipeline
- GITLAB_HELM_CHART_REF: "b2e14fd2722528e664401e736e0e1edd0f4a6f69"
+ GITLAB_HELM_CHART_REF: "e05a64634ee53ca6868c47c6081e0f3a686e41f1"
diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js
index fb708909269..29bbee23217 100644
--- a/app/assets/javascripts/persistent_user_callouts.js
+++ b/app/assets/javascripts/persistent_user_callouts.js
@@ -29,6 +29,7 @@ const PERSISTENT_USER_CALLOUTS = [
'.js-all-seats-used',
'.js-period-in-terraform-state-name-alert',
'.js-expired-duo-pro-trial-widget',
+ '.js-duo-free-access-ending-banner',
];
const initCallouts = () => {
diff --git a/app/assets/stylesheets/page_bundles/notes/_timeline.scss b/app/assets/stylesheets/page_bundles/notes/_timeline.scss
index 2b47346469c..4f3071ffd45 100644
--- a/app/assets/stylesheets/page_bundles/notes/_timeline.scss
+++ b/app/assets/stylesheets/page_bundles/notes/_timeline.scss
@@ -77,11 +77,6 @@
.timeline-entry:not(.draft-note):last-child::before {
@apply gl-bg-default;
- // stylelint-disable-next-line gitlab/no-gl-class
- .gl-dark & {
- @apply gl-bg-subtle;
- }
-
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark .modal-body & {
@apply gl-bg-strong;
diff --git a/app/graphql/resolvers/projects/user_contributed_projects_resolver.rb b/app/graphql/resolvers/projects/user_contributed_projects_resolver.rb
index 73f8b72ed04..712c0141163 100644
--- a/app/graphql/resolvers/projects/user_contributed_projects_resolver.rb
+++ b/app/graphql/resolvers/projects/user_contributed_projects_resolver.rb
@@ -14,17 +14,26 @@ module Resolvers
required: false,
description: 'Return only projects where current user has at least the specified access level.'
+ argument :include_personal, GraphQL::Types::Boolean,
+ description: 'Include personal projects.',
+ required: false,
+ default_value: false
+
alias_method :user, :object
def resolve(**args)
- ContributedProjectsFinder.new(
+ contributed_projects = ContributedProjectsFinder.new(
user: user,
current_user: current_user,
params: {
order_by: args[:sort],
min_access_level: args[:min_access_level]
}
- ).execute.joined(user)
+ ).execute
+
+ return contributed_projects if args[:include_personal]
+
+ contributed_projects.joined(user)
end
end
end
diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb
index 6c0a13a630e..75e0ba6e7a1 100644
--- a/app/models/users/callout.rb
+++ b/app/models/users/callout.rb
@@ -93,7 +93,8 @@ module Users
period_in_terraform_state_name_alert: 91,
work_item_epic_feedback: 92, # EE-only
branch_rules_tip_callout: 93,
- openssl_callout: 94
+ openssl_callout: 94,
+ duo_free_access_ending_banner: 95 # EE-only
}
validates :feature_name,
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 8d01876cf2b..fbcf3046348 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -21,6 +21,8 @@
= render_if_exists 'gitlab_subscriptions/trials/alert', namespace: @group
+= render_if_exists 'shared/duo_free_access_ending_banner', resource: @group
+
= render 'groups/home_panel'
= render_if_exists 'groups/group_activity_analytics', group: @group
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 5245cf03f4b..e892c1d9ab1 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -3,6 +3,7 @@
- @skip_current_level_breadcrumb = true
+= render_if_exists 'projects/duo_free_access_ending_banner', project: @project
= render partial: 'flash_messages', locals: { project: @project }
= render 'clusters_deprecation_alert'
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index b75fa6d531b..1419c4cc05f 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -7,6 +7,7 @@
= auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
= render_if_exists 'shared/promotions/promote_mobile_devops', project: @project
+= render_if_exists 'projects/duo_free_access_ending_banner', project: @project
= render partial: 'flash_messages', locals: { project: @project }
= render 'clusters_deprecation_alert'
diff --git a/db/docs/batched_background_migrations/backfill_agent_activity_events_agent_project_id.yml b/db/docs/batched_background_migrations/backfill_agent_activity_events_agent_project_id.yml
index c39a9d0b817..9e703bdfd44 100644
--- a/db/docs/batched_background_migrations/backfill_agent_activity_events_agent_project_id.yml
+++ b/db/docs/batched_background_migrations/backfill_agent_activity_events_agent_project_id.yml
@@ -1,8 +1,9 @@
---
migration_job_name: BackfillAgentActivityEventsAgentProjectId
-description: Backfills sharding key `agent_activity_events.agent_project_id` from `cluster_agents`.
+description: Backfills sharding key `agent_activity_events.agent_project_id` from
+ `cluster_agents`.
feature_category: deployment_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154615
milestone: '17.1'
queued_migration_version: 20240529184616
-finalized_by: # version of the migration that finalized this BBM
+finalized_by: '20240926231943'
diff --git a/db/migrate/20240924195606_add_migrate_memberships_to_bulk_import_configurations.rb b/db/migrate/20240924195606_add_migrate_memberships_to_bulk_import_configurations.rb
new file mode 100644
index 00000000000..1508ec00111
--- /dev/null
+++ b/db/migrate/20240924195606_add_migrate_memberships_to_bulk_import_configurations.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddMigrateMembershipsToBulkImportConfigurations < Gitlab::Database::Migration[2.2]
+ milestone '17.5'
+
+ def change
+ add_column :bulk_import_configurations, :migrate_memberships, :boolean, default: true, null: false
+ end
+end
diff --git a/db/post_migrate/20240924234448_remove_faulty_async_index_definitions.rb b/db/post_migrate/20240924234448_remove_faulty_async_index_definitions.rb
new file mode 100644
index 00000000000..7d1acf37d7c
--- /dev/null
+++ b/db/post_migrate/20240924234448_remove_faulty_async_index_definitions.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class RemoveFaultyAsyncIndexDefinitions < Gitlab::Database::Migration[2.2]
+ milestone '17.5'
+
+ def up
+ unprepare_async_index_by_name :merge_request_diff_commits_b5377a7a34,
+ :index_merge_request_diff_commits_b5377a7a34_on_project_id
+ unprepare_async_index_by_name :merge_request_diff_files_99208b8fac,
+ :index_merge_request_diff_files_99208b8fac_on_project_id
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20240926231943_finalize_backfill_agent_activity_events_agent_project_id.rb b/db/post_migrate/20240926231943_finalize_backfill_agent_activity_events_agent_project_id.rb
new file mode 100644
index 00000000000..d53091f9f30
--- /dev/null
+++ b/db/post_migrate/20240926231943_finalize_backfill_agent_activity_events_agent_project_id.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class FinalizeBackfillAgentActivityEventsAgentProjectId < Gitlab::Database::Migration[2.2]
+ milestone '17.5'
+
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: 'BackfillAgentActivityEventsAgentProjectId',
+ table_name: :agent_activity_events,
+ column_name: :id,
+ job_arguments: [:agent_project_id, :cluster_agents, :project_id, :agent_id],
+ finalize: true
+ )
+ end
+
+ def down; end
+end
diff --git a/db/schema_migrations/20240924195606 b/db/schema_migrations/20240924195606
new file mode 100644
index 00000000000..5f3340c63f1
--- /dev/null
+++ b/db/schema_migrations/20240924195606
@@ -0,0 +1 @@
+7388e13f70524cc140a13602d7dde566e213ac16773ee66b776c065c29489e33
\ No newline at end of file
diff --git a/db/schema_migrations/20240924234448 b/db/schema_migrations/20240924234448
new file mode 100644
index 00000000000..d7bbe91b516
--- /dev/null
+++ b/db/schema_migrations/20240924234448
@@ -0,0 +1 @@
+dca955fb2f25ca3e40cdf2a4fe195a27bc53a1134eb172ecbe6b436fdc888ca0
\ No newline at end of file
diff --git a/db/schema_migrations/20240926231943 b/db/schema_migrations/20240926231943
new file mode 100644
index 00000000000..b59fab4aa74
--- /dev/null
+++ b/db/schema_migrations/20240926231943
@@ -0,0 +1 @@
+0c7ed8e3faa3001929f69f884ca08614190e86040794b2320b79c0469fe412e7
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 0f8a3c53be8..bfda44c4b9e 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -7572,7 +7572,8 @@ CREATE TABLE bulk_import_configurations (
encrypted_access_token text,
encrypted_access_token_iv text,
created_at timestamp with time zone NOT NULL,
- updated_at timestamp with time zone NOT NULL
+ updated_at timestamp with time zone NOT NULL,
+ migrate_memberships boolean DEFAULT true NOT NULL
);
CREATE SEQUENCE bulk_import_configurations_id_seq
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index c592421acfa..e8ba4a043cd 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -17369,6 +17369,7 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `includePersonal` | [`Boolean`](#boolean) | Include personal projects. |
| `minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. |
| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
@@ -18257,6 +18258,7 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `includePersonal` | [`Boolean`](#boolean) | Include personal projects. |
| `minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. |
| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
@@ -20649,6 +20651,7 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `includePersonal` | [`Boolean`](#boolean) | Include personal projects. |
| `minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. |
| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
@@ -26221,6 +26224,7 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `includePersonal` | [`Boolean`](#boolean) | Include personal projects. |
| `minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. |
| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
@@ -26611,6 +26615,7 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `includePersonal` | [`Boolean`](#boolean) | Include personal projects. |
| `minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. |
| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
@@ -27047,6 +27052,7 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `includePersonal` | [`Boolean`](#boolean) | Include personal projects. |
| `minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. |
| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
@@ -27456,6 +27462,7 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `includePersonal` | [`Boolean`](#boolean) | Include personal projects. |
| `minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. |
| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
@@ -33950,6 +33957,7 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `includePersonal` | [`Boolean`](#boolean) | Include personal projects. |
| `minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. |
| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
@@ -38764,6 +38772,7 @@ Name of the feature that the callout is for.
| `DEPLOYMENT_APPROVALS_EMPTY_STATE` | Callout feature name for deployment_approvals_empty_state. |
| `DEPLOYMENT_DETAILS_FEEDBACK` | Callout feature name for deployment_details_feedback. |
| `DUO_CHAT_CALLOUT` | Callout feature name for duo_chat_callout. |
+| `DUO_FREE_ACCESS_ENDING_BANNER` | Callout feature name for duo_free_access_ending_banner. |
| `FEATURE_FLAGS_NEW_VERSION` | Callout feature name for feature_flags_new_version. |
| `GCP_SIGNUP_OFFER` | Callout feature name for gcp_signup_offer. |
| `GEO_ENABLE_HASHED_STORAGE` | Callout feature name for geo_enable_hashed_storage. |
@@ -41100,6 +41109,7 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `includePersonal` | [`Boolean`](#boolean) | Include personal projects. |
| `minAccessLevel` | [`AccessLevelEnum`](#accesslevelenum) | Return only projects where current user has at least the specified access level. |
| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
diff --git a/doc/ci/testing/test_coverage_visualization/jacoco.md b/doc/ci/testing/test_coverage_visualization/jacoco.md
index 6f640335e5c..9b02266d887 100644
--- a/doc/ci/testing/test_coverage_visualization/jacoco.md
+++ b/doc/ci/testing/test_coverage_visualization/jacoco.md
@@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
DETAILS:
**Tier:** Free, Premium, Ultimate
-**Offering:** GitLab.com, Self-managed, GitLab Dedicated
+**Offering:** GitLab.com, Self-managed
**Status:** Beta
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227345) in GitLab 17.3 [with a flag](../../../administration/feature_flags.md) named `jacoco_coverage_reports`. Disabled by default.
diff --git a/doc/development/cells/index.md b/doc/development/cells/index.md
index c5fc80879eb..77dac8f737f 100644
--- a/doc/development/cells/index.md
+++ b/doc/development/cells/index.md
@@ -12,11 +12,16 @@ For background of GitLab Cells, refer to the [design document](https://handbook.
Depending on the use case, your feature may be [cell-local or clusterwide](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/cells/#how-do-i-decide-whether-to-move-my-feature-to-the-cluster-cell-or-organization-level) and hence the tables used for the feature should also use the appropriate schema.
-When you choose the appropriate schema for tables, consider the following guidelines as part of the [Cells](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/cells/) architecture:
+When you choose the appropriate [schema](../database/multiple_databases.md#gitlab-schema) for tables, consider the following guidelines as part of the [Cells](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/cells/) architecture:
- Default to `gitlab_main_cell`: We expect most tables to be assigned to the `gitlab_main_cell` schema by default. Choose this schema if the data in the table is related to `projects` or `namespaces`.
- Consult with the Tenant Scale group: If you believe that the `gitlab_main_clusterwide` schema is more suitable for a table, seek approval from the Tenant Scale group. This is crucial because it has scaling implications and may require reconsideration of the schema choice.
+Tables with `gitlab_main_clusterwide` schema will need additional work to be replicated to other / all cells.
+The replication strategy will likely be different for each case, but will involve internal APIs.
+The application may also need to be modified to restrict writes to prevent conflicts.
+We may also ask teams to update tables from `gitlab_main_clusterwide` to `gitlab_main_cell` as required, which also might require adding sharding keys to these tables.
+
To understand how existing tables are classified, you can use [this dashboard](https://manojmj.gitlab.io/tenant-scale-schema-progress/).
After a schema has been assigned, the merge request pipeline might fail due to one or more of the following reasons, which can be rectified by following the linked guidelines:
diff --git a/doc/development/code_suggestions/index.md b/doc/development/code_suggestions/index.md
index 8598a3e9c3d..eda634eb5e3 100644
--- a/doc/development/code_suggestions/index.md
+++ b/doc/development/code_suggestions/index.md
@@ -32,7 +32,7 @@ This should enable everyone to see locally any change in an IDE being sent to th
1. Main Application (GDK):
1. Install the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/index.md#one-line-installation).
- 1. Enable Feature Flag ```ai_duo_code_suggestions_switch```:
+ 1. Enable Feature Flag `ai_duo_code_suggestions_switch`:
1. In your terminal, go to your `gitlab-development-kit` > `gitlab` directory.
1. Run `gdk rails console` or `bundle exec rails c` to start a Rails console.
1. [Enable the Feature Flag](../../administration/feature_flags.md#enable-or-disable-the-feature) for the Code Suggestions tokens API by calling `Feature.enable(:ai_duo_code_suggestions_switch)` from the console.
@@ -63,7 +63,7 @@ with the deployed staging AI Gateway. To do this:
1. Add a **GitLab Ultimate Self-Managed** subscription with a [Duo Pro subscription add-on](../../subscriptions/subscription-add-ons.md) to your GDK instance.
1. Sign in to the [staging Customers Portal](https://customers.staging.gitlab.com) by selecting the **Continue with GitLab.com account** button.
- If you do not have an existing account, you are prompted to create one.
+ If you do not have an existing account, you are prompted to create one.
1. If you do not have an existing cloud activation code, visit the **Ultimate Self-Managed Subscription** page using the [buy subscription flow link](https://gitlab.com/gitlab-org/customers-gitlab-com/-/blob/8aa922840091ad5c5d96ada43d0065a1b6198841/doc/flows/buy_subscription.md).
1. Purchase the subscription using [a test credit card](https://gitlab.com/gitlab-org/customers-gitlab-com/#testing-credit-card-information).
1. Once you have a subscription, on the subscription card, select the ellipse menu **...** > **Buy Duo Pro add-on**.
@@ -81,13 +81,13 @@ with the deployed staging AI Gateway. To do this:
export GITLAB_SIMULATE_SAAS=0
```
- On a non-GDK instance, you can set the variables using `gitlab_rails['env']` in the `gitlab.rb` file:
+ On a non-GDK instance, you can set the variables using `gitlab_rails['env']` in the `gitlab.rb` file:
```shell
gitlab_rails['env'] = {
- 'GITLAB_LICENSE_MODE' => 'test',
- 'CUSTOMER_PORTAL_URL' => 'https://customers.staging.gitlab.com',
- 'AI_GATEWAY_URL' => 'https://cloud.staging.gitlab.com/ai'
+ 'GITLAB_LICENSE_MODE' => 'test',
+ 'CUSTOMER_PORTAL_URL' => 'https://customers.staging.gitlab.com',
+ 'AI_GATEWAY_URL' => 'https://cloud.staging.gitlab.com/ai'
}
```
@@ -97,19 +97,19 @@ with the deployed staging AI Gateway. To do this:
1. Add the new activation code.
1. Inside your GDK, navigate to **Admin area** > **GitLab Duo Pro**, go to `/admin/code_suggestions`
-1. Filter users to find `root` and click the toggle to assign a GitLab Duo Pro add-on seat to the root user
+1. Filter users to find `root` and click the toggle to assign a GitLab Duo Pro add-on seat to the root user.
### Setup instructions to use the Duo Pro add-on with a **staging** GitLab.com account
-1. Have your account ready at
-1. [Create a new group](../../user/group/index.md#create-a-group) or use an existing one as the namespace which will receive the Duo Pro access
-1. Navigate to `Settings > Billing`
-1. Initiate the purchase flow for the Ultimate plan by clicking on `Upgrade to Ultimate`
-1. After being redirected to , click on `Continue with your Gitlab.com account`
-1. Purchase the SaaS Ultimate subscription using [a test credit card](https://gitlab.com/gitlab-org/customers-gitlab-com#testing-credit-card-information)
-1. Find the newly purchased subscription card, and select from the three dots menu the option `Buy GitLab Duo Pro`
-1. Purchase the GitLab Duo Pro add-on using the same test credit card from the above steps
-1. Go back to and verify that your group has access to Duo Pro by navigating to `Settings > GitLab Duo` and managing seats
+1. Have your account ready at .
+1. [Create a new group](../../user/group/index.md#create-a-group) or use an existing one as the namespace which will receive the Duo Pro access.
+1. Navigate to `Settings > Billing`.
+1. Initiate the purchase flow for the Ultimate plan by clicking on `Upgrade to Ultimate`.
+1. After being redirected to , click on `Continue with your Gitlab.com account`.
+1. Purchase the SaaS Ultimate subscription using [a test credit card](https://gitlab.com/gitlab-org/customers-gitlab-com#testing-credit-card-information).
+1. Find the newly purchased subscription card, and select from the three dots menu the option `Buy GitLab Duo Pro`.
+1. Purchase the GitLab Duo Pro add-on using the same test credit card from the above steps.
+1. Go back to and verify that your group has access to Duo Pro by navigating to `Settings > GitLab Duo` and managing seats.
### Video demonstrations of installing and using Code Suggestions in IDEs
diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb
index addd7f098a3..5f9dbf90c05 100644
--- a/lib/gitlab/quick_actions/merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/merge_request_actions.rb
@@ -351,8 +351,7 @@ module Gitlab
end
types MergeRequest
condition do
- quick_action_target.persisted? &&
- reviewers_to_remove?(@updates) &&
+ reviewers_to_remove?(@updates) &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
parse_params do |unassign_reviewer_param|
@@ -360,12 +359,24 @@ module Gitlab
extract_users(unassign_reviewer_param) if quick_action_target.allows_multiple_reviewers?
end
command :unassign_reviewer, :remove_reviewer do |users = nil|
+ current_reviewers = quick_action_target.reviewers
+ # if preceding commands have been executed already, we need to use the updated reviewer_ids
+ current_reviewers = User.find(@updates[:reviewer_ids]) if @updates[:reviewer_ids].present?
+
if quick_action_target.allows_multiple_reviewers? && users&.any?
@updates[:reviewer_ids] ||= quick_action_target.reviewers.map(&:id)
@updates[:reviewer_ids] -= users.map(&:id)
else
@updates[:reviewer_ids] = []
end
+
+ removed_reviewers = current_reviewers.select { |user| @updates[:reviewer_ids].exclude?(user.id) }
+ # only generate the message here if the change would not be traceable otherwise
+ # because all reviewers have been assigned and removed immediately
+ if removed_reviewers.present? && !reviewers_to_remove?(@updates)
+ @execution_message[:unassign_reviewer] = _("Removed %{reviewer_text} %{reviewer_references}.") %
+ { reviewer_text: 'reviewer'.pluralize(removed_reviewers.size), reviewer_references: removed_reviewers.map(&:to_reference).to_sentence }
+ end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ecc0a5aea15..9c7f1646953 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -20213,6 +20213,18 @@ msgstr ""
msgid "DuoEnterpriseTrial|We just need some additional information to activate your trial."
msgstr ""
+msgid "DuoFreeAccessEndingBanner|Buy Duo Pro"
+msgstr ""
+
+msgid "DuoFreeAccessEndingBanner|Dismiss Free Access Ending banner"
+msgstr ""
+
+msgid "DuoFreeAccessEndingBanner|Free access to GitLab Duo is ending on %{date}"
+msgstr ""
+
+msgid "DuoFreeAccessEndingBanner|Starting %{date}, all GitLab Duo features, including Duo Chat, %{link_start}require a paid add-on subscription.%{link_end} To ensure uninterrupted access to Code Suggestions and Chat, buy Duo Pro and assign seats to your users. Or, for Duo Enterprise options, contact Sales."
+msgstr ""
+
msgid "DuoProDiscover|Accelerate your path to market"
msgstr ""
diff --git a/package.json b/package.json
index 4b3995e822e..de86839e0e9 100644
--- a/package.json
+++ b/package.json
@@ -279,7 +279,7 @@
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-local-rules": "^3.0.2",
"eslint-plugin-no-jquery": "2.7.0",
- "eslint-plugin-no-unsanitized": "^4.1.0",
+ "eslint-plugin-no-unsanitized": "^4.1.1",
"fake-indexeddb": "^4.0.1",
"gettext-extractor": "^3.7.0",
"gettext-extractor-vue": "^5.1.0",
diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
index 848ce551916..f66acb1226e 100644
--- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
@@ -33,8 +33,7 @@ RSpec.describe 'cross-database foreign keys' do
'protected_branch_merge_access_levels.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/431055
'user_group_callouts.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/421287
'subscription_user_add_on_assignments.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/444666
- 'subscription_add_on_purchases.subscription_add_on_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/444666
- 'dast_pre_scan_verifications.dast_profile_id' # https://gitlab.com/gitlab-org/gitlab/-/issues/479141
+ 'subscription_add_on_purchases.subscription_add_on_id' # https://gitlab.com/gitlab-org/gitlab/-/issues/444666
]
end
diff --git a/spec/requests/api/graphql/user/contributed_projects_query_spec.rb b/spec/requests/api/graphql/user/contributed_projects_query_spec.rb
index 1fe088e1403..64b7a24dea2 100644
--- a/spec/requests/api/graphql/user/contributed_projects_query_spec.rb
+++ b/spec/requests/api/graphql/user/contributed_projects_query_spec.rb
@@ -9,22 +9,25 @@ RSpec.describe 'Getting contributedProjects of the user', feature_category: :gro
let(:user_params) { { username: user.username } }
let(:user_fields) { 'contributedProjects { nodes { id } }' }
- let_it_be(:user) { create(:user) }
+ let_it_be(:user) { create(:user, :with_namespace) }
let_it_be(:current_user) { create(:user) }
let_it_be(:public_project) { create(:project, :public) }
let_it_be(:private_project) { create(:project, :private) }
let_it_be(:internal_project) { create(:project, :internal) }
+ let_it_be(:personal_project) { create(:project, namespace: user.namespace) }
let(:path) { %i[user contributed_projects nodes] }
before_all do
private_project.add_developer(user)
private_project.add_developer(current_user)
+ personal_project.add_developer(current_user)
travel_to(4.hours.from_now) { create(:push_event, project: private_project, author: user) }
travel_to(3.hours.from_now) { create(:push_event, project: internal_project, author: user) }
travel_to(2.hours.from_now) { create(:push_event, project: public_project, author: user) }
+ travel_to(2.hours.from_now) { create(:push_event, project: personal_project, author: user) }
end
it_behaves_like 'a working graphql query' do
@@ -410,6 +413,37 @@ RSpec.describe 'Getting contributedProjects of the user', feature_category: :gro
end
end
+ context 'when include_personal argument is false' do
+ it 'does not include personal projects' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data_at(*path))
+ .to contain_exactly(
+ a_graphql_entity_for(private_project),
+ a_graphql_entity_for(internal_project),
+ a_graphql_entity_for(public_project)
+ )
+ end
+ end
+
+ context 'when include_personal argument is true' do
+ let(:query_with_include_personal) do
+ graphql_query_for(:user, user_params, 'contributedProjects(includePersonal: true) { nodes { id } }')
+ end
+
+ it 'includes personal projects' do
+ post_graphql(query_with_include_personal, current_user: current_user)
+
+ expect(graphql_data_at(*path))
+ .to contain_exactly(
+ a_graphql_entity_for(private_project),
+ a_graphql_entity_for(internal_project),
+ a_graphql_entity_for(public_project),
+ a_graphql_entity_for(personal_project)
+ )
+ end
+ end
+
describe 'sorting and pagination' do
let(:data_path) { [:user, :contributed_projects] }
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index e7503fbd19b..715ca663c88 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -1200,6 +1200,26 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
it_behaves_like 'failed command', 'Could not apply unassign_reviewer command.'
end
+ context 'with a not-yet-persisted merge request and a preceding assign_reviewer command' do
+ let(:content) do
+ <<-QUICKACTION
+/assign_reviewer #{developer.to_reference}
+/unassign_reviewer #{developer.to_reference}
+ QUICKACTION
+ end
+
+ let(:issuable) { build(:merge_request) }
+
+ it 'adds and then removes a single reviewer in a single step' do
+ _, updates, message = service.execute(content, issuable)
+ translated_string = _("Assigned %{developer_to_reference} as reviewer. Removed reviewer %{developer_to_reference}.")
+ formatted_message = format(translated_string, developer_to_reference: developer.to_reference.to_s)
+
+ expect(updates).to eq(reviewer_ids: [])
+ expect(message).to eq(formatted_message)
+ end
+ end
+
context 'with anything after the command' do
let(:content) { '/unassign_reviewer supercalifragilisticexpialidocious' }
diff --git a/yarn.lock b/yarn.lock
index aa5f5ba85ad..ac8672cfba2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6800,10 +6800,10 @@ eslint-plugin-no-jquery@2.7.0:
resolved "https://registry.yarnpkg.com/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.7.0.tgz#855f5631cf5b8e25b930cf6f06e02dd81f132e72"
integrity sha512-Aeg7dA6GTH1AcWLlBtWNzOU9efK5KpNi7b0EhBO0o0M+awyzguUUo8gF6hXGjQ9n5h8/uRtYv9zOqQkeC5CG0w==
-eslint-plugin-no-unsanitized@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.0.tgz#2c914e8ea8048c3afaac8f0c12384747aba6497a"
- integrity sha512-9A8Yrbkkex8e56ivxJ2f5dXN2Js2BmKC8QgmeYZjadyiGUngo3KLXDlq6ZzalmCHyLwLF5MoQLPR6FWlNc+Qbw==
+eslint-plugin-no-unsanitized@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.1.tgz#15e638416fcac50c4f1d4d13473cfa339ea163aa"
+ integrity sha512-N0yf7iYWQAO/qiglZlrASXRf6I/18q9d9NNR5Vw175zgrPduvLfnBwgWwM75D4g4lbrd9uPNXlieaFGWZ40h4A==
eslint-plugin-promise@^7.0.0:
version "7.0.0"