Skip to content

Commit

Permalink
GraphQL - Group Attachment Resolver + AttachFilesToGroup Mutation (#763)
Browse files Browse the repository at this point in the history
* first cut, working functionality

* update schema

* Fix auth + add test case

* add tests for attaching files to group

* add test cases and fixtures
  • Loading branch information
JeffreyThiessen authored Sep 18, 2024
1 parent d54efd9 commit 622a807
Show file tree
Hide file tree
Showing 14 changed files with 700 additions and 1 deletion.
49 changes: 49 additions & 0 deletions app/graphql/mutations/attach_files_to_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

module Mutations
# Mutation that attaches files to group
class AttachFilesToGroup < BaseMutation
description 'Attaches files to group.'
argument :files, [String], required: true, description: 'A list of files (signedBlobId) to attach to the group'
argument :group_id, ID,
required: false,
description: 'The Node ID of the group to be updated. For example, `gid://irida/group/a84cd757-dedb-4c64-8b01-097020163077`' # rubocop:disable Layout/LineLength
argument :group_puid, ID, # rubocop:disable GraphQL/ExtractInputType
required: false,
description: 'Persistent Unique Identifier of the group. For example, `INXT_PRJ_AAAAAAAAAA`.'
validates required: { one_of: %i[group_id group_puid] }

field :errors, [Types::UserErrorType], null: false, description: 'A list of errors that prevented the mutation.'
field :group, Types::GroupType, null: true, description: 'The updated group.'
field :status, GraphQL::Types::JSON, null: true, description: 'The status of the mutation.'

def resolve(args) # rubocop:disable Metrics/MethodLength
group = get_group_from_id_or_puid_args(args)

if group.nil?
return {
group:,
status: nil,
errors: [{ path: ['group'], message: 'not found by provided ID or PUID' }]
}
end

files_attached = Attachments::CreateService.new(current_user, group, { files: args[:files] }).execute

status, user_errors = attachment_status_and_errors(files_attached:, file_blob_id_list: args[:files])

# append query level errors
user_errors.push(*get_errors_from_object(group, 'group'))

{
group:,
status:,
errors: user_errors
}
end

def ready?(**_args)
authorize!(to: :mutate?, with: GraphqlPolicy, context: { user: context[:current_user], token: context[:token] })
end
end
end
10 changes: 10 additions & 0 deletions app/graphql/mutations/base_mutation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ def get_project_from_id_or_puid_args(args)
nil
end

def get_group_from_id_or_puid_args(args)
if args[:group_id]
IridaSchema.object_from_id(args[:group_id], { expected_type: Group })
else
Group.find_by!(puid: args[:group_puid])
end
rescue ActiveRecord::RecordNotFound
nil
end

def attachment_status_and_errors(files_attached:, file_blob_id_list:)
# initialize status hash such that all blob ids given by user are included
status = Hash[*file_blob_id_list.collect { |v| [v, nil] }.flatten]
Expand Down
25 changes: 25 additions & 0 deletions app/graphql/resolvers/group_attachments_resolver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Resolvers
# Group Attachments Resolver
class GroupAttachmentsResolver < BaseResolver
argument :filter, Types::AttachmentFilterInputType,
required: false,
description: 'Ransack filter',
default_value: nil

argument :order_by, Types::AttachmentOrderInputType,
required: false,
description: 'Order by',
default_value: nil

alias group object

def resolve(filter:, order_by:)
ransack_obj = group.attachments.joins(:file_blob).ransack(filter&.to_h)
ransack_obj.sorts = ["#{order_by.field} #{order_by.direction}"] if order_by.present?

ransack_obj.result
end
end
end
95 changes: 95 additions & 0 deletions app/graphql/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
"""
Autogenerated input type of AttachFilesToGroup
"""
input AttachFilesToGroupInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String

"""
A list of files (signedBlobId) to attach to the group
"""
files: [String!]!

"""
The Node ID of the group to be updated. For example, `gid://irida/group/a84cd757-dedb-4c64-8b01-097020163077`
"""
groupId: ID

"""
Persistent Unique Identifier of the group. For example, `INXT_PRJ_AAAAAAAAAA`.
"""
groupPuid: ID
}

"""
Autogenerated return type of AttachFilesToGroup.
"""
type AttachFilesToGroupPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String

"""
A list of errors that prevented the mutation.
"""
errors: [UserError!]!

"""
The updated group.
"""
group: Group

"""
The status of the mutation.
"""
status: JSON
}

