From 4ea1208d0c992428b397b147de201d44f79bf2e5 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Sat, 3 Dec 2016 14:37:52 +0000 Subject: Add inline pipelines graph in mr widget pipeline status header --- app/assets/javascripts/merge_request_widget.js.es6 | 18 +++ app/assets/stylesheets/framework/dropdowns.scss | 28 ++++ app/assets/stylesheets/pages/icons.scss | 48 ++++++ app/assets/stylesheets/pages/merge_requests.scss | 164 ++++++++++++++++++++- app/assets/stylesheets/pages/pipelines.scss | 107 ++++++-------- app/helpers/ci_status_helper.rb | 4 +- .../merge_requests/widget/_heading.html.haml | 30 +++- app/views/shared/icons/_icon_status_canceled.svg | 1 - .../shared/icons/_icon_status_canceled.svg.erb | 1 + app/views/shared/icons/_icon_status_created.svg | 1 - .../shared/icons/_icon_status_created.svg.erb | 1 + app/views/shared/icons/_icon_status_failed.svg | 1 - app/views/shared/icons/_icon_status_failed.svg.erb | 1 + app/views/shared/icons/_icon_status_pending.svg | 1 - .../shared/icons/_icon_status_pending.svg.erb | 1 + app/views/shared/icons/_icon_status_running.svg | 1 - .../shared/icons/_icon_status_running.svg.erb | 1 + app/views/shared/icons/_icon_status_skipped.svg | 1 - .../shared/icons/_icon_status_skipped.svg.erb | 1 + app/views/shared/icons/_icon_status_success.svg | 1 - .../shared/icons/_icon_status_success.svg.erb | 2 + app/views/shared/icons/_icon_status_warning.svg | 1 - .../shared/icons/_icon_status_warning.svg.erb | 1 + 23 files changed, 330 insertions(+), 86 deletions(-) delete mode 100644 app/views/shared/icons/_icon_status_canceled.svg create mode 100644 app/views/shared/icons/_icon_status_canceled.svg.erb delete mode 100644 app/views/shared/icons/_icon_status_created.svg create mode 100644 app/views/shared/icons/_icon_status_created.svg.erb delete mode 100644 app/views/shared/icons/_icon_status_failed.svg create mode 100644 app/views/shared/icons/_icon_status_failed.svg.erb delete mode 100644 app/views/shared/icons/_icon_status_pending.svg create mode 100644 app/views/shared/icons/_icon_status_pending.svg.erb delete mode 100644 app/views/shared/icons/_icon_status_running.svg create mode 100644 app/views/shared/icons/_icon_status_running.svg.erb delete mode 100644 app/views/shared/icons/_icon_status_skipped.svg create mode 100644 app/views/shared/icons/_icon_status_skipped.svg.erb delete mode 100644 app/views/shared/icons/_icon_status_success.svg create mode 100644 app/views/shared/icons/_icon_status_success.svg.erb delete mode 100644 app/views/shared/icons/_icon_status_warning.svg create mode 100644 app/views/shared/icons/_icon_status_warning.svg.erb diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 7022aa1263b..f5974c074cc 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -150,6 +150,7 @@ if (data.status !== _this.opts.ci_status && (data.status != null)) { _this.opts.ci_status = data.status; _this.showCIStatus(data.status); + _this.initPipelineGraph(); if (data.coverage) { _this.showCICoverage(data.coverage); } @@ -176,6 +177,23 @@ })(this)); }; + MergeRequestWidget.prototype.initPipelineGraph = function() { + const widgets = document.querySelectorAll('.ci_widget'); + const widget = [].find.call(widgets, widget => widget.style.display !== 'none'); + widget.addEventListener('mouseover', this.delegatePipelineNodeHover.bind(this)); + }; + + MergeRequestWidget.prototype.delegatePipelineNodeHover = function(e) { + const stageContainer = e.target.closest('.stage-container'); + if (!stageContainer || (this.currentTarget && stageContainer.id === this.currentTarget.id)) return; + this.currentTarget = stageContainer; + this.currentTarget.addEventListener('mouseleave', this.hidePipelineNodeMenu.bind(this)); + }; + + MergeRequestWidget.prototype.hidePipelineNodeMenu = function() { + $(this.currentTarget.querySelector('.dropdown-menu')).dropdown('toggle'); + }; + MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() { $.getJSON(this.opts.ci_environments_status_url, (environments) => { if (environments && environments.length) this.renderEnvironments(environments); diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index d5914b900e2..72b2de92b55 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -225,6 +225,34 @@ padding: 5px 8px; color: $dropdown-header-color; } + + .arrow { + &::before, + &::after { + content: ''; + display: inline-block; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: 18px; + } + + &::before { + left: -5px; + margin-top: -6px; + border-width: 7px 5px 7px 0; + border-right-color: $border-color; + } + + &::after { + left: -4px; + margin-top: -9px; + border-width: 10px 7px 10px 0; + border-right-color: $white-light; + } + } } .dropdown-menu-large { diff --git a/app/assets/stylesheets/pages/icons.scss b/app/assets/stylesheets/pages/icons.scss index 226bd2ead31..c22522e3b64 100644 --- a/app/assets/stylesheets/pages/icons.scss +++ b/app/assets/stylesheets/pages/icons.scss @@ -4,6 +4,14 @@ svg { fill: $gl-success; } + + &.background-icon { + background-color: $white-light; + + &:hover { + background-color: $gl-success; + } + } } .ci-status-icon-failed { @@ -12,6 +20,14 @@ svg { fill: $gl-danger; } + + &.background-icon { + background-color: $white-light; + + &:hover { + background-color: $gl-danger; + } + } } .ci-status-icon-pending, @@ -21,6 +37,14 @@ svg { fill: $gl-warning; } + + &.background-icon { + background-color: $white-light; + + &:hover { + background-color: $gl-warning; + } + } } .ci-status-icon-running { @@ -29,6 +53,14 @@ svg { fill: $blue-normal; } + + &.background-icon { + background-color: $white-light; + + &:hover { + background-color: $blue-normal; + } + } } .ci-status-icon-canceled, @@ -39,6 +71,14 @@ svg { fill: $gl-gray; } + + &.background-icon { + background-color: $white-light; + + &:hover { + background-color: $gl-gray; + } + } } .ci-status-icon-created, @@ -48,4 +88,12 @@ svg { fill: $gray-darkest; } + + &.background-icon { + background-color: $white-light; + + &:hover { + background-color: $gray-darkest; + } + } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 6234779ac19..284b4568c33 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -61,20 +61,174 @@ } .ci_widget { + display: -webkit-flex; + display: flex; + -webkit-align-items: center; + align-items: center; border-bottom: 1px solid $well-inner-border; color: $gl-gray; svg { - margin-right: 4px; position: relative; - top: 1px; overflow: visible; + z-index: 2; } - &.ci-success_with_warnings { + &> .pipeline-text { + padding: 0 7px 0 $gl-btn-padding; + } - i { - color: $gl-warning; + &> .monospace { + padding-left: 7px; + } + + &> .status { + height: 14px; + width: 14px; + } + + &.ci-success_with_warnings i { + color: $gl-warning; + } + + &> .stage-container { + height: 24px; + width: 24px; + margin: 0 3px; + background-color: $white-light; + border-radius: 50%; + + .tooltip { + white-space: nowrap; + } + + .dropdown-caret { + display: none; + position: absolute; + top: 3px; + right: 6px; + } + + a { + display: block; + } + + .dropdown-menu { + margin-top: 13px; + + ul { + max-height: 245px; + overflow-y: scroll; + + li > a { + &> svg { + height: 14px; + width: 14px; + } + + &> svg, + &> .ci-status-text { + display: inline-block; + } + } + } + } + + .arrow { + &::before, + &::after { + top: -30px; + left: 10px; + border-right-color: transparent; + } + + &::before { + margin-top: 13px; + border-width: 9px 7px 7px; + border-bottom-color: $border-color; + + } + + &::after { + margin-top: 16px; + border-width: 7px; + border-bottom-color: $white-light; + } + } + + &:not(:last-child) { + &::after { + content: ''; + width: 8px; + position: absolute; + right: -7px; + bottom: 10px; + border-bottom: 2px solid $border-color; + } + } + + &.grouped { + transition: width 150ms, border-radius 150ms, background-color 150ms; + } + + &.grouped:hover { + width: 40px; + border-radius: 15px; + border: 2px solid; + + .toggle { + .dropdown-caret { + display: inline-block; + } + + svg { + top: -2px; + left: -2px; + + path:first-of-type { + fill: transparent; + } + } + } + + &::after { + width: 6px; + right: -8px; + } + + &.ci-status-icon-success { + background-color: rgba($gl-success, 0.1); + border-color: $gl-success; + } + + &.ci-status-icon-failed { + background-color: rgba($gl-danger, 0.1); + border-color: $gl-danger; + } + + &.ci-status-icon-pending, + &.ci-status-icon-success_with_warnings { + background-color: rgba($gl-warning, 0.1); + border-color: $gl-warning; + } + + &.ci-status-icon-running { + background-color: rgba($blue-normal, 0.1); + border-color: $blue-normal; + } + + &.ci-status-icon-canceled, + &.ci-status-icon-disabled, + &.ci-status-icon-not-found { + background-color: rgba($gl-gray, 0.1); + border-color: $gl-gray; + } + + &.ci-status-icon-created, + &.ci-status-icon-skipped { + background-color: rgba($gray-darkest, 0.1); + border-color: $gray-darkest; + } } } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 08062b85504..2867f5962db 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -441,75 +441,9 @@ } .grouped-pipeline-dropdown { - padding: 0; - width: 186px; left: auto; right: -197px; top: -9px; - - ul { - max-height: 245px; - overflow: auto; - - li:first-child { - padding-top: 8px; - } - - li:last-child { - padding-bottom: 8px; - } - } - - a { - color: $gl-text-color; - padding: 7px 8px 8px; - - &:hover { - background-color: $blue-light-transparent; - border-radius: 3px; - - .ci-status-text { - text-decoration: none; - } - } - } - - svg { - width: 14px; - height: 14px; - } - - .ci-status-text { - width: 112px; - } - - .arrow { - &::before, - &::after { - content: ''; - display: inline-block; - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - top: 18px; - } - - &::before { - left: -5px; - margin-top: -6px; - border-width: 7px 5px 7px 0; - border-right-color: $border-color; - } - - &::after { - left: -4px; - margin-top: -9px; - border-width: 10px 7px 10px 0; - border-right-color: $white-light; - } - } } .badge { @@ -647,6 +581,47 @@ } } +.grouped-pipeline-dropdown { + padding: 0; + width: 186px; + + ul { + max-height: 245px; + overflow: auto; + + li:first-child { + padding-top: 8px; + } + + li:last-child { + padding-bottom: 8px; + } + } + + a { + color: $gl-text-color; + padding: 7px 8px 8px; + + &:hover { + background-color: $blue-light-transparent; + border-radius: 3px; + + .ci-status-text { + text-decoration: none; + } + } + } + + svg { + width: 14px; + height: 14px; + } + + .ci-status-text { + width: 112px; + } +} + .pipeline-actions { border-bottom: none; } diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 8e19752a8a1..b2177c8b5cd 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -43,7 +43,7 @@ module CiStatusHelper status.humanize end - def ci_icon_for_status(status) + def ci_icon_for_status(status, size: 14) if detailed_status?(status) return custom_icon(status.icon) end @@ -70,7 +70,7 @@ module CiStatusHelper 'icon_status_canceled' end - custom_icon(icon_name) + custom_icon(icon_name, size: size) end def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left') diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 9ab7971b56c..dc3b74c73bb 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -1,17 +1,37 @@ - if @pipeline .mr-widget-heading - %w[success success_with_warnings skipped canceled failed running pending].each do |status| - .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) } - = ci_icon_for_status(status) - %span + .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) } + %span.status{ class: "ci-status-icon-#{status}" }= ci_icon_for_status(status) + %span.pipeline-text Pipeline = link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline' = ci_label_for_status(status) - for + + - stages = @pipeline.stages_with_latest_statuses + - stages_status = @pipeline.statuses.latest.stages_status + - stages.each do |stage| + - status = stages_status[stage[0]] + - hasMultipleBuilds = stage[1].count > 1 + - tooltip = "#{stage[0].titleize}: #{status || 'not found'}" + - if status + .stage-container.inline{ id: stage[0], class: "ci-status-icon-#{status} background-icon #{'dropdown grouped' if hasMultipleBuilds}" } + .toggle{ class: "#{'has-tooltip' unless hasMultipleBuilds}", title: tooltip, data: { placement: 'top', toggle: "#{'dropdown' if hasMultipleBuilds}" } } + = ci_icon_for_status(status, size: 24) + - if hasMultipleBuilds + = icon('caret-down', class: 'dropdown-caret') + - if hasMultipleBuilds + .dropdown-menu.grouped-pipeline-dropdown + .arrow + %ul + - stage[1].each do |status| + %li + = render "projects/#{status.to_partial_path}_pipeline", subject: status + + - commit = @merge_request.diff_head_commit = succeed "." do = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace" %span.ci-coverage - - elsif @merge_request.has_ci? - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX - # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API diff --git a/app/views/shared/icons/_icon_status_canceled.svg b/app/views/shared/icons/_icon_status_canceled.svg deleted file mode 100644 index 41a210a8ed9..00000000000 --- a/app/views/shared/icons/_icon_status_canceled.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/shared/icons/_icon_status_canceled.svg.erb b/app/views/shared/icons/_icon_status_canceled.svg.erb new file mode 100644 index 00000000000..0ca1e8aa391 --- /dev/null +++ b/app/views/shared/icons/_icon_status_canceled.svg.erb @@ -0,0 +1 @@ + diff --git a/app/views/shared/icons/_icon_status_created.svg b/app/views/shared/icons/_icon_status_created.svg deleted file mode 100644 index 1f5c3b51b03..00000000000 --- a/app/views/shared/icons/_icon_status_created.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/shared/icons/_icon_status_created.svg.erb b/app/views/shared/icons/_icon_status_created.svg.erb new file mode 100644 index 00000000000..89585f16c78 --- /dev/null +++ b/app/views/shared/icons/_icon_status_created.svg.erb @@ -0,0 +1 @@ + diff --git a/app/views/shared/icons/_icon_status_failed.svg b/app/views/shared/icons/_icon_status_failed.svg deleted file mode 100644 index af267b8938a..00000000000 --- a/app/views/shared/icons/_icon_status_failed.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/shared/icons/_icon_status_failed.svg.erb b/app/views/shared/icons/_icon_status_failed.svg.erb new file mode 100644 index 00000000000..5aa7f75cc64 --- /dev/null +++ b/app/views/shared/icons/_icon_status_failed.svg.erb @@ -0,0 +1 @@ + diff --git a/app/views/shared/icons/_icon_status_pending.svg b/app/views/shared/icons/_icon_status_pending.svg deleted file mode 100644 index 516231d1b44..00000000000 --- a/app/views/shared/icons/_icon_status_pending.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/shared/icons/_icon_status_pending.svg.erb b/app/views/shared/icons/_icon_status_pending.svg.erb new file mode 100644 index 00000000000..b81ce2a58a9 --- /dev/null +++ b/app/views/shared/icons/_icon_status_pending.svg.erb @@ -0,0 +1 @@ + diff --git a/app/views/shared/icons/_icon_status_running.svg b/app/views/shared/icons/_icon_status_running.svg deleted file mode 100644 index d2618bce200..00000000000 --- a/app/views/shared/icons/_icon_status_running.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/shared/icons/_icon_status_running.svg.erb b/app/views/shared/icons/_icon_status_running.svg.erb new file mode 100644 index 00000000000..204578678b5 --- /dev/null +++ b/app/views/shared/icons/_icon_status_running.svg.erb @@ -0,0 +1 @@ + diff --git a/app/views/shared/icons/_icon_status_skipped.svg b/app/views/shared/icons/_icon_status_skipped.svg deleted file mode 100644 index 701f33bcbea..00000000000 --- a/app/views/shared/icons/_icon_status_skipped.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/shared/icons/_icon_status_skipped.svg.erb b/app/views/shared/icons/_icon_status_skipped.svg.erb new file mode 100644 index 00000000000..8b8a9dec950 --- /dev/null +++ b/app/views/shared/icons/_icon_status_skipped.svg.erb @@ -0,0 +1 @@ + diff --git a/app/views/shared/icons/_icon_status_success.svg b/app/views/shared/icons/_icon_status_success.svg deleted file mode 100644 index b7c21ba6971..00000000000 --- a/app/views/shared/icons/_icon_status_success.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/shared/icons/_icon_status_success.svg.erb b/app/views/shared/icons/_icon_status_success.svg.erb new file mode 100644 index 00000000000..c075315a1af --- /dev/null +++ b/app/views/shared/icons/_icon_status_success.svg.erb @@ -0,0 +1,2 @@ +<% size = 14 if local_assigns[:size].nil? %> + diff --git a/app/views/shared/icons/_icon_status_warning.svg b/app/views/shared/icons/_icon_status_warning.svg deleted file mode 100644 index 9191e0050a6..00000000000 --- a/app/views/shared/icons/_icon_status_warning.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/shared/icons/_icon_status_warning.svg.erb b/app/views/shared/icons/_icon_status_warning.svg.erb new file mode 100644 index 00000000000..d0a375f8d88 --- /dev/null +++ b/app/views/shared/icons/_icon_status_warning.svg.erb @@ -0,0 +1 @@ + -- cgit v1.2.1