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
91783e1a
Commit
91783e1a
authored
Nov 02, 2020
by
Kushal Pandya
Committed by
Natalia Tepluhina
Nov 02, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add Jira app feature flag and mount-point
- Adds `jira_issues_list` feature flag - Adds mount-point for new app.
parent
8b043949
Changes
19
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
1022 additions
and
23 deletions
+1022
-23
app/assets/javascripts/issuable_list/components/issuable_item.vue
...ts/javascripts/issuable_list/components/issuable_item.vue
+4
-1
app/assets/javascripts/issuable_list/components/issuable_list_root.vue
...vascripts/issuable_list/components/issuable_list_root.vue
+27
-5
app/assets/javascripts/issuable_list/constants.js
app/assets/javascripts/issuable_list/constants.js
+2
-0
app/assets/javascripts/pages/projects/integrations/jira/issues/index/index.js
...ts/pages/projects/integrations/jira/issues/index/index.js
+0
-5
config/feature_flags/development/jira_issues_list.yml
config/feature_flags/development/jira_issues_list.yml
+7
-0
ee/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js
...vascripts/filtered_search/recent_searches_storage_keys.js
+1
-0
ee/app/assets/javascripts/integrations/jira/issues_list/components/jira_issues_list_empty_state.vue
...a/issues_list/components/jira_issues_list_empty_state.vue
+71
-0
ee/app/assets/javascripts/integrations/jira/issues_list/components/jira_issues_list_root.vue
...ons/jira/issues_list/components/jira_issues_list_root.vue
+231
-0
ee/app/assets/javascripts/integrations/jira/issues_list/jira_issues_list_bundle.js
.../integrations/jira/issues_list/jira_issues_list_bundle.js
+42
-0
ee/app/assets/javascripts/pages/projects/integrations/jira/issues/index.js
...ascripts/pages/projects/integrations/jira/issues/index.js
+12
-0
ee/app/controllers/projects/integrations/jira/issues_controller.rb
...ntrollers/projects/integrations/jira/issues_controller.rb
+1
-0
ee/app/views/projects/integrations/jira/issues/index.html.haml
...p/views/projects/integrations/jira/issues/index.html.haml
+18
-9
ee/spec/features/integrations/jira/jira_issues_list_spec.rb
ee/spec/features/integrations/jira/jira_issues_list_spec.rb
+1
-0
ee/spec/frontend/integrations/jira/issues_list/components/jira_issues_list_empty_state_spec.js
...sues_list/components/jira_issues_list_empty_state_spec.js
+203
-0
ee/spec/frontend/integrations/jira/issues_list/components/jira_issues_list_root_spec.js
...jira/issues_list/components/jira_issues_list_root_spec.js
+263
-0
ee/spec/frontend/integrations/jira/issues_list/mock_data.js
ee/spec/frontend/integrations/jira/issues_list/mock_data.js
+81
-0
locale/gitlab.pot
locale/gitlab.pot
+12
-0
spec/frontend/issuable_list/components/issuable_item_spec.js
spec/frontend/issuable_list/components/issuable_item_spec.js
+18
-0
spec/frontend/issuable_list/components/issuable_list_root_spec.js
...ntend/issuable_list/components/issuable_list_root_spec.js
+28
-3
No files found.
app/assets/javascripts/issuable_list/components/issuable_item.vue
View file @
91783e1a
...
@@ -116,7 +116,8 @@ export default {
...
@@ -116,7 +116,8 @@ export default {
<div
data-testid=
"issuable-title"
class=
"issue-title title"
>
<div
data-testid=
"issuable-title"
class=
"issue-title title"
>
<span
class=
"issue-title-text"
dir=
"auto"
>
<span
class=
"issue-title-text"
dir=
"auto"
>
<gl-link
:href=
"issuable.webUrl"
v-bind=
"issuableTitleProps"
<gl-link
:href=
"issuable.webUrl"
v-bind=
"issuableTitleProps"
>
{{
issuable
.
title
}}
<gl-icon
v-if=
"isIssuableUrlExternal"
name=
"external-link"
>
{{
issuable
.
title
}}
<gl-icon
v-if=
"isIssuableUrlExternal"
name=
"external-link"
class=
"gl-ml-2"
/></gl-link>
/></gl-link>
</span>
</span>
</div>
</div>
...
@@ -134,7 +135,9 @@ export default {
...
@@ -134,7 +135,9 @@ export default {
>
{{
createdAt
}}
</span
>
{{
createdAt
}}
</span
>
>
{{
__
(
'
by
'
)
}}
{{
__
(
'
by
'
)
}}
<slot
v-if=
"hasSlotContents('author')"
name=
"author"
></slot>
<gl-link
<gl-link
v-else
:data-user-id=
"authorId"
:data-user-id=
"authorId"
:data-username=
"author.username"
:data-username=
"author.username"
:data-name=
"author.name"
:data-name=
"author.name"
...
...
app/assets/javascripts/issuable_list/components/issuable_list_root.vue
View file @
91783e1a
<
script
>
<
script
>
import
{
Gl
LoadingIcon
,
GlPagination
}
from
'
@gitlab/ui
'
;
import
{
Gl
SkeletonLoading
,
GlPagination
}
from
'
@gitlab/ui
'
;
import
{
updateHistory
,
setUrlParams
}
from
'
~/lib/utils/url_utility
'
;
import
{
updateHistory
,
setUrlParams
}
from
'
~/lib/utils/url_utility
'
;
import
FilteredSearchBar
from
'
~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
'
;
import
FilteredSearchBar
from
'
~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
'
;
...
@@ -7,9 +7,11 @@ import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filte
...
@@ -7,9 +7,11 @@ import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filte
import
IssuableTabs
from
'
./issuable_tabs.vue
'
;
import
IssuableTabs
from
'
./issuable_tabs.vue
'
;
import
IssuableItem
from
'
./issuable_item.vue
'
;
import
IssuableItem
from
'
./issuable_item.vue
'
;
import
{
DEFAULT_SKELETON_COUNT
}
from
'
../constants
'
;
export
default
{
export
default
{
components
:
{
components
:
{
Gl
LoadingIcon
,
Gl
SkeletonLoading
,
IssuableTabs
,
IssuableTabs
,
FilteredSearchBar
,
FilteredSearchBar
,
IssuableItem
,
IssuableItem
,
...
@@ -88,7 +90,7 @@ export default {
...
@@ -88,7 +90,7 @@ export default {
required
:
false
,
required
:
false
,
default
:
20
,
default
:
20
,
},
},
total
Page
s
:
{
total
Item
s
:
{
type
:
Number
,
type
:
Number
,
required
:
false
,
required
:
false
,
default
:
0
,
default
:
0
,
...
@@ -114,6 +116,19 @@ export default {
...
@@ -114,6 +116,19 @@ export default {
default
:
true
,
default
:
true
,
},
},
},
},
computed
:
{
skeletonItemCount
()
{
const
{
totalItems
,
defaultPageSize
,
currentPage
}
=
this
;
const
totalPages
=
Math
.
ceil
(
totalItems
/
defaultPageSize
);
if
(
totalPages
)
{
return
currentPage
<
totalPages
?
defaultPageSize
:
totalItems
%
defaultPageSize
||
defaultPageSize
;
}
return
DEFAULT_SKELETON_COUNT
;
},
},
watch
:
{
watch
:
{
urlParams
:
{
urlParams
:
{
deep
:
true
,
deep
:
true
,
...
@@ -157,7 +172,11 @@ export default {
...
@@ -157,7 +172,11 @@ export default {
@
onSort=
"$emit('sort', $event)"
@
onSort=
"$emit('sort', $event)"
/>
/>
<div
class=
"issuables-holder"
>
<div
class=
"issuables-holder"
>
<gl-loading-icon
v-if=
"issuablesLoading"
size=
"md"
class=
"gl-mt-5"
/>
<ul
v-if=
"issuablesLoading"
class=
"content-list"
>
<li
v-for=
"n in skeletonItemCount"
:key=
"n"
class=
"issue gl-px-5! gl-py-5!"
>
<gl-skeleton-loading
/>
</li>
</ul>
<ul
<ul
v-if=
"!issuablesLoading && issuables.length"
v-if=
"!issuablesLoading && issuables.length"
class=
"content-list issuable-list issues-list"
class=
"content-list issuable-list issues-list"
...
@@ -172,6 +191,9 @@ export default {
...
@@ -172,6 +191,9 @@ export default {
<
template
#reference
>
<
template
#reference
>
<slot
name=
"reference"
:issuable=
"issuable"
></slot>
<slot
name=
"reference"
:issuable=
"issuable"
></slot>
</
template
>
</
template
>
<
template
#author
>
<slot
name=
"author"
:author=
"issuable.author"
></slot>
</
template
>
<
template
#status
>
<
template
#status
>
<slot
name=
"status"
:issuable=
"issuable"
></slot>
<slot
name=
"status"
:issuable=
"issuable"
></slot>
</
template
>
</
template
>
...
@@ -181,7 +203,7 @@ export default {
...
@@ -181,7 +203,7 @@ export default {
<gl-pagination
<gl-pagination
v-if=
"showPaginationControls"
v-if=
"showPaginationControls"
:per-page=
"defaultPageSize"
:per-page=
"defaultPageSize"
:total-items=
"total
Page
s"
:total-items=
"total
Item
s"
:value=
"currentPage"
:value=
"currentPage"
:prev-page=
"previousPage"
:prev-page=
"previousPage"
:next-page=
"nextPage"
:next-page=
"nextPage"
...
...
app/assets/javascripts/issuable_list/constants.js
View file @
91783e1a
...
@@ -47,3 +47,5 @@ export const AvailableSortOptions = [
...
@@ -47,3 +47,5 @@ export const AvailableSortOptions = [
];
];
export
const
DEFAULT_PAGE_SIZE
=
20
;
export
const
DEFAULT_PAGE_SIZE
=
20
;
export
const
DEFAULT_SKELETON_COUNT
=
5
;
app/assets/javascripts/pages/projects/integrations/jira/issues/index/index.js
deleted
100644 → 0
View file @
8b043949
import
initIssuablesList
from
'
~/issues_list
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
initIssuablesList
();
});
config/feature_flags/development/jira_issues_list.yml
0 → 100644
View file @
91783e1a
---
name
:
jira_issues_list
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45678
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/273726
type
:
development
group
:
group::ecosystem
default_enabled
:
false
ee/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js
View file @
91783e1a
...
@@ -5,4 +5,5 @@ export default {
...
@@ -5,4 +5,5 @@ export default {
epics
:
'
epics-recent-searches
'
,
epics
:
'
epics-recent-searches
'
,
requirements
:
'
requirements-recent-searches
'
,
requirements
:
'
requirements-recent-searches
'
,
test_cases
:
'
test-cases-recent-searches
'
,
test_cases
:
'
test-cases-recent-searches
'
,
jira_issues
:
'
jira-issues-recent-searches
'
,
};
};
ee/app/assets/javascripts/integrations/jira/issues_list/components/jira_issues_list_empty_state.vue
0 → 100644
View file @
91783e1a
<
script
>
import
{
GlEmptyState
,
GlButton
,
GlIcon
,
GlSprintf
}
from
'
@gitlab/ui
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
{
IssuableStates
}
from
'
~/issuable_list/constants
'
;
export
default
{
FilterStateEmptyMessage
:
{
[
IssuableStates
.
Opened
]:
__
(
'
There are no open issues
'
),
[
IssuableStates
.
Closed
]:
__
(
'
There are no closed issues
'
),
},
components
:
{
GlEmptyState
,
GlButton
,
GlIcon
,
GlSprintf
,
},
inject
:
[
'
emptyStatePath
'
,
'
issueCreateUrl
'
],
props
:
{
currentState
:
{
type
:
String
,
required
:
true
,
},
issuesCount
:
{
type
:
Object
,
required
:
true
,
},
hasFiltersApplied
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
computed
:
{
hasIssues
()
{
return
this
.
issuesCount
[
IssuableStates
.
Opened
]
+
this
.
issuesCount
[
IssuableStates
.
Closed
]
>
0
;
},
emptyStateTitle
()
{
if
(
this
.
hasFiltersApplied
)
{
return
__
(
'
Sorry, your filter produced no results
'
);
}
else
if
(
this
.
hasIssues
)
{
return
this
.
$options
.
FilterStateEmptyMessage
[
this
.
currentState
];
}
return
s__
(
'
Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira.
'
,
);
},
emptyStateDescription
()
{
if
(
this
.
hasFiltersApplied
)
{
return
__
(
'
To widen your search, change or remove filters above
'
);
}
else
if
(
!
this
.
hasIssues
)
{
return
s__
(
'
Integrations|To keep this project going, create a new issue.
'
);
}
return
''
;
},
},
};
</
script
>
<
template
>
<gl-empty-state
:svg-path=
"emptyStatePath"
:title=
"emptyStateTitle"
>
<template
v-if=
"!hasIssues || hasFiltersApplied"
#description
>
<gl-sprintf
:message=
"emptyStateDescription"
/>
</
template
>
<
template
v-if=
"!hasIssues"
#actions
>
<gl-button
:href=
"issueCreateUrl"
target=
"_blank"
category=
"primary"
variant=
"success"
>
{{
s__
(
'
Integrations|Create new issue in Jira
'
)
}}
<gl-icon
name=
"external-link"
/></gl-button>
</
template
>
</gl-empty-state>
</template>
ee/app/assets/javascripts/integrations/jira/issues_list/components/jira_issues_list_root.vue
0 → 100644
View file @
91783e1a
<
script
>
import
{
GlButton
,
GlIcon
,
GlLink
,
GlSprintf
,
GlSafeHtmlDirective
as
SafeHtml
}
from
'
@gitlab/ui
'
;
import
jiraLogo
from
'
@gitlab/svgs/dist/illustrations/logos/jira.svg
'
;
import
{
__
}
from
'
~/locale
'
;
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
IssuableList
from
'
~/issuable_list/components/issuable_list_root.vue
'
;
import
JiraIssuesListEmptyState
from
'
./jira_issues_list_empty_state.vue
'
;
import
{
IssuableStates
,
IssuableListTabs
,
AvailableSortOptions
,
DEFAULT_PAGE_SIZE
,
}
from
'
~/issuable_list/constants
'
;
export
default
{
name
:
'
JiraIssuesList
'
,
IssuableListTabs
,
AvailableSortOptions
,
defaultPageSize
:
DEFAULT_PAGE_SIZE
,
components
:
{
GlButton
,
GlIcon
,
GlLink
,
GlSprintf
,
IssuableList
,
JiraIssuesListEmptyState
,
},
directives
:
{
SafeHtml
,
},
inject
:
[
'
initialState
'
,
'
initialSortBy
'
,
'
page
'
,
'
issuesFetchPath
'
,
'
projectFullPath
'
,
'
issueCreateUrl
'
,
],
props
:
{
initialFilterParams
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
},
data
()
{
return
{
jiraLogo
,
issues
:
[],
issuesListLoading
:
false
,
issuesListLoadFailed
:
false
,
totalIssues
:
0
,
currentState
:
this
.
initialState
,
filterParams
:
this
.
initialFilterParams
,
sortedBy
:
this
.
initialSortBy
,
currentPage
:
this
.
page
,
issuesCount
:
{
[
IssuableStates
.
Opened
]:
0
,
[
IssuableStates
.
Closed
]:
0
,
[
IssuableStates
.
All
]:
0
,
},
};
},
computed
:
{
showPaginationControls
()
{
return
Boolean
(
!
this
.
issuesListLoading
&&
!
this
.
issuesListLoadFailed
&&
this
.
issues
.
length
&&
this
.
totalIssues
>
1
,
);
},
hasFiltersApplied
()
{
return
Boolean
(
this
.
filterParams
.
search
);
},
urlParams
()
{
return
{
state
:
this
.
currentState
,
page
:
this
.
currentPage
,
sort
:
this
.
sortedBy
,
search
:
this
.
filterParams
.
search
,
};
},
},
mounted
()
{
this
.
fetchIssues
();
},
methods
:
{
fetchIssues
()
{
this
.
issuesListLoading
=
true
;
this
.
issuesListLoadFailed
=
false
;
return
axios
.
get
(
this
.
issuesFetchPath
,
{
params
:
{
with_labels_details
:
true
,
page
:
this
.
currentPage
,
per_page
:
this
.
$options
.
defaultPageSize
,
state
:
this
.
currentState
,
sort
:
this
.
sortedBy
,
search
:
this
.
filterParams
.
search
,
},
})
.
then
(
res
=>
{
const
{
headers
,
data
}
=
res
;
this
.
currentPage
=
parseInt
(
headers
[
'
x-page
'
],
10
);
this
.
totalIssues
=
parseInt
(
headers
[
'
x-total
'
],
10
);
this
.
issues
=
data
.
map
((
rawIssue
,
index
)
=>
{
const
issue
=
convertObjectPropsToCamelCase
(
rawIssue
,
{
deep
:
true
});
return
{
...
issue
,
// JIRA issues don't have ID so we extract
// an ID equivalent from references.relative
id
:
parseInt
(
rawIssue
.
references
.
relative
.
split
(
'
-
'
).
pop
(),
10
),
author
:
{
...
issue
.
author
,
id
:
index
,
},
};
});
this
.
issuesCount
[
this
.
currentState
]
=
this
.
issues
.
length
;
})
.
catch
(
error
=>
{
this
.
issuesListLoadFailed
=
true
;
createFlash
({
message
:
__
(
'
An error occurred while loading issues
'
),
captureError
:
true
,
error
,
});
})
.
finally
(()
=>
{
this
.
issuesListLoading
=
false
;
});
},
getFilteredSearchValue
()
{
return
[
{
type
:
'
filtered-search-term
'
,
value
:
{
data
:
this
.
filterParams
.
search
||
''
,
},
},
];
},
fetchIssuesBy
(
propsName
,
propValue
)
{
this
[
propsName
]
=
propValue
;
this
.
fetchIssues
();
},
handleFilterIssues
(
filters
=
[])
{
const
filterParams
=
{};
const
plainText
=
[];
filters
.
forEach
(
filter
=>
{
if
(
filter
.
type
===
'
filtered-search-term
'
&&
filter
.
value
.
data
)
{
plainText
.
push
(
filter
.
value
.
data
);
}
});
if
(
plainText
.
length
)
{
filterParams
.
search
=
plainText
.
join
(
'
'
);
}
this
.
filterParams
=
filterParams
;
this
.
fetchIssues
();
},
},
};
</
script
>
<
template
>
<issuable-list
:namespace=
"projectFullPath"
:tabs=
"$options.IssuableListTabs"
:current-tab=
"currentState"
:search-input-placeholder=
"s__('Integrations|Search Jira issues')"
:search-tokens=
"[]"
:sort-options=
"$options.AvailableSortOptions"
:initial-filter-value=
"getFilteredSearchValue()"
:initial-sort-by=
"sortedBy"
:issuables=
"issues"
:issuables-loading=
"issuesListLoading"
:show-pagination-controls=
"showPaginationControls"
:default-page-size=
"$options.defaultPageSize"
:total-items=
"totalIssues"
:current-page=
"currentPage"
:previous-page=
"currentPage - 1"
:next-page=
"currentPage + 1"
:url-params=
"urlParams"
:enable-label-permalinks=
"false"
recent-searches-storage-key=
"jira_issues"
@
click-tab=
"fetchIssuesBy('currentState', $event)"
@
page-change=
"fetchIssuesBy('currentPage', $event)"
@
sort=
"fetchIssuesBy('sortedBy', $event)"
@
filter=
"handleFilterIssues"
>
<template
#nav-actions
>
<gl-button
:href=
"issueCreateUrl"
target=
"_blank"
>
{{
s__
(
'
Integrations|Create new issue in Jira
'
)
}}
<gl-icon
name=
"external-link"
/></gl-button>
</
template
>
<
template
#reference=
"{ issuable }"
>
<span
v-safe-html=
"jiraLogo"
class=
"svg-container jira-logo-container"
></span>
<span>
{{
issuable
.
references
.
relative
}}
</span>
</
template
>
<
template
#author=
"{ author }"
>
<gl-sprintf
message=
"%
{authorName} in Jira">
<template
#authorName
>
<gl-link
class=
"author-link js-user-link"
target=
"_blank"
:href=
"author.webUrl"
>
{{
author
.
name
}}
</gl-link>
</
template
>
</gl-sprintf>
</template>
<
template
#status=
"{ issuable }"
>
{{
issuable
.
status
}}
</
template
>
<
template
#empty-state
>
<jira-issues-list-empty-state
:current-state=
"currentState"
:issues-count=
"issuesCount"
:has-filters-applied=
"hasFiltersApplied"
/>
</
template
>
</issuable-list>
</template>
ee/app/assets/javascripts/integrations/jira/issues_list/jira_issues_list_bundle.js
0 → 100644
View file @
91783e1a
import
Vue
from
'
vue
'
;
import
{
urlParamsToObject
,
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
IssuableStates
}
from
'
~/issuable_list/constants
'
;
import
JiraIssuesListApp
from
'
./components/jira_issues_list_root.vue
'
;
export
default
function
initJiraIssuesList
({
mountPointSelector
})
{
const
mountPointEl
=
document
.
querySelector
(
mountPointSelector
);
if
(
!
mountPointEl
)
{
return
null
;
}
const
{
page
=
1
,
initialState
=
IssuableStates
.
Opened
,
initialSortBy
=
'
created_desc
'
,
}
=
mountPointEl
.
dataset
;
const
initialFilterParams
=
Object
.
assign
(
convertObjectPropsToCamelCase
(
urlParamsToObject
(
window
.
location
.
search
.
substring
(
1
)),
{
dropKeys
:
[
'
scope
'
,
'
utf8
'
,
'
state
'
,
'
sort
'
],
// These keys are unsupported/unnecessary
}),
);
return
new
Vue
({
el
:
mountPointEl
,
provide
:
{
...
mountPointEl
.
dataset
,
page
:
parseInt
(
page
,
10
),
initialState
,
initialSortBy
,
},
render
:
createElement
=>
createElement
(
JiraIssuesListApp
,
{
props
:
{
initialFilterParams
,
},
}),
});
}
ee/app/assets/javascripts/pages/projects/integrations/jira/issues/index.js
0 → 100644
View file @
91783e1a
import
initJiraIssuesList
from
'
ee/integrations/jira/issues_list/jira_issues_list_bundle
'
;
import
initIssuablesList
from
'
~/issues_list
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
if
(
gon
.
features
.
jiraIssuesList
)
{
initJiraIssuesList
({
mountPointSelector
:
'
#js-jira-issues-list
'
,
});
}
else
{
initIssuablesList
();
}
});
ee/app/controllers/projects/integrations/jira/issues_controller.rb
View file @
91783e1a
...
@@ -12,6 +12,7 @@ module Projects
...
@@ -12,6 +12,7 @@ module Projects
before_action
do
before_action
do
push_frontend_feature_flag
(
:jira_issues_integration
,
project
,
type: :licensed
,
default_enabled:
true
)
push_frontend_feature_flag
(
:jira_issues_integration
,
project
,
type: :licensed
,
default_enabled:
true
)
push_frontend_feature_flag
(
:jira_issues_list
,
project
,
type: :development
)
end
end
rescue_from
::
Projects
::
Integrations
::
Jira
::
IssuesFinder
::
IntegrationError
,
with: :render_integration_error
rescue_from
::
Projects
::
Integrations
::
Jira
::
IssuesFinder
::
IntegrationError
,
with: :render_integration_error
...
...
ee/app/views/projects/integrations/jira/issues/index.html.haml
View file @
91783e1a
-
page_title
_
(
'Jira Issues'
)
-
page_title
_
(
'Jira Issues'
)
-
add_page_specific_style
'page_bundles/issues_list'
-
add_page_specific_style
'page_bundles/issues_list'
.top-area.gl-border-b-0.gl-mt-6
-
if
Feature
.
enabled?
(
:jira_issues_list
,
@project
,
type: :development
)
#js-jira-issues-list
{
data:
{
issues_fetch_path:
project_integrations_jira_issues_path
(
@project
,
format: :json
),
page:
params
[
:page
],
initial_state:
params
[
:state
],
initial_sort_by:
params
[
:sort
],
project_full_path:
@project
.
full_path
,
issue_create_url:
@project
.
external_issue_tracker
.
new_issue_url
,
empty_state_path:
image_path
(
'illustrations/issues.svg'
)
}
}
-
else
.top-area.gl-border-b-0.gl-mt-6
=
render
'shared/issuable/nav'
,
type: :issues
,
display_count:
false
=
render
'shared/issuable/nav'
,
type: :issues
,
display_count:
false
=
render
'projects/integrations/jira/issues/nav_btns'
=
render
'projects/integrations/jira/issues/nav_btns'
.js-issuables-list
{
data:
{
endpoint:
project_integrations_jira_issues_path
(
@project
,
format: :json
),
.js-issuables-list
{
data:
{
endpoint:
project_integrations_jira_issues_path
(
@project
,
format: :json
),
'can-bulk-edit'
:
false
,
'can-bulk-edit'
:
false
,
'empty-state-meta'
:
{
svg_path:
image_path
(
'illustrations/issues.svg'
)
},
'empty-state-meta'
:
{
svg_path:
image_path
(
'illustrations/issues.svg'
)
},
'sort-key'
:
@sort
,
'sort-key'
:
@sort
,
...
...
ee/spec/features/integrations/jira/jira_issues_list_spec.rb
View file @
91783e1a
...
@@ -9,6 +9,7 @@ RSpec.describe 'Jira issues list' do
...
@@ -9,6 +9,7 @@ RSpec.describe 'Jira issues list' do
before
do
before
do
stub_licensed_features
(
jira_issues_integration:
true
)
stub_licensed_features
(
jira_issues_integration:
true
)
stub_feature_flags
(
jira_issues_list:
false
)
project
.
add_user
(
user
,
:developer
)
project
.
add_user
(
user
,
:developer
)
sign_in
(
user
)
sign_in
(
user
)
end
end
...
...
ee/spec/frontend/integrations/jira/issues_list/components/jira_issues_list_empty_state_spec.js
0 → 100644
View file @
91783e1a
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlEmptyState
,
GlSprintf
,
GlButton
}
from
'
@gitlab/ui
'
;
import
JiraIssuesListEmptyState
from
'
ee/integrations/jira/issues_list/components/jira_issues_list_empty_state.vue
'
;
import
{
IssuableStates
}
from
'
~/issuable_list/constants
'
;
import
{
mockProvide
}
from
'
../mock_data
'
;
const
createComponent
=
(
props
=
{})
=>
shallowMount
(
JiraIssuesListEmptyState
,
{
provide
:
mockProvide
,
propsData
:
{
currentState
:
'
opened
'
,
issuesCount
:
{
[
IssuableStates
.
Opened
]:
0
,
[
IssuableStates
.
Closed
]:
0
,
[
IssuableStates
.
All
]:
0
,
},
hasFiltersApplied
:
false
,
...
props
,
},
stubs
:
{
GlEmptyState
},
});
describe
(
'
JiraIssuesListEmptyState
'
,
()
=>
{
const
titleDefault
=
'
Issues created in Jira are shown here once you have created the issues in project setup in Jira.
'
;
const
titleWhenFilters
=
'
Sorry, your filter produced no results
'
;
const
titleWhenIssues
=
'
There are no open issues
'
;
const
descriptionWhenFilters
=
'
To widen your search, change or remove filters above
'
;
const
descriptionWhenNoIssues
=
'
To keep this project going, create a new issue.
'
;
let
wrapper
;
beforeEach
(()
=>
{
wrapper
=
createComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
computed
'
,
()
=>
{
describe
(
'
hasIssues
'
,
()
=>
{
it
(
'
returns false when total of opened and closed issues within `issuesCount` is 0
'
,
()
=>
{
expect
(
wrapper
.
vm
.
hasIssues
).
toBe
(
false
);
});
it
(
'
returns true when total of opened and closed issues within `issuesCount` is more than 0
'
,
async
()
=>
{
wrapper
.
setProps
({
issuesCount
:
{
[
IssuableStates
.
Opened
]:
1
,
[
IssuableStates
.
Closed
]:
1
,
},
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
vm
.
hasIssues
).
toBe
(
true
);
});
});
describe
(
'
emptyStateTitle
'
,
()
=>
{
it
(
`returns string "
${
titleWhenFilters
}
" when hasFiltersApplied prop is true`
,
async
()
=>
{
wrapper
.
setProps
({
hasFiltersApplied
:
true
,
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
vm
.
emptyStateTitle
).
toBe
(
titleWhenFilters
);
});
it
(
`returns string "
${
titleWhenIssues
}
" when hasFiltersApplied prop is false and hasIssues is true`
,
async
()
=>
{
wrapper
.
setProps
({
hasFiltersApplied
:
false
,
issuesCount
:
{
[
IssuableStates
.
Opened
]:
1
,
[
IssuableStates
.
Closed
]:
1
,
},
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
vm
.
emptyStateTitle
).
toBe
(
titleWhenIssues
);
});
it
(
'
returns default title string when both hasFiltersApplied and hasIssues props are false
'
,
async
()
=>
{
wrapper
.
setProps
({
hasFiltersApplied
:
false
,
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
vm
.
emptyStateTitle
).
toBe
(
titleDefault
);
});
});
describe
(
'
emptyStateDescription
'
,
()
=>
{
it
(
`returns string "
${
descriptionWhenFilters
}
" when hasFiltersApplied prop is true`
,
async
()
=>
{
wrapper
.
setProps
({
hasFiltersApplied
:
true
,
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
vm
.
emptyStateDescription
).
toBe
(
descriptionWhenFilters
);
});
it
(
`returns string "
${
descriptionWhenNoIssues
}
" when both hasFiltersApplied and hasIssues props are false`
,
async
()
=>
{
wrapper
.
setProps
({
hasFiltersApplied
:
false
,
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
vm
.
emptyStateDescription
).
toBe
(
descriptionWhenNoIssues
);
});
it
(
`returns empty string when hasFiltersApplied is false and hasIssues is true`
,
async
()
=>
{
wrapper
.
setProps
({
hasFiltersApplied
:
false
,
issuesCount
:
{
[
IssuableStates
.
Opened
]:
1
,
[
IssuableStates
.
Closed
]:
1
,
},
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
vm
.
emptyStateDescription
).
toBe
(
''
);
});
});
});
describe
(
'
template
'
,
()
=>
{
it
(
'
renders gl-empty-state component
'
,
()
=>
{
expect
(
wrapper
.
find
(
GlEmptyState
).
exists
()).
toBe
(
true
);
});
it
(
'
renders empty state title
'
,
async
()
=>
{
const
emptyStateEl
=
wrapper
.
find
(
GlEmptyState
);
expect
(
emptyStateEl
.
props
()).
toMatchObject
({
svgPath
:
mockProvide
.
emptyStatePath
,
title
:
'
Issues created in Jira are shown here once you have created the issues in project setup in Jira.
'
,
});
wrapper
.
setProps
({
hasFiltersApplied
:
true
,
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
emptyStateEl
.
props
(
'
title
'
)).
toBe
(
'
Sorry, your filter produced no results
'
);
wrapper
.
setProps
({
hasFiltersApplied
:
false
,
issuesCount
:
{
[
IssuableStates
.
Opened
]:
1
,
[
IssuableStates
.
Closed
]:
1
,
},
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
emptyStateEl
.
props
(
'
title
'
)).
toBe
(
'
There are no open issues
'
);
});
it
(
'
renders empty state description
'
,
()
=>
{
const
descriptionEl
=
wrapper
.
find
(
GlSprintf
);
expect
(
descriptionEl
.
exists
()).
toBe
(
true
);
expect
(
descriptionEl
.
attributes
(
'
message
'
)).
toBe
(
'
To keep this project going, create a new issue.
'
,
);
});
it
(
'
does not render empty state description when issues are present
'
,
async
()
=>
{
wrapper
.
setProps
({
issuesCount
:
{
[
IssuableStates
.
Opened
]:
1
,
[
IssuableStates
.
Closed
]:
1
,
},
});
await
wrapper
.
vm
.
$nextTick
();
const
descriptionEl
=
wrapper
.
find
(
GlSprintf
);
expect
(
descriptionEl
.
exists
()).
toBe
(
false
);
});
it
(
'
renders "Create new issue in Jira" button
'
,
()
=>
{
const
buttonEl
=
wrapper
.
find
(
GlButton
);
expect
(
buttonEl
.
exists
()).
toBe
(
true
);
expect
(
buttonEl
.
attributes
(
'
href
'
)).
toBe
(
mockProvide
.
issueCreateUrl
);
expect
(
buttonEl
.
text
()).
toBe
(
'
Create new issue in Jira
'
);
});
});
});
ee/spec/frontend/integrations/jira/issues_list/components/jira_issues_list_root_spec.js
0 → 100644
View file @
91783e1a
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
JiraIssuesListRoot
from
'
ee/integrations/jira/issues_list/components/jira_issues_list_root.vue
'
;
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
IssuableList
from
'
~/issuable_list/components/issuable_list_root.vue
'
;
import
{
IssuableStates
,
IssuableListTabs
,
AvailableSortOptions
}
from
'
~/issuable_list/constants
'
;
import
{
mockProvide
,
mockJiraIssues
}
from
'
../mock_data
'
;
jest
.
mock
(
'
~/flash
'
);
jest
.
mock
(
'
~/issuable_list/constants
'
,
()
=>
({
DEFAULT_PAGE_SIZE
:
2
,
IssuableStates
:
jest
.
requireActual
(
'
~/issuable_list/constants
'
).
IssuableStates
,
IssuableListTabs
:
jest
.
requireActual
(
'
~/issuable_list/constants
'
).
IssuableListTabs
,
AvailableSortOptions
:
jest
.
requireActual
(
'
~/issuable_list/constants
'
).
AvailableSortOptions
,
}));
const
createComponent
=
({
provide
=
mockProvide
,
initialFilterParams
=
{}
}
=
{})
=>
shallowMount
(
JiraIssuesListRoot
,
{
propsData
:
{
initialFilterParams
,
},
provide
,
});
describe
(
'
JiraIssuesListRoot
'
,
()
=>
{
const
resolvedValue
=
{
headers
:
{
'
x-page
'
:
1
,
'
x-total
'
:
3
,
},
data
:
mockJiraIssues
,
};
let
wrapper
;
let
mock
;
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
wrapper
=
createComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
mock
.
restore
();
});
describe
(
'
computed
'
,
()
=>
{
describe
(
'
showPaginationControls
'
,
()
=>
{
it
.
each
`
issuesListLoading | issuesListLoadFailed | issues | totalIssues | returnValue
${
true
}
|
${
false
}
|
${[]}
|
${
0
}
|
${
false
}
${
false
}
|
${
true
}
|
${[]}
|
${
0
}
|
${
false
}
${
false
}
|
${
false
}
|
${
mockJiraIssues
}
|
${
mockJiraIssues
.
length
}
|
${
true
}
`
(
'
returns $returnValue when issuesListLoading is $issuesListLoading, issuesListLoadFailed is $issuesListLoadFailed, issues is $issues and totalIssues is $totalIssues
'
,
({
issuesListLoading
,
issuesListLoadFailed
,
issues
,
totalIssues
,
returnValue
})
=>
{
wrapper
.
setData
({
issuesListLoading
,
issuesListLoadFailed
,
issues
,
totalIssues
,
});
expect
(
wrapper
.
vm
.
showPaginationControls
).
toBe
(
returnValue
);
},
);
});
describe
(
'
urlParams
'
,
()
=>
{
it
(
'
returns object containing `state`, `page`, `sort` and `search` properties
'
,
()
=>
{
wrapper
.
setData
({
currentState
:
'
closed
'
,
currentPage
:
2
,
sortedBy
:
'
created_asc
'
,
filterParams
:
{
search
:
'
foo
'
,
},
});
expect
(
wrapper
.
vm
.
urlParams
).
toMatchObject
({
state
:
'
closed
'
,
page
:
2
,
sort
:
'
created_asc
'
,
search
:
'
foo
'
,
});
});
});
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
fetchIssues
'
,
()
=>
{
it
(
'
sets issuesListLoading to true and issuesListLoadFailed to false
'
,
()
=>
{
wrapper
.
vm
.
fetchIssues
();
expect
(
wrapper
.
vm
.
issuesListLoading
).
toBe
(
true
);
expect
(
wrapper
.
vm
.
issuesListLoadFailed
).
toBe
(
false
);
});
it
(
'
calls `axios.get` with `issuesFetchPath` and query params
'
,
()
=>
{
jest
.
spyOn
(
axios
,
'
get
'
).
mockResolvedValue
(
resolvedValue
);
wrapper
.
vm
.
fetchIssues
();
expect
(
axios
.
get
).
toHaveBeenCalledWith
(
mockProvide
.
issuesFetchPath
,
expect
.
objectContaining
({
params
:
{
with_labels_details
:
true
,
page
:
wrapper
.
vm
.
currentPage
,
per_page
:
wrapper
.
vm
.
$options
.
defaultPageSize
,
state
:
wrapper
.
vm
.
currentState
,
sort
:
wrapper
.
vm
.
sortedBy
,
search
:
wrapper
.
vm
.
filterParams
.
search
,
},
}),
);
});
it
(
'
sets `currentPage` and `totalIssues` from response headers and `issues` & `issuesCount` from response body when request is successful
'
,
async
()
=>
{
jest
.
spyOn
(
axios
,
'
get
'
).
mockResolvedValue
(
resolvedValue
);
await
wrapper
.
vm
.
fetchIssues
();
const
firstIssue
=
convertObjectPropsToCamelCase
(
mockJiraIssues
[
0
],
{
deep
:
true
});
expect
(
wrapper
.
vm
.
currentPage
).
toBe
(
resolvedValue
.
headers
[
'
x-page
'
]);
expect
(
wrapper
.
vm
.
totalIssues
).
toBe
(
resolvedValue
.
headers
[
'
x-total
'
]);
expect
(
wrapper
.
vm
.
issues
[
0
]).
toEqual
({
...
firstIssue
,
id
:
31596
,
author
:
{
...
firstIssue
.
author
,
id
:
0
,
},
});
expect
(
wrapper
.
vm
.
issuesCount
[
IssuableStates
.
Opened
]).
toBe
(
3
);
});
it
(
'
sets `issuesListLoadFailed` to true and calls `createFlash` when request fails
'
,
async
()
=>
{
jest
.
spyOn
(
axios
,
'
get
'
).
mockRejectedValue
({});
await
wrapper
.
vm
.
fetchIssues
();
expect
(
wrapper
.
vm
.
issuesListLoadFailed
).
toBe
(
true
);
expect
(
createFlash
).
toHaveBeenCalledWith
({
message
:
'
An error occurred while loading issues
'
,
captureError
:
true
,
error
:
expect
.
any
(
Object
),
});
});
it
(
'
sets `issuesListLoading` to false when request completes
'
,
async
()
=>
{
jest
.
spyOn
(
axios
,
'
get
'
).
mockRejectedValue
({});
await
wrapper
.
vm
.
fetchIssues
();
expect
(
wrapper
.
vm
.
issuesListLoading
).
toBe
(
false
);
});
});
describe
(
'
fetchIssuesBy
'
,
()
=>
{
it
(
'
sets provided prop value for given prop name and calls `fetchIssues`
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
fetchIssues
'
);
wrapper
.
vm
.
fetchIssuesBy
(
'
currentPage
'
,
2
);
expect
(
wrapper
.
vm
.
currentPage
).
toBe
(
2
);
expect
(
wrapper
.
vm
.
fetchIssues
).
toHaveBeenCalled
();
});
});
});
describe
(
'
template
'
,
()
=>
{
const
getIssuableList
=
()
=>
wrapper
.
find
(
IssuableList
);
it
(
'
renders issuable-list component
'
,
async
()
=>
{
wrapper
.
setData
({
filterParams
:
{
search
:
'
foo
'
,
},
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
getIssuableList
().
exists
()).
toBe
(
true
);
expect
(
getIssuableList
().
props
()).
toMatchObject
({
namespace
:
mockProvide
.
projectFullPath
,
tabs
:
IssuableListTabs
,
currentTab
:
'
opened
'
,
searchInputPlaceholder
:
'
Search Jira issues
'
,
searchTokens
:
[],
sortOptions
:
AvailableSortOptions
,
initialFilterValue
:
[
{
type
:
'
filtered-search-term
'
,
value
:
{
data
:
'
foo
'
,
},
},
],
initialSortBy
:
'
created_desc
'
,
issuables
:
[],
issuablesLoading
:
true
,
showPaginationControls
:
wrapper
.
vm
.
showPaginationControls
,
defaultPageSize
:
2
,
// mocked value in tests
totalItems
:
0
,
currentPage
:
1
,
previousPage
:
0
,
nextPage
:
2
,
urlParams
:
wrapper
.
vm
.
urlParams
,
recentSearchesStorageKey
:
'
jira_issues
'
,
enableLabelPermalinks
:
false
,
});
});
describe
(
'
issuable-list events
'
,
()
=>
{
beforeEach
(()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
fetchIssues
'
);
});
it
(
'
click-tab event changes currentState value and calls fetchIssues via `fetchIssuesBy`
'
,
()
=>
{
getIssuableList
().
vm
.
$emit
(
'
click-tab
'
,
'
closed
'
);
expect
(
wrapper
.
vm
.
currentState
).
toBe
(
'
closed
'
);
expect
(
wrapper
.
vm
.
fetchIssues
).
toHaveBeenCalled
();
});
it
(
'
page-change event changes currentPage value and calls fetchIssues via `fetchIssuesBy`
'
,
()
=>
{
getIssuableList
().
vm
.
$emit
(
'
page-change
'
,
2
);
expect
(
wrapper
.
vm
.
currentPage
).
toBe
(
2
);
expect
(
wrapper
.
vm
.
fetchIssues
).
toHaveBeenCalled
();
});
it
(
'
sort event changes sortedBy value and calls fetchIssues via `fetchIssuesBy`
'
,
()
=>
{
getIssuableList
().
vm
.
$emit
(
'
sort
'
,
'
updated_asc
'
);
expect
(
wrapper
.
vm
.
sortedBy
).
toBe
(
'
updated_asc
'
);
expect
(
wrapper
.
vm
.
fetchIssues
).
toHaveBeenCalled
();
});
it
(
'
filter event sets `filterParams` value and calls fetchIssues
'
,
()
=>
{
getIssuableList
().
vm
.
$emit
(
'
filter
'
,
[
{
type
:
'
filtered-search-term
'
,
value
:
{
data
:
'
foo
'
,
},
},
]);
expect
(
wrapper
.
vm
.
filterParams
).
toEqual
({
search
:
'
foo
'
,
});
expect
(
wrapper
.
vm
.
fetchIssues
).
toHaveBeenCalled
();
});
});
});
});
ee/spec/frontend/integrations/jira/issues_list/mock_data.js
0 → 100644
View file @
91783e1a
export
const
mockProvide
=
{
initialState
:
'
opened
'
,
initialSortBy
:
'
created_desc
'
,
page
:
1
,
issuesFetchPath
:
'
/gitlab-org/gitlab-test/-/integrations/jira/issues.json
'
,
projectFullPath
:
'
gitlab-org/gitlab-test
'
,
issueCreateUrl
:
'
https://gitlab-jira.atlassian.net/secure/CreateIssue!default.jspa
'
,
emptyStatePath
:
'
/assets/illustrations/issues.svg
'
,
};
export
const
mockJiraIssue1
=
{
project_id
:
1
,
title
:
'
Eius fuga voluptates.
'
,
created_at
:
'
2020-03-19T14:31:51.281Z
'
,
updated_at
:
'
2020-10-20T07:01:45.865Z
'
,
closed_at
:
null
,
status
:
'
Selected for Development
'
,
labels
:
[
{
name
:
'
backend
'
,
color
:
'
#EBECF0
'
,
text_color
:
'
#283856
'
,
},
],
author
:
{
name
:
'
jhope
'
,
web_url
:
'
https://gitlab-jira.atlassian.net/people/5e32f803e127810e82875bc1
'
,
},
assignees
:
[
{
name
:
'
Kushal Pandya
'
,
},
],
web_url
:
'
https://gitlab-jira.atlassian.net/browse/IG-31596
'
,
references
:
{
relative
:
'
IG-31596
'
,
},
external_tracker
:
'
jira
'
,
};
export
const
mockJiraIssue2
=
{
project_id
:
1
,
title
:
'
Hic sit sint ducimus ea et sint.
'
,
created_at
:
'
2020-03-19T14:31:50.677Z
'
,
updated_at
:
'
2020-03-19T14:31:50.677Z
'
,
closed_at
:
null
,
status
:
'
Backlog
'
,
labels
:
[],
author
:
{
name
:
'
Gabe Weaver
'
,
web_url
:
'
https://gitlab-jira.atlassian.net/people/5e320a31fe03e20c9d1dccde
'
,
},
assignees
:
[],
web_url
:
'
https://gitlab-jira.atlassian.net/browse/IG-31595
'
,
references
:
{
relative
:
'
IG-31595
'
,
},
external_tracker
:
'
jira
'
,
};
export
const
mockJiraIssue3
=
{
project_id
:
1
,
title
:
'
Alias ut modi est labore.
'
,
created_at
:
'
2020-03-19T14:31:50.012Z
'
,
updated_at
:
'
2020-03-19T14:31:50.012Z
'
,
closed_at
:
null
,
status
:
'
Backlog
'
,
labels
:
[],
author
:
{
name
:
'
Gabe Weaver
'
,
web_url
:
'
https://gitlab-jira.atlassian.net/people/5e320a31fe03e20c9d1dccde
'
,
},
assignees
:
[],
web_url
:
'
https://gitlab-jira.atlassian.net/browse/IG-31594
'
,
references
:
{
relative
:
'
IG-31594
'
,
},
external_tracker
:
'
jira
'
,
};
export
const
mockJiraIssues
=
[
mockJiraIssue1
,
mockJiraIssue2
,
mockJiraIssue3
];
locale/gitlab.pot
View file @
91783e1a
...
@@ -14382,6 +14382,9 @@ msgstr ""
...
@@ -14382,6 +14382,9 @@ msgstr ""
msgid "Integrations|Connection successful."
msgid "Integrations|Connection successful."
msgstr ""
msgstr ""
msgid "Integrations|Create new issue in Jira"
msgstr ""
msgid "Integrations|Default settings are inherited from the group level."
msgid "Integrations|Default settings are inherited from the group level."
msgstr ""
msgstr ""
...
@@ -14397,6 +14400,9 @@ msgstr ""
...
@@ -14397,6 +14400,9 @@ msgstr ""
msgid "Integrations|Includes commit title and branch"
msgid "Integrations|Includes commit title and branch"
msgstr ""
msgstr ""
msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira."
msgstr ""
msgid "Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use instance-level defaults."
msgid "Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use instance-level defaults."
msgstr ""
msgstr ""
...
@@ -14409,9 +14415,15 @@ msgstr ""
...
@@ -14409,9 +14415,15 @@ msgstr ""
msgid "Integrations|Saving will update the default settings for all projects that are not using custom settings."
msgid "Integrations|Saving will update the default settings for all projects that are not using custom settings."
msgstr ""
msgstr ""
msgid "Integrations|Search Jira issues"
msgstr ""
msgid "Integrations|Standard"
msgid "Integrations|Standard"
msgstr ""
msgstr ""
msgid "Integrations|To keep this project going, create a new issue."
msgstr ""
msgid "Integrations|Update your projects on Packagist, the main Composer repository"
msgid "Integrations|Update your projects on Packagist, the main Composer repository"
msgstr ""
msgstr ""
...
...
spec/frontend/issuable_list/components/issuable_item_spec.js
View file @
91783e1a
...
@@ -261,6 +261,24 @@ describe('IssuableItem', () => {
...
@@ -261,6 +261,24 @@ describe('IssuableItem', () => {
expect
(
authorEl
.
text
()).
toBe
(
mockAuthor
.
name
);
expect
(
authorEl
.
text
()).
toBe
(
mockAuthor
.
name
);
});
});
it
(
'
renders issuable author info via slot
'
,
()
=>
{
const
wrapperWithAuthorSlot
=
createComponent
({
issuableSymbol
:
'
#
'
,
issuable
:
mockIssuable
,
slots
:
{
reference
:
`
<span class="js-author">
${
mockAuthor
.
name
}
</span>
`
,
},
});
const
authorEl
=
wrapperWithAuthorSlot
.
find
(
'
.js-author
'
);
expect
(
authorEl
.
exists
()).
toBe
(
true
);
expect
(
authorEl
.
text
()).
toBe
(
mockAuthor
.
name
);
wrapperWithAuthorSlot
.
destroy
();
});
it
(
'
renders gl-label component for each label present within `issuable` prop
'
,
()
=>
{
it
(
'
renders gl-label component for each label present within `issuable` prop
'
,
()
=>
{
const
labelsEl
=
wrapper
.
findAll
(
GlLabel
);
const
labelsEl
=
wrapper
.
findAll
(
GlLabel
);
...
...
spec/frontend/issuable_list/components/issuable_list_root_spec.js
View file @
91783e1a
import
{
mount
}
from
'
@vue/test-utils
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
{
Gl
LoadingIcon
,
GlPagination
}
from
'
@gitlab/ui
'
;
import
{
Gl
SkeletonLoading
,
GlPagination
}
from
'
@gitlab/ui
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
...
@@ -34,6 +34,31 @@ describe('IssuableListRoot', () => {
...
@@ -34,6 +34,31 @@ describe('IssuableListRoot', () => {
wrapper
.
destroy
();
wrapper
.
destroy
();
});
});
describe
(
'
computed
'
,
()
=>
{
describe
(
'
skeletonItemCount
'
,
()
=>
{
it
.
each
`
totalItems | defaultPageSize | currentPage | returnValue
${
100
}
|
${
20
}
|
${
1
}
|
${
20
}
${
105
}
|
${
20
}
|
${
6
}
|
${
5
}
${
7
}
|
${
20
}
|
${
1
}
|
${
7
}
${
0
}
|
${
20
}
|
${
1
}
|
${
5
}
`
(
'
returns $returnValue when totalItems is $totalItems, defaultPageSize is $defaultPageSize and currentPage is $currentPage
'
,
async
({
totalItems
,
defaultPageSize
,
currentPage
,
returnValue
})
=>
{
wrapper
.
setProps
({
totalItems
,
defaultPageSize
,
currentPage
,
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
vm
.
skeletonItemCount
).
toBe
(
returnValue
);
},
);
});
});
describe
(
'
watch
'
,
()
=>
{
describe
(
'
watch
'
,
()
=>
{
describe
(
'
urlParams
'
,
()
=>
{
describe
(
'
urlParams
'
,
()
=>
{
it
(
'
updates window URL reflecting props within `urlParams`
'
,
async
()
=>
{
it
(
'
updates window URL reflecting props within `urlParams`
'
,
async
()
=>
{
...
@@ -111,7 +136,7 @@ describe('IssuableListRoot', () => {
...
@@ -111,7 +136,7 @@ describe('IssuableListRoot', () => {
await
wrapper
.
vm
.
$nextTick
();
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
All
(
GlSkeletonLoading
)).
toHaveLength
(
wrapper
.
vm
.
skeletonItemCount
);
});
});
it
(
'
renders issuable-item component for each item within `issuables` array
'
,
()
=>
{
it
(
'
renders issuable-item component for each item within `issuables` array
'
,
()
=>
{
...
@@ -139,7 +164,7 @@ describe('IssuableListRoot', () => {
...
@@ -139,7 +164,7 @@ describe('IssuableListRoot', () => {
it
(
'
renders gl-pagination when `showPaginationControls` prop is true
'
,
async
()
=>
{
it
(
'
renders gl-pagination when `showPaginationControls` prop is true
'
,
async
()
=>
{
wrapper
.
setProps
({
wrapper
.
setProps
({
showPaginationControls
:
true
,
showPaginationControls
:
true
,
total
Page
s
:
10
,
total
Item
s
:
10
,
});
});
await
wrapper
.
vm
.
$nextTick
();
await
wrapper
.
vm
.
$nextTick
();
...
...
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