Finalise cycle analytics frontend.
This commit is contained in:
parent
3f3bdeced1
commit
e49c6f8666
5 changed files with 165 additions and 140 deletions
|
@ -1,69 +1,92 @@
|
|||
((global) => {
|
||||
|
||||
const COOKIE_NAME = 'cycle_analytics_help_dismissed';
|
||||
|
||||
gl.CycleAnalytics = class CycleAnalytics {
|
||||
constructor() {
|
||||
const that = this;
|
||||
|
||||
this.isHelpDismissed = $.cookie(COOKIE_NAME);
|
||||
this.vue = new Vue({
|
||||
el: '#cycle-analytics',
|
||||
name: 'CycleAnalytics',
|
||||
created: this.fetchData(),
|
||||
data: this.getData({ isLoading: true })
|
||||
data: this.decorateData({ isLoading: true }),
|
||||
methods: {
|
||||
dismissLanding() {
|
||||
that.dismissLanding();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
})
|
||||
fetchData(options) {
|
||||
options = options || { startDate: 30 };
|
||||
|
||||
$.ajax({
|
||||
url: $('#cycle-analytics').data('request-path'),
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: { start_date: options.startDate }
|
||||
}).done((data) => {
|
||||
this.vue.$data = this.decorateData(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 || '-' }
|
||||
]
|
||||
}
|
||||
}
|
||||
decorateData(data) {
|
||||
data.summary = data.summary || [];
|
||||
data.stats = data.stats || [];
|
||||
data.isHelpDismissed = this.isHelpDismissed;
|
||||
data.isLoading = data.isLoading || false;
|
||||
|
||||
data.summary.forEach((item) => {
|
||||
item.value = item.value || '-';
|
||||
});
|
||||
|
||||
data.stats.forEach((item) => {
|
||||
item.value = item.value || '-';
|
||||
})
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
handleError(data) {
|
||||
// TODO: Make sure that this is the proper error handling
|
||||
new Flash('There was an error while fetching cycyle analytics data.', 'alert');
|
||||
this.vue.$data = {
|
||||
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() {
|
||||
const $dropdown = $('.js-ca-dropdown');
|
||||
const $label = $dropdown.find('.dropdown-label');
|
||||
|
||||
$dropdown.find('li a').on('click', (e) => {
|
||||
$dropdown.find('li a').off('click').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
const $target = $(e.currentTarget);
|
||||
const value = $target.data('value');
|
||||
|
||||
$label.text($target.text().trim());
|
||||
this.vue.isLoading = true;
|
||||
this.fetchData({ startDate: value });
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
new MergedButtons();
|
||||
break;
|
||||
case "projects:merge_requests:conflicts":
|
||||
window.mcui = new MergeConflictResolver()
|
||||
new MergeConflictResolver()
|
||||
case 'projects:merge_requests:index':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
Issuable.init();
|
||||
|
@ -187,7 +187,7 @@
|
|||
new gl.ProtectedBranchEditList();
|
||||
break;
|
||||
case 'projects:cycle_analytics:show':
|
||||
window.ca = new gl.CycleAnalytics();
|
||||
new gl.CycleAnalytics();
|
||||
break;
|
||||
}
|
||||
switch (path.first()) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#cycle-analytics {
|
||||
margin-top: 24px;
|
||||
margin: 24px auto 0;
|
||||
width: 800px;
|
||||
position: relative;
|
||||
|
||||
.panel {
|
||||
|
||||
|
@ -22,6 +24,10 @@
|
|||
.text {
|
||||
color: $layout-link-gray;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
|
@ -39,9 +45,13 @@
|
|||
.content-list {
|
||||
li {
|
||||
padding: 18px $gl-padding $gl-padding;
|
||||
|
||||
.container-fluid {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.col-md-10 {
|
||||
.title-col {
|
||||
span {
|
||||
&:first-child {
|
||||
line-height: 19px;
|
||||
|
@ -54,62 +64,54 @@
|
|||
}
|
||||
}
|
||||
|
||||
.col-md-2 span {
|
||||
line-height: 42px;
|
||||
.value-col {
|
||||
text-align: right;
|
||||
|
||||
span {
|
||||
line-height: 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inner-content {
|
||||
width: 450px;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
padding: 62px 0;
|
||||
.landing {
|
||||
margin-bottom: $gl-padding;
|
||||
overflow: hidden;
|
||||
|
||||
.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;
|
||||
.dismiss-icon {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin: -16px 0 0 -16px;
|
||||
right: $gl-padding;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin: 0 20px;
|
||||
float: left;
|
||||
width: 136px;
|
||||
height: 136px;
|
||||
}
|
||||
|
||||
.inner-content {
|
||||
width: 480px;
|
||||
float: left;
|
||||
|
||||
h4 {
|
||||
color: $gl-text-color;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #8C8C8C;
|
||||
margin-bottom: $gl-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fa-spinner {
|
||||
font-size: 28px;
|
||||
position: relative;
|
||||
margin-left: -20px;
|
||||
left: 50%;
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ module CycleAnalyticsHelper
|
|||
[:plan, "Plan", "Time before an issue starts implementation"],
|
||||
[:code, "Code", "Time until first merge request"],
|
||||
[:test, "Test", "Total test time for all commits/merges"],
|
||||
[:review, "Review", "Time between MR creation and merge/close"],
|
||||
[:staging, "Staging", "From MR merge until deploy to production"],
|
||||
[:review, "Review", "Time between merge request creation and merge/close"],
|
||||
[:staging, "Staging", "From merge request merge 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)|
|
||||
|
|
|
@ -1,58 +1,58 @@
|
|||
= render 'projects/pipelines/head'
|
||||
|
||||
#cycle-analytics{"v-cloak" => "true", ":class" => "{ 'waiting': isLoading }"}
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Pipeline Health
|
||||
#cycle-analytics{"v-cloak" => "true", data: { request_path: "#{project_cycle_analytics_path(@project)}"}}
|
||||
|
||||
.content-block
|
||||
= icon("spinner spin", "v-if" => "isLoading")
|
||||
.bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"}
|
||||
= 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
|
||||
.row
|
||||
%template{"v-for" => "info in analytics.summary"}
|
||||
.col-xs-3.column
|
||||
%span.header {{info.value}}
|
||||
%br
|
||||
%span.text {{info.desc}}
|
||||
= button_tag 'Read more', class: 'btn'
|
||||
|
||||
.col-xs-3.column
|
||||
.dropdown.inline.js-ca-dropdown
|
||||
%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
|
||||
= icon("spinner spin", "v-show" => "isLoading")
|
||||
|
||||
.bordered-box
|
||||
= icon("spinner spin", "v-if" => "isLoading")
|
||||
.wrapper{"v-show" => "!isLoading && !hasError"}
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Pipeline Health
|
||||
|
||||
%ul.content-list{{"v-if" => "!notAvailable"}}
|
||||
%li{"v-for" => "info in analytics.data"}
|
||||
.content-block
|
||||
.container-fluid
|
||||
.row
|
||||
.col-xs-10
|
||||
%span
|
||||
{{info.title}}
|
||||
%br
|
||||
%span
|
||||
{{info.desc}}
|
||||
.col-xs-2
|
||||
%span
|
||||
{{info.value}}
|
||||
%template{"v-for" => "item in summary"}
|
||||
.col-xs-3.column
|
||||
%span.header {{item.value}}
|
||||
%br
|
||||
%span.text {{item.title}}
|
||||
|
||||
.col-xs-3.column
|
||||
.dropdown.inline.js-ca-dropdown
|
||||
%button.dropdown-menu-toggle{"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' => '30'}
|
||||
Last 30 days
|
||||
%li
|
||||
%a{'href' => "#", 'data-value' => '90'}
|
||||
Last 90 days
|
||||
|
||||
.content-block{{"v-if" => "notAvailable"}}
|
||||
.inner-content
|
||||
= custom_icon('icon_cycle_analytics_splash')
|
||||
%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'
|
||||
.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}}
|
||||
|
|
Loading…
Reference in a new issue