diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-24 21:11:26 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-24 21:11:26 +0000 |
commit | 74f16c03423e42428ce10ec34d1a0a20700e79a0 (patch) | |
tree | 75eba7c1606a6d372fbb1e0bb9daf9bbe5b3283e | |
parent | e20fed01c86f47ffba316483f312a36330fd084d (diff) | |
download | gitlab-ce-74f16c03423e42428ce10ec34d1a0a20700e79a0.tar.gz |
Add latest changes from gitlab-org/gitlab@master
24 files changed, 576 insertions, 360 deletions
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue index 99eb167172b..46d546c2ee4 100644 --- a/app/assets/javascripts/repository/components/table/index.vue +++ b/app/assets/javascripts/repository/components/table/index.vue @@ -1,6 +1,5 @@ <script> import { GlSkeletonLoader, GlButton } from '@gitlab/ui'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { sprintf, __ } from '~/locale'; import { joinPaths } from '~/lib/utils/url_utility'; import getRefMixin from '../../mixins/get_ref'; @@ -17,7 +16,7 @@ export default { ParentRow, GlButton, }, - mixins: [getRefMixin, glFeatureFlagMixin()], + mixins: [getRefMixin], apollo: { projectPath: { query: projectPathQuery, @@ -93,9 +92,6 @@ export default { }, generateRowNumber(path, id, index) { const key = `${path}-${id}-${index}`; - if (!this.glFeatures.lazyLoadCommits) { - return 0; - } if (!this.rowNumbers[key] && this.rowNumbers[key] !== 0) { this.$options.totalRowsLoaded += 1; @@ -105,10 +101,6 @@ export default { return this.rowNumbers[key]; }, getCommit(fileName) { - if (!this.glFeatures.lazyLoadCommits) { - return {}; - } - return this.commits.find( (commitEntry) => commitEntry.filePath === joinPaths(this.path, fileName), ); diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index e10ec07abc4..27ac11f3c58 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -19,7 +19,6 @@ import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import blobInfoQuery from 'shared_queries/repository/blob_info.query.graphql'; import getRefMixin from '../../mixins/get_ref'; -import commitQuery from '../../queries/commit.query.graphql'; export default { components: { @@ -37,22 +36,6 @@ export default { GlHoverLoad: GlHoverLoadDirective, SafeHtml, }, - apollo: { - commit: { - query: commitQuery, - variables() { - return { - fileName: this.name, - path: this.currentPath, - projectPath: this.projectPath, - maxOffset: this.totalEntries, - }; - }, - skip() { - return this.glFeatures.lazyLoadCommits; - }, - }, - }, mixins: [getRefMixin, glFeatureFlagMixin()], props: { commitInfo: { @@ -125,14 +108,13 @@ export default { }, data() { return { - commit: null, hasRowAppeared: false, delayedRowAppear: null, }; }, computed: { commitData() { - return this.glFeatures.lazyLoadCommits ? this.commitInfo : this.commit; + return this.commitInfo; }, routerLinkTo() { const blobRouteConfig = { path: `/-/blob/${this.escapedRef}/${escapeFileUrl(this.path)}` }; @@ -200,12 +182,10 @@ export default { return; } - if (this.glFeatures.lazyLoadCommits) { - this.delayedRowAppear = setTimeout( - () => this.$emit('row-appear', this.rowNumber), - ROW_APPEAR_DELAY, - ); - } + this.delayedRowAppear = setTimeout( + () => this.$emit('row-appear', this.rowNumber), + ROW_APPEAR_DELAY, + ); }, rowDisappeared() { clearTimeout(this.delayedRowAppear); diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue index 8a45a351c35..4a8f83458f4 100644 --- a/app/assets/javascripts/repository/components/tree_content.vue +++ b/app/assets/javascripts/repository/components/tree_content.vue @@ -157,7 +157,7 @@ export default { .find(({ hasNextPage }) => hasNextPage); }, handleRowAppear(rowNumber) { - if (!this.glFeatures.lazyLoadCommits || isRequested(rowNumber)) { + if (isRequested(rowNumber)) { return; } diff --git a/app/assets/javascripts/repository/queries/commit.query.graphql b/app/assets/javascripts/repository/queries/commit.query.graphql deleted file mode 100644 index 1a01462bd19..00000000000 --- a/app/assets/javascripts/repository/queries/commit.query.graphql +++ /dev/null @@ -1,7 +0,0 @@ -#import "ee_else_ce/repository/queries/commit.fragment.graphql" - -query getCommit($fileName: String!, $path: String!, $maxOffset: Number!) { - commit(path: $path, fileName: $fileName, maxOffset: $maxOffset) @client { - ...TreeEntryCommit - } -} diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index ce1b9af648f..737a6290431 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -17,7 +17,6 @@ class Projects::TreeController < Projects::ApplicationController before_action :authorize_edit_tree!, only: [:create_dir] before_action do - push_frontend_feature_flag(:lazy_load_commits, @project) push_frontend_feature_flag(:highlight_js, @project) push_frontend_feature_flag(:file_line_blame, @project) push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index c705122818a..1a2216014bc 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -37,7 +37,6 @@ class ProjectsController < Projects::ApplicationController before_action :check_export_rate_limit!, only: [:export, :download_export, :generate_new_export] before_action do - push_frontend_feature_flag(:lazy_load_commits, @project) push_frontend_feature_flag(:highlight_js, @project) push_frontend_feature_flag(:file_line_blame, @project) push_frontend_feature_flag(:increase_page_size_exponentially, @project) diff --git a/config/feature_flags/development/lazy_load_commits.yml b/config/feature_flags/development/lazy_load_commits.yml deleted file mode 100644 index 6140b88c3c2..00000000000 --- a/config/feature_flags/development/lazy_load_commits.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: lazy_load_commits -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71633 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/342497 -milestone: '14.4' -type: development -group: group::source code -default_enabled: true diff --git a/config/initializers_before_autoloader/000_inflections.rb b/config/initializers_before_autoloader/000_inflections.rb index 795b0f20128..2bf98a38544 100644 --- a/config/initializers_before_autoloader/000_inflections.rb +++ b/config/initializers_before_autoloader/000_inflections.rb @@ -19,6 +19,7 @@ ActiveSupport::Inflector.inflections do |inflect| container_repository_registry dependency_proxy_blob_registry design_registry + dependency_proxy_manifest_registry event_log file_registry group_view diff --git a/db/docs/dependency_proxy_manifest_states.yml b/db/docs/dependency_proxy_manifest_states.yml new file mode 100644 index 00000000000..28169dded19 --- /dev/null +++ b/db/docs/dependency_proxy_manifest_states.yml @@ -0,0 +1,9 @@ +--- +table_name: dependency_proxy_manifest_states +classes: + - Geo::DependencyProxyManifestState +feature_categories: + - geo_replication +description: Separate table for dependency proxy manifest verification states +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102908 +milestone: '15.6' diff --git a/db/migrate/20221102195642_create_dependency_proxy_manifest_states.rb b/db/migrate/20221102195642_create_dependency_proxy_manifest_states.rb new file mode 100644 index 00000000000..e0da92c6c94 --- /dev/null +++ b/db/migrate/20221102195642_create_dependency_proxy_manifest_states.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class CreateDependencyProxyManifestStates < Gitlab::Database::Migration[2.0] + DEPENDENCY_PROXY_MANIFEST_INDEX_NAME = "index_manifest_states_on_dependency_proxy_manifest_id" + VERIFICATION_STATE_INDEX_NAME = "index_manifest_states_on_verification_state" + PENDING_VERIFICATION_INDEX_NAME = "index_manifest_states_pending_verification" + FAILED_VERIFICATION_INDEX_NAME = "index_manifest_states_failed_verification" + NEEDS_VERIFICATION_INDEX_NAME = "index_manifest_states_needs_verification" + + enable_lock_retries! + + def up + create_table :dependency_proxy_manifest_states, id: false do |t| + t.datetime_with_timezone :verification_started_at + t.datetime_with_timezone :verification_retry_at + t.datetime_with_timezone :verified_at + t.references :dependency_proxy_manifest, + primary_key: true, + index: { name: DEPENDENCY_PROXY_MANIFEST_INDEX_NAME }, + default: nil, + foreign_key: { on_delete: :cascade } + t.integer :verification_state, default: 0, limit: 2, null: false + t.integer :verification_retry_count, limit: 2, default: 0, null: false + t.binary :verification_checksum, using: 'verification_checksum::bytea' + t.text :verification_failure, limit: 255 + + t.index :verification_state, name: VERIFICATION_STATE_INDEX_NAME + t.index :verified_at, + where: "(verification_state = 0)", + order: { verified_at: 'ASC NULLS FIRST' }, + name: PENDING_VERIFICATION_INDEX_NAME + t.index :verification_retry_at, + where: "(verification_state = 3)", + order: { verification_retry_at: 'ASC NULLS FIRST' }, + name: FAILED_VERIFICATION_INDEX_NAME + t.index :verification_state, + where: "(verification_state = 0 OR verification_state = 3)", + name: NEEDS_VERIFICATION_INDEX_NAME + end + end + + def down + drop_table :dependency_proxy_manifest_states + end +end diff --git a/db/schema_migrations/20221102195642 b/db/schema_migrations/20221102195642 new file mode 100644 index 00000000000..746c62e7f7e --- /dev/null +++ b/db/schema_migrations/20221102195642 @@ -0,0 +1 @@ +66a97a441e7be47db9d4dfd49bfe5b600cc2977e581ade98daa923778a142b85
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index c7c8d98589a..82ad720f1c9 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14684,6 +14684,18 @@ CREATE TABLE dependency_proxy_image_ttl_group_policies ( enabled boolean DEFAULT false NOT NULL ); +CREATE TABLE dependency_proxy_manifest_states ( + verification_started_at timestamp with time zone, + verification_retry_at timestamp with time zone, + verified_at timestamp with time zone, + dependency_proxy_manifest_id bigint NOT NULL, + verification_state smallint DEFAULT 0 NOT NULL, + verification_retry_count smallint DEFAULT 0 NOT NULL, + verification_checksum bytea, + verification_failure text, + CONSTRAINT check_fdd5d9791b CHECK ((char_length(verification_failure) <= 255)) +); + CREATE TABLE dependency_proxy_manifests ( id bigint NOT NULL, created_at timestamp with time zone NOT NULL, @@ -25704,6 +25716,9 @@ ALTER TABLE ONLY dependency_proxy_group_settings ALTER TABLE ONLY dependency_proxy_image_ttl_group_policies ADD CONSTRAINT dependency_proxy_image_ttl_group_policies_pkey PRIMARY KEY (group_id); +ALTER TABLE ONLY dependency_proxy_manifest_states + ADD CONSTRAINT dependency_proxy_manifest_states_pkey PRIMARY KEY (dependency_proxy_manifest_id); + ALTER TABLE ONLY dependency_proxy_manifests ADD CONSTRAINT dependency_proxy_manifests_pkey PRIMARY KEY (id); @@ -29695,6 +29710,16 @@ CREATE INDEX index_lists_on_user_id ON lists USING btree (user_id); CREATE INDEX index_loose_foreign_keys_deleted_records_for_partitioned_query ON ONLY loose_foreign_keys_deleted_records USING btree (partition, fully_qualified_table_name, consume_after, id) WHERE (status = 1); +CREATE INDEX index_manifest_states_failed_verification ON dependency_proxy_manifest_states USING btree (verification_retry_at NULLS FIRST) WHERE (verification_state = 3); + +CREATE INDEX index_manifest_states_needs_verification ON dependency_proxy_manifest_states USING btree (verification_state) WHERE ((verification_state = 0) OR (verification_state = 3)); + +CREATE INDEX index_manifest_states_on_dependency_proxy_manifest_id ON dependency_proxy_manifest_states USING btree (dependency_proxy_manifest_id); + +CREATE INDEX index_manifest_states_on_verification_state ON dependency_proxy_manifest_states USING btree (verification_state); + +CREATE INDEX index_manifest_states_pending_verification ON dependency_proxy_manifest_states USING btree (verified_at NULLS FIRST) WHERE (verification_state = 0); + CREATE INDEX index_member_roles_on_namespace_id ON member_roles USING btree (namespace_id); CREATE INDEX index_member_tasks_on_member_id ON member_tasks USING btree (member_id); @@ -34617,6 +34642,9 @@ ALTER TABLE ONLY application_settings ALTER TABLE ONLY clusters_kubernetes_namespaces ADD CONSTRAINT fk_rails_7e7688ecaf FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE; +ALTER TABLE ONLY dependency_proxy_manifest_states + ADD CONSTRAINT fk_rails_806cf07a3c FOREIGN KEY (dependency_proxy_manifest_id) REFERENCES dependency_proxy_manifests(id) ON DELETE CASCADE; + ALTER TABLE ONLY ci_job_artifact_states ADD CONSTRAINT fk_rails_80a9cba3b2 FOREIGN KEY (job_artifact_id) REFERENCES ci_job_artifacts(id) ON DELETE CASCADE; diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index eac5e643b2e..7c2d341b779 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -332,6 +332,16 @@ configuration option in `gitlab.yml`. These metrics are served from the | `geo_dependency_proxy_blob_verification_total` | Gauge | 15.6 | Number of dependency proxy blobs verifications tried on secondary | | | `geo_dependency_proxy_blob_verified` | Gauge | 15.6 | Number of dependency proxy blobs verified on secondary | | | `geo_dependency_proxy_blob_verification_failed` | Gauge | 15.6 | Number of dependency proxy blobs verifications failed on secondary | | +| `geo_dependency_proxy_manifests` | Gauge | 15.6 | Number of dependency proxy manifests on primary | `url` | +| `geo_dependency_proxy_manifests_checksum_total` | Gauge | 15.6 | Number of dependency proxy manifests tried to checksum on primary | `url` | +| `geo_dependency_proxy_manifests_checksummed` | Gauge | 15.6 | Number of dependency proxy manifests successfully checksummed on primary | `url` | +| `geo_dependency_proxy_manifests_checksum_failed` | Gauge | 15.6 | Number of dependency proxy manifests failed to calculate the checksum on primary | `url` | +| `geo_dependency_proxy_manifests_synced` | Gauge | 15.6 | Number of syncable dependency proxy manifests synced on secondary | `url` | +| `geo_dependency_proxy_manifests_failed` | Gauge | 15.6 | Number of syncable dependency proxy manifests failed to sync on secondary | `url` | +| `geo_dependency_proxy_manifests_registry` | Gauge | 15.6 | Number of dependency proxy manifests in the registry | `url` | +| `geo_dependency_proxy_manifests_verification_total` | Gauge | 15.6 | Number of dependency proxy manifests verifications tried on secondary | `url` | +| `geo_dependency_proxy_manifests_verified` | Gauge | 15.6 | Number of dependency proxy manifests verified on secondary | `url` | +| `geo_dependency_proxy_manifests_verification_failed` | Gauge | 15.6 | Number of dependency proxy manifests verifications failed on secondary | `url` | ## Database load balancing metrics **(PREMIUM SELF)** diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md index 00380e1624b..6b62e82f54d 100644 --- a/doc/api/geo_nodes.md +++ b/doc/api/geo_nodes.md @@ -523,6 +523,18 @@ Example response: "container_repositories_registry_count": 5, "container_repositories_synced_in_percentage": "100.00%", "container_repositories_synced_missing_on_primary_count": 0, + "dependency_proxy_manifests_count": 5, + "dependency_proxy_manifests_checksum_total_count": 5, + "dependency_proxy_manifests_checksummed_count": 5, + "dependency_proxy_manifests_checksum_failed_count": 5, + "dependency_proxy_manifests_synced_count": 5, + "dependency_proxy_manifests_failed_count": 0, + "dependency_proxy_manifests_registry_count": 5, + "dependency_proxy_manifests_verification_total_count": 5, + "dependency_proxy_manifests_verified_count": 5, + "dependency_proxy_manifests_verification_failed_count": 5, + "dependency_proxy_manifests_synced_in_percentage": "100.00%", + "dependency_proxy_manifests_verified_in_percentage": "100.00%" }, { "geo_node_id": 2, @@ -707,6 +719,18 @@ Example response: "container_repositories_registry_count": 5, "container_repositories_synced_in_percentage": "100.00%", "container_repositories_synced_missing_on_primary_count": 0, + "dependency_proxy_manifests_count": 5, + "dependency_proxy_manifests_checksum_total_count": 5, + "dependency_proxy_manifests_checksummed_count": 5, + "dependency_proxy_manifests_checksum_failed_count": 5, + "dependency_proxy_manifests_synced_count": 5, + "dependency_proxy_manifests_failed_count": 0, + "dependency_proxy_manifests_registry_count": 5, + "dependency_proxy_manifests_verification_total_count": 5, + "dependency_proxy_manifests_verified_count": 5, + "dependency_proxy_manifests_verification_failed_count": 5, + "dependency_proxy_manifests_synced_in_percentage": "100.00%", + "dependency_proxy_manifests_verified_in_percentage": "100.00%" } ] ``` @@ -901,6 +925,18 @@ Example response: "container_repositories_registry_count": 5, "container_repositories_synced_in_percentage": "100.00%", "container_repositories_synced_missing_on_primary_count": 0, + "dependency_proxy_manifests_count": 5, + "dependency_proxy_manifests_checksum_total_count": 5, + "dependency_proxy_manifests_checksummed_count": 5, + "dependency_proxy_manifests_checksum_failed_count": 5, + "dependency_proxy_manifests_synced_count": 5, + "dependency_proxy_manifests_failed_count": 0, + "dependency_proxy_manifests_registry_count": 5, + "dependency_proxy_manifests_verification_total_count": 5, + "dependency_proxy_manifests_verified_count": 5, + "dependency_proxy_manifests_verification_failed_count": 5, + "dependency_proxy_manifests_synced_in_percentage": "100.00%", + "dependency_proxy_manifests_verified_in_percentage": "100.00%" } ``` diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 3d22277e0f2..647246705ed 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -7367,6 +7367,29 @@ The edge type for [`DependencyProxyManifest`](#dependencyproxymanifest). | <a id="dependencyproxymanifestedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | | <a id="dependencyproxymanifestedgenode"></a>`node` | [`DependencyProxyManifest`](#dependencyproxymanifest) | The item at the end of the edge. | +#### `DependencyProxyManifestRegistryConnection` + +The connection type for [`DependencyProxyManifestRegistry`](#dependencyproxymanifestregistry). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="dependencyproxymanifestregistryconnectionedges"></a>`edges` | [`[DependencyProxyManifestRegistryEdge]`](#dependencyproxymanifestregistryedge) | A list of edges. | +| <a id="dependencyproxymanifestregistryconnectionnodes"></a>`nodes` | [`[DependencyProxyManifestRegistry]`](#dependencyproxymanifestregistry) | A list of nodes. | +| <a id="dependencyproxymanifestregistryconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `DependencyProxyManifestRegistryEdge` + +The edge type for [`DependencyProxyManifestRegistry`](#dependencyproxymanifestregistry). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="dependencyproxymanifestregistryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | +| <a id="dependencyproxymanifestregistryedgenode"></a>`node` | [`DependencyProxyManifestRegistry`](#dependencyproxymanifestregistry) | The item at the end of the edge. | + #### `DeploymentConnection` The connection type for [`Deployment`](#deployment). @@ -11804,6 +11827,25 @@ Dependency proxy manifest. | <a id="dependencyproxymanifeststatus"></a>`status` | [`DependencyProxyManifestStatus!`](#dependencyproxymanifeststatus) | Status of the manifest (default, pending_destruction, processing, error). | | <a id="dependencyproxymanifestupdatedat"></a>`updatedAt` | [`Time!`](#time) | Date of most recent update. | +### `DependencyProxyManifestRegistry` + +Represents the Geo replication and verification state of a dependency_proxy_manifest. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="dependencyproxymanifestregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the DependencyProxyManifestRegistry was created. | +| <a id="dependencyproxymanifestregistrydependencyproxymanifestid"></a>`dependencyProxyManifestId` | [`ID!`](#id) | ID of the Dependency Proxy Manifest. | +| <a id="dependencyproxymanifestregistryid"></a>`id` | [`ID!`](#id) | ID of the DependencyProxyManifestRegistry. | +| <a id="dependencyproxymanifestregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the DependencyProxyManifestRegistry. | +| <a id="dependencyproxymanifestregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the DependencyProxyManifestRegistry. | +| <a id="dependencyproxymanifestregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the DependencyProxyManifestRegistry is resynced. | +| <a id="dependencyproxymanifestregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the DependencyProxyManifestRegistry. | +| <a id="dependencyproxymanifestregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the DependencyProxyManifestRegistry. | +| <a id="dependencyproxymanifestregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the DependencyProxyManifestRegistry is reverified. | +| <a id="dependencyproxymanifestregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the DependencyProxyManifestRegistry. | + ### `DependencyProxySetting` Group-level Dependency Proxy settings. @@ -13001,6 +13043,28 @@ four standard [pagination arguments](#connection-pagination-arguments): | <a id="geonodedependencyproxyblobregistriesreplicationstate"></a>`replicationState` | [`ReplicationStateEnum`](#replicationstateenum) | Filters registries by their replication state. | | <a id="geonodedependencyproxyblobregistriesverificationstate"></a>`verificationState` | [`VerificationStateEnum`](#verificationstateenum) | Filters registries by their verification state. | +##### `GeoNode.dependencyProxyManifestRegistries` + +Find Dependency Proxy Manifest registries on this Geo node. Ignored if `geo_dependency_proxy_manifest_replication` feature flag is disabled. + +WARNING: +**Introduced** in 15.6. +This feature is in Alpha. It can be changed or removed at any time. + +Returns [`DependencyProxyManifestRegistryConnection`](#dependencyproxymanifestregistryconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="geonodedependencyproxymanifestregistriesids"></a>`ids` | [`[ID!]`](#id) | Filters registries by their ID. | +| <a id="geonodedependencyproxymanifestregistriesreplicationstate"></a>`replicationState` | [`ReplicationStateEnum`](#replicationstateenum) | Filters registries by their replication state. | +| <a id="geonodedependencyproxymanifestregistriesverificationstate"></a>`verificationState` | [`VerificationStateEnum`](#verificationstateenum) | Filters registries by their verification state. | + ##### `GeoNode.groupWikiRepositoryRegistries` Find group wiki repository registries on this Geo node. diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml index a5bab379f53..462f006fabd 100644 --- a/lib/gitlab/database/gitlab_schemas.yml +++ b/lib/gitlab/database/gitlab_schemas.yml @@ -174,6 +174,7 @@ dependency_proxy_blobs: :gitlab_main dependency_proxy_group_settings: :gitlab_main dependency_proxy_image_ttl_group_policies: :gitlab_main dependency_proxy_manifests: :gitlab_main +dependency_proxy_manifest_states: :gitlab_main deploy_keys_projects: :gitlab_main deployment_approvals: :gitlab_main deployment_clusters: :gitlab_main diff --git a/spec/factories/dependency_proxy.rb b/spec/factories/dependency_proxy.rb index 33356a701df..43cc923a4c5 100644 --- a/spec/factories/dependency_proxy.rb +++ b/spec/factories/dependency_proxy.rb @@ -23,14 +23,21 @@ FactoryBot.define do factory :dependency_proxy_manifest, class: 'DependencyProxy::Manifest' do group size { 1234 } - file { fixture_file_upload('spec/fixtures/dependency_proxy/manifest') } digest { 'sha256:d0710affa17fad5f466a70159cc458227bd25d4afb39514ef662ead3e6c99515' } sequence(:file_name) { |n| "alpine:latest#{n}.json" } content_type { 'application/vnd.docker.distribution.manifest.v2+json' } status { :default } + after(:build) do |manifest, _evaluator| + manifest.file = fixture_file_upload('spec/fixtures/dependency_proxy/manifest') + end + trait :pending_destruction do status { :pending_destruction } end + + trait :remote_store do + file_store { DependencyProxy::FileUploader::Store::REMOTE } + end end end diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js index 2180f78a8df..8b987551b33 100644 --- a/spec/frontend/repository/components/table/index_spec.js +++ b/spec/frontend/repository/components/table/index_spec.js @@ -82,9 +82,6 @@ function factory({ path, isLoading = false, hasMore = true, entries = {}, commit mocks: { $apollo, }, - provide: { - glFeatures: { lazyLoadCommits: true }, - }, }); } diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js index 64aa6d179a8..5d9138ab9cd 100644 --- a/spec/frontend/repository/components/table/row_spec.js +++ b/spec/frontend/repository/components/table/row_spec.js @@ -30,9 +30,6 @@ function factory(propsData = {}) { directives: { GlHoverLoad: createMockDirective(), }, - provide: { - glFeatures: { lazyLoadCommits: true }, - }, mocks: { $router, }, diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js index 352f4314232..6eea66f1a7d 100644 --- a/spec/frontend/repository/components/tree_content_spec.js +++ b/spec/frontend/repository/components/tree_content_spec.js @@ -31,7 +31,6 @@ function factory(path, data = () => ({})) { glFeatures: { increasePageSizeExponentially: true, paginatedTreeGraphqlQuery: true, - lazyLoadCommits: true, }, }, }); diff --git a/spec/models/factories_spec.rb b/spec/models/factories_spec.rb index 65b993cca7f..4915c0bd870 100644 --- a/spec/models/factories_spec.rb +++ b/spec/models/factories_spec.rb @@ -44,6 +44,8 @@ RSpec.describe 'factories', :saas do [:ci_pipeline_artifact, :remote_store], # EE [:dast_profile, :with_dast_site_validation], + [:dependency_proxy_manifest, :remote_store], + [:geo_dependency_proxy_manifest_state, any], [:ee_ci_build, :dependency_scanning_report], [:ee_ci_build, :license_scan_v1], [:ee_ci_job_artifact, :v1], diff --git a/spec/requests/api/graphql/issues_spec.rb b/spec/requests/api/graphql/issues_spec.rb index 8838ad78f72..8c8690a5a3e 100644 --- a/spec/requests/api/graphql/issues_spec.rb +++ b/spec/requests/api/graphql/issues_spec.rb @@ -13,27 +13,75 @@ RSpec.describe 'getting an issue list at root level' do let_it_be(:project_b) { create(:project, :repository, :private, group: group1) } let_it_be(:project_c) { create(:project, :repository, :public, group: group2) } let_it_be(:project_d) { create(:project, :repository, :private, group: group2) } - let_it_be(:early_milestone) { create(:milestone, project: project_d, due_date: 10.days.from_now) } - let_it_be(:late_milestone) { create(:milestone, project: project_c, due_date: 30.days.from_now) } + let_it_be(:milestone1) { create(:milestone, project: project_c, due_date: 10.days.from_now) } + let_it_be(:milestone2) { create(:milestone, project: project_d, due_date: 20.days.from_now) } + let_it_be(:milestone3) { create(:milestone, project: project_d, due_date: 30.days.from_now) } + let_it_be(:milestone4) { create(:milestone, project: project_a, due_date: 40.days.from_now) } let_it_be(:priority1) { create(:label, project: project_c, priority: 1) } let_it_be(:priority2) { create(:label, project: project_d, priority: 5) } let_it_be(:priority3) { create(:label, project: project_a, priority: 10) } + let_it_be(:priority4) { create(:label, project: project_d, priority: 15) } + + let_it_be(:issue_a) do + create( + :issue, + project: project_a, + labels: [priority3], + due_date: 1.day.ago, + milestone: milestone4, + relative_position: 1000 + ) + end + + let_it_be(:issue_b) do + create( + :issue, + :with_alert, + project: project_b, + discussion_locked: true, + due_date: 1.day.from_now, + relative_position: 3000 + ) + end - let_it_be(:issue_a) { create(:issue, project: project_a, labels: [priority3]) } - let_it_be(:issue_b) { create(:issue, :with_alert, project: project_b, discussion_locked: true) } let_it_be(:issue_c) do create( :issue, + :confidential, project: project_c, title: 'title matching issue plus', labels: [priority1], - milestone: late_milestone + milestone: milestone1, + due_date: 3.days.from_now, + relative_position: nil + ) + end + + let_it_be(:issue_d) do + create( + :issue, + :with_alert, + project: project_d, + discussion_locked: true, + labels: [priority2], + milestone: milestone3, + relative_position: 5000 ) end - let_it_be(:issue_d) { create(:issue, :with_alert, project: project_d, discussion_locked: true, labels: [priority2]) } - let_it_be(:issue_e) { create(:issue, project: project_d, milestone: early_milestone) } + let_it_be(:issue_e) do + create( + :issue, + :confidential, + project: project_d, + milestone: milestone2, + due_date: 3.days.ago, + relative_position: nil, + labels: [priority2, priority4] + ) + end + let(:issues) { [issue_a, issue_b, issue_c, issue_d, issue_e] } let(:issue_filter_params) { {} } let(:fields) do @@ -61,7 +109,9 @@ RSpec.describe 'getting an issue list at root level' do end it_behaves_like 'graphql issue list request spec' do - subject(:post_query) { post_graphql(query, current_user: current_user) } + let_it_be(:external_user) { create(:user) } + + let(:public_projects) { [project_a, project_c] } let(:current_user) { developer } let(:another_user) { reporter } @@ -77,12 +127,22 @@ RSpec.describe 'getting an issue list at root level' do let(:unlocked_discussion_issues) { [issue_a, issue_c, issue_e] } let(:search_title_term) { 'matching issue' } let(:title_search_issue) { issue_c } + let(:confidential_issues) { [issue_c, issue_e] } + let(:non_confidential_issues) { [issue_a, issue_b, issue_d] } + let(:public_non_confidential_issues) { [issue_a] } # sorting let(:data_path) { [:issues] } let(:expected_severity_sorted_asc) { [issue_c, issue_a, issue_b, issue_e, issue_d] } - let(:expected_priority_sorted_asc) { [issue_e, issue_c, issue_d, issue_a, issue_b] } - let(:expected_priority_sorted_desc) { [issue_c, issue_e, issue_a, issue_d, issue_b] } + let(:expected_priority_sorted_asc) { [issue_c, issue_e, issue_d, issue_a, issue_b] } + let(:expected_priority_sorted_desc) { [issue_a, issue_d, issue_e, issue_c, issue_b] } + let(:expected_due_date_sorted_desc) { [issue_c, issue_b, issue_a, issue_e, issue_d] } + let(:expected_due_date_sorted_asc) { [issue_e, issue_a, issue_b, issue_c, issue_d] } + let(:expected_relative_position_sorted_asc) { [issue_a, issue_b, issue_d, issue_c, issue_e] } + let(:expected_label_priority_sorted_asc) { [issue_c, issue_e, issue_d, issue_a, issue_b] } + let(:expected_label_priority_sorted_desc) { [issue_a, issue_e, issue_d, issue_c, issue_b] } + let(:expected_milestone_sorted_asc) { [issue_c, issue_e, issue_d, issue_a, issue_b] } + let(:expected_milestone_sorted_desc) { [issue_a, issue_d, issue_e, issue_c, issue_b] } before_all do issue_a.assignee_ids = developer.id @@ -105,6 +165,10 @@ RSpec.describe 'getting an issue list at root level' do "#{page_info} nodes { id }" ) end + + def post_query(request_user = current_user) + post_graphql(query, current_user: request_user) + end end def query(params = issue_filter_params) diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb index 14ca2e439bf..57f7a410d4b 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -9,17 +9,69 @@ RSpec.describe 'getting an issue list for a project' do let_it_be(:project) { create(:project, :repository, :public, group: group) } let_it_be(:current_user) { create(:user) } let_it_be(:another_user) { create(:user).tap { |u| group.add_reporter(u) } } - let_it_be(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) } - let_it_be(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) } + let_it_be(:milestone1) { create(:milestone, project: project, due_date: 10.days.from_now) } + let_it_be(:milestone2) { create(:milestone, project: project, due_date: 20.days.from_now) } + let_it_be(:milestone3) { create(:milestone, project: project, due_date: 30.days.from_now) } + let_it_be(:milestone4) { create(:milestone, project: project, due_date: 40.days.from_now) } let_it_be(:priority1) { create(:label, project: project, priority: 1) } let_it_be(:priority2) { create(:label, project: project, priority: 5) } let_it_be(:priority3) { create(:label, project: project, priority: 10) } - let_it_be(:issue_a, reload: true) { create(:issue, project: project, discussion_locked: true, labels: [priority3]) } - let_it_be(:issue_b, reload: true) { create(:issue, :with_alert, project: project, title: 'title matching issue i') } - let_it_be(:issue_c) { create(:issue, project: project, labels: [priority1], milestone: late_milestone) } - let_it_be(:issue_d) { create(:issue, project: project, labels: [priority2]) } - let_it_be(:issue_e) { create(:issue, project: project, milestone: early_milestone) } + let_it_be(:issue_a, reload: true) do + create( + :issue, + project: project, + discussion_locked: true, + labels: [priority3], + relative_position: 1000, + milestone: milestone4 + ) + end + + let_it_be(:issue_b, reload: true) do + create( + :issue, + :with_alert, + project: project, + title: 'title matching issue i', + due_date: 3.days.ago, + relative_position: 3000, + labels: [priority2, priority3], + milestone: milestone1 + ) + end + + let_it_be(:issue_c) do + create( + :issue, + project: project, + labels: [priority1], + milestone: milestone2, + due_date: 1.day.ago, + relative_position: nil + ) + end + + let_it_be(:issue_d) do + create(:issue, + project: project, + labels: [priority2], + due_date: 3.days.from_now, + relative_position: 5000, + milestone: milestone3 + ) + end + + let_it_be(:issue_e) do + create( + :issue, + :confidential, + project: project, + due_date: 1.day.from_now, + relative_position: nil + ) + end + let_it_be(:issues, reload: true) { [issue_a, issue_b, issue_c, issue_d, issue_e] } let(:issue_a_gid) { issue_a.to_global_id.to_s } @@ -39,7 +91,13 @@ RSpec.describe 'getting an issue list for a project' do # affects the `issues` query at the root level of the API. # Shared example also used in spec/requests/api/graphql/issues_spec.rb it_behaves_like 'graphql issue list request spec' do - subject(:post_query) { post_graphql(query, current_user: current_user) } + let_it_be(:external_user) { create(:user) } + + let(:public_projects) { [project] } + + before_all do + group.add_developer(current_user) + end # filters let(:expected_negated_assignee_issues) { [issue_b, issue_c, issue_d, issue_e] } @@ -50,12 +108,22 @@ RSpec.describe 'getting an issue list for a project' do let(:unlocked_discussion_issues) { [issue_b, issue_c, issue_d, issue_e] } let(:search_title_term) { 'matching issue' } let(:title_search_issue) { issue_b } + let(:confidential_issues) { [issue_e] } + let(:non_confidential_issues) { [issue_a, issue_b, issue_c, issue_d] } + let(:public_non_confidential_issues) { non_confidential_issues } # sorting let(:data_path) { [:project, :issues] } let(:expected_severity_sorted_asc) { [issue_c, issue_a, issue_b, issue_e, issue_d] } - let(:expected_priority_sorted_asc) { [issue_e, issue_c, issue_d, issue_a, issue_b] } - let(:expected_priority_sorted_desc) { [issue_c, issue_e, issue_a, issue_d, issue_b] } + let(:expected_priority_sorted_asc) { [issue_b, issue_c, issue_d, issue_a, issue_e] } + let(:expected_priority_sorted_desc) { [issue_a, issue_d, issue_c, issue_b, issue_e] } + let(:expected_due_date_sorted_desc) { [issue_d, issue_e, issue_c, issue_b, issue_a] } + let(:expected_due_date_sorted_asc) { [issue_b, issue_c, issue_e, issue_d, issue_a] } + let(:expected_relative_position_sorted_asc) { [issue_a, issue_b, issue_d, issue_c, issue_e] } + let(:expected_label_priority_sorted_asc) { [issue_c, issue_d, issue_b, issue_a, issue_e] } + let(:expected_label_priority_sorted_desc) { [issue_a, issue_d, issue_b, issue_c, issue_e] } + let(:expected_milestone_sorted_asc) { [issue_b, issue_c, issue_d, issue_a, issue_e] } + let(:expected_milestone_sorted_desc) { [issue_a, issue_d, issue_c, issue_b, issue_e] } before_all do issue_a.assignee_ids = current_user.id @@ -77,252 +145,9 @@ RSpec.describe 'getting an issue list for a project' do query_graphql_field(:issues, params, "#{page_info} nodes { id }") ) end - end - - context 'when limiting the number of results' do - let(:query) do - <<~GQL - query($path: ID!, $n: Int) { - project(fullPath: $path) { - issues(first: $n) { #{fields} } - } - } - GQL - end - - let(:issue_limit) { 1 } - let(:variables) do - { path: project.full_path, n: issue_limit } - end - - it_behaves_like 'a working graphql query' do - before do - post_graphql(query, current_user: current_user, variables: variables) - end - - it 'only returns N issues' do - expect(issues_data.size).to eq(issue_limit) - end - end - - context 'when no limit is provided' do - let(:issue_limit) { nil } - - it 'returns all issues' do - post_graphql(query, current_user: current_user, variables: variables) - - expect(issues_data.size).to be > 1 - end - end - - it 'is expected to check permissions on the first issue only' do - allow(Ability).to receive(:allowed?).and_call_original - # Newest first, we only want to see the newest checked - expect(Ability).not_to receive(:allowed?).with(current_user, :read_issue, issues.first) - - post_graphql(query, current_user: current_user, variables: variables) - end - end - - context 'when the user does not have access to the issue' do - it 'returns nil' do - project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) - - post_graphql(query) - - expect(issues_data).to eq([]) - end - end - - context 'when there is a confidential issue' do - let_it_be(:confidential_issue) do - create(:issue, :confidential, project: project) - end - - let(:confidential_issue_gid) { confidential_issue.to_global_id.to_s } - - context 'when the user cannot see confidential issues' do - it 'returns issues without confidential issues' do - post_graphql(query, current_user: current_user) - - expect(issues_data.size).to eq(5) - - issues_data.each do |issue| - expect(issue['confidential']).to eq(false) - end - end - - context 'filtering for confidential issues' do - let(:issue_filter_params) { { confidential: true } } - - it 'returns no issues' do - post_graphql(query, current_user: current_user) - - expect(issues_data.size).to eq(0) - end - end - - context 'filtering for non-confidential issues' do - let(:issue_filter_params) { { confidential: false } } - - it 'returns correctly filtered issues' do - post_graphql(query, current_user: current_user) - - expect(issue_ids).to match_array(issues.map { |i| i.to_gid.to_s }) - end - end - end - - context 'when the user can see confidential issues' do - before do - project.add_developer(current_user) - end - - it 'returns issues with confidential issues' do - post_graphql(query, current_user: current_user) - - expect(issues_data.size).to eq(6) - - confidentials = issues_data.map do |issue| - issue['confidential'] - end - - expect(confidentials).to contain_exactly(true, false, false, false, false, false) - end - - context 'filtering for confidential issues' do - let(:issue_filter_params) { { confidential: true } } - - it 'returns correctly filtered issues' do - post_graphql(query, current_user: current_user) - - expect(issue_ids).to contain_exactly(confidential_issue_gid) - end - end - - context 'filtering for non-confidential issues' do - let(:issue_filter_params) { { confidential: false } } - - it 'returns correctly filtered issues' do - post_graphql(query, current_user: current_user) - - expect(issue_ids).to match_array([issue_a, issue_b, issue_c, issue_d, issue_e].map { |i| i.to_gid.to_s }) - end - end - end - end - - describe 'sorting and pagination' do - let_it_be(:sort_project) { create(:project, :public) } - let_it_be(:data_path) { [:project, :issues] } - - def pagination_query(params) - graphql_query_for( - :project, - { full_path: sort_project.full_path }, - query_graphql_field(:issues, params, "#{page_info} nodes { iid }") - ) - end - - def pagination_results_data(data) - data.map { |issue| issue['iid'].to_i } - end - # rubocop:disable RSpec/MultipleMemoizedHelpers - context 'when sorting by due date' do - let_it_be(:due_issue1) { create(:issue, project: sort_project, due_date: 3.days.from_now) } - let_it_be(:due_issue2) { create(:issue, project: sort_project, due_date: nil) } - let_it_be(:due_issue3) { create(:issue, project: sort_project, due_date: 2.days.ago) } - let_it_be(:due_issue4) { create(:issue, project: sort_project, due_date: nil) } - let_it_be(:due_issue5) { create(:issue, project: sort_project, due_date: 1.day.ago) } - - context 'when ascending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :DUE_DATE_ASC } - let(:first_param) { 2 } - let(:all_records) { [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid] } - end - end - - context 'when descending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :DUE_DATE_DESC } - let(:first_param) { 2 } - let(:all_records) { [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid] } - end - end - end - - context 'when sorting by relative position' do - let_it_be(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) } - let_it_be(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) } - let_it_be(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) } - let_it_be(:relative_issue4) { create(:issue, project: sort_project, relative_position: nil) } - let_it_be(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) } - - context 'when ascending' do - it_behaves_like 'sorted paginated query', is_reversible: true do - let(:sort_param) { :RELATIVE_POSITION_ASC } - let(:first_param) { 2 } - let(:all_records) do - [ - relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, - relative_issue2.iid, relative_issue4.iid - ] - end - end - end - end - - context 'when sorting by label priority' do - let_it_be(:label1) { create(:label, project: sort_project, priority: 1) } - let_it_be(:label2) { create(:label, project: sort_project, priority: 5) } - let_it_be(:label3) { create(:label, project: sort_project, priority: 10) } - let_it_be(:label_issue1) { create(:issue, project: sort_project, labels: [label1]) } - let_it_be(:label_issue2) { create(:issue, project: sort_project, labels: [label2]) } - let_it_be(:label_issue3) { create(:issue, project: sort_project, labels: [label1, label3]) } - let_it_be(:label_issue4) { create(:issue, project: sort_project) } - - context 'when ascending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :LABEL_PRIORITY_ASC } - let(:first_param) { 2 } - let(:all_records) { [label_issue3.iid, label_issue1.iid, label_issue2.iid, label_issue4.iid] } - end - end - - context 'when descending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :LABEL_PRIORITY_DESC } - let(:first_param) { 2 } - let(:all_records) { [label_issue2.iid, label_issue3.iid, label_issue1.iid, label_issue4.iid] } - end - end - end - # rubocop:enable RSpec/MultipleMemoizedHelpers - - context 'when sorting by milestone due date' do - let_it_be(:early_milestone) { create(:milestone, project: sort_project, due_date: 10.days.from_now) } - let_it_be(:late_milestone) { create(:milestone, project: sort_project, due_date: 30.days.from_now) } - let_it_be(:milestone_issue1) { create(:issue, project: sort_project) } - let_it_be(:milestone_issue2) { create(:issue, project: sort_project, milestone: early_milestone) } - let_it_be(:milestone_issue3) { create(:issue, project: sort_project, milestone: late_milestone) } - - context 'when ascending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :MILESTONE_DUE_ASC } - let(:first_param) { 2 } - let(:all_records) { [milestone_issue2.iid, milestone_issue3.iid, milestone_issue1.iid] } - end - end - - context 'when descending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :MILESTONE_DUE_DESC } - let(:first_param) { 2 } - let(:all_records) { [milestone_issue3.iid, milestone_issue2.iid, milestone_issue1.iid] } - end - end + def post_query(request_user = current_user) + post_graphql(query, current_user: request_user) end end @@ -421,6 +246,7 @@ RSpec.describe 'getting an issue list for a project' do end before do + project.add_developer(current_user) issues.each do |issue| # create a label for each issue we have to properly test N+1 label = create(:label, project: project) @@ -469,6 +295,7 @@ RSpec.describe 'getting an issue list for a project' do end before do + project.add_developer(current_user) issues.each do |issue| # create an assignee for each issue we have to properly test N+1 assignee = create(:user) @@ -502,41 +329,6 @@ RSpec.describe 'getting an issue list for a project' do end end - context 'when fetching escalation status' do - let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue_a) } - - let(:statuses) { issue_data.to_h { |issue| [issue['iid'], issue['escalationStatus']] } } - let(:fields) do - <<~QUERY - nodes { - id - escalationStatus - } - QUERY - end - - before do - issue_a.update!(issue_type: Issue.issue_types[:incident]) - end - - it 'returns the escalation status values' do - post_graphql(query, current_user: current_user) - - statuses = issues_data.map { |issue| issue['escalationStatus'] } - - expect(statuses).to contain_exactly(escalation_status.status_name.upcase.to_s, nil, nil, nil, nil) - end - - it 'avoids N+1 queries', :aggregate_failures do - base_count = ActiveRecord::QueryRecorder.new { run_with_clean_state(query, context: { current_user: current_user }) } - - new_incident = create(:incident, project: project) - create(:incident_management_issuable_escalation_status, issue: new_incident) - - expect { run_with_clean_state(query, context: { current_user: current_user }) }.not_to exceed_query_limit(base_count) - end - end - describe 'N+1 query checks' do let(:extra_iid_for_second_query) { issue_b.iid.to_s } let(:search_params) { { iids: [issue_a.iid.to_s] } } @@ -597,6 +389,7 @@ RSpec.describe 'getting an issue list for a project' do include_examples 'N+1 query check' end + # rubocop:disable RSpec/MultipleMemoizedHelpers context 'when requesting `closed_as_duplicate_of`' do let(:requested_fields) { 'closedAsDuplicateOf { id }' } let(:issue_a_dup) { create(:issue, project: project) } @@ -609,6 +402,7 @@ RSpec.describe 'getting an issue list for a project' do include_examples 'N+1 query check' end + # rubocop:enable RSpec/MultipleMemoizedHelpers context 'when award emoji votes' do let(:requested_fields) { [:upvotes, :downvotes] } diff --git a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb index 5469fd80a4f..9de741ec529 100644 --- a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb @@ -109,6 +109,44 @@ RSpec.shared_examples 'graphql issue list request spec' do let(:ids) { issue_ids } end end + + context 'when filtering by confidentiality' do + context 'when fetching confidential issues' do + let(:issue_filter_params) { { confidential: true } } + + it 'returns only confidential issues' do + post_query + + expect(issue_ids).to match_array(to_gid_list(confidential_issues)) + end + + context 'when user cannot see confidential issues' do + it 'returns an empty list' do + post_query(external_user) + + expect(issue_ids).to be_empty + end + end + end + + context 'when fetching non-confidential issues' do + let(:issue_filter_params) { { confidential: false } } + + it 'returns only non-confidential issues' do + post_query + + expect(issue_ids).to match_array(to_gid_list(non_confidential_issues)) + end + + context 'when user cannot see confidential issues' do + it 'returns an empty list' do + post_query(external_user) + + expect(issue_ids).to match_array(to_gid_list(public_non_confidential_issues)) + end + end + end + end end describe 'sorting and pagination' do @@ -147,6 +185,174 @@ RSpec.shared_examples 'graphql issue list request spec' do end end end + + context 'when sorting by due date' do + context 'when ascending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :DUE_DATE_ASC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_due_date_sorted_asc) } + end + end + + context 'when descending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :DUE_DATE_DESC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_due_date_sorted_desc) } + end + end + end + + context 'when sorting by relative position' do + context 'when ascending' do + it_behaves_like 'sorted paginated query', is_reversible: true do + let(:sort_param) { :RELATIVE_POSITION_ASC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_relative_position_sorted_asc) } + end + end + end + + context 'when sorting by label priority' do + context 'when ascending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :LABEL_PRIORITY_ASC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_label_priority_sorted_asc) } + end + end + + context 'when descending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :LABEL_PRIORITY_DESC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_label_priority_sorted_desc) } + end + end + end + + context 'when sorting by milestone due date' do + context 'when ascending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :MILESTONE_DUE_ASC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_milestone_sorted_asc) } + end + end + + context 'when descending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :MILESTONE_DUE_DESC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_milestone_sorted_desc) } + end + end + end + end + + context 'when confidential issues exist' do + context 'when user can see confidential issues' do + it 'includes confidential issues' do + post_query + + all_issues = confidential_issues + non_confidential_issues + + expect(issue_ids).to match_array(to_gid_list(all_issues)) + expect(issues_data.map { |i| i['confidential'] }).to match_array(all_issues.map(&:confidential)) + end + end + + context 'when user cannot see confidential issues' do + let(:current_user) { external_user } + + it 'does not include confidential issues' do + post_query + + expect(issue_ids).to match_array(to_gid_list(public_non_confidential_issues)) + end + end + end + + context 'when limiting the number of results' do + let(:issue_limit) { 1 } + let(:issue_filter_params) { { first: issue_limit } } + + it_behaves_like 'a working graphql query' do + before do + post_query + end + + it 'only returns N issues' do + expect(issues_data.size).to eq(issue_limit) + end + end + + context 'when no limit is provided' do + let(:issue_limit) { nil } + + it 'returns all issues' do + post_query + + expect(issues_data.size).to be > 1 + end + end + + it 'is expected to check permissions on the first issue only' do + allow(Ability).to receive(:allowed?).and_call_original + # Newest first, we only want to see the newest checked + expect(Ability).not_to receive(:allowed?).with(current_user, :read_issue, issues.first) + + post_query + end + end + + context 'when the user does not have access to the issue' do + let(:current_user) { external_user } + + it 'returns no issues' do + public_projects.each do |public_project| + public_project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) + end + + post_query + + expect(issues_data).to eq([]) + end + end + + context 'when fetching escalation status' do + let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue_a) } + + let(:fields) do + <<~QUERY + nodes { + id + escalationStatus + } + QUERY + end + + before do + issue_a.update_columns(issue_type: Issue.issue_types[:incident]) + end + + it 'returns the escalation status values' do + post_query + + statuses = issues_data.map { |issue| issue['escalationStatus'] } + + expect(statuses).to contain_exactly(escalation_status.status_name.upcase.to_s, nil, nil, nil, nil) + end + + it 'avoids N+1 queries', :aggregate_failures do + control = ActiveRecord::QueryRecorder.new { run_with_clean_state(query, context: { current_user: current_user }) } + + new_incident = create(:incident, project: public_projects.first) + create(:incident_management_issuable_escalation_status, issue: new_incident) + + expect { run_with_clean_state(query, context: { current_user: current_user }) }.not_to exceed_query_limit(control) + end end it 'includes a web_url' do |