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
13ffb66e
Commit
13ffb66e
authored
Jul 01, 2021
by
Justin Ho Tuan Duong
Committed by
Sean McGivern
Jul 01, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow users to edit the status of a Jira issue on details page
parent
719697bd
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
473 additions
and
46 deletions
+473
-46
config/feature_flags/development/jira_issue_details_edit_status.yml
...ture_flags/development/jira_issue_details_edit_status.yml
+8
-0
ee/app/assets/javascripts/integrations/jira/issues_show/api.js
...p/assets/javascripts/integrations/jira/issues_show/api.js
+19
-0
ee/app/assets/javascripts/integrations/jira/issues_show/components/jira_issues_show_root.vue
...ons/jira/issues_show/components/jira_issues_show_root.vue
+50
-2
ee/app/assets/javascripts/integrations/jira/issues_show/components/sidebar/issue_field.vue
...tions/jira/issues_show/components/sidebar/issue_field.vue
+87
-9
ee/app/assets/javascripts/integrations/jira/issues_show/components/sidebar/issue_field_dropdown.vue
...a/issues_show/components/sidebar/issue_field_dropdown.vue
+66
-0
ee/app/assets/javascripts/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue
...sues_show/components/sidebar/jira_issues_sidebar_root.vue
+70
-2
ee/app/controllers/projects/integrations/jira/issues_controller.rb
...ntrollers/projects/integrations/jira/issues_controller.rb
+3
-0
ee/spec/frontend/integrations/jira/issues_show/components/jira_issues_show_root_spec.js
...jira/issues_show/components/jira_issues_show_root_spec.js
+49
-6
ee/spec/frontend/integrations/jira/issues_show/components/sidebar/issue_field_dropdown_spec.js
...sues_show/components/sidebar/issue_field_dropdown_spec.js
+57
-0
ee/spec/frontend/integrations/jira/issues_show/components/sidebar/issue_field_spec.js
...s/jira/issues_show/components/sidebar/issue_field_spec.js
+52
-23
ee/spec/frontend/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root_spec.js
..._show/components/sidebar/jira_issues_sidebar_root_spec.js
+1
-4
ee/spec/frontend/integrations/jira/issues_show/mock_data.js
ee/spec/frontend/integrations/jira/issues_show/mock_data.js
+2
-0
locale/gitlab.pot
locale/gitlab.pot
+9
-0
No files found.
config/feature_flags/development/jira_issue_details_edit_status.yml
0 → 100644
View file @
13ffb66e
---
name
:
jira_issue_details_edit_status
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60092
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/330628
milestone
:
'
14.1'
type
:
development
group
:
group::ecosystem
default_enabled
:
false
ee/app/assets/javascripts/integrations/jira/issues_show/api.js
View file @
13ffb66e
...
@@ -5,3 +5,22 @@ export const fetchIssue = async (issuePath) => {
...
@@ -5,3 +5,22 @@ export const fetchIssue = async (issuePath) => {
return
data
;
return
data
;
});
});
};
};
export
const
fetchIssueStatuses
=
()
=>
{
// We are using mock data here which should come from the backend
return
new
Promise
((
resolve
)
=>
{
setTimeout
(()
=>
{
// eslint-disable-next-line @gitlab/require-i18n-strings
resolve
([{
title
:
'
In Progress
'
},
{
title
:
'
Done
'
}]);
},
1000
);
});
};
export
const
updateIssue
=
(
issue
,
{
status
})
=>
{
// We are using mock call here which should become a backend call
return
new
Promise
((
resolve
)
=>
{
setTimeout
(()
=>
{
resolve
({
...
issue
,
status
});
},
1000
);
});
};
ee/app/assets/javascripts/integrations/jira/issues_show/components/jira_issues_show_root.vue
View file @
13ffb66e
...
@@ -7,9 +7,11 @@ import {
...
@@ -7,9 +7,11 @@ import {
GlBadge
,
GlBadge
,
GlTooltipDirective
as
GlTooltip
,
GlTooltipDirective
as
GlTooltip
,
}
from
'
@gitlab/ui
'
;
}
from
'
@gitlab/ui
'
;
import
{
fetchIssue
}
from
'
ee/integrations/jira/issues_show/api
'
;
import
{
fetchIssue
,
fetchIssueStatuses
,
updateIssue
}
from
'
ee/integrations/jira/issues_show/api
'
;
import
JiraIssueSidebar
from
'
ee/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue
'
;
import
JiraIssueSidebar
from
'
ee/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue
'
;
import
{
issueStates
,
issueStateLabels
}
from
'
ee/integrations/jira/issues_show/constants
'
;
import
{
issueStates
,
issueStateLabels
}
from
'
ee/integrations/jira/issues_show/constants
'
;
import
createFlash
from
'
~/flash
'
;
import
IssuableShow
from
'
~/issuable_show/components/issuable_show_root.vue
'
;
import
IssuableShow
from
'
~/issuable_show/components/issuable_show_root.vue
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
s__
}
from
'
~/locale
'
;
...
@@ -38,8 +40,11 @@ export default {
...
@@ -38,8 +40,11 @@ export default {
data
()
{
data
()
{
return
{
return
{
isLoading
:
true
,
isLoading
:
true
,
isLoadingStatus
:
false
,
isUpdatingStatus
:
false
,
errorMessage
:
null
,
errorMessage
:
null
,
issue
:
{},
issue
:
{},
statuses
:
[],
};
};
},
},
computed
:
{
computed
:
{
...
@@ -78,6 +83,41 @@ export default {
...
@@ -78,6 +83,41 @@ export default {
jiraIssueCommentId
(
id
)
{
jiraIssueCommentId
(
id
)
{
return
`jira_note_
${
id
}
`
;
return
`jira_note_
${
id
}
`
;
},
},
onIssueStatusFetch
()
{
this
.
isLoadingStatus
=
true
;
fetchIssueStatuses
()
.
then
((
response
)
=>
{
this
.
statuses
=
response
;
})
.
catch
(()
=>
{
createFlash
({
message
:
s__
(
'
JiraService|Failed to load Jira issue statuses. View the issue in Jira, or reload the page.
'
,
),
});
})
.
finally
(()
=>
{
this
.
isLoadingStatus
=
false
;
});
},
onIssueStatusUpdated
(
status
)
{
this
.
isUpdatingStatus
=
true
;
updateIssue
(
this
.
issue
,
{
status
})
.
then
(()
=>
{
this
.
issue
=
{
...
this
.
issue
,
status
};
})
.
catch
(()
=>
{
createFlash
({
message
:
s__
(
'
JiraService|Failed to update Jira issue status. View the issue in Jira, or reload the page.
'
,
),
});
})
.
finally
(()
=>
{
this
.
isUpdatingStatus
=
false
;
});
},
},
},
};
};
</
script
>
</
script
>
...
@@ -117,7 +157,15 @@ export default {
...
@@ -117,7 +157,15 @@ export default {
<
template
#status-badge
>
{{
statusBadgeText
}}
</
template
>
<
template
#status-badge
>
{{
statusBadgeText
}}
</
template
>
<
template
#right-sidebar-items=
"{ sidebarExpanded }"
>
<
template
#right-sidebar-items=
"{ sidebarExpanded }"
>
<jira-issue-sidebar
:sidebar-expanded=
"sidebarExpanded"
:issue=
"issue"
/>
<jira-issue-sidebar
:sidebar-expanded=
"sidebarExpanded"
:issue=
"issue"
:is-loading-status=
"isLoadingStatus"
:is-updating-status=
"isUpdatingStatus"
:statuses=
"statuses"
@
issue-status-fetch=
"onIssueStatusFetch"
@
issue-status-updated=
"onIssueStatusUpdated"
/>
</
template
>
</
template
>
<
template
#discussion
>
<
template
#discussion
>
...
...
ee/app/assets/javascripts/integrations/jira/issues_show/components/sidebar/issue_field.vue
View file @
13ffb66e
<
script
>
<
script
>
import
{
GlIcon
,
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
{
GlIcon
,
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
import
SidebarEditableItem
from
'
~/sidebar/components/sidebar_editable_item.vue
'
;
import
IssueFieldDropdown
from
'
./issue_field_dropdown.vue
'
;
export
default
{
export
default
{
directives
:
{
directives
:
{
...
@@ -8,12 +10,50 @@ export default {
...
@@ -8,12 +10,50 @@ export default {
},
},
components
:
{
components
:
{
GlIcon
,
GlIcon
,
IssueFieldDropdown
,
SidebarEditableItem
,
},
provide
()
{
return
{
isClassicSidebar
:
true
,
canUpdate
:
this
.
canUpdate
,
};
},
},
props
:
{
props
:
{
canUpdate
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
dropdownEmpty
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
dropdownTitle
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
icon
:
{
icon
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
items
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
loading
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
updating
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
title
:
{
title
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
...
@@ -44,20 +84,58 @@ export default {
...
@@ -44,20 +84,58 @@ export default {
i18n
:
{
i18n
:
{
none
:
__
(
'
None
'
),
none
:
__
(
'
None
'
),
},
},
methods
:
{
showDropdown
()
{
this
.
$refs
.
dropdown
.
showDropdown
();
this
.
$emit
(
'
issue-field-fetch
'
);
},
expandSidebarAndOpenDropdown
()
{
this
.
$emit
(
'
expand-sidebar
'
,
this
.
$refs
.
editableItem
);
},
onIssueFieldUpdated
(
value
)
{
this
.
$emit
(
'
issue-field-updated
'
,
value
);
},
},
};
};
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"block"
>
<div
class=
"block"
>
<div
v-gl-tooltip=
"tooltipProps"
class=
"sidebar-collapsed-icon"
data-testid=
"field-collapsed"
>
<sidebar-editable-item
<gl-icon
:name=
"icon"
/>
ref=
"editableItem"
</div>
:loading=
"updating"
:title=
"title"
@
open=
"showDropdown"
>
<template
#collapsed
>
<div
v-gl-tooltip=
"tooltipProps"
class=
"sidebar-collapsed-icon"
data-testid=
"field-collapsed"
@
click=
"expandSidebarAndOpenDropdown"
>
<gl-icon
:name=
"icon"
/>
</div>
<div
class=
"hide-collapsed"
>
<div
class=
"value"
data-testid=
"field-value"
>
<span
:class=
"valueClass"
>
{{
valueWithFallback
}}
</span>
</div>
</div>
</
template
>
<div
class=
"hide-collapsed"
>
<
template
#default
>
<div
class=
"title"
data-testid=
"field-title"
>
{{
title
}}
</div>
<issue-field-dropdown
<div
class=
"value"
>
v-if=
"canUpdate"
<span
:class=
"valueClass"
data-testid=
"field-value"
>
{{
valueWithFallback
}}
</span>
ref=
"dropdown"
</div>
:empty-text=
"dropdownEmpty"
</div>
:items=
"items"
:loading=
"loading"
:text=
"valueWithFallback"
:title=
"dropdownTitle"
@
issue-field-updated=
"onIssueFieldUpdated"
/>
</
template
>
</sidebar-editable-item>
</div>
</div>
</template>
</template>
ee/app/assets/javascripts/integrations/jira/issues_show/components/sidebar/issue_field_dropdown.vue
0 → 100644
View file @
13ffb66e
<
script
>
import
{
GlDropdown
,
GlDropdownItem
,
GlDropdownText
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
export
default
{
components
:
{
GlDropdown
,
GlDropdownItem
,
GlDropdownText
,
GlLoadingIcon
,
},
props
:
{
emptyText
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
items
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
loading
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
text
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
title
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
},
computed
:
{
noItems
()
{
return
this
.
items
.
length
===
0
;
},
},
methods
:
{
showDropdown
()
{
this
.
$refs
.
dropdown
.
show
();
},
selectItem
(
item
)
{
this
.
$emit
(
'
issue-field-updated
'
,
item
.
title
);
},
},
};
</
script
>
<
template
>
<gl-dropdown
ref=
"dropdown"
:text=
"text"
:header-text=
"title"
block
lazy
>
<div
v-if=
"loading"
class=
"gl-h-13"
>
<gl-loading-icon
size=
"md"
/>
</div>
<div
v-else
>
<gl-dropdown-text
v-if=
"noItems"
>
{{
emptyText
}}
</gl-dropdown-text>
<gl-dropdown-item
v-for=
"item in items"
:key=
"item.title"
@
click=
"selectItem(item)"
>
{{
item
.
title
}}
</gl-dropdown-item>
</div>
</gl-dropdown>
</
template
>
ee/app/assets/javascripts/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue
View file @
13ffb66e
<
script
>
<
script
>
import
{
labelsFilterParam
}
from
'
ee/integrations/jira/issues_show/constants
'
;
import
{
labelsFilterParam
}
from
'
ee/integrations/jira/issues_show/constants
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
CopyableField
from
'
~/vue_shared/components/sidebar/copyable_field.vue
'
;
import
CopyableField
from
'
~/vue_shared/components/sidebar/copyable_field.vue
'
;
import
LabelsSelect
from
'
~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue
'
;
import
LabelsSelect
from
'
~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue
'
;
import
glFeatureFlagsMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
Assignee
from
'
./assignee.vue
'
;
import
Assignee
from
'
./assignee.vue
'
;
import
IssueDueDate
from
'
./issue_due_date.vue
'
;
import
IssueDueDate
from
'
./issue_due_date.vue
'
;
import
IssueField
from
'
./issue_field.vue
'
;
import
IssueField
from
'
./issue_field.vue
'
;
...
@@ -16,6 +17,7 @@ export default {
...
@@ -16,6 +17,7 @@ export default {
CopyableField
,
CopyableField
,
LabelsSelect
,
LabelsSelect
,
},
},
mixins
:
[
glFeatureFlagsMixin
()],
inject
:
{
inject
:
{
issuesListPath
:
{
issuesListPath
:
{
default
:
null
,
default
:
null
,
...
@@ -30,6 +32,21 @@ export default {
...
@@ -30,6 +32,21 @@ export default {
type
:
Object
,
type
:
Object
,
required
:
true
,
required
:
true
,
},
},
isLoadingStatus
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
isUpdatingStatus
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
statuses
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
},
},
computed
:
{
computed
:
{
assignee
()
{
assignee
()
{
...
@@ -39,12 +56,50 @@ export default {
...
@@ -39,12 +56,50 @@ export default {
reference
()
{
reference
()
{
return
this
.
issue
.
references
?.
relative
;
return
this
.
issue
.
references
?.
relative
;
},
},
canUpdateStatus
()
{
return
this
.
glFeatures
.
jiraIssueDetailsEditStatus
;
},
},
},
labelsFilterParam
,
labelsFilterParam
,
i18n
:
{
i18n
:
{
statusTitle
:
__
(
'
Status
'
),
statusTitle
:
__
(
'
Status
'
),
statusDropdownEmpty
:
s__
(
'
JiraService|No available statuses
'
),
statusDropdownTitle
:
__
(
'
Change status
'
),
referenceName
:
__
(
'
Reference
'
),
referenceName
:
__
(
'
Reference
'
),
},
},
mounted
()
{
this
.
sidebarEl
=
document
.
querySelector
(
'
aside.right-sidebar
'
);
this
.
sidebarToggleEl
=
document
.
querySelector
(
'
.js-toggle-right-sidebar-button
'
);
},
methods
:
{
toggleSidebar
()
{
this
.
sidebarToggleEl
.
dispatchEvent
(
new
Event
(
'
click
'
));
},
expandSidebarAndOpenDropdown
(
dropdownRef
=
null
)
{
// Expand the sidebar if not already expanded.
if
(
!
this
.
sidebarExpanded
)
{
this
.
toggleSidebar
();
}
if
(
dropdownRef
)
{
// Wait for sidebar expand animation to complete
// before revealing the dropdown.
this
.
sidebarEl
.
addEventListener
(
'
transitionend
'
,
()
=>
{
dropdownRef
.
expand
();
},
{
once
:
true
},
);
}
},
onIssueStatusFetch
()
{
this
.
$emit
(
'
issue-status-fetch
'
);
},
onIssueStatusUpdated
(
status
)
{
this
.
$emit
(
'
issue-status-updated
'
,
status
);
},
},
};
};
</
script
>
</
script
>
...
@@ -52,7 +107,20 @@ export default {
...
@@ -52,7 +107,20 @@ export default {
<div>
<div>
<assignee
class=
"block"
:assignee=
"assignee"
/>
<assignee
class=
"block"
:assignee=
"assignee"
/>
<issue-due-date
:due-date=
"issue.dueDate"
/>
<issue-due-date
:due-date=
"issue.dueDate"
/>
<issue-field
icon=
"progress"
:title=
"$options.i18n.statusTitle"
:value=
"issue.status"
/>
<issue-field
icon=
"progress"
:can-update=
"canUpdateStatus"
:dropdown-title=
"$options.i18n.statusDropdownTitle"
:dropdown-empty=
"$options.i18n.statusDropdownEmpty"
:items=
"statuses"
:loading=
"isLoadingStatus"
:title=
"$options.i18n.statusTitle"
:updating=
"isUpdatingStatus"
:value=
"issue.status"
@
expand-sidebar=
"expandSidebarAndOpenDropdown"
@
issue-field-fetch=
"onIssueStatusFetch"
@
issue-field-updated=
"onIssueStatusUpdated"
/>
<labels-select
<labels-select
:selected-labels=
"issue.labels"
:selected-labels=
"issue.labels"
:labels-filter-base-path=
"issuesListPath"
:labels-filter-base-path=
"issuesListPath"
...
...
ee/app/controllers/projects/integrations/jira/issues_controller.rb
View file @
13ffb66e
...
@@ -13,6 +13,9 @@ module Projects
...
@@ -13,6 +13,9 @@ module Projects
name:
'i_ecosystem_jira_service_list_issues'
name:
'i_ecosystem_jira_service_list_issues'
before_action
:check_feature_enabled!
before_action
:check_feature_enabled!
before_action
only: :show
do
push_frontend_feature_flag
(
:jira_issue_details_edit_status
,
project
,
default_enabled: :yaml
)
end
rescue_from
::
Projects
::
Integrations
::
Jira
::
IssuesFinder
::
IntegrationError
,
with: :render_integration_error
rescue_from
::
Projects
::
Integrations
::
Jira
::
IssuesFinder
::
IntegrationError
,
with: :render_integration_error
rescue_from
::
Projects
::
Integrations
::
Jira
::
IssuesFinder
::
RequestError
,
with: :render_request_error
rescue_from
::
Projects
::
Integrations
::
Jira
::
IssuesFinder
::
RequestError
,
with: :render_request_error
...
...
ee/spec/frontend/integrations/jira/issues_show/components/jira_issues_show_root_spec.js
View file @
13ffb66e
import
{
GlAlert
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
{
GlAlert
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
*
as
JiraIssuesShowApi
from
'
ee/integrations/jira/issues_show/api
'
;
import
JiraIssuesShow
from
'
ee/integrations/jira/issues_show/components/jira_issues_show_root.vue
'
;
import
JiraIssuesShow
from
'
ee/integrations/jira/issues_show/components/jira_issues_show_root.vue
'
;
import
JiraIssueSidebar
from
'
ee/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue
'
;
import
{
issueStates
}
from
'
ee/integrations/jira/issues_show/constants
'
;
import
{
issueStates
}
from
'
ee/integrations/jira/issues_show/constants
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
IssuableHeader
from
'
~/issuable_show/components/issuable_header.vue
'
;
import
IssuableHeader
from
'
~/issuable_show/components/issuable_header.vue
'
;
import
IssuableShow
from
'
~/issuable_show/components/issuable_show_root.vue
'
;
import
IssuableShow
from
'
~/issuable_show/components/issuable_show_root.vue
'
;
import
IssuableSidebar
from
'
~/issuable_sidebar/components/issuable_sidebar_root.vue
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
mockJiraIssue
}
from
'
../mock_data
'
;
import
{
mockJiraIssue
}
from
'
../mock_data
'
;
...
@@ -18,14 +22,16 @@ describe('JiraIssuesShow', () => {
...
@@ -18,14 +22,16 @@ describe('JiraIssuesShow', () => {
const
findGlAlert
=
()
=>
wrapper
.
findComponent
(
GlAlert
);
const
findGlAlert
=
()
=>
wrapper
.
findComponent
(
GlAlert
);
const
findGlLoadingIcon
=
()
=>
wrapper
.
findComponent
(
GlLoadingIcon
);
const
findGlLoadingIcon
=
()
=>
wrapper
.
findComponent
(
GlLoadingIcon
);
const
findIssuableShow
=
()
=>
wrapper
.
findComponent
(
IssuableShow
);
const
findIssuableShow
=
()
=>
wrapper
.
findComponent
(
IssuableShow
);
const
findJiraIssueSidebar
=
()
=>
wrapper
.
findComponent
(
JiraIssueSidebar
);
const
findIssuableShowStatusBadge
=
()
=>
const
findIssuableShowStatusBadge
=
()
=>
wrapper
.
findComponent
(
IssuableHeader
).
find
(
'
[data-testid="status"]
'
);
wrapper
.
findComponent
(
IssuableHeader
).
find
(
'
[data-testid="status"]
'
);
const
createComponent
=
()
=>
{
const
createComponent
=
()
=>
{
wrapper
=
shallowMount
(
JiraIssuesShow
,
{
wrapper
=
shallowMount
(
JiraIssuesShow
,
{
stubs
:
{
stubs
:
{
IssuableShow
,
IssuableHeader
,
IssuableHeader
,
IssuableShow
,
IssuableSidebar
,
},
},
provide
:
{
provide
:
{
issuesShowPath
:
mockJiraIssuesShowPath
,
issuesShowPath
:
mockJiraIssuesShowPath
,
...
@@ -39,11 +45,7 @@ describe('JiraIssuesShow', () => {
...
@@ -39,11 +45,7 @@ describe('JiraIssuesShow', () => {
afterEach
(()
=>
{
afterEach
(()
=>
{
mockAxios
.
restore
();
mockAxios
.
restore
();
wrapper
.
destroy
();
if
(
wrapper
)
{
wrapper
.
destroy
();
wrapper
=
null
;
}
});
});
describe
(
'
when issue is loading
'
,
()
=>
{
describe
(
'
when issue is loading
'
,
()
=>
{
...
@@ -109,4 +111,45 @@ describe('JiraIssuesShow', () => {
...
@@ -109,4 +111,45 @@ describe('JiraIssuesShow', () => {
expect
(
findIssuableShowStatusBadge
().
text
()).
toBe
(
badgeText
);
expect
(
findIssuableShowStatusBadge
().
text
()).
toBe
(
badgeText
);
});
});
});
});
describe
(
'
JiraIssueSidebar events
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockAxios
.
onGet
(
mockJiraIssuesShowPath
).
replyOnce
(
200
,
mockJiraIssue
);
createComponent
();
await
waitForPromises
();
});
it
(
'
fetches issue statuses on issue-status-fetch
'
,
async
()
=>
{
const
fetchIssueStatusesSpy
=
jest
.
spyOn
(
JiraIssuesShowApi
,
'
fetchIssueStatuses
'
)
.
mockResolvedValue
();
findJiraIssueSidebar
().
vm
.
$emit
(
'
issue-status-fetch
'
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
fetchIssueStatusesSpy
).
toHaveBeenCalled
();
expect
(
findJiraIssueSidebar
().
props
(
'
isLoadingStatus
'
)).
toBe
(
true
);
await
waitForPromises
();
expect
(
findJiraIssueSidebar
().
props
(
'
isLoadingStatus
'
)).
toBe
(
false
);
});
it
(
'
updates issue status on issue-status-updated
'
,
async
()
=>
{
const
updateIssueSpy
=
jest
.
spyOn
(
JiraIssuesShowApi
,
'
updateIssue
'
).
mockResolvedValue
();
const
status
=
'
In Review
'
;
findJiraIssueSidebar
().
vm
.
$emit
(
'
issue-status-updated
'
,
status
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
updateIssueSpy
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
{
status
});
expect
(
findJiraIssueSidebar
().
props
(
'
isUpdatingStatus
'
)).
toBe
(
true
);
await
waitForPromises
();
expect
(
findJiraIssueSidebar
().
props
(
'
isUpdatingStatus
'
)).
toBe
(
false
);
});
});
});
});
ee/spec/frontend/integrations/jira/issues_show/components/sidebar/issue_field_dropdown_spec.js
0 → 100644
View file @
13ffb66e
import
{
GlDropdownItem
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
IssueFieldDropdown
from
'
ee/integrations/jira/issues_show/components/sidebar/issue_field_dropdown.vue
'
;
import
{
mockJiraIssueStatuses
}
from
'
../../mock_data
'
;
describe
(
'
IssueFieldDropdown
'
,
()
=>
{
let
wrapper
;
const
emptyText
=
'
empty text
'
;
const
defaultProps
=
{
emptyText
,
text
:
'
issue field text
'
,
title
:
'
issue field header text
'
,
};
const
createComponent
=
({
props
=
{}
}
=
{})
=>
{
wrapper
=
shallowMount
(
IssueFieldDropdown
,
{
propsData
:
{
...
defaultProps
,
...
props
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
});
const
findAllGlDropdownItems
=
()
=>
wrapper
.
findAllComponents
(
GlDropdownItem
);
const
findGlLoadingIcon
=
()
=>
wrapper
.
findComponent
(
GlLoadingIcon
);
it
.
each
`
loading | items
${
true
}
|
${[]}
${
true
}
|
${
mockJiraIssueStatuses
}
${
false
}
|
${[]}
${
false
}
|
${
mockJiraIssueStatuses
}
`
(
'
with loading = $loading, items = $items
'
,
({
loading
,
items
})
=>
{
createComponent
({
props
:
{
loading
,
items
,
},
});
expect
(
findGlLoadingIcon
().
exists
()).
toBe
(
loading
);
if
(
!
loading
)
{
if
(
items
.
length
)
{
findAllGlDropdownItems
().
wrappers
.
forEach
((
itemWrapper
,
index
)
=>
{
expect
(
itemWrapper
.
text
()).
toBe
(
mockJiraIssueStatuses
[
index
].
title
);
});
}
else
{
expect
(
wrapper
.
text
()).
toBe
(
emptyText
);
}
}
});
});
ee/spec/frontend/integrations/jira/issues_show/components/sidebar/issue_field_spec.js
View file @
13ffb66e
import
{
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
GlButton
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
IssueField
from
'
ee/integrations/jira/issues_show/components/sidebar/issue_field.vue
'
;
import
IssueField
from
'
ee/integrations/jira/issues_show/components/sidebar/issue_field.vue
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
{
extendedWrapper
}
from
'
helpers/vue_test_utils_helper
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
SidebarEditableItem
from
'
~/sidebar/components/sidebar_editable_item.vue
'
;
describe
(
'
IssueField
'
,
()
=>
{
describe
(
'
IssueField
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
...
@@ -15,37 +16,44 @@ describe('IssueField', () => {
...
@@ -15,37 +16,44 @@ describe('IssueField', () => {
};
};
const
createComponent
=
({
props
=
{}
}
=
{})
=>
{
const
createComponent
=
({
props
=
{}
}
=
{})
=>
{
wrapper
=
extendedWrapper
(
wrapper
=
shallowMountExtended
(
IssueField
,
{
shallowMount
(
IssueField
,
{
directives
:
{
propsData
:
{
...
defaultProps
,
...
props
},
GlTooltip
:
createMockDirective
(),
directives
:
{
},
GlTooltip
:
createMockDirective
(),
propsData
:
{
...
defaultProps
,
...
props
},
},
stubs
:
{
}),
SidebarEditableItem
,
);
},
});
};
};
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
wrapper
=
null
;
});
});
const
find
FieldTitle
=
()
=>
wrapper
.
findByTestId
(
'
field-title
'
);
const
find
EditableItem
=
()
=>
wrapper
.
findComponent
(
SidebarEditableItem
);
const
find
FieldValue
=
()
=>
wrapper
.
findByTestId
(
'
field-value
'
);
const
find
EditButton
=
()
=>
wrapper
.
findComponent
(
GlButton
);
const
findFieldCollapsed
=
()
=>
wrapper
.
findByTestId
(
'
field-collapsed
'
);
const
findFieldCollapsed
=
()
=>
wrapper
.
findByTestId
(
'
field-collapsed
'
);
const
findFieldCollapsedTooltip
=
()
=>
getBinding
(
findFieldCollapsed
().
element
,
'
gl-tooltip
'
);
const
findFieldCollapsedTooltip
=
()
=>
getBinding
(
findFieldCollapsed
().
element
,
'
gl-tooltip
'
);
const
findFieldValue
=
()
=>
wrapper
.
findByTestId
(
'
field-value
'
);
const
findGlIcon
=
()
=>
wrapper
.
findComponent
(
GlIcon
);
const
findGlIcon
=
()
=>
wrapper
.
findComponent
(
GlIcon
);
it
(
'
renders title
'
,
()
=>
{
describe
(
'
template
'
,
()
=>
{
createComponent
();
beforeEach
(()
=>
{
createComponent
();
});
expect
(
findFieldTitle
().
text
()).
toBe
(
defaultProps
.
title
);
it
(
'
renders title
'
,
()
=>
{
});
expect
(
findEditableItem
().
props
(
'
title
'
)).
toBe
(
defaultProps
.
title
);
});
it
(
'
renders GlIcon (when collapsed)
'
,
()
=>
{
it
(
'
renders GlIcon (when collapsed)
'
,
()
=>
{
createComponent
();
expect
(
findGlIcon
().
props
(
'
name
'
)).
toBe
(
defaultProps
.
icon
);
});
expect
(
findGlIcon
().
props
(
'
name
'
)).
toBe
(
defaultProps
.
icon
);
it
(
'
does not render "Edit" button
'
,
()
=>
{
expect
(
findEditButton
().
exists
()).
toBe
(
false
);
});
});
});
describe
(
'
without value prop
'
,
()
=>
{
describe
(
'
without value prop
'
,
()
=>
{
...
@@ -53,7 +61,7 @@ describe('IssueField', () => {
...
@@ -53,7 +61,7 @@ describe('IssueField', () => {
createComponent
();
createComponent
();
});
});
it
(
'
renders fallback value with "no-value" class
'
,
()
=>
{
it
(
'
falls back to "None"
'
,
()
=>
{
expect
(
findFieldValue
().
text
()).
toBe
(
'
None
'
);
expect
(
findFieldValue
().
text
()).
toBe
(
'
None
'
);
});
});
...
@@ -74,7 +82,7 @@ describe('IssueField', () => {
...
@@ -74,7 +82,7 @@ describe('IssueField', () => {
});
});
});
});
it
(
'
renders value
'
,
()
=>
{
it
(
'
renders
the
value
'
,
()
=>
{
expect
(
findFieldValue
().
text
()).
toBe
(
value
);
expect
(
findFieldValue
().
text
()).
toBe
(
value
);
});
});
...
@@ -85,4 +93,25 @@ describe('IssueField', () => {
...
@@ -85,4 +93,25 @@ describe('IssueField', () => {
expect
(
tooltip
.
value
.
title
).
toBe
(
value
);
expect
(
tooltip
.
value
.
title
).
toBe
(
value
);
});
});
});
});
describe
(
'
with canUpdate = true
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
props
:
{
canUpdate
:
true
},
});
});
it
(
'
renders "Edit" button
'
,
()
=>
{
expect
(
findEditButton
().
text
()).
toBe
(
'
Edit
'
);
});
it
(
'
emits "issue-field-fetch" when dropdown is opened
'
,
()
=>
{
wrapper
.
vm
.
$refs
.
dropdown
.
showDropdown
=
jest
.
fn
();
findEditableItem
().
vm
.
$emit
(
'
open
'
);
expect
(
wrapper
.
vm
.
$refs
.
dropdown
.
showDropdown
).
toHaveBeenCalled
();
expect
(
wrapper
.
emitted
(
'
issue-field-fetch
'
)).
toHaveLength
(
1
);
});
});
});
});
ee/spec/frontend/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root_spec.js
View file @
13ffb66e
...
@@ -25,10 +25,7 @@ describe('JiraIssuesSidebar', () => {
...
@@ -25,10 +25,7 @@ describe('JiraIssuesSidebar', () => {
};
};
afterEach
(()
=>
{
afterEach
(()
=>
{
if
(
wrapper
)
{
wrapper
.
destroy
();
wrapper
.
destroy
();
wrapper
=
null
;
}
});
});
const
findLabelsSelect
=
()
=>
wrapper
.
findComponent
(
LabelsSelect
);
const
findLabelsSelect
=
()
=>
wrapper
.
findComponent
(
LabelsSelect
);
...
...
ee/spec/frontend/integrations/jira/issues_show/mock_data.js
View file @
13ffb66e
...
@@ -41,3 +41,5 @@ export const mockJiraIssueComment = {
...
@@ -41,3 +41,5 @@ export const mockJiraIssueComment = {
},
},
id
:
10000
,
id
:
10000
,
};
};
export
const
mockJiraIssueStatuses
=
[{
title
:
'
In Progress
'
},
{
title
:
'
Done
'
}];
locale/gitlab.pot
View file @
13ffb66e
...
@@ -18394,9 +18394,15 @@ msgstr ""
...
@@ -18394,9 +18394,15 @@ msgstr ""
msgid "JiraService|Events for %{noteable_model_name} are disabled."
msgid "JiraService|Events for %{noteable_model_name} are disabled."
msgstr ""
msgstr ""
msgid "JiraService|Failed to load Jira issue statuses. View the issue in Jira, or reload the page."
msgstr ""
msgid "JiraService|Failed to load Jira issue. View the issue in Jira, or reload the page."
msgid "JiraService|Failed to load Jira issue. View the issue in Jira, or reload the page."
msgstr ""
msgstr ""
msgid "JiraService|Failed to update Jira issue status. View the issue in Jira, or reload the page."
msgstr ""
msgid "JiraService|Fetch issue types for this Jira project"
msgid "JiraService|Fetch issue types for this Jira project"
msgstr ""
msgstr ""
...
@@ -18445,6 +18451,9 @@ msgstr ""
...
@@ -18445,6 +18451,9 @@ msgstr ""
msgid "JiraService|Move to Done"
msgid "JiraService|Move to Done"
msgstr ""
msgstr ""
msgid "JiraService|No available statuses"
msgstr ""
msgid "JiraService|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}Jira%{linkEnd}."
msgid "JiraService|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}Jira%{linkEnd}."
msgstr ""
msgstr ""
...
...
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