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
b93b446b
Commit
b93b446b
authored
May 27, 2021
by
Kyle Wiebers
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "Merge branch '322755-use-graphql-in-issues-refactor' into 'master'"
This reverts merge request !62312
parent
2ee7ccf6
Changes
17
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
459 additions
and
569 deletions
+459
-569
app/assets/javascripts/issuable_list/components/issuable_item.vue
...ts/javascripts/issuable_list/components/issuable_item.vue
+5
-11
app/assets/javascripts/issuable_list/components/issuable_list_root.vue
...vascripts/issuable_list/components/issuable_list_root.vue
+1
-2
app/assets/javascripts/issues_list/components/issue_card_time_info.vue
...vascripts/issues_list/components/issue_card_time_info.vue
+2
-5
app/assets/javascripts/issues_list/components/issues_list_app.vue
...ts/javascripts/issues_list/components/issues_list_app.vue
+74
-81
app/assets/javascripts/issues_list/constants.js
app/assets/javascripts/issues_list/constants.js
+178
-41
app/assets/javascripts/issues_list/index.js
app/assets/javascripts/issues_list/index.js
+3
-8
app/assets/javascripts/issues_list/queries/get_issues.query.graphql
.../javascripts/issues_list/queries/get_issues.query.graphql
+0
-45
app/assets/javascripts/issues_list/queries/issue_info.fragment.graphql
...vascripts/issues_list/queries/issue_info.fragment.graphql
+0
-51
app/assets/javascripts/issues_list/utils.js
app/assets/javascripts/issues_list/utils.js
+9
-41
app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue
...components/filtered_search_bar/tokens/iteration_token.vue
+3
-3
app/helpers/issues_helper.rb
app/helpers/issues_helper.rb
+1
-0
ee/app/assets/javascripts/issues_list/queries/get_issues.query.graphql
.../javascripts/issues_list/queries/get_issues.query.graphql
+0
-56
spec/frontend/issues_list/components/issue_card_time_info_spec.js
...ntend/issues_list/components/issue_card_time_info_spec.js
+6
-4
spec/frontend/issues_list/components/issues_list_app_spec.js
spec/frontend/issues_list/components/issues_list_app_spec.js
+136
-113
spec/frontend/issues_list/mock_data.js
spec/frontend/issues_list/mock_data.js
+25
-97
spec/frontend/issues_list/utils_spec.js
spec/frontend/issues_list/utils_spec.js
+15
-11
spec/helpers/issues_helper_spec.rb
spec/helpers/issues_helper_spec.rb
+1
-0
No files found.
app/assets/javascripts/issuable_list/components/issuable_item.vue
View file @
b93b446b
...
...
@@ -50,9 +50,6 @@ export default {
},
},
computed
:
{
issuableId
()
{
return
getIdFromGraphQLId
(
this
.
issuable
.
id
);
},
createdInPastDay
()
{
const
createdSecondsAgo
=
differenceInSeconds
(
new
Date
(
this
.
issuable
.
createdAt
),
new
Date
());
return
createdSecondsAgo
<
SECONDS_IN_DAY
;
...
...
@@ -64,7 +61,7 @@ export default {
return
this
.
issuable
.
gitlabWebUrl
||
this
.
issuable
.
webUrl
;
},
authorId
()
{
return
getIdFromGraphQLId
(
this
.
author
.
id
);
return
getIdFromGraphQLId
(
`
${
this
.
author
.
id
}
`
);
},
isIssuableUrlExternal
()
{
return
isExternal
(
this
.
webUrl
);
...
...
@@ -73,10 +70,10 @@ export default {
return
this
.
issuable
.
labels
?.
nodes
||
this
.
issuable
.
labels
||
[];
},
labelIdsString
()
{
return
JSON
.
stringify
(
this
.
labels
.
map
((
label
)
=>
getIdFromGraphQLId
(
label
.
id
)
));
return
JSON
.
stringify
(
this
.
labels
.
map
((
label
)
=>
label
.
id
));
},
assignees
()
{
return
this
.
issuable
.
assignees
?.
nodes
||
this
.
issuable
.
assignees
||
[];
return
this
.
issuable
.
assignees
||
[];
},
createdAt
()
{
return
sprintf
(
__
(
'
created %{timeAgo}
'
),
{
...
...
@@ -84,9 +81,6 @@ export default {
});
},
updatedAt
()
{
if
(
!
this
.
issuable
.
updatedAt
)
{
return
''
;
}
return
sprintf
(
__
(
'
updated %{timeAgo}
'
),
{
timeAgo
:
getTimeago
().
format
(
this
.
issuable
.
updatedAt
),
});
...
...
@@ -163,7 +157,7 @@ export default {
<
template
>
<li
:id=
"`issuable_$
{issuable
I
d}`"
:id=
"`issuable_$
{issuable
.i
d}`"
class="issue gl-px-5!"
:class="{ closed: issuable.closedAt, today: createdInPastDay }"
:data-labels="labelIdsString"
...
...
@@ -173,7 +167,7 @@ export default {
<gl-form-checkbox
class=
"gl-mr-0"
:checked=
"checked"
:data-id=
"issuable
I
d"
:data-id=
"issuable
.i
d"
@
input=
"$emit('checked-input', $event)"
>
<span
class=
"gl-sr-only"
>
{{
issuable
.
title
}}
</span>
...
...
app/assets/javascripts/issuable_list/components/issuable_list_root.vue
View file @
b93b446b
...
...
@@ -2,7 +2,6 @@
import
{
GlSkeletonLoading
,
GlPagination
}
from
'
@gitlab/ui
'
;
import
{
uniqueId
}
from
'
lodash
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
updateHistory
,
setUrlParams
}
from
'
~/lib/utils/url_utility
'
;
import
FilteredSearchBar
from
'
~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
'
;
...
...
@@ -212,7 +211,7 @@ export default {
},
methods
:
{
issuableId
(
issuable
)
{
return
getIdFromGraphQLId
(
issuable
.
id
)
||
issuable
.
iid
||
uniqueId
();
return
issuable
.
id
||
issuable
.
iid
||
uniqueId
();
},
issuableChecked
(
issuable
)
{
return
this
.
checkedIssuables
[
this
.
issuableId
(
issuable
)]?.
checked
;
...
...
app/assets/javascripts/issues_list/components/issue_card_time_info.vue
View file @
b93b446b
...
...
@@ -42,9 +42,6 @@ export default {
}
return
__
(
'
Milestone
'
);
},
milestoneLink
()
{
return
this
.
issue
.
milestone
.
webPath
||
this
.
issue
.
milestone
.
webUrl
;
},
dueDate
()
{
return
this
.
issue
.
dueDate
&&
dateInWords
(
new
Date
(
this
.
issue
.
dueDate
),
true
);
},
...
...
@@ -52,7 +49,7 @@ export default {
return
isInPast
(
new
Date
(
this
.
issue
.
dueDate
));
},
timeEstimate
()
{
return
this
.
issue
.
humanTimeEstimate
||
this
.
issue
.
timeStats
?.
humanTimeEstimate
;
return
this
.
issue
.
timeStats
?.
humanTimeEstimate
;
},
showHealthStatus
()
{
return
this
.
hasIssuableHealthStatusFeature
&&
this
.
issue
.
healthStatus
;
...
...
@@ -88,7 +85,7 @@ export default {
class=
"issuable-milestone gl-display-none gl-sm-display-inline-block! gl-mr-3"
data-testid=
"issuable-milestone"
>
<gl-link
v-gl-tooltip
:href=
"
milestoneLink
"
:title=
"milestoneDate"
>
<gl-link
v-gl-tooltip
:href=
"
issue.milestone.webUrl
"
:title=
"milestoneDate"
>
<gl-icon
name=
"clock"
/>
{{
issue
.
milestone
.
title
}}
</gl-link>
...
...
app/assets/javascripts/issues_list/components/issues_list_app.vue
View file @
b93b446b
...
...
@@ -9,21 +9,24 @@ import {
GlTooltipDirective
,
}
from
'
@gitlab/ui
'
;
import
fuzzaldrinPlus
from
'
fuzzaldrin-plus
'
;
import
getIssuesQuery
from
'
ee_else_ce/issues_list/queries/get_issues.query.graphql
'
;
import
{
toNumber
}
from
'
lodash
'
;
import
createFlash
from
'
~/flash
'
;
import
CsvImportExportButtons
from
'
~/issuable/components/csv_import_export_buttons.vue
'
;
import
IssuableByEmail
from
'
~/issuable/components/issuable_by_email.vue
'
;
import
IssuableList
from
'
~/issuable_list/components/issuable_list_root.vue
'
;
import
{
IssuableListTabs
,
IssuableStates
}
from
'
~/issuable_list/constants
'
;
import
{
API_PARAM
,
apiSortParams
,
CREATED_DESC
,
i18n
,
MAX_LIST_SIZE
,
PAGE_SIZE
,
PARAM_DUE_DATE
,
PARAM_PAGE
,
PARAM_SORT
,
PARAM_STATE
,
RELATIVE_POSITION_
A
SC
,
RELATIVE_POSITION_
DE
SC
,
TOKEN_TYPE_ASSIGNEE
,
TOKEN_TYPE_AUTHOR
,
TOKEN_TYPE_CONFIDENTIAL
,
...
...
@@ -34,19 +37,19 @@ import {
TOKEN_TYPE_MILESTONE
,
TOKEN_TYPE_WEIGHT
,
UPDATED_DESC
,
URL_PARAM
,
urlSortParams
,
}
from
'
~/issues_list/constants
'
;
import
{
convertTo
Api
Params
,
convertToParams
,
convertToSearchQuery
,
convertToUrlParams
,
getDueDateValue
,
getFilterTokens
,
getSortKey
,
getSortOptions
,
}
from
'
~/issues_list/utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
getParameterByName
}
from
'
~/lib/utils/common_utils
'
;
import
{
convertObjectPropsToCamelCase
,
getParameterByName
}
from
'
~/lib/utils/common_utils
'
;
import
{
DEFAULT_NONE_ANY
,
OPERATOR_IS_ONLY
,
...
...
@@ -104,6 +107,9 @@ export default {
emptyStateSvgPath
:
{
default
:
''
,
},
endpoint
:
{
default
:
''
,
},
exportCsvPath
:
{
default
:
''
,
},
...
...
@@ -167,53 +173,15 @@ export default {
dueDateFilter
:
getDueDateValue
(
getParameterByName
(
PARAM_DUE_DATE
)),
exportCsvPathWithQuery
:
this
.
getExportCsvPathWithQuery
(),
filterTokens
:
getFilterTokens
(
window
.
location
.
search
),
isLoading
:
false
,
issues
:
[],
page
:
1
,
pageInfo
:
{},
pageParams
:
{
firstPageSize
:
PAGE_SIZE
,
},
page
:
toNumber
(
getParameterByName
(
PARAM_PAGE
))
||
1
,
showBulkEditSidebar
:
false
,
sortKey
:
getSortKey
(
getParameterByName
(
PARAM_SORT
))
||
defaultSortKey
,
state
:
state
||
IssuableStates
.
Opened
,
totalIssues
:
0
,
};
},
apollo
:
{
issues
:
{
query
:
getIssuesQuery
,
variables
()
{
const
filterParams
=
{
...
this
.
apiFilterParams
,
};
if
(
filterParams
.
epicId
)
{
filterParams
.
epicId
=
filterParams
.
epicId
.
split
(
'
::&
'
).
pop
();
}
else
if
(
filterParams
.
not
?.
epicId
)
{
filterParams
.
not
.
epicId
=
filterParams
.
not
.
epicId
.
split
(
'
::&
'
).
pop
();
}
return
{
projectPath
:
this
.
projectPath
,
search
:
this
.
searchQuery
,
sort
:
this
.
sortKey
,
state
:
this
.
state
,
...
this
.
pageParams
,
...
filterParams
,
};
},
update
:
({
project
})
=>
project
.
issues
.
nodes
,
result
({
data
})
{
this
.
pageInfo
=
data
.
project
.
issues
.
pageInfo
;
this
.
totalIssues
=
data
.
project
.
issues
.
count
;
this
.
exportCsvPathWithQuery
=
this
.
getExportCsvPathWithQuery
();
},
error
()
{
createFlash
({
message
:
this
.
$options
.
i18n
.
errorFetchingIssues
});
},
debounce
:
200
,
},
},
computed
:
{
hasSearch
()
{
return
this
.
searchQuery
||
Object
.
keys
(
this
.
urlFilterParams
).
length
;
...
...
@@ -222,22 +190,16 @@ export default {
return
this
.
showBulkEditSidebar
||
!
this
.
issues
.
length
;
},
isManualOrdering
()
{
return
this
.
sortKey
===
RELATIVE_POSITION_
A
SC
;
return
this
.
sortKey
===
RELATIVE_POSITION_
DE
SC
;
},
isOpenTab
()
{
return
this
.
state
===
IssuableStates
.
Opened
;
},
nextPage
()
{
return
Number
(
this
.
pageInfo
.
hasNextPage
);
},
previousPage
()
{
return
Number
(
this
.
pageInfo
.
hasPreviousPage
);
},
apiFilterParams
()
{
return
convertTo
ApiParams
(
this
.
filterTokens
);
return
convertTo
Params
(
this
.
filterTokens
,
API_PARAM
);
},
urlFilterParams
()
{
return
convertTo
UrlParams
(
this
.
filterTokens
);
return
convertTo
Params
(
this
.
filterTokens
,
URL_PARAM
);
},
searchQuery
()
{
return
convertToSearchQuery
(
this
.
filterTokens
)
||
undefined
;
...
...
@@ -252,7 +214,6 @@ export default {
dataType
:
'
user
'
,
unique
:
true
,
defaultAuthors
:
[],
operators
:
OPERATOR_IS_ONLY
,
fetchAuthors
:
this
.
fetchUsers
,
},
{
...
...
@@ -279,7 +240,7 @@ export default {
title
:
TOKEN_TITLE_LABEL
,
icon
:
'
labels
'
,
token
:
LabelToken
,
defaultLabels
:
DEFAULT_NONE_ANY
,
defaultLabels
:
[]
,
fetchLabels
:
this
.
fetchLabels
,
},
];
...
...
@@ -372,9 +333,10 @@ export default {
return
{
due_date
:
this
.
dueDateFilter
,
page
:
this
.
page
,
search
:
this
.
searchQuery
,
sort
:
urlSortParams
[
this
.
sortKey
],
state
:
this
.
state
,
...
urlSortParams
[
this
.
sortKey
],
...
filterParams
,
};
},
...
...
@@ -384,6 +346,7 @@ export default {
},
mounted
()
{
eventHub
.
$on
(
'
issuables:toggleBulkEdit
'
,
this
.
toggleBulkEditSidebar
);
this
.
fetchIssues
();
},
beforeDestroy
()
{
eventHub
.
$off
(
'
issuables:toggleBulkEdit
'
,
this
.
toggleBulkEditSidebar
);
...
...
@@ -423,19 +386,59 @@ export default {
return
this
.
fetchWithCache
(
this
.
projectMilestonesPath
,
'
milestones
'
,
'
title
'
,
search
,
true
);
},
fetchIterations
(
search
)
{
const
number
=
Number
(
search
);
return
!
search
||
Number
.
isNaN
(
number
)
?
axios
.
get
(
this
.
projectIterationsPath
,
{
params
:
{
search
}
})
:
axios
.
get
(
this
.
projectIterationsPath
,
{
params
:
{
id
:
number
}
});
return
axios
.
get
(
this
.
projectIterationsPath
,
{
params
:
{
search
}
});
},
fetchUsers
(
search
)
{
return
axios
.
get
(
this
.
autocompleteUsersPath
,
{
params
:
{
search
}
});
},
fetchIssues
()
{
if
(
!
this
.
hasProjectIssues
)
{
return
undefined
;
}
this
.
isLoading
=
true
;
const
filterParams
=
{
...
this
.
apiFilterParams
,
};
if
(
filterParams
.
epic_id
)
{
filterParams
.
epic_id
=
filterParams
.
epic_id
.
split
(
'
::&
'
).
pop
();
}
else
if
(
filterParams
[
'
not[epic_id]
'
])
{
filterParams
[
'
not[epic_id]
'
]
=
filterParams
[
'
not[epic_id]
'
].
split
(
'
::&
'
).
pop
();
}
return
axios
.
get
(
this
.
endpoint
,
{
params
:
{
due_date
:
this
.
dueDateFilter
,
page
:
this
.
page
,
per_page
:
PAGE_SIZE
,
search
:
this
.
searchQuery
,
state
:
this
.
state
,
with_labels_details
:
true
,
...
apiSortParams
[
this
.
sortKey
],
...
filterParams
,
},
})
.
then
(({
data
,
headers
})
=>
{
this
.
page
=
Number
(
headers
[
'
x-page
'
]);
this
.
totalIssues
=
Number
(
headers
[
'
x-total
'
]);
this
.
issues
=
data
.
map
((
issue
)
=>
convertObjectPropsToCamelCase
(
issue
,
{
deep
:
true
}));
this
.
exportCsvPathWithQuery
=
this
.
getExportCsvPathWithQuery
();
})
.
catch
(()
=>
{
createFlash
({
message
:
this
.
$options
.
i18n
.
errorFetchingIssues
});
})
.
finally
(()
=>
{
this
.
isLoading
=
false
;
});
},
getExportCsvPathWithQuery
()
{
return
`
${
this
.
exportCsvPath
}${
window
.
location
.
search
}
`
;
},
getStatus
(
issue
)
{
if
(
issue
.
closedAt
&&
issue
.
moved
)
{
if
(
issue
.
closedAt
&&
issue
.
moved
ToId
)
{
return
this
.
$options
.
i18n
.
closedMoved
;
}
if
(
issue
.
closedAt
)
{
...
...
@@ -466,30 +469,18 @@ export default {
},
handleClickTab
(
state
)
{
if
(
this
.
state
!==
state
)
{
this
.
pageParams
=
{
firstPageSize
:
PAGE_SIZE
,
};
this
.
page
=
1
;
}
this
.
state
=
state
;
this
.
fetchIssues
();
},
handleFilter
(
filter
)
{
this
.
filterTokens
=
filter
;
this
.
fetchIssues
();
},
handlePageChange
(
page
)
{
if
(
page
>
this
.
page
)
{
this
.
pageParams
=
{
afterCursor
:
this
.
pageInfo
.
endCursor
,
firstPageSize
:
PAGE_SIZE
,
};
}
else
{
this
.
pageParams
=
{
beforeCursor
:
this
.
pageInfo
.
startCursor
,
lastPageSize
:
PAGE_SIZE
,
};
}
this
.
page
=
page
;
this
.
fetchIssues
();
},
handleReorder
({
newIndex
,
oldIndex
})
{
const
issueToMove
=
this
.
issues
[
oldIndex
];
...
...
@@ -526,6 +517,7 @@ export default {
},
handleSort
(
value
)
{
this
.
sortKey
=
value
;
this
.
fetchIssues
();
},
toggleBulkEditSidebar
(
showBulkEditSidebar
)
{
this
.
showBulkEditSidebar
=
showBulkEditSidebar
;
...
...
@@ -549,13 +541,14 @@ export default {
:tabs=
"$options.IssuableListTabs"
:current-tab=
"state"
:tab-counts=
"tabCounts"
:issuables-loading=
"
$apollo.l
oading"
:issuables-loading=
"
isL
oading"
:is-manual-ordering=
"isManualOrdering"
:show-bulk-edit-sidebar=
"showBulkEditSidebar"
:show-pagination-controls=
"showPaginationControls"
:total-items=
"totalIssues"
:current-page=
"page"
:previous-page=
"p
reviousPage
"
:next-page=
"
nextPage
"
:previous-page=
"p
age - 1
"
:next-page=
"
page + 1
"
:url-params=
"urlParams"
@
click-tab=
"handleClickTab"
@
filter=
"handleFilter"
...
...
@@ -638,7 +631,7 @@ export default {
</li>
<blocking-issues-count
class=
"gl-display-none gl-sm-display-block"
:blocking-issues-count=
"issuable.block
edBy
Count"
:blocking-issues-count=
"issuable.block
ingIssues
Count"
:is-list-item=
"true"
/>
</
template
>
...
...
app/assets/javascripts/issues_list/constants.js
View file @
b93b446b
...
...
@@ -101,6 +101,7 @@ export const i18n = {
export
const
JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY
=
'
jira-import-success-alert-hide-map
'
;
export
const
PARAM_DUE_DATE
=
'
due_date
'
;
export
const
PARAM_PAGE
=
'
page
'
;
export
const
PARAM_SORT
=
'
sort
'
;
export
const
PARAM_STATE
=
'
state
'
;
...
...
@@ -124,21 +125,21 @@ export const CREATED_ASC = 'CREATED_ASC';
export
const
CREATED_DESC
=
'
CREATED_DESC
'
;
export
const
DUE_DATE_ASC
=
'
DUE_DATE_ASC
'
;
export
const
DUE_DATE_DESC
=
'
DUE_DATE_DESC
'
;
export
const
LABEL_PRIORITY_ASC
=
'
LABEL_PRIORITY_ASC
'
;
export
const
LABEL_PRIORITY_DESC
=
'
LABEL_PRIORITY_DESC
'
;
export
const
MILESTONE_DUE_ASC
=
'
MILESTONE_DUE_ASC
'
;
export
const
MILESTONE_DUE_DESC
=
'
MILESTONE_DUE_DESC
'
;
export
const
POPULARITY_ASC
=
'
POPULARITY_ASC
'
;
export
const
POPULARITY_DESC
=
'
POPULARITY_DESC
'
;
export
const
PRIORITY_ASC
=
'
PRIORITY_ASC
'
;
export
const
PRIORITY_DESC
=
'
PRIORITY_DESC
'
;
export
const
RELATIVE_POSITION_
ASC
=
'
RELATIVE_POSITION_A
SC
'
;
export
const
RELATIVE_POSITION_
DESC
=
'
RELATIVE_POSITION_DE
SC
'
;
export
const
UPDATED_ASC
=
'
UPDATED_ASC
'
;
export
const
UPDATED_DESC
=
'
UPDATED_DESC
'
;
export
const
WEIGHT_ASC
=
'
WEIGHT_ASC
'
;
export
const
WEIGHT_DESC
=
'
WEIGHT_DESC
'
;
const
PRIORITY_ASC_SORT
=
'
priority_asc
'
;
const
SORT_ASC
=
'
asc
'
;
const
SORT_DESC
=
'
desc
'
;
const
CREATED_DATE_SORT
=
'
created_date
'
;
const
CREATED_ASC_SORT
=
'
created_asc
'
;
const
UPDATED_DESC_SORT
=
'
updated_desc
'
;
...
...
@@ -146,30 +147,129 @@ const UPDATED_ASC_SORT = 'updated_asc';
const
MILESTONE_SORT
=
'
milestone
'
;
const
MILESTONE_DUE_DESC_SORT
=
'
milestone_due_desc
'
;
const
DUE_DATE_DESC_SORT
=
'
due_date_desc
'
;
const
LABEL_PRIORITY_ASC_SORT
=
'
label_priority_asc
'
;
const
POPULARITY_ASC_SORT
=
'
popularity_asc
'
;
const
WEIGHT_DESC_SORT
=
'
weight_desc
'
;
const
BLOCKING_ISSUES_DESC_SORT
=
'
blocking_issues_desc
'
;
const
BLOCKING_ISSUES
=
'
blocking_issues
'
;
export
const
apiSortParams
=
{
[
PRIORITY_DESC
]:
{
order_by
:
PRIORITY
,
sort
:
SORT_DESC
,
},
[
CREATED_ASC
]:
{
order_by
:
CREATED_AT
,
sort
:
SORT_ASC
,
},
[
CREATED_DESC
]:
{
order_by
:
CREATED_AT
,
sort
:
SORT_DESC
,
},
[
UPDATED_ASC
]:
{
order_by
:
UPDATED_AT
,
sort
:
SORT_ASC
,
},
[
UPDATED_DESC
]:
{
order_by
:
UPDATED_AT
,
sort
:
SORT_DESC
,
},
[
MILESTONE_DUE_ASC
]:
{
order_by
:
MILESTONE_DUE
,
sort
:
SORT_ASC
,
},
[
MILESTONE_DUE_DESC
]:
{
order_by
:
MILESTONE_DUE
,
sort
:
SORT_DESC
,
},
[
DUE_DATE_ASC
]:
{
order_by
:
DUE_DATE
,
sort
:
SORT_ASC
,
},
[
DUE_DATE_DESC
]:
{
order_by
:
DUE_DATE
,
sort
:
SORT_DESC
,
},
[
POPULARITY_ASC
]:
{
order_by
:
POPULARITY
,
sort
:
SORT_ASC
,
},
[
POPULARITY_DESC
]:
{
order_by
:
POPULARITY
,
sort
:
SORT_DESC
,
},
[
LABEL_PRIORITY_DESC
]:
{
order_by
:
LABEL_PRIORITY
,
sort
:
SORT_DESC
,
},
[
RELATIVE_POSITION_DESC
]:
{
order_by
:
RELATIVE_POSITION
,
per_page
:
100
,
sort
:
SORT_ASC
,
},
[
WEIGHT_ASC
]:
{
order_by
:
WEIGHT
,
sort
:
SORT_ASC
,
},
[
WEIGHT_DESC
]:
{
order_by
:
WEIGHT
,
sort
:
SORT_DESC
,
},
[
BLOCKING_ISSUES_DESC
]:
{
order_by
:
BLOCKING_ISSUES
,
sort
:
SORT_DESC
,
},
};
export
const
urlSortParams
=
{
[
PRIORITY_ASC
]:
PRIORITY_ASC_SORT
,
[
PRIORITY_DESC
]:
PRIORITY
,
[
CREATED_ASC
]:
CREATED_ASC_SORT
,
[
CREATED_DESC
]:
CREATED_DATE_SORT
,
[
UPDATED_ASC
]:
UPDATED_ASC_SORT
,
[
UPDATED_DESC
]:
UPDATED_DESC_SORT
,
[
MILESTONE_DUE_ASC
]:
MILESTONE_SORT
,
[
MILESTONE_DUE_DESC
]:
MILESTONE_DUE_DESC_SORT
,
[
DUE_DATE_ASC
]:
DUE_DATE
,
[
DUE_DATE_DESC
]:
DUE_DATE_DESC_SORT
,
[
POPULARITY_ASC
]:
POPULARITY_ASC_SORT
,
[
POPULARITY_DESC
]:
POPULARITY
,
[
LABEL_PRIORITY_ASC
]:
LABEL_PRIORITY_ASC_SORT
,
[
LABEL_PRIORITY_DESC
]:
LABEL_PRIORITY
,
[
RELATIVE_POSITION_ASC
]:
RELATIVE_POSITION
,
[
WEIGHT_ASC
]:
WEIGHT
,
[
WEIGHT_DESC
]:
WEIGHT_DESC_SORT
,
[
BLOCKING_ISSUES_DESC
]:
BLOCKING_ISSUES_DESC_SORT
,
[
PRIORITY_DESC
]:
{
sort
:
PRIORITY
,
},
[
CREATED_ASC
]:
{
sort
:
CREATED_ASC_SORT
,
},
[
CREATED_DESC
]:
{
sort
:
CREATED_DATE_SORT
,
},
[
UPDATED_ASC
]:
{
sort
:
UPDATED_ASC_SORT
,
},
[
UPDATED_DESC
]:
{
sort
:
UPDATED_DESC_SORT
,
},
[
MILESTONE_DUE_ASC
]:
{
sort
:
MILESTONE_SORT
,
},
[
MILESTONE_DUE_DESC
]:
{
sort
:
MILESTONE_DUE_DESC_SORT
,
},
[
DUE_DATE_ASC
]:
{
sort
:
DUE_DATE
,
},
[
DUE_DATE_DESC
]:
{
sort
:
DUE_DATE_DESC_SORT
,
},
[
POPULARITY_ASC
]:
{
sort
:
POPULARITY_ASC_SORT
,
},
[
POPULARITY_DESC
]:
{
sort
:
POPULARITY
,
},
[
LABEL_PRIORITY_DESC
]:
{
sort
:
LABEL_PRIORITY
,
},
[
RELATIVE_POSITION_DESC
]:
{
sort
:
RELATIVE_POSITION
,
per_page
:
100
,
},
[
WEIGHT_ASC
]:
{
sort
:
WEIGHT
,
},
[
WEIGHT_DESC
]:
{
sort
:
WEIGHT_DESC_SORT
,
},
[
BLOCKING_ISSUES_DESC
]:
{
sort
:
BLOCKING_ISSUES_DESC_SORT
,
},
};
export
const
MAX_LIST_SIZE
=
10
;
...
...
@@ -194,7 +294,12 @@ export const TOKEN_TYPE_WEIGHT = 'weight';
export
const
filters
=
{
[
TOKEN_TYPE_AUTHOR
]:
{
[
API_PARAM
]:
{
[
NORMAL_FILTER
]:
'
authorUsername
'
,
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
author_username
'
,
},
[
OPERATOR_IS_NOT
]:
{
[
NORMAL_FILTER
]:
'
not[author_username]
'
,
},
},
[
URL_PARAM
]:
{
[
OPERATOR_IS
]:
{
...
...
@@ -207,8 +312,13 @@ export const filters = {
},
[
TOKEN_TYPE_ASSIGNEE
]:
{
[
API_PARAM
]:
{
[
NORMAL_FILTER
]:
'
assigneeUsernames
'
,
[
SPECIAL_FILTER
]:
'
assigneeId
'
,
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
assignee_username
'
,
[
SPECIAL_FILTER
]:
'
assignee_id
'
,
},
[
OPERATOR_IS_NOT
]:
{
[
NORMAL_FILTER
]:
'
not[assignee_username]
'
,
},
},
[
URL_PARAM
]:
{
[
OPERATOR_IS
]:
{
...
...
@@ -223,7 +333,12 @@ export const filters = {
},
[
TOKEN_TYPE_MILESTONE
]:
{
[
API_PARAM
]:
{
[
NORMAL_FILTER
]:
'
milestoneTitle
'
,
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
milestone
'
,
},
[
OPERATOR_IS_NOT
]:
{
[
NORMAL_FILTER
]:
'
not[milestone]
'
,
},
},
[
URL_PARAM
]:
{
[
OPERATOR_IS
]:
{
...
...
@@ -236,13 +351,16 @@ export const filters = {
},
[
TOKEN_TYPE_LABEL
]:
{
[
API_PARAM
]:
{
[
NORMAL_FILTER
]:
'
labelName
'
,
[
SPECIAL_FILTER
]:
'
labelName
'
,
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
labels
'
,
},
[
OPERATOR_IS_NOT
]:
{
[
NORMAL_FILTER
]:
'
not[labels]
'
,
},
},
[
URL_PARAM
]:
{
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
label_name[]
'
,
[
SPECIAL_FILTER
]:
'
label_name[]
'
,
},
[
OPERATOR_IS_NOT
]:
{
[
NORMAL_FILTER
]:
'
not[label_name][]
'
,
...
...
@@ -251,8 +369,10 @@ export const filters = {
},
[
TOKEN_TYPE_MY_REACTION
]:
{
[
API_PARAM
]:
{
[
NORMAL_FILTER
]:
'
myReactionEmoji
'
,
[
SPECIAL_FILTER
]:
'
myReactionEmoji
'
,
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
my_reaction_emoji
'
,
[
SPECIAL_FILTER
]:
'
my_reaction_emoji
'
,
},
},
[
URL_PARAM
]:
{
[
OPERATOR_IS
]:
{
...
...
@@ -263,8 +383,10 @@ export const filters = {
},
[
TOKEN_TYPE_CONFIDENTIAL
]:
{
[
API_PARAM
]:
{
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
confidential
'
,
},
},
[
URL_PARAM
]:
{
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
confidential
'
,
...
...
@@ -273,23 +395,33 @@ export const filters = {
},
[
TOKEN_TYPE_ITERATION
]:
{
[
API_PARAM
]:
{
[
NORMAL_FILTER
]:
'
iterationId
'
,
[
SPECIAL_FILTER
]:
'
iterationWildcardId
'
,
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
iteration_title
'
,
[
SPECIAL_FILTER
]:
'
iteration_id
'
,
},
[
OPERATOR_IS_NOT
]:
{
[
NORMAL_FILTER
]:
'
not[iteration_title]
'
,
},
},
[
URL_PARAM
]:
{
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
iteration_
id
'
,
[
NORMAL_FILTER
]:
'
iteration_
title
'
,
[
SPECIAL_FILTER
]:
'
iteration_id
'
,
},
[
OPERATOR_IS_NOT
]:
{
[
NORMAL_FILTER
]:
'
not[iteration_
id
]
'
,
[
NORMAL_FILTER
]:
'
not[iteration_
title
]
'
,
},
},
},
[
TOKEN_TYPE_EPIC
]:
{
[
API_PARAM
]:
{
[
NORMAL_FILTER
]:
'
epicId
'
,
[
SPECIAL_FILTER
]:
'
epicId
'
,
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
epic_id
'
,
[
SPECIAL_FILTER
]:
'
epic_id
'
,
},
[
OPERATOR_IS_NOT
]:
{
[
NORMAL_FILTER
]:
'
not[epic_id]
'
,
},
},
[
URL_PARAM
]:
{
[
OPERATOR_IS
]:
{
...
...
@@ -303,9 +435,14 @@ export const filters = {
},
[
TOKEN_TYPE_WEIGHT
]:
{
[
API_PARAM
]:
{
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
weight
'
,
[
SPECIAL_FILTER
]:
'
weight
'
,
},
[
OPERATOR_IS_NOT
]:
{
[
NORMAL_FILTER
]:
'
not[weight]
'
,
},
},
[
URL_PARAM
]:
{
[
OPERATOR_IS
]:
{
[
NORMAL_FILTER
]:
'
weight
'
,
...
...
app/assets/javascripts/issues_list/index.js
View file @
b93b446b
...
...
@@ -73,13 +73,6 @@ export function mountIssuesListApp() {
return
false
;
}
Vue
.
use
(
VueApollo
);
const
defaultClient
=
createDefaultClient
({},
{
assumeImmutableResults
:
true
});
const
apolloProvider
=
new
VueApollo
({
defaultClient
,
});
const
{
autocompleteAwardEmojisPath
,
autocompleteUsersPath
,
...
...
@@ -90,6 +83,7 @@ export function mountIssuesListApp() {
email
,
emailsHelpPagePath
,
emptyStateSvgPath
,
endpoint
,
exportCsvPath
,
groupEpicsPath
,
hasBlockedIssuesFeature
,
...
...
@@ -121,13 +115,14 @@ export function mountIssuesListApp() {
el
,
// Currently does not use Vue Apollo, but need to provide {} for now until the
// issue is fixed upstream in https://github.com/vuejs/vue-apollo/pull/1153
apolloProvider
,
apolloProvider
:
{}
,
provide
:
{
autocompleteAwardEmojisPath
,
autocompleteUsersPath
,
calendarPath
,
canBulkUpdate
:
parseBoolean
(
canBulkUpdate
),
emptyStateSvgPath
,
endpoint
,
groupEpicsPath
,
hasBlockedIssuesFeature
:
parseBoolean
(
hasBlockedIssuesFeature
),
hasIssuableHealthStatusFeature
:
parseBoolean
(
hasIssuableHealthStatusFeature
),
...
...
app/assets/javascripts/issues_list/queries/get_issues.query.graphql
deleted
100644 → 0
View file @
2ee7ccf6
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "./issue_info.fragment.graphql"
query
getProjectIssues
(
$projectPath
:
ID
!
$search
:
String
$sort
:
IssueSort
$state
:
IssuableState
$assigneeId
:
String
$authorUsername
:
String
$assigneeUsernames
:
[
String
!]
$milestoneTitle
:
[
String
]
$labelName
:
[
String
]
$not
:
NegatedIssueFilterInput
$beforeCursor
:
String
$afterCursor
:
String
$firstPageSize
:
Int
$lastPageSize
:
Int
)
{
project
(
fullPath
:
$projectPath
)
{
issues
(
search
:
$search
sort
:
$sort
state
:
$state
assigneeId
:
$assigneeId
authorUsername
:
$authorUsername
assigneeUsernames
:
$assigneeUsernames
milestoneTitle
:
$milestoneTitle
labelName
:
$labelName
not
:
$not
before
:
$beforeCursor
after
:
$afterCursor
first
:
$firstPageSize
last
:
$lastPageSize
)
{
count
pageInfo
{
...
PageInfo
}
nodes
{
...
IssueInfo
}
}
}
}
app/assets/javascripts/issues_list/queries/issue_info.fragment.graphql
deleted
100644 → 0
View file @
2ee7ccf6
fragment
IssueInfo
on
Issue
{
id
iid
closedAt
confidential
createdAt
downvotes
dueDate
humanTimeEstimate
moved
title
updatedAt
upvotes
userDiscussionsCount
webUrl
assignees
{
nodes
{
id
avatarUrl
name
username
webUrl
}
}
author
{
id
avatarUrl
name
username
webUrl
}
labels
{
nodes
{
id
color
title
description
}
}
milestone
{
id
dueDate
startDate
webPath
title
}
taskCompletionStatus
{
completedCount
count
}
}
app/assets/javascripts/issues_list/utils.js
View file @
b93b446b
import
{
API_PARAM
,
BLOCKING_ISSUES_DESC
,
CREATED_ASC
,
CREATED_DESC
,
...
...
@@ -7,36 +6,29 @@ import {
DUE_DATE_DESC
,
DUE_DATE_VALUES
,
filters
,
LABEL_PRIORITY_ASC
,
LABEL_PRIORITY_DESC
,
MILESTONE_DUE_ASC
,
MILESTONE_DUE_DESC
,
NORMAL_FILTER
,
POPULARITY_ASC
,
POPULARITY_DESC
,
PRIORITY_ASC
,
PRIORITY_DESC
,
RELATIVE_POSITION_
A
SC
,
RELATIVE_POSITION_
DE
SC
,
SPECIAL_FILTER
,
SPECIAL_FILTER_VALUES
,
TOKEN_TYPE_ASSIGNEE
,
TOKEN_TYPE_ITERATION
,
UPDATED_ASC
,
UPDATED_DESC
,
URL_PARAM
,
urlSortParams
,
WEIGHT_ASC
,
WEIGHT_DESC
,
}
from
'
~/issues_list/constants
'
;
import
{
isPositiveInteger
}
from
'
~/lib/utils/number_utils
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
FILTERED_SEARCH_TERM
,
OPERATOR_IS_NOT
,
}
from
'
~/vue_shared/components/filtered_search_bar/constants
'
;
import
{
FILTERED_SEARCH_TERM
}
from
'
~/vue_shared/components/filtered_search_bar/constants
'
;
export
const
getSortKey
=
(
sort
)
=>
Object
.
keys
(
urlSortParams
).
find
((
key
)
=>
urlSortParams
[
key
]
===
sort
);
Object
.
keys
(
urlSortParams
).
find
((
key
)
=>
urlSortParams
[
key
]
.
sort
===
sort
);
export
const
getDueDateValue
=
(
value
)
=>
(
DUE_DATE_VALUES
.
includes
(
value
)
?
value
:
undefined
);
...
...
@@ -46,7 +38,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
id
:
1
,
title
:
__
(
'
Priority
'
),
sortDirection
:
{
ascending
:
PRIORITY_
A
SC
,
ascending
:
PRIORITY_
DE
SC
,
descending
:
PRIORITY_DESC
,
},
},
...
...
@@ -94,7 +86,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
id
:
7
,
title
:
__
(
'
Label priority
'
),
sortDirection
:
{
ascending
:
LABEL_PRIORITY_
A
SC
,
ascending
:
LABEL_PRIORITY_
DE
SC
,
descending
:
LABEL_PRIORITY_DESC
,
},
},
...
...
@@ -102,8 +94,8 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
id
:
8
,
title
:
__
(
'
Manual
'
),
sortDirection
:
{
ascending
:
RELATIVE_POSITION_
A
SC
,
descending
:
RELATIVE_POSITION_
A
SC
,
ascending
:
RELATIVE_POSITION_
DE
SC
,
descending
:
RELATIVE_POSITION_
DE
SC
,
},
},
];
...
...
@@ -186,36 +178,12 @@ const getFilterType = (data, tokenType = '') =>
?
SPECIAL_FILTER
:
NORMAL_FILTER
;
const
isIterationSpecialValue
=
(
tokenType
,
value
)
=>
tokenType
===
TOKEN_TYPE_ITERATION
&&
SPECIAL_FILTER_VALUES
.
includes
(
value
);
export
const
convertToApiParams
=
(
filterTokens
)
=>
{
const
params
=
{};
const
not
=
{};
filterTokens
.
filter
((
token
)
=>
token
.
type
!==
FILTERED_SEARCH_TERM
)
.
forEach
((
token
)
=>
{
const
filterType
=
getFilterType
(
token
.
value
.
data
,
token
.
type
);
const
field
=
filters
[
token
.
type
][
API_PARAM
][
filterType
];
const
obj
=
token
.
value
.
operator
===
OPERATOR_IS_NOT
?
not
:
params
;
const
data
=
isIterationSpecialValue
(
token
.
type
,
token
.
value
.
data
)
?
token
.
value
.
data
.
toUpperCase
()
:
token
.
value
.
data
;
Object
.
assign
(
obj
,
{
[
field
]:
obj
[
field
]
?
[
obj
[
field
],
data
].
flat
()
:
data
,
});
});
return
Object
.
keys
(
not
).
length
?
Object
.
assign
(
params
,
{
not
})
:
params
;
};
export
const
convertToUrlParams
=
(
filterTokens
)
=>
export
const
convertToParams
=
(
filterTokens
,
paramType
)
=>
filterTokens
.
filter
((
token
)
=>
token
.
type
!==
FILTERED_SEARCH_TERM
)
.
reduce
((
acc
,
token
)
=>
{
const
filterType
=
getFilterType
(
token
.
value
.
data
,
token
.
type
);
const
param
=
filters
[
token
.
type
][
URL_PARAM
][
token
.
value
.
operator
]?.[
filterType
];
const
param
=
filters
[
token
.
type
][
paramType
][
token
.
value
.
operator
]?.[
filterType
];
return
Object
.
assign
(
acc
,
{
[
param
]:
acc
[
param
]
?
[
acc
[
param
],
token
.
value
.
data
].
flat
()
:
token
.
value
.
data
,
});
...
...
app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue
View file @
b93b446b
...
...
@@ -39,7 +39,7 @@ export default {
return
this
.
value
.
data
;
},
activeIteration
()
{
return
this
.
iterations
.
find
((
iteration
)
=>
iteration
.
id
===
Number
(
this
.
currentValue
)
);
return
this
.
iterations
.
find
((
iteration
)
=>
iteration
.
title
===
this
.
currentValue
);
},
},
watch
:
{
...
...
@@ -99,8 +99,8 @@ export default {
<template
v-else
>
<gl-filtered-search-suggestion
v-for=
"iteration in iterations"
:key=
"iteration.
id
"
:value=
"
String(iteration.id)
"
:key=
"iteration.
title
"
:value=
"
iteration.title
"
>
{{
iteration
.
title
}}
</gl-filtered-search-suggestion>
...
...
app/helpers/issues_helper.rb
View file @
b93b446b
...
...
@@ -190,6 +190,7 @@ module IssuesHelper
email:
current_user
&
.
notification_email
,
emails_help_page_path:
help_page_path
(
'development/emails'
,
anchor:
'email-namespace'
),
empty_state_svg_path:
image_path
(
'illustrations/issues.svg'
),
endpoint:
expose_path
(
api_v4_projects_issues_path
(
id:
project
.
id
)),
export_csv_path:
export_csv_project_issues_path
(
project
),
has_project_issues:
project_issues
(
project
).
exists?
.
to_s
,
import_csv_issues_path:
import_csv_namespace_project_issues_path
,
...
...
ee/app/assets/javascripts/issues_list/queries/get_issues.query.graphql
deleted
100644 → 0
View file @
2ee7ccf6
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "~/issues_list/queries/issue_info.fragment.graphql"
query
getProjectIssues
(
$projectPath
:
ID
!
$search
:
String
$sort
:
IssueSort
$state
:
IssuableState
$assigneeId
:
String
$authorUsername
:
String
$assigneeUsernames
:
[
String
!]
$milestoneTitle
:
[
String
]
$labelName
:
[
String
]
$iterationId
:
[
ID
]
$iterationWildcardId
:
IterationWildcardId
$epicId
:
String
$weight
:
String
$not
:
NegatedIssueFilterInput
$beforeCursor
:
String
$afterCursor
:
String
$firstPageSize
:
Int
$lastPageSize
:
Int
)
{
project
(
fullPath
:
$projectPath
)
{
issues
(
search
:
$search
sort
:
$sort
state
:
$state
assigneeId
:
$assigneeId
authorUsername
:
$authorUsername
assigneeUsernames
:
$assigneeUsernames
milestoneTitle
:
$milestoneTitle
labelName
:
$labelName
iterationId
:
$iterationId
iterationWildcardId
:
$iterationWildcardId
epicId
:
$epicId
weight
:
$weight
not
:
$not
before
:
$beforeCursor
after
:
$afterCursor
first
:
$firstPageSize
last
:
$lastPageSize
)
{
count
pageInfo
{
...
PageInfo
}
nodes
{
...
IssueInfo
blockedByCount
healthStatus
weight
}
}
}
}
spec/frontend/issues_list/components/issue_card_time_info_spec.js
View file @
b93b446b
...
...
@@ -13,10 +13,12 @@ describe('IssuesListApp component', () => {
dueDate
:
'
2020-12-17
'
,
startDate
:
'
2020-12-10
'
,
title
:
'
My milestone
'
,
web
Path
:
'
/milestone/webPath
'
,
web
Url
:
'
/milestone/webUrl
'
,
},
dueDate
:
'
2020-12-12
'
,
timeStats
:
{
humanTimeEstimate
:
'
1w
'
,
},
};
const
findMilestone
=
()
=>
wrapper
.
find
(
'
[data-testid="issuable-milestone"]
'
);
...
...
@@ -54,7 +56,7 @@ describe('IssuesListApp component', () => {
expect
(
milestone
.
text
()).
toBe
(
issue
.
milestone
.
title
);
expect
(
milestone
.
find
(
GlIcon
).
props
(
'
name
'
)).
toBe
(
'
clock
'
);
expect
(
milestone
.
find
(
GlLink
).
attributes
(
'
href
'
)).
toBe
(
issue
.
milestone
.
web
Path
);
expect
(
milestone
.
find
(
GlLink
).
attributes
(
'
href
'
)).
toBe
(
issue
.
milestone
.
web
Url
);
});
describe
.
each
`
...
...
@@ -100,7 +102,7 @@ describe('IssuesListApp component', () => {
const
timeEstimate
=
wrapper
.
find
(
'
[data-testid="time-estimate"]
'
);
expect
(
timeEstimate
.
text
()).
toBe
(
issue
.
humanTimeEstimate
);
expect
(
timeEstimate
.
text
()).
toBe
(
issue
.
timeStats
.
humanTimeEstimate
);
expect
(
timeEstimate
.
attributes
(
'
title
'
)).
toBe
(
'
Estimate
'
);
expect
(
timeEstimate
.
find
(
GlIcon
).
props
(
'
name
'
)).
toBe
(
'
timer
'
);
});
...
...
spec/frontend/issues_list/components/issues_list_app_spec.js
View file @
b93b446b
import
{
GlButton
,
GlEmptyState
,
GlLink
}
from
'
@gitlab/ui
'
;
import
{
createLocalVue
,
mount
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
mount
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
AxiosMockAdapter
from
'
axios-mock-adapter
'
;
import
{
nextTick
}
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
getIssuesQuery
from
'
ee_else_ce/issues_list/queries/get_issues.query.graphql
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
filteredTokens
,
getIssuesQueryResponse
,
locationSearch
,
urlParams
,
}
from
'
jest/issues_list/mock_data
'
;
import
{
apiParams
,
filteredTokens
,
locationSearch
,
urlParams
}
from
'
jest/issues_list/mock_data
'
;
import
createFlash
from
'
~/flash
'
;
import
CsvImportExportButtons
from
'
~/issuable/components/csv_import_export_buttons.vue
'
;
import
IssuableByEmail
from
'
~/issuable/components/issuable_by_email.vue
'
;
...
...
@@ -20,9 +11,13 @@ import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import
{
IssuableListTabs
,
IssuableStates
}
from
'
~/issuable_list/constants
'
;
import
IssuesListApp
from
'
~/issues_list/components/issues_list_app.vue
'
;
import
{
apiSortParams
,
CREATED_DESC
,
DUE_DATE_OVERDUE
,
PAGE_SIZE
,
PAGE_SIZE_MANUAL
,
PARAM_DUE_DATE
,
RELATIVE_POSITION_DESC
,
TOKEN_TYPE_ASSIGNEE
,
TOKEN_TYPE_AUTHOR
,
TOKEN_TYPE_CONFIDENTIAL
,
...
...
@@ -45,14 +40,12 @@ describe('IssuesListApp component', () => {
let
axiosMock
;
let
wrapper
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
VueApollo
);
const
defaultProvide
=
{
autocompleteUsersPath
:
'
autocomplete/users/path
'
,
calendarPath
:
'
calendar/path
'
,
canBulkUpdate
:
false
,
emptyStateSvgPath
:
'
empty-state.svg
'
,
endpoint
:
'
api/endpoint
'
,
exportCsvPath
:
'
export/csv/path
'
,
hasBlockedIssuesFeature
:
true
,
hasIssueWeightsFeature
:
true
,
...
...
@@ -68,6 +61,22 @@ describe('IssuesListApp component', () => {
signInPath
:
'
sign/in/path
'
,
};
const
state
=
'
opened
'
;
const
xPage
=
1
;
const
xTotal
=
25
;
const
tabCounts
=
{
opened
:
xTotal
,
closed
:
undefined
,
all
:
undefined
,
};
const
fetchIssuesResponse
=
{
data
:
[],
headers
:
{
'
x-page
'
:
xPage
,
'
x-total
'
:
xTotal
,
},
};
const
findCsvImportExportButtons
=
()
=>
wrapper
.
findComponent
(
CsvImportExportButtons
);
const
findIssuableByEmail
=
()
=>
wrapper
.
findComponent
(
IssuableByEmail
);
const
findGlButton
=
()
=>
wrapper
.
findComponent
(
GlButton
);
...
...
@@ -77,26 +86,19 @@ describe('IssuesListApp component', () => {
const
findGlLink
=
()
=>
wrapper
.
findComponent
(
GlLink
);
const
findIssuableList
=
()
=>
wrapper
.
findComponent
(
IssuableList
);
const
mountComponent
=
({
provide
=
{},
response
=
getIssuesQueryResponse
,
mountFn
=
shallowMount
,
}
=
{})
=>
{
const
requestHandlers
=
[[
getIssuesQuery
,
jest
.
fn
().
mockResolvedValue
(
response
)]];
const
apolloProvider
=
createMockApollo
(
requestHandlers
);
return
mountFn
(
IssuesListApp
,
{
localVue
,
apolloProvider
,
const
mountComponent
=
({
provide
=
{},
mountFn
=
shallowMount
}
=
{})
=>
mountFn
(
IssuesListApp
,
{
provide
:
{
...
defaultProvide
,
...
provide
,
},
});
};
beforeEach
(()
=>
{
axiosMock
=
new
AxiosMockAdapter
(
axios
);
axiosMock
.
onGet
(
defaultProvide
.
endpoint
)
.
reply
(
200
,
fetchIssuesResponse
.
data
,
fetchIssuesResponse
.
headers
);
});
afterEach
(()
=>
{
...
...
@@ -106,37 +108,28 @@ describe('IssuesListApp component', () => {
});
describe
(
'
IssuableList
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(
async
()
=>
{
wrapper
=
mountComponent
();
jest
.
runOnlyPendingTimer
s
();
await
waitForPromise
s
();
});
it
(
'
renders
'
,
()
=>
{
expect
(
findIssuableList
().
props
()).
toMatchObject
({
namespace
:
defaultProvide
.
projectPath
,
recentSearchesStorageKey
:
'
issues
'
,
searchInputPlaceholder
:
IssuesListApp
.
i18n
.
searchPlaceholder
,
searchInputPlaceholder
:
'
Search or filter results…
'
,
sortOptions
:
getSortOptions
(
true
,
true
),
initialSortBy
:
CREATED_DESC
,
issuables
:
getIssuesQueryResponse
.
data
.
project
.
issues
.
nodes
,
tabs
:
IssuableListTabs
,
currentTab
:
IssuableStates
.
Opened
,
tabCounts
:
{
opened
:
1
,
closed
:
undefined
,
all
:
undefined
,
},
issuablesLoading
:
false
,
isManualOrdering
:
false
,
showBulkEditSidebar
:
false
,
showPaginationControls
:
true
,
currentPage
:
1
,
previousPage
:
Number
(
getIssuesQueryResponse
.
data
.
project
.
issues
.
pageInfo
.
hasPreviousPage
),
nextPage
:
Number
(
getIssuesQueryResponse
.
data
.
project
.
issues
.
pageInfo
.
hasNextPage
),
urlParams
:
{
sort
:
urlSortParams
[
CREATED_DESC
],
state
:
IssuableStates
.
Opened
,
},
tabCounts
,
showPaginationControls
:
false
,
issuables
:
[],
totalItems
:
xTotal
,
currentPage
:
xPage
,
previousPage
:
xPage
-
1
,
nextPage
:
xPage
+
1
,
urlParams
:
{
page
:
xPage
,
state
},
});
});
});
...
...
@@ -164,9 +157,9 @@ describe('IssuesListApp component', () => {
describe
(
'
csv import/export component
'
,
()
=>
{
describe
(
'
when user is signed in
'
,
()
=>
{
const
search
=
'
?search=refactor&sort=created_date&state=opened
'
;
it
(
'
renders
'
,
async
()
=>
{
const
search
=
'
?page=1&search=refactor&state=opened&sort=created_date
'
;
beforeEach
(()
=>
{
global
.
jsdom
.
reconfigure
({
url
:
`
${
TEST_HOST
}${
search
}
`
});
wrapper
=
mountComponent
({
...
...
@@ -174,13 +167,11 @@ describe('IssuesListApp component', () => {
mountFn
:
mount
,
});
jest
.
runOnlyPendingTimers
();
});
await
waitForPromises
();
it
(
'
renders
'
,
()
=>
{
expect
(
findCsvImportExportButtons
().
props
()).
toMatchObject
({
exportCsvPath
:
`
${
defaultProvide
.
exportCsvPath
}${
search
}
`
,
issuableCount
:
1
,
issuableCount
:
xTotal
,
});
});
});
...
...
@@ -198,7 +189,7 @@ describe('IssuesListApp component', () => {
it
(
'
renders when user has permissions
'
,
()
=>
{
wrapper
=
mountComponent
({
provide
:
{
canBulkUpdate
:
true
},
mountFn
:
mount
});
expect
(
findGlButtonAt
(
2
).
text
()).
toBe
(
IssuesListApp
.
i18n
.
editIssues
);
expect
(
findGlButtonAt
(
2
).
text
()).
toBe
(
'
Edit issues
'
);
});
it
(
'
does not render when user does not have permissions
'
,
()
=>
{
...
...
@@ -224,7 +215,7 @@ describe('IssuesListApp component', () => {
it
(
'
renders when user has permissions
'
,
()
=>
{
wrapper
=
mountComponent
({
provide
:
{
showNewIssueLink
:
true
},
mountFn
:
mount
});
expect
(
findGlButtonAt
(
2
).
text
()).
toBe
(
IssuesListApp
.
i18n
.
newIssueLabel
);
expect
(
findGlButtonAt
(
2
).
text
()).
toBe
(
'
New issue
'
);
expect
(
findGlButtonAt
(
2
).
attributes
(
'
href
'
)).
toBe
(
defaultProvide
.
newIssuePath
);
});
...
...
@@ -247,6 +238,18 @@ describe('IssuesListApp component', () => {
});
});
describe
(
'
page
'
,
()
=>
{
it
(
'
is set from the url params
'
,
()
=>
{
const
page
=
5
;
global
.
jsdom
.
reconfigure
({
url
:
setUrlParams
({
page
},
TEST_HOST
)
});
wrapper
=
mountComponent
();
expect
(
findIssuableList
().
props
(
'
currentPage
'
)).
toBe
(
page
);
});
});
describe
(
'
search
'
,
()
=>
{
it
(
'
is set from the url params
'
,
()
=>
{
global
.
jsdom
.
reconfigure
({
url
:
`
${
TEST_HOST
}${
locationSearch
}
`
});
...
...
@@ -259,15 +262,13 @@ describe('IssuesListApp component', () => {
describe
(
'
sort
'
,
()
=>
{
it
.
each
(
Object
.
keys
(
urlSortParams
))(
'
is set as %s from the url params
'
,
(
sortKey
)
=>
{
global
.
jsdom
.
reconfigure
({
url
:
setUrlParams
({
sort
:
urlSortParams
[
sortKey
]
},
TEST_HOST
),
});
global
.
jsdom
.
reconfigure
({
url
:
setUrlParams
(
urlSortParams
[
sortKey
],
TEST_HOST
)
});
wrapper
=
mountComponent
();
expect
(
findIssuableList
().
props
()).
toMatchObject
({
initialSortBy
:
sortKey
,
urlParams
:
{
sort
:
urlSortParams
[
sortKey
]
}
,
urlParams
:
urlSortParams
[
sortKey
]
,
});
});
});
...
...
@@ -325,10 +326,12 @@ describe('IssuesListApp component', () => {
describe
(
'
empty states
'
,
()
=>
{
describe
(
'
when there are issues
'
,
()
=>
{
describe
(
'
when search returns no results
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(
async
()
=>
{
global
.
jsdom
.
reconfigure
({
url
:
`
${
TEST_HOST
}
?search=no+results`
});
wrapper
=
mountComponent
({
provide
:
{
hasProjectIssues
:
true
},
mountFn
:
mount
});
await
waitForPromises
();
});
it
(
'
shows empty state
'
,
()
=>
{
...
...
@@ -341,8 +344,10 @@ describe('IssuesListApp component', () => {
});
describe
(
'
when "Open" tab has no issues
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(
async
()
=>
{
wrapper
=
mountComponent
({
provide
:
{
hasProjectIssues
:
true
},
mountFn
:
mount
});
await
waitForPromises
();
});
it
(
'
shows empty state
'
,
()
=>
{
...
...
@@ -355,12 +360,14 @@ describe('IssuesListApp component', () => {
});
describe
(
'
when "Closed" tab has no issues
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(
async
()
=>
{
global
.
jsdom
.
reconfigure
({
url
:
setUrlParams
({
state
:
IssuableStates
.
Closed
},
TEST_HOST
),
});
wrapper
=
mountComponent
({
provide
:
{
hasProjectIssues
:
true
},
mountFn
:
mount
});
await
waitForPromises
();
});
it
(
'
shows empty state
'
,
()
=>
{
...
...
@@ -529,67 +536,74 @@ describe('IssuesListApp component', () => {
describe
(
'
events
'
,
()
=>
{
describe
(
'
when "click-tab" event is emitted by IssuableList
'
,
()
=>
{
beforeEach
(()
=>
{
axiosMock
.
onGet
(
defaultProvide
.
endpoint
).
reply
(
200
,
fetchIssuesResponse
.
data
,
{
'
x-page
'
:
2
,
'
x-total
'
:
xTotal
,
});
wrapper
=
mountComponent
();
findIssuableList
().
vm
.
$emit
(
'
click-tab
'
,
IssuableStates
.
Closed
);
});
it
(
'
updates to the new tab
'
,
()
=>
{
expect
(
findIssuableList
().
props
(
'
currentTab
'
)).
toBe
(
IssuableStates
.
Closed
);
it
(
'
makes API call to filter the list by the new state and resets the page to 1
'
,
()
=>
{
expect
(
axiosMock
.
history
.
get
[
1
].
params
).
toMatchObject
({
page
:
1
,
state
:
IssuableStates
.
Closed
,
});
});
});
describe
(
'
when "page-change" event is emitted by IssuableList
'
,
()
=>
{
beforeEach
(()
=>
{
const
data
=
[{
id
:
10
,
title
:
'
title
'
,
state
}];
const
page
=
2
;
const
totalItems
=
21
;
beforeEach
(
async
()
=>
{
axiosMock
.
onGet
(
defaultProvide
.
endpoint
).
reply
(
200
,
data
,
{
'
x-page
'
:
page
,
'
x-total
'
:
totalItems
,
});
wrapper
=
mountComponent
();
findIssuableList
().
vm
.
$emit
(
'
page-change
'
,
2
);
findIssuableList
().
vm
.
$emit
(
'
page-change
'
,
page
);
await
waitForPromises
();
});
it
(
'
updates to the new page
'
,
()
=>
{
expect
(
findIssuableList
().
props
(
'
currentPage
'
)).
toBe
(
2
);
it
(
'
fetches issues with expected params
'
,
()
=>
{
expect
(
axiosMock
.
history
.
get
[
1
].
params
).
toMatchObject
({
page
,
per_page
:
PAGE_SIZE
,
state
,
with_labels_details
:
true
,
});
});
describe
(
'
when "reorder" event is emitted by IssuableList
'
,
()
=>
{
const
issueOne
=
{
...
getIssuesQueryResponse
.
data
.
project
.
issues
.
nodes
[
0
],
id
:
1
,
iid
:
101
,
title
:
'
Issue one
'
,
};
const
issueTwo
=
{
...
getIssuesQueryResponse
.
data
.
project
.
issues
.
nodes
[
0
],
id
:
2
,
iid
:
102
,
title
:
'
Issue two
'
,
};
const
issueThree
=
{
...
getIssuesQueryResponse
.
data
.
project
.
issues
.
nodes
[
0
],
id
:
3
,
iid
:
103
,
title
:
'
Issue three
'
,
};
const
issueFour
=
{
...
getIssuesQueryResponse
.
data
.
project
.
issues
.
nodes
[
0
],
id
:
4
,
iid
:
104
,
title
:
'
Issue four
'
,
};
const
response
=
{
data
:
{
project
:
{
issues
:
{
...
getIssuesQueryResponse
.
data
.
project
.
issues
,
nodes
:
[
issueOne
,
issueTwo
,
issueThree
,
issueFour
],
},
},
},
};
it
(
'
updates IssuableList with response data
'
,
()
=>
{
expect
(
findIssuableList
().
props
()).
toMatchObject
({
issuables
:
data
,
totalItems
,
currentPage
:
page
,
previousPage
:
page
-
1
,
nextPage
:
page
+
1
,
urlParams
:
{
page
,
state
},
});
});
});
beforeEach
(()
=>
{
wrapper
=
mountComponent
({
response
});
jest
.
runOnlyPendingTimers
();
describe
(
'
when "reorder" event is emitted by IssuableList
'
,
()
=>
{
const
issueOne
=
{
id
:
1
,
iid
:
101
,
title
:
'
Issue one
'
};
const
issueTwo
=
{
id
:
2
,
iid
:
102
,
title
:
'
Issue two
'
};
const
issueThree
=
{
id
:
3
,
iid
:
103
,
title
:
'
Issue three
'
};
const
issueFour
=
{
id
:
4
,
iid
:
104
,
title
:
'
Issue four
'
};
const
issues
=
[
issueOne
,
issueTwo
,
issueThree
,
issueFour
];
beforeEach
(
async
()
=>
{
axiosMock
.
onGet
(
defaultProvide
.
endpoint
).
reply
(
200
,
issues
,
fetchIssuesResponse
.
headers
);
wrapper
=
mountComponent
();
await
waitForPromises
();
});
describe
(
'
when successful
'
,
()
=>
{
...
...
@@ -630,18 +644,21 @@ describe('IssuesListApp component', () => {
});
describe
(
'
when "sort" event is emitted by IssuableList
'
,
()
=>
{
it
.
each
(
Object
.
keys
(
url
SortParams
))(
'
updates to the new sort when payload is
`%s`
'
,
it
.
each
(
Object
.
keys
(
api
SortParams
))(
'
fetches issues with correct params with payload
`%s`
'
,
async
(
sortKey
)
=>
{
wrapper
=
mountComponent
();
findIssuableList
().
vm
.
$emit
(
'
sort
'
,
sortKey
);
jest
.
runOnlyPendingTimers
();
await
nextTick
();
await
waitForPromises
();
expect
(
findIssuableList
().
props
(
'
urlParams
'
)).
toMatchObject
({
sort
:
urlSortParams
[
sortKey
],
expect
(
axiosMock
.
history
.
get
[
1
].
params
).
toEqual
({
page
:
xPage
,
per_page
:
sortKey
===
RELATIVE_POSITION_DESC
?
PAGE_SIZE_MANUAL
:
PAGE_SIZE
,
state
,
with_labels_details
:
true
,
...
apiSortParams
[
sortKey
],
});
},
);
...
...
@@ -651,11 +668,13 @@ describe('IssuesListApp component', () => {
beforeEach
(()
=>
{
wrapper
=
mountComponent
();
jest
.
spyOn
(
eventHub
,
'
$emit
'
);
});
it
(
'
emits an "issuables:updateBulkEdit" event to the legacy bulk edit class
'
,
async
()
=>
{
findIssuableList
().
vm
.
$emit
(
'
update-legacy-bulk-edit
'
);
});
it
(
'
emits an "issuables:updateBulkEdit" event to the legacy bulk edit class
'
,
()
=>
{
await
waitForPromises
();
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
issuables:updateBulkEdit
'
);
});
});
...
...
@@ -667,6 +686,10 @@ describe('IssuesListApp component', () => {
findIssuableList
().
vm
.
$emit
(
'
filter
'
,
filteredTokens
);
});
it
(
'
makes an API call to search for issues with the search term
'
,
()
=>
{
expect
(
axiosMock
.
history
.
get
[
1
].
params
).
toMatchObject
(
apiParams
);
});
it
(
'
updates IssuableList with url params
'
,
()
=>
{
expect
(
findIssuableList
().
props
(
'
urlParams
'
)).
toMatchObject
(
urlParams
);
});
...
...
spec/frontend/issues_list/mock_data.js
View file @
b93b446b
...
...
@@ -3,76 +3,6 @@ import {
OPERATOR_IS_NOT
,
}
from
'
~/vue_shared/components/filtered_search_bar/constants
'
;
export
const
getIssuesQueryResponse
=
{
data
:
{
project
:
{
issues
:
{
count
:
1
,
pageInfo
:
{
hasNextPage
:
false
,
hasPreviousPage
:
false
,
startCursor
:
'
startcursor
'
,
endCursor
:
'
endcursor
'
,
},
nodes
:
[
{
id
:
'
gid://gitlab/Issue/123456
'
,
iid
:
'
789
'
,
blockedByCount
:
1
,
closedAt
:
null
,
confidential
:
false
,
createdAt
:
'
2021-05-22T04:08:01Z
'
,
downvotes
:
2
,
dueDate
:
'
2021-05-29
'
,
healthStatus
:
null
,
humanTimeEstimate
:
null
,
moved
:
false
,
title
:
'
Issue title
'
,
updatedAt
:
'
2021-05-22T04:08:01Z
'
,
upvotes
:
3
,
userDiscussionsCount
:
4
,
webUrl
:
'
project/-/issues/789
'
,
weight
:
5
,
assignees
:
{
nodes
:
[
{
id
:
'
gid://gitlab/User/234
'
,
avatarUrl
:
'
avatar/url
'
,
name
:
'
Marge Simpson
'
,
username
:
'
msimpson
'
,
webUrl
:
'
url/msimpson
'
,
},
],
},
author
:
{
id
:
'
gid://gitlab/User/456
'
,
avatarUrl
:
'
avatar/url
'
,
name
:
'
Homer Simpson
'
,
username
:
'
hsimpson
'
,
webUrl
:
'
url/hsimpson
'
,
},
labels
:
{
nodes
:
[
{
id
:
'
gid://gitlab/ProjectLabel/456
'
,
color
:
'
#333
'
,
title
:
'
Label title
'
,
description
:
'
Label description
'
,
},
],
},
milestone
:
null
,
taskCompletionStatus
:
{
completedCount
:
1
,
count
:
2
,
},
},
],
},
},
},
};
export
const
locationSearch
=
[
'
?search=find+issues
'
,
'
author_username=homer
'
,
...
...
@@ -89,8 +19,8 @@ export const locationSearch = [
'
not[label_name][]=drama
'
,
'
my_reaction_emoji=thumbsup
'
,
'
confidential=no
'
,
'
iteration_
id=
4
'
,
'
not[iteration_
id]=
20
'
,
'
iteration_
title=season:+%23
4
'
,
'
not[iteration_
title]=season:+%23
20
'
,
'
epic_id=gitlab-org%3A%3A%2612
'
,
'
not[epic_id]=gitlab-org%3A%3A%2634
'
,
'
weight=1
'
,
...
...
@@ -121,8 +51,8 @@ export const filteredTokens = [
{
type
:
'
labels
'
,
value
:
{
data
:
'
drama
'
,
operator
:
OPERATOR_IS_NOT
}
},
{
type
:
'
my_reaction_emoji
'
,
value
:
{
data
:
'
thumbsup
'
,
operator
:
OPERATOR_IS
}
},
{
type
:
'
confidential
'
,
value
:
{
data
:
'
no
'
,
operator
:
OPERATOR_IS
}
},
{
type
:
'
iteration
'
,
value
:
{
data
:
'
4
'
,
operator
:
OPERATOR_IS
}
},
{
type
:
'
iteration
'
,
value
:
{
data
:
'
20
'
,
operator
:
OPERATOR_IS_NOT
}
},
{
type
:
'
iteration
'
,
value
:
{
data
:
'
season: #
4
'
,
operator
:
OPERATOR_IS
}
},
{
type
:
'
iteration
'
,
value
:
{
data
:
'
season: #
20
'
,
operator
:
OPERATOR_IS_NOT
}
},
{
type
:
'
epic_id
'
,
value
:
{
data
:
'
gitlab-org::&12
'
,
operator
:
OPERATOR_IS
}
},
{
type
:
'
epic_id
'
,
value
:
{
data
:
'
gitlab-org::&34
'
,
operator
:
OPERATOR_IS_NOT
}
},
{
type
:
'
weight
'
,
value
:
{
data
:
'
1
'
,
operator
:
OPERATOR_IS
}
},
...
...
@@ -141,32 +71,30 @@ export const filteredTokensWithSpecialValues = [
];
export
const
apiParams
=
{
authorUsername
:
'
homer
'
,
assigneeUsernames
:
[
'
bart
'
,
'
lisa
'
],
milestoneTitle
:
'
season 4
'
,
labelName
:
[
'
cartoon
'
,
'
tv
'
],
myReactionEmoji
:
'
thumbsup
'
,
author_username
:
'
homer
'
,
'
not[author_username]
'
:
'
marge
'
,
assignee_username
:
[
'
bart
'
,
'
lisa
'
],
'
not[assignee_username]
'
:
[
'
patty
'
,
'
selma
'
],
milestone
:
'
season 4
'
,
'
not[milestone]
'
:
'
season 20
'
,
labels
:
[
'
cartoon
'
,
'
tv
'
],
'
not[labels]
'
:
[
'
live action
'
,
'
drama
'
],
my_reaction_emoji
:
'
thumbsup
'
,
confidential
:
'
no
'
,
iterationId
:
'
4
'
,
epicId
:
'
gitlab-org::&12
'
,
iteration_title
:
'
season: #4
'
,
'
not[iteration_title]
'
:
'
season: #20
'
,
epic_id
:
'
12
'
,
'
not[epic_id]
'
:
'
gitlab-org::&34
'
,
weight
:
'
1
'
,
not
:
{
authorUsername
:
'
marge
'
,
assigneeUsernames
:
[
'
patty
'
,
'
selma
'
],
milestoneTitle
:
'
season 20
'
,
labelName
:
[
'
live action
'
,
'
drama
'
],
iterationId
:
'
20
'
,
epicId
:
'
gitlab-org::&34
'
,
weight
:
'
3
'
,
},
'
not[weight]
'
:
'
3
'
,
};
export
const
apiParamsWithSpecialValues
=
{
assignee
I
d
:
'
123
'
,
assignee
Usernames
:
'
bart
'
,
my
ReactionE
moji
:
'
None
'
,
iteration
WildcardId
:
'
CURRENT
'
,
epic
I
d
:
'
None
'
,
assignee
_i
d
:
'
123
'
,
assignee
_username
:
'
bart
'
,
my
_reaction_e
moji
:
'
None
'
,
iteration
_id
:
'
Current
'
,
epic
_i
d
:
'
None
'
,
weight
:
'
None
'
,
};
...
...
@@ -181,8 +109,8 @@ export const urlParams = {
'
not[label_name][]
'
:
[
'
live action
'
,
'
drama
'
],
my_reaction_emoji
:
'
thumbsup
'
,
confidential
:
'
no
'
,
iteration_
id
:
'
4
'
,
'
not[iteration_
id]
'
:
'
20
'
,
iteration_
title
:
'
season: #
4
'
,
'
not[iteration_
title]
'
:
'
season: #
20
'
,
epic_id
:
'
gitlab-org%3A%3A%2612
'
,
'
not[epic_id]
'
:
'
gitlab-org::&34
'
,
weight
:
'
1
'
,
...
...
spec/frontend/issues_list/utils_spec.js
View file @
b93b446b
...
...
@@ -8,20 +8,19 @@ import {
urlParams
,
urlParamsWithSpecialValues
,
}
from
'
jest/issues_list/mock_data
'
;
import
{
DUE_DATE_VALUES
,
urlSortParams
}
from
'
~/issues_list/constants
'
;
import
{
API_PARAM
,
DUE_DATE_VALUES
,
URL_PARAM
,
urlSortParams
}
from
'
~/issues_list/constants
'
;
import
{
convertTo
Url
Params
,
convertToParams
,
convertToSearchQuery
,
getDueDateValue
,
getFilterTokens
,
getSortKey
,
getSortOptions
,
convertToApiParams
,
}
from
'
~/issues_list/utils
'
;
describe
(
'
getSortKey
'
,
()
=>
{
it
.
each
(
Object
.
keys
(
urlSortParams
))(
'
returns %s given the correct inputs
'
,
(
sortKey
)
=>
{
const
sort
=
urlSortParams
[
sortKey
];
const
{
sort
}
=
urlSortParams
[
sortKey
];
expect
(
getSortKey
(
sort
)).
toBe
(
sortKey
);
});
});
...
...
@@ -81,26 +80,31 @@ describe('getFilterTokens', () => {
});
});
describe
(
'
convertTo
Api
Params
'
,
()
=>
{
describe
(
'
convertToParams
'
,
()
=>
{
it
(
'
returns api params given filtered tokens
'
,
()
=>
{
expect
(
convertToApiParams
(
filteredTokens
)).
toEqual
(
apiParams
);
expect
(
convertToParams
(
filteredTokens
,
API_PARAM
)).
toEqual
({
...
apiParams
,
epic_id
:
'
gitlab-org::&12
'
,
});
});
it
(
'
returns api params given filtered tokens with special values
'
,
()
=>
{
expect
(
convertToApiParams
(
filteredTokensWithSpecialValues
)).
toEqual
(
apiParamsWithSpecialValues
);
expect
(
convertToParams
(
filteredTokensWithSpecialValues
,
API_PARAM
)).
toEqual
(
apiParamsWithSpecialValues
,
);
});
});
describe
(
'
convertToUrlParams
'
,
()
=>
{
it
(
'
returns url params given filtered tokens
'
,
()
=>
{
expect
(
convertTo
UrlParams
(
filteredTokens
)).
toEqual
({
expect
(
convertTo
Params
(
filteredTokens
,
URL_PARAM
)).
toEqual
({
...
urlParams
,
epic_id
:
'
gitlab-org::&12
'
,
});
});
it
(
'
returns url params given filtered tokens with special values
'
,
()
=>
{
expect
(
convertToUrlParams
(
filteredTokensWithSpecialValues
)).
toEqual
(
urlParamsWithSpecialValues
);
expect
(
convertToParams
(
filteredTokensWithSpecialValues
,
URL_PARAM
)).
toEqual
(
urlParamsWithSpecialValues
,
);
});
});
...
...
spec/helpers/issues_helper_spec.rb
View file @
b93b446b
...
...
@@ -302,6 +302,7 @@ RSpec.describe IssuesHelper do
email:
current_user
&
.
notification_email
,
emails_help_page_path:
help_page_path
(
'development/emails'
,
anchor:
'email-namespace'
),
empty_state_svg_path:
'#'
,
endpoint:
expose_path
(
api_v4_projects_issues_path
(
id:
project
.
id
)),
export_csv_path:
export_csv_project_issues_path
(
project
),
has_project_issues:
project_issues
(
project
).
exists?
.
to_s
,
import_csv_issues_path:
'#'
,
...
...
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