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
3b9fc200
Commit
3b9fc200
authored
Apr 28, 2020
by
Martin Wortschack
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add milestone token component
- Use milestone token in code review analytics filtered search
parent
911c4841
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
293 additions
and
14 deletions
+293
-14
ee/app/assets/javascripts/analytics/code_review_analytics/components/filter_bar.vue
...analytics/code_review_analytics/components/filter_bar.vue
+24
-12
ee/app/assets/javascripts/analytics/shared/components/tokens/milestone_token.vue
...ts/analytics/shared/components/tokens/milestone_token.vue
+99
-0
ee/spec/frontend/analytics/code_review_analytics/components/filter_bar_spec.js
...ytics/code_review_analytics/components/filter_bar_spec.js
+47
-2
ee/spec/frontend/analytics/shared/components/tokens/milestone_token_spec.js
...nalytics/shared/components/tokens/milestone_token_spec.js
+93
-0
ee/spec/frontend/analytics/shared/components/tokens/mock_data.js
.../frontend/analytics/shared/components/tokens/mock_data.js
+30
-0
No files found.
ee/app/assets/javascripts/analytics/code_review_analytics/components/filter_bar.vue
View file @
3b9fc200
<
script
>
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
{
GlFilteredSearch
,
GlFilteredSearchToken
}
from
'
@gitlab/ui
'
;
import
{
GlFilteredSearch
}
from
'
@gitlab/ui
'
;
import
{
__
}
from
'
~/locale
'
;
import
MilestoneToken
from
'
../../shared/components/tokens/milestone_token.vue
'
;
export
default
{
components
:
{
...
...
@@ -17,6 +18,7 @@ export default {
milestonePath
:
'
milestonePath
'
,
labelsPath
:
'
labelsPath
'
,
milestones
:
state
=>
state
.
milestones
.
data
,
milestonesLoading
:
state
=>
state
.
milestones
.
isLoading
,
}),
tokens
()
{
return
[
...
...
@@ -24,9 +26,11 @@ export default {
icon
:
'
clock
'
,
title
:
__
(
'
Milestone
'
),
type
:
'
milestone
'
,
token
:
GlFilteredSearch
Token
,
option
s
:
this
.
milestones
,
token
:
Milestone
Token
,
milestone
s
:
this
.
milestones
,
unique
:
true
,
symbol
:
'
%
'
,
isLoading
:
this
.
milestonesLoading
,
},
];
},
...
...
@@ -36,22 +40,30 @@ export default {
},
methods
:
{
...
mapActions
(
'
filters
'
,
[
'
fetchMilestones
'
,
'
setFilters
'
]),
filteredSearchSubmit
(
filters
)
{
const
result
=
filters
.
reduce
((
acc
,
item
)
=>
{
const
{
type
,
value
:
{
data
},
}
=
item
;
processFilters
(
filters
)
{
return
filters
.
reduce
((
acc
,
token
)
=>
{
const
{
type
,
value
}
=
token
;
let
tokenValue
=
value
.
data
;
// remove wrapping double quotes which were added for token values that include spaces
if
(
(
tokenValue
[
0
]
===
"
'
"
&&
tokenValue
[
tokenValue
.
length
-
1
]
===
"
'
"
)
||
(
tokenValue
[
0
]
===
'
"
'
&&
tokenValue
[
tokenValue
.
length
-
1
]
===
'
"
'
)
)
{
tokenValue
=
tokenValue
.
slice
(
1
,
-
1
);
}
if
(
!
acc
[
type
])
{
acc
[
type
]
=
[];
}
acc
[
type
].
push
(
data
);
acc
[
type
].
push
(
tokenValue
);
return
acc
;
},
{});
this
.
setFilters
({
label_name
:
result
.
label
,
milestone_title
:
result
.
milestone
});
},
filteredSearchSubmit
(
filters
)
{
const
{
label
,
milestone
}
=
this
.
processFilters
(
filters
);
this
.
setFilters
({
label_name
:
label
,
milestone_title
:
milestone
});
},
},
};
...
...
ee/app/assets/javascripts/analytics/shared/components/tokens/milestone_token.vue
0 → 100644
View file @
3b9fc200
<
script
>
import
{
GlFilteredSearchToken
,
GlFilteredSearchSuggestion
,
GlDropdownDivider
,
GlLoadingIcon
,
}
from
'
@gitlab/ui
'
;
import
{
__
}
from
'
~/locale
'
;
export
default
{
components
:
{
GlFilteredSearchToken
,
GlFilteredSearchSuggestion
,
GlDropdownDivider
,
GlLoadingIcon
,
},
inheritAttrs
:
false
,
props
:
{
config
:
{
type
:
Object
,
required
:
true
,
},
value
:
{
type
:
Object
,
required
:
true
,
},
},
computed
:
{
milestones
()
{
return
this
.
config
.
milestones
;
},
filteredMilestones
()
{
return
this
.
milestones
.
filter
(
milestone
=>
milestone
.
title
.
toLowerCase
().
indexOf
(
this
.
value
.
data
?.
toLowerCase
())
!==
-
1
,
);
},
},
methods
:
{
getEscapedText
(
text
)
{
let
escapedText
=
text
;
const
hasSpace
=
text
.
indexOf
(
'
'
)
!==
-
1
;
const
hasDoubleQuote
=
text
.
indexOf
(
'
"
'
)
!==
-
1
;
// Encapsulate value with quotes if it has spaces
// Known side effect: values's with both single and double quotes
// won't escape properly
if
(
hasSpace
)
{
if
(
hasDoubleQuote
)
{
escapedText
=
`'
${
text
}
'`
;
}
else
{
// Encapsulate singleQuotes or if it hasSpace
escapedText
=
`"
${
text
}
"`
;
}
}
return
escapedText
;
},
},
defaultSuggestions
:
[
// eslint-disable-next-line @gitlab/require-i18n-strings
{
value
:
'
None
'
,
text
:
__
(
'
None
'
)
},
// eslint-disable-next-line @gitlab/require-i18n-strings
{
value
:
'
Any
'
,
text
:
__
(
'
Any
'
)
},
// eslint-disable-next-line @gitlab/require-i18n-strings
{
value
:
'
Upcoming
'
,
text
:
__
(
'
Upcoming
'
)
},
// eslint-disable-next-line @gitlab/require-i18n-strings
{
value
:
'
Started
'
,
text
:
__
(
'
Started
'
)
},
],
};
</
script
>
<
template
>
<gl-filtered-search-token
:config=
"config"
v-bind=
"
{ ...this.$attrs }" v-on="$listeners">
<template
#view
="
{ inputValue }">
<template
v-if=
"config.symbol"
>
{{
config
.
symbol
}}
</
template
>
{{ inputValue }}
</template>
<
template
#suggestions
>
<gl-filtered-search-suggestion
v-for=
"suggestion in $options.defaultSuggestions"
:key=
"suggestion.value"
:value=
"suggestion.value"
>
{{
suggestion
.
text
}}
</gl-filtered-search-suggestion
>
<gl-dropdown-divider
v-if=
"config.isLoading || filteredMilestones.length"
/>
<gl-loading-icon
v-if=
"config.isLoading"
/>
<template
v-else
>
<gl-filtered-search-suggestion
v-for=
"milestone in filteredMilestones"
ref=
"milestoneItem"
:key=
"milestone.id"
:value=
"getEscapedText(milestone.title)"
>
<div>
{{
milestone
.
title
}}
</div>
</gl-filtered-search-suggestion>
</
template
>
</template>
</gl-filtered-search-token>
</template>
ee/spec/frontend/analytics/code_review_analytics/components/filter_bar_spec.js
View file @
3b9fc200
...
...
@@ -73,9 +73,9 @@ describe('FilteredSearchBar', () => {
});
it
(
'
displays options in the milestone token
'
,
()
=>
{
const
{
options
}
=
getSearchToken
(
milestoneTokenType
);
const
{
milestones
:
milestoneToken
}
=
getSearchToken
(
milestoneTokenType
);
expect
(
options
).
toHaveLength
(
mockMilestones
.
length
);
expect
(
milestoneToken
).
toHaveLength
(
mockMilestones
.
length
);
});
});
...
...
@@ -99,5 +99,50 @@ describe('FilteredSearchBar', () => {
undefined
,
);
});
it
(
'
removes wrapping double quotes from the data and dispatches setFilters
'
,
()
=>
{
findFilteredSearch
().
vm
.
$emit
(
'
submit
'
,
[
{
type
:
'
milestone
'
,
value
:
{
data
:
'
"milestone with spaces"
'
,
operator
:
'
=
'
}
},
]);
expect
(
setFiltersMock
).
toHaveBeenCalledWith
(
expect
.
anything
(),
{
label_name
:
undefined
,
milestone_title
:
[
'
milestone with spaces
'
],
},
undefined
,
);
});
it
(
'
removes wrapping single quotes from the data and dispatches setFilters
'
,
()
=>
{
findFilteredSearch
().
vm
.
$emit
(
'
submit
'
,
[
{
type
:
'
milestone
'
,
value
:
{
data
:
"
'milestone with spaces'
"
,
operator
:
'
=
'
}
},
]);
expect
(
setFiltersMock
).
toHaveBeenCalledWith
(
expect
.
anything
(),
{
label_name
:
undefined
,
milestone_title
:
[
'
milestone with spaces
'
],
},
undefined
,
);
});
it
(
'
does not remove inner double quotes from the data and dispatches setFilters
'
,
()
=>
{
findFilteredSearch
().
vm
.
$emit
(
'
submit
'
,
[
{
type
:
'
milestone
'
,
value
:
{
data
:
'
milestone "with" spaces
'
,
operator
:
'
=
'
}
},
]);
expect
(
setFiltersMock
).
toHaveBeenCalledWith
(
expect
.
anything
(),
{
label_name
:
undefined
,
milestone_title
:
[
'
milestone "with" spaces
'
],
},
undefined
,
);
});
});
});
ee/spec/frontend/analytics/shared/components/tokens/milestone_token_spec.js
0 → 100644
View file @
3b9fc200
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlFilteredSearchSuggestion
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
MilestoneToken
from
'
ee/analytics/shared/components/tokens/milestone_token.vue
'
;
import
mockMilestones
from
'
./mock_data
'
;
describe
(
'
MilestoneToken
'
,
()
=>
{
let
wrapper
;
let
value
;
let
config
;
let
stubs
;
const
createComponent
=
(
props
=
{},
options
)
=>
{
wrapper
=
shallowMount
(
MilestoneToken
,
{
propsData
:
props
,
...
options
,
});
};
const
findFilteredSearchSuggestion
=
index
=>
wrapper
.
findAll
(
GlFilteredSearchSuggestion
).
at
(
index
);
const
findAllMilestoneSuggestions
=
()
=>
wrapper
.
findAll
({
ref
:
'
milestoneItem
'
});
const
findLoadingIcon
=
()
=>
wrapper
.
find
(
GlLoadingIcon
);
beforeEach
(()
=>
{
value
=
{
data
:
''
};
config
=
{
icon
:
'
clock
'
,
title
:
'
Milestone
'
,
type
:
'
milestone
'
,
milestones
:
mockMilestones
,
unique
:
true
,
symbol
:
'
%
'
,
isLoading
:
false
,
};
stubs
=
{
GlFilteredSearchToken
:
{
template
:
`<div><slot name="suggestions"></slot></div>`
,
},
};
});
it
(
'
renders a loading icon
'
,
()
=>
{
config
.
isLoading
=
true
;
createComponent
({
config
,
value
:
{}
},
{
stubs
});
expect
(
findLoadingIcon
().
exists
()).
toBe
(
true
);
});
describe
(
'
suggestions
'
,
()
=>
{
describe
(
'
default suggestions
'
,
()
=>
{
it
.
each
`
text | dropdownIndex
${
'
None
'
}
|
${
0
}
${
'
Any
'
}
|
${
1
}
${
'
Upcoming
'
}
|
${
2
}
${
'
Started
'
}
|
${
3
}
`
(
'
renders the "$text" suggestion
'
,
({
text
,
dropdownIndex
})
=>
{
createComponent
({
config
,
value
},
{
stubs
});
expect
(
findFilteredSearchSuggestion
(
dropdownIndex
).
text
()).
toEqual
(
text
);
});
});
it
(
"
adds wrapping quotes to the suggestion's value when the milestone title has spaces
"
,
()
=>
{
createComponent
({
config
,
value
},
{
stubs
});
const
milestoneWithSpaces
=
findAllMilestoneSuggestions
().
at
(
0
);
expect
(
milestoneWithSpaces
.
props
(
'
value
'
)).
toBe
(
'
"Sprint - Eligendi et aut pariatur ab rerum vel."
'
,
);
});
describe
(
'
when no search term is given
'
,
()
=>
{
it
(
'
renders two milestone suggestions
'
,
()
=>
{
createComponent
({
config
,
value
},
{
stubs
});
expect
(
findAllMilestoneSuggestions
()).
toHaveLength
(
2
);
});
});
describe
(
'
when the search term "v4" is given
'
,
()
=>
{
it
(
'
renders one milestone suggestion that matches the search term
'
,
()
=>
{
value
.
data
=
'
v4
'
;
createComponent
({
config
,
value
},
{
stubs
});
expect
(
findAllMilestoneSuggestions
()).
toHaveLength
(
1
);
});
});
});
});
ee/spec/frontend/analytics/shared/components/tokens/mock_data.js
0 → 100644
View file @
3b9fc200
export
default
[
{
id
:
41
,
title
:
'
Sprint - Eligendi et aut pariatur ab rerum vel.
'
,
project_id
:
1
,
description
:
'
Accusamus qui sapiente porro et in voluptates.
'
,
due_date
:
'
2020-01-14
'
,
created_at
:
'
2020-01-08T15:47:37.697Z
'
,
updated_at
:
'
2020-01-08T15:47:37.697Z
'
,
state
:
'
active
'
,
iid
:
6
,
start_date
:
'
2020-01-08
'
,
group_id
:
null
,
name
:
'
Sprint - Eligendi et aut pariatur ab rerum vel.
'
,
},
{
id
:
5
,
title
:
'
v4.0
'
,
project_id
:
1
,
description
:
'
Atque laudantium reiciendis consequatur temporibus qui qui.
'
,
due_date
:
null
,
created_at
:
'
2020-01-18T15:46:07.448Z
'
,
updated_at
:
'
2020-01-18T15:46:07.448Z
'
,
state
:
'
active
'
,
iid
:
5
,
start_date
:
null
,
group_id
:
null
,
name
:
'
v4.0
'
,
},
];
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