diff --git a/app/assets/javascripts/members/placeholders/components/app.vue b/app/assets/javascripts/members/placeholders/components/app.vue
index 21942038b2f..b3b1e7f358b 100644
--- a/app/assets/javascripts/members/placeholders/components/app.vue
+++ b/app/assets/javascripts/members/placeholders/components/app.vue
@@ -1,7 +1,7 @@
-
+
{{ s__('UserMapping|Awaiting reassignment') }}
@@ -151,5 +154,18 @@ export default {
@next="onNextPage"
/>
+
+
+
+ {{ s__('UserMapping|Reassign with CSV file') }}
+
+
+
diff --git a/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue b/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue
new file mode 100644
index 00000000000..990a5c8da5a
--- /dev/null
+++ b/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+ -
+ {{ s__('UserMapping|Download the pre-filled CSV template.') }}
+
+ - {{ s__('UserMapping|Review and complete filling out the CSV file.') }}
+ - {{ s__('UserMapping|Upload reviewed and completed CSV file.') }}
+
+
+ {{
+ s__(
+ 'UserMapping|Once you select "Reassign", the processing will start and users will receive and email to accept the contribution reassignment. Once a users has accepted the reassignment, it cannot be undone. Check all data is correct before continuing.',
+ )
+ }}
+
+
+
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
index 5b6d98bb717..cfc74638668 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
@@ -293,6 +293,7 @@ export default {
} else {
this.unsetError();
this.workItemsToAdd = [];
+ this.closeForm();
}
})
.catch(() => {
@@ -328,6 +329,7 @@ export default {
} else {
this.unsetError();
this.$emit('addChild');
+ this.closeForm();
}
})
.catch(() => {
@@ -340,6 +342,9 @@ export default {
this.submitInProgress = false;
});
},
+ closeForm() {
+ this.$emit('cancel');
+ },
},
i18n: {
titleInputLabel: __('Title'),
@@ -446,7 +451,7 @@ export default {
>
{{ addOrCreateButtonLabel }}
-
+
{{ s__('WorkItem|Cancel') }}
diff --git a/config/feature_flags/beta/ensure_lfs_object_project_uniqueness.yml b/config/feature_flags/beta/ensure_lfs_object_project_uniqueness.yml
index e6702b528a9..fa9647ca350 100644
--- a/config/feature_flags/beta/ensure_lfs_object_project_uniqueness.yml
+++ b/config/feature_flags/beta/ensure_lfs_object_project_uniqueness.yml
@@ -6,4 +6,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/472750
milestone: '17.3'
group: group::source code
type: beta
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/beta/show_role_details_in_drawer.yml b/config/feature_flags/beta/show_role_details_in_drawer.yml
index e37953c4c50..958f8b290a9 100644
--- a/config/feature_flags/beta/show_role_details_in_drawer.yml
+++ b/config/feature_flags/beta/show_role_details_in_drawer.yml
@@ -6,4 +6,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/468669
milestone: '17.2'
group: group::authorization
type: beta
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/wip/autoflow_enabled.yml b/config/feature_flags/wip/autoflow_enabled.yml
new file mode 100644
index 00000000000..0a172face71
--- /dev/null
+++ b/config/feature_flags/wip/autoflow_enabled.yml
@@ -0,0 +1,9 @@
+---
+name: autoflow_enabled
+feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/12120
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/160947
+rollout_issue_url:
+milestone: '17.3'
+group: group::environments
+type: wip
+default_enabled: false
diff --git a/doc/administration/auth/ldap/index.md b/doc/administration/auth/ldap/index.md
index db72524ef32..c269ea90780 100644
--- a/doc/administration/auth/ldap/index.md
+++ b/doc/administration/auth/ldap/index.md
@@ -52,6 +52,12 @@ Users are considered inactive in LDAP when they:
- Are marked as disabled or deactivated in Active Directory through the user account control attribute. This means attribute
`userAccountControl:1.2.840.113556.1.4.803` has bit 2 set.
+To check if a user is active or inactive in LDAP, use the following PowerShell command and the [Active Directory Module](https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2022-ps) to check the Active Directory:
+
+```powershell
+Get-ADUser -Identity -Properties userAccountControl | Select-Object Name, userAccountControl
+```
+
GitLab checks LDAP users' status:
- When signing in using any authentication provider.
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index da2baa4a292..08001bd2a2a 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -1211,9 +1211,11 @@ than GitLab to prevent XSS attacks.
### Rate limits
+> - [Changed](https://gitlab.com/groups/gitlab-org/-/epics/14653) in GitLab 17.3: You can exclude subnets from Pages rate limits.
+
You can enforce rate limits to help minimize the risk of a Denial of Service (DoS) attack. GitLab Pages
uses a [token bucket algorithm](https://en.wikipedia.org/wiki/Token_bucket) to enforce rate limiting. By default,
-requests or TLS connections that exceed the specified limits are reported but not rejected.
+requests or TLS connections that exceed the specified limits are reported and rejected.
GitLab Pages supports the following types of rate limiting:
@@ -1222,20 +1224,25 @@ GitLab Pages supports the following types of rate limiting:
HTTP request-based rate limits are enforced using the following:
-- `rate_limit_source_ip`: Set the maximum threshold in number of requests per client IP per second. Set to 0 to disable this feature.
+- `rate_limit_source_ip`: Sets the maximum threshold in number of requests per client IP per second. Set to 0 to disable this feature.
- `rate_limit_source_ip_burst`: Sets the maximum threshold of number of requests allowed in an initial outburst of requests per client IP.
For example, when you load a web page that loads a number of resources at the same time.
-- `rate_limit_domain`: Set the maximum threshold in number of requests per hosted pages domain per second. Set to 0 to disable this feature.
+- `rate_limit_domain`: Sets the maximum threshold in number of requests per hosted pages domain per second. Set to 0 to disable this feature.
- `rate_limit_domain_burst`: Sets the maximum threshold of number of requests allowed in an initial outburst of requests per hosted pages domain.
TLS connection-based rate limits are enforced using the following:
-- `rate_limit_tls_source_ip`: Set the maximum threshold in number of TLS connections per client IP per second. Set to 0 to disable this feature.
+- `rate_limit_tls_source_ip`: Sets the maximum threshold in number of TLS connections per client IP per second. Set to 0 to disable this feature.
- `rate_limit_tls_source_ip_burst`: Sets the maximum threshold of number of TLS connections allowed in an initial outburst of TLS connections per client IP.
For example, when you load a web page from different web browsers at the same time.
-- `rate_limit_tls_domain`: Set the maximum threshold in number of TLS connections per hosted pages domain per second. Set to 0 to disable this feature.
+- `rate_limit_tls_domain`: Sets the maximum threshold in number of TLS connections per hosted pages domain per second. Set to 0 to disable this feature.
- `rate_limit_tls_domain_burst`: Sets the maximum threshold of number of TLS connections allowed in an initial outburst of TLS connections per hosted pages domain.
+To allow certain IP ranges (subnets) to bypass all rate limits:
+
+- `rate_limit_subnets_allow_list`: Sets the allow list with the IP ranges (subnets) that should bypass all rate limits.
+ For example, `['1.2.3.4/24', '2001:db8::1/32']`.
+
An IPv6 address receives a large prefix in the 128-bit address space. The prefix is typically at least size /64. Because of the large number of possible addresses, if the client's IP address is IPv6, the limit is applied to the IPv6 prefix with a length of 64, rather than the entire IPv6 address.
#### Enable HTTP requests rate limits by source-IP
diff --git a/doc/editor_extensions/gitlab_cli/index.md b/doc/editor_extensions/gitlab_cli/index.md
index a6de6a76c2c..eb83457df16 100644
--- a/doc/editor_extensions/gitlab_cli/index.md
+++ b/doc/editor_extensions/gitlab_cli/index.md
@@ -73,7 +73,7 @@ glab mr merge
## GitLab Duo for the CLI
DETAILS:
-**Tier:** Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md)
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** Experiment, Self-managed, GitLab Dedicated
The GitLab CLI includes features powered by [GitLab Duo](../../user/ai_features.md). These include:
diff --git a/doc/user/analytics/analytics_dashboards.md b/doc/user/analytics/analytics_dashboards.md
index 5c04566571f..cd74af90ea1 100644
--- a/doc/user/analytics/analytics_dashboards.md
+++ b/doc/user/analytics/analytics_dashboards.md
@@ -269,7 +269,7 @@ After you save a visualization, you can add it to a new or existing custom dashb
### Generate a custom visualization with GitLab Duo
DETAILS:
-**Tier:** Ultimate for a limited time. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
diff --git a/doc/user/application_security/vulnerabilities/index.md b/doc/user/application_security/vulnerabilities/index.md
index fc5d89a20dc..be6163971dc 100644
--- a/doc/user/application_security/vulnerabilities/index.md
+++ b/doc/user/application_security/vulnerabilities/index.md
@@ -32,7 +32,7 @@ the Vulnerability Report's [Activity filter](../vulnerability_report/index.md#ac
## Explaining a vulnerability
DETAILS:
-**Tier:** Ultimate with [GitLab Duo Enterprise](../../../subscriptions/subscription-add-ons.md)
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10368) in GitLab 16.0 as an [experiment](../../../policy/experiment-beta-support.md#experiment) on GitLab.com.
@@ -90,7 +90,7 @@ The following data is shared with third-party AI APIs:
## Vulnerability resolution
DETAILS:
-**Tier:** Ultimate with [GitLab Duo Enterprise](../../../subscriptions/subscription-add-ons.md)
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
diff --git a/doc/user/gitlab_duo/experiments.md b/doc/user/gitlab_duo/experiments.md
index a6d5b37e7e0..dd12dbeb0af 100644
--- a/doc/user/gitlab_duo/experiments.md
+++ b/doc/user/gitlab_duo/experiments.md
@@ -12,7 +12,7 @@ The following GitLab Duo features are
## Summarize issue discussions with Discussion summary
DETAILS:
-**Tier:** Ultimate for a limited time. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
@@ -76,7 +76,7 @@ Provide feedback on this experimental feature in [issue 416833](https://gitlab.c
## Summarize an issue with Issue description generation
DETAILS:
-**Tier:** Ultimate for a limited time. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
diff --git a/doc/user/gitlab_duo/index.md b/doc/user/gitlab_duo/index.md
index d22fe1ed08d..4ca7491d8d6 100644
--- a/doc/user/gitlab_duo/index.md
+++ b/doc/user/gitlab_duo/index.md
@@ -26,7 +26,7 @@ how and where you can access these features.
### GitLab Duo Chat
DETAILS:
-**Tier: GitLab.com and Self-managed:** Premium or Ultimate for a limited time. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise.
+**Tier: GitLab.com and Self-managed:** For a limited time, Premium or Ultimate. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise.
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
- Help you write and understand code faster, get up to speed on the status of projects,
@@ -55,7 +55,7 @@ DETAILS:
### Code explanation in the IDE
DETAILS:
-**Tier: GitLab.com and Self-managed:** Premium or Ultimate for a limited time. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise.
+**Tier: GitLab.com and Self-managed:** For a limited time, Premium or Ultimate. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise.
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
- Helps you understand the selected code by explaining it more clearly.
@@ -76,7 +76,7 @@ DETAILS:
### GitLab Duo for the CLI
DETAILS:
-**Tier:** Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment, Self-managed, GitLab Dedicated
@@ -87,7 +87,7 @@ DETAILS:
### Merge commit message generation
DETAILS:
-**Tier:** Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md)
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
- Helps you merge more quickly by generating meaningful commit messages.
@@ -97,7 +97,7 @@ DETAILS:
### Root cause analysis
DETAILS:
-**Tier:** Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md)
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123692) in GitLab 16.2 as an [experiment](../../policy/experiment-beta-support.md#experiment) on GitLab.com.
@@ -110,7 +110,7 @@ DETAILS:
### Vulnerability explanation
DETAILS:
-**Tier:** Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md)
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
- Helps you understand vulnerabilities, how they can be exploited, and how to fix them.
@@ -134,7 +134,7 @@ DETAILS:
### Merge request summary
DETAILS:
-**Tier:** Ultimate for a limited time. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Beta
@@ -147,7 +147,7 @@ DETAILS:
### Issue description generation
DETAILS:
-**Tier:** Ultimate for a limited time. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
@@ -158,7 +158,7 @@ DETAILS:
### Discussion summary
DETAILS:
-**Tier:** Ultimate for a limited time. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
@@ -183,7 +183,7 @@ DETAILS:
### Code review summary
DETAILS:
-**Tier:** Ultimate for a limited time. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
@@ -195,7 +195,7 @@ DETAILS:
### Vulnerability resolution
DETAILS:
-**Tier:** Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md)
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
@@ -206,7 +206,7 @@ DETAILS:
### Product Analytics
DETAILS:
-**Tier:** Ultimate for a limited time. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
diff --git a/doc/user/gitlab_duo_chat/examples.md b/doc/user/gitlab_duo_chat/examples.md
index 3e17e70e061..af6ae458186 100644
--- a/doc/user/gitlab_duo_chat/examples.md
+++ b/doc/user/gitlab_duo_chat/examples.md
@@ -85,7 +85,7 @@ Alternatively, you can use root cause analysis to [troubleshoot failed CI/CD job
## Troubleshoot failed CI/CD jobs with root cause analysis
DETAILS:
-**Tier:** Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md)
+**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123692) in GitLab 16.2 as an [experiment](../../policy/experiment-beta-support.md#experiment) on GitLab.com.
diff --git a/doc/user/profile/achievements.md b/doc/user/profile/achievements.md
index 8cc6942a9ea..a2c6fe61307 100644
--- a/doc/user/profile/achievements.md
+++ b/doc/user/profile/achievements.md
@@ -303,6 +303,31 @@ If you don't want to display achievements on your profile, you can opt out. To d
1. In the **Main settings** section, clear the **Display achievements on your profile** checkbox.
1. Select **Update profile settings**.
+## Change visibility of specific achievements
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161225) in GitLab 17.3.
+
+If you don't want to display all achievements on your profile, you can change the visibility of specific achievements.
+
+To hide one of your achievements, call the [`userAchievementsUpdate` GraphQL mutation](../../api/graphql/reference/index.md#mutationuserachievementsupdate).
+
+```graphql
+mutation {
+ userAchievementsUpdate(input: {
+ userAchievementId: "gid://gitlab/Achievements::UserAchievement/"
+ showOnProfile: false
+ }) {
+ userAchievement {
+ id
+ showOnProfile
+ }
+ errors
+ }
+}
+```
+
+To show one of your achievements again, call the same mutation with the value `true` for the `showOnProfile` argument.
+
## Reorder achievements
By default, achievements on your profile are displayed in ascending order by awarded date.
diff --git a/doc/user/project/repository/code_suggestions/index.md b/doc/user/project/repository/code_suggestions/index.md
index f4523827067..a5b7651c096 100644
--- a/doc/user/project/repository/code_suggestions/index.md
+++ b/doc/user/project/repository/code_suggestions/index.md
@@ -42,6 +42,7 @@ Code generation is used when:
- You write a comment and press Enter.
- You enter an empty function or method.
- The file you're editing has fewer than five lines of code.
+
Code generation requests take longer than code completion requests, but provide more accurate responses because:
- A larger LLM is used.
diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md
index 6a16a27bd0c..e938d8f81e1 100644
--- a/doc/user/project/working_with_projects.md
+++ b/doc/user/project/working_with_projects.md
@@ -82,7 +82,7 @@ If you are not authenticated, then the list shows public projects only.
To view projects you are a member of:
1. On the left sidebar, select **Search or go to**.
-1. Select **Your work**.
+1. Select **View all my projects**.
On the left sidebar, **Projects** is selected. On the list, on the **Yours** tab,
all the projects you are a member of are displayed.
@@ -96,6 +96,12 @@ called `my-project` under your username, the project is created at `https://gitl
To view your personal projects:
+1. On the left sidebar, select **Search or go to**.
+1. Select **View all my projects**.
+1. Select the **Personal** tab.
+
+Or
+
1. On the left sidebar, select your avatar and then your username.
1. On the left sidebar, select **Personal projects**.
@@ -103,6 +109,12 @@ To view your personal projects:
To view projects you have [starred](#star-a-project):
+1. On the left sidebar, select **Search or go to**.
+1. Select **View all my projects**.
+1. Select the **Starred** tab.
+
+Or
+
1. On the left sidebar, select your avatar and then your username.
1. On the left sidebar, select **Starred projects**.
@@ -259,8 +271,7 @@ Active pipeline schedules of archived projects don't become read-only.
Archived projects are:
- Labeled with an `archived` badge on the project page.
-- Listed on the group page in the **Inactive** tab.
-- Hidden from project lists in **Your Work** and **Explore**.
+- Listed in the **Inactive** tab on the group page, **Your work** page, and **Explore** page.
- Read-only.
Prerequisites:
@@ -328,9 +339,7 @@ You can sort projects by:
- Name
- Created date
- Updated date
-- Owner
-
-You can also choose to hide or show archived projects.
+- Stars
### Filter projects by language
@@ -343,10 +352,22 @@ You can filter projects by the programming language they use. To do this:
1. Select either:
- **View all your projects**, to filter your projects.
- **Explore**, to filter all projects you can access.
+1. Above the list of projects, select **Search or filter results**.
1. From the **Language** dropdown list, select the language you want to filter projects by.
A list of projects that use the selected language is displayed.
+### View only projects you own
+
+To view only the projects you are the owner of:
+
+1. On the left sidebar, select **Search or go to**.
+1. Select either:
+ - **View all your projects**, to filter your projects.
+ - **Explore**, to filter all projects you can access.
+1. Above the list of projects, select **Search or filter results**.
+1. From the **Role** dropdown list, select **Owner**.
+
## Rename a repository
A project's repository name defines its URL and its place on the file disk
diff --git a/lib/api/api.rb b/lib/api/api.rb
index b95912e8bad..73ee3787825 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -391,6 +391,7 @@ module API
mount ::API::Ml::Mlflow::Entrypoint
end
+ mount ::API::Internal::Autoflow
mount ::API::Internal::Base
mount ::API::Internal::Coverage if Gitlab::Utils.to_boolean(ENV['COVERBAND_ENABLED'], default: false)
mount ::API::Internal::Lfs
diff --git a/lib/api/helpers/kas_helpers.rb b/lib/api/helpers/kas_helpers.rb
new file mode 100644
index 00000000000..6bd9a25ba43
--- /dev/null
+++ b/lib/api/helpers/kas_helpers.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module KasHelpers
+ def authenticate_gitlab_kas_request!
+ render_api_error!('KAS JWT authentication invalid', 401) unless Gitlab::Kas.verify_api_request(headers)
+ end
+
+ def gitaly_info(project)
+ Gitlab::GitalyClient.connection_data(project.repository_storage)
+ end
+
+ def gitaly_repository(project)
+ project.repository.gitaly_repository.to_h
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/kubernetes/agent_helpers.rb b/lib/api/helpers/kubernetes/agent_helpers.rb
index a2b9c255e19..a8a8196d4e0 100644
--- a/lib/api/helpers/kubernetes/agent_helpers.rb
+++ b/lib/api/helpers/kubernetes/agent_helpers.rb
@@ -14,10 +14,6 @@ module API
'k8s_api_proxy_requests_via_pat_access' => 'request_api_proxy_access_via_pat'
}.freeze
- def authenticate_gitlab_kas_request!
- render_api_error!('KAS JWT authentication invalid', 401) unless Gitlab::Kas.verify_api_request(headers)
- end
-
def agent_token
cluster_agent_token_from_authorization_token
end
@@ -28,14 +24,6 @@ module API
end
strong_memoize_attr :agent
- def gitaly_info(project)
- Gitlab::GitalyClient.connection_data(project.repository_storage)
- end
-
- def gitaly_repository(project)
- project.repository.gitaly_repository.to_h
- end
-
def check_agent_token
unauthorized! unless agent_token
diff --git a/lib/api/internal/autoflow.rb b/lib/api/internal/autoflow.rb
new file mode 100644
index 00000000000..333a8afc81c
--- /dev/null
+++ b/lib/api/internal/autoflow.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module API
+ # AutoFlow Internal API
+ module Internal
+ class Autoflow < ::API::Base
+ before do
+ authenticate_gitlab_kas_request!
+ end
+
+ helpers ::API::Helpers::KasHelpers
+
+ namespace 'internal' do
+ namespace 'autoflow' do
+ desc 'Retrieve repository information' do
+ detail 'Retrieve repository information for the given project'
+ end
+ params do
+ requires :id, type: String, desc: 'ID or full path of the project'
+ end
+ get '/repository_info', feature_category: :deployment_management, urgency: :low do
+ project = find_project(params[:id])
+
+ not_found! unless project
+
+ status 200
+ {
+ project_id: project.id,
+ gitaly_info: gitaly_info(project),
+ gitaly_repository: gitaly_repository(project),
+ default_branch: project.default_branch_or_main
+ }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb
index dc8e288b3d7..782ce007c99 100644
--- a/lib/api/internal/kubernetes.rb
+++ b/lib/api/internal/kubernetes.rb
@@ -8,6 +8,7 @@ module API
authenticate_gitlab_kas_request!
end
+ helpers ::API::Helpers::KasHelpers
helpers ::API::Helpers::Kubernetes::AgentHelpers
namespace 'internal' do
diff --git a/lib/gitlab/kas/client.rb b/lib/gitlab/kas/client.rb
index 278a1ea13e1..3aa9655f44e 100644
--- a/lib/gitlab/kas/client.rb
+++ b/lib/gitlab/kas/client.rb
@@ -9,6 +9,7 @@ module Gitlab
STUB_CLASSES = {
agent_tracker: Gitlab::Agent::AgentTracker::Rpc::AgentTracker::Stub,
configuration_project: Gitlab::Agent::ConfigurationProject::Rpc::ConfigurationProject::Stub,
+ autoflow: Gitlab::Agent::AutoFlow::Rpc::AutoFlow::Stub,
notifications: Gitlab::Agent::Notifications::Rpc::Notifications::Stub
}.freeze
@@ -54,6 +55,34 @@ module Gitlab
.git_push_event(request, metadata: metadata)
end
+ def send_autoflow_event(project:, type:, id:, data:)
+ # We only want to send events if AutoFlow is enabled and no-op otherwise
+ return unless Feature.enabled?(:autoflow_enabled, project)
+
+ project_proto = Gitlab::Agent::Event::Project.new(
+ id: project.id,
+ full_path: project.full_path
+ )
+ request = Gitlab::Agent::AutoFlow::Rpc::CloudEventRequest.new(
+ event: Gitlab::Agent::Event::CloudEvent.new(
+ id: id,
+ source: "GitLab",
+ spec_version: "v1",
+ type: type,
+ attributes: {
+ datacontenttype: Gitlab::Agent::Event::CloudEvent::CloudEventAttributeValue.new(
+ ce_string: "application/json"
+ )
+ },
+ text_data: data.to_json
+ ),
+ flow_project: project_proto
+ )
+
+ stub_for(:autoflow)
+ .cloud_event(request, metadata: metadata)
+ end
+
private
def stub_for(service)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9f1e233a2e0..9b4e5d896fd 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -15825,6 +15825,9 @@ msgstr ""
msgid "CreateValueStreamForm|'%{name}' Value Stream created"
msgstr ""
+msgid "CreateValueStreamForm|'%{name}' Value Stream has been successfully created."
+msgstr ""
+
msgid "CreateValueStreamForm|'%{name}' Value Stream saved"
msgstr ""
@@ -57953,6 +57956,9 @@ msgstr ""
msgid "UserMapping|Don't reassign"
msgstr ""
+msgid "UserMapping|Download the pre-filled CSV template."
+msgstr ""
+
msgid "UserMapping|Import details:"
msgstr ""
@@ -57974,6 +57980,9 @@ msgstr ""
msgid "UserMapping|Notify again"
msgstr ""
+msgid "UserMapping|Once you select \"Reassign\", the processing will start and users will receive and email to accept the contribution reassignment. Once a users has accepted the reassignment, it cannot be undone. Check all data is correct before continuing."
+msgstr ""
+
msgid "UserMapping|Original user: %{source_name} (%{source_username})"
msgstr ""
@@ -58001,6 +58010,9 @@ msgstr ""
msgid "UserMapping|Reassign placeholder to"
msgstr ""
+msgid "UserMapping|Reassign with CSV file"
+msgstr ""
+
msgid "UserMapping|Reassigned"
msgstr ""
@@ -58058,6 +58070,9 @@ msgstr ""
msgid "UserMapping|Rejected"
msgstr ""
+msgid "UserMapping|Review and complete filling out the CSV file."
+msgstr ""
+
msgid "UserMapping|Review reassignment details"
msgstr ""
@@ -58094,6 +58109,12 @@ msgstr ""
msgid "UserMapping|To learn more about reassignments, visit the docs (%{help_link}). If you don't recognize this request, report abuse (%{report_link})."
msgstr ""
+msgid "UserMapping|Upload reviewed and completed CSV file."
+msgstr ""
+
+msgid "UserMapping|Use a CSV file to reassign contributions from placeholder users to existing group members. This can be done in a few steps. %{linkStart}Learn more about matching users by CSV%{linkEnd}."
+msgstr ""
+
msgid "UserMapping|You have approved the reassignment of contributions from %{strong_open}%{source_user_name} (@%{source_username})%{strong_close} on %{strong_open}%{source_hostname}%{strong_close} to yourself on the group %{strong_open}%{destination_group}%{strong_close}. Reassignment processing is in progress. You'll have access to the group soon."
msgstr ""
diff --git a/spec/frontend/members/placeholders/components/app_spec.js b/spec/frontend/members/placeholders/components/app_spec.js
index 6cb3a3dd623..0400c52e944 100644
--- a/spec/frontend/members/placeholders/components/app_spec.js
+++ b/spec/frontend/members/placeholders/components/app_spec.js
@@ -2,15 +2,16 @@ import Vue, { nextTick } from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import VueApollo from 'vue-apollo';
-import { shallowMount } from '@vue/test-utils';
-import { GlTab, GlTabs } from '@gitlab/ui';
+import { GlTab, GlTabs, GlModal } from '@gitlab/ui';
import { createAlert } from '~/alert';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import setWindowLocation from 'helpers/set_window_location_helper';
-
+import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
import PlaceholdersTabApp from '~/members/placeholders/components/app.vue';
import PlaceholdersTable from '~/members/placeholders/components/placeholders_table.vue';
+import CsvUploadModal from '~/members/placeholders/components/csv_upload_modal.vue';
import importSourceUsersQuery from '~/members/placeholders/graphql/queries/import_source_users.query.graphql';
import { MEMBERS_TAB_TYPES } from '~/members/constants';
import {
@@ -38,7 +39,10 @@ describe('PlaceholdersTabApp', () => {
show: jest.fn(),
};
- const createComponent = ({ queryHandler = sourceUsersQueryHandler } = {}) => {
+ const createComponent = ({
+ queryHandler = sourceUsersQueryHandler,
+ mountFn = shallowMountExtended,
+ } = {}) => {
store = new Vuex.Store({
modules: {
[MEMBERS_TAB_TYPES.placeholder]: {
@@ -52,20 +56,31 @@ describe('PlaceholdersTabApp', () => {
mockApollo = createMockApollo([[importSourceUsersQuery, queryHandler]]);
- wrapper = shallowMount(PlaceholdersTabApp, {
+ wrapper = mountFn(PlaceholdersTabApp, {
apolloProvider: mockApollo,
store,
provide: {
group: mockGroup,
+ reassignmentCsvDownloadPath: 'foo/bar',
},
mocks: { $toast },
- stubs: { GlTab },
+ stubs: {
+ GlTabs: stubComponent(GlTabs, {
+ template: RENDER_ALL_SLOTS_TEMPLATE,
+ }),
+ GlTab,
+ GlModal: stubComponent(GlModal, {
+ template: RENDER_ALL_SLOTS_TEMPLATE,
+ }),
+ },
});
};
const findTabs = () => wrapper.findComponent(GlTabs);
const findTabAt = (index) => wrapper.findAllComponents(GlTab).at(index);
const findPlaceholdersTable = () => wrapper.findComponent(PlaceholdersTable);
+ const findReassignCsvButton = () => wrapper.findByTestId('reassign-csv-button');
+ const findCsvModal = () => wrapper.findComponent(CsvUploadModal);
it('renders tabs', () => {
createComponent();
@@ -250,4 +265,23 @@ describe('PlaceholdersTabApp', () => {
});
});
});
+
+ describe('reassign CSV button', () => {
+ it('renders the button and the modal', () => {
+ createComponent();
+
+ expect(findReassignCsvButton().exists()).toBe(true);
+ expect(findCsvModal().exists()).toBe(true);
+ });
+
+ it('shows modal when button is clicked', async () => {
+ createComponent({ mountFn: mountExtended });
+
+ findReassignCsvButton().trigger('click');
+
+ await nextTick();
+
+ expect(findCsvModal().findComponent(GlModal).isVisible()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js b/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js
new file mode 100644
index 00000000000..26f9c6275ac
--- /dev/null
+++ b/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js
@@ -0,0 +1,36 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import CsvUploadModal from '~/members/placeholders/components/csv_upload_modal.vue';
+
+describe('CsvUploadModal', () => {
+ let wrapper;
+
+ const defaultInjectedAttributes = {
+ reassignmentCsvDownloadPath: 'foo/bar',
+ };
+
+ const findDownloadLink = () => wrapper.findByTestId('csv-download-button');
+
+ function createComponent() {
+ return shallowMountExtended(CsvUploadModal, {
+ propsData: {
+ modalId: 'csv-upload-modal',
+ },
+ provide: {
+ ...defaultInjectedAttributes,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ it('has the CSV download button with the required attributes', () => {
+ const downloadLink = findDownloadLink();
+
+ expect(downloadLink.exists()).toBe(true);
+ expect(downloadLink.attributes('href')).toBe(
+ defaultInjectedAttributes.reassignmentCsvDownloadPath,
+ );
+ });
+});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
index 400cb2b52ac..0b77fea3e85 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
@@ -181,7 +181,7 @@ describe('WorkItemLinksForm', () => {
expect(findInput().props('state')).toBe(false);
});
- it('creates child task in non confidential parent', async () => {
+ it('creates child task in non confidential parent and closes the form', async () => {
findInput().vm.$emit('input', 'Create task test');
findForm().vm.$emit('submit', {
@@ -201,6 +201,7 @@ describe('WorkItemLinksForm', () => {
},
});
expect(wrapper.emitted('addChild')).toEqual([[]]);
+ expect(wrapper.emitted('cancel')).toEqual([[]]);
});
it('creates child task in confidential parent', async () => {
@@ -244,7 +245,7 @@ describe('WorkItemLinksForm', () => {
expect(findWorkItemTokenInput().exists()).toBe(false);
});
- it('creates child issue in non confidential parent', async () => {
+ it('creates child issue in non confidential parent and closes the form', async () => {
findInput().vm.$emit('input', 'Create issue test');
findProjectSelector().vm.$emit('selectProject', projectData[0].fullPath);
@@ -267,6 +268,7 @@ describe('WorkItemLinksForm', () => {
},
});
expect(wrapper.emitted('addChild')).toEqual([[]]);
+ expect(wrapper.emitted('cancel')).toEqual([[]]);
});
it('creates child issue in confidential parent', async () => {
@@ -423,7 +425,7 @@ describe('WorkItemLinksForm', () => {
});
});
- it('selects and adds children', async () => {
+ it('selects, adds children and closes the form', async () => {
await selectAvailableWorkItemTokens();
expect(findAddChildButton().text()).toBe('Add tasks');
@@ -435,7 +437,9 @@ describe('WorkItemLinksForm', () => {
preventDefault: jest.fn(),
});
await waitForPromises();
+
expect(updateMutationResolver).toHaveBeenCalled();
+ expect(wrapper.emitted('cancel')).toEqual([[]]);
});
it('shows validation error when non-confidential child items are being added to confidential parent', async () => {
diff --git a/spec/lib/gitlab/kas/client_spec.rb b/spec/lib/gitlab/kas/client_spec.rb
index 1f00c2b2a11..0e0a80903bf 100644
--- a/spec/lib/gitlab/kas/client_spec.rb
+++ b/spec/lib/gitlab/kas/client_spec.rb
@@ -37,12 +37,12 @@ RSpec.describe Gitlab::Kas::Client do
allow(Gitlab::Kas).to receive(:enabled?).and_return(true)
allow(Gitlab::Kas).to receive(:internal_url).and_return(kas_url)
- expect(JSONWebToken::HMACToken).to receive(:new)
+ allow(JSONWebToken::HMACToken).to receive(:new)
.with(Gitlab::Kas.secret)
.and_return(token)
- expect(token).to receive(:issuer=).with(Settings.gitlab.host)
- expect(token).to receive(:audience=).with(described_class::JWT_AUDIENCE)
+ allow(token).to receive(:issuer=).with(Settings.gitlab.host)
+ allow(token).to receive(:audience=).with(described_class::JWT_AUDIENCE)
end
describe '#get_connected_agents_by_agent_ids' do
@@ -110,6 +110,59 @@ RSpec.describe Gitlab::Kas::Client do
it { expect(subject).to eq(agent_configurations) }
end
+ describe '#send_autoflow_event' do
+ subject { described_class.new.send_autoflow_event(project: project, type: 'any-type', id: 'any-id', data: { 'any-data-key': 'any-data-value' }) }
+
+ context 'when autoflow_enabled FF is disabled' do
+ before do
+ stub_feature_flags(autoflow_enabled: false)
+ end
+
+ it { expect(subject).to be_nil }
+ end
+
+ context 'when autoflow_enabled FF is enabled' do
+ let(:stub) { instance_double(Gitlab::Agent::AutoFlow::Rpc::AutoFlow::Stub) }
+ let(:request) { instance_double(Gitlab::Agent::AutoFlow::Rpc::CloudEventRequest) }
+ let(:event_param) { instance_double(Gitlab::Agent::Event::CloudEvent) }
+ let(:project_param) { instance_double(Gitlab::Agent::Event::Project) }
+ let(:response) { double(Gitlab::Agent::AutoFlow::Rpc::CloudEventResponse) }
+
+ before do
+ stub_feature_flags(autoflow_enabled: true)
+
+ expect(Gitlab::Agent::AutoFlow::Rpc::AutoFlow::Stub).to receive(:new)
+ .with('example.kas.internal', :this_channel_is_insecure, timeout: described_class::TIMEOUT)
+ .and_return(stub)
+
+ expect(Gitlab::Agent::Event::Project).to receive(:new)
+ .with(id: project.id, full_path: project.full_path)
+ .and_return(project_param)
+
+ expect(Gitlab::Agent::Event::CloudEvent).to receive(:new)
+ .with(id: 'any-id', source: "GitLab", spec_version: "v1", type: 'any-type',
+ attributes: {
+ datacontenttype: Gitlab::Agent::Event::CloudEvent::CloudEventAttributeValue.new(
+ ce_string: "application/json"
+ )
+ },
+ text_data: '{"any-data-key":"any-data-value"}'
+ )
+ .and_return(event_param)
+
+ expect(Gitlab::Agent::AutoFlow::Rpc::CloudEventRequest).to receive(:new)
+ .with(event: event_param, flow_project: project_param)
+ .and_return(request)
+
+ expect(stub).to receive(:cloud_event)
+ .with(request, metadata: { 'authorization' => 'bearer test-token' })
+ .and_return(response)
+ end
+
+ it { expect(subject).to eq(response) }
+ end
+ end
+
describe '#send_git_push_event' do
let(:stub) { instance_double(Gitlab::Agent::Notifications::Rpc::Notifications::Stub) }
let(:request) { instance_double(Gitlab::Agent::Notifications::Rpc::GitPushEventRequest) }
diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb
index bbf04ef9430..526045375cd 100644
--- a/spec/models/ci/build_trace_chunks/fog_spec.rb
+++ b/spec/models/ci/build_trace_chunks/fog_spec.rb
@@ -207,7 +207,7 @@ RSpec.describe Ci::BuildTraceChunks::Fog do
create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 1, build: build)
end
- it 'deletes multiple data' do
+ it 'deletes multiple data', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/456375' do
files = connection.directories.new(key: bucket).files
expect(files.count).to eq(2)
diff --git a/spec/requests/api/internal/autoflow_spec.rb b/spec/requests/api/internal/autoflow_spec.rb
new file mode 100644
index 00000000000..18af19eee1f
--- /dev/null
+++ b/spec/requests/api/internal/autoflow_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Internal::Autoflow, feature_category: :deployment_management do
+ let(:jwt_auth_headers) do
+ jwt_token = JWT.encode(
+ { 'iss' => Gitlab::Kas::JWT_ISSUER, 'aud' => Gitlab::Kas::JWT_AUDIENCE },
+ Gitlab::Kas.secret,
+ 'HS256'
+ )
+
+ { Gitlab::Kas::INTERNAL_API_KAS_REQUEST_HEADER => jwt_token }
+ end
+
+ let(:jwt_secret) { SecureRandom.random_bytes(Gitlab::Kas::SECRET_LENGTH) }
+
+ before do
+ allow(Gitlab::Kas).to receive(:secret).and_return(jwt_secret)
+ end
+
+ shared_examples 'authorization' do
+ context 'when not authenticated' do
+ it 'returns 401' do
+ send_request(headers: { Gitlab::Kas::INTERNAL_API_KAS_REQUEST_HEADER => '' })
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'GET /internal/autoflow/repository_info' do
+ def send_request(headers: {}, params: {})
+ get api('/internal/autoflow/repository_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
+ end
+
+ def expect_success_response
+ expect(response).to have_gitlab_http_status(:success)
+
+ expect(json_response).to match(
+ a_hash_including(
+ 'project_id' => project.id,
+ 'gitaly_info' => a_hash_including(
+ 'address' => match(/\.socket$/),
+ 'token' => 'secret'
+ ),
+ 'gitaly_repository' => a_hash_including(
+ 'storage_name' => project.repository_storage,
+ 'relative_path' => "#{project.disk_path}.git",
+ 'gl_repository' => "project-#{project.id}",
+ 'gl_project_path' => project.full_path
+ ),
+ 'default_branch' => project.default_branch_or_main
+ )
+ )
+ end
+
+ include_examples 'authorization'
+
+ context 'when project exists' do
+ let_it_be(:project) { create(:project) }
+
+ it 'returns expected data for numerical project id', :aggregate_failures do
+ send_request(params: { id: project.id })
+
+ expect_success_response
+ end
+
+ it 'returns expected data for project full path', :aggregate_failures do
+ send_request(params: { id: project.full_path })
+
+ expect_success_response
+ end
+ end
+
+ context 'when project does not exists' do
+ it 'returns expected data', :aggregate_failures do
+ send_request(params: { id: non_existing_record_id })
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end