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
Tatuya Kamada
gitlab-ce
Commits
e49c6f86
Commit
e49c6f86
authored
Sep 19, 2016
by
Fatih Acet
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Finalise cycle analytics frontend.
parent
3f3bdece
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
164 additions
and
139 deletions
+164
-139
app/assets/javascripts/cycle-analytics.js.es6
app/assets/javascripts/cycle-analytics.js.es6
+59
-36
app/assets/javascripts/dispatcher.js
app/assets/javascripts/dispatcher.js
+2
-2
app/assets/stylesheets/pages/cycle_analytics.scss
app/assets/stylesheets/pages/cycle_analytics.scss
+49
-47
app/helpers/cycle_analytics_helper.rb
app/helpers/cycle_analytics_helper.rb
+2
-2
app/views/projects/cycle_analytics/show.html.haml
app/views/projects/cycle_analytics/show.html.haml
+52
-52
No files found.
app/assets/javascripts/cycle-analytics.js.es6
View file @
e49c6f86
((global) => {
((global) => {
const COOKIE_NAME = 'cycle_analytics_help_dismissed';
gl.CycleAnalytics = class CycleAnalytics {
gl.CycleAnalytics = class CycleAnalytics {
constructor() {
constructor() {
const that = this;
this.isHelpDismissed = $.cookie(COOKIE_NAME);
this.vue = new Vue({
this.vue = new Vue({
el: '#cycle-analytics',
el: '#cycle-analytics',
name: 'CycleAnalytics',
name: 'CycleAnalytics',
created: this.fetchData(),
created: this.fetchData(),
data: this.getData({ isLoading: true })
data: this.decorateData({ isLoading: true }),
methods: {
dismissLanding() {
that.dismissLanding();
}
}
});
});
}
}
fetchData() {
fetchData(options) {
$.get('cycle_analytics.json')
options = options || { startDate: 30 };
.done((data) => {
this.vue.$data = this.getData(data);
$.ajax({
this.initDropdown();
url: $('#cycle-analytics').data('request-path'),
})
method: 'GET',
.error((data) => {
dataType: 'json',
this.handleError(data);
contentType: 'application/json',
})
data: { start_date: options.startDate }
.always(() => {
}).done((data) => {
this.vue.isLoading = false;
this.vue.$data = this.decorateData(data);
})
this.initDropdown();
})
.error((data) => {
this.handleError(data);
})
.always(() => {
this.vue.isLoading = false;
})
}
}
getData(data) {
decorateData(data) {
return {
data.summary = data.summary || [];
notAvailable: data.notAvailable || false,
data.stats = data.stats || [];
isLoading: data.isLoading || false,
data.isHelpDismissed = this.isHelpDismissed;
analytics: {
data.isLoading = data.isLoading || false;
summary: [
{ desc: 'New Issues', value: data.issues || '-' },
data.summary.forEach((item) => {
{ desc: 'Commits', value: data.commits || '-' },
item.value = item.value || '-';
{ desc: 'Deploys', value: data.deploys || '-' }
});
],
data: [
data.stats.forEach((item) => {
{ title: 'Issue', desc: 'Time before an issue get scheduled', value: data.issue || '-' },
item.value = item.value || '-';
{ title: 'Plan', desc: 'Time before an issue starts implementation', value: data.plan || '-' },
})
{ title: 'Code', desc: 'Time until first merge request', value: data.code || '-' },
{ title: 'Test', desc: 'CI test time of the default branch', value: data.test || '-' },
return data;
{ title: 'Review', desc: 'Time between MR creation and merge/close', value: data.review || '-' },
{ title: 'Deploy', desc: 'Time for a new commit to land in one of the environments', value: data.deploy || '-' }
]
}
}
}
}
handleError(data) {
handleError(data) {
// TODO: Make sure that this is the proper error handling
this.vue.$data = {
new Flash('There was an error while fetching cycyle analytics data.', 'alert');
hasError: true,
isHelpDismissed: this.isHelpDismissed
};
new Flash('There was an error while fetching cycle analytics data.', 'alert');
}
dismissLanding() {
this.vue.isHelpDismissed = true;
$.cookie(COOKIE_NAME, true);
}
}
initDropdown() {
initDropdown() {
const $dropdown = $('.js-ca-dropdown');
const $dropdown = $('.js-ca-dropdown');
const $label = $dropdown.find('.dropdown-label');
const $label = $dropdown.find('.dropdown-label');
$dropdown.find('li a').on('click', (e) => {
$dropdown.find('li a').o
ff('click').o
n('click', (e) => {
e.preventDefault();
e.preventDefault();
const $target = $(e.currentTarget);
const $target = $(e.currentTarget);
const value = $target.data('value');
const value = $target.data('value');
$label.text($target.text().trim());
$label.text($target.text().trim());
this.vue.isLoading = true;
this.vue.isLoading = true;
this.fetchData({ startDate: value });
})
})
}
}
}
}
})(window.gl || (window.gl = {}));
})(window.gl || (window.gl = {}));
app/assets/javascripts/dispatcher.js
View file @
e49c6f86
...
@@ -92,7 +92,7 @@
...
@@ -92,7 +92,7 @@
new
MergedButtons
();
new
MergedButtons
();
break
;
break
;
case
"
projects:merge_requests:conflicts
"
:
case
"
projects:merge_requests:conflicts
"
:
window
.
mcui
=
new
MergeConflictResolver
()
new
MergeConflictResolver
()
case
'
projects:merge_requests:index
'
:
case
'
projects:merge_requests:index
'
:
shortcut_handler
=
new
ShortcutsNavigation
();
shortcut_handler
=
new
ShortcutsNavigation
();
Issuable
.
init
();
Issuable
.
init
();
...
@@ -187,7 +187,7 @@
...
@@ -187,7 +187,7 @@
new
gl
.
ProtectedBranchEditList
();
new
gl
.
ProtectedBranchEditList
();
break
;
break
;
case
'
projects:cycle_analytics:show
'
:
case
'
projects:cycle_analytics:show
'
:
window
.
ca
=
new
gl
.
CycleAnalytics
();
new
gl
.
CycleAnalytics
();
break
;
break
;
}
}
switch
(
path
.
first
())
{
switch
(
path
.
first
())
{
...
...
app/assets/stylesheets/pages/cycle_analytics.scss
View file @
e49c6f86
#cycle-analytics
{
#cycle-analytics
{
margin-top
:
24px
;
margin
:
24px
auto
0
;
width
:
800px
;
position
:
relative
;
.panel
{
.panel
{
...
@@ -22,6 +24,10 @@
...
@@ -22,6 +24,10 @@
.text
{
.text
{
color
:
$layout-link-gray
;
color
:
$layout-link-gray
;
}
}
&
:last-child
{
text-align
:
right
;
}
}
}
.dropdown
{
.dropdown
{
...
@@ -39,9 +45,13 @@
...
@@ -39,9 +45,13 @@
.content-list
{
.content-list
{
li
{
li
{
padding
:
18px
$gl-padding
$gl-padding
;
padding
:
18px
$gl-padding
$gl-padding
;
.container-fluid
{
padding
:
0
;
}
}
}
.
col-md-10
{
.
title-col
{
span
{
span
{
&
:first-child
{
&
:first-child
{
line-height
:
19px
;
line-height
:
19px
;
...
@@ -54,62 +64,54 @@
...
@@ -54,62 +64,54 @@
}
}
}
}
.col-md-2
span
{
.value-col
{
line-height
:
42px
;
text-align
:
right
;
}
}
.inner-content
{
width
:
450px
;
text-align
:
center
;
margin
:
0
auto
;
padding
:
62px
0
;
.btn-block
{
span
{
max-width
:
130
px
;
line-height
:
42
px
;
margin
:
0
auto
;
}
}
}
}
h4
{
.landing
{
color
:
$gl-text-color
;
margin-bottom
:
$gl-padding
;
font-size
:
17px
;
overflow
:
hidden
;
}
p
{
.dismiss-icon
{
color
:
#8C8C8C
;
position
:
absolute
;
margin-bottom
:
$gl-padding
;
right
:
$gl-padding
;
cursor
:
pointer
;
}
}
}
&
.waitin
g
{
sv
g
{
.panel
.header
{
margin
:
0
20px
;
width
:
35px
;
float
:
left
;
height
:
35
px
;
width
:
136
px
;
margin-bottom
:
3
px
;
height
:
136
px
;
}
}
span
{
.inner-content
{
background-color
:
#F8F8F8
;
width
:
480px
;
color
:
#F8F8F8
!
important
;
float
:
left
;
display
:
inline-block
;
line-height
:
13px
!
important
;
}
.dropdown
{
h4
{
opacity
:
.33
;
color
:
$gl-text-color
;
}
font-size
:
17px
;
}
.col-md-2
span
{
p
{
position
:
relative
;
color
:
#8C8C8C
;
top
:
11px
;
margin-bottom
:
$gl-padding
;
}
}
}
}
.fa-spinner
{
.fa-spinner
{
font-size
:
32px
;
font-size
:
28px
;
position
:
absolute
;
position
:
relative
;
left
:
50%
;
margin-left
:
-20px
;
top
:
50%
;
left
:
50%
;
margin
:
-16px
0
0
-16px
;
margin-top
:
36px
;
}
}
}
}
}
app/helpers/cycle_analytics_helper.rb
View file @
e49c6f86
...
@@ -6,8 +6,8 @@ module CycleAnalyticsHelper
...
@@ -6,8 +6,8 @@ module CycleAnalyticsHelper
[
:plan
,
"Plan"
,
"Time before an issue starts implementation"
],
[
:plan
,
"Plan"
,
"Time before an issue starts implementation"
],
[
:code
,
"Code"
,
"Time until first merge request"
],
[
:code
,
"Code"
,
"Time until first merge request"
],
[
:test
,
"Test"
,
"Total test time for all commits/merges"
],
[
:test
,
"Test"
,
"Total test time for all commits/merges"
],
[
:review
,
"Review"
,
"Time between
MR
creation and merge/close"
],
[
:review
,
"Review"
,
"Time between
merge request
creation and merge/close"
],
[
:staging
,
"Staging"
,
"From
MR
merge until deploy to production"
],
[
:staging
,
"Staging"
,
"From
merge request
merge until deploy to production"
],
[
:production
,
"Production"
,
"From issue creation until deploy to production"
]]
[
:production
,
"Production"
,
"From issue creation until deploy to production"
]]
stats
=
cycle_analytics_view_data
.
reduce
([])
do
|
stats
,
(
stage_method
,
stage_text
,
stage_description
)
|
stats
=
cycle_analytics_view_data
.
reduce
([])
do
|
stats
,
(
stage_method
,
stage_text
,
stage_description
)
|
...
...
app/views/projects/cycle_analytics/show.html.haml
View file @
e49c6f86
=
render
'projects/pipelines/head'
=
render
'projects/pipelines/head'
#cycle-analytics
{
"v-cloak"
=>
"true"
,
":class"
=>
"{ 'waiting': isLoading }"
}
#cycle-analytics
{
"v-cloak"
=>
"true"
,
data:
{
request_path:
"#{project_cycle_analytics_path(@project)}"
}}
.panel.panel-default
.panel-heading
Pipeline Health
.content-block
.bordered-box.landing.content-block
{
"v-if"
=>
"!isHelpDismissed"
}
=
icon
(
"spinner spin"
,
"v-if"
=>
"isLoading"
)
=
icon
(
'times'
,
class:
'dismiss-icon'
,
"@click"
:
"dismissLanding()"
)
=
custom_icon
(
'icon_cycle_analytics_splash'
)
.inner-content
%h4
Introducing Cycle Analytics
%p
Cycle Analytics gives an overview on how much time it takes to go from idea to production in your project.
.container-fluid
=
button_tag
'Read more'
,
class:
'btn'
.row
%template
{
"v-for"
=>
"info in analytics.summary"
}
=
icon
(
"spinner spin"
,
"v-show"
=>
"isLoading"
)
.col-xs-3.column
%span
.header
{{info.value}}
.wrapper
{
"v-show"
=>
"!isLoading && !hasError"
}
%br
.panel.panel-default
%span
.text
{{info.desc}}
.panel-heading
Pipeline Health
.col-xs-3.column
.dropdown.inline.js-ca-dropdown
.content-block
%button
.dropdown-menu-toggle
{
"aria-expanded"
=>
"false"
,
"data-toggle"
=>
"dropdown"
,
:type
=>
"button"
}
%span
.dropdown-label
Last 30 days
%i
.fa.fa-chevron-down
%ul
.dropdown-menu.dropdown-menu-align-right
%li
%a
{
'href'
=>
"#"
,
'data-value'
=>
'30days'
}
Last 30 days
%li
%a
{
'href'
=>
"#"
,
'data-value'
=>
'90days'
}
Last 90 days
.bordered-box
=
icon
(
"spinner spin"
,
"v-if"
=>
"isLoading"
)
%ul
.content-list
{{
"v-if"
=>
"!notAvailable"
}}
%li
{
"v-for"
=>
"info in analytics.data"
}
.container-fluid
.container-fluid
.row
.row
.col-xs-10
%template
{
"v-for"
=>
"item in summary"
}
%span
.col-xs-3.column
{{info.title}}
%span
.header
{{item.value}}
%br
%br
%span
%span
.text
{{item.title}}
{{info.desc}}
.col-xs-2
.col-xs-3.column
%span
.dropdown.inline.js-ca-dropdown
{{info.value}}
%button
.dropdown-menu-toggle
{
"data-toggle"
=>
"dropdown"
,
:type
=>
"button"
}
%span
.dropdown-label
Last 30 days
%i
.fa.fa-chevron-down
.content-block
{{
"v-if"
=>
"notAvailable"
}}
%ul
.dropdown-menu.dropdown-menu-align-right
.inner-content
%li
=
custom_icon
(
'icon_cycle_analytics_splash'
)
%a
{
'href'
=>
"#"
,
'data-value'
=>
'30'
}
%h4
Last 30 days
Set up your deploys to environment!
%li
%p
%a
{
'href'
=>
"#"
,
'data-value'
=>
'90'
}
Cycle Analytics will give an overview on how much time it takes to go from an idea to production in your project.
Last 90 days
=
button_tag
'Set up'
,
class:
'btn btn-create btn-block'
.bordered-box
%ul
.content-list
%li
{
"v-for"
=>
"item in stats"
}
.container-fluid
.row
.col-xs-10.title-col
%span
{{item.title}}
%br
%span
{{item.description}}
.col-xs-2.value-col
%span
{{item.value}}
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