From 69c85c455f17bf59049f10f3a700882f3613c899 Mon Sep 17 00:00:00 2001 From: Chad Woolley Date: Fri, 17 Nov 2023 03:53:04 -0800 Subject: [PATCH 1/4] Backend changes for workspaces admin - Changes to GraphQL and auth policies to support Workspaces Admin UI. Changelog: changed EE: true --- doc/api/graphql/reference/index.md | 24 ++++++++++ .../graphql/ee/types/clusters/agent_type.rb | 7 +++ .../workspaces_for_agent_resolver.rb | 43 ++++++++++++++++++ .../api/graphql/remote_development/README.md | 3 ++ .../cluster_agent/workspaces/shared.rb | 45 +++++++++++++++++++ .../workspaces/with_actual_states_arg_spec.rb | 11 +++++ .../workspaces/with_ids_arg_spec.rb | 11 +++++ .../workspaces/with_no_args_spec.rb | 13 ++++++ .../workspaces/with_project_ids_arg_spec.rb | 11 +++++ .../api/graphql/remote_development/shared.rb | 14 +++--- 10 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/shared.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_actual_states_arg_spec.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_ids_arg_spec.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_no_args_spec.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_project_ids_arg_spec.rb diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index fbba2f2474b097..fe765681c7f697 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -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.6. +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`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. | +| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. | +| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project id. | + ### `ClusterAgentActivityEvent` #### Fields diff --git a/ee/app/graphql/ee/types/clusters/agent_type.rb b/ee/app/graphql/ee/types/clusters/agent_type.rb index b7edda890cd8be..538b1280e35acf 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.6' } end end end 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 00000000000000..23ecb71c49a168 --- /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: + 'Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`.' + + argument :project_ids, [::Types::GlobalIDType[Project]], + required: false, + description: 'Filter workspaces by project id.' + + 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[:include_actual_states] || [] + ) + end + end + end +end diff --git a/ee/spec/requests/api/graphql/remote_development/README.md b/ee/spec/requests/api/graphql/remote_development/README.md index 4173d78688731f..4e1a75e638a78f 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 00000000000000..82735eb2d8d239 --- /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.clusterAgent.workspaces query, even if they otherwise have full access to the workspace. + let_it_be(:unauthorized_user) { workspace.user } + + let_it_be(:agent) { workspace.agent } + let_it_be(:agent_project) { 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 00000000000000..9729a71724ab46 --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_actual_states_arg_spec.rb @@ -0,0 +1,11 @@ +# 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' + + 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 00000000000000..f020d66447535f --- /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 00000000000000..4d3bc381af03ab --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_no_args_spec.rb @@ -0,0 +1,13 @@ +# 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 00000000000000..73360bf853d169 --- /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/shared.rb b/ee/spec/requests/api/graphql/remote_development/shared.rb index cc49269f7a6b32..91f807624e2dfd 100644 --- a/ee/spec/requests/api/graphql/remote_development/shared.rb +++ b/ee/spec/requests/api/graphql/remote_development/shared.rb @@ -61,11 +61,7 @@ let_it_be(:agent) { create(:ee_cluster_agent, :with_remote_development_agent_config) } let_it_be(:workspace) { 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 } } @@ -192,6 +188,14 @@ let_it_be(:other_workspace) { create(:workspace, name: 'other-workspace') } 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) 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. -- GitLab From 47b49abc236d0e6b4317f148082fd3f5148e8ec0 Mon Sep 17 00:00:00 2001 From: Chad Woolley Date: Fri, 8 Dec 2023 23:50:50 -0800 Subject: [PATCH 2/4] Improve test coverage --- .../workspaces_for_agent_resolver.rb | 2 +- .../cluster_agent/workspaces/shared.rb | 8 ++-- .../workspaces/with_actual_states_arg_spec.rb | 1 + .../workspaces/with_no_args_spec.rb | 1 - .../current_user/workspaces/shared.rb | 2 +- .../workspaces/with_no_args_spec.rb | 2 +- .../api/graphql/remote_development/shared.rb | 40 +++++++++++++------ .../remote_development/workspaces/shared.rb | 2 +- 8 files changed, 36 insertions(+), 22 deletions(-) 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 index 23ecb71c49a168..bb66ecb3716930 100644 --- a/ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb +++ b/ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb @@ -35,7 +35,7 @@ def resolve(**args) 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[:include_actual_states] || [] + actual_states: args[:actual_states] || [] ) end end 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 index 82735eb2d8d239..dab7b3861ec1f3 100644 --- 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 @@ -9,11 +9,11 @@ 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.clusterAgent.workspaces query, even if they otherwise have full access to the workspace. - let_it_be(:unauthorized_user) { workspace.user } + # 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) { workspace.agent } - let_it_be(:agent_project) { agent.project } + let_it_be(:agent, reload: true) { workspace.agent } + let_it_be(:agent_project, reload: true) { agent.project } let(:fields) do <<~QUERY 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 index 9729a71724ab46..951fa340a5816e 100644 --- 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 @@ -6,6 +6,7 @@ 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_no_args_spec.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_no_args_spec.rb index 4d3bc381af03ab..8313850a538c71 100644 --- 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 @@ -6,7 +6,6 @@ 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' 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 adfae4cf56c571..a57a35e4d8ae2f 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 0e906b3e1a1dda..a5124014b63997 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 91f807624e2dfd..f289773ca2a0dc 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,7 +63,7 @@ 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') } include_context 'with non_matching_workspace associated with other agent created' @@ -68,15 +72,17 @@ 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 @@ -188,10 +194,18 @@ 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) do + let_it_be(:non_matching_workspace, reload: true) do create(:workspace, agent: other_agent, user: workspace.user, name: 'non-matching-workspace') end end 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 1d3451c88429a1..57500dca65b137 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 -- GitLab From bce2c8df835f6acda140b5769423659c0b14955c Mon Sep 17 00:00:00 2001 From: Chad Woolley Date: Thu, 14 Dec 2023 20:27:07 -0800 Subject: [PATCH 3/4] Address review feedback --- doc/api/graphql/reference/index.md | 64 +++++++++---------- .../graphql/ee/types/clusters/agent_type.rb | 2 +- .../remote_development/workspaces/create.rb | 2 +- .../workspaces_for_agent_resolver.rb | 4 +- .../workspaces_for_current_user_resolver.rb | 6 +- .../workspaces_for_query_root_resolver.rb | 8 +-- 6 files changed, 43 insertions(+), 43 deletions(-) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index fe765681c7f697..ac213175e3bd4d 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` @@ -16008,7 +16008,7 @@ GitLab CI/CD configuration template. Workspaces associated with the agent. WARNING: -**Introduced** in 16.6. +**Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Returns [`WorkspaceConnection`](#workspaceconnection). @@ -16022,8 +16022,8 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | | `actualStates` | [`[String!]`](#string) | Filter workspaces by actual states. | -| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. | -| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project id. | +| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. | +| `projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalID. | ### `ClusterAgentActivityEvent` @@ -16960,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` @@ -21828,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` @@ -22116,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` @@ -22467,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` @@ -22791,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` @@ -27857,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 538b1280e35acf..c125b2e12a5492 100644 --- a/ee/app/graphql/ee/types/clusters/agent_type.rb +++ b/ee/app/graphql/ee/types/clusters/agent_type.rb @@ -18,7 +18,7 @@ module AgentType null: true, resolver: ::Resolvers::RemoteDevelopment::WorkspacesForAgentResolver, description: 'Workspaces associated with the agent.', - alpha: { milestone: '16.6' } + 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 11b01d9d1a6e31..5e8a4f9bc51e9f 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 index bb66ecb3716930..880535807c02d9 100644 --- a/ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb +++ b/ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb @@ -13,11 +13,11 @@ class WorkspacesForAgentResolver < ::Resolvers::BaseResolver argument :ids, [::Types::GlobalIDType[::RemoteDevelopment::Workspace]], required: false, description: - 'Array of global 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 id.' + description: 'Filter workspaces by project GlobalID.' argument :actual_states, [GraphQL::Types::String], required: false, 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 e29213394a9adb..4ca802928a50d0 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 287e7eb951cb4b..b96aea7190a5c1 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, -- GitLab From 64c1ffe6b92dba302adf63ba7da18f4746af117c Mon Sep 17 00:00:00 2001 From: Chad Woolley Date: Thu, 14 Dec 2023 23:52:41 -0800 Subject: [PATCH 4/4] Graphql doc change after rebase --- doc/api/graphql/reference/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index ac213175e3bd4d..62101b4f3a6e44 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -16015,7 +16015,7 @@ 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`, `last: Int`. +`before: String`, `after: String`, `first: Int`, and `last: Int`. ###### Arguments -- GitLab