diff options
Diffstat (limited to 'app/graphql/types')
39 files changed, 404 insertions, 71 deletions
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index b4cd54b1332..6aee9a5c052 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -53,6 +53,30 @@ module Types field_authorized?(object, ctx) && resolver_authorized?(object, ctx) end + # This gets called from the gem's `calculate_complexity` method, allowing us + # to ensure our complexity calculation is used even for connections. + # This code is actually a copy of the default case in `calculate_complexity` + # in `lib/graphql/schema/field.rb` + # (https://github.com/rmosolgo/graphql-ruby/blob/master/lib/graphql/schema/field.rb) + def complexity_for(child_complexity:, query:, lookahead:) + defined_complexity = complexity + + case defined_complexity + when Proc + arguments = query.arguments_for(lookahead.ast_nodes.first, self) + + if arguments.respond_to?(:keyword_arguments) + defined_complexity.call(query.context, arguments.keyword_arguments, child_complexity) + else + child_complexity + end + when Numeric + defined_complexity + child_complexity + else + raise("Invalid complexity: #{defined_complexity.inspect} on #{path} (#{inspect})") + end + end + def base_complexity complexity = DEFAULT_COMPLEXITY complexity += 1 if calls_gitaly? @@ -150,10 +174,9 @@ module Types def connection_complexity_multiplier(ctx, args) # Resolvers may add extra complexity depending on number of items being loaded. - field_defn = to_graphql - return 0 unless field_defn.connection? + return 0 unless connection? - page_size = field_defn.connection_max_page_size || ctx.schema.default_max_page_size + page_size = max_page_size || ctx.schema.default_max_page_size limit_value = [args[:first], args[:last], page_size].compact.min multiplier = resolver&.try(:complexity_multiplier, args).to_f limit_value * multiplier diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb index e3413551a3f..3fab040cc0b 100644 --- a/app/graphql/types/ci/detailed_status_type.rb +++ b/app/graphql/types/ci/detailed_status_type.rb @@ -33,7 +33,7 @@ module Types method: :status_tooltip def id(parent:) - "#{object.id}-#{parent.object.object.id}" + "#{object.id}-#{parent.id}" end def action diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index f25fc56a588..b20a671179b 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -7,7 +7,7 @@ module Types class JobType < BaseObject graphql_name 'CiJob' - connection_type_class(Types::CountableConnectionType) + connection_type_class(Types::LimitedCountableConnectionType) expose_permissions Types::PermissionTypes::Ci::Job diff --git a/app/graphql/types/ci/pipeline_merge_request_event_type_enum.rb b/app/graphql/types/ci/pipeline_merge_request_event_type_enum.rb new file mode 100644 index 00000000000..a1236b8f2c1 --- /dev/null +++ b/app/graphql/types/ci/pipeline_merge_request_event_type_enum.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + module Ci + class PipelineMergeRequestEventTypeEnum < BaseEnum + graphql_name 'PipelineMergeRequestEventType' + description 'Event type of the pipeline associated with a merge request' + + value 'MERGED_RESULT', + 'Pipeline run on the changes from the source branch combined with the target branch.', + value: :merged_result + value 'DETACHED', + 'Pipeline run on the changes in the merge request source branch.', + value: :detached + end + end +end + +Types::Ci::PipelineMergeRequestEventTypeEnum.prepend_mod diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb index 81afc7f0f42..60418fec6c5 100644 --- a/app/graphql/types/ci/pipeline_type.rb +++ b/app/graphql/types/ci/pipeline_type.rb @@ -175,6 +175,9 @@ module Types field :warning_messages, [Types::Ci::PipelineMessageType], null: true, description: 'Pipeline warning messages.' + field :merge_request_event_type, Types::Ci::PipelineMergeRequestEventTypeEnum, null: true, + description: "Event type of the pipeline associated with a merge request." + def detailed_status object.detailed_status(current_user) end diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb index 6f957d2511f..949e216a982 100644 --- a/app/graphql/types/ci/runner_type.rb +++ b/app/graphql/types/ci/runner_type.rb @@ -85,6 +85,15 @@ module Types method: :token_expires_at field :version, GraphQL::Types::String, null: true, description: 'Version of the runner.' + field :owner_project, ::Types::ProjectType, null: true, + description: 'Project that owns the runner. For project runners only.', + resolver: ::Resolvers::Ci::RunnerOwnerProjectResolver + + markdown_field :maintenance_note_html, null: true + + def maintenance_note_html_resolver + ::MarkupHelper.markdown(object.maintenance_note, context.to_h.dup) + end def job_count # We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT @@ -136,16 +145,22 @@ module Types # rubocop: disable CodeReuse/ActiveRecord def batched_owners(runner_assoc_type, assoc_type, key, column_name) - BatchLoader::GraphQL.for(runner.id).batch(key: key) do |runner_ids, loader, args| - runner_and_owner_ids = runner_assoc_type.where(runner_id: runner_ids).pluck(:runner_id, column_name) - - owner_ids_by_runner_id = runner_and_owner_ids.group_by(&:first).transform_values { |v| v.pluck(1) } - owner_ids = runner_and_owner_ids.pluck(1).uniq - + BatchLoader::GraphQL.for(runner.id).batch(key: key) do |runner_ids, loader| + plucked_runner_and_owner_ids = runner_assoc_type + .select(:runner_id, column_name) + .where(runner_id: runner_ids) + .pluck(:runner_id, column_name) + # In plucked_runner_and_owner_ids, first() represents the runner ID, and second() the owner ID, + # so let's group the owner IDs by runner ID + runner_owner_ids_by_runner_id = plucked_runner_and_owner_ids + .group_by(&:first) + .transform_values { |runner_and_owner_id| runner_and_owner_id.map(&:second) } + + owner_ids = runner_owner_ids_by_runner_id.values.flatten.uniq owners = assoc_type.where(id: owner_ids).index_by(&:id) runner_ids.each do |runner_id| - loader.call(runner_id, owner_ids_by_runner_id[runner_id]&.map { |owner_id| owners[owner_id] } || []) + loader.call(runner_id, runner_owner_ids_by_runner_id[runner_id]&.map { |owner_id| owners[owner_id] } || []) end end end diff --git a/app/graphql/types/ci/runner_web_url_edge.rb b/app/graphql/types/ci/runner_web_url_edge.rb index 035d75c22c6..7dfcd1f3510 100644 --- a/app/graphql/types/ci/runner_web_url_edge.rb +++ b/app/graphql/types/ci/runner_web_url_edge.rb @@ -4,8 +4,6 @@ module Types module Ci # rubocop: disable Graphql/AuthorizeTypes class RunnerWebUrlEdge < ::Types::BaseEdge - include FindClosest - field :edit_url, GraphQL::Types::String, null: true, description: 'Web URL of the runner edit page. The value depends on where you put this field in the query. You can use it for projects or groups.', extras: [:parent] @@ -19,19 +17,18 @@ module Types @runner = node.node end + # here parent is a Keyset::Connection def edit_url(parent:) - runner_url(parent: parent, url_type: :edit_url) + runner_url(owner: parent.parent, url_type: :edit_url) end def web_url(parent:) - runner_url(parent: parent, url_type: :default) + runner_url(owner: parent.parent, url_type: :default) end private - def runner_url(parent:, url_type: :default) - owner = closest_parent([::Types::ProjectType, ::Types::GroupType], parent) - + def runner_url(owner:, url_type: :default) # Only ::Group is supported at the moment, future iterations will include ::Project. # See https://gitlab.com/gitlab-org/gitlab/-/issues/16338 case owner diff --git a/app/graphql/types/ci/status_action_type.rb b/app/graphql/types/ci/status_action_type.rb index 26ca3c1438a..c0f61cf49f2 100644 --- a/app/graphql/types/ci/status_action_type.rb +++ b/app/graphql/types/ci/status_action_type.rb @@ -21,7 +21,8 @@ module Types description: 'Title for the action, for example: Retry.' def id(parent:) - "#{parent.parent.object.object.class.name}-#{parent.object.object.id}" + # parent is a SimpleDelegator + "#{parent.subject.class.name}-#{parent.id}" end def action_method diff --git a/app/graphql/types/concerns/find_closest.rb b/app/graphql/types/concerns/find_closest.rb deleted file mode 100644 index 3064db19ea0..00000000000 --- a/app/graphql/types/concerns/find_closest.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module FindClosest - # Find the closest node which has any of the given types above this node, and return the domain object - def closest_parent(types, parent) - while parent - - if types.any? {|type| parent.object.instance_of? type} - return parent.object.object - else - parent = parent.try(:parent) - end - end - end -end diff --git a/app/graphql/types/customer_relations/contact_state_enum.rb b/app/graphql/types/customer_relations/contact_state_enum.rb new file mode 100644 index 00000000000..445d2a41401 --- /dev/null +++ b/app/graphql/types/customer_relations/contact_state_enum.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Types + module CustomerRelations + class ContactStateEnum < BaseEnum + graphql_name 'CustomerRelationsContactState' + + value 'active', + description: "Active contact.", + value: :active + + value 'inactive', + description: "Inactive contact.", + value: :inactive + end + end +end diff --git a/app/graphql/types/customer_relations/organization_state_enum.rb b/app/graphql/types/customer_relations/organization_state_enum.rb new file mode 100644 index 00000000000..ecdd7d092ad --- /dev/null +++ b/app/graphql/types/customer_relations/organization_state_enum.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Types + module CustomerRelations + class OrganizationStateEnum < BaseEnum + graphql_name 'CustomerRelationsOrganizationState' + + value 'active', + description: "Active organization.", + value: :active + + value 'inactive', + description: "Inactive organization.", + value: :inactive + end + end +end diff --git a/app/graphql/types/global_id_type.rb b/app/graphql/types/global_id_type.rb index 6a924c13a3c..145a5a22460 100644 --- a/app/graphql/types/global_id_type.rb +++ b/app/graphql/types/global_id_type.rb @@ -84,7 +84,8 @@ module Types end define_singleton_method(:suitable?) do |gid| - next false if gid.nil? + # an argument can be nil, so allow it here + next true if gid.nil? gid.model_name.safe_constantize.present? && gid.model_class.ancestors.include?(model_class) diff --git a/app/graphql/types/group_member_type.rb b/app/graphql/types/group_member_type.rb index 18242f7b8b1..c4582f31bec 100644 --- a/app/graphql/types/group_member_type.rb +++ b/app/graphql/types/group_member_type.rb @@ -15,7 +15,7 @@ module Types field :notification_email, resolver: Resolvers::GroupMembers::NotificationEmailResolver, - description: "Group notification email for User. Only availble for admins." + description: "Group notification email for User. Only available for admins." def group Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.source_id).find diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index a94cd6fad20..49971d52a30 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -201,11 +201,13 @@ module Types field :organizations, Types::CustomerRelations::OrganizationType.connection_type, null: true, - description: "Find organizations of this group." + description: "Find organizations of this group.", + resolver: Resolvers::Crm::OrganizationsResolver field :contacts, Types::CustomerRelations::ContactType.connection_type, null: true, - description: "Find contacts of this group." + description: "Find contacts of this group.", + resolver: Resolvers::Crm::ContactsResolver field :work_item_types, Types::WorkItems::TypeType.connection_type, resolver: Resolvers::WorkItems::TypesResolver, diff --git a/app/graphql/types/issue_sort_enum.rb b/app/graphql/types/issue_sort_enum.rb index db51e491d4e..7dced3c8e00 100644 --- a/app/graphql/types/issue_sort_enum.rb +++ b/app/graphql/types/issue_sort_enum.rb @@ -14,8 +14,10 @@ module Types value 'TITLE_DESC', 'Title by descending order.', value: :title_desc value 'POPULARITY_ASC', 'Number of upvotes (awarded "thumbs up" emoji) by ascending order.', value: :popularity_asc value 'POPULARITY_DESC', 'Number of upvotes (awarded "thumbs up" emoji) by descending order.', value: :popularity_desc - value 'ESCALATION_STATUS_ASC', 'Status from triggered to resolved. Defaults to `CREATED_DESC` if `incident_escalations` feature flag is disabled.', value: :escalation_status_asc - value 'ESCALATION_STATUS_DESC', 'Status from resolved to triggered. Defaults to `CREATED_DESC` if `incident_escalations` feature flag is disabled.', value: :escalation_status_desc + value 'ESCALATION_STATUS_ASC', 'Status from triggered to resolved.', value: :escalation_status_asc + value 'ESCALATION_STATUS_DESC', 'Status from resolved to triggered.', value: :escalation_status_desc + value 'CLOSED_AT_ASC', 'Closed time by ascending order.', value: :closed_at_asc + value 'CLOSED_AT_DESC', 'Closed time by descending order.', value: :closed_at_desc end end diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index c83200bd614..58729b34fc7 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -127,6 +127,9 @@ module Types field :moved_to, Types::IssueType, null: true, description: 'Updated Issue after it got moved to another project.' + field :closed_as_duplicate_of, Types::IssueType, null: true, + description: 'Issue this issue was closed as a duplicate of.' + field :create_note_email, GraphQL::Types::String, null: true, description: 'User specific email address for the issue.' @@ -161,6 +164,10 @@ module Types Gitlab::Graphql::Loaders::BatchModelLoader.new(Issue, object.moved_to_id).find end + def closed_as_duplicate_of + Gitlab::Graphql::Loaders::BatchModelLoader.new(Issue, object.duplicated_to_id).find + end + def discussion_locked !!object.discussion_locked end diff --git a/app/graphql/types/limited_countable_connection_type.rb b/app/graphql/types/limited_countable_connection_type.rb new file mode 100644 index 00000000000..f0698222ea3 --- /dev/null +++ b/app/graphql/types/limited_countable_connection_type.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes + class LimitedCountableConnectionType < GraphQL::Types::Relay::BaseConnection + COUNT_LIMIT = 1000 + COUNT_DESCRIPTION = "Limited count of collection. Returns limit + 1 for counts greater than the limit." + + field :count, GraphQL::Types::Int, null: false, description: COUNT_DESCRIPTION do + argument :limit, GraphQL::Types::Int, + required: false, default_value: COUNT_LIMIT, + validates: { numericality: { greater_than: 0, less_than_or_equal_to: COUNT_LIMIT } }, + description: "Limit value to be applied to the count query. Default is 1000." + end + + def count(limit:) + relation = object.items + + if relation.respond_to?(:page) + relation.page.total_count_with_limit(:all, limit: limit) + else + [relation.size, limit.next].min + end + end + end +end diff --git a/app/graphql/types/merge_requests/interacts_with_merge_request.rb b/app/graphql/types/merge_requests/interacts_with_merge_request.rb index 15621ef1472..bef2d39dc5c 100644 --- a/app/graphql/types/merge_requests/interacts_with_merge_request.rb +++ b/app/graphql/types/merge_requests/interacts_with_merge_request.rb @@ -5,8 +5,6 @@ module Types module InteractsWithMergeRequest extend ActiveSupport::Concern - include FindClosest - included do field :merge_request_interaction, type: ::Types::UserMergeRequestInteractionType, @@ -16,11 +14,9 @@ module Types end def merge_request_interaction(parent:, id: nil) - merge_request = closest_parent([::Types::MergeRequestType], parent) - - return unless merge_request - - Users::MergeRequestInteraction.new(user: object, merge_request: merge_request) + # need the connection parent if called from a connection node: + parent = parent.parent if parent.try(:field)&.connection? + Users::MergeRequestInteraction.new(user: object, merge_request: parent) end end end diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb index 18e4a5d33e3..7741fd723f0 100644 --- a/app/graphql/types/milestone_type.rb +++ b/app/graphql/types/milestone_type.rb @@ -59,6 +59,10 @@ module Types field :stats, Types::MilestoneStatsType, null: true, description: 'Milestone statistics.' + field :releases, ::Types::ReleaseType.connection_type, + null: true, + description: 'Releases associated with this milestone.' + def stats milestone end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 7d8ada82d40..8642957af02 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -136,12 +136,16 @@ module Types mount_mutation Mutations::UserPreferences::Update mount_mutation Mutations::Packages::Destroy mount_mutation Mutations::Packages::DestroyFile + mount_mutation Mutations::Packages::DestroyFiles + mount_mutation Mutations::Packages::Cleanup::Policy::Update mount_mutation Mutations::Echo - mount_mutation Mutations::WorkItems::Create - mount_mutation Mutations::WorkItems::CreateFromTask - mount_mutation Mutations::WorkItems::Delete - mount_mutation Mutations::WorkItems::DeleteTask - mount_mutation Mutations::WorkItems::Update + mount_mutation Mutations::WorkItems::Create, deprecated: { milestone: '15.1', reason: :alpha } + mount_mutation Mutations::WorkItems::CreateFromTask, deprecated: { milestone: '15.1', reason: :alpha } + mount_mutation Mutations::WorkItems::Delete, deprecated: { milestone: '15.1', reason: :alpha } + mount_mutation Mutations::WorkItems::DeleteTask, deprecated: { milestone: '15.1', reason: :alpha } + mount_mutation Mutations::WorkItems::Update, deprecated: { milestone: '15.1', reason: :alpha } + mount_mutation Mutations::WorkItems::UpdateWidgets, deprecated: { milestone: '15.1', reason: :alpha } + mount_mutation Mutations::WorkItems::UpdateTask, deprecated: { milestone: '15.1', reason: :alpha } mount_mutation Mutations::SavedReplies::Create mount_mutation Mutations::SavedReplies::Update mount_mutation Mutations::SavedReplies::Destroy diff --git a/app/graphql/types/packages/cleanup/keep_duplicated_package_files_enum.rb b/app/graphql/types/packages/cleanup/keep_duplicated_package_files_enum.rb new file mode 100644 index 00000000000..bf8d625a334 --- /dev/null +++ b/app/graphql/types/packages/cleanup/keep_duplicated_package_files_enum.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + module Packages + module Cleanup + class KeepDuplicatedPackageFilesEnum < BaseEnum + graphql_name 'PackagesCleanupKeepDuplicatedPackageFilesEnum' + + OPTIONS_MAPPING = { + 'all' => 'ALL_PACKAGE_FILES', + '1' => 'ONE_PACKAGE_FILE', + '10' => 'TEN_PACKAGE_FILES', + '20' => 'TWENTY_PACKAGE_FILES', + '30' => 'THIRTY_PACKAGE_FILES', + '40' => 'FORTY_PACKAGE_FILES', + '50' => 'FIFTY_PACKAGE_FILES' + }.freeze + + ::Packages::Cleanup::Policy::KEEP_N_DUPLICATED_PACKAGE_FILES_VALUES.each do |keep_value| + value OPTIONS_MAPPING[keep_value], value: keep_value, description: "Value to keep #{keep_value} package files" + end + end + end + end +end diff --git a/app/graphql/types/packages/cleanup/policy_type.rb b/app/graphql/types/packages/cleanup/policy_type.rb new file mode 100644 index 00000000000..f08aace7df9 --- /dev/null +++ b/app/graphql/types/packages/cleanup/policy_type.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Types + module Packages + module Cleanup + class PolicyType < ::Types::BaseObject + graphql_name 'PackagesCleanupPolicy' + description 'A packages cleanup policy designed to keep only packages and packages assets that matter most' + + authorize :admin_package + + field :keep_n_duplicated_package_files, + Types::Packages::Cleanup::KeepDuplicatedPackageFilesEnum, + null: false, + description: 'Number of duplicated package files to retain.' + field :next_run_at, + Types::TimeType, + null: true, + description: 'Next time that this packages cleanup policy will be executed.' + end + end + end +end diff --git a/app/graphql/types/permission_types/base_permission_type.rb b/app/graphql/types/permission_types/base_permission_type.rb index a2cefb872c9..07e6e7a55d6 100644 --- a/app/graphql/types/permission_types/base_permission_type.rb +++ b/app/graphql/types/permission_types/base_permission_type.rb @@ -12,11 +12,7 @@ module Types end def self.ability_field(ability, **kword_args) - unless resolving_keywords?(kword_args) - kword_args[:resolve] ||= -> (object, args, context) do - can?(context[:current_user], ability, object, args.to_h) - end - end + define_field_resolver_method(ability) unless resolving_keywords?(kword_args) permission_field(ability, **kword_args) end @@ -31,6 +27,14 @@ module Types field(**kword_args) # rubocop:disable Graphql/Descriptions end + def self.define_field_resolver_method(ability) + unless self.respond_to?(ability) + define_method ability.to_sym do |*args| + Ability.allowed?(context[:current_user], ability, object, args.to_h) + end + end + end + def self.resolving_keywords?(arguments) RESOLVING_KEYWORDS.intersect?(arguments.keys.to_set) end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index f1de8e985b3..603d5ead540 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -4,6 +4,8 @@ module Types class ProjectType < BaseObject graphql_name 'Project' + connection_type_class(Types::CountableConnectionType) + authorize :read_project expose_permissions Types::PermissionTypes::Project @@ -142,6 +144,14 @@ module Types extras: [:lookahead], resolver: Resolvers::IssuesResolver + field :work_items, + Types::WorkItemType.connection_type, + null: true, + deprecated: { milestone: '15.1', reason: :alpha }, + description: 'Work items of the project.', + extras: [:lookahead], + resolver: Resolvers::WorkItemsResolver + field :issue_status_counts, Types::IssueStatusCountsType, null: true, @@ -179,6 +189,11 @@ module Types description: 'Packages of the project.', resolver: Resolvers::ProjectPackagesResolver + field :packages_cleanup_policy, + Types::Packages::Cleanup::PolicyType, + null: true, + description: 'Packages cleanup policy for the project.' + field :jobs, type: Types::Ci::JobType.connection_type, null: true, diff --git a/app/graphql/types/query_complexity_type.rb b/app/graphql/types/query_complexity_type.rb index 13b618cf5ce..ddcf448c64a 100644 --- a/app/graphql/types/query_complexity_type.rb +++ b/app/graphql/types/query_complexity_type.rb @@ -5,7 +5,7 @@ module Types class QueryComplexityType < ::Types::BaseObject graphql_name 'QueryComplexity' - ANALYZER = GraphQL::Analysis::QueryComplexity.new { |_query, complexity| complexity } + ANALYZER = GraphQL::Analysis::AST::QueryComplexity alias_method :query, :object @@ -23,7 +23,7 @@ module Types description: 'GraphQL query complexity score.' def score - ::GraphQL::Analysis.analyze_query(query, [ANALYZER]).first + ::GraphQL::Analysis::AST.analyze_query(query, [ANALYZER]).first end end # rubocop: enable Graphql/AuthorizeTypes diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 01b1a71896a..46d121f6552 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -52,6 +52,7 @@ module Types field :milestone, ::Types::MilestoneType, null: true, + extras: [:lookahead], description: 'Find a milestone.' do argument :id, ::Types::GlobalIDType[Milestone], required: true, description: 'Find a milestone by its ID.' end @@ -90,8 +91,8 @@ module Types field :work_item, Types::WorkItemType, null: true, resolver: Resolvers::WorkItemResolver, - description: 'Find a work item. Returns `null` if `work_items` feature flag is disabled.' \ - ' The feature is experimental and is subject to change without notice.' + deprecated: { milestone: '15.1', reason: :alpha }, + description: 'Find a work item. Returns `null` if `work_items` feature flag is disabled.' field :merge_request, Types::MergeRequestType, null: true, @@ -156,8 +157,9 @@ module Types GitlabSchema.find_by_gid(id) end - def milestone(id:) - GitlabSchema.find_by_gid(id) + def milestone(id:, lookahead:) + preloads = [:releases] if lookahead.selects?(:releases) + Gitlab::Graphql::Loaders::BatchModelLoader.new(id.model_class, id.model_id, preloads).find end def container_repository(id:) diff --git a/app/graphql/types/release_asset_link_type.rb b/app/graphql/types/release_asset_link_type.rb index 33dcb5125e3..29738de27e5 100644 --- a/app/graphql/types/release_asset_link_type.rb +++ b/app/graphql/types/release_asset_link_type.rb @@ -7,6 +7,8 @@ module Types authorize :read_release + present_using Releases::LinkPresenter + field :external, GraphQL::Types::Boolean, null: true, method: :external?, description: 'Indicates the link points to an external resource.' field :id, GraphQL::Types::ID, null: false, @@ -22,12 +24,5 @@ module Types description: 'Relative path for the direct asset link.' field :direct_asset_url, GraphQL::Types::String, null: true, description: 'Direct asset URL of the link.' - - def direct_asset_url - return object.url unless object.filepath - - release = object.release.present - release.download_url(object.filepath) - end end end diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb index 95b6b43bb46..43dc0c4ce85 100644 --- a/app/graphql/types/release_type.rb +++ b/app/graphql/types/release_type.rb @@ -13,6 +13,9 @@ module Types present_using ReleasePresenter + field :id, ::Types::GlobalIDType[Release], + null: false, + description: 'Global ID of the release.' field :assets, Types::ReleaseAssetsType, null: true, method: :itself, description: 'Assets of the release.' field :created_at, Types::TimeType, null: true, diff --git a/app/graphql/types/terraform/state_type.rb b/app/graphql/types/terraform/state_type.rb index bce34a85f85..be17fc41c2c 100644 --- a/app/graphql/types/terraform/state_type.rb +++ b/app/graphql/types/terraform/state_type.rb @@ -38,6 +38,10 @@ module Types null: false, description: 'Timestamp the Terraform state was updated.' + field :deleted_at, Types::TimeType, + null: true, + description: 'Timestamp the Terraform state was deleted.' + def locked_by_user Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.locked_by_user_id).find end diff --git a/app/graphql/types/time_type.rb b/app/graphql/types/time_type.rb index 2db14953308..121515c04db 100644 --- a/app/graphql/types/time_type.rb +++ b/app/graphql/types/time_type.rb @@ -12,6 +12,9 @@ module Types DESC def self.coerce_input(value, ctx) + # arguments can be nil, so don't raise an error + return if value.nil? + Time.parse(value) rescue ArgumentError, TypeError => e raise GraphQL::CoercionError, e.message diff --git a/app/graphql/types/todo_type.rb b/app/graphql/types/todo_type.rb index f21b2b261a3..0de6b1d6f8a 100644 --- a/app/graphql/types/todo_type.rb +++ b/app/graphql/types/todo_type.rb @@ -53,6 +53,10 @@ module Types description: 'Timestamp this to-do item was created.', null: false + field :note, Types::Notes::NoteType, + description: 'Note which created this to-do item.', + null: true + def project Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find end diff --git a/app/graphql/types/work_item_sort_enum.rb b/app/graphql/types/work_item_sort_enum.rb new file mode 100644 index 00000000000..e644313d409 --- /dev/null +++ b/app/graphql/types/work_item_sort_enum.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Types + class WorkItemSortEnum < SortEnum + graphql_name 'WorkItemSort' + description 'Values for sorting work items' + + value 'TITLE_ASC', 'Title by ascending order.', value: :title_asc + value 'TITLE_DESC', 'Title by descending order.', value: :title_desc + end +end diff --git a/app/graphql/types/work_item_type.rb b/app/graphql/types/work_item_type.rb index cd784d54959..18b9bfd1c9a 100644 --- a/app/graphql/types/work_item_type.rb +++ b/app/graphql/types/work_item_type.rb @@ -18,6 +18,8 @@ module Types description: 'State of the work item.' field :title, GraphQL::Types::String, null: false, description: 'Title of the work item.' + field :widgets, [Types::WorkItems::WidgetInterface], null: true, + description: 'Collection of widgets that belong to the work item.' field :work_item_type, Types::WorkItems::TypeType, null: false, description: 'Type assigned to the work item.' diff --git a/app/graphql/types/work_items/updated_task_input_type.rb b/app/graphql/types/work_items/updated_task_input_type.rb new file mode 100644 index 00000000000..9f8afa2ff1b --- /dev/null +++ b/app/graphql/types/work_items/updated_task_input_type.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Types + module WorkItems + class UpdatedTaskInputType < BaseInputObject + graphql_name 'WorkItemUpdatedTaskInput' + + include Mutations::WorkItems::UpdateArguments + end + end +end diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb new file mode 100644 index 00000000000..f3cf1d74829 --- /dev/null +++ b/app/graphql/types/work_items/widget_interface.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module WidgetInterface + include Types::BaseInterface + + graphql_name 'WorkItemWidget' + + field :type, ::Types::WorkItems::WidgetTypeEnum, null: true, + description: 'Widget type.' + + def self.resolve_type(object, context) + case object + when ::WorkItems::Widgets::Description + ::Types::WorkItems::Widgets::DescriptionType + when ::WorkItems::Widgets::Hierarchy + ::Types::WorkItems::Widgets::HierarchyType + else + raise "Unknown GraphQL type for widget #{object}" + end + end + + orphan_types ::Types::WorkItems::Widgets::DescriptionType, + ::Types::WorkItems::Widgets::HierarchyType + end + end +end diff --git a/app/graphql/types/work_items/widget_type_enum.rb b/app/graphql/types/work_items/widget_type_enum.rb new file mode 100644 index 00000000000..4e5933bff86 --- /dev/null +++ b/app/graphql/types/work_items/widget_type_enum.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Types + module WorkItems + class WidgetTypeEnum < BaseEnum + graphql_name 'WorkItemWidgetType' + description 'Type of a work item widget' + + ::WorkItems::Type.available_widgets.each do |widget| + value widget.type.to_s.upcase, value: widget.type, description: "#{widget.type.to_s.titleize} widget." + end + end + end +end diff --git a/app/graphql/types/work_items/widgets/description_input_type.rb b/app/graphql/types/work_items/widgets/description_input_type.rb new file mode 100644 index 00000000000..382cfdf659f --- /dev/null +++ b/app/graphql/types/work_items/widgets/description_input_type.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module Widgets + class DescriptionInputType < BaseInputObject + graphql_name 'WorkItemWidgetDescriptionInput' + + argument :description, GraphQL::Types::String, + required: true, + description: copy_field_description(Types::WorkItemType, :description) + end + end + end +end diff --git a/app/graphql/types/work_items/widgets/description_type.rb b/app/graphql/types/work_items/widgets/description_type.rb new file mode 100644 index 00000000000..79192d7c3d4 --- /dev/null +++ b/app/graphql/types/work_items/widgets/description_type.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module Widgets + # Disabling widget level authorization as it might be too granular + # and we already authorize the parent work item + # rubocop:disable Graphql/AuthorizeTypes + class DescriptionType < BaseObject + graphql_name 'WorkItemWidgetDescription' + description 'Represents a description widget' + + implements Types::WorkItems::WidgetInterface + + field :description, GraphQL::Types::String, null: true, + description: 'Description of the work item.' + + markdown_field :description_html, null: true do |resolved_object| + resolved_object.work_item + end + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end diff --git a/app/graphql/types/work_items/widgets/hierarchy_type.rb b/app/graphql/types/work_items/widgets/hierarchy_type.rb new file mode 100644 index 00000000000..057d5fbf056 --- /dev/null +++ b/app/graphql/types/work_items/widgets/hierarchy_type.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module Widgets + # Disabling widget level authorization as it might be too granular + # and we already authorize the parent work item + # rubocop:disable Graphql/AuthorizeTypes + class HierarchyType < BaseObject + graphql_name 'WorkItemWidgetHierarchy' + description 'Represents a hierarchy widget' + + implements Types::WorkItems::WidgetInterface + + field :parent, ::Types::WorkItemType, null: true, + description: 'Parent work item.', + complexity: 5 + + field :children, ::Types::WorkItemType.connection_type, null: true, + description: 'Child work items.', + complexity: 5 + + def children + object.children.inc_relations_for_permission_check + end + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end |