Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-11-25 00:11:26 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-11-25 00:11:26 +0300
commit74f16c03423e42428ce10ec34d1a0a20700e79a0 (patch)
tree75eba7c1606a6d372fbb1e0bb9daf9bbe5b3283e
parente20fed01c86f47ffba316483f312a36330fd084d (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue10
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue30
-rw-r--r--app/assets/javascripts/repository/components/tree_content.vue2
-rw-r--r--app/assets/javascripts/repository/queries/commit.query.graphql7
-rw-r--r--app/controllers/projects/tree_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--config/feature_flags/development/lazy_load_commits.yml8
-rw-r--r--config/initializers_before_autoloader/000_inflections.rb1
-rw-r--r--db/docs/dependency_proxy_manifest_states.yml9
-rw-r--r--db/migrate/20221102195642_create_dependency_proxy_manifest_states.rb45
-rw-r--r--db/schema_migrations/202211021956421
-rw-r--r--db/structure.sql28
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md10
-rw-r--r--doc/api/geo_nodes.md36
-rw-r--r--doc/api/graphql/reference/index.md64
-rw-r--r--lib/gitlab/database/gitlab_schemas.yml1
-rw-r--r--spec/factories/dependency_proxy.rb9
-rw-r--r--spec/frontend/repository/components/table/index_spec.js3
-rw-r--r--spec/frontend/repository/components/table/row_spec.js3
-rw-r--r--spec/frontend/repository/components/tree_content_spec.js1
-rw-r--r--spec/models/factories_spec.rb2
-rw-r--r--spec/requests/api/graphql/issues_spec.rb84
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb374
-rw-r--r--spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb206
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