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
cf27abe5
Commit
cf27abe5
authored
Feb 19, 2021
by
Florie Guibert
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Create Epic Board
Create epic board from boards dropdown
parent
2f1dfd77
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
315 additions
and
33 deletions
+315
-33
app/assets/javascripts/boards/components/board_form.vue
app/assets/javascripts/boards/components/board_form.vue
+49
-30
app/assets/javascripts/boards/components/boards_selector.vue
app/assets/javascripts/boards/components/boards_selector.vue
+6
-3
ee/app/assets/javascripts/boards/components/board_form.vue
ee/app/assets/javascripts/boards/components/board_form.vue
+50
-0
ee/app/assets/javascripts/boards/components/boards_selector.vue
.../assets/javascripts/boards/components/boards_selector.vue
+3
-0
ee/app/assets/javascripts/boards/graphql/epic_board_create.mutation.graphql
...scripts/boards/graphql/epic_board_create.mutation.graphql
+9
-0
ee/spec/features/epic_boards/multiple_epic_boards_spec.rb
ee/spec/features/epic_boards/multiple_epic_boards_spec.rb
+14
-0
ee/spec/frontend/boards/components/board_form_spec.js
ee/spec/frontend/boards/components/board_form_spec.js
+184
-0
No files found.
app/assets/javascripts/boards/components/board_form.vue
View file @
cf27abe5
...
...
@@ -161,42 +161,55 @@ export default {
currentMutation
()
{
return
this
.
board
.
id
?
updateBoardMutation
:
createBoardMutation
;
},
m
utationVariables
()
{
baseM
utationVariables
()
{
const
{
board
}
=
this
;
/* eslint-disable @gitlab/require-i18n-strings */
let
baseMutationVariables
=
{
const
variables
=
{
name
:
board
.
name
,
hideBacklogList
:
board
.
hide_backlog_list
,
hideClosedList
:
board
.
hide_closed_list
,
};
if
(
this
.
scopedIssueBoardFeatureEnabled
)
{
baseMutationVariables
=
{
...
baseMutationVariables
,
weight
:
board
.
weight
,
assigneeId
:
board
.
assignee
?.
id
?
convertToGraphQLId
(
'
User
'
,
board
.
assignee
.
id
)
:
null
,
milestoneId
:
board
.
milestone
?.
id
||
board
.
milestone
?.
id
===
0
?
convertToGraphQLId
(
'
Milestone
'
,
board
.
milestone
.
id
)
:
null
,
labelIds
:
board
.
labels
.
map
(
fullLabelId
),
iterationId
:
board
.
iteration_id
?
convertToGraphQLId
(
'
Iteration
'
,
board
.
iteration_id
)
:
null
,
};
}
/* eslint-enable @gitlab/require-i18n-strings */
return
board
.
id
?
{
...
baseMutationV
ariables
,
...
v
ariables
,
id
:
fullBoardId
(
board
.
id
),
}
:
{
...
baseMutationV
ariables
,
projectPath
:
this
.
projectId
?
this
.
fullPath
:
null
,
groupPath
:
this
.
groupId
?
this
.
fullPath
:
null
,
...
v
ariables
,
projectPath
:
this
.
projectId
?
this
.
fullPath
:
undefined
,
groupPath
:
this
.
groupId
?
this
.
fullPath
:
undefined
,
};
},
boardScopeMutationVariables
()
{
/* eslint-disable @gitlab/require-i18n-strings */
return
{
weight
:
this
.
board
.
weight
,
assigneeId
:
this
.
board
.
assignee
?.
id
?
convertToGraphQLId
(
'
User
'
,
this
.
board
.
assignee
.
id
)
:
null
,
milestoneId
:
this
.
board
.
milestone
?.
id
||
this
.
board
.
milestone
?.
id
===
0
?
convertToGraphQLId
(
'
Milestone
'
,
this
.
board
.
milestone
.
id
)
:
null
,
labelIds
:
this
.
board
.
labels
.
map
(
fullLabelId
),
iterationId
:
this
.
board
.
iteration_id
?
convertToGraphQLId
(
'
Iteration
'
,
this
.
board
.
iteration_id
)
:
null
,
};
/* eslint-enable @gitlab/require-i18n-strings */
},
mutationVariables
()
{
let
variables
=
this
.
baseMutationVariables
;
if
(
this
.
scopedIssueBoardFeatureEnabled
)
{
variables
=
{
...
variables
,
...
this
.
boardScopeMutationVariables
,
};
}
return
variables
;
},
},
mounted
()
{
this
.
resetFormState
();
...
...
@@ -208,6 +221,16 @@ export default {
setIteration
(
iterationId
)
{
this
.
board
.
iteration_id
=
iterationId
;
},
boardCreateResponse
(
data
)
{
return
data
.
createBoard
.
board
.
webPath
;
},
boardUpdateResponse
(
data
)
{
const
path
=
data
.
updateBoard
.
board
.
webPath
;
const
param
=
getParameterByName
(
'
group_by
'
)
?
`?group_by=
${
getParameterByName
(
'
group_by
'
)}
`
:
''
;
return
`
${
path
}${
param
}
`
;
},
async
createOrUpdateBoard
()
{
const
response
=
await
this
.
$apollo
.
mutate
({
mutation
:
this
.
currentMutation
,
...
...
@@ -215,14 +238,10 @@ export default {
});
if
(
!
this
.
board
.
id
)
{
return
response
.
data
.
createBoard
.
board
.
webPath
;
return
this
.
boardCreateResponse
(
response
.
data
)
;
}
const
path
=
response
.
data
.
updateBoard
.
board
.
webPath
;
const
param
=
getParameterByName
(
'
group_by
'
)
?
`?group_by=
${
getParameterByName
(
'
group_by
'
)}
`
:
''
;
return
`
${
path
}${
param
}
`
;
return
this
.
boardUpdateResponse
(
response
.
data
);
},
async
submit
()
{
if
(
this
.
board
.
name
.
length
===
0
)
return
;
...
...
@@ -245,7 +264,7 @@ export default {
try
{
const
url
=
await
this
.
createOrUpdateBoard
();
visitUrl
(
url
);
}
catch
{
}
catch
(
e
)
{
Flash
(
this
.
$options
.
i18n
.
saveErrorMessage
);
}
finally
{
this
.
isLoading
=
false
;
...
...
app/assets/javascripts/boards/components/boards_selector.vue
View file @
cf27abe5
...
...
@@ -10,6 +10,8 @@ import {
}
from
'
@gitlab/ui
'
;
import
{
throttle
}
from
'
lodash
'
;
import
BoardForm
from
'
ee_else_ce/boards/components/board_form.vue
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
httpStatusCodes
from
'
~/lib/utils/http_status
'
;
...
...
@@ -18,8 +20,6 @@ import eventHub from '../eventhub';
import
groupQuery
from
'
../graphql/group_boards.query.graphql
'
;
import
projectQuery
from
'
../graphql/project_boards.query.graphql
'
;
import
BoardForm
from
'
./board_form.vue
'
;
const
MIN_BOARDS_TO_VIEW_RECENT
=
10
;
export
default
{
...
...
@@ -123,6 +123,9 @@ export default {
board
()
{
return
this
.
currentBoard
;
},
showCreate
()
{
return
this
.
multipleIssueBoardsAvailable
;
},
showDelete
()
{
return
this
.
boards
.
length
>
1
;
},
...
...
@@ -327,7 +330,7 @@ export default {
<gl-dropdown-divider
/>
<gl-dropdown-item
v-if=
"
multipleIssueBoardsAvailabl
e"
v-if=
"
showCreat
e"
v-gl-modal-directive=
"'board-config-modal'"
data-qa-selector=
"create_new_board_button"
@
click.prevent=
"showPage('new')"
...
...
ee/app/assets/javascripts/boards/components/board_form.vue
0 → 100644
View file @
cf27abe5
<
script
>
// This is a false violation of @gitlab/no-runtime-template-compiler, since it
// extends a valid Vue single file component.
/* eslint-disable @gitlab/no-runtime-template-compiler */
import
{
mapState
}
from
'
vuex
'
;
import
BoardFormFoss
from
'
~/boards/components/board_form.vue
'
;
import
createEpicBoardMutation
from
'
../graphql/epic_board_create.mutation.graphql
'
;
export
default
{
extends
:
BoardFormFoss
,
computed
:
{
...
mapState
([
'
isEpicBoard
'
]),
epicBoardCreateQuery
()
{
return
createEpicBoardMutation
;
},
mutationVariables
()
{
let
variables
=
this
.
baseMutationVariables
;
// TODO: Epic board scope will be added in a future iteration: https://gitlab.com/gitlab-org/gitlab/-/issues/231389
if
(
this
.
scopedIssueBoardFeatureEnabled
&&
!
this
.
isEpicBoard
)
{
variables
=
{
...
variables
,
...
this
.
boardScopeMutationVariables
,
};
}
return
variables
;
},
},
methods
:
{
epicBoardCreateResponse
(
data
)
{
return
data
.
epicBoardCreate
.
epicBoard
.
webPath
;
},
async
createOrUpdateBoard
()
{
const
response
=
await
this
.
$apollo
.
mutate
({
mutation
:
this
.
isEpicBoard
?
this
.
epicBoardCreateQuery
:
this
.
currentMutation
,
variables
:
{
input
:
this
.
mutationVariables
},
});
if
(
!
this
.
board
.
id
)
{
return
this
.
isEpicBoard
?
this
.
epicBoardCreateResponse
(
response
.
data
)
:
this
.
boardCreateResponse
(
response
.
data
);
}
return
this
.
boardUpdateResponse
(
response
.
data
);
},
},
};
</
script
>
ee/app/assets/javascripts/boards/components/boards_selector.vue
View file @
cf27abe5
...
...
@@ -11,6 +11,9 @@ export default {
extends
:
BoardsSelectorFoss
,
computed
:
{
...
mapState
([
'
isEpicBoard
'
]),
showCreate
()
{
return
this
.
isEpicBoard
||
this
.
multipleIssueBoardsAvailable
;
},
},
methods
:
{
epicBoardUpdate
(
data
)
{
...
...
ee/app/assets/javascripts/boards/graphql/epic_board_create.mutation.graphql
0 → 100644
View file @
cf27abe5
mutation
createEpicBoard
(
$input
:
EpicBoardCreateInput
!)
{
epicBoardCreate
(
input
:
$input
)
{
epicBoard
{
id
webPath
}
errors
}
}
ee/spec/features/epic_boards/multiple_epic_boards_spec.rb
View file @
cf27abe5
...
...
@@ -12,6 +12,8 @@ RSpec.describe 'epic boards', :js do
context
'multiple epic boards'
do
before
do
stub_licensed_features
(
epics:
true
)
group
.
add_maintainer
(
user
)
sign_in
(
user
)
visit_epic_boards_page
end
...
...
@@ -40,6 +42,18 @@ RSpec.describe 'epic boards', :js do
expect
(
page
).
to
have_content
(
epic_board2
.
name
)
end
end
it
'creates new epic board without detailed configuration'
do
in_boards_switcher_dropdown
do
click_button
'Create new board'
end
fill_in
'board-new-name'
,
with:
'This is a new board'
click_button
'Create board'
wait_for_requests
expect
(
page
).
to
have_button
(
'This is a new board'
)
end
end
def
visit_epic_boards_page
...
...
ee/spec/frontend/boards/components/board_form_spec.js
0 → 100644
View file @
cf27abe5
import
{
GlModal
}
from
'
@gitlab/ui
'
;
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
BoardForm
from
'
ee/boards/components/board_form.vue
'
;
import
createEpicBoardMutation
from
'
ee/boards/graphql/epic_board_create.mutation.graphql
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
formType
}
from
'
~/boards/constants
'
;
import
{
deprecatedCreateFlash
as
createFlash
}
from
'
~/flash
'
;
import
{
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
jest
.
mock
(
'
~/lib/utils/url_utility
'
,
()
=>
({
visitUrl
:
jest
.
fn
().
mockName
(
'
visitUrlMock
'
),
stripFinalUrlSegment
:
jest
.
requireActual
(
'
~/lib/utils/url_utility
'
).
stripFinalUrlSegment
,
}));
jest
.
mock
(
'
~/flash
'
);
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
const
currentBoard
=
{
id
:
1
,
name
:
'
test
'
,
labels
:
[],
milestone_id
:
undefined
,
assignee
:
{},
assignee_id
:
undefined
,
weight
:
null
,
hide_backlog_list
:
false
,
hide_closed_list
:
false
,
};
const
defaultProps
=
{
canAdminBoard
:
false
,
labelsPath
:
`
${
TEST_HOST
}
/labels/path`
,
labelsWebUrl
:
`
${
TEST_HOST
}
/-/labels`
,
currentBoard
,
currentPage
:
''
,
};
describe
(
'
BoardForm
'
,
()
=>
{
let
wrapper
;
let
mutate
;
const
findModal
=
()
=>
wrapper
.
find
(
GlModal
);
const
findModalActionPrimary
=
()
=>
findModal
().
props
(
'
actionPrimary
'
);
const
findFormWrapper
=
()
=>
wrapper
.
find
(
'
[data-testid="board-form-wrapper"]
'
);
const
findDeleteConfirmation
=
()
=>
wrapper
.
find
(
'
[data-testid="delete-confirmation-message"]
'
);
const
findInput
=
()
=>
wrapper
.
find
(
'
#board-new-name
'
);
const
createStore
=
()
=>
{
return
new
Vuex
.
Store
({
state
:
{
isEpicBoard
:
true
,
},
});
};
const
store
=
createStore
();
const
createComponent
=
(
props
,
data
)
=>
{
wrapper
=
shallowMount
(
BoardForm
,
{
localVue
,
propsData
:
{
...
defaultProps
,
...
props
},
data
()
{
return
{
...
data
,
};
},
provide
:
{
rootPath
:
'
root
'
,
},
mocks
:
{
$apollo
:
{
mutate
,
},
},
attachTo
:
document
.
body
,
store
,
});
};
beforeEach
(()
=>
{
delete
window
.
location
;
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
mutate
=
null
;
});
describe
(
'
when creating a new epic board
'
,
()
=>
{
describe
(
'
on non-scoped-board
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
new
});
});
it
(
'
clears the form
'
,
()
=>
{
expect
(
findInput
().
element
.
value
).
toBe
(
''
);
});
it
(
'
shows a correct title about creating a board
'
,
()
=>
{
expect
(
findModal
().
attributes
(
'
title
'
)).
toBe
(
'
Create new board
'
);
});
it
(
'
passes correct primary action text and variant
'
,
()
=>
{
expect
(
findModalActionPrimary
().
text
).
toBe
(
'
Create board
'
);
expect
(
findModalActionPrimary
().
attributes
[
0
].
variant
).
toBe
(
'
success
'
);
});
it
(
'
does not render delete confirmation message
'
,
()
=>
{
expect
(
findDeleteConfirmation
().
exists
()).
toBe
(
false
);
});
it
(
'
renders form wrapper
'
,
()
=>
{
expect
(
findFormWrapper
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
when submitting a create event
'
,
()
=>
{
const
fillForm
=
()
=>
{
findInput
().
value
=
'
Test name
'
;
findInput
().
trigger
(
'
input
'
);
findInput
().
trigger
(
'
keyup.enter
'
,
{
metaKey
:
true
});
};
beforeEach
(()
=>
{
mutate
=
jest
.
fn
().
mockResolvedValue
({
data
:
{
epicBoardCreate
:
{
epicBoard
:
{
id
:
'
gid://gitlab/Boards::EpicBoard/123
'
,
webPath
:
'
test-path
'
},
},
},
});
});
it
(
'
does not call API if board name is empty
'
,
async
()
=>
{
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
new
});
findInput
().
trigger
(
'
keyup.enter
'
,
{
metaKey
:
true
});
await
waitForPromises
();
expect
(
mutate
).
not
.
toHaveBeenCalled
();
});
it
(
'
calls a correct GraphQL mutation and redirects to correct page from existing board
'
,
async
()
=>
{
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
new
});
fillForm
();
await
waitForPromises
();
expect
(
mutate
).
toHaveBeenCalledWith
({
mutation
:
createEpicBoardMutation
,
variables
:
{
input
:
expect
.
objectContaining
({
name
:
'
test
'
,
}),
},
});
await
waitForPromises
();
expect
(
visitUrl
).
toHaveBeenCalledWith
(
'
test-path
'
);
});
it
(
'
shows an error flash if GraphQL mutation fails
'
,
async
()
=>
{
mutate
=
jest
.
fn
().
mockRejectedValue
(
'
Houston, we have a problem
'
);
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
new
});
fillForm
();
await
waitForPromises
();
expect
(
mutate
).
toHaveBeenCalled
();
await
waitForPromises
();
expect
(
visitUrl
).
not
.
toHaveBeenCalled
();
expect
(
createFlash
).
toHaveBeenCalled
();
});
});
});
});
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