Commit 26dde5f5 authored by Taurie Davis, Simon Knox and Adam Niedzielski's avatar Taurie Davis, Simon Knox and Adam Niedzielski Committed by Adam Niedzielski
Browse files

Add Conversational Development Index page to admin panel

parent c72abcef
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 120" enable-background="new 0 0 12 120">
<path d="m12 6c0-3.309-2.691-6-6-6s-6 2.691-6 6c0 2.967 2.167 5.431 5 5.91v108.09h2v-108.09c2.833-.479 5-2.943 5-5.91m-6 4c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4"/>
</svg>
......@@ -393,6 +393,9 @@ import ShortcutsBlob from './shortcuts_blob';
case 'users:show':
new UserCallout();
break;
case 'admin:conversational_development_index:show':
new UserCallout();
break;
case 'snippets:show':
new LineHighlighter();
new BlobViewer();
......
import Cookies from 'js-cookie';
const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
export default class UserCallout {
constructor() {
this.isCalloutDismissed = Cookies.get(USER_CALLOUT_COOKIE);
this.userCalloutBody = $('.user-callout');
constructor(className = 'user-callout') {
this.userCalloutBody = $(`.${className}`);
this.cookieName = this.userCalloutBody.data('uid');
this.isCalloutDismissed = Cookies.get(this.cookieName);
this.init();
}
......@@ -18,7 +17,7 @@ export default class UserCallout {
dismissCallout(e) {
const $currentTarget = $(e.currentTarget);
Cookies.set(USER_CALLOUT_COOKIE, 'true', { expires: 365 });
Cookies.set(this.cookieName, 'true', { expires: 365 });
if ($currentTarget.hasClass('close')) {
this.userCalloutBody.remove();
......
......@@ -569,3 +569,10 @@ $filter-value-selected-color: #d7d7d7;
Animation Functions
*/
$dropdown-animation-timing: cubic-bezier(0.23, 1, 0.32, 1);
/*
Convdev Index
*/
$color-high-score: $green-400;
$color-average-score: $orange-400;
$color-low-score: $red-400;
$space-between-cards: 8px;
.convdev-empty svg {
margin: 64px auto 32px;
max-width: 420px;
}
.convdev-header {
margin-top: $gl-padding;
margin-bottom: $gl-padding;
padding: 0 4px;
display: flex;
align-items: center;
.convdev-header-title {
font-size: 48px;
line-height: 1;
margin: 0;
}
.convdev-header-subtitle {
font-size: 22px;
line-height: 1;
color: $gl-text-color-secondary;
margin-left: 8px;
font-weight: 500;
a {
font-size: 18px;
color: $gl-text-color-secondary;
&:hover {
color: $blue-500;
}
}
}
}
.convdev-cards {
display: flex;
justify-content: center;
@media (max-width: $screen-lg-min) {
flex-wrap: wrap;
}
}
.convdev-card-wrapper {
display: flex;
flex-direction: column;
align-items: stretch;
text-align: center;
width: 10%;
border-color: $border-color;
margin: 0 0 32px;
padding: $space-between-cards / 2;
position: relative;
@media (max-width: $screen-lg-min) {
width: 16.667%;
.convdev-card-title {
max-width: 100px;
margin: $gl-padding auto auto;
}
.card-scores {
margin: $gl-padding 24px;
}
}
@media (max-width: $screen-md-min) {
width: 20%;
}
@media (max-width: $screen-sm-min) {
width: 25%;
}
@media (max-width: $screen-xs-min) {
width: 50%;
}
}
.convdev-card {
border: solid 1px $border-color;
border-top-width: 3px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.convdev-card-low {
border-top-color: $color-low-score;
.card-score-big {
background-color: $red-25;
}
}
.convdev-card-average {
border-top-color: $color-average-score;
.card-score-big {
background-color: $orange-25;
}
}
.convdev-card-high {
border-top-color: $color-high-score;
.card-score-big {
background-color: $green-25;
}
}
.convdev-card-title {
margin-top: $gl-padding;
margin-bottom: auto;
h3 {
font-size: 14px;
margin: 0 0 2px;
}
.text-light {
font-size: 13px;
line-height: 1.25;
color: $gl-text-color-secondary;
}
}
.card-scores {
display: flex;
justify-content: space-around;
align-items: center;
margin: $gl-padding $gl-btn-padding;
line-height: 1;
}
.card-score {
color: $gl-text-color-secondary;
.card-score-name {
font-size: 13px;
margin-top: 4px;
}
}
.card-score-value {
font-size: 16px;
color: $gl-text-color;
font-weight: 500;
}
.card-score-big {
border-top: 2px solid $border-color;
border-bottom: 1px solid $border-color;
font-size: 22px;
padding: 10px 0;
font-weight: 500;
}
.card-buttons {
display: flex;
justify-content: stretch;
> * {
font-size: 16px;
color: $gl-text-color-secondary;
padding: 10px;
flex-grow: 1;
&:hover {
background-color: $border-color;
color: $gl-text-color;
}
+ * {
border-left: solid 1px $border-color;
}
}
}
.convdev-steps {
margin-top: $gl-padding;
height: 1px;
min-width: 100%;
justify-content: space-around;
position: relative;
background: $border-color;
@media (max-width: $screen-lg-min) {
display: none;
}
}
.convdev-step {
$step-positions: 5% 10% 30% 42% 48% 55% 60% 70% 75% 90%;
@each $pos in $step-positions {
$i: index($step-positions, $pos);
&:nth-child(#{$i}) {
left: $pos;
}
}
position: absolute;
transform-origin: 75% 50%;
padding: 8px;
height: 50px;
width: 50px;
border-radius: 3px;
display: flex;
flex-direction: column;
align-items: center;
border: solid 1px $border-color;
background: $white-light;
transform: translate(-50%, -50%);
color: $gl-text-color-secondary;
fill: $gl-text-color-secondary;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&:hover {
padding: 8px 10px;
fill: currentColor;
z-index: 100;
height: auto;
width: auto;
.convdev-step-title {
max-height: 2em;
opacity: 1;
transition: opacity 0.2s;
}
svg {
transform: scale(1.5);
margin: $gl-btn-padding;
}
}
svg {
transition: transform 0.1s;
width: 30px;
height: 30px;
}
}
.convdev-step-title {
max-height: 0;
opacity: 0;
text-transform: uppercase;
margin: $gl-vert-padding 0 0;
text-align: center;
font-size: 12px;
}
.convdev-high-score {
color: $color-high-score;
}
.convdev-average-score {
color: $color-average-score;
}
.convdev-low-score {
color: $color-low-score;
}
......@@ -287,6 +287,7 @@ table.u2f-registrations {
.user-callout {
margin: 0 auto;
max-width: $screen-lg-min;
.bordered-box {
border: 1px solid $blue-300;
......@@ -295,14 +296,15 @@ table.u2f-registrations {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.landing {
margin-top: $gl-padding;
margin-bottom: $gl-padding;
padding: 32px;
.close {
position: absolute;
top: 20px;
right: 20px;
opacity: 1;
......@@ -330,11 +332,20 @@ table.u2f-registrations {
height: 110px;
vertical-align: top;
}
&.convdev {
margin: 0 0 0 30px;
svg {
height: 127px;
}
}
}
.user-callout-copy {
display: inline-block;
vertical-align: top;
max-width: 570px;
}
}
......@@ -348,12 +359,20 @@ table.u2f-registrations {
.landing {
.svg-container,
.user-callout-copy {
margin: 0;
margin: 0 auto;
display: block;
svg {
height: 75px;
}
&.convdev {
margin: $gl-padding auto 0;
svg {
height: 120px;
}
}
}
}
}
......
class Admin::ConversationalDevelopmentIndexController < Admin::ApplicationController
def show
@metric = ConversationalDevelopmentIndex::Metric.order(:created_at).last&.present
end
end
......@@ -275,8 +275,8 @@ def active_when(condition)
'active' if condition
end
def show_user_callout?
cookies[:user_callout_dismissed].nil?
def show_callout?(name)
cookies[name] != 'true'
end
def linkedin_url(user)
......
module ConversationalDevelopmentIndexHelper
def score_level(score)
if score < 33.33
'low'
elsif score < 66.66
'average'
else
'high'
end
end
def format_score(score)
precision = score < 1 ? 2 : 1
number_with_precision(score, precision: precision)
end
end
module ConversationalDevelopmentIndex
class Card
attr_accessor :metric, :title, :description, :feature, :blog, :docs
def initialize(metric:, title:, description:, feature:, blog:, docs: nil)
self.metric = metric
self.title = title
self.description = description
self.feature = feature
self.blog = blog
self.docs = docs
end
def instance_score
metric.instance_score(feature)
end
def leader_score
metric.leader_score(feature)
end
def percentage_score
metric.percentage_score(feature)
end
end
end
module ConversationalDevelopmentIndex
class IdeaToProductionStep
attr_accessor :metric, :title, :features
def initialize(metric:, title:, features:)
self.metric = metric
self.title = title
self.features = features
end
def percentage_score
sum = features.map do |feature|
metric.percentage_score(feature)
end.inject(:+)
sum / features.size.to_f
end
end
end
module ConversationalDevelopmentIndex
class Metric < ActiveRecord::Base
include Presentable
self.table_name = 'conversational_development_index_metrics'
def instance_score(feature)
self["instance_#{feature}"]
end
def leader_score(feature)
self["leader_#{feature}"]
end
def percentage_score(feature)
return 100 if leader_score(feature).zero?
100 * instance_score(feature) / leader_score(feature)
end
end
end
module ConversationalDevelopmentIndex
class MetricPresenter < Gitlab::View::Presenter::Simple
def cards
[
Card.new(
metric: subject,
title: 'Issues',
description: 'created per active user',
feature: 'issues',
blog: 'https://www2.deloitte.com/content/dam/Deloitte/se/Documents/technology-media-telecommunications/deloitte-digital-collaboration.pdf'
),
Card.new(
metric: subject,
title: 'Comments',
description: 'created per active user',
feature: 'notes',
blog: 'http://conversationaldevelopment.com/why/'
),
Card.new(
metric: subject,
title: 'Milestones',
description: 'created per active user',
feature: 'milestones',
blog: 'http://conversationaldevelopment.com/shorten-cycle/',
docs: help_page_path('user/project/milestones/index')
),
Card.new(
metric: subject,
title: 'Boards',
description: 'created per active user',
feature: 'boards',
blog: 'http://jpattonassociates.com/user-story-mapping/',
docs: help_page_path('user/project/issue_board')
),
Card.new(
metric: subject,
title: 'Merge Requests',
description: 'per active user',
feature: 'merge_requests',
blog: 'https://8thlight.com/blog/uncle-bob/2013/02/01/The-Humble-Craftsman.html',
docs: help_page_path('user/project/merge_requests/index')
),
Card.new(
metric: subject,
title: 'Pipelines',
description: 'created per active user',
feature: 'ci_pipelines',
blog: 'https://martinfowler.com/bliki/ContinuousDelivery.html',
docs: help_page_path('ci/README')
),
Card.new(
metric: subject,
title: 'Environments',
description: 'created per active user',
feature: 'environments',
blog: 'https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/',
docs: help_page_path('ci/environments')
),
Card.new(
metric: subject,
title: 'Deployments',
description: 'created per active user',
feature: 'deployments',
blog: 'https://puppet.com/blog/continuous-delivery-vs-continuous-deployment-what-s-diff'
),
Card.new(
metric: subject,
title: 'Monitoring',
description: 'fraction of all projects',
feature: 'projects_prometheus_active',
blog: 'https://prometheus.io/docs/introduction/overview/',
docs: help_page_path('user/project/integrations/prometheus')
),
Card.new(
metric: subject,
title: 'Service Desk',
description: 'issues created per active user',
feature: 'service_desk_issues',
blog: 'http://blogs.forrester.com/kate_leggett/17-01-30-top_trends_for_customer_service_in_2017_operations_become_smarter_and_more_strategic',
docs: 'https://docs.gitlab.com/ee/user/project/service_desk.html'
)
]
end
def idea_to_production_steps
[
IdeaToProductionStep.new(
metric: subject,
title: 'Idea',
features: %w(issues)
),