Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
41d446ba3f
commit
3fe3436877
43 changed files with 1634 additions and 37 deletions
|
@ -81,3 +81,20 @@ export const lineChartOptions = ({ width, numberOfPoints, shouldAdjustFontSize }
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Takes a dataset and returns an array containing the y-values of it's first and last entry.
|
||||
* (e.g., [['xValue1', 'yValue1'], ['xValue2', 'yValue2'], ['xValue3', 'yValue3']] will yield ['yValue1', 'yValue3'])
|
||||
*
|
||||
* @param {Array} data
|
||||
* @returns {[*, *]}
|
||||
*/
|
||||
export const firstAndLastY = data => {
|
||||
const [firstEntry] = data;
|
||||
const [lastEntry] = data.slice(-1);
|
||||
|
||||
const firstY = firstEntry[1];
|
||||
const lastY = lastEntry[1];
|
||||
|
||||
return [firstY, lastY];
|
||||
};
|
||||
|
|
|
@ -117,3 +117,36 @@ export const median = arr => {
|
|||
const sorted = arr.sort((a, b) => a - b);
|
||||
return arr.length % 2 !== 0 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the change from one value to the other as a percentage.
|
||||
* @param {Number} firstY
|
||||
* @param {Number} lastY
|
||||
* @returns {Number}
|
||||
*/
|
||||
export const changeInPercent = (firstY, lastY) => {
|
||||
if (firstY === lastY) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.round(((lastY - firstY) / Math.abs(firstY)) * 100);
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes and formats the change from one value to the other as a percentage.
|
||||
* Prepends the computed percentage with either "+" or "-" to indicate an in- or decrease and
|
||||
* returns a given string if the result is not finite (for example, if the first value is "0").
|
||||
* @param firstY
|
||||
* @param lastY
|
||||
* @param nonFiniteResult
|
||||
* @returns {String}
|
||||
*/
|
||||
export const formattedChangeInPercent = (firstY, lastY, { nonFiniteResult = '-' } = {}) => {
|
||||
const change = changeInPercent(firstY, lastY);
|
||||
|
||||
if (!Number.isFinite(change)) {
|
||||
return nonFiniteResult;
|
||||
}
|
||||
|
||||
return `${change >= 0 ? '+' : ''}${change}%`;
|
||||
};
|
||||
|
|
|
@ -265,7 +265,11 @@ export default {
|
|||
<div class="table-section section-10 commit-link">
|
||||
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Status') }}</div>
|
||||
<div class="table-mobile-content">
|
||||
<ci-badge :status="pipelineStatus" :show-text="!isChildView" />
|
||||
<ci-badge
|
||||
:status="pipelineStatus"
|
||||
:show-text="!isChildView"
|
||||
data-qa-selector="pipeline_commit_status"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -562,6 +562,8 @@ img.emoji {
|
|||
}
|
||||
|
||||
.gl-font-size-small { font-size: $gl-font-size-small; }
|
||||
.gl-font-size-large { font-size: $gl-font-size-large; }
|
||||
|
||||
.gl-line-height-24 { line-height: $gl-line-height-24; }
|
||||
|
||||
.gl-font-size-12 { font-size: $gl-font-size-12; }
|
||||
|
|
|
@ -110,7 +110,10 @@ class ProjectsFinder < UnionFinder
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def by_ids(items)
|
||||
project_ids_relation ? items.where(id: project_ids_relation) : items
|
||||
items = items.where(id: project_ids_relation) if project_ids_relation
|
||||
items = items.where('id > ?', params[:id_after]) if params[:id_after]
|
||||
items = items.where('id < ?', params[:id_before]) if params[:id_before]
|
||||
items
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
|
|
53
app/graphql/mutations/merge_requests/set_labels.rb
Normal file
53
app/graphql/mutations/merge_requests/set_labels.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module MergeRequests
|
||||
class SetLabels < Base
|
||||
graphql_name 'MergeRequestSetLabels'
|
||||
|
||||
argument :label_ids,
|
||||
[GraphQL::ID_TYPE],
|
||||
required: true,
|
||||
description: <<~DESC
|
||||
The Label IDs to set. Replaces existing labels by default.
|
||||
DESC
|
||||
|
||||
argument :operation_mode,
|
||||
Types::MutationOperationModeEnum,
|
||||
required: false,
|
||||
description: <<~DESC
|
||||
Changes the operation mode. Defaults to REPLACE.
|
||||
DESC
|
||||
|
||||
def resolve(project_path:, iid:, label_ids:, operation_mode: Types::MutationOperationModeEnum.enum[:replace])
|
||||
merge_request = authorized_find!(project_path: project_path, iid: iid)
|
||||
project = merge_request.project
|
||||
|
||||
label_ids = label_ids
|
||||
.select(&method(:label_descendant?))
|
||||
.map { |gid| GlobalID.parse(gid).model_id } # MergeRequests::UpdateService expects integers
|
||||
|
||||
attribute_name = case operation_mode
|
||||
when Types::MutationOperationModeEnum.enum[:append]
|
||||
:add_label_ids
|
||||
when Types::MutationOperationModeEnum.enum[:remove]
|
||||
:remove_label_ids
|
||||
else
|
||||
:label_ids
|
||||
end
|
||||
|
||||
::MergeRequests::UpdateService.new(project, current_user, attribute_name => label_ids)
|
||||
.execute(merge_request)
|
||||
|
||||
{
|
||||
merge_request: merge_request,
|
||||
errors: merge_request.errors.full_messages
|
||||
}
|
||||
end
|
||||
|
||||
def label_descendant?(gid)
|
||||
GlobalID.parse(gid)&.model_class&.ancestors&.include?(Label)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
29
app/graphql/mutations/merge_requests/set_locked.rb
Normal file
29
app/graphql/mutations/merge_requests/set_locked.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module MergeRequests
|
||||
class SetLocked < Base
|
||||
graphql_name 'MergeRequestSetLocked'
|
||||
|
||||
argument :locked,
|
||||
GraphQL::BOOLEAN_TYPE,
|
||||
required: true,
|
||||
description: <<~DESC
|
||||
Whether or not to lock the merge request.
|
||||
DESC
|
||||
|
||||
def resolve(project_path:, iid:, locked:)
|
||||
merge_request = authorized_find!(project_path: project_path, iid: iid)
|
||||
project = merge_request.project
|
||||
|
||||
::MergeRequests::UpdateService.new(project, current_user, discussion_locked: locked)
|
||||
.execute(merge_request)
|
||||
|
||||
{
|
||||
merge_request: merge_request,
|
||||
errors: merge_request.errors.full_messages
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
app/graphql/mutations/merge_requests/set_subscription.rb
Normal file
26
app/graphql/mutations/merge_requests/set_subscription.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module MergeRequests
|
||||
class SetSubscription < Base
|
||||
graphql_name 'MergeRequestSetSubscription'
|
||||
|
||||
argument :subscribed_state,
|
||||
GraphQL::BOOLEAN_TYPE,
|
||||
required: true,
|
||||
description: 'The desired state of the subscription'
|
||||
|
||||
def resolve(project_path:, iid:, subscribed_state:)
|
||||
merge_request = authorized_find!(project_path: project_path, iid: iid)
|
||||
project = merge_request.project
|
||||
|
||||
merge_request.set_subscription(current_user, subscribed_state, project)
|
||||
|
||||
{
|
||||
merge_request: merge_request,
|
||||
errors: merge_request.errors.full_messages
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,6 +6,8 @@ module Types
|
|||
|
||||
authorize :read_label
|
||||
|
||||
field :id, GraphQL::ID_TYPE, null: false,
|
||||
description: 'Label ID'
|
||||
field :description, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Description of the label (markdown rendered as HTML for caching)'
|
||||
markdown_field :description_html, null: true
|
||||
|
|
|
@ -9,7 +9,10 @@ module Types
|
|||
mount_mutation Mutations::AwardEmojis::Add
|
||||
mount_mutation Mutations::AwardEmojis::Remove
|
||||
mount_mutation Mutations::AwardEmojis::Toggle
|
||||
mount_mutation Mutations::MergeRequests::SetLabels
|
||||
mount_mutation Mutations::MergeRequests::SetLocked
|
||||
mount_mutation Mutations::MergeRequests::SetMilestone
|
||||
mount_mutation Mutations::MergeRequests::SetSubscription
|
||||
mount_mutation Mutations::MergeRequests::SetWip, calls_gitaly: true
|
||||
mount_mutation Mutations::MergeRequests::SetAssignees
|
||||
mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
.container.section-body
|
||||
.row
|
||||
.blank-state-welcome.w-100
|
||||
%h2.blank-state-welcome-title
|
||||
%h2.blank-state-welcome-title{ data: { qa_selector: 'welcome_title_content' } }
|
||||
= _('Welcome to GitLab')
|
||||
%p.blank-state-text
|
||||
= _('Faster releases. Better code. Less pain.')
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Vulnerabilities history chart - use sparklines
|
||||
merge_request: 19745
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'GraphQL: Create MR mutations needed for the sidebar'
|
||||
merge_request: 19913
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/ab-projects-id-filter.yml
Normal file
5
changelogs/unreleased/ab-projects-id-filter.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add id_before, id_after filter param to projects API
|
||||
merge_request: 19949
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Change the default concurrency factor of merge train to 20
|
||||
merge_request: 20201
|
||||
author:
|
||||
type: changed
|
|
@ -2814,6 +2814,11 @@ type Label {
|
|||
"""
|
||||
descriptionHtml: String
|
||||
|
||||
"""
|
||||
Label ID
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Text color of the label
|
||||
"""
|
||||
|
@ -3407,6 +3412,101 @@ type MergeRequestSetAssigneesPayload {
|
|||
mergeRequest: MergeRequest
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of MergeRequestSetLabels
|
||||
"""
|
||||
input MergeRequestSetLabelsInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
The iid of the merge request to mutate
|
||||
"""
|
||||
iid: String!
|
||||
|
||||
"""
|
||||
The Label IDs to set. Replaces existing labels by default.
|
||||
"""
|
||||
labelIds: [ID!]!
|
||||
|
||||
"""
|
||||
Changes the operation mode. Defaults to REPLACE.
|
||||
"""
|
||||
operationMode: MutationOperationMode
|
||||
|
||||
"""
|
||||
The project the merge request to mutate is in
|
||||
"""
|
||||
projectPath: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of MergeRequestSetLabels
|
||||
"""
|
||||
type MergeRequestSetLabelsPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Reasons why the mutation failed.
|
||||
"""
|
||||
errors: [String!]!
|
||||
|
||||
"""
|
||||
The merge request after mutation
|
||||
"""
|
||||
mergeRequest: MergeRequest
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of MergeRequestSetLocked
|
||||
"""
|
||||
input MergeRequestSetLockedInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
The iid of the merge request to mutate
|
||||
"""
|
||||
iid: String!
|
||||
|
||||
"""
|
||||
Whether or not to lock the merge request.
|
||||
"""
|
||||
locked: Boolean!
|
||||
|
||||
"""
|
||||
The project the merge request to mutate is in
|
||||
"""
|
||||
projectPath: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of MergeRequestSetLocked
|
||||
"""
|
||||
type MergeRequestSetLockedPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Reasons why the mutation failed.
|
||||
"""
|
||||
errors: [String!]!
|
||||
|
||||
"""
|
||||
The merge request after mutation
|
||||
"""
|
||||
mergeRequest: MergeRequest
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of MergeRequestSetMilestone
|
||||
"""
|
||||
|
@ -3452,6 +3552,51 @@ type MergeRequestSetMilestonePayload {
|
|||
mergeRequest: MergeRequest
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of MergeRequestSetSubscription
|
||||
"""
|
||||
input MergeRequestSetSubscriptionInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
The iid of the merge request to mutate
|
||||
"""
|
||||
iid: String!
|
||||
|
||||
"""
|
||||
The project the merge request to mutate is in
|
||||
"""
|
||||
projectPath: ID!
|
||||
|
||||
"""
|
||||
The desired state of the subscription
|
||||
"""
|
||||
subscribedState: Boolean!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of MergeRequestSetSubscription
|
||||
"""
|
||||
type MergeRequestSetSubscriptionPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Reasons why the mutation failed.
|
||||
"""
|
||||
errors: [String!]!
|
||||
|
||||
"""
|
||||
The merge request after mutation
|
||||
"""
|
||||
mergeRequest: MergeRequest
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of MergeRequestSetWip
|
||||
"""
|
||||
|
@ -3588,7 +3733,10 @@ type Mutation {
|
|||
epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
|
||||
epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
|
||||
mergeRequestSetAssignees(input: MergeRequestSetAssigneesInput!): MergeRequestSetAssigneesPayload
|
||||
mergeRequestSetLabels(input: MergeRequestSetLabelsInput!): MergeRequestSetLabelsPayload
|
||||
mergeRequestSetLocked(input: MergeRequestSetLockedInput!): MergeRequestSetLockedPayload
|
||||
mergeRequestSetMilestone(input: MergeRequestSetMilestoneInput!): MergeRequestSetMilestonePayload
|
||||
mergeRequestSetSubscription(input: MergeRequestSetSubscriptionInput!): MergeRequestSetSubscriptionPayload
|
||||
mergeRequestSetWip(input: MergeRequestSetWipInput!): MergeRequestSetWipPayload
|
||||
removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload
|
||||
todoMarkDone(input: TodoMarkDoneInput!): TodoMarkDonePayload
|
||||
|
|
|
@ -6479,6 +6479,24 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "Label ID",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "textColor",
|
||||
"description": "Text color of the label",
|
||||
|
@ -14763,6 +14781,60 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequestSetLabels",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "MergeRequestSetLabelsInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequestSetLabelsPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequestSetLocked",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "MergeRequestSetLockedInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequestSetLockedPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequestSetMilestone",
|
||||
"description": null,
|
||||
|
@ -14790,6 +14862,33 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequestSetSubscription",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "MergeRequestSetSubscriptionInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequestSetSubscriptionPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequestSetWip",
|
||||
"description": null,
|
||||
|
@ -15447,6 +15546,313 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequestSetLabelsPayload",
|
||||
"description": "Autogenerated return type of MergeRequestSetLabels",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Reasons why the mutation failed.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequest",
|
||||
"description": "The merge request after mutation",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequest",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "MergeRequestSetLabelsInput",
|
||||
"description": "Autogenerated input type of MergeRequestSetLabels",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "projectPath",
|
||||
"description": "The project the merge request to mutate is in",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "iid",
|
||||
"description": "The iid of the merge request to mutate",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "labelIds",
|
||||
"description": "The Label IDs to set. Replaces existing labels by default.\n",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "operationMode",
|
||||
"description": "Changes the operation mode. Defaults to REPLACE.\n",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "MutationOperationMode",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "MutationOperationMode",
|
||||
"description": "Different toggles for changing mutator behavior.",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "REPLACE",
|
||||
"description": "Performs a replace operation",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "APPEND",
|
||||
"description": "Performs an append operation",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "REMOVE",
|
||||
"description": "Performs a removal operation",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequestSetLockedPayload",
|
||||
"description": "Autogenerated return type of MergeRequestSetLocked",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Reasons why the mutation failed.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequest",
|
||||
"description": "The merge request after mutation",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequest",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "MergeRequestSetLockedInput",
|
||||
"description": "Autogenerated input type of MergeRequestSetLocked",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "projectPath",
|
||||
"description": "The project the merge request to mutate is in",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "iid",
|
||||
"description": "The iid of the merge request to mutate",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "locked",
|
||||
"description": "Whether or not to lock the merge request.\n",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequestSetMilestonePayload",
|
||||
|
@ -15573,6 +15979,136 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequestSetSubscriptionPayload",
|
||||
"description": "Autogenerated return type of MergeRequestSetSubscription",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Reasons why the mutation failed.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequest",
|
||||
"description": "The merge request after mutation",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequest",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "MergeRequestSetSubscriptionInput",
|
||||
"description": "Autogenerated input type of MergeRequestSetSubscription",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "projectPath",
|
||||
"description": "The project the merge request to mutate is in",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "iid",
|
||||
"description": "The iid of the merge request to mutate",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "subscribedState",
|
||||
"description": "The desired state of the subscription",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequestSetWipPayload",
|
||||
|
@ -15851,35 +16387,6 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "MutationOperationMode",
|
||||
"description": "Different toggles for changing mutator behavior.",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "REPLACE",
|
||||
"description": "Performs a replace operation",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "APPEND",
|
||||
"description": "Performs an append operation",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "REMOVE",
|
||||
"description": "Performs a removal operation",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "CreateNotePayload",
|
||||
|
|
|
@ -410,6 +410,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `id` | ID! | Label ID |
|
||||
| `description` | String | Description of the label (markdown rendered as HTML for caching) |
|
||||
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
|
||||
| `title` | String! | Content of the label |
|
||||
|
@ -491,6 +492,22 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `mergeRequest` | MergeRequest | The merge request after mutation |
|
||||
|
||||
### MergeRequestSetLabelsPayload
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `mergeRequest` | MergeRequest | The merge request after mutation |
|
||||
|
||||
### MergeRequestSetLockedPayload
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `mergeRequest` | MergeRequest | The merge request after mutation |
|
||||
|
||||
### MergeRequestSetMilestonePayload
|
||||
|
||||
| Name | Type | Description |
|
||||
|
@ -499,6 +516,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `mergeRequest` | MergeRequest | The merge request after mutation |
|
||||
|
||||
### MergeRequestSetSubscriptionPayload
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||
| `mergeRequest` | MergeRequest | The merge request after mutation |
|
||||
|
||||
### MergeRequestSetWipPayload
|
||||
|
||||
| Name | Type | Description |
|
||||
|
|
|
@ -58,6 +58,8 @@ GET /projects
|
|||
| `wiki_checksum_failed` | boolean | no | **(PREMIUM)** Limit projects where the wiki checksum calculation has failed ([Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/6137) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2) |
|
||||
| `repository_checksum_failed` | boolean | no | **(PREMIUM)** Limit projects where the repository checksum calculation has failed ([Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/6137) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2) |
|
||||
| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) |
|
||||
| `id_after` | integer | no | Limit results to projects with IDs greater than the specified ID |
|
||||
| `id_before` | integer | no | Limit results to projects with IDs less than the specified ID |
|
||||
|
||||
When `simple=true` or the user is unauthenticated this returns something like:
|
||||
|
||||
|
@ -304,6 +306,8 @@ GET /users/:user_id/projects
|
|||
| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature |
|
||||
| `with_programming_language` | string | no | Limit by projects which use the given programming language |
|
||||
| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) |
|
||||
| `id_after` | integer | no | Limit results to projects with IDs greater than the specified ID |
|
||||
| `id_before` | integer | no | Limit results to projects with IDs less than the specified ID |
|
||||
|
||||
```json
|
||||
[
|
||||
|
|
|
@ -32,8 +32,8 @@ Merge trains have the following requirements and limitations:
|
|||
- This feature requires that
|
||||
[pipelines for merged results](../index.md#pipelines-for-merged-results-premium) are
|
||||
**configured properly**.
|
||||
- Each merge train can run a maximum of **four** pipelines in parallel.
|
||||
If more than four merge requests are added to the merge train, the merge requests
|
||||
- Each merge train can run a maximum of **twenty** pipelines in parallel.
|
||||
If more than twenty merge requests are added to the merge train, the merge requests
|
||||
will be queued until a slot in the merge train is free. There is no limit to the
|
||||
number of merge requests that can be queued.
|
||||
- This feature does not support [squash and merge](../../../../user/project/merge_requests/squash_and_merge.md).
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 60 KiB |
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
|
@ -76,7 +76,7 @@ To the right of the filters, you should see a **Hide dismissed** toggle button (
|
|||
NOTE: **Note:**
|
||||
The dashboard only shows projects with [security reports](#supported-reports) enabled in a group.
|
||||
|
||||
![dashboard with action buttons and metrics](img/group_security_dashboard_v12_3.png)
|
||||
![dashboard with action buttons and metrics](img/group_security_dashboard_v12_4.png)
|
||||
|
||||
Selecting one or more filters will filter the results in this page. Disabling the **Hide dismissed**
|
||||
toggle button will let you also see vulnerabilities that have been dismissed.
|
||||
|
|
|
@ -479,6 +479,8 @@ module API
|
|||
finder_params[:user] = params.delete(:user) if params[:user]
|
||||
finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes]
|
||||
finder_params[:min_access_level] = params[:min_access_level] if params[:min_access_level]
|
||||
finder_params[:id_after] = params[:id_after] if params[:id_after]
|
||||
finder_params[:id_before] = params[:id_before] if params[:id_before]
|
||||
finder_params
|
||||
end
|
||||
|
||||
|
|
|
@ -61,6 +61,8 @@ module API
|
|||
optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
|
||||
optional :with_programming_language, type: String, desc: 'Limit to repositories which use the given programming language'
|
||||
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user'
|
||||
optional :id_after, type: Integer, desc: 'Limit results to projects with IDs greater than the specified ID'
|
||||
optional :id_before, type: Integer, desc: 'Limit results to projects with IDs less than the specified ID'
|
||||
|
||||
use :optional_filter_params_ee
|
||||
end
|
||||
|
|
|
@ -4994,6 +4994,9 @@ msgstr ""
|
|||
msgid "Current password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Current vulnerabilities count"
|
||||
msgstr ""
|
||||
|
||||
msgid "CurrentUser|Profile"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5791,6 +5794,9 @@ msgstr ""
|
|||
msgid "Diff limits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Difference between start date and now"
|
||||
msgstr ""
|
||||
|
||||
msgid "DiffsCompareBaseBranch|(base)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19180,6 +19186,9 @@ msgstr ""
|
|||
msgid "VulnerabilityChart|%{formattedStartDate} to today"
|
||||
msgstr ""
|
||||
|
||||
msgid "VulnerabilityChart|Severity"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Class"
|
||||
msgstr ""
|
||||
|
||||
|
|
1
qa/qa.rb
1
qa/qa.rb
|
@ -165,6 +165,7 @@ module QA
|
|||
module Dashboard
|
||||
autoload :Projects, 'qa/page/dashboard/projects'
|
||||
autoload :Groups, 'qa/page/dashboard/groups'
|
||||
autoload :Welcome, 'qa/page/dashboard/welcome'
|
||||
|
||||
module Snippet
|
||||
autoload :New, 'qa/page/dashboard/snippet/new'
|
||||
|
|
|
@ -135,6 +135,40 @@ module QA
|
|||
has_no_css?('.fa-spinner.block-loading', wait: Capybara.default_max_wait_time)
|
||||
end
|
||||
|
||||
def has_loaded_all_images?
|
||||
# I don't know of a foolproof way to wait for all images to load
|
||||
# This loop gives time for the img tags to be rendered and for
|
||||
# images to start loading.
|
||||
previous_total_images = 0
|
||||
wait(interval: 1) do
|
||||
current_total_images = all("img").size
|
||||
result = previous_total_images == current_total_images
|
||||
previous_total_images = current_total_images
|
||||
result
|
||||
end
|
||||
|
||||
# Retry until all images found can be fetched via HTTP, and
|
||||
# check that the image has a non-zero natural width (a broken
|
||||
# img tag could have a width, but wouldn't have a natural width)
|
||||
|
||||
# Unfortunately, this doesn't account for SVGs. They're rendered
|
||||
# as HTML, so there doesn't seem to be a way to check that they
|
||||
# display properly via Selenium. However, if the SVG couldn't be
|
||||
# rendered (e.g., because the file doesn't exist), the whole page
|
||||
# won't display properly, so we should catch that with the test
|
||||
# this method is called from.
|
||||
|
||||
# The user's avatar is an img, which could be a gravatar image,
|
||||
# so we skip that by only checking for images hosted internally
|
||||
retry_until(sleep_interval: 1) do
|
||||
all("img").all? do |image|
|
||||
next true unless URI(image['src']).host == URI(page.current_url).host
|
||||
|
||||
asset_exists?(image['src']) && image['naturalWidth'].to_i > 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def wait_for_animated_element(name)
|
||||
# It would be ideal if we could detect when the animation is complete
|
||||
# but in some cases there's nothing we can easily access via capybara
|
||||
|
|
17
qa/qa/page/dashboard/welcome.rb
Normal file
17
qa/qa/page/dashboard/welcome.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Page
|
||||
module Dashboard
|
||||
class Welcome < Page::Base
|
||||
view 'app/views/dashboard/projects/_zero_authorized_projects.html.haml' do
|
||||
element :welcome_title_content
|
||||
end
|
||||
|
||||
def has_welcome_title?(text)
|
||||
has_element?(:welcome_title_content, text: text)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,6 +7,10 @@ module QA::Page
|
|||
element :pipeline_link, 'class="js-pipeline-url-link' # rubocop:disable QA/ElementWithPattern
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/pipelines/components/pipelines_table_row.vue' do
|
||||
element :pipeline_commit_status
|
||||
end
|
||||
|
||||
def click_on_latest_pipeline
|
||||
css = '.js-pipeline-url-link'
|
||||
|
||||
|
@ -16,6 +20,14 @@ module QA::Page
|
|||
|
||||
link.click
|
||||
end
|
||||
|
||||
def wait_for_latest_pipeline_success
|
||||
wait(reload: false, max: 300) do
|
||||
within_element_by_index(:pipeline_commit_status, 0) do
|
||||
has_text?('passed')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,13 +7,14 @@ module QA
|
|||
class User < Base
|
||||
attr_reader :unique_id
|
||||
attr_writer :username, :password
|
||||
attr_accessor :provider, :extern_uid
|
||||
attr_accessor :admin, :provider, :extern_uid
|
||||
|
||||
attribute :id
|
||||
attribute :name
|
||||
attribute :email
|
||||
|
||||
def initialize
|
||||
@admin = false
|
||||
@unique_id = SecureRandom.hex(8)
|
||||
end
|
||||
|
||||
|
@ -75,6 +76,16 @@ module QA
|
|||
super
|
||||
end
|
||||
|
||||
def api_delete
|
||||
super
|
||||
|
||||
QA::Runtime::Logger.debug("Deleted user '#{username}'") if Runtime::Env.debug?
|
||||
end
|
||||
|
||||
def api_delete_path
|
||||
"/users/#{id}"
|
||||
end
|
||||
|
||||
def api_get_path
|
||||
"/users/#{fetch_id(username)}"
|
||||
end
|
||||
|
@ -85,6 +96,7 @@ module QA
|
|||
|
||||
def api_post_body
|
||||
{
|
||||
admin: admin,
|
||||
email: email,
|
||||
password: password,
|
||||
username: username,
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'nokogiri'
|
||||
|
||||
module QA
|
||||
context 'Manage' do
|
||||
describe 'Check for broken images', :requires_admin do
|
||||
before(:context) do
|
||||
admin = QA::Resource::User.new.tap do |user|
|
||||
user.username = QA::Runtime::User.admin_username
|
||||
user.password = QA::Runtime::User.admin_password
|
||||
end
|
||||
@api_client = Runtime::API::Client.new(:gitlab, user: admin)
|
||||
@new_user = Resource::User.fabricate_via_api! do |user|
|
||||
user.api_client = @api_client
|
||||
end
|
||||
@new_admin = Resource::User.fabricate_via_api! do |user|
|
||||
user.admin = true
|
||||
user.api_client = @api_client
|
||||
end
|
||||
|
||||
Page::Main::Menu.perform(&:sign_out_if_signed_in)
|
||||
end
|
||||
|
||||
after(:context) do
|
||||
@new_user.remove_via_api!
|
||||
@new_admin.remove_via_api!
|
||||
end
|
||||
|
||||
shared_examples 'loads all images' do
|
||||
it 'loads all images' do
|
||||
Runtime::Browser.visit(:gitlab, Page::Main::Login)
|
||||
Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: new_user) }
|
||||
|
||||
Page::Dashboard::Welcome.perform do |welcome|
|
||||
expect(welcome).to have_welcome_title("Welcome to GitLab")
|
||||
|
||||
# This would be better if it were a visual validation test
|
||||
expect(welcome).to have_loaded_all_images
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in as a new user' do
|
||||
it_behaves_like 'loads all images' do
|
||||
let(:new_user) { @new_user }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in as a new admin' do
|
||||
it_behaves_like 'loads all images' do
|
||||
let(:new_user) { @new_admin }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -58,6 +58,31 @@ describe ProjectsFinder, :do_not_mock_admin_mode do
|
|||
it { is_expected.to eq([internal_project]) }
|
||||
end
|
||||
|
||||
describe 'with id_after' do
|
||||
context 'only returns projects with a project id greater than given' do
|
||||
let(:params) { { id_after: internal_project.id }}
|
||||
|
||||
it { is_expected.to eq([public_project]) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with id_before' do
|
||||
context 'only returns projects with a project id less than given' do
|
||||
let(:params) { { id_before: public_project.id }}
|
||||
|
||||
it { is_expected.to eq([internal_project]) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with both id_before and id_after' do
|
||||
context 'only returns projects with a project id less than given' do
|
||||
let!(:projects) { create_list(:project, 5, :public) }
|
||||
let(:params) { { id_after: projects.first.id, id_before: projects.last.id }}
|
||||
|
||||
it { is_expected.to contain_exactly(*projects[1..-2]) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'filter by visibility_level' do
|
||||
before do
|
||||
private_project.add_maintainer(user)
|
||||
|
|
11
spec/frontend/lib/utils/chart_utils_spec.js
Normal file
11
spec/frontend/lib/utils/chart_utils_spec.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { firstAndLastY } from '~/lib/utils/chart_utils';
|
||||
|
||||
describe('Chart utils', () => {
|
||||
describe('firstAndLastY', () => {
|
||||
it('returns the first and last y-values of a given data set as an array', () => {
|
||||
const data = [['', 1], ['', 2], ['', 3]];
|
||||
|
||||
expect(firstAndLastY(data)).toEqual([1, 3]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,6 +7,8 @@ import {
|
|||
sum,
|
||||
isOdd,
|
||||
median,
|
||||
changeInPercent,
|
||||
formattedChangeInPercent,
|
||||
} from '~/lib/utils/number_utils';
|
||||
|
||||
describe('Number Utils', () => {
|
||||
|
@ -122,4 +124,42 @@ describe('Number Utils', () => {
|
|||
expect(median(items)).toBe(14.5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeInPercent', () => {
|
||||
it.each`
|
||||
firstValue | secondValue | expectedOutput
|
||||
${99} | ${100} | ${1}
|
||||
${100} | ${99} | ${-1}
|
||||
${0} | ${99} | ${Infinity}
|
||||
${2} | ${2} | ${0}
|
||||
${-100} | ${-99} | ${1}
|
||||
`(
|
||||
'computes the change between $firstValue and $secondValue in percent',
|
||||
({ firstValue, secondValue, expectedOutput }) => {
|
||||
expect(changeInPercent(firstValue, secondValue)).toBe(expectedOutput);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('formattedChangeInPercent', () => {
|
||||
it('prepends "%" to the output', () => {
|
||||
expect(formattedChangeInPercent(1, 2)).toMatch(/%$/);
|
||||
});
|
||||
|
||||
it('indicates if the change was a decrease', () => {
|
||||
expect(formattedChangeInPercent(100, 99)).toContain('-1');
|
||||
});
|
||||
|
||||
it('indicates if the change was an increase', () => {
|
||||
expect(formattedChangeInPercent(99, 100)).toContain('+1');
|
||||
});
|
||||
|
||||
it('shows "-" per default if the change can not be expressed in an integer', () => {
|
||||
expect(formattedChangeInPercent(0, 1)).toBe('-');
|
||||
});
|
||||
|
||||
it('shows the given fallback if the change can not be expressed in an integer', () => {
|
||||
expect(formattedChangeInPercent(0, 1, { nonFiniteResult: '*' })).toBe('*');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
77
spec/graphql/mutations/merge_requests/set_labels_spec.rb
Normal file
77
spec/graphql/mutations/merge_requests/set_labels_spec.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutations::MergeRequests::SetLabels do
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:user) { create(:user) }
|
||||
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) }
|
||||
|
||||
describe '#resolve' do
|
||||
let(:label) { create(:label, project: merge_request.project) }
|
||||
let(:label2) { create(:label, project: merge_request.project) }
|
||||
let(:label_ids) { [label.to_global_id] }
|
||||
let(:mutated_merge_request) { subject[:merge_request] }
|
||||
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids) }
|
||||
|
||||
it 'raises an error if the resource is not accessible to the user' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
|
||||
context 'when the user can update the merge request' do
|
||||
before do
|
||||
merge_request.project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'sets the labels, removing all others' do
|
||||
merge_request.update!(labels: [label2])
|
||||
|
||||
expect(mutated_merge_request).to eq(merge_request)
|
||||
expect(mutated_merge_request.labels).to contain_exactly(label)
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
|
||||
it 'returns errors merge request could not be updated' do
|
||||
# Make the merge request invalid
|
||||
merge_request.allow_broken = true
|
||||
merge_request.update!(source_project: nil)
|
||||
|
||||
expect(subject[:errors]).not_to be_empty
|
||||
end
|
||||
|
||||
context 'when passing an empty array' do
|
||||
let(:label_ids) { [] }
|
||||
|
||||
it 'removes all labels' do
|
||||
merge_request.update!(labels: [label])
|
||||
|
||||
expect(mutated_merge_request.labels).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when passing operation_mode as APPEND' do
|
||||
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids, operation_mode: Types::MutationOperationModeEnum.enum[:append]) }
|
||||
|
||||
it 'sets the labels, without removing others' do
|
||||
merge_request.update!(labels: [label2])
|
||||
|
||||
expect(mutated_merge_request).to eq(merge_request)
|
||||
expect(mutated_merge_request.labels).to contain_exactly(label, label2)
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when passing operation_mode as REMOVE' do
|
||||
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids, operation_mode: Types::MutationOperationModeEnum.enum[:remove])}
|
||||
|
||||
it 'removes the labels, without removing others' do
|
||||
merge_request.update!(labels: [label, label2])
|
||||
|
||||
expect(mutated_merge_request).to eq(merge_request)
|
||||
expect(mutated_merge_request.labels).to contain_exactly(label2)
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
49
spec/graphql/mutations/merge_requests/set_locked_spec.rb
Normal file
49
spec/graphql/mutations/merge_requests/set_locked_spec.rb
Normal file
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutations::MergeRequests::SetLocked do
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:user) { create(:user) }
|
||||
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) }
|
||||
|
||||
describe '#resolve' do
|
||||
let(:locked) { true }
|
||||
let(:mutated_merge_request) { subject[:merge_request] }
|
||||
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, locked: locked) }
|
||||
|
||||
it 'raises an error if the resource is not accessible to the user' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
|
||||
context 'when the user can update the merge request' do
|
||||
before do
|
||||
merge_request.project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns the merge request as discussion locked' do
|
||||
expect(mutated_merge_request).to eq(merge_request)
|
||||
expect(mutated_merge_request).to be_discussion_locked
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
|
||||
it 'returns errors merge request could not be updated' do
|
||||
# Make the merge request invalid
|
||||
merge_request.allow_broken = true
|
||||
merge_request.update!(source_project: nil)
|
||||
|
||||
expect(subject[:errors]).not_to be_empty
|
||||
end
|
||||
|
||||
context 'when passing locked as false' do
|
||||
let(:locked) { false }
|
||||
|
||||
it 'unlocks the discussion' do
|
||||
merge_request.update(discussion_locked: true)
|
||||
|
||||
expect(mutated_merge_request).not_to be_discussion_locked
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutations::MergeRequests::SetSubscription do
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:project) { merge_request.project }
|
||||
let(:user) { create(:user) }
|
||||
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) }
|
||||
|
||||
describe '#resolve' do
|
||||
let(:subscribe) { true }
|
||||
let(:mutated_merge_request) { subject[:merge_request] }
|
||||
subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, subscribed_state: subscribe) }
|
||||
|
||||
it 'raises an error if the resource is not accessible to the user' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
|
||||
context 'when the user can update the merge request' do
|
||||
before do
|
||||
merge_request.project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns the merge request as discussion locked' do
|
||||
expect(mutated_merge_request).to eq(merge_request)
|
||||
expect(mutated_merge_request.subscribed?(user, project)).to eq(true)
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
|
||||
context 'when passing subscribe as false' do
|
||||
let(:subscribe) { false }
|
||||
|
||||
it 'unsubscribes from the discussion' do
|
||||
merge_request.subscribe(user, project)
|
||||
|
||||
expect(mutated_merge_request.subscribed?(user, project)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,7 +3,7 @@ require 'spec_helper'
|
|||
|
||||
describe GitlabSchema.types['Label'] do
|
||||
it 'has the correct fields' do
|
||||
expected_fields = [:description, :description_html, :title, :color, :text_color]
|
||||
expected_fields = [:id, :description, :description_html, :title, :color, :text_color]
|
||||
|
||||
is_expected.to have_graphql_fields(*expected_fields)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'Setting labels of a merge request' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let(:current_user) { create(:user) }
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:project) { merge_request.project }
|
||||
let(:label) { create(:label, project: project) }
|
||||
let(:label2) { create(:label, project: project) }
|
||||
let(:input) { { label_ids: [GitlabSchema.id_from_object(label).to_s] } }
|
||||
|
||||
let(:mutation) do
|
||||
variables = {
|
||||
project_path: project.full_path,
|
||||
iid: merge_request.iid.to_s
|
||||
}
|
||||
graphql_mutation(:merge_request_set_labels, variables.merge(input),
|
||||
<<-QL.strip_heredoc
|
||||
clientMutationId
|
||||
errors
|
||||
mergeRequest {
|
||||
id
|
||||
labels {
|
||||
nodes {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
QL
|
||||
)
|
||||
end
|
||||
|
||||
def mutation_response
|
||||
graphql_mutation_response(:merge_request_set_labels)
|
||||
end
|
||||
|
||||
def mutation_label_nodes
|
||||
mutation_response['mergeRequest']['labels']['nodes']
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
it 'returns an error if the user is not allowed to update the merge request' do
|
||||
post_graphql_mutation(mutation, current_user: create(:user))
|
||||
|
||||
expect(graphql_errors).not_to be_empty
|
||||
end
|
||||
|
||||
it 'sets the merge request labels, removing existing ones' do
|
||||
merge_request.update(labels: [label2])
|
||||
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_label_nodes.count).to eq(1)
|
||||
expect(mutation_label_nodes[0]['id']).to eq(label.to_global_id.to_s)
|
||||
end
|
||||
|
||||
context 'when passing label_ids empty array as input' do
|
||||
let(:input) { { label_ids: [] } }
|
||||
|
||||
it 'removes the merge request labels' do
|
||||
merge_request.update!(labels: [label])
|
||||
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_label_nodes.count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when passing operation_mode as APPEND' do
|
||||
let(:input) { { operation_mode: Types::MutationOperationModeEnum.enum[:append], label_ids: [GitlabSchema.id_from_object(label).to_s] } }
|
||||
|
||||
before do
|
||||
merge_request.update!(labels: [label2])
|
||||
end
|
||||
|
||||
it 'sets the labels, without removing others' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_label_nodes.count).to eq(2)
|
||||
expect(mutation_label_nodes).to contain_exactly({ 'id' => label.to_global_id.to_s }, { 'id' => label2.to_global_id.to_s })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when passing operation_mode as REMOVE' do
|
||||
let(:input) { { operation_mode: Types::MutationOperationModeEnum.enum[:remove], label_ids: [GitlabSchema.id_from_object(label).to_s] } }
|
||||
|
||||
before do
|
||||
merge_request.update!(labels: [label, label2])
|
||||
end
|
||||
|
||||
it 'removes the labels, without removing others' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_label_nodes.count).to eq(1)
|
||||
expect(mutation_label_nodes[0]['id']).to eq(label2.to_global_id.to_s)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,79 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'Setting locked status of a merge request' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let(:current_user) { create(:user) }
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:project) { merge_request.project }
|
||||
let(:input) { { locked: true } }
|
||||
|
||||
let(:mutation) do
|
||||
variables = {
|
||||
project_path: project.full_path,
|
||||
iid: merge_request.iid.to_s
|
||||
}
|
||||
graphql_mutation(:merge_request_set_locked, variables.merge(input),
|
||||
<<-QL.strip_heredoc
|
||||
clientMutationId
|
||||
errors
|
||||
mergeRequest {
|
||||
id
|
||||
discussionLocked
|
||||
}
|
||||
QL
|
||||
)
|
||||
end
|
||||
|
||||
def mutation_response
|
||||
graphql_mutation_response(:merge_request_set_locked)['mergeRequest']['discussionLocked']
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
it 'returns an error if the user is not allowed to update the merge request' do
|
||||
post_graphql_mutation(mutation, current_user: create(:user))
|
||||
|
||||
expect(graphql_errors).not_to be_empty
|
||||
end
|
||||
|
||||
it 'marks the merge request as WIP' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_response).to eq(true)
|
||||
end
|
||||
|
||||
it 'does not do anything if the merge request was already locked' do
|
||||
merge_request.update!(discussion_locked: true)
|
||||
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_response).to eq(true)
|
||||
end
|
||||
|
||||
context 'when passing locked false as input' do
|
||||
let(:input) { { locked: false } }
|
||||
|
||||
it 'does not do anything if the merge request was not marked locked' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_response).to eq(false)
|
||||
end
|
||||
|
||||
it 'unmarks the merge request as locked' do
|
||||
merge_request.update!(discussion_locked: true)
|
||||
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_response).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'Setting subscribed status of a merge request' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let(:current_user) { create(:user) }
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:project) { merge_request.project }
|
||||
let(:input) { { subscribed_state: true } }
|
||||
|
||||
let(:mutation) do
|
||||
variables = {
|
||||
project_path: project.full_path,
|
||||
iid: merge_request.iid.to_s
|
||||
}
|
||||
graphql_mutation(:merge_request_set_subscription, variables.merge(input),
|
||||
<<-QL.strip_heredoc
|
||||
clientMutationId
|
||||
errors
|
||||
mergeRequest {
|
||||
id
|
||||
subscribed
|
||||
}
|
||||
QL
|
||||
)
|
||||
end
|
||||
|
||||
def mutation_response
|
||||
graphql_mutation_response(:merge_request_set_subscription)['mergeRequest']['subscribed']
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
it 'returns an error if the user is not allowed to update the merge request' do
|
||||
post_graphql_mutation(mutation, current_user: create(:user))
|
||||
|
||||
expect(graphql_errors).not_to be_empty
|
||||
end
|
||||
|
||||
it 'marks the merge request as WIP' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_response).to eq(true)
|
||||
end
|
||||
|
||||
context 'when passing subscribe false as input' do
|
||||
let(:input) { { subscribed_state: false } }
|
||||
|
||||
it 'unmarks the merge request as subscribed' do
|
||||
merge_request.subscribe(current_user, project)
|
||||
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_response).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -362,6 +362,30 @@ describe API::Projects do
|
|||
end
|
||||
end
|
||||
|
||||
context 'and using id_after' do
|
||||
it_behaves_like 'projects response' do
|
||||
let(:filter) { { id_after: project2.id } }
|
||||
let(:current_user) { user }
|
||||
let(:projects) { [public_project, project, project2, project3].select { |p| p.id > project2.id } }
|
||||
end
|
||||
end
|
||||
|
||||
context 'and using id_before' do
|
||||
it_behaves_like 'projects response' do
|
||||
let(:filter) { { id_before: project2.id } }
|
||||
let(:current_user) { user }
|
||||
let(:projects) { [public_project, project, project2, project3].select { |p| p.id < project2.id } }
|
||||
end
|
||||
end
|
||||
|
||||
context 'and using both id_after and id_before' do
|
||||
it_behaves_like 'projects response' do
|
||||
let(:filter) { { id_before: project2.id, id_after: public_project.id } }
|
||||
let(:current_user) { user }
|
||||
let(:projects) { [public_project, project, project2, project3].select { |p| p.id < project2.id && p.id > public_project.id } }
|
||||
end
|
||||
end
|
||||
|
||||
context 'and membership=true' do
|
||||
it_behaves_like 'projects response' do
|
||||
let(:filter) { { membership: true } }
|
||||
|
@ -848,6 +872,63 @@ describe API::Projects do
|
|||
expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id)
|
||||
end
|
||||
|
||||
context 'and using id_after' do
|
||||
let!(:another_public_project) { create(:project, :public, name: 'another_public_project', creator_id: user4.id, namespace: user4.namespace) }
|
||||
|
||||
it 'only returns projects with id_after filter given' do
|
||||
get api("/users/#{user4.id}/projects?id_after=#{public_project.id}", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.map { |project| project['id'] }).to contain_exactly(another_public_project.id)
|
||||
end
|
||||
|
||||
it 'returns both projects without a id_after filter' do
|
||||
get api("/users/#{user4.id}/projects", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id, another_public_project.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and using id_before' do
|
||||
let!(:another_public_project) { create(:project, :public, name: 'another_public_project', creator_id: user4.id, namespace: user4.namespace) }
|
||||
|
||||
it 'only returns projects with id_before filter given' do
|
||||
get api("/users/#{user4.id}/projects?id_before=#{another_public_project.id}", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id)
|
||||
end
|
||||
|
||||
it 'returns both projects without a id_before filter' do
|
||||
get api("/users/#{user4.id}/projects", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id, another_public_project.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and using both id_before and id_after' do
|
||||
let!(:more_projects) { create_list(:project, 5, :public, creator_id: user4.id, namespace: user4.namespace) }
|
||||
|
||||
it 'only returns projects with id matching the range' do
|
||||
get api("/users/#{user4.id}/projects?id_after=#{more_projects.first.id}&id_before=#{more_projects.last.id}", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.map { |project| project['id'] }).to contain_exactly(*more_projects[1..-2].map(&:id))
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns projects filtered by username' do
|
||||
get api("/users/#{user4.username}/projects/", user)
|
||||
|
||||
|
|
Loading…
Reference in a new issue