"""
Autogenerated input type of AttachFilesToProject
"""
Expand Down Expand Up @@ -642,6 +692,41 @@ type DirectUpload {
A group
"""
type Group implements Node {
"""
Attachments on the group
"""
attachments(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String

"""
Returns the elements in the list that come before the specified cursor.
"""
before: String

"""
Ransack filter
"""
filter: AttachmentFilter = null

"""
Returns the first _n_ elements from the list.
"""
first: Int

"""
Returns the last _n_ elements from the list.
"""
last: Int

"""
Order by
"""
orderBy: AttachmentOrder = null
): AttachmentConnection

"""
Datetime of creation.
"""
Expand Down Expand Up @@ -1076,6 +1161,16 @@ scalar JSON
The mutation root of this schema
"""
type Mutation {
"""
Attaches files to group.
"""
attachFilesToGroup(
"""
Parameters for AttachFilesToGroup
"""
input: AttachFilesToGroupInput!
): AttachFilesToGroupPayload

"""
Attaches files to project.
"""
Expand Down
3 changes: 2 additions & 1 deletion app/graphql/types/attachment_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ def attachment_url
end

def self.authorized?(object, context)
attachable = object.attachable.is_a?(Group) ? object.attachable : object.attachable.project
super && (context[:attachments_preauthorized] ||
allowed_to?(
:read?,
object.attachable.project,
attachable,
context: { user: context[:current_user], token: context[:token] }
))
end
Expand Down
6 changes: 6 additions & 0 deletions app/graphql/types/group_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ class GroupType < Types::NamespaceType
implements GraphQL::Types::Relay::Node
description 'A group'

field :attachments,
AttachmentType.connection_type,
null: true,
description: 'Attachments on the group',
resolver: Resolvers::GroupAttachmentsResolver

field :descendant_groups, connection_type,
null: true,
description: 'List of descendant groups of this group.',
Expand Down
1 change: 1 addition & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Types
class MutationType < Types::BaseObject
description 'The mutation root of this schema'

field :attach_files_to_group, mutation: Mutations::AttachFilesToGroup # rubocop:disable GraphQL/FieldDescription
field :attach_files_to_project, mutation: Mutations::AttachFilesToProject # rubocop:disable GraphQL/FieldDescription
field :attach_files_to_sample, mutation: Mutations::AttachFilesToSample # rubocop:disable GraphQL/FieldDescription, GraphQL/ExtractType
field :create_direct_upload, mutation: Mutations::CreateDirectUpload # rubocop:disable GraphQL/FieldDescription
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/groups.yml
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,11 @@ empty_group:
description: Group without any Samples
owner_id: <%= ActiveRecord::FixtureSet.identify(:empty_doe, :uuid) %>
puid: INXT_GRP_AAAAAAAABHI

group_jeff:
<<: *DEFAULTS
name: Group Jeff
path: group-jeff
description: Group Jeff description
owner_id: <%= ActiveRecord::FixtureSet.identify(:jeff_doe, :uuid) %>
puid: INXT_GRP_AAAAAAJEFF
13 changes: 13 additions & 0 deletions test/fixtures/members.yml
Original file line number Diff line number Diff line change
Expand Up @@ -555,3 +555,16 @@ empty_group_member_empty_doe:
created_by_id: <%= ActiveRecord::FixtureSet.identify(:empty_doe, :uuid) %>
namespace_id: <%= ActiveRecord::FixtureSet.identify(:empty_group, :uuid) %>
access_level: <%= Member::AccessLevel::OWNER %>

group_jeff_member_jeff_doe:
user_id: <%= ActiveRecord::FixtureSet.identify(:jeff_doe, :uuid) %>
created_by_id: <%= ActiveRecord::FixtureSet.identify(:jeff_doe, :uuid) %>
namespace_id: <%= ActiveRecord::FixtureSet.identify(:group_jeff, :uuid) %>
access_level: <%= Member::AccessLevel::OWNER %>

group_jeff_member_user_bot_account:
user_id: <%= ActiveRecord::FixtureSet.identify(:groupJeff_bot, :uuid) %>
created_by_id: <%= ActiveRecord::FixtureSet.identify(:jeff_doe, :uuid) %>
namespace_id: <%= ActiveRecord::FixtureSet.identify(:group_jeff, :uuid) %>
access_level: <%= Member::AccessLevel::UPLOADER %>
expires_at: <%= 20.days.from_now %>
4 changes: 4 additions & 0 deletions test/fixtures/namespace_bots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ invalid_user_bot:
projectJeff_bot:
namespace_id: <%= ActiveRecord::FixtureSet.identify(:project_jeff_namespace, :uuid) %>
user_id: <%= ActiveRecord::FixtureSet.identify(:projectJeff_bot, :uuid) %>

groupJeff_bot:
namespace_id: <%= ActiveRecord::FixtureSet.identify(:group_jeff, :uuid) %>
user_id: <%= ActiveRecord::FixtureSet.identify(:groupJeff_bot, :uuid) %>
11 changes: 11 additions & 0 deletions test/fixtures/personal_access_tokens.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ projectJeff_bot_account_valid_pat:
token_digest: <%= Devise.token_generator.digest(PersonalAccessToken, :token_digest, "FX3wwK3sWCjn1xdCyZtA") %>
last_used_at: nil

groupJeff_bot_account_valid_pat:
user_id: <%= ActiveRecord::FixtureSet.identify(:groupJeff_bot, :uuid) %>
scopes:
- read_api
- api
name: Valid PAT1 %>
revoked: false
expires_at: <%= 10.days.from_now.to_date %>
token_digest: <%= Devise.token_generator.digest(PersonalAccessToken, :token_digest, "FX3wwK3sWCjn1xdCyZtB") %>
last_used_at: nil

user_bot_account0_expired_pat:
user_id: <%= ActiveRecord::FixtureSet.identify(:user_bot_account0, :uuid) %>
scopes:
Expand Down
7 changes: 7 additions & 0 deletions test/fixtures/users.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ projectJeff_bot:
last_name: Bot
user_type: <%= User.user_types[:project_bot] %>

groupJeff_bot:
<<: *DEFAULTS
email: <%= "inxt_grp_aaaaaajeff_001@localhost" %>
first_name: INXT_GRP_AAAAAAJEFF
last_name: Bot
user_type: <%= User.user_types[:group_bot] %>

empty_doe:
<<: *DEFAULTS
email: <%= "empty_doe@localhost" %>
Expand Down
Loading

0 comments on commit 622a807

Please sign in to comment.