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
89194380
Commit
89194380
authored
Mar 31, 2020
by
Tristan Read
Committed by
Natalia Tepluhina
Mar 31, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow embedded metrics charts to be hidden
Adds show and hide options to metric chart embeds
parent
d8812abf
Changes
27
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
726 additions
and
50 deletions
+726
-50
app/assets/javascripts/behaviors/markdown/render_metrics.js
app/assets/javascripts/behaviors/markdown/render_metrics.js
+27
-7
app/assets/javascripts/monitoring/components/embeds/embed_group.vue
.../javascripts/monitoring/components/embeds/embed_group.vue
+101
-0
app/assets/javascripts/monitoring/components/embeds/metric_embed.vue
...javascripts/monitoring/components/embeds/metric_embed.vue
+131
-0
app/assets/javascripts/monitoring/components/panel_type.vue
app/assets/javascripts/monitoring/components/panel_type.vue
+21
-1
app/assets/javascripts/monitoring/stores/embed_group/actions.js
...sets/javascripts/monitoring/stores/embed_group/actions.js
+5
-0
app/assets/javascripts/monitoring/stores/embed_group/getters.js
...sets/javascripts/monitoring/stores/embed_group/getters.js
+4
-0
app/assets/javascripts/monitoring/stores/embed_group/index.js
...assets/javascripts/monitoring/stores/embed_group/index.js
+24
-0
app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js
...vascripts/monitoring/stores/embed_group/mutation_types.js
+3
-0
app/assets/javascripts/monitoring/stores/embed_group/mutations.js
...ts/javascripts/monitoring/stores/embed_group/mutations.js
+7
-0
app/assets/javascripts/monitoring/stores/embed_group/state.js
...assets/javascripts/monitoring/stores/embed_group/state.js
+3
-0
app/assets/javascripts/monitoring/stores/index.js
app/assets/javascripts/monitoring/stores/index.js
+9
-7
app/assets/stylesheets/components/collapsible_card.scss
app/assets/stylesheets/components/collapsible_card.scss
+9
-0
changelogs/unreleased/tr-remove-unfurled-chart.yml
changelogs/unreleased/tr-remove-unfurled-chart.yml
+5
-0
doc/user/project/integrations/img/embed_metrics.png
doc/user/project/integrations/img/embed_metrics.png
+0
-0
doc/user/project/integrations/img/hide_embedded_metrics_v12_10.png
...project/integrations/img/hide_embedded_metrics_v12_10.png
+0
-0
doc/user/project/integrations/img/view_embedded_metrics_v12_10.png
...project/integrations/img/view_embedded_metrics_v12_10.png
+0
-0
doc/user/project/integrations/prometheus.md
doc/user/project/integrations/prometheus.md
+5
-1
locale/gitlab.pot
locale/gitlab.pot
+10
-0
spec/frontend/behaviors/markdown/render_metrics_spec.js
spec/frontend/behaviors/markdown/render_metrics_spec.js
+33
-21
spec/frontend/monitoring/components/embeds/embed_group_spec.js
...frontend/monitoring/components/embeds/embed_group_spec.js
+163
-0
spec/frontend/monitoring/components/embeds/metric_embed_spec.js
...rontend/monitoring/components/embeds/metric_embed_spec.js
+3
-3
spec/frontend/monitoring/components/embeds/mock_data.js
spec/frontend/monitoring/components/embeds/mock_data.js
+33
-0
spec/frontend/monitoring/components/panel_type_spec.js
spec/frontend/monitoring/components/panel_type_spec.js
+62
-10
spec/frontend/monitoring/mock_data.js
spec/frontend/monitoring/mock_data.js
+17
-0
spec/frontend/monitoring/store/embed_group/actions_spec.js
spec/frontend/monitoring/store/embed_group/actions_spec.js
+16
-0
spec/frontend/monitoring/store/embed_group/getters_spec.js
spec/frontend/monitoring/store/embed_group/getters_spec.js
+19
-0
spec/frontend/monitoring/store/embed_group/mutations_spec.js
spec/frontend/monitoring/store/embed_group/mutations_spec.js
+16
-0
No files found.
app/assets/javascripts/behaviors/markdown/render_metrics.js
View file @
89194380
import
Vue
from
'
vue
'
;
import
Metrics
from
'
~/monitoring/components/embed
.vue
'
;
import
{
createStore
}
from
'
~/monitoring/stores
'
;
import
EmbedGroup
from
'
~/monitoring/components/embeds/embed_group
.vue
'
;
import
{
createStore
}
from
'
~/monitoring/stores
/embed_group/
'
;
// TODO: Handle copy-pasting - https://gitlab.com/gitlab-org/gitlab-foss/issues/64369.
export
default
function
renderMetrics
(
elements
)
{
...
...
@@ -8,16 +8,36 @@ export default function renderMetrics(elements) {
return
;
}
const
EmbedGroupComponent
=
Vue
.
extend
(
EmbedGroup
);
const
wrapperList
=
[];
elements
.
forEach
(
element
=>
{
const
{
dashboardUrl
}
=
element
.
dataset
;
const
MetricsComponent
=
Vue
.
extend
(
Metrics
);
let
wrapper
;
const
{
previousElementSibling
}
=
element
;
const
isFirstElementInGroup
=
!
previousElementSibling
?.
urls
;
if
(
isFirstElementInGroup
)
{
wrapper
=
document
.
createElement
(
'
div
'
);
wrapper
.
urls
=
[
element
.
dataset
.
dashboardUrl
];
element
.
parentNode
.
insertBefore
(
wrapper
,
element
);
wrapperList
.
push
(
wrapper
);
}
else
{
wrapper
=
previousElementSibling
;
wrapper
.
urls
.
push
(
element
.
dataset
.
dashboardUrl
);
}
// Clean up processed element
element
.
parentNode
.
removeChild
(
element
);
});
wrapperList
.
forEach
(
wrapper
=>
{
// eslint-disable-next-line no-new
new
Metrics
Component
({
el
:
element
,
new
EmbedGroup
Component
({
el
:
wrapper
,
store
:
createStore
(),
propsData
:
{
dashboardUrl
,
urls
:
wrapper
.
urls
,
},
});
});
...
...
app/assets/javascripts/monitoring/components/embeds/embed_group.vue
0 → 100644
View file @
89194380
<
script
>
import
{
mapState
,
mapActions
,
mapGetters
}
from
'
vuex
'
;
import
sum
from
'
lodash/sum
'
;
import
{
GlButton
,
GlCard
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
n__
}
from
'
~/locale
'
;
import
{
monitoringDashboard
}
from
'
~/monitoring/stores
'
;
import
MetricEmbed
from
'
./metric_embed.vue
'
;
export
default
{
components
:
{
GlButton
,
GlCard
,
GlIcon
,
MetricEmbed
,
},
props
:
{
urls
:
{
type
:
Array
,
required
:
true
,
validator
:
urls
=>
urls
.
length
>
0
,
},
},
data
()
{
return
{
isCollapsed
:
false
,
};
},
computed
:
{
...
mapState
(
'
embedGroup
'
,
[
'
module
'
]),
...
mapGetters
(
'
embedGroup
'
,
[
'
metricsWithData
'
]),
arrowIconName
()
{
return
this
.
isCollapsed
?
'
chevron-right
'
:
'
chevron-down
'
;
},
bodyClass
()
{
return
[
'
border-top
'
,
'
pl-3
'
,
'
pt-3
'
,
{
'
d-none
'
:
this
.
isCollapsed
}];
},
buttonLabel
()
{
return
this
.
isCollapsed
?
n__
(
'
View chart
'
,
'
View charts
'
,
this
.
numCharts
)
:
n__
(
'
Hide chart
'
,
'
Hide charts
'
,
this
.
numCharts
);
},
containerClass
()
{
return
this
.
isSingleChart
?
'
col-lg-12
'
:
'
col-lg-6
'
;
},
numCharts
()
{
if
(
this
.
metricsWithData
===
null
)
{
return
0
;
}
return
sum
(
this
.
metricsWithData
);
},
isSingleChart
()
{
return
this
.
numCharts
===
1
;
},
},
created
()
{
this
.
urls
.
forEach
((
url
,
index
)
=>
{
const
name
=
this
.
getNamespace
(
index
);
this
.
$store
.
registerModule
(
name
,
monitoringDashboard
);
this
.
addModule
(
name
);
});
},
methods
:
{
...
mapActions
(
'
embedGroup
'
,
[
'
addModule
'
]),
getNamespace
(
id
)
{
return
`monitoringDashboard/
${
id
}
`
;
},
toggleCollapsed
()
{
this
.
isCollapsed
=
!
this
.
isCollapsed
;
},
},
};
</
script
>
<
template
>
<gl-card
v-show=
"numCharts > 0"
class=
"collapsible-card border p-0 mb-3"
header-class=
"d-flex align-items-center border-bottom-0 py-2"
:body-class=
"bodyClass"
>
<template
#header
>
<gl-button
class=
"collapsible-card-btn d-flex text-decoration-none"
:aria-label=
"buttonLabel"
variant=
"link"
@
click=
"toggleCollapsed"
>
<gl-icon
class=
"mr-1"
:name=
"arrowIconName"
/>
{{
buttonLabel
}}
</gl-button>
</
template
>
<div
class=
"d-flex flex-wrap"
>
<metric-embed
v-for=
"(url, index) in urls"
:key=
"`${index}/${url}`"
:dashboard-url=
"url"
:namespace=
"getNamespace(index)"
:container-class=
"containerClass"
/>
</div>
</gl-card>
</template>
app/assets/javascripts/monitoring/components/embed.vue
→
app/assets/javascripts/monitoring/components/embed
s/metric_embed
.vue
View file @
89194380
<
script
>
import
{
map
Actions
,
mapState
,
mapGetter
s
}
from
'
vuex
'
;
import
{
map
State
,
mapAction
s
}
from
'
vuex
'
;
import
PanelType
from
'
ee_else_ce/monitoring/components/panel_type.vue
'
;
import
{
convertToFixedRange
}
from
'
~/lib/utils/datetime_range
'
;
import
{
timeRangeFromUrl
,
removeTimeRangeParams
}
from
'
../utils
'
;
import
{
sidebarAnimationDuration
}
from
'
../constants
'
;
import
{
defaultTimeRange
}
from
'
~/vue_shared/constants
'
;
import
{
timeRangeFromUrl
,
removeTimeRangeParams
}
from
'
../../utils
'
;
import
{
sidebarAnimationDuration
}
from
'
../../constants
'
;
let
sidebarMutationObserver
;
...
...
@@ -13,10 +13,20 @@ export default {
PanelType
,
},
props
:
{
containerClass
:
{
type
:
String
,
required
:
false
,
default
:
'
col-lg-12
'
,
},
dashboardUrl
:
{
type
:
String
,
required
:
true
,
},
namespace
:
{
type
:
String
,
required
:
false
,
default
:
'
monitoringDashboard
'
,
},
},
data
()
{
const
timeRange
=
timeRangeFromUrl
(
this
.
dashboardUrl
)
||
defaultTimeRange
;
...
...
@@ -26,21 +36,32 @@ export default {
};
},
computed
:
{
...
mapState
(
'
monitoringDashboard
'
,
[
'
dashboard
'
]),
...
mapGetters
(
'
monitoringDashboard
'
,
[
'
metricsWithData
'
]),
...
mapState
({
dashboard
(
state
)
{
return
state
[
this
.
namespace
].
dashboard
;
},
metricsWithData
(
state
,
getters
)
{
return
getters
[
`
${
this
.
namespace
}
/metricsWithData`
]();
},
}),
charts
()
{
if
(
!
this
.
dashboard
||
!
this
.
dashboard
.
panelGroups
)
{
return
[];
}
const
groupWithMetrics
=
this
.
dashboard
.
panelGroups
.
find
(
group
=>
group
.
panels
.
find
(
chart
=>
this
.
chartHasData
(
chart
)),
)
||
{
panels
:
[]
};
return
groupWithMetrics
.
panels
.
filter
(
chart
=>
this
.
chartHasData
(
chart
));
return
this
.
dashboard
.
panelGroups
.
reduce
(
(
acc
,
currentGroup
)
=>
acc
.
concat
(
currentGroup
.
panels
.
filter
(
this
.
chartHasData
)),
[],
);
},
isSingleChart
()
{
return
this
.
charts
.
length
===
1
;
},
embedClass
()
{
return
this
.
isSingleChart
?
this
.
containerClass
:
'
col-lg-12
'
;
},
panelClass
()
{
return
this
.
isSingleChart
?
'
col-lg-12
'
:
'
col-lg-6
'
;
},
},
mounted
()
{
this
.
setInitialState
();
...
...
@@ -60,15 +81,27 @@ export default {
}
},
methods
:
{
...
mapActions
(
'
monitoringDashboard
'
,
[
'
setTimeRange
'
,
'
fetchDashboard
'
,
'
setEndpoints
'
,
'
setFeatureFlags
'
,
'
setShowErrorBanner
'
,
]),
// Use function args to support dynamic namespaces in mapXXX helpers. Pattern described
// in https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
...
mapActions
({
setTimeRange
(
dispatch
,
payload
)
{
return
dispatch
(
`
${
this
.
namespace
}
/setTimeRange`
,
payload
);
},
fetchDashboard
(
dispatch
,
payload
)
{
return
dispatch
(
`
${
this
.
namespace
}
/fetchDashboard`
,
payload
);
},
setEndpoints
(
dispatch
,
payload
)
{
return
dispatch
(
`
${
this
.
namespace
}
/setEndpoints`
,
payload
);
},
setFeatureFlags
(
dispatch
,
payload
)
{
return
dispatch
(
`
${
this
.
namespace
}
/setFeatureFlags`
,
payload
);
},
setShowErrorBanner
(
dispatch
,
payload
)
{
return
dispatch
(
`
${
this
.
namespace
}
/setShowErrorBanner`
,
payload
);
},
}),
chartHasData
(
chart
)
{
return
chart
.
metrics
.
some
(
metric
=>
this
.
metricsWithData
()
.
includes
(
metric
.
metricId
));
return
chart
.
metrics
.
some
(
metric
=>
this
.
metricsWithData
.
includes
(
metric
.
metricId
));
},
onSidebarMutation
()
{
setTimeout
(()
=>
{
...
...
@@ -85,15 +118,14 @@ export default {
};
</
script
>
<
template
>
<div
class=
"metrics-embed"
:class=
"
{ 'd-inline-flex col-lg-6 p-0': isSingleChart }">
<div
v-if=
"charts.length"
class=
"row w-100 m-n2 pb-4"
>
<panel-type
v-for=
"(graphData, graphIndex) in charts"
:key=
"`panel-type-$
{graphIndex}`"
class="w-100"
:graph-data="graphData"
:group-id="dashboardUrl"
/>
</div>
<div
class=
"metrics-embed p-0 d-flex flex-wrap"
:class=
"embedClass"
>
<panel-type
v-for=
"(graphData, graphIndex) in charts"
:key=
"`panel-type-$
{graphIndex}`"
:class="panelClass"
:graph-data="graphData"
:group-id="dashboardUrl"
:namespace="namespace"
/>
</div>
</
template
>
app/assets/javascripts/monitoring/components/panel_type.vue
View file @
89194380
...
...
@@ -68,6 +68,11 @@ export default {
required
:
false
,
default
:
'
panel-type-chart
'
,
},
namespace
:
{
type
:
String
,
required
:
false
,
default
:
'
monitoringDashboard
'
,
},
},
data
()
{
return
{
...
...
@@ -76,7 +81,22 @@ export default {
};
},
computed
:
{
...
mapState
(
'
monitoringDashboard
'
,
[
'
deploymentData
'
,
'
projectPath
'
,
'
logsPath
'
,
'
timeRange
'
]),
// Use functions to support dynamic namespaces in mapXXX helpers. Pattern described
// in https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
...
mapState
({
deploymentData
(
state
)
{
return
state
[
this
.
namespace
].
deploymentData
;
},
projectPath
(
state
)
{
return
state
[
this
.
namespace
].
projectPath
;
},
logsPath
(
state
)
{
return
state
[
this
.
namespace
].
logsPath
;
},
timeRange
(
state
)
{
return
state
[
this
.
namespace
].
timeRange
;
},
}),
title
()
{
return
this
.
graphData
.
title
||
''
;
},
...
...
app/assets/javascripts/monitoring/stores/embed_group/actions.js
0 → 100644
View file @
89194380
import
*
as
types
from
'
./mutation_types
'
;
export
const
addModule
=
({
commit
},
data
)
=>
commit
(
types
.
ADD_MODULE
,
data
);
export
default
()
=>
{};
app/assets/javascripts/monitoring/stores/embed_group/getters.js
0 → 100644
View file @
89194380
export
const
metricsWithData
=
(
state
,
getters
,
rootState
,
rootGetters
)
=>
state
.
modules
.
map
(
module
=>
rootGetters
[
`
${
module
}
/metricsWithData`
]().
length
);
export
default
()
=>
{};
app/assets/javascripts/monitoring/stores/embed_group/index.js
0 → 100644
View file @
89194380
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
*
as
actions
from
'
./actions
'
;
import
*
as
getters
from
'
./getters
'
;
import
mutations
from
'
./mutations
'
;
import
state
from
'
./state
'
;
Vue
.
use
(
Vuex
);
// In practice this store will have a number of `monitoringDashboard` modules added dynamically
export
const
createStore
=
()
=>
new
Vuex
.
Store
({
modules
:
{
embedGroup
:
{
namespaced
:
true
,
actions
,
getters
,
mutations
,
state
,
},
},
});
export
default
createStore
();
app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js
0 → 100644
View file @
89194380
export
const
ADD_MODULE
=
'
ADD_MODULE
'
;
export
default
()
=>
{};
app/assets/javascripts/monitoring/stores/embed_group/mutations.js
0 → 100644
View file @
89194380
import
*
as
types
from
'
./mutation_types
'
;
export
default
{
[
types
.
ADD_MODULE
](
state
,
module
)
{
state
.
modules
.
push
(
module
);
},
};
app/assets/javascripts/monitoring/stores/embed_group/state.js
0 → 100644
View file @
89194380
export
default
()
=>
({
modules
:
[],
});
app/assets/javascripts/monitoring/stores/index.js
View file @
89194380
...
...
@@ -7,16 +7,18 @@ import state from './state';
Vue
.
use
(
Vuex
);
export
const
monitoringDashboard
=
{
namespaced
:
true
,
actions
,
getters
,
mutations
,
state
,
};
export
const
createStore
=
()
=>
new
Vuex
.
Store
({
modules
:
{
monitoringDashboard
:
{
namespaced
:
true
,
actions
,
getters
,
mutations
,
state
,
},
monitoringDashboard
,
},
});
...
...
app/assets/stylesheets/components/collapsible_card.scss
0 → 100644
View file @
89194380
.collapsible-card
{
.collapsible-card-btn
{
color
:
$gl-text-color
;
&
:hover
{
color
:
$blue-600
;
}
}
}
changelogs/unreleased/tr-remove-unfurled-chart.yml
0 → 100644
View file @
89194380
---
title
:
Allow embedded metrics charts to be hidden
merge_request
:
23929
author
:
type
:
added
doc/user/project/integrations/img/embed_metrics.png
deleted
100644 → 0
View file @
d8812abf
100 KB
doc/user/project/integrations/img/hide_embedded_metrics_v12_10.png
0 → 100644
View file @
89194380
20.8 KB
doc/user/project/integrations/img/view_embedded_metrics_v12_10.png
0 → 100644
View file @
89194380
35.9 KB
doc/user/project/integrations/prometheus.md
View file @
89194380
...
...
@@ -777,7 +777,11 @@ The following requirements must be met for the metric to unfurl:
If all of the above are true, then the metric will unfurl as seen below:
![
Embedded Metrics
](
img/embed_metrics.png
)
![
Embedded Metrics
](
img/view_embedded_metrics_v12_10.png
)
Metric charts may also be hidden:
![
Show Hide
](
img/hide_embedded_metrics_v12_10.png
)
### Embedding metrics in issue templates
...
...
locale/gitlab.pot
View file @
89194380
...
...
@@ -10488,6 +10488,11 @@ msgstr ""
msgid "Hide archived projects"
msgstr ""
msgid "Hide chart"
msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
msgid "Hide file browser"
msgstr ""
...
...
@@ -22340,6 +22345,11 @@ msgstr ""
msgid "View blame prior to this change"
msgstr ""
msgid "View chart"
msgid_plural "View charts"
msgstr[0] ""
msgstr[1] ""
msgid "View dependency details for your project"
msgstr ""
...
...
spec/frontend/behaviors/markdown/render_metrics_spec.js
View file @
89194380
import
Vue
from
'
vue
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
renderMetrics
from
'
~/behaviors/markdown/render_metrics
'
;
const
originalExtend
=
Vue
.
extend
;
const
mockEmbedGroup
=
jest
.
fn
()
;
describe
(
'
Render metrics for Gitlab Flavoured Markdown
'
,
()
=>
{
const
container
=
{
Metrics
()
{},
};
let
spyExtend
;
beforeEach
(()
=>
{
Vue
.
extend
=
()
=>
container
.
Metrics
;
spyExtend
=
jest
.
spyOn
(
Vue
,
'
extend
'
);
});
jest
.
mock
(
'
vue
'
,
()
=>
({
extend
:
()
=>
mockEmbedGroup
}));
jest
.
mock
(
'
~/monitoring/components/embeds/embed_group.vue
'
,
()
=>
jest
.
fn
());
jest
.
mock
(
'
~/monitoring/stores/embed_group/
'
,
()
=>
({
createStore
:
jest
.
fn
()
}));
afterEach
(()
=>
{
Vue
.
extend
=
originalExtend
;
});
const
getElements
=
()
=>
Array
.
from
(
document
.
getElementsByClassName
(
'
js-render-metrics
'
));
describe
(
'
Render metrics for Gitlab Flavoured Markdown
'
,
()
=>
{
it
(
'
does nothing when no elements are found
'
,
()
=>
{
renderMetrics
([]);
expect
(
spyExtend
).
not
.
toHaveBeenCalled
();
expect
(
mockEmbedGroup
).
not
.
toHaveBeenCalled
();
});
it
(
'
renders a vue component when elements are found
'
,
()
=>
{
const
element
=
document
.
createElement
(
'
div
'
);
element
.
setAttribute
(
'
data-dashboard-url
'
,
TEST_HOST
);
document
.
body
.
innerHTML
=
`<div class="js-render-metrics" data-dashboard-url="
${
TEST_HOST
}
"></div>`
;
renderMetrics
([
element
]);
renderMetrics
(
getElements
());
expect
(
mockEmbedGroup
).
toHaveBeenCalledTimes
(
1
);
expect
(
mockEmbedGroup
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
propsData
:
{
urls
:
[
`
${
TEST_HOST
}
`
]
}
}),
);
});
expect
(
spyExtend
).
toHaveBeenCalled
();
it
(
'
takes sibling metrics and groups them under a shared parent
'
,
()
=>
{
document
.
body
.
innerHTML
=
`
<p><span>Hello</span></p>
<div class="js-render-metrics" data-dashboard-url="
${
TEST_HOST
}
/1"></div>
<div class="js-render-metrics" data-dashboard-url="
${
TEST_HOST
}
/2"></div>
<p><span>Hello</span></p>
<div class="js-render-metrics" data-dashboard-url="
${
TEST_HOST
}
/3"></div>
`
;
renderMetrics
(
getElements
());
expect
(
mockEmbedGroup
).
toHaveBeenCalledTimes
(
2
);
expect
(
mockEmbedGroup
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
propsData
:
{
urls
:
[
`
${
TEST_HOST
}
/1`
,
`
${
TEST_HOST
}
/2`
]
}
}),
);
expect
(
mockEmbedGroup
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
propsData
:
{
urls
:
[
`
${
TEST_HOST
}
/3`
]
}
}),
);
});
});
spec/frontend/monitoring/components/embeds/embed_group_spec.js
0 → 100644
View file @
89194380
import
{
createLocalVue
,
mount
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
{
GlButton
,
GlCard
}
from
'
@gitlab/ui
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
EmbedGroup
from
'
~/monitoring/components/embeds/embed_group.vue
'
;
import
MetricEmbed
from
'
~/monitoring/components/embeds/metric_embed.vue
'
;
import
{
addModuleAction
,
initialEmbedGroupState
,
singleEmbedProps
,
dashboardEmbedProps
,
multipleEmbedProps
,
}
from
'
./mock_data
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
Embed Group
'
,
()
=>
{
let
wrapper
;
let
store
;
const
metricsWithDataGetter
=
jest
.
fn
();
function
mountComponent
({
urls
=
[
TEST_HOST
],
shallow
=
true
,
stubs
}
=
{})
{
const
mountMethod
=
shallow
?
shallowMount
:
mount
;
wrapper
=
mountMethod
(
EmbedGroup
,
{
localVue
,
store
,
propsData
:
{
urls
,
},
stubs
,
});
}
beforeEach
(()
=>
{
store
=
new
Vuex
.
Store
({
modules
:
{
embedGroup
:
{
namespaced
:
true
,
actions
:
{
addModule
:
jest
.
fn
()
},
getters
:
{
metricsWithData
:
metricsWithDataGetter
},
state
:
initialEmbedGroupState
,
},
},
});
store
.
registerModule
=
jest
.
fn
();
jest
.
spyOn
(
store
,
'
dispatch
'
);
});
afterEach
(()
=>
{
metricsWithDataGetter
.
mockReset
();
if
(
wrapper
)
{
wrapper
.
destroy
();
}
});
describe
(
'
interactivity
'
,
()
=>
{
it
(
'
hides the component when no chart data is loaded
'
,
()
=>
{
metricsWithDataGetter
.
mockReturnValue
([]);
mountComponent
();
expect
(
wrapper
.
find
(
GlCard
).
isVisible
()).
toBe
(
false
);
});
it
(
'
shows the component when chart data is loaded
'
,
()
=>
{
metricsWithDataGetter
.
mockReturnValue
([
1
]);
mountComponent
();
expect
(
wrapper
.
find
(
GlCard
).
isVisible
()).
toBe
(
true
);
});
it
(
'
is expanded by default
'
,
()
=>
{
metricsWithDataGetter
.
mockReturnValue
([
1
]);
mountComponent
({
shallow
:
false
,
stubs
:
{
MetricEmbed
:
'
<div />
'
}
});
expect
(
wrapper
.
find
(
'
.card-body
'
).
classes
()).
not
.
toContain
(
'
d-none
'
);
});
it
(
'
collapses when clicked
'
,
done
=>
{
metricsWithDataGetter
.
mockReturnValue
([
1
]);
mountComponent
({
shallow
:
false
,
stubs
:
{
MetricEmbed
:
'
<div />
'
}
});
wrapper
.
find
(
GlButton
).
trigger
(
'
click
'
);
wrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
wrapper
.
find
(
'
.card-body
'
).
classes
()).
toContain
(
'
d-none
'
);
done
();
});
});
});
describe
(
'
single metrics
'
,
()
=>
{
beforeEach
(()
=>
{
metricsWithDataGetter
.
mockReturnValue
([
1
]);
mountComponent
();
});
it
(
'
renders an Embed component
'
,
()
=>
{
expect
(
wrapper
.
find
(
MetricEmbed
).
exists
()).
toBe
(
true
);
});
it
(
'
passes the correct props to the Embed component
'
,
()
=>
{
expect
(
wrapper
.
find
(
MetricEmbed
).
props
()).
toEqual
(
singleEmbedProps
());
});
it
(
'
adds the monitoring dashboard module
'
,
()
=>
{
expect
(
store
.
dispatch
).
toHaveBeenCalledWith
(
addModuleAction
,
'
monitoringDashboard/0
'
);
});
});
describe
(
'
dashboard metrics
'
,
()
=>
{
beforeEach
(()
=>
{
metricsWithDataGetter
.
mockReturnValue
([
2
]);
mountComponent
();
});
it
(
'
passes the correct props to the dashboard Embed component
'
,
()
=>
{
expect
(
wrapper
.
find
(
MetricEmbed
).
props
()).
toEqual
(
dashboardEmbedProps
());
});
it
(
'
adds the monitoring dashboard module
'
,
()
=>
{
expect
(
store
.
dispatch
).
toHaveBeenCalledWith
(
addModuleAction
,
'
monitoringDashboard/0
'
);
});
});
describe
(
'
multiple metrics
'
,
()
=>
{
beforeEach
(()
=>
{
metricsWithDataGetter
.
mockReturnValue
([
1
,
1
]);
mountComponent
({
urls
:
[
TEST_HOST
,
TEST_HOST
]
});
});
it
(
'
creates Embed components
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
MetricEmbed
)).
toHaveLength
(
2
);
});
it
(
'
passes the correct props to the Embed components
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
MetricEmbed
).
wrappers
.
map
(
item
=>
item
.
props
())).
toEqual
(
multipleEmbedProps
(),
);
});
it
(
'
adds multiple monitoring dashboard modules
'
,
()
=>
{
expect
(
store
.
dispatch
).
toHaveBeenCalledWith
(
addModuleAction
,
'
monitoringDashboard/0
'
);
expect
(
store
.
dispatch
).
toHaveBeenCalledWith
(
addModuleAction
,
'
monitoringDashboard/1
'
);
});
});
describe
(
'
button text
'
,
()
=>
{
it
(
'
has a singular label when there is one embed
'
,
()
=>
{
metricsWithDataGetter
.
mockReturnValue
([
1
]);
mountComponent
({
shallow
:
false
,
stubs
:
{
MetricEmbed
:
'
<div />
'
}
});
expect
(
wrapper
.
find
(
GlButton
).
text
()).
toBe
(
'
Hide chart
'
);
});
it
(
'
has a plural label when there are multiple embeds
'
,
()
=>
{
metricsWithDataGetter
.
mockReturnValue
([
2
]);
mountComponent
({
shallow
:
false
,
stubs
:
{
MetricEmbed
:
'
<div />
'
}
});
expect
(
wrapper
.
find
(
GlButton
).
text
()).
toBe
(
'
Hide charts
'
);
});
});
});
spec/frontend/monitoring/
embed/
embed_spec.js
→
spec/frontend/monitoring/
components/embeds/metric_
embed_spec.js
View file @
89194380
...
...
@@ -2,20 +2,20 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import
Vuex
from
'
vuex
'
;
import
PanelType
from
'
ee_else_ce/monitoring/components/panel_type.vue
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
Embed
from
'
~/monitoring/components/
embed.vue
'
;
import
MetricEmbed
from
'
~/monitoring/components/embeds/metric_
embed.vue
'
;
import
{
groups
,
initialState
,
metricsData
,
metricsWithData
}
from
'
./mock_data
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
Embed
'
,
()
=>
{
describe
(
'
Metric
Embed
'
,
()
=>
{
let
wrapper
;
let
store
;
let
actions
;
let
metricsWithDataGetter
;
function
mountComponent
()
{
wrapper
=
shallowMount
(
Embed
,
{
wrapper
=
shallowMount
(
Metric
Embed
,
{
localVue
,
store
,
propsData
:
{
...
...
spec/frontend/monitoring/
embed
/mock_data.js
→
spec/frontend/monitoring/
components/embeds
/mock_data.js
View file @
89194380
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
export
const
metricsWithData
=
[
'
15_metric_a
'
,
'
16_metric_b
'
];
export
const
groups
=
[
...
...
@@ -52,3 +54,34 @@ export const initialState = () => ({
},
useDashboardEndpoint
:
true
,
});
export
const
initialEmbedGroupState
=
()
=>
({
modules
:
[],
});
export
const
singleEmbedProps
=
()
=>
({
dashboardUrl
:
TEST_HOST
,
containerClass
:
'
col-lg-12
'
,
namespace
:
'
monitoringDashboard/0
'
,
});
export
const
dashboardEmbedProps
=
()
=>
({
dashboardUrl
:
TEST_HOST
,
containerClass
:
'
col-lg-6
'
,
namespace
:
'
monitoringDashboard/0
'
,
});
export
const
multipleEmbedProps
=
()
=>
[
{
dashboardUrl
:
TEST_HOST
,
containerClass
:
'
col-lg-6
'
,
namespace
:
'
monitoringDashboard/0
'
,
},
{
dashboardUrl
:
TEST_HOST
,
containerClass
:
'
col-lg-6
'
,
namespace
:
'
monitoringDashboard/1
'
,
},
];
export
const
addModuleAction
=
'
embedGroup/addModule
'
;
spec/frontend/monitoring/components/panel_type_spec.js
View file @
89194380
...
...
@@ -8,8 +8,17 @@ import PanelType from '~/monitoring/components/panel_type.vue';
import
EmptyChart
from
'
~/monitoring/components/charts/empty_chart.vue
'
;
import
TimeSeriesChart
from
'
~/monitoring/components/charts/time_series.vue
'
;
import
AnomalyChart
from
'
~/monitoring/components/charts/anomaly.vue
'
;
import
{
anomalyMockGraphData
,
graphDataPrometheusQueryRange
}
from
'
jest/monitoring/mock_data
'
;
import
{
createStore
}
from
'
~/monitoring/stores
'
;
import
{
anomalyMockGraphData
,
graphDataPrometheusQueryRange
,
mockLogsHref
,
mockLogsPath
,
mockNamespace
,
mockNamespacedData
,
mockTimeRange
,
}
from
'
jest/monitoring/mock_data
'
;
import
{
createStore
,
monitoringDashboard
}
from
'
~/monitoring/stores
'
;
import
{
createStore
as
createEmbedGroupStore
}
from
'
~/monitoring/stores/embed_group
'
;
global
.
IS_EE
=
true
;
global
.
URL
.
createObjectURL
=
jest
.
fn
();
...
...
@@ -29,6 +38,7 @@ describe('Panel Type component', () => {
const
exampleText
=
'
example_text
'
;
const
findCopyLink
=
()
=>
wrapper
.
find
({
ref
:
'
copyChartLink
'
});
const
findTimeChart
=
()
=>
wrapper
.
find
({
ref
:
'
timeChart
'
});
const
createWrapper
=
props
=>
{
wrapper
=
shallowMount
(
PanelType
,
{
...
...
@@ -99,8 +109,6 @@ describe('Panel Type component', () => {
});
describe
(
'
when graph data is available
'
,
()
=>
{
const
findTimeChart
=
()
=>
wrapper
.
find
({
ref
:
'
timeChart
'
});
beforeEach
(()
=>
{
createWrapper
({
graphData
:
graphDataPrometheusQueryRange
,
...
...
@@ -242,10 +250,6 @@ describe('Panel Type component', () => {
});
describe
(
'
View Logs dropdown item
'
,
()
=>
{
const
mockLogsPath
=
'
/path/to/logs
'
;
const
mockTimeRange
=
{
duration
:
{
seconds
:
120
}
};
const
findTimeChart
=
()
=>
wrapper
.
find
({
ref
:
'
timeChart
'
});
const
findViewLogsLink
=
()
=>
wrapper
.
find
({
ref
:
'
viewLogsLink
'
});
beforeEach
(()
=>
{
...
...
@@ -292,8 +296,7 @@ describe('Panel Type component', () => {
state
.
timeRange
=
mockTimeRange
;
return
wrapper
.
vm
.
$nextTick
(()
=>
{
const
href
=
`
${
mockLogsPath
}
?duration_seconds=
${
mockTimeRange
.
duration
.
seconds
}
`
;
expect
(
findViewLogsLink
().
attributes
(
'
href
'
)).
toMatch
(
href
);
expect
(
findViewLogsLink
().
attributes
(
'
href
'
)).
toMatch
(
mockLogsHref
);
});
});
...
...
@@ -388,4 +391,53 @@ describe('Panel Type component', () => {
});
});
});
describe
(
'
when using dynamic modules
'
,
()
=>
{
const
{
mockDeploymentData
,
mockProjectPath
}
=
mockNamespacedData
;
beforeEach
(()
=>
{
store
=
createEmbedGroupStore
();
store
.
registerModule
(
mockNamespace
,
monitoringDashboard
);
store
.
state
.
embedGroup
.
modules
.
push
(
mockNamespace
);
wrapper
=
shallowMount
(
PanelType
,
{
propsData
:
{
graphData
:
graphDataPrometheusQueryRange
,
namespace
:
mockNamespace
,
},
store
,
mocks
,
});
});
it
(
'
handles namespaced time range and logs path state
'
,
()
=>
{
store
.
state
[
mockNamespace
].
timeRange
=
mockTimeRange
;
store
.
state
[
mockNamespace
].
logsPath
=
mockLogsPath
;
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
wrapper
.
find
({
ref
:
'
viewLogsLink
'
}).
attributes
().
href
).
toBe
(
mockLogsHref
);
});
});
it
(
'
handles namespaced deployment data state
'
,
()
=>
{
store
.
state
[
mockNamespace
].
deploymentData
=
mockDeploymentData
;
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findTimeChart
().
props
().
deploymentData
).
toEqual
(
mockDeploymentData
);
});
});
it
(
'
handles namespaced project path state
'
,
()
=>
{
store
.
state
[
mockNamespace
].
projectPath
=
mockProjectPath
;
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findTimeChart
().
props
().
projectPath
).
toBe
(
mockProjectPath
);
});
});
it
(
'
it renders a time series chart with no errors
'
,
()
=>
{
expect
(
wrapper
.
find
(
TimeSeriesChart
).
isVueInstance
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
TimeSeriesChart
).
exists
()).
toBe
(
true
);
});
});
});
spec/frontend/monitoring/mock_data.js
View file @
89194380
...
...
@@ -750,3 +750,20 @@ export const barMockData = {
},
],
};
export
const
baseNamespace
=
'
monitoringDashboard
'
;
export
const
mockNamespace
=
`
${
baseNamespace
}
/1`
;
export
const
mockNamespaces
=
[
`
${
baseNamespace
}
/1`
,
`
${
baseNamespace
}
/2`
];
export
const
mockTimeRange
=
{
duration
:
{
seconds
:
120
}
};
export
const
mockNamespacedData
=
{
mockDeploymentData
:
[
'
mockDeploymentData
'
],
mockProjectPath
:
'
/mockProjectPath
'
,
};
export
const
mockLogsPath
=
'
/mockLogsPath
'
;
export
const
mockLogsHref
=
`
${
mockLogsPath
}
?duration_seconds=
${
mockTimeRange
.
duration
.
seconds
}
`
;
spec/frontend/monitoring/store/embed_group/actions_spec.js
0 → 100644
View file @
89194380
// import store from '~/monitoring/stores/embed_group';
import
*
as
actions
from
'
~/monitoring/stores/embed_group/actions
'
;
import
*
as
types
from
'
~/monitoring/stores/embed_group/mutation_types
'
;
import
{
mockNamespace
}
from
'
../../mock_data
'
;
describe
(
'
Embed group actions
'
,
()
=>
{
describe
(
'
addModule
'
,
()
=>
{
it
(
'
adds a module to the store
'
,
()
=>
{
const
commit
=
jest
.
fn
();
actions
.
addModule
({
commit
},
mockNamespace
);
expect
(
commit
).
toHaveBeenCalledWith
(
types
.
ADD_MODULE
,
mockNamespace
);
});
});
});
spec/frontend/monitoring/store/embed_group/getters_spec.js
0 → 100644
View file @
89194380
import
{
metricsWithData
}
from
'
~/monitoring/stores/embed_group/getters
'
;
import
{
mockNamespaces
}
from
'
../../mock_data
'
;
describe
(
'
Embed group getters
'
,
()
=>
{
describe
(
'
metricsWithData
'
,
()
=>
{
it
(
'
correctly sums the number of metrics with data
'
,
()
=>
{
const
mockMetric
=
{};
const
state
=
{
modules
:
mockNamespaces
,
};
const
rootGetters
=
{
[
`
${
mockNamespaces
[
0
]}
/metricsWithData`
]:
()
=>
[
mockMetric
],
[
`
${
mockNamespaces
[
1
]}
/metricsWithData`
]:
()
=>
[
mockMetric
,
mockMetric
],
};
expect
(
metricsWithData
(
state
,
null
,
null
,
rootGetters
)).
toEqual
([
1
,
2
]);
});
});
});
spec/frontend/monitoring/store/embed_group/mutations_spec.js
0 → 100644
View file @
89194380
import
state
from
'
~/monitoring/stores/embed_group/state
'
;
import
mutations
from
'
~/monitoring/stores/embed_group/mutations
'
;
import
*
as
types
from
'
~/monitoring/stores/embed_group/mutation_types
'
;
import
{
mockNamespace
}
from
'
../../mock_data
'
;
describe
(
'
Embed group mutations
'
,
()
=>
{
describe
(
'
ADD_MODULE
'
,
()
=>
{
it
(
'
should add a module
'
,
()
=>
{
const
stateCopy
=
state
();
mutations
[
types
.
ADD_MODULE
](
stateCopy
,
mockNamespace
);
expect
(
stateCopy
.
modules
).
toEqual
([
mockNamespace
]);
});
});
});
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