提交 e49c6f86 编写于 作者: F Fatih Acet

Finalise cycle analytics frontend.

上级 3f3bdece
((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').off('click').on('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 = {}));
...@@ -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()) {
......
#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: 130px; line-height: 42px;
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;
} }
}
&.waiting { svg {
.panel .header { margin: 0 20px;
width: 35px; float: left;
height: 35px; width: 136px;
margin-bottom: 3px; height: 136px;
} }
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;
}
} }
} }
...@@ -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)|
......
= 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}}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册