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
b19091f3
Commit
b19091f3
authored
Feb 09, 2022
by
Natalia Tepluhina
Committed by
Simon Knox
Feb 09, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add dropdown with work item types to create task component
parent
d87e3b00
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
183 additions
and
20 deletions
+183
-20
app/assets/javascripts/issues/show/components/description.vue
...assets/javascripts/issues/show/components/description.vue
+8
-5
app/assets/javascripts/issues/show/index.js
app/assets/javascripts/issues/show/index.js
+3
-0
app/assets/javascripts/work_items/graphql/project_work_item_types.query.graphql
.../work_items/graphql/project_work_item_types.query.graphql
+11
-0
app/assets/javascripts/work_items/index.js
app/assets/javascripts/work_items/index.js
+4
-0
app/assets/javascripts/work_items/pages/create_work_item.vue
app/assets/javascripts/work_items/pages/create_work_item.vue
+66
-8
app/views/shared/issue_type/_details_content.html.haml
app/views/shared/issue_type/_details_content.html.haml
+1
-1
locale/gitlab.pot
locale/gitlab.pot
+9
-3
spec/frontend/work_items/mock_data.js
spec/frontend/work_items/mock_data.js
+14
-0
spec/frontend/work_items/pages/create_work_item_spec.js
spec/frontend/work_items/pages/create_work_item_spec.js
+57
-3
spec/frontend/work_items/router_spec.js
spec/frontend/work_items/router_spec.js
+10
-0
No files found.
app/assets/javascripts/issues/show/components/description.vue
View file @
b19091f3
...
@@ -95,7 +95,7 @@ export default {
...
@@ -95,7 +95,7 @@ export default {
this
.
renderGFM
();
this
.
renderGFM
();
this
.
updateTaskStatusText
();
this
.
updateTaskStatusText
();
if
(
this
.
workItemsEnabled
)
{
if
(
this
.
workItemsEnabled
&&
this
.
$el
)
{
this
.
renderTaskActions
();
this
.
renderTaskActions
();
}
}
},
},
...
@@ -174,8 +174,11 @@ export default {
...
@@ -174,8 +174,11 @@ export default {
);
);
button
.
id
=
`js-task-button-
${
index
}
`
;
button
.
id
=
`js-task-button-
${
index
}
`
;
this
.
taskButtons
.
push
(
button
.
id
);
this
.
taskButtons
.
push
(
button
.
id
);
button
.
innerHTML
=
button
.
innerHTML
=
`
'
<svg data-testid="ellipsis_v-icon" role="img" aria-hidden="true" class="dropdown-icon gl-icon s14"><use href="/assets/icons-7f1680a3670112fe4c8ef57b9dfb93f0f61b43a2a479d7abd6c83bcb724b9201.svg#ellipsis_v"></use></svg>
'
;
<svg data-testid="ellipsis_v-icon" role="img" aria-hidden="true" class="dropdown-icon gl-icon s14">
<use href="
${
gon
.
sprite_icons
}
#ellipsis_v"></use>
</svg>
`
;
item
.
prepend
(
button
);
item
.
prepend
(
button
);
});
});
},
},
...
@@ -191,7 +194,7 @@ export default {
...
@@ -191,7 +194,7 @@ export default {
const
taskBadge
=
document
.
createElement
(
'
span
'
);
const
taskBadge
=
document
.
createElement
(
'
span
'
);
taskBadge
.
innerHTML
=
`
taskBadge
.
innerHTML
=
`
<svg data-testid="issue-open-m-icon" role="img" aria-hidden="true" class="gl-icon gl-fill-green-500 s12">
<svg data-testid="issue-open-m-icon" role="img" aria-hidden="true" class="gl-icon gl-fill-green-500 s12">
<use href="
/assets/icons-7f1680a3670112fe4c8ef57b9dfb93f0f61b43a2a479d7abd6c83bcb724b9201.svg
#issue-open-m"></use>
<use href="
${
gon
.
sprite_icons
}
#issue-open-m"></use>
</svg>
</svg>
<span class="badge badge-info badge-pill gl-badge sm gl-mr-1">
<span class="badge badge-info badge-pill gl-badge sm gl-mr-1">
${
__
(
'
Task
'
)}
${
__
(
'
Task
'
)}
...
@@ -245,7 +248,7 @@ export default {
...
@@ -245,7 +248,7 @@ export default {
modal-id=
"create-task-modal"
modal-id=
"create-task-modal"
:title=
"s__('WorkItem|New Task')"
:title=
"s__('WorkItem|New Task')"
hide-footer
hide-footer
body-class=
"gl-p
y
-0!"
body-class=
"gl-p-0!"
>
>
<create-work-item
<create-work-item
:is-modal=
"true"
:is-modal=
"true"
...
...
app/assets/javascripts/issues/show/index.js
View file @
b19091f3
...
@@ -74,6 +74,8 @@ export function initIssueApp(issueData, store) {
...
@@ -74,6 +74,8 @@ export function initIssueApp(issueData, store) {
return
undefined
;
return
undefined
;
}
}
const
{
fullPath
}
=
el
.
dataset
;
if
(
gon
?.
features
?.
fixCommentScroll
)
{
if
(
gon
?.
features
?.
fixCommentScroll
)
{
scrollToTargetOnResize
();
scrollToTargetOnResize
();
}
}
...
@@ -88,6 +90,7 @@ export function initIssueApp(issueData, store) {
...
@@ -88,6 +90,7 @@ export function initIssueApp(issueData, store) {
store
,
store
,
provide
:
{
provide
:
{
canCreateIncident
,
canCreateIncident
,
fullPath
,
},
},
computed
:
{
computed
:
{
...
mapGetters
([
'
getNoteableData
'
]),
...
mapGetters
([
'
getNoteableData
'
]),
...
...
app/assets/javascripts/work_items/graphql/project_work_item_types.query.graphql
0 → 100644
View file @
b19091f3
query
projectWorkItemTypes
(
$fullPath
:
ID
!)
{
workspace
:
project
(
fullPath
:
$fullPath
)
{
id
workItemTypes
{
nodes
{
id
name
}
}
}
}
app/assets/javascripts/work_items/index.js
View file @
b19091f3
...
@@ -5,11 +5,15 @@ import { createApolloProvider } from './graphql/provider';
...
@@ -5,11 +5,15 @@ import { createApolloProvider } from './graphql/provider';
export
const
initWorkItemsRoot
=
()
=>
{
export
const
initWorkItemsRoot
=
()
=>
{
const
el
=
document
.
querySelector
(
'
#js-work-items
'
);
const
el
=
document
.
querySelector
(
'
#js-work-items
'
);
const
{
fullPath
}
=
el
.
dataset
;
return
new
Vue
({
return
new
Vue
({
el
,
el
,
router
:
createRouter
(
el
.
dataset
.
fullPath
),
router
:
createRouter
(
el
.
dataset
.
fullPath
),
apolloProvider
:
createApolloProvider
(),
apolloProvider
:
createApolloProvider
(),
provide
:
{
fullPath
,
},
render
(
createElement
)
{
render
(
createElement
)
{
return
createElement
(
App
);
return
createElement
(
App
);
},
},
...
...
app/assets/javascripts/work_items/pages/create_work_item.vue
View file @
b19091f3
<
script
>
<
script
>
import
{
GlButton
,
GlAlert
}
from
'
@gitlab/ui
'
;
import
{
GlButton
,
GlAlert
,
GlLoadingIcon
,
GlDropdown
,
GlDropdownItem
}
from
'
@gitlab/ui
'
;
import
{
s__
}
from
'
~/locale
'
;
import
createWorkItemMutation
from
'
../graphql/create_work_item.mutation.graphql
'
;
import
createWorkItemMutation
from
'
../graphql/create_work_item.mutation.graphql
'
;
import
projectWorkItemTypesQuery
from
'
../graphql/project_work_item_types.query.graphql
'
;
import
ItemTitle
from
'
../components/item_title.vue
'
;
import
ItemTitle
from
'
../components/item_title.vue
'
;
...
@@ -8,8 +10,12 @@ export default {
...
@@ -8,8 +10,12 @@ export default {
components
:
{
components
:
{
GlButton
,
GlButton
,
GlAlert
,
GlAlert
,
GlLoadingIcon
,
GlDropdown
,
GlDropdownItem
,
ItemTitle
,
ItemTitle
,
},
},
inject
:
[
'
fullPath
'
],
props
:
{
props
:
{
isModal
:
{
isModal
:
{
type
:
Boolean
,
type
:
Boolean
,
...
@@ -25,9 +31,34 @@ export default {
...
@@ -25,9 +31,34 @@ export default {
data
()
{
data
()
{
return
{
return
{
title
:
this
.
initialTitle
,
title
:
this
.
initialTitle
,
error
:
false
,
error
:
null
,
workItemTypes
:
[],
selectedWorkItemType
:
null
,
};
};
},
},
apollo
:
{
workItemTypes
:
{
query
:
projectWorkItemTypesQuery
,
variables
()
{
return
{
fullPath
:
this
.
fullPath
,
};
},
update
(
data
)
{
return
data
.
workspace
?.
workItemTypes
?.
nodes
;
},
error
()
{
this
.
error
=
s__
(
'
WorkItem|Something went wrong when fetching work item types. Please try again
'
,
);
},
},
},
computed
:
{
dropdownButtonText
()
{
return
this
.
selectedWorkItemType
?.
name
||
s__
(
'
WorkItem|Type
'
);
},
},
methods
:
{
methods
:
{
async
createWorkItem
()
{
async
createWorkItem
()
{
try
{
try
{
...
@@ -53,7 +84,9 @@ export default {
...
@@ -53,7 +84,9 @@ export default {
this
.
$emit
(
'
onCreate
'
,
this
.
title
);
this
.
$emit
(
'
onCreate
'
,
this
.
title
);
}
}
}
catch
{
}
catch
{
this
.
error
=
true
;
this
.
error
=
s__
(
'
WorkItem|Something went wrong when creating a work item. Please try again
'
,
);
}
}
},
},
handleTitleInput
(
title
)
{
handleTitleInput
(
title
)
{
...
@@ -66,18 +99,43 @@ export default {
...
@@ -66,18 +99,43 @@ export default {
}
}
this
.
$emit
(
'
closeModal
'
);
this
.
$emit
(
'
closeModal
'
);
},
},
selectWorkItemType
(
type
)
{
this
.
selectedWorkItemType
=
type
;
},
},
},
};
};
</
script
>
</
script
>
<
template
>
<
template
>
<form
@
submit.prevent=
"createWorkItem"
>
<form
@
submit.prevent=
"createWorkItem"
>
<gl-alert
v-if=
"error"
variant=
"danger"
@
dismiss=
"error = false"
>
{{
<gl-alert
v-if=
"error"
variant=
"danger"
@
dismiss=
"error = null"
>
{{
error
}}
</gl-alert>
__
(
'
Something went wrong when creating a work item. Please try again
'
)
<div
:class=
"
{ 'gl-px-5': isModal }" data-testid="content">
}}
</gl-alert>
<item-title
<item-title
:initial-title=
"title"
data-testid=
"title-input"
@
title-input=
"handleTitleInput"
/>
:initial-title=
"title"
data-testid=
"title-input"
@
title-input=
"handleTitleInput"
/>
<div>
<gl-dropdown
:text=
"dropdownButtonText"
>
<gl-loading-icon
v-if=
"$apollo.queries.workItemTypes.loading"
size=
"md"
data-testid=
"loading-types"
/>
<template
v-else
>
<gl-dropdown-item
v-for=
"type in workItemTypes"
:key=
"type.id"
@
click=
"selectWorkItemType(type)"
>
{{
type
.
name
}}
</gl-dropdown-item>
</
template
>
</gl-dropdown>
</div>
</div>
<div
<div
class=
"gl-bg-gray-10 gl-py-5 gl-px-6"
class=
"gl-bg-gray-10 gl-py-5 gl-px-6
gl-mt-4
"
:class=
"{ 'gl-display-flex gl-justify-content-end': isModal }"
:class=
"{ 'gl-display-flex gl-justify-content-end': isModal }"
>
>
<gl-button
<gl-button
...
...
app/views/shared/issue_type/_details_content.html.haml
View file @
b19091f3
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
.issue-details.issuable-details
.issue-details.issuable-details
.detail-page-description.content-block
.detail-page-description.content-block
#js-issuable-app
{
data:
{
initial:
issuable_initial_data
(
issuable
).
to_json
}
}
#js-issuable-app
{
data:
{
initial:
issuable_initial_data
(
issuable
).
to_json
,
full_path:
@project
.
full_path
}
}
.title-container
.title-container
%h2
.title
=
markdown_field
(
issuable
,
:title
)
%h2
.title
=
markdown_field
(
issuable
,
:title
)
-
if
issuable
.
description
.
present?
-
if
issuable
.
description
.
present?
...
...
locale/gitlab.pot
View file @
b19091f3
...
@@ -33830,9 +33830,6 @@ msgstr ""
...
@@ -33830,9 +33830,6 @@ msgstr ""
msgid "Something went wrong trying to load issue contacts."
msgid "Something went wrong trying to load issue contacts."
msgstr ""
msgstr ""
msgid "Something went wrong when creating a work item. Please try again"
msgstr ""
msgid "Something went wrong when reordering designs. Please try again"
msgid "Something went wrong when reordering designs. Please try again"
msgstr ""
msgstr ""
...
@@ -41105,6 +41102,15 @@ msgstr ""
...
@@ -41105,6 +41102,15 @@ msgstr ""
msgid "WorkItem|New Task"
msgid "WorkItem|New Task"
msgstr ""
msgstr ""
msgid "WorkItem|Something went wrong when creating a work item. Please try again"
msgstr ""
msgid "WorkItem|Something went wrong when fetching work item types. Please try again"
msgstr ""
msgid "WorkItem|Type"
msgstr ""
msgid "WorkItem|Work Items"
msgid "WorkItem|Work Items"
msgstr ""
msgstr ""
...
...
spec/frontend/work_items/mock_data.js
View file @
b19091f3
...
@@ -34,3 +34,17 @@ export const updateWorkItemMutationResponse = {
...
@@ -34,3 +34,17 @@ export const updateWorkItemMutationResponse = {
},
},
},
},
};
};
export
const
projectWorkItemTypesQueryResponse
=
{
data
:
{
workspace
:
{
id
:
'
1
'
,
workItemTypes
:
{
nodes
:
[
{
id
:
'
work-item-1
'
,
name
:
'
Issue
'
},
{
id
:
'
work-item-2
'
,
name
:
'
Incident
'
},
],
},
},
},
};
spec/frontend/work_items/pages/create_work_item_spec.js
View file @
b19091f3
import
Vue
,
{
nextTick
}
from
'
vue
'
;
import
Vue
,
{
nextTick
}
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
{
GlAlert
,
GlDropdown
,
GlDropdownItem
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
CreateWorkItem
from
'
~/work_items/pages/create_work_item.vue
'
;
import
CreateWorkItem
from
'
~/work_items/pages/create_work_item.vue
'
;
import
ItemTitle
from
'
~/work_items/components/item_title.vue
'
;
import
ItemTitle
from
'
~/work_items/components/item_title.vue
'
;
import
{
resolvers
}
from
'
~/work_items/graphql/resolvers
'
;
import
{
resolvers
}
from
'
~/work_items/graphql/resolvers
'
;
import
projectWorkItemTypesQuery
from
'
~/work_items/graphql/project_work_item_types.query.graphql
'
;
import
{
projectWorkItemTypesQueryResponse
}
from
'
../mock_data
'
;
Vue
.
use
(
VueApollo
);
Vue
.
use
(
VueApollo
);
...
@@ -14,13 +16,20 @@ describe('Create work item component', () => {
...
@@ -14,13 +16,20 @@ describe('Create work item component', () => {
let
wrapper
;
let
wrapper
;
let
fakeApollo
;
let
fakeApollo
;
const
querySuccessHandler
=
jest
.
fn
().
mockResolvedValue
(
projectWorkItemTypesQueryResponse
);
const
findAlert
=
()
=>
wrapper
.
findComponent
(
GlAlert
);
const
findAlert
=
()
=>
wrapper
.
findComponent
(
GlAlert
);
const
findTitleInput
=
()
=>
wrapper
.
findComponent
(
ItemTitle
);
const
findTitleInput
=
()
=>
wrapper
.
findComponent
(
ItemTitle
);
const
findDropdown
=
()
=>
wrapper
.
findComponent
(
GlDropdown
);
const
findDropdownItems
=
()
=>
wrapper
.
findAllComponents
(
GlDropdownItem
);
const
findCreateButton
=
()
=>
wrapper
.
find
(
'
[data-testid="create-button"]
'
);
const
findCreateButton
=
()
=>
wrapper
.
find
(
'
[data-testid="create-button"]
'
);
const
findCancelButton
=
()
=>
wrapper
.
find
(
'
[data-testid="cancel-button"]
'
);
const
findCancelButton
=
()
=>
wrapper
.
find
(
'
[data-testid="cancel-button"]
'
);
const
findContent
=
()
=>
wrapper
.
find
(
'
[data-testid="content"]
'
);
const
findLoadingTypesIcon
=
()
=>
wrapper
.
find
(
'
[data-testid="loading-types"]
'
);
const
createComponent
=
({
data
=
{},
props
=
{}
}
=
{})
=>
{
const
createComponent
=
({
data
=
{},
props
=
{}
,
queryHandler
=
querySuccessHandler
}
=
{})
=>
{
fakeApollo
=
createMockApollo
([],
resolvers
);
fakeApollo
=
createMockApollo
([
[
projectWorkItemTypesQuery
,
queryHandler
]
],
resolvers
);
wrapper
=
shallowMount
(
CreateWorkItem
,
{
wrapper
=
shallowMount
(
CreateWorkItem
,
{
apolloProvider
:
fakeApollo
,
apolloProvider
:
fakeApollo
,
data
()
{
data
()
{
...
@@ -37,6 +46,9 @@ describe('Create work item component', () => {
...
@@ -37,6 +46,9 @@ describe('Create work item component', () => {
push
:
jest
.
fn
(),
push
:
jest
.
fn
(),
},
},
},
},
provide
:
{
fullPath
:
'
full-path
'
,
},
});
});
};
};
...
@@ -84,6 +96,10 @@ describe('Create work item component', () => {
...
@@ -84,6 +96,10 @@ describe('Create work item component', () => {
it
(
'
does not add right margin for cancel button
'
,
()
=>
{
it
(
'
does not add right margin for cancel button
'
,
()
=>
{
expect
(
findCancelButton
().
classes
()).
not
.
toContain
(
'
gl-mr-3
'
);
expect
(
findCancelButton
().
classes
()).
not
.
toContain
(
'
gl-mr-3
'
);
});
});
it
(
'
does not add padding for content
'
,
()
=>
{
expect
(
findContent
().
classes
(
'
gl-px-5
'
)).
toBe
(
false
);
});
});
});
describe
(
'
when displayed in a modal
'
,
()
=>
{
describe
(
'
when displayed in a modal
'
,
()
=>
{
...
@@ -118,6 +134,44 @@ describe('Create work item component', () => {
...
@@ -118,6 +134,44 @@ describe('Create work item component', () => {
it
(
'
adds right margin for cancel button
'
,
()
=>
{
it
(
'
adds right margin for cancel button
'
,
()
=>
{
expect
(
findCancelButton
().
classes
()).
toContain
(
'
gl-mr-3
'
);
expect
(
findCancelButton
().
classes
()).
toContain
(
'
gl-mr-3
'
);
});
});
it
(
'
adds padding for content
'
,
()
=>
{
expect
(
findContent
().
classes
(
'
gl-px-5
'
)).
toBe
(
true
);
});
});
it
(
'
displays a loading icon inside dropdown when work items query is loading
'
,
()
=>
{
createComponent
();
expect
(
findLoadingTypesIcon
().
exists
()).
toBe
(
true
);
});
it
(
'
displays an alert when work items query is rejected
'
,
async
()
=>
{
createComponent
({
queryHandler
:
jest
.
fn
().
mockRejectedValue
(
'
Houston, we have a problem
'
)
});
await
waitForPromises
();
expect
(
findAlert
().
exists
()).
toBe
(
true
);
expect
(
findAlert
().
text
()).
toContain
(
'
fetching work item types
'
);
});
describe
(
'
when work item types are fetched
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
return
waitForPromises
();
});
it
(
'
displays a list of work item types
'
,
()
=>
{
expect
(
findDropdownItems
()).
toHaveLength
(
2
);
expect
(
findDropdownItems
().
at
(
0
).
text
()).
toContain
(
'
Issue
'
);
});
it
(
'
selects a work item type on click
'
,
async
()
=>
{
expect
(
findDropdown
().
props
(
'
text
'
)).
toBe
(
'
Type
'
);
findDropdownItems
().
at
(
0
).
vm
.
$emit
(
'
click
'
);
await
nextTick
();
expect
(
findDropdown
().
props
(
'
text
'
)).
toBe
(
'
Issue
'
);
});
});
});
it
(
'
hides the alert on dismissing the error
'
,
async
()
=>
{
it
(
'
hides the alert on dismissing the error
'
,
async
()
=>
{
...
...
spec/frontend/work_items/router_spec.js
View file @
b19091f3
...
@@ -15,6 +15,16 @@ describe('Work items router', () => {
...
@@ -15,6 +15,16 @@ describe('Work items router', () => {
wrapper
=
mount
(
App
,
{
wrapper
=
mount
(
App
,
{
router
,
router
,
provide
:
{
fullPath
:
'
full-path
'
,
},
mocks
:
{
$apollo
:
{
queries
:
{
workItemTypes
:
{},
},
},
},
});
});
};
};
...
...
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