diff --git a/app/assets/javascripts/observability/client.js b/app/assets/javascripts/observability/client.js
index 81ca75579c0..f513d3b9b30 100644
--- a/app/assets/javascripts/observability/client.js
+++ b/app/assets/javascripts/observability/client.js
@@ -613,7 +613,7 @@ export async function fetchLogs(logsSearchUrl, { pageToken, pageSize, filters =
}
}
-export async function fetchLogsSearchMetadata(_logsSearchMetadataUrl, { filters = {} }) {
+export async function fetchLogsSearchMetadata(logsSearchMetadataUrl, { filters = {} } = {}) {
try {
const params = new URLSearchParams();
@@ -626,354 +626,11 @@ export async function fetchLogsSearchMetadata(_logsSearchMetadataUrl, { filters
addLogsAttributesFiltersToQueryParams(attributes, params);
}
- // TODO remove mocks (and add UTs) when API is ready https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2782
- // const { data } = await axios.get(logsSearchMetadataUrl, {
- // withCredentials: true,
- // params,
- // });
- // return data;
-
- return {
- start_ts: 1713513680617331200,
- end_ts: 1714723280617331200,
- summary: {
- service_names: ['adservice', 'cartservice', 'quoteservice', 'recommendationservice'],
- trace_flags: [0, 1],
- severity_names: ['info', 'warn'],
- severity_numbers: [9, 13],
- },
- severity_numbers_counts: [
- {
- time: 1713519360000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713545280000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713571200000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713597120000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713623040000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713648960000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713674880000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713700800000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713726720000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713752640000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713778560000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713804480000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713830400000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713856320000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713882240000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713908160000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713934080000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713960000000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1713985920000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714011840000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714037760000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714063680000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714089600000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714115520000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714141440000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714167360000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714193280000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714219200000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714245120000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714271040000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714296960000000000,
- counts: {
- 13: 0,
- 9: 0,
- },
- },
- {
- time: 1714322880000000000,
- counts: {
- 13: 1,
- 9: 26202,
- },
- },
- {
- time: 1714348800000000000,
- counts: {
- 13: 0,
- 9: 53103,
- },
- },
- {
- time: 1714374720000000000,
- counts: {
- 13: 0,
- 9: 52854,
- },
- },
- {
- time: 1714400640000000000,
- counts: {
- 13: 0,
- 9: 49598,
- },
- },
- {
- time: 1714426560000000000,
- counts: {
- 13: 0,
- 9: 45266,
- },
- },
- {
- time: 1714452480000000000,
- counts: {
- 13: 0,
- 9: 44951,
- },
- },
- {
- time: 1714478400000000000,
- counts: {
- 13: 0,
- 9: 45096,
- },
- },
- {
- time: 1714504320000000000,
- counts: {
- 13: 0,
- 9: 45301,
- },
- },
- {
- time: 1714530240000000000,
- counts: {
- 13: 0,
- 9: 44894,
- },
- },
- {
- time: 1714556160000000000,
- counts: {
- 13: 0,
- 9: 45444,
- },
- },
- {
- time: 1714582080000000000,
- counts: {
- 13: 0,
- 9: 45067,
- },
- },
- {
- time: 1714608000000000000,
- counts: {
- 13: 0,
- 9: 45119,
- },
- },
- {
- time: 1714633920000000000,
- counts: {
- 13: 0,
- 9: 45817,
- },
- },
- {
- time: 1714659840000000000,
- counts: {
- 13: 0,
- 9: 44574,
- },
- },
- {
- time: 1714685760000000000,
- counts: {
- 13: 0,
- 9: 44652,
- },
- },
- {
- time: 1714711680000000000,
- counts: {
- 13: 0,
- 9: 20470,
- },
- },
- ],
- };
+ const { data } = await axios.get(logsSearchMetadataUrl, {
+ withCredentials: true,
+ params,
+ });
+ return data;
} catch (e) {
return reportErrorAndThrow(e);
}
diff --git a/app/graphql/resolvers/projects/user_contributed_projects_resolver.rb b/app/graphql/resolvers/projects/user_contributed_projects_resolver.rb
new file mode 100644
index 00000000000..218b77d760f
--- /dev/null
+++ b/app/graphql/resolvers/projects/user_contributed_projects_resolver.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Projects
+ class UserContributedProjectsResolver < BaseResolver
+ type Types::ProjectType.connection_type, null: true
+
+ argument :sort, Types::Projects::ProjectSortEnum,
+ description: 'Sort contributed projects.',
+ required: false,
+ default_value: :latest_activity_desc
+
+ alias_method :user, :object
+
+ def resolve(**args)
+ ContributedProjectsFinder.new(user).execute(current_user, order_by: args[:sort]).joined(user)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/projects/project_sort_enum.rb b/app/graphql/types/projects/project_sort_enum.rb
new file mode 100644
index 00000000000..d4a391aff7c
--- /dev/null
+++ b/app/graphql/types/projects/project_sort_enum.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ module Projects
+ class ProjectSortEnum < SortEnum
+ graphql_name 'ProjectSort'
+ description 'Values for sorting projects'
+
+ value 'ID_ASC', 'ID by ascending order.', value: :id_asc
+ value 'ID_DESC', 'ID by descending order.', value: :id_desc
+ value 'LATEST_ACTIVITY_ASC', 'Latest activity by ascending order.', value: :latest_activity_asc
+ value 'LATEST_ACTIVITY_DESC', 'Latest activity by descending order.', value: :latest_activity_desc
+ value 'NAME_ASC', 'Name by ascending order.', value: :name_asc
+ value 'NAME_DESC', 'Name by descending order.', value: :name_desc
+ value 'PATH_ASC', 'Path by ascending order.', value: :path_asc
+ value 'PATH_DESC', 'Path by descending order.', value: :path_desc
+ value 'STARS_ASC', 'Stars by ascending order.', value: :stars_asc
+ value 'STARS_DESC', 'Stars by descending order.', value: :stars_desc
+ end
+ end
+end
diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb
index 45c24f5ef30..fdf04b5db0e 100644
--- a/app/graphql/types/user_interface.rb
+++ b/app/graphql/types/user_interface.rb
@@ -101,6 +101,9 @@ module Types
field :starred_projects,
description: 'Projects starred by the user.',
resolver: Resolvers::UserStarredProjectsResolver
+ field :contributed_projects,
+ description: 'Projects the user has contributed to.',
+ resolver: Resolvers::Projects::UserContributedProjectsResolver
field :namespace,
type: Types::NamespaceType,
null: true,
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index 403efcd0b01..c8f7bf69c6d 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -73,11 +73,7 @@ module AppearancesHelper
end
def custom_sign_in_description
- [
- markdown_field(current_appearance, :description),
- markdown(Gitlab::CurrentSettings.sign_in_text),
- markdown(Gitlab::CurrentSettings.help_text)
- ].compact_blank.join("
").html_safe
+ markdown_field(current_appearance, :description)
end
def brand_member_guidelines
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 51f912b5e4e..f9515bed57c 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -25,6 +25,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
container_registry_import_target_plan
container_registry_import_created_before
], remove_with: '17.2', remove_after: '2024-06-24'
+ ignore_column %i[sign_in_text help_text], remove_with: '17.3', remove_after: '2024-08-15'
INSTANCE_REVIEW_MIN_USERS = 50
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index f5c521c2cc6..8cd1fd7c624 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -15785,6 +15785,22 @@ four standard [pagination arguments](#pagination-arguments):
| `updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| `updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
+##### `AddOnUser.contributedProjects`
+
+Projects the user has contributed to.
+
+Returns [`ProjectConnection`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
+
##### `AddOnUser.groups`
Groups where the user has access.
@@ -16549,6 +16565,22 @@ four standard [pagination arguments](#pagination-arguments):
| `updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| `updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
+##### `AutocompletedUser.contributedProjects`
+
+Projects the user has contributed to.
+
+Returns [`ProjectConnection`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
+
##### `AutocompletedUser.groups`
Groups where the user has access.
@@ -18725,6 +18757,22 @@ four standard [pagination arguments](#pagination-arguments):
| `updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| `updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
+##### `CurrentUser.contributedProjects`
+
+Projects the user has contributed to.
+
+Returns [`ProjectConnection`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
+
##### `CurrentUser.groups`
Groups where the user has access.
@@ -24001,6 +24049,22 @@ four standard [pagination arguments](#pagination-arguments):
| `updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| `updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
+##### `MergeRequestAssignee.contributedProjects`
+
+Projects the user has contributed to.
+
+Returns [`ProjectConnection`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
+
##### `MergeRequestAssignee.groups`
Groups where the user has access.
@@ -24313,6 +24377,22 @@ four standard [pagination arguments](#pagination-arguments):
| `updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| `updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
+##### `MergeRequestAuthor.contributedProjects`
+
+Projects the user has contributed to.
+
+Returns [`ProjectConnection`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
+
##### `MergeRequestAuthor.groups`
Groups where the user has access.
@@ -24672,6 +24752,22 @@ four standard [pagination arguments](#pagination-arguments):
| `updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| `updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
+##### `MergeRequestParticipant.contributedProjects`
+
+Projects the user has contributed to.
+
+Returns [`ProjectConnection`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
+
##### `MergeRequestParticipant.groups`
Groups where the user has access.
@@ -25020,6 +25116,22 @@ four standard [pagination arguments](#pagination-arguments):
| `updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| `updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
+##### `MergeRequestReviewer.contributedProjects`
+
+Projects the user has contributed to.
+
+Returns [`ProjectConnection`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
+
##### `MergeRequestReviewer.groups`
Groups where the user has access.
@@ -27431,6 +27543,7 @@ four standard [pagination arguments](#pagination-arguments):
| `componentNames` | [`[String!]`](#string) | Filter dependencies by component names. |
| `packageManagers` | [`[PackageManager!]`](#packagemanager) | Filter dependencies by package managers. |
| `sort` | [`DependencySort`](#dependencysort) | Sort dependencies by given criteria. |
+| `sourceTypes` | [`[SbomSourceType!]`](#sbomsourcetype) | Filter dependencies by source type. |
##### `Project.deployment`
@@ -30727,6 +30840,22 @@ four standard [pagination arguments](#pagination-arguments):
| `updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| `updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
+##### `UserCore.contributedProjects`
+
+Projects the user has contributed to.
+
+Returns [`ProjectConnection`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
+
##### `UserCore.groups`
Groups where the user has access.
@@ -34477,6 +34606,31 @@ Project member relation.
| `INVITED_GROUPS` | Invited Groups members. |
| `SHARED_INTO_ANCESTORS` | Shared Into Ancestors members. |
+### `ProjectSort`
+
+Values for sorting projects.
+
+| Value | Description |
+| ----- | ----------- |
+| `CREATED_ASC` | Created at ascending order. |
+| `CREATED_DESC` | Created at descending order. |
+| `ID_ASC` | ID by ascending order. |
+| `ID_DESC` | ID by descending order. |
+| `LATEST_ACTIVITY_ASC` | Latest activity by ascending order. |
+| `LATEST_ACTIVITY_DESC` | Latest activity by descending order. |
+| `NAME_ASC` | Name by ascending order. |
+| `NAME_DESC` | Name by descending order. |
+| `PATH_ASC` | Path by ascending order. |
+| `PATH_DESC` | Path by descending order. |
+| `STARS_ASC` | Stars by ascending order. |
+| `STARS_DESC` | Stars by descending order. |
+| `UPDATED_ASC` | Updated at ascending order. |
+| `UPDATED_DESC` | Updated at descending order. |
+| `created_asc` **{warning-solid}** | **Deprecated** in GitLab 13.5. This was renamed. Use: `CREATED_ASC`. |
+| `created_desc` **{warning-solid}** | **Deprecated** in GitLab 13.5. This was renamed. Use: `CREATED_DESC`. |
+| `updated_asc` **{warning-solid}** | **Deprecated** in GitLab 13.5. This was renamed. Use: `UPDATED_ASC`. |
+| `updated_desc` **{warning-solid}** | **Deprecated** in GitLab 13.5. This was renamed. Use: `UPDATED_DESC`. |
+
### `RefType`
Type of ref.
@@ -34584,6 +34738,17 @@ Size of UI component in SAST configuration page.
| `MEDIUM` | Size of UI component in SAST configuration page is medium. |
| `SMALL` | Size of UI component in SAST configuration page is small. |
+### `SbomSourceType`
+
+Values for sbom source types.
+
+| Value | Description |
+| ----- | ----------- |
+| `CONTAINER_SCANNING` | Source Type: container_scanning. |
+| `CONTAINER_SCANNING_FOR_REGISTRY` | Source Type: container_scanning_for_registry. |
+| `DEPENDENCY_SCANNING` | Source Type: dependency_scanning. |
+| `NIL_SOURCE` | Enum source nil. |
+
### `ScanStatus`
The status of the security scan.
@@ -36983,6 +37148,22 @@ four standard [pagination arguments](#pagination-arguments):
| `updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| `updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
+###### `User.contributedProjects`
+
+Projects the user has contributed to.
+
+Returns [`ProjectConnection`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+####### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
+
###### `User.groups`
Groups where the user has access.
diff --git a/doc/ci/cloud_services/index.md b/doc/ci/cloud_services/index.md
index eb02b3d1978..10431d5fb72 100644
--- a/doc/ci/cloud_services/index.md
+++ b/doc/ci/cloud_services/index.md
@@ -57,7 +57,11 @@ Each job can be configured with ID tokens, which are provided as a CI/CD variabl
### Authorization workflow
```mermaid
+%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
+accTitle: Authorization workflow
+accDescr: The flow of authorization requests between GitLab and a cloud provider.
+
participant GitLab
Note right of Cloud: Create OIDC identity provider
Note right of Cloud: Create role with conditionals
diff --git a/doc/ci/pipelines/pipeline_architectures.md b/doc/ci/pipelines/pipeline_architectures.md
index 0f7d411d6e5..741b2e17ef9 100644
--- a/doc/ci/pipelines/pipeline_architectures.md
+++ b/doc/ci/pipelines/pipeline_architectures.md
@@ -42,19 +42,26 @@ It's not the most efficient, and if you have lots of steps it can grow quite com
easier to maintain:
```mermaid
+%%{init: { "fontFamily": "GitLab Sans" }}%%
graph LR
+accTitle: Basic pipelines
+accDescr: Shows a pipeline that runs sequentially through the build, test, and deploy stages.
+
subgraph deploy stage
deploy --> deploy_a
deploy --> deploy_b
end
+
subgraph test stage
test --> test_a
test --> test_b
end
+
subgraph build stage
build --> build_a
build --> build_b
end
+
build_a -.-> test
build_b -.-> test
test_a -.-> deploy
@@ -121,7 +128,11 @@ In the example below, if `build_a` and `test_a` are much faster than `build_b` a
`test_b`, GitLab starts `deploy_a` even if `build_b` is still running.
```mermaid
+%%{init: { "fontFamily": "GitLab Sans" }}%%
graph LR
+accTitle: Pipeline using DAG
+accDescr: Shows how two jobs can start without waiting for earlier stages to complete
+
subgraph Pipeline using DAG
build_a --> test_a --> deploy_a
build_b --> test_b --> deploy_b
@@ -210,7 +221,11 @@ You can combine parent-child pipelines with:
- [DAG pipelines](#directed-acyclic-graph-pipelines) inside of child pipelines, achieving the benefits of both.
```mermaid
+%%{init: { "fontFamily": "GitLab Sans" }}%%
graph LR
+accTitle: Parent and child pipelines
+accDescr: Shows that a parent pipeline can trigger independent child pipelines
+
subgraph Parent pipeline
trigger_a -.-> build_a
trigger_b -.-> build_b
diff --git a/doc/ci/runners/long_polling.md b/doc/ci/runners/long_polling.md
index 28be6512e02..b9c370f75b5 100644
--- a/doc/ci/runners/long_polling.md
+++ b/doc/ci/runners/long_polling.md
@@ -120,7 +120,11 @@ You can see an [example of how one user discovered an issue with long polling wi
The diagram shows how a single runner gets a job with long polling enabled:
```mermaid
+%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
+accTitle: Long polling workflow
+accDescr: The flow of a single runner getting a job with long polling enabled
+
autonumber
participant C as Runner
participant W as Workhorse
diff --git a/doc/integration/mattermost/index.md b/doc/integration/mattermost/index.md
index 783cceffe8b..620b8235364 100644
--- a/doc/integration/mattermost/index.md
+++ b/doc/integration/mattermost/index.md
@@ -394,7 +394,11 @@ provider for Mattermost. You can use this to troubleshoot errors
in getting the integration to work:
```mermaid
+%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
+accTitle: GitLab as OAuth 2.0 provider
+accDescr: Sequence of actions that happen when a user authenticates to GitLab through Mattermost.
+
User->>Mattermost: GET https://mm.domain.com
Note over Mattermost, GitLab: Obtain access code
Mattermost->>GitLab: GET https://gitlab.domain.com/oauth/authorize
diff --git a/doc/integration/partner_marketplace.md b/doc/integration/partner_marketplace.md
index cbcb8f70164..582c80204f2 100644
--- a/doc/integration/partner_marketplace.md
+++ b/doc/integration/partner_marketplace.md
@@ -28,7 +28,11 @@ The following example shows a typical purchase flow of request and response betw
- Salesforce
```mermaid
+%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
+accTitle: Purchase flow
+accDescr: Shows the flow of a purchase from the customer, through the customer portal, Zuora, and Salesforce.
+
participant Customer
participant Marketplace partner system
participant Customers Portal
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 840ea93aaf7..fe32300d1fd 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -181,7 +181,7 @@ The following languages and dependency managers are supported when using the Dep
setup.py
+ Excludes both pip and setuptools from the report as they are required by the installer.
+
1
') - expect(helper.custom_sign_in_description).to eq('11
') end end diff --git a/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_notes/approved_event_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_notes/approved_event_spec.rb index 036d40f254d..c7d68f01449 100644 --- a/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_notes/approved_event_spec.rb +++ b/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_notes/approved_event_spec.rb @@ -61,23 +61,44 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestNotes::Appro end context 'when a user with a matching username does not exist' do - before do - pull_request_author.update!(username: 'another_username') + let(:approved_event) { super().merge(approver_username: 'another_username') } + + it 'does not set an approver' do + expect_log( + stage: 'import_approved_event', + message: 'skipped due to missing user', + iid: merge_request.iid, + event_id: 4 + ) + + expect { importer.execute(approved_event) } + .to not_change { merge_request.approvals.count } + .and not_change { merge_request.notes.count } + .and not_change { merge_request.reviewers.count } + + expect(merge_request.approvals).to be_empty end - it 'finds the user based on email' do - importer.execute(approved_event) + context 'when bitbucket_server_user_mapping_by_username flag is disabled' do + before do + stub_feature_flags(bitbucket_server_user_mapping_by_username: false) + end - approval = merge_request.approvals.first + it 'finds the user based on email' do + importer.execute(approved_event) - expect(approval.user).to eq(pull_request_author) + approval = merge_request.approvals.first + + expect(approval.user).to eq(pull_request_author) + end end context 'when no users match email or username' do - let_it_be(:another_author) { create(:user) } - - before do - pull_request_author.destroy! + let(:approved_event) do + super().merge( + approver_username: 'another_username', + approver_email: 'anotheremail@example.com' + ) end it 'does not set an approver' do diff --git a/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_notes_importer_spec.rb index 11c42e715eb..7f7d12eb8fc 100644 --- a/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_notes_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_notes_importer_spec.rb @@ -342,12 +342,27 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestNotesImporte pull_request_author.update!(username: 'another_username') end - it 'finds the user based on email' do - importer.execute + it 'does not set an approver' do + expect { importer.execute } + .to not_change { merge_request.approvals.count } + .and not_change { merge_request.notes.count } + .and not_change { merge_request.reviewers.count } - approval = merge_request.approvals.first + expect(merge_request.approvals).to be_empty + end - expect(approval.user).to eq(pull_request_author) + context 'when bitbucket_server_user_mapping_by_username flag is disabled' do + before do + stub_feature_flags(bitbucket_server_user_mapping_by_username: false) + end + + it 'finds the user based on email' do + importer.execute + + approval = merge_request.approvals.first + + expect(approval.user).to eq(pull_request_author) + end end context 'when no users match email or username' do diff --git a/spec/lib/gitlab/bitbucket_server_import/user_finder_spec.rb b/spec/lib/gitlab/bitbucket_server_import/user_finder_spec.rb index 16aff872c6d..78d258bd444 100644 --- a/spec/lib/gitlab/bitbucket_server_import/user_finder_spec.rb +++ b/spec/lib/gitlab/bitbucket_server_import/user_finder_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::BitbucketServerImport::UserFinder, :clean_gitlab_redis_sh let_it_be(:user) { create(:user) } let(:created_id) { 1 } - let(:project) { instance_double(Project, creator_id: created_id, id: 1) } + let(:project) { build_stubbed(:project, creator_id: created_id, id: 1) } subject(:user_finder) { described_class.new(project) } diff --git a/spec/requests/api/graphql/user/contributed_projects_query_spec.rb b/spec/requests/api/graphql/user/contributed_projects_query_spec.rb new file mode 100644 index 00000000000..87dd1c1ded6 --- /dev/null +++ b/spec/requests/api/graphql/user/contributed_projects_query_spec.rb @@ -0,0 +1,407 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Getting contributedProjects of the user', feature_category: :groups_and_projects do + include GraphqlHelpers + + let(:query) { graphql_query_for(:user, user_params, user_fields) } + let(:user_params) { { username: user.username } } + let(:user_fields) { 'contributedProjects { nodes { id } }' } + + let_it_be(:user) { create(:user) } + 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(:path) { %i[user contributed_projects nodes] } + + before_all do + private_project.add_developer(user) + private_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) } + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end + + describe 'sorting' do + let(:user_fields_with_sort) { "contributedProjects(sort: #{sort_parameter}) { nodes { id } }" } + let(:query_with_sort) { graphql_query_for(:user, user_params, user_fields_with_sort) } + + context 'when sort parameter is not provided' do + it 'returns contributed projects in default order(LATEST_ACTIVITY_DESC)' do + post_graphql(query, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + private_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + public_project.to_global_id.to_s + ]) + end + end + + context 'when sort parameter for id is provided' do + context 'when ID_ASC is provided' do + let(:sort_parameter) { 'ID_ASC' } + + it 'returns contributed projects in id ascending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + public_project.to_global_id.to_s, + private_project.to_global_id.to_s, + internal_project.to_global_id.to_s + ]) + end + end + + context 'when ID_DESC is provided' do + let(:sort_parameter) { 'ID_DESC' } + + it 'returns contributed projects in id descending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + internal_project.to_global_id.to_s, + private_project.to_global_id.to_s, + public_project.to_global_id.to_s + ]) + end + end + end + + context 'when sort parameter for name is provided' do + before_all do + public_project.update!(name: 'Project A') + internal_project.update!(name: 'Project B') + private_project.update!(name: 'Project C') + end + + context 'when NAME_ASC is provided' do + let(:sort_parameter) { 'NAME_ASC' } + + it 'returns contributed projects in name ascending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + public_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + private_project.to_global_id.to_s + ]) + end + end + + context 'when NAME_DESC is provided' do + let(:sort_parameter) { 'NAME_DESC' } + + it 'returns contributed projects in name descending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + private_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + public_project.to_global_id.to_s + ]) + end + end + end + + context 'when sort parameter for path is provided' do + before_all do + public_project.update!(path: 'Project-1') + internal_project.update!(path: 'Project-2') + private_project.update!(path: 'Project-3') + end + + context 'when PATH_ASC is provided' do + let(:sort_parameter) { 'PATH_ASC' } + + it 'returns contributed projects in path ascending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + public_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + private_project.to_global_id.to_s + ]) + end + end + + context 'when PATH_DESC is provided' do + let(:sort_parameter) { 'PATH_DESC' } + + it 'returns contributed projects in path descending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + private_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + public_project.to_global_id.to_s + ]) + end + end + end + + context 'when sort parameter for stars is provided' do + before_all do + public_project.update!(star_count: 10) + internal_project.update!(star_count: 20) + private_project.update!(star_count: 30) + end + + context 'when STARS_ASC is provided' do + let(:sort_parameter) { 'STARS_ASC' } + + it 'returns contributed projects in stars ascending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + public_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + private_project.to_global_id.to_s + ]) + end + end + + context 'when STARS_DESC is provided' do + let(:sort_parameter) { 'STARS_DESC' } + + it 'returns contributed projects in stars descending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + private_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + public_project.to_global_id.to_s + ]) + end + end + end + + context 'when sort parameter for latest activity is provided' do + context 'when LATEST_ACTIVITY_ASC is provided' do + let(:sort_parameter) { 'LATEST_ACTIVITY_ASC' } + + it 'returns contributed projects in latest activity ascending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + public_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + private_project.to_global_id.to_s + ]) + end + end + + context 'when LATEST_ACTIVITY_DESC is provided' do + let(:sort_parameter) { 'LATEST_ACTIVITY_DESC' } + + it 'returns contributed projects in latest activity descending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + private_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + public_project.to_global_id.to_s + ]) + end + end + end + + context 'when sort parameter for created_at is provided' do + before_all do + public_project.update!(created_at: Time.current + 1.hour) + internal_project.update!(created_at: Time.current + 2.hours) + private_project.update!(created_at: Time.current + 3.hours) + end + + context 'when CREATED_ASC is provided' do + let(:sort_parameter) { 'CREATED_ASC' } + + it 'returns contributed projects in created_at ascending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + public_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + private_project.to_global_id.to_s + ]) + end + end + + context 'when CREATED_DESC is provided' do + let(:sort_parameter) { 'CREATED_DESC' } + + it 'returns contributed projects in created_at descending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + private_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + public_project.to_global_id.to_s + ]) + end + end + end + + context 'when sort parameter for updated_at is provided' do + before_all do + public_project.update!(updated_at: Time.current + 1.hour) + internal_project.update!(updated_at: Time.current + 2.hours) + private_project.update!(updated_at: Time.current + 3.hours) + end + + context 'when UPDATED_ASC is provided' do + let(:sort_parameter) { 'UPDATED_ASC' } + + it 'returns contributed projects in updated_at ascending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + public_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + private_project.to_global_id.to_s + ]) + end + end + + context 'when UPDATED_DESC is provided' do + let(:sort_parameter) { 'UPDATED_DESC' } + + it 'returns contributed projects in updated_at descending order' do + post_graphql(query_with_sort, current_user: current_user) + + expect(graphql_data_at(*path).pluck('id')).to eq([ + private_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + public_project.to_global_id.to_s + ]) + end + end + end + end + + describe 'accessible' do + context 'when user profile is public' do + context 'when a logged in user with membership in the private project' do + it 'returns contributed projects with visibility to the logged in user' 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 a logged in user with no visibility to the private project' do + let_it_be(:current_user_2) { create(:user) } + + it 'returns contributed projects with visibility to the logged in user' do + post_graphql(query, current_user: current_user_2) + + expect(graphql_data_at(*path)).to contain_exactly( + a_graphql_entity_for(internal_project), + a_graphql_entity_for(public_project) + ) + end + end + + context 'when an anonymous user' do + it 'returns nothing' do + post_graphql(query, current_user: nil) + + expect(graphql_data_at(*path)).to be_nil + end + end + end + + context 'when user profile is private' do + let(:user_params) { { username: private_user.username } } + let_it_be(:private_user) { create(:user, :private_profile) } + + before_all do + private_project.add_developer(private_user) + private_project.add_developer(current_user) + + create(:push_event, project: private_project, author: private_user) + create(:push_event, project: internal_project, author: private_user) + create(:push_event, project: public_project, author: private_user) + end + + context 'when a logged in user' do + it 'returns no project' do + post_graphql(query, current_user: current_user) + + expect(graphql_data_at(*path)).to be_empty + end + end + + context 'when an anonymous user' do + it 'returns nothing' do + post_graphql(query, current_user: nil) + + expect(graphql_data_at(*path)).to be_nil + end + end + + context 'when a logged in user is the user' do + it 'returns the user\'s all contributed projects' do + post_graphql(query, current_user: private_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 + end + end + + describe 'sorting and pagination' do + let(:data_path) { [:user, :contributed_projects] } + + def pagination_query(params) + graphql_query_for(:user, user_params, "contributedProjects(#{params}) { #{page_info} nodes { id } }") + end + + context 'when sorting in latest activity ascending order' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :LATEST_ACTIVITY_ASC } + let(:first_param) { 1 } + let(:all_records) do + [ + public_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + private_project.to_global_id.to_s + ] + end + end + end + + context 'when sorting in latest activity descending order' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :LATEST_ACTIVITY_DESC } + let(:first_param) { 1 } + let(:all_records) do + [ + private_project.to_global_id.to_s, + internal_project.to_global_id.to_s, + public_project.to_global_id.to_s + ] + end + end + end + end +end diff --git a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb index 7a3b3d6924c..6c0aebaf7b7 100644 --- a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb +++ b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb @@ -33,6 +33,7 @@ RSpec.shared_examples "a user type with merge request interaction type" do groupCount projectMemberships starredProjects + contributedProjects callouts merge_request_interaction namespace