Commit 798b17a3 authored by Fatih Acet's avatar Fatih Acet

Implement Cycle Analytics frontend.

parent 74626106
((global) => {
gl.CycleAnalytics = class CycleAnalytics {
constructor() {
this.vue = new Vue({
el: '#cycle-analytics',
name: 'CycleAnalytics',
created: this.fetchData(),
data: this.getData({ isLoading: true })
});
}
fetchData() {
$.get('cycle_analytics.json')
.done((data) => {
this.vue.$data = this.getData(data);
this.initDropdown();
})
.error((data) => {
this.handleError(data);
})
.always(() => {
this.vue.isLoading = false;
})
}
getData(data) {
return {
notAvailable: data.notAvailable || false,
isLoading: data.isLoading || false,
analytics: {
summary: [
{ desc: 'New Issues', value: data.issues || '-' },
{ desc: 'Commits', value: data.commits || '-' },
{ desc: 'Deploys', value: data.deploys || '-' }
],
data: [
{ title: 'Issue', desc: 'Time before an issue get scheduled', value: data.issue || '-' },
{ 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 || '-' },
{ 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) {
// TODO: Make sure that this is the proper error handling
new Flash('There was an error while fetching cycyle analytics data.', 'alert');
}
initDropdown() {
const $dropdown = $('.js-ca-dropdown');
const $label = $dropdown.find('.dropdown-label');
$dropdown.find('li a').on('click', (e) => {
e.preventDefault();
const $target = $(e.currentTarget);
$label.text($target.text().trim());
value = $target.data('value');
this.vue.isLoading = true;
})
}
}
})(window.gl || (window.gl = {}));
...@@ -186,6 +186,9 @@ ...@@ -186,6 +186,9 @@
new gl.ProtectedBranchCreate(); new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList(); new gl.ProtectedBranchEditList();
break; break;
case 'projects:cycle_analytics:show':
window.ca = new gl.CycleAnalytics();
break;
} }
switch (path.first()) { switch (path.first()) {
case 'admin': case 'admin':
......
#cycle-analytics {
margin-top: 24px;
.panel {
.content-block {
padding: 24px 0;
border-bottom: none;
position: relative;
}
.column {
text-align: center;
.header {
font-size: 30px;
line-height: 38px;
font-weight: normal;
margin: 0;
}
.text {
color: $layout-link-gray;
}
}
.dropdown {
position: relative;
top: 10px;
}
}
.bordered-box {
border: 1px solid $border-color;
@include border-radius($border-radius-default);
position: relative;
}
.content-list {
li {
padding: 18px $gl-padding $gl-padding;
}
.col-md-10 {
span {
&:first-child {
line-height: 19px;
font-size: 15px;
font-weight: 600;
}
&:last-child {
color: #8C8C8C;
}
}
}
.col-md-2 span {
line-height: 42px;
}
}
.inner-content {
width: 450px;
text-align: center;
margin: 0 auto;
padding: 62px 0;
.btn-block {
max-width: 130px;
margin: 0 auto;
}
h4 {
color: $gl-text-color;
font-size: 17px;
}
p {
color: #8C8C8C;
margin-bottom: $gl-padding;
}
}
&.waiting {
.panel .header {
width: 35px;
height: 35px;
margin-bottom: 3px;
}
span {
background-color: #F8F8F8;
color: #F8F8F8 !important;
display: inline-block;
line-height: 13px !important;
}
.dropdown {
opacity: .33;
}
.col-md-2 span {
position: relative;
top: 11px;
}
.fa-spinner {
font-size: 32px;
position: absolute;
left: 50%;
top: 50%;
margin: -16px 0 0 -16px;
}
}
}
...@@ -46,6 +46,10 @@ module GitlabRoutingHelper ...@@ -46,6 +46,10 @@ module GitlabRoutingHelper
namespace_project_environments_path(project.namespace, project, *args) namespace_project_environments_path(project.namespace, project, *args)
end end
def project_cycle_analytics_path(project, *args)
namespace_project_cycle_analytics_path(project.namespace, project, *args)
end
def project_builds_path(project, *args) def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args) namespace_project_builds_path(project.namespace, project, *args)
end end
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
Repository Repository
- if project_nav_tab? :pipelines - if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :environments]) do = nav_link(controller: [:pipelines, :builds, :environments, :cycle_analytics]) do
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
%span %span
Pipelines Pipelines
......
%h2 Cycle Analytics from #{@cycle_analytics.from} to Today = render 'projects/pipelines/head'
%ul.list-group #cycle-analytics{"v-cloak" => "true", ":class" => "{ 'waiting': isLoading }"}
%li.list-group-item .panel.panel-default
Issue: .panel-heading
- if issue = @cycle_analytics.issue Pipeline Health
= distance_of_time_in_words issue
- else .content-block
= "<Not enough data>" = icon("spinner spin", "v-if" => "isLoading")
%li.list-group-item .row
Plan: %template{"v-for" => "info in analytics.summary"}
- if plan = @cycle_analytics.plan .col-md-3.column
= distance_of_time_in_words plan %span.header {{info.value}}
- else %br
= "<Not enough data>" %span.text {{info.desc}}
%li.list-group-item .col-md-3.column
Code: .dropdown.inline.js-ca-dropdown
- if code = @cycle_analytics.code.presence %button.dropdown-menu-toggle{"aria-expanded" => "false", "data-toggle" => "dropdown", :type => "button"}
= distance_of_time_in_words code %span.dropdown-label Last 30 days
- else %i.fa.fa-chevron-down
= "<Not enough data>" %ul.dropdown-menu.dropdown-menu-align-right
%li
%li.list-group-item %a{'href' => "#", 'data-value' => '30days'}
Test: Last 30 days
- if test = @cycle_analytics.test.presence %li
= distance_of_time_in_words test %a{'href' => "#", 'data-value' => '90days'}
- else Last 90 days
= "<Not enough data>"
.bordered-box
%li.list-group-item = icon("spinner spin", "v-if" => "isLoading")
Review:
- if review = @cycle_analytics.review.presence %ul.content-list{{"v-if" => "!notAvailable"}}
= distance_of_time_in_words review %li{"v-for" => "info in analytics.data"}
- else .row
= "<Not enough data>" .col-md-10
%span
%li.list-group-item {{info.title}}
Staging: %br
- if staging = @cycle_analytics.staging.presence %span
= distance_of_time_in_words staging {{info.desc}}
- else .col-md-2
= "<Not enough data>" %span
{{info.value}}
%li.list-group-item
Production:
- if production = @cycle_analytics.production.presence .content-block{{"v-if" => "notAvailable"}}
= distance_of_time_in_words production .inner-content
- else = custom_icon('icon_cycle_analytics_splash')
= "<Not enough data>" %h4
Set up your deploys to environment!
%p
Cycle Analytics will give an overview on how much time it takes to go from an idea to production in your project.
= button_tag 'Set up', class: 'btn btn-create btn-block'
...@@ -19,3 +19,8 @@ ...@@ -19,3 +19,8 @@
= link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
%span %span
Environments Environments
= nav_link(controller: %w(cycle_analytics)) do
= link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do
%span
Cycle Analytics
<svg width="167" height="166" viewBox="261 77 167 166" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M18 84h34v9H18z"/><mask id="h" x="0" y="0" width="34" height="9" fill="#fff"><use xlink:href="#a"/></mask><path d="M26 95c0 4.97 4.03 9 9 9s9-4.03 9-9" id="b"/><mask id="i" x="0" y="0" width="18" height="9" fill="#fff"><use xlink:href="#b"/></mask><path d="M42 48l25.564 18.26C68.91 67.22 70 69.34 70 70.994v22.004c0 2.21-1.456 2.962-3.253 1.678L42 77V48z" id="c"/><mask id="j" x="0" y="0" width="28" height="47.303" fill="#fff"><use xlink:href="#c"/></mask><path d="M0 48l25.564 18.26C26.91 67.22 28 69.34 28 70.994v22.004c0 2.21-1.456 2.962-3.253 1.678L0 77V48z" id="d"/><mask id="k" x="0" y="0" width="28" height="47.303" fill="#fff"><use xlink:href="#d"/></mask><path d="M52.994 86C59.156 79.382 62 67.656 62 49.725 62 19.235 38.032 1.18 38.032 1.18c-1.675-1.434-4.408-1.466-6.064 0C31.968 1.18 8 19.234 8 49.724 8 67.655 10.844 79.382 17.006 86h35.988z" id="e"/><mask id="l" x="0" y="0" width="54" height="85.908" fill="#fff"><use xlink:href="#e"/></mask><circle id="f" cx="35" cy="43" r="13"/><mask id="m" x="0" y="0" width="26" height="26" fill="#fff"><use xlink:href="#f"/></mask><circle id="g" cx="35" cy="43" r="8"/><mask id="n" x="0" y="0" width="16" height="16" fill="#fff"><use xlink:href="#g"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(262 77)"><g transform="translate(47 6)"><path d="M51 132c0 2.21 1.79 4 4 4s4-1.79 4-4M11 122c0 2.21 1.79 4 4 4s4-1.79 4-4" stroke="#E5E5E5" stroke-width="2" stroke-linecap="round"/><rect fill="#E5E5E5" x="50" y="107" width="2" height="6" rx="1"/><rect fill="#E5E5E5" x="50" y="117" width="2" height="6" rx="1"/><rect fill="#E5E5E5" x="34" y="107" width="2" height="6" rx="1"/><rect fill="#E5E5E5" x="42" y="107" width="2" height="6" rx="1"/><rect fill="#E5E5E5" x="34" y="117" width="2" height="6" rx="1"/><rect fill="#E5E5E5" x="34" y="127" width="2" height="6" rx="1"/><rect fill="#E5E5E5" x="42" y="117" width="2" height="30" rx="1"/><rect fill="#E5E5E5" x="26" y="107" width="2" height="27" rx="1"/><rect fill="#E5E5E5" x="50" y="127" width="2" height="6" rx="1"/><rect fill="#E5E5E5" x="18" y="107" width="2" height="6" rx="1"/><rect fill="#E5E5E5" x="18" y="117" width="2" height="6" rx="1"/><use stroke="#E5E5E5" mask="url(#h)" stroke-width="4" xlink:href="#a"/><use stroke="#E5E5E5" mask="url(#i)" stroke-width="4" xlink:href="#b"/><use stroke="#E5E5E5" mask="url(#j)" stroke-width="4" xlink:href="#c"/><use stroke="#E5E5E5" mask="url(#k)" stroke-width="4" transform="matrix(-1 0 0 1 28 0)" xlink:href="#d"/><use stroke="#E5E5E5" mask="url(#l)" stroke-width="4" fill="#FFF" xlink:href="#e"/><use stroke="#E5E5E5" mask="url(#m)" stroke-width="4" xlink:href="#f"/><use stroke="#E5E5E5" mask="url(#n)" stroke-width="4" xlink:href="#g"/><path d="M43 146c0 3.59 2.91 6.5 6.5 6.5s6.5-2.91 6.5-6.5M14 133c0 3.59 2.91 6.5 6.5 6.5s6.5-2.91 6.5-6.5" stroke="#E5E5E5" stroke-width="2" stroke-linecap="round"/></g><circle stroke="#E5E5E5" stroke-width="2" cx="138.5" cy="115.5" r="6.5"/><circle stroke="#E5E5E5" stroke-width="2" cx="33.5" cy="28.5" r="6.5"/><circle stroke="#E5E5E5" stroke-width="2" cx="142" cy="52" r="4"/><circle stroke="#E5E5E5" stroke-width="2" cx="4" cy="83" r="4"/></g></svg>
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment