summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-06-17 06:08:15 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-17 06:08:15 +0000
commite4476c4a182e5af930799342f681405dc98d6a1c (patch)
treeff66eed0b3b797eb7605c1c1b2cf7e4415c21c7a
parent80816914ff75f9e0708bc7747200a9cfc5fc24e5 (diff)
downloadgitlab-ce-e4476c4a182e5af930799342f681405dc98d6a1c.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list.vue8
-rw-r--r--app/assets/javascripts/work_items/components/work_item_assignees.vue27
-rw-r--r--app/controllers/projects/settings/packages_and_registries_controller.rb7
-rw-r--r--app/graphql/types/ci/job_type.rb2
-rw-r--r--app/graphql/types/limited_countable_connection_type.rb26
-rw-r--r--app/policies/project_policy.rb8
-rw-r--r--app/services/two_factor/destroy_service.rb8
-rw-r--r--app/views/admin/broadcast_messages/index.html.haml4
-rw-r--r--config/feature_flags/development/omit_epic_subscribed.yml8
-rw-r--r--doc/administration/audit_event_streaming.md12
-rw-r--r--doc/administration/audit_events.md1
-rw-r--r--doc/api/graphql/reference/index.md39
-rw-r--r--doc/ci/cloud_deployment/ecs/deploy_to_aws_ecs.md4
-rw-r--r--doc/ci/cloud_deployment/index.md4
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md19
-rw-r--r--lib/sidebars/projects/menus/settings_menu.rb3
-rw-r--r--spec/frontend/work_items/components/work_item_assignees_spec.js21
-rw-r--r--spec/graphql/types/limited_countable_connection_type_spec.rb11
-rw-r--r--spec/policies/project_policy_spec.rb62
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb77
20 files changed, 288 insertions, 63 deletions
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
index 86b0666e7b0..91d78a7c28c 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
@@ -80,10 +80,10 @@ export default {
<template>
<div class="ide-commit-list-container">
- <header class="multi-file-commit-panel-header d-flex mb-0">
- <div class="d-flex align-items-center flex-fill">
+ <header class="multi-file-commit-panel-header gl-display-flex gl-mb-0">
+ <div class="gl-display-flex gl-align-items-center flex-fill">
<strong> {{ titleText }} </strong>
- <div class="d-flex ml-auto">
+ <div class="gl-display-flex gl-ml-auto">
<gl-button
v-if="!stagedList"
v-gl-tooltip
@@ -114,7 +114,7 @@ export default {
/>
</li>
</ul>
- <p v-else class="multi-file-commit-list form-text text-muted text-center">
+ <p v-else class="multi-file-commit-list form-text gl-text-gray-600 gl-text-center">
{{ emptyStateText }}
</p>
<gl-modal
diff --git a/app/assets/javascripts/work_items/components/work_item_assignees.vue b/app/assets/javascripts/work_items/components/work_item_assignees.vue
index 1c89476ea34..4d1c171772e 100644
--- a/app/assets/javascripts/work_items/components/work_item_assignees.vue
+++ b/app/assets/javascripts/work_items/components/work_item_assignees.vue
@@ -75,25 +75,25 @@ export default {
<span class="gl-font-weight-bold gl-w-15 gl-pt-2" data-testid="assignees-title">{{
__('Assignee(s)')
}}</span>
- <!-- TODO: Remove this div when https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2872 is merged -->
- <div
- v-if="assigneeListEmpty && !isEditing"
- class="add-assignees gl-min-w-fit-content gl-absolute gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-top-2 gl-z-index-0"
- data-testid="empty-state"
- >
- <gl-icon name="profile" />
- <span class="gl-ml-2">{{ __('Add assignees') }}</span>
- </div>
<gl-token-selector
ref="tokenSelector"
v-model="localAssignees"
hide-dropdown-with-no-items
:container-class="containerClass"
- class="gl-w-full gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base gl-z-index-1 gl-bg-transparent!"
+ class="gl-w-full gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base"
@token-remove="focusTokenSelector"
@focus="isEditing = true"
@blur="setAssignees"
>
+ <template #empty-placeholder>
+ <div
+ class="add-assignees gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-top-2"
+ data-testid="empty-state"
+ >
+ <gl-icon name="profile" />
+ <span class="gl-ml-2">{{ __('Add assignees') }}</span>
+ </div>
+ </template>
<template #token-content="{ token }">
<gl-link
:href="token.webUrl"
@@ -109,10 +109,3 @@ export default {
</gl-token-selector>
</div>
</template>
-
-<style lang="scss">
-/* TODO: Remove style block when https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2872 is merged */
-.work-item-assignees .add-assignees {
- left: 7.5rem;
-}
-</style>
diff --git a/app/controllers/projects/settings/packages_and_registries_controller.rb b/app/controllers/projects/settings/packages_and_registries_controller.rb
index 0cd2bfa9695..d3c08bef808 100644
--- a/app/controllers/projects/settings/packages_and_registries_controller.rb
+++ b/app/controllers/projects/settings/packages_and_registries_controller.rb
@@ -17,12 +17,7 @@ module Projects
private
def packages_and_registries_settings_enabled!
- render_404 unless can_destroy_container_registry_image?(project)
- end
-
- def can_destroy_container_registry_image?(project)
- Gitlab.config.registry.enabled &&
- can?(current_user, :destroy_container_image, project)
+ render_404 unless can?(current_user, :view_package_registry_project_settings, project)
end
end
end
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index f25fc56a588..b20a671179b 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -7,7 +7,7 @@ module Types
class JobType < BaseObject
graphql_name 'CiJob'
- connection_type_class(Types::CountableConnectionType)
+ connection_type_class(Types::LimitedCountableConnectionType)
expose_permissions Types::PermissionTypes::Ci::Job
diff --git a/app/graphql/types/limited_countable_connection_type.rb b/app/graphql/types/limited_countable_connection_type.rb
new file mode 100644
index 00000000000..f0698222ea3
--- /dev/null
+++ b/app/graphql/types/limited_countable_connection_type.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class LimitedCountableConnectionType < GraphQL::Types::Relay::BaseConnection
+ COUNT_LIMIT = 1000
+ COUNT_DESCRIPTION = "Limited count of collection. Returns limit + 1 for counts greater than the limit."
+
+ field :count, GraphQL::Types::Int, null: false, description: COUNT_DESCRIPTION do
+ argument :limit, GraphQL::Types::Int,
+ required: false, default_value: COUNT_LIMIT,
+ validates: { numericality: { greater_than: 0, less_than_or_equal_to: COUNT_LIMIT } },
+ description: "Limit value to be applied to the count query. Default is 1000."
+ end
+
+ def count(limit:)
+ relation = object.items
+
+ if relation.respond_to?(:page)
+ relation.page.total_count_with_limit(:all, limit: limit)
+ else
+ [relation.size, limit.next].min
+ end
+ end
+ end
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index fc4ff02934d..3bce26be756 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -203,6 +203,10 @@ class ProjectPolicy < BasePolicy
Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?('project')
end
+ condition :registry_enabled do
+ Gitlab.config.registry.enabled
+ end
+
# `:read_project` may be prevented in EE, but `:read_project_for_iids` should
# not.
rule { guest | admin }.enable :read_project_for_iids
@@ -760,6 +764,10 @@ class ProjectPolicy < BasePolicy
enable :import_project_members_from_another_project
end
+ rule { registry_enabled & can?(:admin_container_image) }.policy do
+ enable :view_package_registry_project_settings
+ end
+
private
def user_is_user?
diff --git a/app/services/two_factor/destroy_service.rb b/app/services/two_factor/destroy_service.rb
index b8bbe215d6e..859012c2153 100644
--- a/app/services/two_factor/destroy_service.rb
+++ b/app/services/two_factor/destroy_service.rb
@@ -8,7 +8,7 @@ module TwoFactor
result = disable_two_factor
- notification_service.disabled_two_factor(user) if result[:status] == :success
+ notify_on_success(user) if result[:status] == :success
result
end
@@ -20,5 +20,11 @@ module TwoFactor
user.disable_two_factor!
end
end
+
+ def notify_on_success(user)
+ notification_service.disabled_two_factor(user)
+ end
end
end
+
+TwoFactor::DestroyService.prepend_mod_with('TwoFactor::DestroyService')
diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml
index b6b762c64e8..46924393a27 100644
--- a/app/views/admin/broadcast_messages/index.html.haml
+++ b/app/views/admin/broadcast_messages/index.html.haml
@@ -43,7 +43,7 @@
= message.target_path
%td
= message.broadcast_type.capitalize
- %td.gl-white-space-nowrap
+ %td.gl-white-space-nowrap<
= link_to sprite_icon('pencil', css_class: 'gl-icon'), edit_admin_broadcast_message_path(message), title: _('Edit'), class: 'btn btn-icon gl-button'
- = link_to sprite_icon('remove', css_class: 'gl-icon'), admin_broadcast_message_path(message), method: :delete, remote: true, title: _('Remove'), class: 'js-remove-tr btn btn-icon gl-button btn-danger ml-2'
+ = link_to sprite_icon('remove', css_class: 'gl-icon'), admin_broadcast_message_path(message), method: :delete, remote: true, title: _('Remove'), class: 'js-remove-tr btn btn-icon gl-button btn-danger gl-ml-3'
= paginate @broadcast_messages, theme: 'gitlab'
diff --git a/config/feature_flags/development/omit_epic_subscribed.yml b/config/feature_flags/development/omit_epic_subscribed.yml
deleted file mode 100644
index 885636d6626..00000000000
--- a/config/feature_flags/development/omit_epic_subscribed.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: omit_epic_subscribed
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86016
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360663
-milestone: '15.0'
-type: development
-group: group::product planning
-default_enabled: true
diff --git a/doc/administration/audit_event_streaming.md b/doc/administration/audit_event_streaming.md
index c7b2406174d..ad235ead992 100644
--- a/doc/administration/audit_event_streaming.md
+++ b/doc/administration/audit_event_streaming.md
@@ -166,6 +166,18 @@ mutation {
}
```
+### Delete with the API
+
+Group owners can remove a HTTP header using the GraphQL `auditEventsStreamingHeadersDestroy` mutation.
+
+```graphql
+mutation {
+ auditEventsStreamingHeadersDestroy(input: { headerId: "gid://gitlab/AuditEvents::ExternalAuditEventDestination/24601" }) {
+ errors
+ }
+}
+```
+
The header is created if the returned `errors` object is empty.
## Verify event authenticity
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index 442f743f2c7..a329adbed22 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -234,6 +234,7 @@ The following user actions are recorded:
- Administrator added or removed ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/323905) in GitLab 14.1)
- Removed SSH key ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220127) in GitLab 14.1)
- Added or removed GPG key ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220127) in GitLab 14.1)
+- A user's two-factor authentication was disabled ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/238177) in GitLab 15.1)
Instance events can also be accessed via the [Instance Audit Events API](../api/audit_events.md#instance-audit-events).
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 750006950fd..9d5b9a8971f 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -738,6 +738,24 @@ Input type: `AuditEventsStreamingHeadersCreateInput`
| <a id="mutationauditeventsstreamingheaderscreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationauditeventsstreamingheaderscreateheader"></a>`header` | [`AuditEventStreamingHeader`](#auditeventstreamingheader) | Created header. |
+### `Mutation.auditEventsStreamingHeadersDestroy`
+
+Input type: `AuditEventsStreamingHeadersDestroyInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationauditeventsstreamingheadersdestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationauditeventsstreamingheadersdestroyheaderid"></a>`headerId` | [`AuditEventsStreamingHeaderID!`](#auditeventsstreamingheaderid) | Header to delete. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationauditeventsstreamingheadersdestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationauditeventsstreamingheadersdestroyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+
### `Mutation.awardEmojiAdd`
Input type: `AwardEmojiAddInput`
@@ -6033,11 +6051,24 @@ The connection type for [`CiJob`](#cijob).
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="cijobconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
| <a id="cijobconnectionedges"></a>`edges` | [`[CiJobEdge]`](#cijobedge) | A list of edges. |
| <a id="cijobconnectionnodes"></a>`nodes` | [`[CiJob]`](#cijob) | A list of nodes. |
| <a id="cijobconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+##### Fields with arguments
+
+###### `CiJobConnection.count`
+
+Limited count of collection. Returns limit + 1 for counts greater than the limit.
+
+Returns [`Int!`](#int).
+
+####### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cijobconnectioncountlimit"></a>`limit` | [`Int`](#int) | Limit value to be applied to the count query. Default is 1000. |
+
#### `CiJobEdge`
The edge type for [`CiJob`](#cijob).
@@ -20079,6 +20110,12 @@ A `AuditEventsExternalAuditEventDestinationID` is a global ID. It is encoded as
An example `AuditEventsExternalAuditEventDestinationID` is: `"gid://gitlab/AuditEvents::ExternalAuditEventDestination/1"`.
+### `AuditEventsStreamingHeaderID`
+
+A `AuditEventsStreamingHeaderID` is a global ID. It is encoded as a string.
+
+An example `AuditEventsStreamingHeaderID` is: `"gid://gitlab/AuditEvents::Streaming::Header/1"`.
+
### `AwardableID`
A `AwardableID` is a global ID. It is encoded as a string.
diff --git a/doc/ci/cloud_deployment/ecs/deploy_to_aws_ecs.md b/doc/ci/cloud_deployment/ecs/deploy_to_aws_ecs.md
index 9837722046b..9af5218e058 100644
--- a/doc/ci/cloud_deployment/ecs/deploy_to_aws_ecs.md
+++ b/doc/ci/cloud_deployment/ecs/deploy_to_aws_ecs.md
@@ -242,6 +242,10 @@ Change a file in the project and see if it's reflected in the demo application o
Congratulations! You successfully set up continuous deployment to ECS.
+NOTE:
+ECS deploy jobs wait for the rollout to complete before exiting. To disable this behavior,
+set `CI_AWS_ECS_WAIT_FOR_ROLLOUT_COMPLETE_DISABLED` to a non-empty value.
+
## Further reading
- If you're interested in more of the continuous deployments to clouds, see [cloud deployments](../index.md).
diff --git a/doc/ci/cloud_deployment/index.md b/doc/ci/cloud_deployment/index.md
index dc9bd07f713..c5be2328264 100644
--- a/doc/ci/cloud_deployment/index.md
+++ b/doc/ci/cloud_deployment/index.md
@@ -124,6 +124,10 @@ Finally, your AWS ECS service is updated with the new revision of the
task definition, making the cluster pull the newest version of your
application.
+NOTE:
+ECS deploy jobs wait for the rollout to complete before exiting. To disable this behavior,
+set `CI_AWS_ECS_WAIT_FOR_ROLLOUT_COMPLETE_DISABLED` to a non-empty value.
+
WARNING:
The [`AWS/Deploy-ECS.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/AWS/Deploy-ECS.gitlab-ci.yml)
template includes two templates: [`Jobs/Build.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml)
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index 534450c1871..9d08c5898ac 100644
--- a/doc/user/admin_area/settings/sign_up_restrictions.md
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -118,6 +118,25 @@ create or update pipelines until their email address is confirmed.
You can [change](../../../security/password_length_limits.md#modify-minimum-password-length-using-gitlab-ui)
the minimum number of characters a user must have in their password using the GitLab UI.
+### Password complexity requirements **(PREMIUM SELF)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354965) in GitLab 15.1.
+
+By default, the only requirement for user passwords is [minimum password length](#minimum-password-length-limit).
+You can add additional complexity requirements. Changes to password complexity requirements apply to new passwords:
+
+- For new users that sign up.
+- For existing users that reset their password.
+
+Existing passwords are unaffected. To change password complexity requirements:
+
+1. On the top bar, select **Menu > Admin**.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Sign-up restrictions**.
+1. Under **Minimum password length (number of characters)**, select additional password complexity requirements. You can require numbers, uppercase letters, lowercase letters,
+ and symbols.
+1. Select **Save changes**.
+
## Allow or deny sign ups using specific email domains
You can specify an inclusive or exclusive list of email domains which can be used for user sign up.
diff --git a/lib/sidebars/projects/menus/settings_menu.rb b/lib/sidebars/projects/menus/settings_menu.rb
index d2d5b5881ca..85931e63ebc 100644
--- a/lib/sidebars/projects/menus/settings_menu.rb
+++ b/lib/sidebars/projects/menus/settings_menu.rb
@@ -104,8 +104,7 @@ module Sidebars
end
def packages_and_registries_menu_item
- if !Gitlab.config.registry.enabled ||
- !can?(context.current_user, :destroy_container_image, context.project)
+ unless can?(context.current_user, :view_package_registry_project_settings, context.project)
return ::Sidebars::NilMenuItem.new(item_id: :packages_and_registries)
end
diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js
index c73b433c0bb..0552fe5050e 100644
--- a/spec/frontend/work_items/components/work_item_assignees_spec.js
+++ b/spec/frontend/work_items/components/work_item_assignees_spec.js
@@ -60,32 +60,11 @@ describe('WorkItemAssignees component', () => {
expect(findAssigneeLinks().at(0).attributes('data-user-id')).toBe('1');
});
- describe('when there are no assignees', () => {
- beforeEach(() => {
- createComponent({ assignees: [] });
- });
-
- it('should render empty state placeholder', () => {
- expect(findEmptyState().exists()).toBe(true);
- });
-
- it('should hide empty state placeholder on focusing token selector', async () => {
- findTokenSelector().vm.$emit('focus');
- await nextTick();
-
- expect(findEmptyState().exists()).toBe(false);
- });
- });
-
describe('when there are assignees', () => {
beforeEach(() => {
createComponent();
});
- it('should not render empty state placeholder', () => {
- expect(findEmptyState().exists()).toBe(false);
- });
-
it('should focus token selector on token removal', async () => {
findTokenSelector().vm.$emit('token-remove', mockAssignees[0].id);
await nextTick();
diff --git a/spec/graphql/types/limited_countable_connection_type_spec.rb b/spec/graphql/types/limited_countable_connection_type_spec.rb
new file mode 100644
index 00000000000..30af26cdb83
--- /dev/null
+++ b/spec/graphql/types/limited_countable_connection_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::LimitedCountableConnectionType do
+ it 'has the expected fields' do
+ expected_fields = %i[count page_info]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 3050cf6000e..7b3d1abadc1 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -1369,6 +1369,68 @@ RSpec.describe ProjectPolicy do
end
end
+ describe 'view_package_registry_project_settings' do
+ context 'with registry enabled' do
+ before do
+ stub_config(registry: { enabled: true })
+ end
+
+ context 'with an admin user' do
+ let(:current_user) { admin }
+
+ context 'when admin mode enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed(:view_package_registry_project_settings) }
+ end
+
+ context 'when admin mode disabled' do
+ it { is_expected.to be_disallowed(:view_package_registry_project_settings) }
+ end
+ end
+
+ %i[owner maintainer].each do |role|
+ context "with #{role}" do
+ let(:current_user) { public_send(role) }
+
+ it { is_expected.to be_allowed(:view_package_registry_project_settings) }
+ end
+ end
+
+ %i[developer reporter guest non_member anonymous].each do |role|
+ context "with #{role}" do
+ let(:current_user) { public_send(role) }
+
+ it { is_expected.to be_disallowed(:view_package_registry_project_settings) }
+ end
+ end
+ end
+
+ context 'with registry disabled' do
+ before do
+ stub_config(registry: { enabled: false })
+ end
+
+ context 'with admin user' do
+ let(:current_user) { admin }
+
+ context 'when admin mode enabled', :enable_admin_mode do
+ it { is_expected.to be_disallowed(:view_package_registry_project_settings) }
+ end
+
+ context 'when admin mode disabled' do
+ it { is_expected.to be_disallowed(:view_package_registry_project_settings) }
+ end
+ end
+
+ %i[owner maintainer developer reporter guest non_member anonymous].each do |role|
+ context "with #{role}" do
+ let(:current_user) { public_send(role) }
+
+ it { is_expected.to be_disallowed(:view_package_registry_project_settings) }
+ end
+ end
+ end
+ end
+
describe 'read_feature_flag' do
subject { described_class.new(current_user, project) }
diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb
index 2d1bb45390b..d1737fc22ae 100644
--- a/spec/requests/api/graphql/ci/jobs_spec.rb
+++ b/spec/requests/api/graphql/ci/jobs_spec.rb
@@ -258,4 +258,81 @@ RSpec.describe 'Query.project.pipeline' do
end
end
end
+
+ describe '.jobs.count' do
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:successful_job) { create(:ci_build, :success, pipeline: pipeline) }
+ let_it_be(:pending_job) { create(:ci_build, :pending, pipeline: pipeline) }
+ let_it_be(:failed_job) { create(:ci_build, :failed, pipeline: pipeline) }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ pipeline(iid: "#{pipeline.iid}") {
+ jobs {
+ count
+ }
+ }
+ }
+ }
+ )
+ end
+
+ before do
+ post_graphql(query, current_user: user)
+ end
+
+ it 'returns the number of jobs' do
+ expect(graphql_data_at(:project, :pipeline, :jobs, :count)).to eq(3)
+ end
+
+ context 'with limit value' do
+ let(:limit) { 1 }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ pipeline(iid: "#{pipeline.iid}") {
+ jobs {
+ count(limit: #{limit})
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns a limited number of jobs' do
+ expect(graphql_data_at(:project, :pipeline, :jobs, :count)).to eq(2)
+ end
+
+ context 'with invalid value' do
+ let(:limit) { 1500 }
+
+ it 'returns a validation error' do
+ expect(graphql_errors).to include(a_hash_including('message' => 'limit must be less than or equal to 1000'))
+ end
+ end
+ end
+
+ context 'with jobs filter' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ jobs(statuses: FAILED) {
+ count
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns the number of failed jobs' do
+ expect(graphql_data_at(:project, :jobs, :count)).to eq(1)
+ end
+ end
+ end
end