diff options
author | Bob Van Landuyt <bob@vanlanduyt.co> | 2018-05-23 10:55:14 +0300 |
---|---|---|
committer | Bob Van Landuyt <bob@vanlanduyt.co> | 2018-06-06 11:58:54 +0300 |
commit | 9b65d4bb417fb4939289eab94487c894f0a62db6 (patch) | |
tree | 1f97b9a1bd0d722a3c3ff4e89ec13bdb7a3aec00 /app/graphql | |
parent | c443133e779c4c508b9c6429dd4ba623d64f03f1 (diff) |
Initial setup GraphQL using graphql-ruby 1.8
- All definitions have been replaced by classes:
http://graphql-ruby.org/schema/class_based_api.html
- Authorization & Presentation have been refactored to work in the
class based system
- Loaders have been replaced by resolvers
- Times are now coersed as ISO 8601
Diffstat (limited to 'app/graphql')
21 files changed, 218 insertions, 182 deletions
diff --git a/app/graphql/functions/base_function.rb b/app/graphql/functions/base_function.rb new file mode 100644 index 00000000000..42fb8f99acc --- /dev/null +++ b/app/graphql/functions/base_function.rb @@ -0,0 +1,4 @@ +module Functions + class BaseFunction < GraphQL::Function + end +end diff --git a/app/graphql/functions/echo.rb b/app/graphql/functions/echo.rb new file mode 100644 index 00000000000..e5bf109b8d7 --- /dev/null +++ b/app/graphql/functions/echo.rb @@ -0,0 +1,13 @@ +module Functions + class Echo < BaseFunction + argument :text, GraphQL::STRING_TYPE + + description "Testing endpoint to validate the API with" + + def call(obj, args, ctx) + username = ctx[:current_user]&.username + + "#{username.inspect} says: #{args[:text]}" + end + end +end diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb index ac75c4bf2e3..de4fc1d8e32 100644 --- a/app/graphql/gitlab_schema.rb +++ b/app/graphql/gitlab_schema.rb @@ -1,12 +1,8 @@ -Gitlab::Graphql::Authorize.register! -Gitlab::Graphql::Present.register! - -GitlabSchema = GraphQL::Schema.define do +class GitlabSchema < GraphQL::Schema use BatchLoader::GraphQL - - enable_preloading - enable_authorization - enable_presenting + use Gitlab::Graphql::Authorize + use Gitlab::Graphql::Present query(Types::QueryType) + # mutation(Types::MutationType) end diff --git a/app/graphql/loaders/base_loader.rb b/app/graphql/loaders/base_loader.rb deleted file mode 100644 index aad435ea09b..00000000000 --- a/app/graphql/loaders/base_loader.rb +++ /dev/null @@ -1,15 +0,0 @@ -# Helper methods for all loaders -module Loaders::BaseLoader - extend ActiveSupport::Concern - - class_methods do - # Convert a class method into a resolver proc. The method should follow the - # (obj, args, ctx) calling convention - def [](sym) - resolver = method(sym) - raise ArgumentError.new("#{self}.#{sym} is not a resolver") unless resolver.arity == 3 - - resolver - end - end -end diff --git a/app/graphql/loaders/iid_loader.rb b/app/graphql/loaders/iid_loader.rb deleted file mode 100644 index f9827765a92..00000000000 --- a/app/graphql/loaders/iid_loader.rb +++ /dev/null @@ -1,23 +0,0 @@ -class Loaders::IidLoader - include Loaders::BaseLoader - - class << self - def merge_request(obj, args, ctx) - iid = args[:iid] - project = Loaders::FullPathLoader.project_by_full_path(args[:project]) - merge_request_by_project_and_iid(project, iid) - end - - def merge_request_by_project_and_iid(project_loader, iid) - project_id = project_loader.__sync&.id - - # IIDs are represented as the GraphQL `id` type, which is a string - BatchLoader.for(iid.to_s).batch(key: "merge_request:target_project:#{project_id}:iid") do |iids, loader| - if project_id - results = MergeRequest.where(target_project_id: project_id, iid: iids) - results.each { |mr| loader.call(mr.iid.to_s, mr) } - end - end - end - end -end diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb new file mode 100644 index 00000000000..89b7f9dad6f --- /dev/null +++ b/app/graphql/resolvers/base_resolver.rb @@ -0,0 +1,4 @@ +module Resolvers + class BaseResolver < GraphQL::Schema::Resolver + end +end diff --git a/app/graphql/loaders/full_path_loader.rb b/app/graphql/resolvers/full_path_resolver.rb index 27ada90b82a..4eb28aaed6c 100644 --- a/app/graphql/loaders/full_path_loader.rb +++ b/app/graphql/resolvers/full_path_resolver.rb @@ -1,13 +1,11 @@ -module Loaders::FullPathLoader - include Loaders::BaseLoader +module Resolvers + module FullPathResolver + extend ActiveSupport::Concern - class << self - def project(obj, args, ctx) - project_by_full_path(args[:full_path]) - end - - def project_by_full_path(full_path) - model_by_full_path(Project, full_path) + prepended do + argument :full_path, GraphQL::ID_TYPE, + required: true, + description: 'The full path of the project or namespace, e.g., "gitlab-org/gitlab-ce"' end def model_by_full_path(model, full_path) diff --git a/app/graphql/resolvers/merge_request_resolver.rb b/app/graphql/resolvers/merge_request_resolver.rb new file mode 100644 index 00000000000..b1857ab09f7 --- /dev/null +++ b/app/graphql/resolvers/merge_request_resolver.rb @@ -0,0 +1,21 @@ +module Resolvers + class MergeRequestResolver < BaseResolver + prepend FullPathResolver + + type Types::ProjectType, null: true + + argument :iid, GraphQL::ID_TYPE, + required: true, + description: 'The IID of the merge request, e.g., "1"' + + def resolve(full_path:, iid:) + project = model_by_full_path(Project, full_path) + return unless project.present? + + BatchLoader.for(iid.to_s).batch(key: project.id) do |iids, loader| + results = project.merge_requests.where(iid: iids) + results.each { |mr| loader.call(mr.iid.to_s, mr) } + end + end + end +end diff --git a/app/graphql/resolvers/project_resolver.rb b/app/graphql/resolvers/project_resolver.rb new file mode 100644 index 00000000000..ec115bad896 --- /dev/null +++ b/app/graphql/resolvers/project_resolver.rb @@ -0,0 +1,11 @@ +module Resolvers + class ProjectResolver < BaseResolver + prepend FullPathResolver + + type Types::ProjectType, null: true + + def resolve(full_path:) + model_by_full_path(Project, full_path) + end + end +end diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb new file mode 100644 index 00000000000..b45a845f74f --- /dev/null +++ b/app/graphql/types/base_enum.rb @@ -0,0 +1,4 @@ +module Types + class BaseEnum < GraphQL::Schema::Enum + end +end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb new file mode 100644 index 00000000000..c5740a334d7 --- /dev/null +++ b/app/graphql/types/base_field.rb @@ -0,0 +1,5 @@ +module Types + class BaseField < GraphQL::Schema::Field + prepend Gitlab::Graphql::Authorize + end +end diff --git a/app/graphql/types/base_input_object.rb b/app/graphql/types/base_input_object.rb new file mode 100644 index 00000000000..309e336e6c8 --- /dev/null +++ b/app/graphql/types/base_input_object.rb @@ -0,0 +1,4 @@ +module Types + class BaseInputObject < GraphQL::Schema::InputObject + end +end diff --git a/app/graphql/types/base_interface.rb b/app/graphql/types/base_interface.rb new file mode 100644 index 00000000000..69e72dc5808 --- /dev/null +++ b/app/graphql/types/base_interface.rb @@ -0,0 +1,5 @@ +module Types + module BaseInterface + include GraphQL::Schema::Interface + end +end diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb new file mode 100644 index 00000000000..e033ef96ce9 --- /dev/null +++ b/app/graphql/types/base_object.rb @@ -0,0 +1,7 @@ +module Types + class BaseObject < GraphQL::Schema::Object + prepend Gitlab::Graphql::Present + + field_class Types::BaseField + end +end diff --git a/app/graphql/types/base_scalar.rb b/app/graphql/types/base_scalar.rb new file mode 100644 index 00000000000..c0aa38be239 --- /dev/null +++ b/app/graphql/types/base_scalar.rb @@ -0,0 +1,4 @@ +module Types + class BaseScalar < GraphQL::Schema::Scalar + end +end diff --git a/app/graphql/types/base_union.rb b/app/graphql/types/base_union.rb new file mode 100644 index 00000000000..36337fc6ee5 --- /dev/null +++ b/app/graphql/types/base_union.rb @@ -0,0 +1,4 @@ +module Types + class BaseUnion < GraphQL::Schema::Union + end +end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index fc430ca03d5..d5d24952984 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -1,46 +1,47 @@ -Types::MergeRequestType = GraphQL::ObjectType.define do - present_using MergeRequestPresenter +module Types + class MergeRequestType < BaseObject + present_using MergeRequestPresenter - name 'MergeRequest' + graphql_name 'MergeRequest' - field :id, !types.ID - field :iid, !types.ID - field :title, !types.String - field :description, types.String - field :state, types.String - field :created_at, !Types::TimeType - field :updated_at, !Types::TimeType - field :source_project, Types::ProjectType - field :target_project, !Types::ProjectType - # Alias for target_project - field :project, !Types::ProjectType - field :project_id, !types.Int, property: :target_project_id - field :source_project_id, types.Int - field :target_project_id, !types.Int - field :source_branch, !types.String - field :target_branch, !types.String - field :work_in_progress, types.Boolean, property: :work_in_progress? - field :merge_when_pipeline_succeeds, types.Boolean - field :sha, types.String, property: :diff_head_sha - field :merge_commit_sha, types.String - field :user_notes_count, types.Int - field :should_remove_source_branch, types.Boolean, property: :should_remove_source_branch? - field :force_remove_source_branch, types.Boolean, property: :force_remove_source_branch? - field :merge_status, types.String - field :in_progress_merge_commit_sha, types.String - field :merge_error, types.String - field :allow_maintainer_to_push, types.Boolean - field :should_be_rebased, types.Boolean, property: :should_be_rebased? - field :rebase_commit_sha, types.String - field :rebase_in_progress, types.Boolean, property: :rebase_in_progress? - field :diff_head_sha, types.String - field :merge_commit_message, types.String - field :merge_ongoing, types.Boolean, property: :merge_ongoing? - field :work_in_progress, types.Boolean, property: :work_in_progress? - field :source_branch_exists, types.Boolean, property: :source_branch_exists? - field :mergeable_discussions_state, types.Boolean - field :web_url, types.String, property: :web_url - field :upvotes, types.Int - field :downvotes, types.Int - field :subscribed, types.Boolean, property: :subscribed? + field :id, GraphQL::ID_TYPE, null: false + field :iid, GraphQL::ID_TYPE, null: false + field :title, GraphQL::STRING_TYPE, null: false + field :description, GraphQL::STRING_TYPE, null: true + field :state, GraphQL::STRING_TYPE, null: true + field :created_at, Types::TimeType, null: false + field :updated_at, Types::TimeType, null: false + field :source_project, Types::ProjectType, null: true + field :target_project, Types::ProjectType, null: false + # Alias for target_project + field :project, Types::ProjectType, null: false + field :project_id, GraphQL::INT_TYPE, null: false, method: :target_project_id + field :source_project_id, GraphQL::INT_TYPE, null: true + field :target_project_id, GraphQL::INT_TYPE, null: false + field :source_branch, GraphQL::STRING_TYPE, null: false + field :target_branch, GraphQL::STRING_TYPE, null: false + field :work_in_progress, GraphQL::BOOLEAN_TYPE, method: :work_in_progress?, null: false + field :merge_when_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true + field :diff_head_sha, GraphQL::STRING_TYPE, null: true + field :merge_commit_sha, GraphQL::STRING_TYPE, null: true + field :user_notes_count, GraphQL::INT_TYPE, null: true + field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true + field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true + field :merge_status, GraphQL::STRING_TYPE, null: true + field :in_progress_merge_commit_sha, GraphQL::STRING_TYPE, null: true + field :merge_error, GraphQL::STRING_TYPE, null: true + field :allow_collaboration, GraphQL::BOOLEAN_TYPE, null: true + field :should_be_rebased, GraphQL::BOOLEAN_TYPE, method: :should_be_rebased?, null: false + field :rebase_commit_sha, GraphQL::STRING_TYPE, null: true + field :rebase_in_progress, GraphQL::BOOLEAN_TYPE, method: :rebase_in_progress?, null: false + field :diff_head_sha, GraphQL::STRING_TYPE, null: true + field :merge_commit_message, GraphQL::STRING_TYPE, null: true + field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false + field :source_branch_exists, GraphQL::BOOLEAN_TYPE, method: :source_branch_exists?, null: false + field :mergeable_discussions_state, GraphQL::BOOLEAN_TYPE, null: true + field :web_url, GraphQL::STRING_TYPE, null: true + field :upvotes, GraphQL::INT_TYPE, null: false + field :downvotes, GraphQL::INT_TYPE, null: false + field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false + end end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index c5061f10239..06ed91c1658 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -1,5 +1,7 @@ -Types::MutationType = GraphQL::ObjectType.define do - name "Mutation" +module Types + class MutationType < BaseObject + graphql_name "Mutation" - # TODO: Add Mutations as fields + # TODO: Add Mutations as fields + end end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 593c1b67830..9e885d5845a 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -1,63 +1,65 @@ -Types::ProjectType = GraphQL::ObjectType.define do - name 'Project' +module Types + class ProjectType < BaseObject + graphql_name 'Project' - field :id, !types.ID + field :id, GraphQL::ID_TYPE, null: false - field :full_path, !types.ID - field :path, !types.String + field :full_path, GraphQL::ID_TYPE, null: false + field :path, GraphQL::STRING_TYPE, null: false - field :name_with_namespace, !types.String - field :name, !types.String + field :name_with_namespace, GraphQL::STRING_TYPE, null: false + field :name, GraphQL::STRING_TYPE, null: false - field :description, types.String + field :description, GraphQL::STRING_TYPE, null: true - field :default_branch, types.String - field :tag_list, types.String + field :default_branch, GraphQL::STRING_TYPE, null: true + field :tag_list, GraphQL::STRING_TYPE, null: true - field :ssh_url_to_repo, types.String - field :http_url_to_repo, types.String - field :web_url, types.String + field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true + field :http_url_to_repo, GraphQL::STRING_TYPE, null: true + field :web_url, GraphQL::STRING_TYPE, null: true - field :star_count, !types.Int - field :forks_count, !types.Int + field :star_count, GraphQL::INT_TYPE, null: false + field :forks_count, GraphQL::INT_TYPE, null: false - field :created_at, Types::TimeType - field :last_activity_at, Types::TimeType + field :created_at, Types::TimeType, null: true + field :last_activity_at, Types::TimeType, null: true - field :archived, types.Boolean + field :archived, GraphQL::BOOLEAN_TYPE, null: true - field :visibility, types.String + field :visibility, GraphQL::STRING_TYPE, null: true - field :container_registry_enabled, types.Boolean - field :shared_runners_enabled, types.Boolean - field :lfs_enabled, types.Boolean - field :ff_only_enabled, types.Boolean, property: :merge_requests_ff_only_enabled + field :container_registry_enabled, GraphQL::BOOLEAN_TYPE, null: true + field :shared_runners_enabled, GraphQL::BOOLEAN_TYPE, null: true + field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true + field :merge_requests_ff_only_enabled, GraphQL::BOOLEAN_TYPE, null: true - field :avatar_url, types.String do - resolve ->(project, args, ctx) { project.avatar_url(only_path: false) } - end + field :avatar_url, GraphQL::STRING_TYPE, null: true, resolve: -> (project, args, ctx) do + project.avatar_url(only_path: false) + end - %i[issues merge_requests wiki snippets].each do |feature| - field "#{feature}_enabled", types.Boolean do - resolve ->(project, args, ctx) { project.feature_available?(feature, ctx[:current_user]) } + %i[issues merge_requests wiki snippets].each do |feature| + field "#{feature}_enabled", GraphQL::BOOLEAN_TYPE, null: true, resolve: -> (project, args, ctx) do + project.feature_available?(feature, ctx[:current_user]) + end end - end - field :jobs_enabled, types.Boolean do - resolve ->(project, args, ctx) { project.feature_available?(:builds, ctx[:current_user]) } - end + field :jobs_enabled, GraphQL::BOOLEAN_TYPE, null: true, resolve: -> (project, args, ctx) do + project.feature_available?(:builds, ctx[:current_user]) + end - field :public_jobs, types.Boolean, property: :public_builds + field :public_jobs, GraphQL::BOOLEAN_TYPE, method: :public_builds, null: true - field :open_issues_count, types.Int do - resolve ->(project, args, ctx) { project.open_issues_count if project.feature_available?(:issues, ctx[:current_user]) } - end + field :open_issues_count, GraphQL::INT_TYPE, null: true, resolve: -> (project, args, ctx) do + project.open_issues_count if project.feature_available?(:issues, ctx[:current_user]) + end - field :import_status, types.String - field :ci_config_path, types.String + field :import_status, GraphQL::STRING_TYPE, null: true + field :ci_config_path, GraphQL::STRING_TYPE, null: true - field :only_allow_merge_if_pipeline_succeeds, types.Boolean - field :request_access_enabled, types.Boolean - field :only_allow_merge_if_all_discussions_are_resolved, types.Boolean - field :printing_merge_request_link_enabled, types.Boolean + field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true + field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true + field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true + field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true + end end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 029bbd098ad..be79c78bf67 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -1,38 +1,21 @@ -Types::QueryType = GraphQL::ObjectType.define do - name 'Query' - - field :project, Types::ProjectType do - argument :full_path, !types.ID do - description 'The full path of the project, e.g., "gitlab-org/gitlab-ce"' - end - - authorize :read_project - - resolve Loaders::FullPathLoader[:project] - end - - field :merge_request, Types::MergeRequestType do - argument :project, !types.ID do - description 'The full path of the target project, e.g., "gitlab-org/gitlab-ce"' +module Types + class QueryType < BaseObject + graphql_name 'Query' + + field :project, Types::ProjectType, + null: true, + resolver: Resolvers::ProjectResolver, + description: "Find a project" do + authorize :read_project end - argument :iid, !types.ID do - description 'The IID of the merge request, e.g., "1"' + field :merge_request, Types::MergeRequestType, + null: true, + resolver: Resolvers::MergeRequestResolver, + description: "Find a merge request" do + authorize :read_merge_request end - authorize :read_merge_request - - resolve Loaders::IidLoader[:merge_request] - end - - # Testing endpoint to validate the API with - field :echo, types.String do - argument :text, types.String - - resolve -> (obj, args, ctx) do - username = ctx[:current_user]&.username - - "#{username.inspect} says: #{args[:text]}" - end + field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new end end diff --git a/app/graphql/types/time_type.rb b/app/graphql/types/time_type.rb index fb717eb3dc7..2333d82ad1e 100644 --- a/app/graphql/types/time_type.rb +++ b/app/graphql/types/time_type.rb @@ -1,8 +1,14 @@ -# Taken from http://www.rubydoc.info/github/rmosolgo/graphql-ruby/GraphQL/ScalarType -Types::TimeType = GraphQL::ScalarType.define do - name 'Time' - description 'Time since epoch in fractional seconds' +module Types + class TimeType < BaseScalar + graphql_name 'Time' + description 'Time represented in ISO 8601' - coerce_input ->(value, ctx) { Time.at(Float(value)) } - coerce_result ->(value, ctx) { value.to_f } + def self.coerce_input(value, ctx) + Time.parse(value) + end + + def self.coerce_result(value, ctx) + value.iso8601 + end + end end |