broken_window 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +45 -0
- data/Rakefile +21 -0
- data/app/assets/javascripts/broken_window/application.js +13 -0
- data/app/assets/stylesheets/broken_window/application.scss +25 -0
- data/app/assets/stylesheets/broken_window/layout.sass +39 -0
- data/app/assets/stylesheets/broken_window/metric.sass +16 -0
- data/app/assets/stylesheets/broken_window/metrics_list.sass +38 -0
- data/app/controllers/broken_window/application_controller.rb +4 -0
- data/app/controllers/broken_window/metrics_controller.rb +48 -0
- data/app/helpers/broken_window/application_helper.rb +4 -0
- data/app/models/broken_window/measured_metric.rb +54 -0
- data/app/models/broken_window/measurement.rb +7 -0
- data/app/models/broken_window/metric.rb +40 -0
- data/app/models/broken_window/metric_status.rb +37 -0
- data/app/services/broken_window/formatters/integer.rb +11 -0
- data/app/services/broken_window/formatters/percentage.rb +11 -0
- data/app/services/broken_window/measurer.rb +11 -0
- data/app/services/broken_window/metric_measurer.rb +26 -0
- data/app/services/broken_window/service.rb +23 -0
- data/app/services/broken_window/status_checker.rb +37 -0
- data/app/views/broken_window/metrics/_error_messages.html.erb +10 -0
- data/app/views/broken_window/metrics/_form.html.erb +35 -0
- data/app/views/broken_window/metrics/_list.html.erb +13 -0
- data/app/views/broken_window/metrics/edit.html.erb +5 -0
- data/app/views/broken_window/metrics/index.html.erb +4 -0
- data/app/views/broken_window/metrics/new.html.erb +5 -0
- data/app/views/broken_window/metrics/show.html.erb +17 -0
- data/app/views/layouts/broken_window/application.html.erb +16 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20160112005320_create_broken_window_metrics.rb +13 -0
- data/db/migrate/20160112010703_create_broken_window_measurements.rb +9 -0
- data/db/migrate/20160419161925_add_parent_id_to_metrics.rb +5 -0
- data/lib/broken_window/engine.rb +5 -0
- data/lib/broken_window/version.rb +3 -0
- data/lib/broken_window.rb +14 -0
- data/lib/tasks/broken_window_tasks.rake +7 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4f486568923c0240a670406f6f39d43106de773f
|
4
|
+
data.tar.gz: c8bc0696766613a6c62aca487087c928e3eb8855
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 62b0209d894eb0d4108689842ac8386673f4905f1f91037b476bb50f092be9b79255c18c4f15704f25df4dea5c87031e6532a24d084bd3143e2adc17533c60e5
|
7
|
+
data.tar.gz: 7a846fb8f52c16f81cb0ff83738bfd93d638af2de767e2ba50b2e1c6f1e95b9e85e050067763e4f93d0471ecadb0947589b33e767ef5935c9ea318b4bb5c6850
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
= BrokenWindow
|
2
|
+
|
3
|
+
This project rocks and uses MIT-LICENSE.
|
4
|
+
|
5
|
+
Installation
|
6
|
+
=============
|
7
|
+
|
8
|
+
Add:
|
9
|
+
|
10
|
+
gem 'broken_window', github: 'webpunch/broken_window'
|
11
|
+
|
12
|
+
to your Gemfile
|
13
|
+
|
14
|
+
Run: rake broken_window:install:migrations && rake db:migrate
|
15
|
+
|
16
|
+
Add to your routes file:
|
17
|
+
|
18
|
+
mount BrokenWindow::Engine => "/status"
|
19
|
+
|
20
|
+
Create config/initializers/broken_window.rb to register your calculators:
|
21
|
+
|
22
|
+
BrokenWindow.register_calculators [
|
23
|
+
BrokenWindow::Calculators::ExampleCalculator
|
24
|
+
]
|
25
|
+
|
26
|
+
Create your calculator:
|
27
|
+
|
28
|
+
|
29
|
+
module BrokenWindow
|
30
|
+
module Calculators
|
31
|
+
class ExampleCalculator
|
32
|
+
def initialize(options = {})
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def call
|
37
|
+
# Thing to be calculated
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'BrokenWindow'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
Bundler::GemHelper.install_tasks
|
21
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,25 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*=
|
14
|
+
*/
|
15
|
+
|
16
|
+
@mixin clearfix {
|
17
|
+
&:after {
|
18
|
+
content: "";
|
19
|
+
display: table;
|
20
|
+
clear: both;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
@import "./*.sass"
|
25
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
a.back-link
|
2
|
+
float: left
|
3
|
+
text-decoration: none
|
4
|
+
color: black
|
5
|
+
font-size: 42px
|
6
|
+
|
7
|
+
.page-actions
|
8
|
+
text-align: center
|
9
|
+
|
10
|
+
a
|
11
|
+
color: black
|
12
|
+
font-size: 42px
|
13
|
+
text-decoration: none
|
14
|
+
|
15
|
+
.error_explanation
|
16
|
+
color: red
|
17
|
+
|
18
|
+
body
|
19
|
+
color: black
|
20
|
+
padding: 10px
|
21
|
+
|
22
|
+
&.fail
|
23
|
+
background-color: red
|
24
|
+
|
25
|
+
&.pass
|
26
|
+
background-color: green
|
27
|
+
|
28
|
+
&.unknown
|
29
|
+
background-color: gray
|
30
|
+
|
31
|
+
&.background-metric
|
32
|
+
&.fail
|
33
|
+
background-color: darken(red, 10%)
|
34
|
+
|
35
|
+
&.pass
|
36
|
+
background-color: darken(green, 10%)
|
37
|
+
|
38
|
+
&.unknown
|
39
|
+
background-color: darken(gray, 10%)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
.metric
|
2
|
+
&.fail
|
3
|
+
background-color: red
|
4
|
+
|
5
|
+
&.pass
|
6
|
+
background-color: green
|
7
|
+
|
8
|
+
&.unknown
|
9
|
+
background-color: grey
|
10
|
+
|
11
|
+
.metrics-list
|
12
|
+
margin: 50px
|
13
|
+
|
14
|
+
a.metric
|
15
|
+
@include clearfix()
|
16
|
+
padding: 10px 20px
|
17
|
+
display: block
|
18
|
+
color: black
|
19
|
+
text-decoration: none
|
20
|
+
font-size: 20px
|
21
|
+
margin-bottom: 5px
|
22
|
+
|
23
|
+
.name
|
24
|
+
float: left
|
25
|
+
font-weight: bold
|
26
|
+
|
27
|
+
.value
|
28
|
+
float: right
|
29
|
+
|
30
|
+
.stale-time
|
31
|
+
background-color: #333
|
32
|
+
color: #CCCCCC
|
33
|
+
display: inline-block
|
34
|
+
padding: 0 5px
|
35
|
+
border-radius: 4px
|
36
|
+
font-weight: normal
|
37
|
+
float: left
|
38
|
+
margin-right: 10px
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module BrokenWindow
|
2
|
+
class MetricsController < ApplicationController
|
3
|
+
layout 'broken_window/application'
|
4
|
+
|
5
|
+
def index
|
6
|
+
@metrics = Metric.roots.map {|metric| MeasuredMetric.new(metric)}
|
7
|
+
@status = MetricStatus.combine(@metrics.map(&:status))
|
8
|
+
@body_class = 'background-metric'
|
9
|
+
end
|
10
|
+
|
11
|
+
def show
|
12
|
+
@metric = MeasuredMetric.find(params[:id])
|
13
|
+
@status = @metric.status
|
14
|
+
end
|
15
|
+
|
16
|
+
def new
|
17
|
+
@metric = Metric.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
@metric = Metric.new(metric_params)
|
22
|
+
if @metric.save
|
23
|
+
redirect_to @metric
|
24
|
+
else
|
25
|
+
render action: :new
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def edit
|
30
|
+
@metric = Metric.find(params[:id])
|
31
|
+
end
|
32
|
+
|
33
|
+
def update
|
34
|
+
@metric = Metric.find(params[:id])
|
35
|
+
if @metric.update_attributes(metric_params)
|
36
|
+
redirect_to @metric
|
37
|
+
else
|
38
|
+
render action: :edit
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def metric_params
|
45
|
+
params[:metric].permit(:name, :threshold, :value_type, :calculator, :arguments, :threshold_type, :parent_id)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module BrokenWindow
|
2
|
+
class MeasuredMetric
|
3
|
+
attr_reader :metric
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def find(param)
|
7
|
+
new(BrokenWindow::Metric.find(param))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(metric)
|
12
|
+
@metric = metric
|
13
|
+
end
|
14
|
+
|
15
|
+
def measurement
|
16
|
+
@measurement ||= @metric.latest_measurement unless container?
|
17
|
+
end
|
18
|
+
|
19
|
+
def status
|
20
|
+
@status ||= calculate_status
|
21
|
+
end
|
22
|
+
|
23
|
+
def old?
|
24
|
+
measurement && measurement.created_at < 3.days.ago
|
25
|
+
end
|
26
|
+
|
27
|
+
def children
|
28
|
+
@metric.children.map do |metric|
|
29
|
+
self.class.new(metric)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.model_name
|
34
|
+
Metric.model_name
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_model
|
38
|
+
metric
|
39
|
+
end
|
40
|
+
|
41
|
+
delegate :name, :to_param, :threshold, :value_type, :threshold_type, :container?, to: :metric
|
42
|
+
delegate :value, to: :measurement, allow_nil: true
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def calculate_status
|
47
|
+
if container?
|
48
|
+
MetricStatus.combine(children.map(&:status))
|
49
|
+
else
|
50
|
+
StatusChecker.check_status(@metric, measurement)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module BrokenWindow
|
2
|
+
class Metric < ActiveRecord::Base
|
3
|
+
VALUE_TYPES = %w(percentage integer)
|
4
|
+
THRESHOLD_TYPES = %w(min max)
|
5
|
+
VALID_CLASS_NAME = /\A[A-Z][a-zA-Z_0-9\:]*\z/
|
6
|
+
|
7
|
+
has_many :measurements
|
8
|
+
|
9
|
+
validates :name, presence: true, length: {maximum: 255}
|
10
|
+
validates :calculator, format: {
|
11
|
+
with: VALID_CLASS_NAME, allow_blank: true, message: "must be a valid class name" }
|
12
|
+
validates :value_type, inclusion: {in: VALUE_TYPES}
|
13
|
+
validates :threshold, presence: true, numericality: {allow_blank: true}, unless: :container?
|
14
|
+
validates :threshold_type, inclusion: {in: THRESHOLD_TYPES}
|
15
|
+
|
16
|
+
acts_as_tree order: "name"
|
17
|
+
|
18
|
+
def latest_measurement
|
19
|
+
measurements.latest_first.first
|
20
|
+
end
|
21
|
+
|
22
|
+
def arguments_hash
|
23
|
+
if arguments.present?
|
24
|
+
YAML::load(arguments)
|
25
|
+
else
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.format_value(value, value_type)
|
31
|
+
formatter = "BrokenWindow::Formatters::#{value_type.camelize}".constantize.new
|
32
|
+
formatter.call(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def container?
|
36
|
+
calculator.blank?
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module BrokenWindow
|
2
|
+
class MetricStatus
|
3
|
+
NAMES = [:fail, :unknown, :pass]
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def pass
|
7
|
+
new(:pass)
|
8
|
+
end
|
9
|
+
|
10
|
+
def unknown
|
11
|
+
new(:unknown)
|
12
|
+
end
|
13
|
+
|
14
|
+
def fail
|
15
|
+
new(:fail)
|
16
|
+
end
|
17
|
+
|
18
|
+
def combine(states)
|
19
|
+
states.min
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :value
|
24
|
+
|
25
|
+
def initialize(name)
|
26
|
+
@value = NAMES.index(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
NAMES[@value].to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
def <=>(other)
|
34
|
+
value <=> other.value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module BrokenWindow
|
2
|
+
class MetricMeasurer
|
3
|
+
include Service
|
4
|
+
|
5
|
+
def initialize(metric)
|
6
|
+
@metric = metric
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
value = calculator.call
|
11
|
+
@metric.measurements.create!(value: value)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def calculator
|
17
|
+
calculator_class.new(ActiveSupport::HashWithIndifferentAccess.new(@metric.arguments_hash))
|
18
|
+
end
|
19
|
+
|
20
|
+
def calculator_class
|
21
|
+
raise ArgumentError unless @metric.calculator
|
22
|
+
|
23
|
+
@metric.calculator.constantize
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module BrokenWindow
|
2
|
+
module Service
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
|
6
|
+
# From http://brewhouse.io/blog/2014/04/30/gourmet-service-objects.html
|
7
|
+
# Possible responses
|
8
|
+
# 1: Fail loudly
|
9
|
+
# 2: Return ActiveRecord results
|
10
|
+
# 3: Response object
|
11
|
+
# Some services have several outcomes and complex error handling. They return a response object which responds to success? and error(s).
|
12
|
+
def call
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def call(*args)
|
19
|
+
new(*args).call
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module BrokenWindow
|
2
|
+
class StatusChecker
|
3
|
+
class << self
|
4
|
+
def check_status(metric, measurement = nil)
|
5
|
+
measurement ||= metric.latest_measurement
|
6
|
+
if measurement
|
7
|
+
if value_passes_threshold?(measurement.value, metric.threshold, metric.threshold_type)
|
8
|
+
BrokenWindow::MetricStatus.pass
|
9
|
+
else
|
10
|
+
BrokenWindow::MetricStatus.fail
|
11
|
+
end
|
12
|
+
else
|
13
|
+
BrokenWindow::MetricStatus.unknown
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def value_passes_threshold?(value, threshold, threshold_type)
|
20
|
+
if value
|
21
|
+
value.send(comparison_method(threshold_type), threshold)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def comparison_method(threshold_type)
|
26
|
+
case threshold_type
|
27
|
+
when 'min'
|
28
|
+
return '>=';
|
29
|
+
when 'max'
|
30
|
+
return '<=';
|
31
|
+
else
|
32
|
+
raise "unknown threshold_type #{threshold_type.inspect}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<% if target.errors.any? %>
|
2
|
+
<div class="error_explanation">
|
3
|
+
<h2><%= pluralize(target.errors.count, "error") %> prevented this record from being saved:</h2>
|
4
|
+
<ul>
|
5
|
+
<% target.errors.full_messages.each do |msg| %>
|
6
|
+
<li><%= msg %></li>
|
7
|
+
<% end %>
|
8
|
+
</ul>
|
9
|
+
</div>
|
10
|
+
<% end %>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<%= form_for @metric do |f| %>
|
2
|
+
<%= render "error_messages", :target => @metric %>
|
3
|
+
|
4
|
+
<div class="form-group">
|
5
|
+
<%= f.label :name %>
|
6
|
+
<%= f.text_field :name, class: 'form-control' %>
|
7
|
+
</div>
|
8
|
+
<div class="form-group">
|
9
|
+
<%= f.label :calculator %>
|
10
|
+
<%= f.select :calculator, BrokenWindow.calculators.map(&:name), {include_blank: true}, class: 'form-control' %>
|
11
|
+
</div>
|
12
|
+
<div class="form-group">
|
13
|
+
<%= f.label :value_type %>
|
14
|
+
<%= f.select :value_type, BrokenWindow::Metric::VALUE_TYPES, class: 'form-control' %>
|
15
|
+
</div>
|
16
|
+
<div class="form-group">
|
17
|
+
<%= f.label :threshold %>
|
18
|
+
<%= f.text_field :threshold, class: 'form-control' %>
|
19
|
+
</div>
|
20
|
+
<div class="form-group">
|
21
|
+
<%= f.label :threshold_type %>
|
22
|
+
<%= f.select :threshold_type, BrokenWindow::Metric::THRESHOLD_TYPES, class: 'form-control' %>
|
23
|
+
</div>
|
24
|
+
<div class="form-group">
|
25
|
+
<%= f.label :arguments %>
|
26
|
+
<%= f.text_field :arguments, class: 'form-control' %>
|
27
|
+
</div>
|
28
|
+
<div class="form-group">
|
29
|
+
<%= f.label :parent_id %>
|
30
|
+
<%= f.text_field :parent_id, class: 'form-control' %>
|
31
|
+
</div>
|
32
|
+
<button type="submit" class="btn btn-default">Submit</button>
|
33
|
+
or
|
34
|
+
<%= link_to 'cancel', root_path %>
|
35
|
+
<% end %>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<div class="metrics-list">
|
2
|
+
<% metrics.each do |metric| %>
|
3
|
+
<%= link_to(metric, class: "metric #{metric.status}") do %>
|
4
|
+
<% if metric.old? %>
|
5
|
+
<span class="stale-time">
|
6
|
+
<%= time_ago_in_words(metric.measurement.created_at) %> old
|
7
|
+
</span>
|
8
|
+
<% end %>
|
9
|
+
<span class="name"><%= metric.name %></span>
|
10
|
+
<div class="value"><%= BrokenWindow::Metric.format_value metric.value, metric.value_type %></div>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
13
|
+
</div>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%= link_to '', metrics_path, class: 'back-link fa fa-level-up' %>
|
2
|
+
|
3
|
+
<div class="show-metric">
|
4
|
+
<h1><%= @metric.name %></h1>
|
5
|
+
<%- unless @metric.container? %>
|
6
|
+
<div class="value"><%= BrokenWindow::Metric.format_value @metric.value, @metric.value_type %></div>
|
7
|
+
<div class="required">
|
8
|
+
<div class="minimum"><%= @metric.threshold_type %> <%= BrokenWindow::Metric.format_value @metric.threshold, @metric.value_type %></div>
|
9
|
+
</div>
|
10
|
+
<%- end %>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<%= render partial: 'list', locals: {metrics: @metric.children} %>
|
14
|
+
|
15
|
+
<div class="page-actions">
|
16
|
+
<%= link_to '', edit_metric_path(@metric), class: 'fa fa-pencil-square-o' %>
|
17
|
+
</div>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>BrokenWindow</title>
|
5
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
|
6
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
|
7
|
+
<%= stylesheet_link_tag "broken_window/application", media: "all" %>
|
8
|
+
<%= javascript_include_tag "broken_window/application" %>
|
9
|
+
<%= csrf_meta_tags %>
|
10
|
+
</head>
|
11
|
+
<body class="<%= [@status.to_s, @body_class].compact.join(' ') %>">
|
12
|
+
|
13
|
+
<%= yield %>
|
14
|
+
|
15
|
+
</body>
|
16
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateBrokenWindowMetrics < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :broken_window_metrics do |t|
|
4
|
+
t.string :name
|
5
|
+
t.string :calculator
|
6
|
+
t.string :value_type
|
7
|
+
t.text :arguments
|
8
|
+
t.decimal :threshold
|
9
|
+
t.string :threshold_type
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: broken_window
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Craig Ambrose
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.1.12
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.1.12
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: acts_as_tree
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: combustion
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Calculate metrics and display a simple broken/not broken dashboard.
|
84
|
+
email:
|
85
|
+
- craig.ambrose@enspiral.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- MIT-LICENSE
|
91
|
+
- README.rdoc
|
92
|
+
- Rakefile
|
93
|
+
- app/assets/javascripts/broken_window/application.js
|
94
|
+
- app/assets/stylesheets/broken_window/application.scss
|
95
|
+
- app/assets/stylesheets/broken_window/layout.sass
|
96
|
+
- app/assets/stylesheets/broken_window/metric.sass
|
97
|
+
- app/assets/stylesheets/broken_window/metrics_list.sass
|
98
|
+
- app/controllers/broken_window/application_controller.rb
|
99
|
+
- app/controllers/broken_window/metrics_controller.rb
|
100
|
+
- app/helpers/broken_window/application_helper.rb
|
101
|
+
- app/models/broken_window/measured_metric.rb
|
102
|
+
- app/models/broken_window/measurement.rb
|
103
|
+
- app/models/broken_window/metric.rb
|
104
|
+
- app/models/broken_window/metric_status.rb
|
105
|
+
- app/services/broken_window/formatters/integer.rb
|
106
|
+
- app/services/broken_window/formatters/percentage.rb
|
107
|
+
- app/services/broken_window/measurer.rb
|
108
|
+
- app/services/broken_window/metric_measurer.rb
|
109
|
+
- app/services/broken_window/service.rb
|
110
|
+
- app/services/broken_window/status_checker.rb
|
111
|
+
- app/views/broken_window/metrics/_error_messages.html.erb
|
112
|
+
- app/views/broken_window/metrics/_form.html.erb
|
113
|
+
- app/views/broken_window/metrics/_list.html.erb
|
114
|
+
- app/views/broken_window/metrics/edit.html.erb
|
115
|
+
- app/views/broken_window/metrics/index.html.erb
|
116
|
+
- app/views/broken_window/metrics/new.html.erb
|
117
|
+
- app/views/broken_window/metrics/show.html.erb
|
118
|
+
- app/views/layouts/broken_window/application.html.erb
|
119
|
+
- config/routes.rb
|
120
|
+
- db/migrate/20160112005320_create_broken_window_metrics.rb
|
121
|
+
- db/migrate/20160112010703_create_broken_window_measurements.rb
|
122
|
+
- db/migrate/20160419161925_add_parent_id_to_metrics.rb
|
123
|
+
- lib/broken_window.rb
|
124
|
+
- lib/broken_window/engine.rb
|
125
|
+
- lib/broken_window/version.rb
|
126
|
+
- lib/tasks/broken_window_tasks.rake
|
127
|
+
homepage: http://app.webpunch12.com
|
128
|
+
licenses:
|
129
|
+
- MIT
|
130
|
+
metadata: {}
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
requirements: []
|
146
|
+
rubyforge_project:
|
147
|
+
rubygems_version: 2.2.2
|
148
|
+
signing_key:
|
149
|
+
specification_version: 4
|
150
|
+
summary: Show me whether the app is broken or not
|
151
|
+
test_files: []
|