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
0
Merge Requests
0
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
Jérome Perrin
gitlab-ce
Commits
f6600616
Commit
f6600616
authored
May 09, 2017
by
kushalpandya
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add Prometheus memory sparkline to MR widget
parent
7ea12d80
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
579 additions
and
74 deletions
+579
-74
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
...e_merge_request_widget/components/mr_widget_deployment.js
+0
-2
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
...merge_request_widget/components/mr_widget_memory_usage.js
+60
-44
app/assets/javascripts/vue_shared/components/memory_graph.js
app/assets/javascripts/vue_shared/components/memory_graph.js
+93
-14
app/assets/stylesheets/framework/memory_graph.scss
app/assets/stylesheets/framework/memory_graph.scss
+7
-1
app/assets/stylesheets/pages/merge_requests.scss
app/assets/stylesheets/pages/merge_requests.scss
+15
-9
app/controllers/projects/merge_requests_controller.rb
app/controllers/projects/merge_requests_controller.rb
+4
-4
spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
...pts/vue_mr_widget/components/mr_widget_deployment_spec.js
+4
-0
spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
...s/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+184
-0
spec/javascripts/vue_shared/components/memory_graph_spec.js
spec/javascripts/vue_shared/components/memory_graph_spec.js
+143
-0
spec/javascripts/vue_shared/components/mock_data.js
spec/javascripts/vue_shared/components/mock_data.js
+69
-0
No files found.
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
View file @
f6600616
...
...
@@ -108,8 +108,6 @@ export default {
</div>
<mr-widget-memory-usage
v-if="deployment.metrics_url"
:mr="mr"
:service="service"
:metricsUrl="deployment.metrics_url"
/>
</div>
...
...
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
View file @
f6600616
...
...
@@ -5,8 +5,6 @@ import MRWidgetService from '../services/mr_widget_service';
export
default
{
name
:
'
MemoryUsage
'
,
props
:
{
mr
:
{
type
:
Object
,
required
:
true
},
service
:
{
type
:
Object
,
required
:
true
},
metricsUrl
:
{
type
:
String
,
required
:
true
},
},
data
()
{
...
...
@@ -14,6 +12,7 @@ export default {
// memoryFrom: 0,
// memoryTo: 0,
memoryMetrics
:
[],
deploymentTime
:
0
,
hasMetrics
:
false
,
loadFailed
:
false
,
loadingMetrics
:
true
,
...
...
@@ -23,8 +22,22 @@ export default {
components
:
{
'
mr-memory-graph
'
:
MemoryGraph
,
},
computed
:
{
shouldShowLoading
()
{
return
this
.
loadingMetrics
&&
!
this
.
hasMetrics
&&
!
this
.
loadFailed
;
},
shouldShowMemoryGraph
()
{
return
!
this
.
loadingMetrics
&&
this
.
hasMetrics
&&
!
this
.
loadFailed
;
},
shouldShowLoadFailure
()
{
return
!
this
.
loadingMetrics
&&
!
this
.
hasMetrics
&&
this
.
loadFailed
;
},
shouldShowMetricsUnavailable
()
{
return
!
this
.
loadingMetrics
&&
!
this
.
hasMetrics
&&
!
this
.
loadFailed
;
},
},
methods
:
{
computeGraphData
(
metrics
)
{
computeGraphData
(
metrics
,
deploymentTime
)
{
this
.
loadingMetrics
=
false
;
const
{
memory_values
}
=
metrics
;
// if (memory_previous.length > 0) {
...
...
@@ -38,70 +51,73 @@ export default {
if
(
memory_values
.
length
>
0
)
{
this
.
hasMetrics
=
true
;
this
.
memoryMetrics
=
memory_values
[
0
].
values
;
this
.
deploymentTime
=
deploymentTime
;
}
},
},
mounted
()
{
this
.
$props
.
loadingMetrics
=
true
;
gl
.
utils
.
backOff
((
next
,
stop
)
=>
{
MRWidgetService
.
fetchMetrics
(
this
.
$props
.
metricsUrl
)
.
then
((
res
)
=>
{
if
(
res
.
status
===
statusCodes
.
NO_CONTENT
)
{
this
.
backOffRequestCounter
=
this
.
backOffRequestCounter
+=
1
;
if
(
this
.
backOffRequestCounter
<
3
)
{
next
();
loadMetrics
()
{
gl
.
utils
.
backOff
((
next
,
stop
)
=>
{
MRWidgetService
.
fetchMetrics
(
this
.
metricsUrl
)
.
then
((
res
)
=>
{
if
(
res
.
status
===
statusCodes
.
NO_CONTENT
)
{
this
.
backOffRequestCounter
=
this
.
backOffRequestCounter
+=
1
;
/* eslint-disable no-unused-expressions */
this
.
backOffRequestCounter
<
3
?
next
()
:
stop
(
res
);
}
else
{
stop
(
res
);
}
}
else
{
stop
(
res
);
})
.
catch
(
stop
);
})
.
then
((
res
)
=>
{
if
(
res
.
status
===
statusCodes
.
NO_CONTENT
)
{
return
res
;
}
})
.
catch
(
stop
);
})
.
then
((
res
)
=>
{
if
(
res
.
status
===
statusCodes
.
NO_CONTENT
)
{
return
res
;
}
return
res
.
json
();
})
.
then
((
res
)
=>
{
this
.
computeGraphData
(
res
.
metrics
);
return
res
;
})
.
catch
(()
=>
{
this
.
$props
.
loadFailed
=
true
;
});
return
res
.
json
();
})
.
then
((
res
)
=>
{
this
.
computeGraphData
(
res
.
metrics
,
res
.
deployment_time
);
return
res
;
})
.
catch
(()
=>
{
this
.
loadFailed
=
true
;
this
.
loadingMetrics
=
false
;
});
},
},
mounted
()
{
this
.
loadingMetrics
=
true
;
this
.
loadMetrics
();
},
template
:
`
<div class="mr-info-list mr-memory-usage">
<div class="mr-info-list
clearfix mr-memory-usage js-
mr-memory-usage">
<div class="legend"></div>
<p
v-if="
loadingMetrics
"
class="usage-info usage-info-loading">
v-if="
shouldShowLoading
"
class="usage-info
js-usage-info
usage-info-loading">
<i
class="fa fa-spinner fa-spin usage-info-load-spinner"
aria-hidden="true" />Loading deployment statistics.
</p>
<p
v-if="!hasMetrics && !loadingMetrics"
class="usage-info usage-info-loading">
Deployment statistics are not available currently.
</p>
<p
v-if="hasMetrics"
class="usage-info">
v-if="shouldShowMemoryGraph"
class="usage-info js-usage-info">
Deployment memory usage:
</p>
<p
v-if="
loadFailed
"
class="usage-info">
v-if="
shouldShowLoadFailure
"
class="usage-info
js-usage-info usage-info-failed
">
Failed to load deployment statistics.
</p>
<p
v-if="shouldShowMetricsUnavailable"
class="usage-info js-usage-info usage-info-unavailable">
Deployment statistics are not available currently.
</p>
<mr-memory-graph
v-if="
hasMetrics
"
v-if="
shouldShowMemoryGraph
"
:metrics="memoryMetrics"
:deploymentTime="deploymentTime"
height="25"
width="100" />
</div>
...
...
app/assets/javascripts/vue_shared/components/memory_graph.js
View file @
f6600616
...
...
@@ -2,6 +2,7 @@ export default {
name
:
'
MemoryGraph
'
,
props
:
{
metrics
:
{
type
:
Array
,
required
:
true
},
deploymentTime
:
{
type
:
Number
,
required
:
true
},
width
:
{
type
:
String
,
required
:
true
},
height
:
{
type
:
String
,
required
:
true
},
},
...
...
@@ -9,27 +10,105 @@ export default {
return
{
pathD
:
''
,
pathViewBox
:
''
,
//
dotX: '',
//
dotY: '',
dotX
:
''
,
dotY
:
''
,
};
},
computed
:
{
getFormattedMedian
()
{
const
deployedSince
=
gl
.
utils
.
getTimeago
().
format
(
this
.
deploymentTime
*
1000
);
return
`Deployed
${
deployedSince
}
`
;
},
},
methods
:
{
/**
* Returns metric value index in metrics array
* with timestamp closest to matching median
*/
getMedianMetricIndex
(
median
,
metrics
)
{
let
matchIndex
=
0
;
let
timestampDiff
=
0
;
let
smallestDiff
=
0
;
const
metricTimestamps
=
metrics
.
map
(
v
=>
v
[
0
]);
// Find metric timestamp which is closest to deploymentTime
timestampDiff
=
Math
.
abs
(
metricTimestamps
[
0
]
-
median
);
metricTimestamps
.
forEach
((
timestamp
,
index
)
=>
{
if
(
index
===
0
)
{
// Skip first element
return
;
}
smallestDiff
=
Math
.
abs
(
timestamp
-
median
);
if
(
smallestDiff
<
timestampDiff
)
{
matchIndex
=
index
;
timestampDiff
=
smallestDiff
;
}
});
return
matchIndex
;
},
/**
* Get Graph Plotting values to render Line and Dot
*/
getGraphPlotValues
(
median
,
metrics
)
{
const
renderData
=
metrics
.
map
(
v
=>
v
[
1
]);
const
medianMetricIndex
=
this
.
getMedianMetricIndex
(
median
,
metrics
);
let
cx
=
0
;
let
cy
=
0
;
// Find Maximum and Minimum values from `renderData` array
const
maxMemory
=
Math
.
max
.
apply
(
null
,
renderData
);
const
minMemory
=
Math
.
min
.
apply
(
null
,
renderData
);
// Find difference between extreme ends
const
diff
=
maxMemory
-
minMemory
;
const
lineWidth
=
renderData
.
length
;
// Iterate over metrics values and perform following
// 1. Find x & y co-ords for deploymentTime's memory value
// 2. Return line path against maxMemory
const
linePath
=
renderData
.
map
((
y
,
x
)
=>
{
if
(
medianMetricIndex
===
x
)
{
cx
=
x
;
cy
=
maxMemory
-
y
;
}
return
`
${
x
}
${
maxMemory
-
y
}
`
;
});
return
{
pathD
:
linePath
,
pathViewBox
:
{
lineWidth
,
diff
,
},
dotX
:
cx
,
dotY
:
cy
,
};
},
/**
* Render Graph based on provided median and metrics values
*/
renderGraph
(
median
,
metrics
)
{
const
{
pathD
,
pathViewBox
,
dotX
,
dotY
}
=
this
.
getGraphPlotValues
(
median
,
metrics
);
// Set props and update graph on UI.
this
.
pathD
=
`M
${
pathD
}
`
;
this
.
pathViewBox
=
`0 0
${
pathViewBox
.
lineWidth
}
${
pathViewBox
.
diff
}
`
;
this
.
dotX
=
dotX
;
this
.
dotY
=
dotY
;
},
},
mounted
()
{
const
renderData
=
this
.
$props
.
metrics
.
map
(
v
=>
v
[
1
]);
const
maxMemory
=
Math
.
max
.
apply
(
null
,
renderData
);
const
minMemory
=
Math
.
min
.
apply
(
null
,
renderData
);
const
diff
=
maxMemory
-
minMemory
;
// const cx = 0;
// const cy = 0;
const
lineWidth
=
renderData
.
length
;
const
linePath
=
renderData
.
map
((
y
,
x
)
=>
`
${
x
}
${
maxMemory
-
y
}
`
);
this
.
pathD
=
`M
${
linePath
}
`
;
this
.
pathViewBox
=
`0 0
${
lineWidth
}
${
diff
}
`
;
this
.
renderGraph
(
this
.
deploymentTime
,
this
.
metrics
);
},
template
:
`
<div class="memory-graph-container">
<svg :width="width" :height="height" xmlns="http://www.w3.org/2000/svg">
<svg
class="has-tooltip" :title="getFormattedMedian"
:width="width" :height="height" xmlns="http://www.w3.org/2000/svg">
<path :d="pathD" :viewBox="pathViewBox" />
<
!--<circle r="0.8" :cx="dotX" :cy="dotY" tranform="translate(0 -1)" /> --
>
<
circle r="1.5" :cx="dotX" :cy="dotY" tranform="translate(0 -1)" /
>
</svg>
</div>
`
,
...
...
app/assets/stylesheets/framework/memory_graph.scss
View file @
f6600616
.memory-graph-container
{
svg
{
background
:
$white-light
;
cursor
:
pointer
;
&
:hover
{
box-shadow
:
0
0
4px
$gray-darkest
inset
;
}
}
path
{
fill
:
none
;
stroke
:
$blue-500
;
stroke-width
:
1
px
;
stroke-width
:
2
px
;
}
circle
{
stroke
:
$blue-700
;
fill
:
$blue-700
;
stroke-width
:
4px
;
}
}
app/assets/stylesheets/pages/merge_requests.scss
View file @
f6600616
...
...
@@ -182,8 +182,7 @@
}
&
.mr-memory-usage
{
margin-top
:
10px
;
margin-bottom
:
10px
;
margin
:
5px
0
10px
25px
;
}
}
...
...
@@ -511,7 +510,12 @@
.mr-info-list.mr-memory-usage
{
.legend
{
height
:
75%
;
height
:
65%
;
top
:
0
;
@media
(
max-width
:
$screen-xs-max
)
{
height
:
20px
;
}
}
p
{
...
...
@@ -731,13 +735,15 @@
}
.mr-memory-usage
{
p
.usage-info-loading
{
margin-bottom
:
6px
;
p
.usage-info-loading
,
p
.usage-info-unavailable
,
p
.usage-info-failed
{
margin-bottom
:
5px
;
}
.usage-info-load-spinner
{
margin-right
:
10px
;
font-size
:
16px
;
}
p
.usage-info-loading
.usage-info-load-spinner
{
margin-right
:
10px
;
font-size
:
16px
;
}
@media
(
max-width
:
$screen-md-min
)
{
...
...
app/controllers/projects/merge_requests_controller.rb
View file @
f6600616
...
...
@@ -410,10 +410,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
metrics_url
=
if
can?
(
current_user
,
:read_environment
,
environment
)
&&
environment
.
has_metrics?
metrics_namespace_project_environment_path
(
environment
.
project
.
namespace
,
environment
.
project
,
environment
,
deployment
)
metrics_namespace_project_environment_
deployment_
path
(
environment
.
project
.
namespace
,
environment
.
project
,
environment
,
deployment
)
end
{
...
...
spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
View file @
f6600616
...
...
@@ -9,6 +9,7 @@ const deploymentMockData = [
name
:
'
review/diplo
'
,
url
:
'
/root/acets-review-apps/environments/15
'
,
stop_url
:
'
/root/acets-review-apps/environments/15/stop
'
,
metrics_url
:
'
/root/acets-review-apps/environments/15/deployments/1/metrics
'
,
external_url
:
'
http://diplo.
'
,
external_url_formatted
:
'
diplo.
'
,
deployed_at
:
'
2017-03-22T22:44:42.258Z
'
,
...
...
@@ -156,6 +157,7 @@ describe('MRWidgetDeployment', () => {
expect
(
el
.
querySelector
(
'
.js-deploy-url
'
).
getAttribute
(
'
href
'
)).
toEqual
(
deployment
.
external_url
);
expect
(
el
.
querySelector
(
'
.js-deploy-url
'
).
innerText
).
toContain
(
deployment
.
external_url_formatted
);
expect
(
el
.
querySelector
(
'
.js-deploy-time
'
).
innerText
).
toContain
(
vm
.
formatDate
(
deployment
.
deployed_at
));
expect
(
el
.
querySelector
(
'
.js-mr-memory-usage
'
)).
toBeDefined
();
expect
(
el
.
querySelector
(
'
button
'
)).
toBeDefined
();
});
...
...
@@ -165,6 +167,7 @@ describe('MRWidgetDeployment', () => {
Vue
.
nextTick
(()
=>
{
expect
(
el
.
querySelectorAll
(
'
.ci-widget
'
).
length
).
toEqual
(
3
);
expect
(
el
.
querySelectorAll
(
'
.js-mr-memory-usage
'
).
length
).
toEqual
(
3
);
done
();
});
});
...
...
@@ -176,6 +179,7 @@ describe('MRWidgetDeployment', () => {
expect
(
el
.
querySelectorAll
(
'
.js-deploy-meta
'
).
length
).
toEqual
(
0
);
expect
(
el
.
querySelectorAll
(
'
.js-deploy-url
'
).
length
).
toEqual
(
0
);
expect
(
el
.
querySelectorAll
(
'
.js-deploy-time
'
).
length
).
toEqual
(
0
);
expect
(
el
.
querySelectorAll
(
'
.js-mr-memory-usage
'
).
length
).
toEqual
(
0
);
expect
(
el
.
querySelectorAll
(
'
.button
'
).
length
).
toEqual
(
0
);
done
();
});
...
...
spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
0 → 100644
View file @
f6600616
import
Vue
from
'
vue
'
;
import
memoryUsageComponent
from
'
~/vue_merge_request_widget/components/mr_widget_memory_usage
'
;
import
MRWidgetService
from
'
~/vue_merge_request_widget/services/mr_widget_service
'
;
const
url
=
'
/root/acets-review-apps/environments/15/deployments/1/metrics
'
;
const
metricsMockData
=
{
success
:
true
,
metrics
:
{
memory_values
:
[
{
metric
:
{},
values
:
[
[
1493716685
,
'
4.30859375
'
],
],
},
],
},
last_update
:
'
2017-05-02T12:34:49.628Z
'
,
deployment_time
:
1493718485
,
};
const
createComponent
=
()
=>
{
const
Component
=
Vue
.
extend
(
memoryUsageComponent
);
return
new
Component
({
el
:
document
.
createElement
(
'
div
'
),
propsData
:
{
metricsUrl
:
url
,
memoryMetrics
:
[],
deploymentTime
:
0
,
hasMetrics
:
false
,
loadFailed
:
false
,
loadingMetrics
:
true
,
backOffRequestCounter
:
0
,
},
});
};
const
messages
=
{
loadingMetrics
:
'
Loading deployment statistics.
'
,
hasMetrics
:
'
Deployment memory usage:
'
,
loadFailed
:
'
Failed to load deployment statistics.
'
,
metricsUnavailable
:
'
Deployment statistics are not available currently.
'
,
};
describe
(
'
MemoryUsage
'
,
()
=>
{
let
vm
;
let
el
;
beforeEach
(()
=>
{
vm
=
createComponent
();
el
=
vm
.
$el
;
});
describe
(
'
props
'
,
()
=>
{
it
(
'
should have props with defaults
'
,
()
=>
{
const
{
metricsUrl
}
=
memoryUsageComponent
.
props
;
const
MetricsUrlTypeClass
=
metricsUrl
.
type
;
Vue
.
nextTick
(()
=>
{
expect
(
new
MetricsUrlTypeClass
()
instanceof
String
).
toBeTruthy
();
expect
(
metricsUrl
.
required
).
toBeTruthy
();
});
});
});
describe
(
'
data
'
,
()
=>
{
it
(
'
should have default data
'
,
()
=>
{
const
data
=
memoryUsageComponent
.
data
();
expect
(
Array
.
isArray
(
data
.
memoryMetrics
)).
toBeTruthy
();
expect
(
data
.
memoryMetrics
.
length
).
toBe
(
0
);
expect
(
typeof
data
.
deploymentTime
).
toBe
(
'
number
'
);
expect
(
data
.
deploymentTime
).
toBe
(
0
);
expect
(
typeof
data
.
hasMetrics
).
toBe
(
'
boolean
'
);
expect
(
data
.
hasMetrics
).
toBeFalsy
();
expect
(
typeof
data
.
loadFailed
).
toBe
(
'
boolean
'
);
expect
(
data
.
loadFailed
).
toBeFalsy
();
expect
(
typeof
data
.
loadingMetrics
).
toBe
(
'
boolean
'
);
expect
(
data
.
loadingMetrics
).
toBeTruthy
();
expect
(
typeof
data
.
backOffRequestCounter
).
toBe
(
'
number
'
);
expect
(
data
.
backOffRequestCounter
).
toBe
(
0
);
});
});
describe
(
'
methods
'
,
()
=>
{
const
{
metrics
,
deployment_time
}
=
metricsMockData
;
describe
(
'
computeGraphData
'
,
()
=>
{
it
(
'
should populate sparkline graph
'
,
()
=>
{
vm
.
computeGraphData
(
metrics
,
deployment_time
);
const
{
hasMetrics
,
memoryMetrics
,
deploymentTime
}
=
vm
;
expect
(
hasMetrics
).
toBeTruthy
();
expect
(
memoryMetrics
.
length
>
0
).
toBeTruthy
();
expect
(
deploymentTime
).
toEqual
(
deployment_time
);
});
});
describe
(
'
loadMetrics
'
,
()
=>
{
const
returnServicePromise
=
()
=>
new
Promise
((
resolve
)
=>
{
resolve
({
json
()
{
return
metricsMockData
;
},
});
});
it
(
'
should load metrics data using MRWidgetService
'
,
(
done
)
=>
{
spyOn
(
MRWidgetService
,
'
fetchMetrics
'
).
and
.
returnValue
(
returnServicePromise
(
true
));
spyOn
(
vm
,
'
computeGraphData
'
);
vm
.
loadMetrics
();
setTimeout
(()
=>
{
expect
(
MRWidgetService
.
fetchMetrics
).
toHaveBeenCalledWith
(
url
);
expect
(
vm
.
computeGraphData
).
toHaveBeenCalledWith
(
metrics
,
deployment_time
);
done
();
},
333
);
});
});
});
describe
(
'
template
'
,
()
=>
{
it
(
'
should render template elements correctly
'
,
()
=>
{
expect
(
el
.
classList
.
contains
(
'
mr-memory-usage
'
)).
toBeTruthy
();
expect
(
el
.
querySelector
(
'
.js-usage-info
'
)).
toBeDefined
();
});
it
(
'
should show loading metrics message while metrics are being loaded
'
,
(
done
)
=>
{
vm
.
loadingMetrics
=
true
;
vm
.
hasMetrics
=
false
;
vm
.
loadFailed
=
false
;
Vue
.
nextTick
(()
=>
{
expect
(
el
.
querySelector
(
'
.js-usage-info.usage-info-loading
'
)).
toBeDefined
();
expect
(
el
.
querySelector
(
'
.js-usage-info .usage-info-load-spinner
'
)).
toBeDefined
();
expect
(
el
.
querySelector
(
'
.js-usage-info
'
).
innerText
).
toContain
(
messages
.
loadingMetrics
);
done
();
});
});
it
(
'
should show deployment memory usage when metrics are loaded
'
,
(
done
)
=>
{
vm
.
loadingMetrics
=
false
;
vm
.
hasMetrics
=
true
;
vm
.
loadFailed
=
false
;
Vue
.
nextTick
(()
=>
{
expect
(
el
.
querySelector
(
'
.memory-graph-container
'
)).
toBeDefined
();
expect
(
el
.
querySelector
(
'
.js-usage-info
'
).
innerText
).
toContain
(
messages
.
hasMetrics
);
done
();
});
});
it
(
'
should show failure message when metrics loading failed
'
,
(
done
)
=>
{
vm
.
loadingMetrics
=
false
;
vm
.
hasMetrics
=
false
;
vm
.
loadFailed
=
true
;
Vue
.
nextTick
(()
=>
{
expect
(
el
.
querySelector
(
'
.js-usage-info.usage-info-failed
'
)).
toBeDefined
();
expect
(
el
.
querySelector
(
'
.js-usage-info
'
).
innerText
).
toContain
(
messages
.
loadFailed
);
done
();
});
});
it
(
'
should show metrics unavailable message when metrics loading failed
'
,
(
done
)
=>
{
vm
.
loadingMetrics
=
false
;
vm
.
hasMetrics
=
false
;
vm
.
loadFailed
=
false
;
Vue
.
nextTick
(()
=>
{
expect
(
el
.
querySelector
(
'
.js-usage-info.usage-info-unavailable
'
)).
toBeDefined
();
expect
(
el
.
querySelector
(
'
.js-usage-info
'
).
innerText
).
toContain
(
messages
.
metricsUnavailable
);
done
();
});
});
});
});
spec/javascripts/vue_shared/components/memory_graph_spec.js
0 → 100644
View file @
f6600616
import
Vue
from
'
vue
'
;
import
memoryGraphComponent
from
'
~/vue_shared/components/memory_graph
'
;
import
{
mockMetrics
,
mockMedian
,
mockMedianIndex
}
from
'
./mock_data
'
;
const
defaultHeight
=
'
25
'
;
const
defaultWidth
=
'
100
'
;
const
createComponent
=
()
=>
{
const
Component
=
Vue
.
extend
(
memoryGraphComponent
);
return
new
Component
({
el
:
document
.
createElement
(
'
div
'
),
propsData
:
{
metrics
:
[],
deploymentTime
:
0
,
width
:
''
,
height
:
''
,
pathD
:
''
,
pathViewBox
:
''
,
dotX
:
''
,
dotY
:
''
,
},
});
};
describe
(
'
MemoryGraph
'
,
()
=>
{
let
vm
;
let
el
;
beforeEach
(()
=>
{
vm
=
createComponent
();
el
=
vm
.
$el
;
});
describe
(
'
props
'
,
()
=>
{
it
(
'
should have props with defaults
'
,
(
done
)
=>
{
const
{
metrics
,
deploymentTime
,
width
,
height
}
=
memoryGraphComponent
.
props
;
Vue
.
nextTick
(()
=>
{
const
typeClassMatcher
=
(
propItem
,
expectedType
)
=>
{
const
PropItemTypeClass
=
propItem
.
type
;
expect
(
new
PropItemTypeClass
()
instanceof
expectedType
).
toBeTruthy
();
expect
(
propItem
.
required
).
toBeTruthy
();
};
typeClassMatcher
(
metrics
,
Array
);
typeClassMatcher
(
deploymentTime
,
Number
);
typeClassMatcher
(
width
,
String
);
typeClassMatcher
(
height
,
String
);
done
();
});
});
});
describe
(
'
data
'
,
()
=>
{
it
(
'
should have default data
'
,
()
=>
{
const
data
=
memoryGraphComponent
.
data
();
const
dataValidator
=
(
dataItem
,
expectedType
,
defaultVal
)
=>
{
expect
(
typeof
dataItem
).
toBe
(
expectedType
);
expect
(
dataItem
).
toBe
(
defaultVal
);
};
dataValidator
(
data
.
pathD
,
'
string
'
,
''
);
dataValidator
(
data
.
pathViewBox
,
'
string
'
,
''
);
dataValidator
(
data
.
dotX
,
'
string
'
,
''
);
dataValidator
(
data
.
dotY
,
'
string
'
,
''
);
});
});
describe
(
'
computed
'
,
()
=>
{
describe
(
'
getFormattedMedian
'
,
()
=>
{
it
(
'
should show human readable median value based on provided median timestamp
'
,
()
=>
{
vm
.
deploymentTime
=
mockMedian
;
const
formattedMedian
=
vm
.
getFormattedMedian
;
expect
(
formattedMedian
.
indexOf
(
'
Deployed
'
)
>
-
1
).
toBeTruthy
();
expect
(
formattedMedian
.
indexOf
(
'
ago
'
)
>
-
1
).
toBeTruthy
();
});
});
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
getMedianMetricIndex
'
,
()
=>
{
it
(
'
should return index of closest metric timestamp to that of median
'
,
()
=>
{
const
matchingIndex
=
vm
.
getMedianMetricIndex
(
mockMedian
,
mockMetrics
);
expect
(
matchingIndex
).
toBe
(
mockMedianIndex
);
});
});
describe
(
'
getGraphPlotValues
'
,
()
=>
{
it
(
'
should return Object containing values to plot graph
'
,
()
=>
{
const
plotValues
=
vm
.
getGraphPlotValues
(
mockMedian
,
mockMetrics
);
expect
(
plotValues
.
pathD
).
toBeDefined
();
expect
(
Array
.
isArray
(
plotValues
.
pathD
)).
toBeTruthy
();
expect
(
plotValues
.
pathViewBox
).
toBeDefined
();
expect
(
typeof
plotValues
.
pathViewBox
).
toBe
(
'
object
'
);
expect
(
plotValues
.
dotX
).
toBeDefined
();
expect
(
typeof
plotValues
.
dotX
).
toBe
(
'
number
'
);
expect
(
plotValues
.
dotY
).
toBeDefined
();
expect
(
typeof
plotValues
.
dotY
).
toBe
(
'
number
'
);
});
});
});
describe
(
'
template
'
,
()
=>
{
it
(
'
should render template elements correctly
'
,
()
=>
{
expect
(
el
.
classList
.
contains
(
'
memory-graph-container
'
)).
toBeTruthy
();
expect
(
el
.
querySelector
(
'
svg
'
)).
toBeDefined
();
});
it
(
'
should render graph when renderGraph is called internally
'
,
(
done
)
=>
{
const
{
pathD
,
pathViewBox
,
dotX
,
dotY
}
=
vm
.
getGraphPlotValues
(
mockMedian
,
mockMetrics
);
vm
.
height
=
defaultHeight
;
vm
.
width
=
defaultWidth
;
vm
.
pathD
=
`M
${
pathD
}
`
;
vm
.
pathViewBox
=
`0 0
${
pathViewBox
.
lineWidth
}
${
pathViewBox
.
diff
}
`
;
vm
.
dotX
=
dotX
;
vm
.
dotY
=
dotY
;
Vue
.
nextTick
(()
=>
{
const
svgEl
=
el
.
querySelector
(
'
svg
'
);
expect
(
svgEl
).
toBeDefined
();
expect
(
svgEl
.
getAttribute
(
'
height
'
)).
toBe
(
defaultHeight
);
expect
(
svgEl
.
getAttribute
(
'
width
'
)).
toBe
(
defaultWidth
);
const
pathEl
=
el
.
querySelector
(
'
path
'
);
expect
(
pathEl
).
toBeDefined
();
expect
(
pathEl
.
getAttribute
(
'
d
'
)).
toBe
(
`M
${
pathD
}
`
);
expect
(
pathEl
.
getAttribute
(
'
viewBox
'
)).
toBe
(
`0 0
${
pathViewBox
.
lineWidth
}
${
pathViewBox
.
diff
}
`
);
const
circleEl
=
el
.
querySelector
(
'
circle
'
);
expect
(
circleEl
).
toBeDefined
();
expect
(
circleEl
.
getAttribute
(
'
r
'
)).
toBe
(
'
1.5
'
);
expect
(
circleEl
.
getAttribute
(
'
tranform
'
)).
toBe
(
'
translate(0 -1)
'
);
expect
(
circleEl
.
getAttribute
(
'
cx
'
)).
toBe
(
`
${
dotX
}
`
);
expect
(
circleEl
.
getAttribute
(
'
cy
'
)).
toBe
(
`
${
dotY
}
`
);
done
();
});
});
});
});
spec/javascripts/vue_shared/components/mock_data.js
0 → 100644
View file @
f6600616
/* eslint-disable */
export
const
mockMetrics
=
[
[
1493716685
,
'
4.30859375
'
],
[
1493716745
,
'
4.30859375
'
],
[
1493716805
,
'
4.30859375
'
],
[
1493716865
,
'
4.30859375
'
],
[
1493716925
,
'
4.30859375
'
],
[
1493716985
,
'
4.30859375
'
],
[
1493717045
,
'
4.30859375
'
],
[
1493717105
,
'
4.30859375
'
],
[
1493717165
,
'
4.30859375
'
],
[
1493717225
,
'
4.30859375
'
],
[
1493717285
,
'
4.30859375
'
],
[
1493717345
,
'
4.30859375
'
],
[
1493717405
,
'
4.30859375
'
],
[
1493717465
,
'
4.30859375
'
],
[
1493717525
,
'
4.30859375
'
],
[
1493717585
,
'
4.30859375
'
],
[
1493717645
,
'
4.30859375
'
],
[
1493717705
,
'
4.30859375
'
],
[
1493717765
,
'
4.30859375
'
],
[
1493717825
,
'
4.30859375
'
],
[
1493717885
,
'
4.30859375
'
],
[
1493717945
,
'
4.30859375
'
],
[
1493718005
,
'
4.30859375
'
],
[
1493718065
,
'
4.30859375
'
],
[
1493718125
,
'
4.30859375
'
],
[
1493718185
,
'
4.30859375
'
],
[
1493718245
,
'
4.30859375
'
],
[
1493718305
,
'
4.234375
'
],
[
1493718365
,
'
4.234375
'
],
[
1493718425
,
'
4.234375
'
],
[
1493718485
,
'
4.234375
'
],
[
1493718545
,
'
4.243489583333333
'
],
[
1493718605
,
'
4.2109375
'
],
[
1493718665
,
'
4.2109375
'
],
[
1493718725
,
'
4.2109375
'
],
[
1493718785
,
'
4.26171875
'
],
[
1493718845
,
'
4.26171875
'
],
[
1493718905
,
'
4.26171875
'
],
[
1493718965
,
'
4.26171875
'
],
[
1493719025
,
'
4.26171875
'
],
[
1493719085
,
'
4.26171875
'
],
[
1493719145
,
'
4.26171875
'
],
[
1493719205
,
'
4.26171875
'
],
[
1493719265
,
'
4.26171875
'
],
[
1493719325
,
'
4.26171875
'
],
[
1493719385
,
'
4.26171875
'
],
[
1493719445
,
'
4.26171875
'
],
[
1493719505
,
'
4.26171875
'
],
[
1493719565
,
'
4.26171875
'
],
[
1493719625
,
'
4.26171875
'
],
[
1493719685
,
'
4.26171875
'
],
[
1493719745
,
'
4.26171875
'
],
[
1493719805
,
'
4.26171875
'
],
[
1493719865
,
'
4.26171875
'
],
[
1493719925
,
'
4.26171875
'
],
[
1493719985
,
'
4.26171875
'
],
[
1493720045
,
'
4.26171875
'
],
[
1493720105
,
'
4.26171875
'
],
[
1493720165
,
'
4.26171875
'
],
[
1493720225
,
'
4.26171875
'
],
[
1493720285
,
'
4.26171875
'
],
];
export
const
mockMedian
=
1493718485
;
export
const
mockMedianIndex
=
30
;
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