Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e27af51af8
commit
74e773b861
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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("<br>").html_safe
|
||||
markdown_field(current_appearance, :description)
|
||||
end
|
||||
|
||||
def brand_member_guidelines
|
||||
|
|
|
|||
|
|
@ -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 ' \
|
||||
|
|
|
|||
|
|
@ -15785,6 +15785,22 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="addonuserauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
|
||||
| <a id="addonuserauthoredmergerequestsupdatedbefore"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="addonusercontributedprojectssort"></a>`sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
|
||||
|
||||
##### `AddOnUser.groups`
|
||||
|
||||
Groups where the user has access.
|
||||
|
|
@ -16549,6 +16565,22 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="autocompleteduserauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
|
||||
| <a id="autocompleteduserauthoredmergerequestsupdatedbefore"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="autocompletedusercontributedprojectssort"></a>`sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
|
||||
|
||||
##### `AutocompletedUser.groups`
|
||||
|
||||
Groups where the user has access.
|
||||
|
|
@ -18725,6 +18757,22 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="currentuserauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
|
||||
| <a id="currentuserauthoredmergerequestsupdatedbefore"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="currentusercontributedprojectssort"></a>`sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
|
||||
|
||||
##### `CurrentUser.groups`
|
||||
|
||||
Groups where the user has access.
|
||||
|
|
@ -24001,6 +24049,22 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="mergerequestassigneeauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
|
||||
| <a id="mergerequestassigneeauthoredmergerequestsupdatedbefore"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestassigneecontributedprojectssort"></a>`sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
|
||||
|
||||
##### `MergeRequestAssignee.groups`
|
||||
|
||||
Groups where the user has access.
|
||||
|
|
@ -24313,6 +24377,22 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="mergerequestauthorauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
|
||||
| <a id="mergerequestauthorauthoredmergerequestsupdatedbefore"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestauthorcontributedprojectssort"></a>`sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
|
||||
|
||||
##### `MergeRequestAuthor.groups`
|
||||
|
||||
Groups where the user has access.
|
||||
|
|
@ -24672,6 +24752,22 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="mergerequestparticipantauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
|
||||
| <a id="mergerequestparticipantauthoredmergerequestsupdatedbefore"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestparticipantcontributedprojectssort"></a>`sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
|
||||
|
||||
##### `MergeRequestParticipant.groups`
|
||||
|
||||
Groups where the user has access.
|
||||
|
|
@ -25020,6 +25116,22 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="mergerequestreviewerauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
|
||||
| <a id="mergerequestreviewerauthoredmergerequestsupdatedbefore"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestreviewercontributedprojectssort"></a>`sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
|
||||
|
||||
##### `MergeRequestReviewer.groups`
|
||||
|
||||
Groups where the user has access.
|
||||
|
|
@ -27431,6 +27543,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="projectdependenciescomponentnames"></a>`componentNames` | [`[String!]`](#string) | Filter dependencies by component names. |
|
||||
| <a id="projectdependenciespackagemanagers"></a>`packageManagers` | [`[PackageManager!]`](#packagemanager) | Filter dependencies by package managers. |
|
||||
| <a id="projectdependenciessort"></a>`sort` | [`DependencySort`](#dependencysort) | Sort dependencies by given criteria. |
|
||||
| <a id="projectdependenciessourcetypes"></a>`sourceTypes` | [`[SbomSourceType!]`](#sbomsourcetype) | Filter dependencies by source type. |
|
||||
|
||||
##### `Project.deployment`
|
||||
|
||||
|
|
@ -30727,6 +30840,22 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="usercoreauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
|
||||
| <a id="usercoreauthoredmergerequestsupdatedbefore"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="usercorecontributedprojectssort"></a>`sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
|
||||
|
||||
##### `UserCore.groups`
|
||||
|
||||
Groups where the user has access.
|
||||
|
|
@ -34477,6 +34606,31 @@ Project member relation.
|
|||
| <a id="projectmemberrelationinvited_groups"></a>`INVITED_GROUPS` | Invited Groups members. |
|
||||
| <a id="projectmemberrelationshared_into_ancestors"></a>`SHARED_INTO_ANCESTORS` | Shared Into Ancestors members. |
|
||||
|
||||
### `ProjectSort`
|
||||
|
||||
Values for sorting projects.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="projectsortcreated_asc"></a>`CREATED_ASC` | Created at ascending order. |
|
||||
| <a id="projectsortcreated_desc"></a>`CREATED_DESC` | Created at descending order. |
|
||||
| <a id="projectsortid_asc"></a>`ID_ASC` | ID by ascending order. |
|
||||
| <a id="projectsortid_desc"></a>`ID_DESC` | ID by descending order. |
|
||||
| <a id="projectsortlatest_activity_asc"></a>`LATEST_ACTIVITY_ASC` | Latest activity by ascending order. |
|
||||
| <a id="projectsortlatest_activity_desc"></a>`LATEST_ACTIVITY_DESC` | Latest activity by descending order. |
|
||||
| <a id="projectsortname_asc"></a>`NAME_ASC` | Name by ascending order. |
|
||||
| <a id="projectsortname_desc"></a>`NAME_DESC` | Name by descending order. |
|
||||
| <a id="projectsortpath_asc"></a>`PATH_ASC` | Path by ascending order. |
|
||||
| <a id="projectsortpath_desc"></a>`PATH_DESC` | Path by descending order. |
|
||||
| <a id="projectsortstars_asc"></a>`STARS_ASC` | Stars by ascending order. |
|
||||
| <a id="projectsortstars_desc"></a>`STARS_DESC` | Stars by descending order. |
|
||||
| <a id="projectsortupdated_asc"></a>`UPDATED_ASC` | Updated at ascending order. |
|
||||
| <a id="projectsortupdated_desc"></a>`UPDATED_DESC` | Updated at descending order. |
|
||||
| <a id="projectsortcreated_asc"></a>`created_asc` **{warning-solid}** | **Deprecated** in GitLab 13.5. This was renamed. Use: `CREATED_ASC`. |
|
||||
| <a id="projectsortcreated_desc"></a>`created_desc` **{warning-solid}** | **Deprecated** in GitLab 13.5. This was renamed. Use: `CREATED_DESC`. |
|
||||
| <a id="projectsortupdated_asc"></a>`updated_asc` **{warning-solid}** | **Deprecated** in GitLab 13.5. This was renamed. Use: `UPDATED_ASC`. |
|
||||
| <a id="projectsortupdated_desc"></a>`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.
|
|||
| <a id="sastuicomponentsizemedium"></a>`MEDIUM` | Size of UI component in SAST configuration page is medium. |
|
||||
| <a id="sastuicomponentsizesmall"></a>`SMALL` | Size of UI component in SAST configuration page is small. |
|
||||
|
||||
### `SbomSourceType`
|
||||
|
||||
Values for sbom source types.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="sbomsourcetypecontainer_scanning"></a>`CONTAINER_SCANNING` | Source Type: container_scanning. |
|
||||
| <a id="sbomsourcetypecontainer_scanning_for_registry"></a>`CONTAINER_SCANNING_FOR_REGISTRY` | Source Type: container_scanning_for_registry. |
|
||||
| <a id="sbomsourcetypedependency_scanning"></a>`DEPENDENCY_SCANNING` | Source Type: dependency_scanning. |
|
||||
| <a id="sbomsourcetypenil_source"></a>`NIL_SOURCE` | Enum source nil. |
|
||||
|
||||
### `ScanStatus`
|
||||
|
||||
The status of the security scan.
|
||||
|
|
@ -36983,6 +37148,22 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="userauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
|
||||
| <a id="userauthoredmergerequestsupdatedbefore"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="usercontributedprojectssort"></a>`sort` | [`ProjectSort`](#projectsort) | Sort contributed projects. |
|
||||
|
||||
###### `User.groups`
|
||||
|
||||
Groups where the user has access.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ The following languages and dependency managers are supported when using the Dep
|
|||
<tr>
|
||||
<td rowspan="4">Python</td>
|
||||
<td rowspan="4">3.11<sup><b><a href="#notes-regarding-supported-languages-and-package-managers-7">7</a></b></sup></td>
|
||||
<td><a href="https://setuptools.readthedocs.io/en/latest/">setuptools</a></td>
|
||||
<td><a href="https://setuptools.readthedocs.io/en/latest/">setuptools</a><sup><b><a href="#notes-regarding-supported-languages-and-package-managers-8">8</a></b></sup></td>
|
||||
<td><code>setup.py</code></td>
|
||||
<td>N</td>
|
||||
</tr>
|
||||
|
|
@ -279,6 +279,12 @@ The following languages and dependency managers are supported when using the Dep
|
|||
Support for prior Python versions was <a href="https://gitlab.com/gitlab-org/gitlab/-/issues/441201">deprecated</a> in GitLab 16.9 and <a href="https://gitlab.com/gitlab-org/gitlab/-/issues/441491">removed</a> in GitLab 17.0.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<a id="notes-regarding-supported-languages-and-package-managers-8"></a>
|
||||
<p>
|
||||
Excludes both <code>pip</code> and <code>setuptools</code> from the report as they are required by the installer.
|
||||
</p>
|
||||
</li>
|
||||
</ol>
|
||||
<!-- markdownlint-enable MD044 -->
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,11 @@ GitLab Advisory Database Terms prohibit the use of data contained in the GitLab
|
|||
As an example, we highlight the use of the database as a source for an Advisory Ingestion process as part of Continuous Vulnerability Scans.
|
||||
|
||||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
flowchart TB
|
||||
accTitle: Advisory ingestion process
|
||||
accDescr: Sequence of actions that make up the advisory ingestion process.
|
||||
|
||||
subgraph Dependency Scanning
|
||||
A[GitLab Advisory Database]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,8 +47,10 @@ This diagram describes how a post-processing hook revokes a secret in the GitLab
|
|||
|
||||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
|
||||
sequenceDiagram
|
||||
accTitle: Architecture diagram
|
||||
accDescr: How a post-processing hook revokes a secret in the GitLab application.
|
||||
|
||||
autonumber
|
||||
GitLab Rails-->+GitLab Rails: gl-secret-detection-report.json
|
||||
GitLab Rails->>+GitLab Sidekiq: StoreScansService
|
||||
|
|
@ -86,7 +88,11 @@ body. We strongly recommend that you verify incoming requests using this signatu
|
|||
request from GitLab. The diagram below details the necessary steps to receive, verify, and revoke leaked tokens:
|
||||
|
||||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
sequenceDiagram
|
||||
accTitle: Partner API data flow
|
||||
accDescr: How a Partner API should receive and respond to leaked token revocation requests.
|
||||
|
||||
autonumber
|
||||
GitLab Token Revocation API-->>+Partner API: Send new leaked credentials
|
||||
Partner API-->>+GitLab Public Keys endpoint: Get active public keys
|
||||
|
|
|
|||
|
|
@ -87,16 +87,19 @@ The following items are changed when they are imported:
|
|||
|
||||
> - Importing approvals by email address or username [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23586) in GitLab 16.7.
|
||||
> - Matching user mentions with GitLab users [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/433008) in GitLab 16.8.
|
||||
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/153041) to import approvals only by email address in GitLab 17.1.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, matching user mentions with GitLab users is not available. To make it available per user,
|
||||
an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `bitbucket_server_import_stage_import_users`.
|
||||
On GitLab.com and GitLab Dedicated, this feature is not available.
|
||||
|
||||
When issues and pull requests are importing, the importer tries to find the author's email address
|
||||
with a confirmed email address in the GitLab user database. If no such user is available, the
|
||||
project creator is set as the author. The importer appends a note in the comment to mark the
|
||||
original creator.
|
||||
When issues and pull requests are importing, the importer tries to match a Bitbucket Server user's email address
|
||||
with a confirmed email address in the GitLab user database. If no such user is found:
|
||||
|
||||
- The project creator is used instead. The importer appends a note in the comment to mark the original creator.
|
||||
- For pull request reviewers, no reviewer is assigned.
|
||||
- For pull request approvers, no approval is added.
|
||||
|
||||
`@mentions` on pull request descriptions and notes are matched to user profiles on a Bitbucket Server by using the user's email address.
|
||||
If a user with the same email address is not found on GitLab, the `@mention` is made static.
|
||||
|
|
@ -107,11 +110,6 @@ If the project is public, GitLab only matches users who are invited to the proje
|
|||
The importer creates any new namespaces (groups) if they don't exist. If the namespace is taken, the
|
||||
repository imports under the namespace of the user who started the import process.
|
||||
|
||||
The importer attempts to find:
|
||||
|
||||
- Reviewers by their email address in the GitLab user database. If they don't exist in GitLab, they are not added as reviewers to a merge request.
|
||||
- Approvers by username or email. If they don't exist in GitLab, the approval is not added to a merge request.
|
||||
|
||||
### User assignment by username
|
||||
|
||||
> - Not recommended for production use.
|
||||
|
|
@ -121,15 +119,13 @@ On self-managed GitLab and GitLab.com, by default this feature is not available.
|
|||
available, an administrator can [enable the feature flag](../../../administration/feature_flags.md)
|
||||
named `bitbucket_server_user_mapping_by_username`. This feature is not ready for production use.
|
||||
|
||||
With this feature enabled, the importer tries to find a user in the GitLab user database with the
|
||||
author's:
|
||||
With this feature enabled, user email address matching is disabled.
|
||||
Instead, the importer matches users in the GitLab user database with the Bitbucket Server user's:
|
||||
|
||||
- `username`
|
||||
- `slug`
|
||||
- `displayName`
|
||||
|
||||
If no user matches these properties, the project creator is set as the author.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### General
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
|
|
@ -329,9 +329,7 @@ To sort members:
|
|||
GitLab users can request to become a member of a project.
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find the project you want to be a member of.
|
||||
1. By the project's name, select **Request Access**.
|
||||
|
||||

|
||||
1. In the top right, select the vertical ellipsis (**{ellipsis_v}**) and select **Request Access**.
|
||||
|
||||
An email is sent to the most recently active project Maintainers or Owners.
|
||||
Up to ten project Maintainers or Owners are notified.
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ module Gitlab
|
|||
return [] unless object[:reviewers].present?
|
||||
|
||||
object[:reviewers].filter_map do |reviewer|
|
||||
if Feature.enabled?(:bitbucket_server_user_mapping_by_username, type: :ops)
|
||||
if Feature.enabled?(:bitbucket_server_user_mapping_by_username, project, type: :ops)
|
||||
user_finder.find_user_id(by: :username, value: reviewer.dig('user', 'slug'))
|
||||
else
|
||||
user_finder.find_user_id(by: :email, value: reviewer.dig('user', 'emailAddress'))
|
||||
|
|
|
|||
|
|
@ -15,8 +15,11 @@ module Gitlab
|
|||
event_id: approved_event[:id]
|
||||
)
|
||||
|
||||
user_id = user_finder.find_user_id(by: :username, value: approved_event[:approver_username]) ||
|
||||
user_finder.find_user_id(by: :email, value: approved_event[:approver_email])
|
||||
user_id = if Feature.enabled?(:bitbucket_server_user_mapping_by_username, project, type: :ops)
|
||||
user_finder.find_user_id(by: :username, value: approved_event[:approver_username])
|
||||
else
|
||||
user_finder.find_user_id(by: :email, value: approved_event[:approver_email])
|
||||
end
|
||||
|
||||
if user_id.nil?
|
||||
log_info(
|
||||
|
|
|
|||
|
|
@ -76,8 +76,11 @@ module Gitlab
|
|||
event_id: approved_event.id
|
||||
)
|
||||
|
||||
user_id = user_finder.find_user_id(by: :username, value: approved_event.approver_username) ||
|
||||
user_finder.find_user_id(by: :email, value: approved_event.approver_email)
|
||||
user_id = if Feature.enabled?(:bitbucket_server_user_mapping_by_username, project, type: :ops)
|
||||
user_finder.find_user_id(by: :username, value: approved_event.approver_username)
|
||||
else
|
||||
user_finder.find_user_id(by: :email, value: approved_event.approver_email)
|
||||
end
|
||||
|
||||
return unless user_id
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ module Gitlab
|
|||
def uid(object)
|
||||
# We want this to only match either username or email depending on the flag state.
|
||||
# There should be no fall-through.
|
||||
if Feature.enabled?(:bitbucket_server_user_mapping_by_username, type: :ops)
|
||||
if Feature.enabled?(:bitbucket_server_user_mapping_by_username, project, type: :ops)
|
||||
find_user_id(by: :username, value: object.is_a?(Hash) ? object[:author_username] : object.author_username)
|
||||
else
|
||||
find_user_id(by: :email, value: object.is_a?(Hash) ? object[:author_email] : object.author_email)
|
||||
|
|
|
|||
|
|
@ -39307,6 +39307,9 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Back to dashboards"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|By providing feedback on AI-generated content, you acknowledge that GitLab may review the prompts you submitted alongside this feedback."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Collector host"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -39361,6 +39364,9 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Events over time"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Feedback acknowledgement"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|For more information, see the %{linkStart}docs%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -39385,12 +39391,18 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Help us improve Product Analytics Dashboards by sharing your experience."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Helpful"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|How many sessions a user has"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|How often users returned compared to all sessions"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|How was the result?"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|I agree to event collection and processing in this region."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -39505,6 +39517,9 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Tell us what you think!"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Thank you for your feedback."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|The Product Analytics Beta on GitLab.com is offered only in the Google Cloud Platform zone %{zone}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -39553,6 +39568,9 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Uncheck if you would like to configure a different provider for this project."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Unhelpful"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Unique Users"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -39595,6 +39613,9 @@ msgstr ""
|
|||
msgid "ProductAnalytics|What metric do you want to visualize?"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Wrong"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|You can instrument your application using a JS module or an HTML script. Follow the instructions below for the option you prefer."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1267,4 +1267,165 @@ describe('buildClient', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchLogsSearchMetadata', () => {
|
||||
const mockResponse = {
|
||||
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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock.onGet(logsSearchMetadataUrl).reply(200, mockResponse);
|
||||
});
|
||||
|
||||
it('fetches logs metadata from the logs URL', async () => {
|
||||
const result = await client.fetchLogsSearchMetadata();
|
||||
|
||||
expect(axios.get).toHaveBeenCalledTimes(1);
|
||||
expect(axios.get).toHaveBeenCalledWith(logsSearchMetadataUrl, {
|
||||
withCredentials: true,
|
||||
params: expect.any(URLSearchParams),
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
describe('filters', () => {
|
||||
describe('date range filter', () => {
|
||||
it('handle predefined date range value', async () => {
|
||||
await client.fetchLogsSearchMetadata({
|
||||
filters: { dateRange: { value: '5m' } },
|
||||
});
|
||||
expect(getQueryParam()).toContain(`period=5m`);
|
||||
});
|
||||
|
||||
it('handle custom date range value', async () => {
|
||||
await client.fetchLogsSearchMetadata({
|
||||
filters: {
|
||||
dateRange: {
|
||||
endDate: new Date('2020-07-06'),
|
||||
startDate: new Date('2020-07-05'),
|
||||
value: 'custom',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(getQueryParam()).toContain(
|
||||
'start_time=2020-07-05T00:00:00.000Z&end_time=2020-07-06T00:00:00.000Z',
|
||||
);
|
||||
});
|
||||
|
||||
it('handles exact timestamps', async () => {
|
||||
await client.fetchLogsSearchMetadata({
|
||||
filters: {
|
||||
dateRange: {
|
||||
timestamp: '2024-02-19T16:10:15.4433398Z',
|
||||
endDate: new Date('2024-02-19'),
|
||||
startDate: new Date('2024-02-19'),
|
||||
value: 'custom',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(getQueryParam()).toContain(
|
||||
'start_time=2024-02-19T16:10:15.4433398Z&end_time=2024-02-19T16:10:15.4433398Z',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('attributes filters', () => {
|
||||
it('converts filter to proper query params', async () => {
|
||||
await client.fetchLogsSearchMetadata({
|
||||
filters: {
|
||||
attributes: {
|
||||
service: [
|
||||
{ operator: '=', value: 'serviceName' },
|
||||
{ operator: '!=', value: 'serviceName2' },
|
||||
],
|
||||
severityName: [
|
||||
{ operator: '=', value: 'info' },
|
||||
{ operator: '!=', value: 'warning' },
|
||||
],
|
||||
severityNumber: [
|
||||
{ operator: '=', value: '9' },
|
||||
{ operator: '!=', value: '10' },
|
||||
],
|
||||
traceId: [{ operator: '=', value: 'traceId' }],
|
||||
spanId: [{ operator: '=', value: 'spanId' }],
|
||||
fingerprint: [{ operator: '=', value: 'fingerprint' }],
|
||||
traceFlags: [
|
||||
{ operator: '=', value: '1' },
|
||||
{ operator: '!=', value: '2' },
|
||||
],
|
||||
attribute: [{ operator: '=', value: 'attr=bar' }],
|
||||
resourceAttribute: [{ operator: '=', value: 'res=foo' }],
|
||||
search: [{ value: 'some-search' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(getQueryParam()).toEqual(
|
||||
`service_name=serviceName¬[service_name]=serviceName2` +
|
||||
`&severity_name=info¬[severity_name]=warning` +
|
||||
`&severity_number=9¬[severity_number]=10` +
|
||||
`&trace_id=traceId` +
|
||||
`&span_id=spanId` +
|
||||
`&fingerprint=fingerprint` +
|
||||
`&trace_flags=1¬[trace_flags]=2` +
|
||||
`&log_attr_name=attr&log_attr_value=bar` +
|
||||
`&res_attr_name=res&res_attr_value=foo` +
|
||||
`&body=some-search`,
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores unsupported operators', async () => {
|
||||
await client.fetchLogsSearchMetadata({
|
||||
filters: {
|
||||
attributes: {
|
||||
traceId: [{ operator: '!=', value: 'traceId2' }],
|
||||
spanId: [{ operator: '!=', value: 'spanId2' }],
|
||||
fingerprint: [{ operator: '!=', value: 'fingerprint2' }],
|
||||
attribute: [{ operator: '!=', value: 'bar' }],
|
||||
resourceAttribute: [{ operator: '!=', value: 'resourceAttribute2' }],
|
||||
unsupported: [{ value: 'something', operator: '=' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(getQueryParam()).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores empty filter', async () => {
|
||||
await client.fetchLogsSearchMetadata({
|
||||
filters: { attributes: {}, dateRange: {} },
|
||||
});
|
||||
expect(getQueryParam()).toBe('');
|
||||
});
|
||||
|
||||
it('ignores undefined filter', async () => {
|
||||
await client.fetchLogsSearchMetadata({
|
||||
filters: { dateRange: undefined, attributes: undefined },
|
||||
});
|
||||
expect(getQueryParam()).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { GlEmptyState } from '@gitlab/ui';
|
|||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import component from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue';
|
||||
import TagsListRow from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue';
|
||||
import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue';
|
||||
|
|
@ -92,7 +92,6 @@ describe('Tags List', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
resolver = jest.fn().mockResolvedValue(imageTagsMock());
|
||||
jest.spyOn(Tracking, 'event');
|
||||
});
|
||||
|
||||
describe('registry list', () => {
|
||||
|
|
@ -153,6 +152,16 @@ describe('Tags List', () => {
|
|||
});
|
||||
|
||||
describe('delete event', () => {
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
describe('single item', () => {
|
||||
beforeEach(() => {
|
||||
findRegistryList().vm.$emit('delete', [tags[0]]);
|
||||
|
|
@ -167,7 +176,7 @@ describe('Tags List', () => {
|
|||
});
|
||||
|
||||
it('tracks a single delete event', () => {
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
|
||||
label: 'registry_tag_delete',
|
||||
});
|
||||
});
|
||||
|
|
@ -187,7 +196,7 @@ describe('Tags List', () => {
|
|||
});
|
||||
|
||||
it('tracks multiple delete event', () => {
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
|
||||
label: 'bulk_registry_tag_delete',
|
||||
});
|
||||
});
|
||||
|
|
@ -266,8 +275,10 @@ describe('Tags List', () => {
|
|||
|
||||
describe('delete event', () => {
|
||||
let mutationResolver;
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(async () => {
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock);
|
||||
resolver = jest.fn().mockResolvedValue(imageTagsMock());
|
||||
await mountComponent({ mutationResolver });
|
||||
|
|
@ -275,12 +286,16 @@ describe('Tags List', () => {
|
|||
findTagsListRow().at(0).vm.$emit('delete');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('opens the modal', () => {
|
||||
expect(DeleteModal.methods.show).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('tracks a single delete event', () => {
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
|
||||
label: 'registry_tag_delete',
|
||||
});
|
||||
});
|
||||
|
|
@ -361,12 +376,22 @@ describe('Tags List', () => {
|
|||
});
|
||||
|
||||
describe('cancel event', () => {
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('tracks cancel_delete', async () => {
|
||||
await mountComponent();
|
||||
|
||||
findDeleteModal().vm.$emit('cancel');
|
||||
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'cancel_delete', {
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'cancel_delete', {
|
||||
label: 'registry_tag_delete',
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
import getContainerRepositoryDetailsQuery from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql';
|
||||
|
||||
import component from '~/packages_and_registries/container_registry/explorer/pages/details.vue';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
|
||||
import {
|
||||
graphQLImageDetailsMock,
|
||||
|
|
@ -100,10 +100,6 @@ describe('Details Page', () => {
|
|||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Tracking, 'event');
|
||||
});
|
||||
|
||||
describe('when isLoading is true', () => {
|
||||
it('shows the loader', () => {
|
||||
mountComponent();
|
||||
|
|
@ -173,6 +169,16 @@ describe('Details Page', () => {
|
|||
});
|
||||
|
||||
describe('cancel event', () => {
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('tracks cancel_delete', async () => {
|
||||
mountComponent();
|
||||
|
||||
|
|
@ -180,7 +186,7 @@ describe('Details Page', () => {
|
|||
|
||||
findDeleteModal().vm.$emit('cancel');
|
||||
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'cancel_delete', {
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'cancel_delete', {
|
||||
label: 'registry_image_delete',
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {
|
|||
import deleteContainerRepositoryMutation from '~/packages_and_registries/container_registry/explorer/graphql/mutations/delete_container_repository.mutation.graphql';
|
||||
import getContainerRepositoriesDetails from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql';
|
||||
import component from '~/packages_and_registries/container_registry/explorer/pages/list.vue';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import PersistedPagination from '~/packages_and_registries/shared/components/persisted_pagination.vue';
|
||||
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
|
||||
import MetadataDatabaseAlert from '~/packages_and_registries/shared/components/container_registry_metadata_database_alert.vue';
|
||||
|
|
@ -671,22 +671,26 @@ describe('List Page', () => {
|
|||
});
|
||||
|
||||
describe('tracking', () => {
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
mountComponent();
|
||||
fireFirstSortUpdate();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
const testTrackingCall = (action) => {
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, action, {
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, action, {
|
||||
label: 'registry_repository_delete',
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Tracking, 'event');
|
||||
});
|
||||
|
||||
it('send an event when delete button is clicked', () => {
|
||||
it('send an event when delete button is clicked', async () => {
|
||||
await waitForPromises();
|
||||
findImageList().vm.$emit('delete', {});
|
||||
|
||||
testTrackingCall('click_button');
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { TRACKING_ACTIONS } from '~/packages_and_registries/shared/constants';
|
|||
import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registry/shared/constants';
|
||||
import TerraformTitle from '~/packages_and_registries/infrastructure_registry/details/components/details_title.vue';
|
||||
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
|
||||
import { mavenPackage, mavenFiles, npmPackage } from '../../mock_data';
|
||||
|
||||
|
|
@ -229,7 +229,11 @@ describe('PackagesApp', () => {
|
|||
let eventSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
eventSpy = jest.spyOn(Tracking, 'event');
|
||||
eventSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it(`delete button on delete modal call event with ${TRACKING_ACTIONS.DELETE_PACKAGE}`, () => {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { stubComponent } from 'helpers/stub_component';
|
|||
import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import { s__ } from '~/locale';
|
||||
import { createAlert } from '~/alert';
|
||||
import {
|
||||
|
|
@ -181,9 +181,16 @@ describe('Package Files', () => {
|
|||
});
|
||||
|
||||
describe('link', () => {
|
||||
beforeEach(async () => {
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('exists', () => {
|
||||
|
|
@ -195,11 +202,9 @@ describe('Package Files', () => {
|
|||
});
|
||||
|
||||
it('tracks "download-file" event on click', () => {
|
||||
const eventSpy = jest.spyOn(Tracking, 'event');
|
||||
|
||||
findFirstRowDownloadLink().vm.$emit('click');
|
||||
|
||||
expect(eventSpy).toHaveBeenCalledWith(
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
eventCategory,
|
||||
DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION,
|
||||
expect.any(Object),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
|
|||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import getPackagePipelines from '~/packages_and_registries/package_registry/graphql/queries/get_package_pipelines.query.graphql';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import {
|
||||
TRACKING_ACTION_CLICK_PIPELINE_LINK,
|
||||
TRACKING_ACTION_CLICK_COMMIT_LINK,
|
||||
|
|
@ -194,8 +194,12 @@ describe('Package History', () => {
|
|||
const category = 'UI::Packages';
|
||||
|
||||
beforeEach(() => {
|
||||
eventSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
mountComponent();
|
||||
eventSpy = jest.spyOn(Tracking, 'event');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('clicking pipeline link tracks the right action', () => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import PackageVersionsList from '~/packages_and_registries/package_registry/comp
|
|||
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
|
||||
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
|
||||
import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import {
|
||||
CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION,
|
||||
CANCEL_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION,
|
||||
|
|
@ -248,12 +248,16 @@ describe('PackageVersionsList', () => {
|
|||
const { findDeletePackagesModal } = uiElements;
|
||||
|
||||
beforeEach(async () => {
|
||||
eventSpy = jest.spyOn(Tracking, 'event');
|
||||
eventSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
mountComponent({ props: { canDestroy: true } });
|
||||
await waitForPromises();
|
||||
finderFunction().vm.$emit('delete', deletePayload);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('passes itemsToBeDeleted to the modal', () => {
|
||||
expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual([
|
||||
packageVersions()[0],
|
||||
|
|
@ -308,11 +312,15 @@ describe('PackageVersionsList', () => {
|
|||
const { findDeletePackagesModal, findRegistryList } = uiElements;
|
||||
|
||||
beforeEach(async () => {
|
||||
eventSpy = jest.spyOn(Tracking, 'event');
|
||||
eventSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
mountComponent({ props: { canDestroy: true } });
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('binds the right props', () => {
|
||||
expect(uiElements.findRegistryList().props()).toMatchObject({
|
||||
items: packageVersions(),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
CANCEL_DELETE_PACKAGES_TRACKING_ACTION,
|
||||
} from '~/packages_and_registries/package_registry/constants';
|
||||
import PackagesList from '~/packages_and_registries/package_registry/components/list/packages_list.vue';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import { defaultPackageGroupSettings, packageData } from '../../mock_data';
|
||||
|
||||
describe('packages_list', () => {
|
||||
|
|
@ -158,21 +158,25 @@ describe('packages_list', () => {
|
|||
${'when the user can destroy the package'} | ${findPackagesListRow} | ${firstPackage}
|
||||
${'when the user can bulk destroy packages and deletes only one package'} | ${findRegistryList} | ${[firstPackage]}
|
||||
`('$description', ({ finderFunction, deletePayload }) => {
|
||||
let eventSpy;
|
||||
let trackingSpy;
|
||||
const category = 'UI::NpmPackages';
|
||||
|
||||
beforeEach(() => {
|
||||
eventSpy = jest.spyOn(Tracking, 'event');
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
mountComponent({ stubs: { RegistryList } });
|
||||
finderFunction().vm.$emit('delete', deletePayload);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('passes itemsToBeDeleted to the modal', () => {
|
||||
expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual([firstPackage]);
|
||||
});
|
||||
|
||||
it('requesting delete tracks the right action', () => {
|
||||
expect(eventSpy).toHaveBeenCalledWith(
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
category,
|
||||
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
|
||||
expect.any(Object),
|
||||
|
|
@ -193,7 +197,7 @@ describe('packages_list', () => {
|
|||
});
|
||||
|
||||
it('tracks the right action', () => {
|
||||
expect(eventSpy).toHaveBeenCalledWith(
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
category,
|
||||
DELETE_PACKAGE_TRACKING_ACTION,
|
||||
expect.any(Object),
|
||||
|
|
@ -210,7 +214,7 @@ describe('packages_list', () => {
|
|||
it('canceling delete tracks the right action', () => {
|
||||
findDeletePackagesModal().vm.$emit('cancel');
|
||||
|
||||
expect(eventSpy).toHaveBeenCalledWith(
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
category,
|
||||
CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
|
||||
expect.any(Object),
|
||||
|
|
@ -219,22 +223,26 @@ describe('packages_list', () => {
|
|||
});
|
||||
|
||||
describe('when the user can bulk destroy packages', () => {
|
||||
let eventSpy;
|
||||
let trackingSpy;
|
||||
const items = [firstPackage, secondPackage];
|
||||
|
||||
beforeEach(() => {
|
||||
eventSpy = jest.spyOn(Tracking, 'event');
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
mountComponent();
|
||||
findRegistryList().vm.$emit('delete', items);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('passes itemsToBeDeleted to the modal', () => {
|
||||
expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual(items);
|
||||
expect(wrapper.emitted('delete')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('requesting delete tracks the right action', () => {
|
||||
expect(eventSpy).toHaveBeenCalledWith(
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
REQUEST_DELETE_PACKAGES_TRACKING_ACTION,
|
||||
expect.any(Object),
|
||||
|
|
@ -251,7 +259,7 @@ describe('packages_list', () => {
|
|||
});
|
||||
|
||||
it('tracks the right action', () => {
|
||||
expect(eventSpy).toHaveBeenCalledWith(
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
DELETE_PACKAGES_TRACKING_ACTION,
|
||||
expect.any(Object),
|
||||
|
|
@ -268,7 +276,7 @@ describe('packages_list', () => {
|
|||
it('canceling delete tracks the right action', () => {
|
||||
findDeletePackagesModal().vm.$emit('cancel');
|
||||
|
||||
expect(eventSpy).toHaveBeenCalledWith(
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
CANCEL_DELETE_PACKAGES_TRACKING_ACTION,
|
||||
expect.any(Object),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import component from '~/packages_and_registries/settings/project/components/con
|
|||
import { UPDATE_SETTINGS_ERROR_MESSAGE } from '~/packages_and_registries/settings/project/constants';
|
||||
import updateContainerExpirationPolicyMutation from '~/packages_and_registries/settings/project/graphql/mutations/update_container_expiration_policy.mutation.graphql';
|
||||
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import { expirationPolicyPayload, expirationPolicyMutationPayload } from '../mock_data';
|
||||
|
||||
describe('Container Expiration Policy Settings Form', () => {
|
||||
|
|
@ -120,10 +120,6 @@ describe('Container Expiration Policy Settings Form', () => {
|
|||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Tracking, 'event');
|
||||
});
|
||||
|
||||
describe.each`
|
||||
model | finder | fieldName | type | defaultValue
|
||||
${'enabled'} | ${findEnableToggle} | ${'Enable'} | ${'toggle'} | ${false}
|
||||
|
|
@ -269,14 +265,26 @@ describe('Container Expiration Policy Settings Form', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('tracks the submit event', async () => {
|
||||
mountComponentWithApollo({
|
||||
mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
|
||||
describe('tracking', () => {
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
});
|
||||
|
||||
await submitForm();
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload);
|
||||
it('tracks the submit event', async () => {
|
||||
mountComponentWithApollo({
|
||||
mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
|
||||
});
|
||||
|
||||
await submitForm();
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload);
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects to package and registry project settings page when submitted successfully', async () => {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from '~/packages_and_registries/settings/project/constants';
|
||||
import packagesCleanupPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_packages_cleanup_policy.query.graphql';
|
||||
import updatePackagesCleanupPolicyMutation from '~/packages_and_registries/settings/project/graphql/mutations/update_packages_cleanup_policy.mutation.graphql';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import { packagesCleanupPolicyPayload, packagesCleanupPolicyMutationPayload } from '../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
|
@ -110,10 +110,6 @@ describe('Packages Cleanup Policy Settings Form', () => {
|
|||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Tracking, 'event');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fakeApollo = null;
|
||||
});
|
||||
|
|
@ -274,18 +270,30 @@ describe('Packages Cleanup Policy Settings Form', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('tracks the submit event', () => {
|
||||
mountComponentWithApollo({
|
||||
mutationResolver: jest.fn().mockResolvedValue(packagesCleanupPolicyMutationPayload()),
|
||||
describe('tracking', () => {
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
});
|
||||
|
||||
findForm().trigger('submit');
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
expect(Tracking.event).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
'submit_packages_cleanup_form',
|
||||
trackingPayload,
|
||||
);
|
||||
it('tracks the submit event', () => {
|
||||
mountComponentWithApollo({
|
||||
mutationResolver: jest.fn().mockResolvedValue(packagesCleanupPolicyMutationPayload()),
|
||||
});
|
||||
|
||||
findForm().trigger('submit');
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
'submit_packages_cleanup_form',
|
||||
trackingPayload,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('show a success toast when submit succeed', async () => {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
PUSH_COMMAND_LABEL,
|
||||
COPY_PUSH_TITLE,
|
||||
} from '~/packages_and_registries/container_registry/explorer/constants';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
|
||||
|
||||
import { dockerCommands } from 'jest/packages_and_registries/container_registry/explorer/mock_data';
|
||||
|
|
@ -35,7 +35,6 @@ describe('cli_commands', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Tracking, 'event');
|
||||
mountComponent();
|
||||
});
|
||||
|
||||
|
|
@ -43,13 +42,25 @@ describe('cli_commands', () => {
|
|||
expect(findDropdownButton().text()).toContain(QUICK_START);
|
||||
});
|
||||
|
||||
it('clicking on the dropdown emit a tracking event', () => {
|
||||
findDropdownButton().vm.$emit('shown');
|
||||
expect(Tracking.event).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
'click_dropdown',
|
||||
expect.objectContaining({ label: 'quickstart_dropdown' }),
|
||||
);
|
||||
describe('tracking', () => {
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('clicking on the dropdown emit a tracking event', () => {
|
||||
findDropdownButton().vm.$emit('shown');
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
'click_dropdown',
|
||||
expect.objectContaining({ label: 'quickstart_dropdown' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import Tracking from '~/tracking';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
|
||||
|
||||
|
|
@ -59,7 +59,11 @@ describe('Package code instruction', () => {
|
|||
const trackingLabel = 'foo_label';
|
||||
|
||||
beforeEach(() => {
|
||||
eventSpy = jest.spyOn(Tracking, 'event');
|
||||
eventSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('should not track when no trackingAction is provided', () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['ProjectSort'], feature_category: :groups_and_projects do
|
||||
specify { expect(described_class.graphql_name).to eq('ProjectSort') }
|
||||
|
||||
it_behaves_like 'common sort values'
|
||||
|
||||
it 'exposes all the existing issue sort values' do
|
||||
expect(described_class.values.keys).to include(
|
||||
*%w[
|
||||
ID_ASC ID_DESC LATEST_ACTIVITY_ASC LATEST_ACTIVITY_DESC
|
||||
NAME_ASC NAME_DESC PATH_ASC PATH_DESC STARS_ASC STARS_DESC
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -38,6 +38,7 @@ RSpec.describe GitlabSchema.types['User'], feature_category: :user_profile do
|
|||
groupCount
|
||||
projectMemberships
|
||||
starredProjects
|
||||
contributedProjects
|
||||
callouts
|
||||
namespace
|
||||
timelogs
|
||||
|
|
|
|||
|
|
@ -234,24 +234,14 @@ RSpec.describe AppearancesHelper do
|
|||
describe '#custom_sign_in_description' do
|
||||
it 'returns an empty string if no custom description is found' do
|
||||
allow(helper).to receive(:current_appearance).and_return(nil)
|
||||
allow(Gitlab::CurrentSettings).to receive(:sign_in_text).and_return(nil)
|
||||
allow(Gitlab::CurrentSettings).to receive(:help_text).and_return(nil)
|
||||
|
||||
expect(helper.custom_sign_in_description).to eq('')
|
||||
end
|
||||
|
||||
it 'returns a custom description if all the setting options are found' do
|
||||
allow(helper).to receive(:markdown_field).and_return('1')
|
||||
allow(helper).to receive(:markdown).and_return('2', '3')
|
||||
it 'returns a markdown of the custom description' do
|
||||
allow(helper).to receive(:markdown_field).and_return('<p>1</p>')
|
||||
|
||||
expect(helper.custom_sign_in_description).to eq('1<br>2<br>3')
|
||||
end
|
||||
|
||||
it 'returns a custom description if only one setting options is found' do
|
||||
allow(helper).to receive(:markdown_field).and_return('')
|
||||
allow(helper).to receive(:markdown).and_return('2', '')
|
||||
|
||||
expect(helper.custom_sign_in_description).to eq('2')
|
||||
expect(helper.custom_sign_in_description).to eq('<p>1</p>')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue