Commit 66d320c0 authored by Alex Kalderimis's avatar Alex Kalderimis

Merge branch 'georgekoltsov/add-events-to-epics-graphql' into 'master'

Add Event type to GraphQL and events to epics

See merge request gitlab-org/gitlab!53152
parents 2f053003 8d5d9384
# frozen_string_literal: true
module Types
class EventActionEnum < BaseEnum
graphql_name 'EventAction'
description 'Event action'
::Event.actions.keys.each do |target_type|
value target_type.upcase, value: target_type, description: "#{target_type.titleize} action"
end
end
end
# frozen_string_literal: true
module Types
class EventType < BaseObject
graphql_name 'Event'
description 'Representing an event'
present_using EventPresenter
authorize :read_event
field :id, GraphQL::ID_TYPE,
description: 'ID of the event.',
null: false
field :author, Types::UserType,
description: 'Author of this event.',
null: false
field :action, Types::EventActionEnum,
description: 'Action of the event.',
null: false
field :created_at, Types::TimeType,
description: 'When this event was created.',
null: false
field :updated_at, Types::TimeType,
description: 'When this event was updated.',
null: false
def author
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
end
end
end
# frozen_string_literal: true
module Types
module EventableType
include Types::BaseInterface
field :events, Types::EventType.connection_type, null: true, description: 'A list of events associated with the object.'
end
end
# frozen_string_literal: true
class EventPolicy < BasePolicy # rubocop:disable Gitlab/NamespacedClass
condition(:visible_to_user) do
subject.visible_to_user?(user)
end
rule { visible_to_user }.enable :read_event
end
......@@ -1639,7 +1639,7 @@ type BoardEdge {
"""
Represents an epic on an issue board
"""
type BoardEpic implements CurrentUserTodos & Noteable {
type BoardEpic implements CurrentUserTodos & Eventable & Noteable {
"""
Author of the epic.
"""
......@@ -1878,6 +1878,31 @@ type BoardEpic implements CurrentUserTodos & Noteable {
"""
dueDateIsFixed: Boolean
"""
A list of events associated with the object.
"""
events(
"""
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
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): EventConnection
"""
Group to which the epic belongs.
"""
......@@ -8682,7 +8707,7 @@ type EnvironmentsCanaryIngressUpdatePayload {
"""
Represents an epic
"""
type Epic implements CurrentUserTodos & Noteable {
type Epic implements CurrentUserTodos & Eventable & Noteable {
"""
Author of the epic.
"""
......@@ -8921,6 +8946,31 @@ type Epic implements CurrentUserTodos & Noteable {
"""
dueDateIsFixed: Boolean
"""
A list of events associated with the object.
"""
events(
"""
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
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): EventConnection
"""
Group to which the epic belongs.
"""
......@@ -10251,6 +10301,168 @@ enum EpicWildcardId {
NONE
}
"""
Representing an event
"""
type Event {
"""
Action of the event.
"""
action: EventAction!
"""
Author of this event.
"""
author: User!
"""
When this event was created.
"""
createdAt: Time!
"""
ID of the event.
"""
id: ID!
"""
When this event was updated.
"""
updatedAt: Time!
}
"""
Event action
"""
enum EventAction {
"""
Approved action
"""
APPROVED
"""
Archived action
"""
ARCHIVED
"""
Closed action
"""
CLOSED
"""
Commented action
"""
COMMENTED
"""
Created action
"""
CREATED
"""
Destroyed action
"""
DESTROYED
"""
Expired action
"""
EXPIRED
"""
Joined action
"""
JOINED
"""
Left action
"""
LEFT
"""
Merged action
"""
MERGED
"""
Pushed action
"""
PUSHED
"""
Reopened action
"""
REOPENED
"""
Updated action
"""
UPDATED
}
"""
The connection type for Event.
"""
type EventConnection {
"""
A list of edges.
"""
edges: [EventEdge]
"""
A list of nodes.
"""
nodes: [Event]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type EventEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: Event
}
interface Eventable {
"""
A list of events associated with the object.
"""
events(
"""
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
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): EventConnection
}
"""
Autogenerated input type of ExportRequirements
"""
......
......@@ -309,6 +309,7 @@ Represents an epic on an issue board.
| `dueDateFixed` | Time | Fixed due date of the epic. |
| `dueDateFromMilestones` | Time | Inherited due date of the epic from milestones. |
| `dueDateIsFixed` | Boolean | Indicates if the due date has been manually set. |
| `events` | EventConnection | A list of events associated with the object. |
| `group` | Group! | Group to which the epic belongs. |
| `hasChildren` | Boolean! | Indicates if the epic has children. |
| `hasIssues` | Boolean! | Indicates if the epic has direct issues. |
......@@ -1467,6 +1468,7 @@ Represents an epic.
| `dueDateFixed` | Time | Fixed due date of the epic. |
| `dueDateFromMilestones` | Time | Inherited due date of the epic from milestones. |
| `dueDateIsFixed` | Boolean | Indicates if the due date has been manually set. |
| `events` | EventConnection | A list of events associated with the object. |
| `group` | Group! | Group to which the epic belongs. |
| `hasChildren` | Boolean! | Indicates if the epic has children. |
| `hasIssues` | Boolean! | Indicates if the epic has direct issues. |
......@@ -1678,6 +1680,18 @@ Autogenerated return type of EpicTreeReorder.
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
### Event
Representing an event.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `action` | EventAction! | Action of the event. |
| `author` | User! | Author of this event. |
| `createdAt` | Time! | When this event was created. |
| `id` | ID! | ID of the event. |
| `updatedAt` | Time! | When this event was updated. |
### ExportRequirementsPayload
Autogenerated return type of ExportRequirements.
......@@ -4879,6 +4893,26 @@ Epic ID wildcard values.
| `ANY` | Any epic is assigned |
| `NONE` | No epic is assigned |
### EventAction
Event action.
| Value | Description |
| ----- | ----------- |
| `APPROVED` | Approved action |
| `ARCHIVED` | Archived action |
| `CLOSED` | Closed action |
| `COMMENTED` | Commented action |
| `CREATED` | Created action |
| `DESTROYED` | Destroyed action |
| `EXPIRED` | Expired action |
| `JOINED` | Joined action |
| `LEFT` | Left action |
| `MERGED` | Merged action |
| `PUSHED` | Pushed action |
| `REOPENED` | Reopened action |
| `UPDATED` | Updated action |
### GroupMemberRelation
Group member relation.
......
......@@ -78,7 +78,8 @@ module Resolvers
def preloads
{
parent: [:parent]
parent: [:parent],
events: { events: [:target] }
}
end
......
......@@ -15,6 +15,7 @@ module Types
implements(Types::Notes::NoteableType)
implements(Types::CurrentUserTodos)
implements(Types::EventableType)
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the epic.'
......
---
title: Add Event type to GraphQL and events to epics
merge_request: 53152
author:
type: changed
......@@ -13,7 +13,7 @@ RSpec.describe GitlabSchema.types['Epic'] do
notes discussions relative_position subscribed participants
descendant_counts descendant_weight_sum upvotes downvotes
user_notes_count user_discussions_count health_status current_user_todos
award_emoji
award_emoji events
]
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EventPolicy do
let(:user) { create(:user) }
let(:event) { create(:event, :created, target: create(:epic, group: group)) }
subject { described_class.new(user, event) }
before do
stub_licensed_features(epics: true)
end
context 'when the user cannot read the epic' do
let(:group) { create(:group, :private) }
it { expect_disallowed(:read_event) }
end
context 'when the user can read the epic' do
let(:group) { create(:group, :public) }
it { expect_allowed(:read_event) }
end
end
......@@ -83,6 +83,49 @@ RSpec.describe 'getting epics information' do
end
end
context 'query for epics with events' do
let_it_be(:epic) { create(:epic, group: group) }
it 'can lookahead to prevent N+1 queries' do
create_list(:event, 10, :created, target: epic, group: group)
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
query_epics_with_events(1)
end.count
events = graphql_dig_at(graphql_data, :group, :epics, :nodes, :events, :nodes)
expect(events.count).to eq(1)
expect do
query_epics_with_events(10)
end.not_to exceed_all_query_limit(control_count)
data = graphql_data(fresh_response_data)
events = graphql_dig_at(data, :group, :epics, :nodes, :events, :nodes)
expect(events.count).to eq(10)
end
end
def query_epics_with_events(number)
epics_field = <<~NODE
epics {
nodes {
id
events(first: #{number}) {
nodes {
id
}
}
}
}
NODE
post_graphql(
graphql_query_for('group', { 'fullPath' => group.full_path }, epics_field),
current_user: user
)
end
def epics_query(group, field, value)
epics_query_by_hash(group, field => value)
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::EventType do
specify { expect(described_class.graphql_name).to eq('Event') }
specify { expect(described_class).to require_graphql_authorizations(:read_event) }
specify { expect(described_class).to have_graphql_fields(:id, :author, :action, :created_at, :updated_at) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::EventableType do
it 'exposes events field' do
expect(described_class).to have_graphql_fields(:events)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment