Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
9ce799c7
Commit
9ce799c7
authored
Dec 17, 2020
by
Jan Provaznik
Committed by
Patrick Bair
Dec 17, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add epic board lists
Adds epic board list and expose it in GraphQL.
parent
1ed6725f
Changes
25
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
753 additions
and
13 deletions
+753
-13
changelogs/unreleased/epic_boards_graphql_lists.yml
changelogs/unreleased/epic_boards_graphql_lists.yml
+5
-0
db/migrate/20201214184020_add_epic_board_list.rb
db/migrate/20201214184020_add_epic_board_list.rb
+33
-0
db/schema_migrations/20201214184020
db/schema_migrations/20201214184020
+1
-0
db/structure.sql
db/structure.sql
+37
-0
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+97
-2
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+272
-2
doc/api/graphql/reference/index.md
doc/api/graphql/reference/index.md
+15
-2
ee/app/graphql/resolvers/boards/epic_lists_resolver.rb
ee/app/graphql/resolvers/boards/epic_lists_resolver.rb
+45
-0
ee/app/graphql/types/boards/epic_board_type.rb
ee/app/graphql/types/boards/epic_board_type.rb
+9
-2
ee/app/graphql/types/boards/epic_list_type.rb
ee/app/graphql/types/boards/epic_list_type.rb
+29
-0
ee/app/models/boards/epic_board.rb
ee/app/models/boards/epic_board.rb
+1
-0
ee/app/models/boards/epic_list.rb
ee/app/models/boards/epic_list.rb
+20
-0
ee/app/models/ee/label.rb
ee/app/models/ee/label.rb
+1
-0
ee/app/policies/ee/group_policy.rb
ee/app/policies/ee/group_policy.rb
+1
-0
ee/spec/factories/boards/epic_lists.rb
ee/spec/factories/boards/epic_lists.rb
+10
-0
ee/spec/graphql/resolvers/boards/epic_boards_resolvers_spec.rb
...ec/graphql/resolvers/boards/epic_boards_resolvers_spec.rb
+2
-2
ee/spec/graphql/resolvers/boards/epic_lists_resolvers_spec.rb
...pec/graphql/resolvers/boards/epic_lists_resolvers_spec.rb
+49
-0
ee/spec/graphql/types/boards/epic_board_type_spec.rb
ee/spec/graphql/types/boards/epic_board_type_spec.rb
+1
-1
ee/spec/graphql/types/boards/epic_list_type_spec.rb
ee/spec/graphql/types/boards/epic_list_type_spec.rb
+13
-0
ee/spec/models/boards/epic_board_spec.rb
ee/spec/models/boards/epic_board_spec.rb
+1
-0
ee/spec/models/boards/epic_list_spec.rb
ee/spec/models/boards/epic_list_spec.rb
+46
-0
ee/spec/models/ee/label_spec.rb
ee/spec/models/ee/label_spec.rb
+1
-0
ee/spec/policies/group_policy_spec.rb
ee/spec/policies/group_policy_spec.rb
+5
-2
ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb
ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb
+58
-0
spec/lib/gitlab/import_export/all_models.yml
spec/lib/gitlab/import_export/all_models.yml
+1
-0
No files found.
changelogs/unreleased/epic_boards_graphql_lists.yml
0 → 100644
View file @
9ce799c7
---
title
:
Add epic board list table
merge_request
:
49728
author
:
type
:
added
db/migrate/20201214184020_add_epic_board_list.rb
0 → 100644
View file @
9ce799c7
# frozen_string_literal: true
class
AddEpicBoardList
<
ActiveRecord
::
Migration
[
6.0
]
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
disable_ddl_transaction!
def
up
unless
table_exists?
(
:boards_epic_lists
)
with_lock_retries
do
create_table
:boards_epic_lists
do
|
t
|
t
.
timestamps_with_timezone
t
.
references
:epic_board
,
index:
true
,
foreign_key:
{
to_table: :boards_epic_boards
,
on_delete: :cascade
},
null:
false
t
.
references
:label
,
index:
true
,
foreign_key:
{
on_delete: :cascade
}
t
.
integer
:position
t
.
integer
:list_type
,
default:
1
,
limit:
2
,
null:
false
t
.
index
[
:epic_board_id
,
:label_id
],
unique:
true
,
where:
'list_type = 1'
,
name:
'index_boards_epic_lists_on_epic_board_id_and_label_id'
end
end
end
add_check_constraint
:boards_epic_lists
,
'(list_type <> 1) OR ("position" IS NOT NULL AND "position" >= 0)'
,
'boards_epic_lists_position_constraint'
end
def
down
with_lock_retries
do
drop_table
:boards_epic_lists
end
end
end
db/schema_migrations/20201214184020
0 → 100644
View file @
9ce799c7
adce3c714064991e93f8a587da3d5892c47dbc14963fa49638ebbbf8b5489359
\ No newline at end of file
db/structure.sql
View file @
9ce799c7
...
...
@@ -9909,6 +9909,26 @@ CREATE SEQUENCE boards_epic_boards_id_seq
ALTER
SEQUENCE
boards_epic_boards_id_seq
OWNED
BY
boards_epic_boards
.
id
;
CREATE
TABLE
boards_epic_lists
(
id
bigint
NOT
NULL
,
created_at
timestamp
with
time
zone
NOT
NULL
,
updated_at
timestamp
with
time
zone
NOT
NULL
,
epic_board_id
bigint
NOT
NULL
,
label_id
bigint
,
"position"
integer
,
list_type
smallint
DEFAULT
1
NOT
NULL
,
CONSTRAINT
boards_epic_lists_position_constraint
CHECK
(((
list_type
<>
1
)
OR
((
"position"
IS
NOT
NULL
)
AND
(
"position"
>=
0
))))
);
CREATE
SEQUENCE
boards_epic_lists_id_seq
START
WITH
1
INCREMENT
BY
1
NO
MINVALUE
NO
MAXVALUE
CACHE
1
;
ALTER
SEQUENCE
boards_epic_lists_id_seq
OWNED
BY
boards_epic_lists
.
id
;
CREATE
TABLE
boards_epic_user_preferences
(
id
bigint
NOT
NULL
,
board_id
bigint
NOT
NULL
,
...
...
@@ -18130,6 +18150,8 @@ ALTER TABLE ONLY boards_epic_board_positions ALTER COLUMN id SET DEFAULT nextval
ALTER
TABLE
ONLY
boards_epic_boards
ALTER
COLUMN
id
SET
DEFAULT
nextval
(
'boards_epic_boards_id_seq'
::
regclass
);
ALTER
TABLE
ONLY
boards_epic_lists
ALTER
COLUMN
id
SET
DEFAULT
nextval
(
'boards_epic_lists_id_seq'
::
regclass
);
ALTER
TABLE
ONLY
boards_epic_user_preferences
ALTER
COLUMN
id
SET
DEFAULT
nextval
(
'boards_epic_user_preferences_id_seq'
::
regclass
);
ALTER
TABLE
ONLY
broadcast_messages
ALTER
COLUMN
id
SET
DEFAULT
nextval
(
'broadcast_messages_id_seq'
::
regclass
);
...
...
@@ -19173,6 +19195,9 @@ ALTER TABLE ONLY boards_epic_board_positions
ALTER
TABLE
ONLY
boards_epic_boards
ADD
CONSTRAINT
boards_epic_boards_pkey
PRIMARY
KEY
(
id
);
ALTER
TABLE
ONLY
boards_epic_lists
ADD
CONSTRAINT
boards_epic_lists_pkey
PRIMARY
KEY
(
id
);
ALTER
TABLE
ONLY
boards_epic_user_preferences
ADD
CONSTRAINT
boards_epic_user_preferences_pkey
PRIMARY
KEY
(
id
);
...
...
@@ -20824,6 +20849,12 @@ CREATE INDEX index_boards_epic_board_positions_on_epic_id ON boards_epic_board_p
CREATE
INDEX
index_boards_epic_boards_on_group_id
ON
boards_epic_boards
USING
btree
(
group_id
);
CREATE
INDEX
index_boards_epic_lists_on_epic_board_id
ON
boards_epic_lists
USING
btree
(
epic_board_id
);
CREATE
UNIQUE
INDEX
index_boards_epic_lists_on_epic_board_id_and_label_id
ON
boards_epic_lists
USING
btree
(
epic_board_id
,
label_id
)
WHERE
(
list_type
=
1
);
CREATE
INDEX
index_boards_epic_lists_on_label_id
ON
boards_epic_lists
USING
btree
(
label_id
);
CREATE
INDEX
index_boards_epic_user_preferences_on_board_id
ON
boards_epic_user_preferences
USING
btree
(
board_id
);
CREATE
UNIQUE
INDEX
index_boards_epic_user_preferences_on_board_user_epic_unique
ON
boards_epic_user_preferences
USING
btree
(
board_id
,
user_id
,
epic_id
);
...
...
@@ -24029,6 +24060,9 @@ ALTER TABLE ONLY user_synced_attributes_metadata
ALTER
TABLE
ONLY
project_authorizations
ADD
CONSTRAINT
fk_rails_0f84bb11f3
FOREIGN
KEY
(
project_id
)
REFERENCES
projects
(
id
)
ON
DELETE
CASCADE
;
ALTER
TABLE
ONLY
boards_epic_lists
ADD
CONSTRAINT
fk_rails_0f9c7f646f
FOREIGN
KEY
(
epic_board_id
)
REFERENCES
boards_epic_boards
(
id
)
ON
DELETE
CASCADE
;
ALTER
TABLE
ONLY
issue_email_participants
ADD
CONSTRAINT
fk_rails_0fdfd8b811
FOREIGN
KEY
(
issue_id
)
REFERENCES
issues
(
id
)
ON
DELETE
CASCADE
;
...
...
@@ -24134,6 +24168,9 @@ ALTER TABLE ONLY boards_epic_board_positions
ALTER
TABLE
ONLY
geo_repository_created_events
ADD
CONSTRAINT
fk_rails_1f49e46a61
FOREIGN
KEY
(
project_id
)
REFERENCES
projects
(
id
)
ON
DELETE
CASCADE
;
ALTER
TABLE
ONLY
boards_epic_lists
ADD
CONSTRAINT
fk_rails_1fe6b54909
FOREIGN
KEY
(
label_id
)
REFERENCES
labels
(
id
)
ON
DELETE
CASCADE
;
ALTER
TABLE
ONLY
approval_merge_request_rules_groups
ADD
CONSTRAINT
fk_rails_2020a7124a
FOREIGN
KEY
(
group_id
)
REFERENCES
namespaces
(
id
)
ON
DELETE
CASCADE
;
...
...
doc/api/graphql/reference/gitlab_schema.graphql
View file @
9ce799c7
...
...
@@ -2225,6 +2225,11 @@ Identifier of Boards::EpicBoard
"""
scalar
BoardsEpicBoardID
"""
Identifier of Boards::EpicList
"""
scalar
BoardsEpicListID
type
Branch
{
"""
Commit
for
the
branch
...
...
@@ -8093,12 +8098,37 @@ Represents an epic board
"""
type
EpicBoard
{
"""
Global
ID
of
the
board
Global
ID
of
the
board
.
"""
id
:
BoardsEpicBoardID
!
"""
Name
of
the
board
Epic
board
lists
.
"""
lists
(
"""
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
):
EpicListConnection
"""
Name
of
the
board
.
"""
name
:
String
}
...
...
@@ -8693,6 +8723,71 @@ type EpicIssueEdge {
node
:
EpicIssue
}
"""
Represents an epic board list
"""
type
EpicList
{
"""
Global
ID
of
the
board
list
.
"""
id
:
BoardsEpicListID
!
"""
Label
of
the
list
.
"""
label
:
Label
"""
Type
of
the
list
.
"""
listType
:
String
!
"""
Position
of
the
list
within
the
board
.
"""
position
:
Int
"""
Title
of
the
list
.
"""
title
:
String
!
}
"""
The connection type for EpicList.
"""
type
EpicListConnection
{
"""
A
list
of
edges
.
"""
edges
:
[
EpicListEdge
]
"""
A
list
of
nodes
.
"""
nodes
:
[
EpicList
]
"""
Information
to
aid
in
pagination
.
"""
pageInfo
:
PageInfo
!
}
"""
An edge in a connection.
"""
type
EpicListEdge
{
"""
A
cursor
for
use
in
pagination
.
"""
cursor
:
String
!
"""
The
item
at
the
end
of
the
edge
.
"""
node
:
EpicList
}
"""
Check permissions for the current user on an epic
"""
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
9ce799c7
...
...
@@ -5899,6 +5899,16 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "BoardsEpicListID",
"description": "Identifier of Boards::EpicList",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Boolean",
...
...
@@ -22633,7 +22643,7 @@
"fields": [
{
"name": "id",
"description": "Global ID of the board",
"description": "Global ID of the board
.
",
"args": [
],
...
...
@@ -22649,9 +22659,62 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "lists",
"description": "Epic board lists.",
"args": [
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "EpicListConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "Name of the board",
"description": "Name of the board
.
",
"args": [
],
...
...
@@ -24375,6 +24438,213 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "EpicList",
"description": "Represents an epic board list",
"fields": [
{
"name": "id",
"description": "Global ID of the board list.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "BoardsEpicListID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "label",
"description": "Label of the list.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Label",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "listType",
"description": "Type of the list.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "position",
"description": "Position of the list within the board.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "title",
"description": "Title of the list.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "EpicListConnection",
"description": "The connection type for EpicList.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "EpicListEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "EpicList",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "EpicListEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "EpicList",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "EpicPermissions",
doc/api/graphql/reference/index.md
View file @
9ce799c7
...
...
@@ -1377,8 +1377,9 @@ Represents an epic board.
| Field | Type | Description |
| ----- | ---- | ----------- |
|
`id`
| BoardsEpicBoardID! | Global ID of the board |
|
`name`
| String | Name of the board |
|
`id`
| BoardsEpicBoardID! | Global ID of the board. |
|
`lists`
| EpicListConnection | Epic board lists. |
|
`name`
| String | Name of the board. |
### EpicDescendantCount
...
...
@@ -1472,6 +1473,18 @@ Relationship between an epic and an issue.
|
`webUrl`
| String! | Web URL of the issue |
|
`weight`
| Int | Weight of the issue. |
### EpicList
Represents an epic board list.
| Field | Type | Description |
| ----- | ---- | ----------- |
|
`id`
| BoardsEpicListID! | Global ID of the board list. |
|
`label`
| Label | Label of the list. |
|
`listType`
| String! | Type of the list. |
|
`position`
| Int | Position of the list within the board. |
|
`title`
| String! | Title of the list. |
### EpicPermissions
Check permissions for the current user on an epic.
...
...
ee/app/graphql/resolvers/boards/epic_lists_resolver.rb
0 → 100644
View file @
9ce799c7
# frozen_string_literal: true
module
Resolvers
module
Boards
class
EpicListsResolver
<
BaseResolver
include
Gitlab
::
Graphql
::
Authorize
::
AuthorizeResource
include
LooksAhead
type
Types
::
Boards
::
EpicListType
.
connection_type
,
null:
true
when_single
do
argument
:id
,
::
Types
::
GlobalIDType
[
::
Boards
::
EpicList
],
required:
true
,
description:
'Find an epic board list by ID.'
end
alias_method
:epic_board
,
:object
def
resolve_with_lookahead
(
id:
nil
)
authorize!
# eventually we may want to (re)use Boards::Lists::ListService
# but we don't support yet creation of default lists so at this
# point there is not reason to introduce a ListService
# https://gitlab.com/gitlab-org/gitlab/-/issues/294043
lists
=
epic_board
.
epic_lists
lists
=
lists
.
where
(
id:
id
.
model_id
)
if
id
# rubocop: disable CodeReuse/ActiveRecord
offset_pagination
(
apply_lookahead
(
lists
))
end
private
def
authorize!
Ability
.
allowed?
(
context
[
:current_user
],
:read_epic_list
,
epic_board
.
group
)
||
raise_resource_not_available_error!
end
def
preloads
{
label:
[
:label
]
}
end
end
end
end
ee/app/graphql/types/boards/epic_board_type.rb
View file @
9ce799c7
...
...
@@ -10,10 +10,17 @@ module Types
authorize
:read_epic_board
field
:id
,
type:
::
Types
::
GlobalIDType
[
::
Boards
::
EpicBoard
],
null:
false
,
description:
'Global ID of the board'
description:
'Global ID of the board
.
'
field
:name
,
type:
GraphQL
::
STRING_TYPE
,
null:
true
,
description:
'Name of the board'
description:
'Name of the board.'
field
:lists
,
Types
::
Boards
::
EpicListType
.
connection_type
,
null:
true
,
description:
'Epic board lists.'
,
extras:
[
:lookahead
],
resolver:
Resolvers
::
Boards
::
EpicListsResolver
end
end
end
ee/app/graphql/types/boards/epic_list_type.rb
0 → 100644
View file @
9ce799c7
# frozen_string_literal: true
module
Types
module
Boards
# rubocop: disable Graphql/AuthorizeTypes
class
EpicListType
<
BaseObject
graphql_name
'EpicList'
description
'Represents an epic board list'
accepts
::
Boards
::
EpicList
field
:id
,
type:
::
Types
::
GlobalIDType
[
::
Boards
::
EpicList
],
null:
false
,
description:
'Global ID of the board list.'
field
:title
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'Title of the list.'
field
:list_type
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'Type of the list.'
field
:position
,
GraphQL
::
INT_TYPE
,
null:
true
,
description:
'Position of the list within the board.'
field
:label
,
Types
::
LabelType
,
null:
true
,
description:
'Label of the list.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
ee/app/models/boards/epic_board.rb
View file @
9ce799c7
...
...
@@ -5,6 +5,7 @@ module Boards
belongs_to
:group
,
optional:
false
,
inverse_of: :epic_boards
has_many
:epic_board_labels
,
foreign_key: :epic_board_id
,
inverse_of: :epic_board
has_many
:epic_board_positions
,
foreign_key: :epic_board_id
,
inverse_of: :epic_board
has_many
:epic_lists
,
->
{
ordered
},
foreign_key: :epic_board_id
,
inverse_of: :epic_board
validates
:name
,
length:
{
maximum:
255
}
...
...
ee/app/models/boards/epic_list.rb
0 → 100644
View file @
9ce799c7
# frozen_string_literal: true
module
Boards
class
EpicList
<
ApplicationRecord
belongs_to
:epic_board
,
optional:
false
,
inverse_of: :epic_lists
belongs_to
:label
,
inverse_of: :epic_lists
enum
list_type:
{
backlog:
0
,
label:
1
,
closed:
2
}
validates
:label
,
:position
,
presence:
true
,
if: :label?
validates
:label_id
,
uniqueness:
{
scope: :epic_board_id
},
if: :label?
validates
:position
,
numericality:
{
only_integer:
true
,
greater_than_or_equal_to:
0
},
if: :label?
scope
:ordered
,
->
{
order
(
:list_type
,
:position
)
}
def
title
label?
?
label
.
name
:
list_type
.
humanize
end
end
end
ee/app/models/ee/label.rb
View file @
9ce799c7
...
...
@@ -9,6 +9,7 @@ module EE
prepended
do
has_many
:epic_board_labels
,
class_name:
'Boards::EpicBoardLabel'
,
inverse_of: :label
has_many
:epic_lists
,
class_name:
'Boards::EpicList'
,
inverse_of: :label
end
def
scoped_label?
...
...
ee/app/policies/ee/group_policy.rb
View file @
9ce799c7
...
...
@@ -176,6 +176,7 @@ module EE
rule
{
can?
(
:read_group
)
&
epics_available
}.
policy
do
enable
:read_epic
enable
:read_epic_board
enable
:read_epic_list
end
rule
{
can?
(
:read_group
)
&
iterations_available
}.
enable
:read_iteration
...
...
ee/spec/factories/boards/epic_lists.rb
0 → 100644
View file @
9ce799c7
# frozen_string_literal: true
FactoryBot
.
define
do
factory
:epic_list
,
class:
'Boards::EpicList'
do
epic_board
label
list_type
{
:label
}
sequence
(
:position
)
end
end
ee/spec/graphql/resolvers/boards/epic_boards_resolvers_spec.rb
View file @
9ce799c7
...
...
@@ -33,7 +33,7 @@ RSpec.describe Resolvers::Boards::EpicBoardsResolver do
end
it
'raises an error if user cannot read epic boards'
do
expect
{
result
}.
to
raise_error
(
Gitlab
::
Graphql
::
Errors
::
ResourceNotAvailable
)
expect
{
result
}.
to
raise_error
(
Gitlab
::
Graphql
::
Errors
::
ResourceNotAvailable
)
end
context
'when user is member of the group'
do
...
...
@@ -42,7 +42,7 @@ RSpec.describe Resolvers::Boards::EpicBoardsResolver do
end
it
'returns epic boards in the group ordered by name'
do
expect
(
result
).
to
match_array
([
epic_board2
,
epic_board1
])
expect
(
result
).
to
eq
([
epic_board2
,
epic_board1
])
end
context
'when epic_boards flag is disabled'
do
...
...
ee/spec/graphql/resolvers/boards/epic_lists_resolvers_spec.rb
0 → 100644
View file @
9ce799c7
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Resolvers
::
Boards
::
EpicListsResolver
do
include
GraphqlHelpers
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be_with_refind
(
:group
)
{
create
(
:group
,
:private
)
}
let_it_be
(
:epic_board
)
{
create
(
:epic_board
,
group:
group
)
}
let_it_be
(
:epic_list1
)
{
create
(
:epic_list
,
epic_board:
epic_board
)
}
let_it_be
(
:epic_list2
)
{
create
(
:epic_list
,
epic_board:
epic_board
)
}
specify
do
expect
(
described_class
).
to
have_nullable_graphql_type
(
Types
::
Boards
::
EpicListType
.
connection_type
)
end
describe
'#resolve'
do
let
(
:args
)
{
{}
}
subject
(
:result
)
{
resolve
(
described_class
,
ctx:
{
current_user:
user
},
obj:
epic_board
,
args:
args
)
}
before
do
stub_licensed_features
(
epics:
true
)
end
it
'raises an error if user cannot read epic lists'
do
expect
{
result
}.
to
raise_error
(
Gitlab
::
Graphql
::
Errors
::
ResourceNotAvailable
)
end
context
'when user is member of the group'
do
before
do
group
.
add_reporter
(
user
)
end
it
'returns epic lists for the board'
do
expect
(
result
.
items
).
to
match_array
([
epic_list1
,
epic_list2
])
end
context
'when list ID param is set'
do
let
(
:args
)
{
{
id:
epic_list1
.
to_global_id
}
}
it
'returns an array with single epic list'
do
expect
(
result
.
items
).
to
match_array
([
epic_list1
])
end
end
end
end
end
ee/spec/graphql/types/boards/epic_board_type_spec.rb
View file @
9ce799c7
...
...
@@ -8,7 +8,7 @@ RSpec.describe GitlabSchema.types['EpicBoard'] do
specify
{
expect
(
described_class
).
to
require_graphql_authorizations
(
:read_epic_board
)
}
it
'has specific fields'
do
expected_fields
=
%w[id name]
expected_fields
=
%w[id name
lists
]
expect
(
described_class
).
to
include_graphql_fields
(
*
expected_fields
)
end
...
...
ee/spec/graphql/types/boards/epic_list_type_spec.rb
0 → 100644
View file @
9ce799c7
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
GitlabSchema
.
types
[
'EpicList'
]
do
specify
{
expect
(
described_class
.
graphql_name
).
to
eq
(
'EpicList'
)
}
it
'has specific fields'
do
expected_fields
=
%w[id title list_type position label]
expect
(
described_class
).
to
include_graphql_fields
(
*
expected_fields
)
end
end
ee/spec/models/boards/epic_board_spec.rb
View file @
9ce799c7
...
...
@@ -7,6 +7,7 @@ RSpec.describe Boards::EpicBoard do
it
{
is_expected
.
to
belong_to
(
:group
).
required
.
inverse_of
(
:epic_boards
)
}
it
{
is_expected
.
to
have_many
(
:epic_board_labels
).
inverse_of
(
:epic_board
)
}
it
{
is_expected
.
to
have_many
(
:epic_board_positions
).
inverse_of
(
:epic_board
)
}
it
{
is_expected
.
to
have_many
(
:epic_lists
).
order
(
list_type: :asc
,
position: :asc
).
inverse_of
(
:epic_board
)
}
end
describe
'validations'
do
...
...
ee/spec/models/boards/epic_list_spec.rb
0 → 100644
View file @
9ce799c7
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Boards
::
EpicList
do
describe
'associations'
do
subject
{
build
(
:epic_list
)
}
it
{
is_expected
.
to
belong_to
(
:epic_board
).
required
.
inverse_of
(
:epic_lists
)
}
it
{
is_expected
.
to
belong_to
(
:label
).
inverse_of
(
:epic_lists
)
}
it
{
is_expected
.
to
validate_presence_of
(
:position
)
}
it
{
is_expected
.
to
validate_numericality_of
(
:position
).
only_integer
.
is_greater_than_or_equal_to
(
0
)
}
it
{
is_expected
.
to
validate_uniqueness_of
(
:label_id
).
scoped_to
(
:epic_board_id
)
}
context
'when list_type is set to closed'
do
subject
{
build
(
:epic_list
,
list_type: :closed
)
}
it
{
is_expected
.
not_to
validate_presence_of
(
:label
)
}
it
{
is_expected
.
not_to
validate_presence_of
(
:position
)
}
end
end
describe
'scopes'
do
describe
'.ordered'
do
it
'returns lists ordered by type and position'
do
list1
=
create
(
:epic_list
,
list_type: :backlog
)
list2
=
create
(
:epic_list
,
list_type: :closed
)
list3
=
create
(
:epic_list
,
position:
1
)
list4
=
create
(
:epic_list
,
position:
2
)
expect
(
described_class
.
ordered
).
to
eq
([
list1
,
list3
,
list4
,
list2
])
end
end
end
describe
'#title'
do
it
'returns label name for label lists'
do
list
=
build
(
:epic_list
)
expect
(
list
.
title
).
to
eq
(
list
.
label
.
name
)
end
it
'returns list type for non-label lists'
do
expect
(
build
(
:epic_list
,
list_type:
::
Boards
::
EpicList
.
list_types
[
:backlog
]).
title
).
to
eq
(
'Backlog'
)
end
end
end
ee/spec/models/ee/label_spec.rb
View file @
9ce799c7
...
...
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec
.
describe
Label
do
describe
'associations'
do
it
{
is_expected
.
to
have_many
(
:epic_board_labels
).
inverse_of
(
:label
)
}
it
{
is_expected
.
to
have_many
(
:epic_lists
).
inverse_of
(
:label
)
}
end
describe
'#scoped_label?'
do
...
...
ee/spec/policies/group_policy_spec.rb
View file @
9ce799c7
...
...
@@ -5,7 +5,10 @@ require 'spec_helper'
RSpec
.
describe
GroupPolicy
do
include_context
'GroupPolicy context'
let
(
:epic_rules
)
{
%i(read_epic create_epic admin_epic destroy_epic read_confidential_epic destroy_epic_link read_epic_board)
}
let
(
:epic_rules
)
do
%i(read_epic create_epic admin_epic destroy_epic read_confidential_epic
destroy_epic_link read_epic_board read_epic_list)
end
context
'when epics feature is disabled'
do
let
(
:current_user
)
{
owner
}
...
...
@@ -55,7 +58,7 @@ RSpec.describe GroupPolicy do
let
(
:current_user
)
{
guest
}
it
{
is_expected
.
to
be_allowed
(
:read_epic
,
:read_epic_board
)
}
it
{
is_expected
.
to
be_disallowed
(
*
(
epic_rules
-
[
:read_epic
,
:read_epic_board
]))
}
it
{
is_expected
.
to
be_disallowed
(
*
(
epic_rules
-
[
:read_epic
,
:read_epic_board
,
:read_epic_list
]))
}
end
context
'when user is not member'
do
...
...
ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb
0 → 100644
View file @
9ce799c7
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
'get list of epic boards'
do
include
GraphqlHelpers
let_it_be
(
:current_user
)
{
create
(
:user
)
}
let_it_be
(
:group
)
{
create
(
:group
,
:private
)
}
let_it_be
(
:board
)
{
create
(
:epic_board
,
group:
group
)
}
let_it_be
(
:list1
)
{
create
(
:epic_list
,
epic_board:
board
)
}
let_it_be
(
:list2
)
{
create
(
:epic_list
,
epic_board:
board
,
list_type: :closed
)
}
let_it_be
(
:list3
)
{
create
(
:epic_list
,
epic_board:
board
,
list_type: :backlog
)
}
def
pagination_query
(
params
=
{})
graphql_query_for
(
:group
,
{
full_path:
group
.
full_path
},
<<~
BOARDS
epicBoard(id: "
#{
board
.
to_global_id
}
") {
#{
query_nodes
(
:lists
,
all_graphql_fields_for
(
'epic_lists'
.
classify
),
include_pagination_info:
true
,
args:
params
)
}
}
BOARDS
)
end
before
do
stub_licensed_features
(
epics:
true
)
end
context
'when the user does not have access to the epic board group'
do
it
'returns nil group'
do
post_graphql
(
pagination_query
,
current_user:
current_user
)
expect
(
graphql_data
[
'group'
]).
to
be_nil
end
end
context
'when user can access the epic board group'
do
before
do
group
.
add_developer
(
current_user
)
end
describe
'sorting and pagination'
do
let
(
:data_path
)
{
[
:group
,
:epicBoard
,
:lists
]
}
let
(
:expected_results
)
{
[
list3
.
to_global_id
.
to_s
,
list1
.
to_global_id
.
to_s
,
list2
.
to_global_id
.
to_s
]
}
def
pagination_results_data
(
nodes
)
nodes
.
map
{
|
list
|
list
[
'id'
]
}
end
it_behaves_like
'sorted paginated query'
do
# currently we don't support custom sorting for epic lists,
# nil value will be ignored by ::Graphql::Arguments
let
(
:sort_param
)
{
nil
}
let
(
:first_param
)
{
2
}
end
end
end
end
spec/lib/gitlab/import_export/all_models.yml
View file @
9ce799c7
...
...
@@ -87,6 +87,7 @@ label:
-
merge_requests
-
priorities
-
epic_board_labels
-
epic_lists
milestone
:
-
group
-
project
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment