diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index fbba2f2474b097c2780b5ad4df7ef85edad5d985..62101b4f3a6e442605a0481289c18464199bb2b6 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1093,11 +1093,11 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. |
-| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent ids. |
-| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent GlobalIDs. |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| `includeActualStates` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in 16.7. Use actual_states instead. |
-| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project ids. |
-| `userIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by user ids. |
+| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalIDs. |
+| `userIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by user GlobalIDs. |
## `Mutation` type
@@ -8620,7 +8620,7 @@ Input type: `WorkspaceCreateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| `clusterAgentId` | [`ClustersAgentID!`](#clustersagentid) | ID of the cluster agent the created workspace will be associated with. |
+| `clusterAgentId` | [`ClustersAgentID!`](#clustersagentid) | GlobalID of the cluster agent the created workspace will be associated with. |
| `desiredState` | [`String!`](#string) | Desired state of the created workspace. |
| `devfilePath` | [`String!`](#string) | Project repo git path containing the devfile used to configure the workspace. |
| `devfileRef` | [`String!`](#string) | Project repo git ref containing the devfile used to configure the workspace. |
@@ -14243,10 +14243,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. |
-| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent ids. |
-| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent GlobalIDs. |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| `includeActualStates` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in 16.7. Use actual_states instead. |
-| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project ids. |
+| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalIDs. |
### `AgentConfiguration`
@@ -14923,10 +14923,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. |
-| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent ids. |
-| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent GlobalIDs. |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| `includeActualStates` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in 16.7. Use actual_states instead. |
-| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project ids. |
+| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalIDs. |
### `AwardEmoji`
@@ -16001,6 +16001,30 @@ GitLab CI/CD configuration template.
| `vulnerabilityImages` | [`VulnerabilityContainerImageConnection`](#vulnerabilitycontainerimageconnection) | Container images reported on the agent vulnerabilities. (see [Connections](#connections)) |
| `webPath` | [`String`](#string) | Web path of the cluster agent. |
+#### Fields with arguments
+
+##### `ClusterAgent.workspaces`
+
+Workspaces associated with the agent.
+
+WARNING:
+**Introduced** in 16.7.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Returns [`WorkspaceConnection`](#workspaceconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalID. |
+
### `ClusterAgentActivityEvent`
#### Fields
@@ -16936,10 +16960,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. |
-| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent ids. |
-| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent GlobalIDs. |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| `includeActualStates` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in 16.7. Use actual_states instead. |
-| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project ids. |
+| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalIDs. |
### `CustomEmoji`
@@ -21804,10 +21828,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. |
-| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent ids. |
-| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent GlobalIDs. |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| `includeActualStates` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in 16.7. Use actual_states instead. |
-| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project ids. |
+| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalIDs. |
### `MergeRequestAuthor`
@@ -22092,10 +22116,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. |
-| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent ids. |
-| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent GlobalIDs. |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| `includeActualStates` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in 16.7. Use actual_states instead. |
-| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project ids. |
+| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalIDs. |
### `MergeRequestDiff`
@@ -22443,10 +22467,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. |
-| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent ids. |
-| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent GlobalIDs. |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| `includeActualStates` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in 16.7. Use actual_states instead. |
-| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project ids. |
+| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalIDs. |
### `MergeRequestPermissions`
@@ -22767,10 +22791,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. |
-| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent ids. |
-| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent GlobalIDs. |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| `includeActualStates` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in 16.7. Use actual_states instead. |
-| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project ids. |
+| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalIDs. |
### `Metadata`
@@ -27833,10 +27857,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. |
-| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent ids. |
-| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+| `agentIds` | [`[ClustersAgentID!]`](#clustersagentid) | Filter workspaces by agent GlobalIDs. |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| `includeActualStates` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in 16.7. Use actual_states instead. |
-| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project ids. |
+| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalIDs. |
### `UserMergeRequestInteraction`
diff --git a/ee/app/graphql/ee/types/clusters/agent_type.rb b/ee/app/graphql/ee/types/clusters/agent_type.rb
index b7edda890cd8be041eade288abd75ccc1d452b01..c125b2e12a5492cf400a37e44dc4369ba1cdf85e 100644
--- a/ee/app/graphql/ee/types/clusters/agent_type.rb
+++ b/ee/app/graphql/ee/types/clusters/agent_type.rb
@@ -12,6 +12,13 @@ module AgentType
null: true,
description: 'Container images reported on the agent vulnerabilities.',
resolver: ::Resolvers::Vulnerabilities::ContainerImagesResolver
+
+ field :workspaces,
+ ::Types::RemoteDevelopment::WorkspaceType.connection_type,
+ null: true,
+ resolver: ::Resolvers::RemoteDevelopment::WorkspacesForAgentResolver,
+ description: 'Workspaces associated with the agent.',
+ alpha: { milestone: '16.7' }
end
end
end
diff --git a/ee/app/graphql/mutations/remote_development/workspaces/create.rb b/ee/app/graphql/mutations/remote_development/workspaces/create.rb
index 11b01d9d1a6e31b0fdc7d1caa41362ae283d8c91..5e8a4f9bc51e9f798d05396de64ad8489f2f4ecb 100644
--- a/ee/app/graphql/mutations/remote_development/workspaces/create.rb
+++ b/ee/app/graphql/mutations/remote_development/workspaces/create.rb
@@ -18,7 +18,7 @@ class Create < BaseMutation
argument :cluster_agent_id,
::Types::GlobalIDType[::Clusters::Agent],
required: true,
- description: 'ID of the cluster agent the created workspace will be associated with.'
+ description: 'GlobalID of the cluster agent the created workspace will be associated with.'
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/409772 - Make this a type:enum
argument :desired_state,
diff --git a/ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb b/ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb
new file mode 100644
index 0000000000000000000000000000000000000000..880535807c02d9d399ba1806b2600b57a32f886e
--- /dev/null
+++ b/ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module RemoteDevelopment
+ class WorkspacesForAgentResolver < ::Resolvers::BaseResolver
+ include ResolvesIds
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type Types::RemoteDevelopment::WorkspaceType.connection_type, null: true
+ authorize :admin_cluster
+ authorizes_object!
+
+ argument :ids, [::Types::GlobalIDType[::RemoteDevelopment::Workspace]],
+ required: false,
+ description:
+ 'Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`.'
+
+ argument :project_ids, [::Types::GlobalIDType[Project]],
+ required: false,
+ description: 'Filter workspaces by project GlobalID.'
+
+ argument :actual_states, [GraphQL::Types::String],
+ required: false,
+ description: 'Filter workspaces by actual states.'
+
+ alias_method :agent, :object
+
+ def resolve(**args)
+ unless License.feature_available?(:remote_development)
+ raise_resource_not_available_error! "'remote_development' licensed feature is not available"
+ end
+
+ ::RemoteDevelopment::WorkspacesFinder.execute(
+ current_user: current_user,
+ agent_ids: [agent.id],
+ ids: resolve_ids(args[:ids]).map(&:to_i),
+ project_ids: resolve_ids(args[:project_ids]).map(&:to_i),
+ actual_states: args[:actual_states] || []
+ )
+ end
+ end
+ end
+end
diff --git a/ee/app/graphql/resolvers/remote_development/workspaces_for_current_user_resolver.rb b/ee/app/graphql/resolvers/remote_development/workspaces_for_current_user_resolver.rb
index e29213394a9adb9c8e7262f74e21a70dd41967ce..4ca802928a50d01b996c66f1b5a531356c866209 100644
--- a/ee/app/graphql/resolvers/remote_development/workspaces_for_current_user_resolver.rb
+++ b/ee/app/graphql/resolvers/remote_development/workspaces_for_current_user_resolver.rb
@@ -11,15 +11,15 @@ class WorkspacesForCurrentUserResolver < ::Resolvers::BaseResolver
argument :ids, [::Types::GlobalIDType[::RemoteDevelopment::Workspace]],
required: false,
description:
- 'Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`.'
+ 'Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`.'
argument :project_ids, [::Types::GlobalIDType[Project]],
required: false,
- description: 'Filter workspaces by project ids.'
+ description: 'Filter workspaces by project GlobalIDs.'
argument :agent_ids, [::Types::GlobalIDType[::Clusters::Agent]],
required: false,
- description: 'Filter workspaces by agent ids.'
+ description: 'Filter workspaces by agent GlobalIDs.'
argument :include_actual_states, [GraphQL::Types::String],
required: false,
diff --git a/ee/app/graphql/resolvers/remote_development/workspaces_for_query_root_resolver.rb b/ee/app/graphql/resolvers/remote_development/workspaces_for_query_root_resolver.rb
index 287e7eb951cb4be7115721898c4659fc67873d44..b96aea7190a5c12d8e66b5373b9944314238fc3f 100644
--- a/ee/app/graphql/resolvers/remote_development/workspaces_for_query_root_resolver.rb
+++ b/ee/app/graphql/resolvers/remote_development/workspaces_for_query_root_resolver.rb
@@ -16,19 +16,19 @@ class WorkspacesForQueryRootResolver < ::Resolvers::BaseResolver
argument :ids, [::Types::GlobalIDType[::RemoteDevelopment::Workspace]],
required: false,
description:
- 'Filter workspaces by workspace ids. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`.'
+ 'Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`.'
argument :user_ids, [::Types::GlobalIDType[Project]],
required: false,
- description: 'Filter workspaces by user ids.'
+ description: 'Filter workspaces by user GlobalIDs.'
argument :project_ids, [::Types::GlobalIDType[Project]],
required: false,
- description: 'Filter workspaces by project ids.'
+ description: 'Filter workspaces by project GlobalIDs.'
argument :agent_ids, [::Types::GlobalIDType[::Clusters::Agent]],
required: false,
- description: 'Filter workspaces by agent ids.'
+ description: 'Filter workspaces by agent GlobalIDs.'
argument :include_actual_states, [GraphQL::Types::String],
required: false,
diff --git a/ee/spec/requests/api/graphql/remote_development/README.md b/ee/spec/requests/api/graphql/remote_development/README.md
index 4173d78688731f01f8082894857f63d58c3ccfd3..4e1a75e638a78f014efff5af69f42ba6fcf9e253 100644
--- a/ee/spec/requests/api/graphql/remote_development/README.md
+++ b/ee/spec/requests/api/graphql/remote_development/README.md
@@ -3,6 +3,9 @@ under the root `Query` type.
For example:
+- `ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces`
+ contains specs which test the `Query.project.clusterAgent.workspaces` field in the GraphQL API schema,
+ as well as any other field which contains a `clusterAgent` field or collection.
- `ee/spec/requests/api/graphql/remote_development/current_user/workspaces`
contains specs which test the `Query.currentUser.workspaces` field in the GraphQL API schema.
- `ee/spec/requests/api/graphql/remote_development/workspace`
diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/shared.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/shared.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dab7b3861ec1f33bdf3abee106082f298cba3a92
--- /dev/null
+++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/shared.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require_relative '../../shared'
+
+RSpec.shared_context 'for a Query.project.clusterAgent.workspaces query' do
+ include GraphqlHelpers
+
+ let_it_be(:cluster_admin_user) { create(:user) }
+ let_it_be(:authorized_user) { cluster_admin_user }
+
+ # NOTE: We use the workspace owner as the unauthorized user, because they should not have any access to the workspace
+ # via the Query.project.clusterAgent.workspaces query, even if they otherwise have full access to the workspace.
+ let_it_be(:unauthorized_user, reload: true) { workspace.user }
+
+ let_it_be(:agent, reload: true) { workspace.agent }
+ let_it_be(:agent_project, reload: true) { agent.project }
+
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ #{all_graphql_fields_for('workspaces'.classify, max_depth: 1)}
+ }
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ :project,
+ { full_path: agent_project.full_path },
+ query_graphql_field(
+ :cluster_agent,
+ { name: agent.name },
+ query_graphql_field('workspaces', args, fields)
+ )
+ )
+ end
+
+ before do
+ agent_project.add_maintainer(cluster_admin_user) # rubocop:disable RSpec/BeforeAllRoleAssignment -- this needs to be `before`, not `before_all`
+ agent.update!(created_by_user: cluster_admin_user)
+ workspace.reload # Ensure loaded workspace fixture's agent reflects updated created_by_user
+ end
+
+ subject { graphql_data.dig('project', 'clusterAgent', 'workspaces', 'nodes') }
+end
diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_actual_states_arg_spec.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_actual_states_arg_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..951fa340a5816e300efedf6d7e5c999b02d72fe4
--- /dev/null
+++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_actual_states_arg_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_relative './shared'
+
+RSpec.describe 'Query.project.clusterAgent.workspaces(actual_states: [GraphQL::Types::String])', feature_category: :remote_development do
+ include_context 'with actual_states argument'
+ include_context 'for a Query.project.clusterAgent.workspaces query'
+ include_context 'with non_matching_workspace associated with same agent'
+
+ it_behaves_like 'multiple workspaces query'
+end
diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_ids_arg_spec.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_ids_arg_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f020d66447535f38da5e207c1609ca234ae96f0c
--- /dev/null
+++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_ids_arg_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_relative './shared'
+
+RSpec.describe 'Query.project.clusterAgent.workspaces(ids: [RemoteDevelopmentWorkspaceID!])', feature_category: :remote_development do
+ include_context 'with ids argument'
+ include_context 'for a Query.project.clusterAgent.workspaces query'
+
+ it_behaves_like 'multiple workspaces query'
+end
diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_no_args_spec.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_no_args_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8313850a538c71d2390a1d606466a061f3776e88
--- /dev/null
+++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_no_args_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_relative './shared'
+
+RSpec.describe 'Query.project.clusterAgent.workspaces (with no arguments)', feature_category: :remote_development do
+ include_context 'with no arguments'
+ include_context 'for a Query.project.clusterAgent.workspaces query'
+ include_context 'with non_matching_workspace associated with other agent created'
+
+ it_behaves_like 'multiple workspaces query'
+end
diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_project_ids_arg_spec.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_project_ids_arg_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..73360bf853d169581f3b2c8a13fc753d11b48c65
--- /dev/null
+++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_project_ids_arg_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_relative './shared'
+
+RSpec.describe 'Query.project.clusterAgent.workspaces(project_ids: [::Types::GlobalIDType[Project]!])', feature_category: :remote_development do
+ include_context 'with project_ids argument'
+ include_context 'for a Query.project.clusterAgent.workspaces query'
+
+ it_behaves_like 'multiple workspaces query'
+end
diff --git a/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/shared.rb b/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/shared.rb
index adfae4cf56c57116cb0f1777fed6ab2415c44a3d..a57a35e4d8ae2f3e1df09107ebe0644c764263e3 100644
--- a/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/shared.rb
+++ b/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/shared.rb
@@ -5,7 +5,7 @@
RSpec.shared_context 'for a Query.currentUser.workspaces query' do
include GraphqlHelpers
- let_it_be(:authorized_user) { workspace.user }
+ let_it_be(:authorized_user, reload: true) { workspace.user }
let_it_be(:unauthorized_user) { create(:user) }
include_context "with authorized user as developer on workspace's project"
diff --git a/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/with_no_args_spec.rb b/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/with_no_args_spec.rb
index 0e906b3e1a1dda67cd12115b065ef5868b9d459e..a5124014b63997cea4f5e2c3f95b77277ece04a6 100644
--- a/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/with_no_args_spec.rb
+++ b/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/with_no_args_spec.rb
@@ -8,7 +8,7 @@
include_context 'for a Query.currentUser.workspaces query'
# create workspace owned by different user, to ensure it is not returned by the query
- let_it_be(:non_matching_workspace) { create(:workspace, name: 'non-matching-workspace') }
+ let_it_be(:non_matching_workspace, reload: true) { create(:workspace, name: 'non-matching-workspace') }
it_behaves_like 'multiple workspaces query'
end
diff --git a/ee/spec/requests/api/graphql/remote_development/shared.rb b/ee/spec/requests/api/graphql/remote_development/shared.rb
index cc49269f7a6b32742ebdcfc504eae5b691bae118..f289773ca2a0dc7f5bddf66705645f5159920b39 100644
--- a/ee/spec/requests/api/graphql/remote_development/shared.rb
+++ b/ee/spec/requests/api/graphql/remote_development/shared.rb
@@ -7,7 +7,7 @@
RSpec.shared_context 'with no arguments' do
include_context 'with unauthorized workspace created'
- let_it_be(:workspace) { create(:workspace, name: 'matching-workspace') }
+ let_it_be(:workspace, reload: true) { create(:workspace, name: 'matching-workspace') }
# NOTE: Specs including this context must define `non_matching_workspace` as follows:
# let_it_be(:non_matching_workspace) { create(:workspace, name: 'non-matching-workspace', ...) }
@@ -19,10 +19,12 @@
RSpec.shared_context 'with id arg' do
include_context 'with unauthorized workspace created'
- let_it_be(:workspace) { create(:workspace, name: 'matching-workspace') }
+ let_it_be(:workspace, reload: true) { create(:workspace, name: 'matching-workspace') }
# create workspace with different ID but still owned by the same user, to ensure isn't returned by the query
- let_it_be(:non_matching_workspace) { create(:workspace, user: workspace.user, name: 'non-matching-workspace') }
+ let_it_be(:non_matching_workspace, reload: true) do
+ create(:workspace, user: workspace.user, name: 'non-matching-workspace')
+ end
let(:id) { workspace.to_global_id.to_s }
let(:args) { { id: id } }
@@ -31,10 +33,12 @@
RSpec.shared_context 'with ids argument' do
include_context 'with unauthorized workspace created'
- let_it_be(:workspace) { create(:workspace, name: 'matching-workspace') }
+ let_it_be(:workspace, reload: true) { create(:workspace, name: 'matching-workspace') }
# create workspace with different ID but still owned by the same user, to ensure isn't returned by the query
- let_it_be(:non_matching_workspace) { create(:workspace, user: workspace.user, name: 'non-matching-workspace') }
+ let_it_be(:non_matching_workspace, reload: true) do
+ create(:workspace, user: workspace.user, name: 'non-matching-workspace')
+ end
let_it_be(:ids) { [workspace.to_global_id.to_s, unauthorized_workspace.to_global_id.to_s] }
let_it_be(:args) { { ids: ids } }
@@ -44,10 +48,10 @@
include_context 'with unauthorized workspace created'
let_it_be(:project) { create(:project, :public) }
- let_it_be(:workspace) { create(:workspace, project_id: project.id, name: 'matching-workspace') }
+ let_it_be(:workspace, reload: true) { create(:workspace, project_id: project.id, name: 'matching-workspace') }
# create workspace with different project but still owned by the same user, to ensure isn't returned by the query
- let_it_be(:non_matching_workspace) do
+ let_it_be(:non_matching_workspace, reload: true) do
create(:workspace, user: workspace.user, name: 'non-matching-workspace')
end
@@ -59,28 +63,26 @@
include_context 'with unauthorized workspace created'
let_it_be(:agent) { create(:ee_cluster_agent, :with_remote_development_agent_config) }
- let_it_be(:workspace) { create(:workspace, agent: agent, name: 'matching-workspace') }
+ let_it_be(:workspace, reload: true) { create(:workspace, agent: agent, name: 'matching-workspace') }
- # create workspace associated with different agent but owned by same user, to ensure isn't returned by the query
- let_it_be(:other_agent) { create(:ee_cluster_agent, :with_remote_development_agent_config) }
- let_it_be(:non_matching_workspace) do
- create(:workspace, agent: other_agent, user: workspace.user, name: 'non-matching-workspace')
- end
+ include_context 'with non_matching_workspace associated with other agent created'
let(:agent_ids) { [agent.to_global_id.to_s, unauthorized_workspace.agent.to_global_id.to_s] }
let(:args) { { agent_ids: agent_ids } }
end
RSpec.shared_context 'with actual_states argument' do
- let_it_be(:matching_actual_state) { ::RemoteDevelopment::Workspaces::States::STOPPED }
+ let_it_be(:matching_actual_state) { ::RemoteDevelopment::Workspaces::States::CREATION_REQUESTED }
include_context 'with unauthorized workspace created'
- let_it_be(:workspace) { create(:workspace, actual_state: matching_actual_state, name: 'matching-workspace') }
+ let_it_be(:workspace, reload: true) do
+ create(:workspace, actual_state: matching_actual_state, name: 'matching-workspace')
+ end
- # create workspace with non-matching actual state but owned by same user, to ensure it is not returned by the query
+ # create workspace with non-matching actual state, to ensure it is not returned by the query
let_it_be(:non_matching_actual_state) { ::RemoteDevelopment::Workspaces::States::RUNNING }
- let_it_be(:non_matching_workspace) do
+ let_it_be(:non_matching_workspace, reload: true) do
create(:workspace, actual_state: non_matching_actual_state, user: workspace.user, name: 'non-matching-workspace')
end
@@ -192,6 +194,22 @@
let_it_be(:other_workspace) { create(:workspace, name: 'other-workspace') }
end
+RSpec.shared_context 'with non_matching_workspace associated with same agent' do
+ before do
+ # Ensure the non-matching workspace is also associated with the same agent
+ non_matching_workspace.update!(agent: agent)
+ non_matching_workspace.reload
+ end
+end
+
+RSpec.shared_context 'with non_matching_workspace associated with other agent created' do
+ # create workspace associated with different agent but owned by same user, to ensure isn't returned by the query
+ let_it_be(:other_agent) { create(:ee_cluster_agent, :with_remote_development_agent_config) }
+ let_it_be(:non_matching_workspace, reload: true) do
+ create(:workspace, agent: other_agent, user: workspace.user, name: 'non-matching-workspace')
+ end
+end
+
RSpec.shared_context 'with unauthorized workspace created' do
# The authorized_user will not be authorized to see the `other-workspace`. We don't name it
# `unauthorized-workspace`, because the admin is still authorized to see it.
diff --git a/ee/spec/requests/api/graphql/remote_development/workspaces/shared.rb b/ee/spec/requests/api/graphql/remote_development/workspaces/shared.rb
index 1d3451c88429a1be3a83cad029c1c4f53ac990f4..57500dca65b137204f5ec7e1ad26afc8c8d5b0f4 100644
--- a/ee/spec/requests/api/graphql/remote_development/workspaces/shared.rb
+++ b/ee/spec/requests/api/graphql/remote_development/workspaces/shared.rb
@@ -8,7 +8,7 @@
let_it_be(:authorized_user) { create(:admin) }
# Only instance admins may use this query, all other users, even workspace owners, will get an empty result
- let_it_be(:workspace_owner) { workspace.user }
+ let_it_be(:workspace_owner, reload: true) { workspace.user }
let_it_be(:unauthorized_user) { workspace_owner }
let(:fields) do