houston-core 0.6.3 → 0.7.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +53 -63
- data/app/adapters/houston/adapters/error_tracker/errbit_adapter.rb +2 -2
- data/app/adapters/houston/adapters/ticket_tracker/github_adapter.rb +1 -1
- data/app/adapters/houston/adapters/ticket_tracker/unfuddle_adapter.rb +5 -5
- data/app/adapters/houston/adapters/version_control/git_adapter.rb +4 -4
- data/app/assets/javascripts/{app → houston/app}/boot.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/emoji.coffee.erb +0 -0
- data/app/assets/javascripts/{app → houston/app}/infinite_scroll.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/models/commit.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/models/release.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/models/task.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/models/tester.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/models/testing_note.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/models/ticket.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/models/user.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/mousetrap-bind-scoped.js +0 -0
- data/app/assets/javascripts/{app → houston/app}/releases.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/table_row_expander.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/ticket_tracker_refresh.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/_show_sprint_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/_tickets_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/all_tickets_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/commit_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/edit_sprint_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/edit_ticket_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/find_or_create_ticket_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/keyboard_shortcuts_modal.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/new_ticket_modal.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/new_ticket_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/problems_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/testing_note_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/testing_report_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/testing_ticket_view.coffee +0 -0
- data/app/assets/javascripts/{app → houston/app}/views/ticket_modal_view.coffee +0 -0
- data/app/assets/javascripts/{application.js → houston/application.js} +1 -1
- data/app/assets/javascripts/{core → houston/core}/app.coffee +0 -0
- data/app/assets/javascripts/{core → houston/core}/burndown_chart.coffee +0 -0
- data/app/assets/javascripts/{core → houston/core}/core_ext/array.coffee +0 -0
- data/app/assets/javascripts/{core → houston/core}/core_ext/date.coffee +0 -0
- data/app/assets/javascripts/{core → houston/core}/core_ext/number.coffee +0 -0
- data/app/assets/javascripts/{core → houston/core}/errors.coffee +1 -1
- data/app/assets/javascripts/{core → houston/core}/handlebars_helpers.coffee +0 -0
- data/app/assets/javascripts/{core → houston/core}/helpers.coffee +0 -0
- data/app/assets/javascripts/{core → houston/core}/jquery_extensions.coffee +5 -3
- data/app/assets/javascripts/{core → houston/core}/stacked_area_graph.coffee +0 -0
- data/app/assets/javascripts/{core → houston/core}/stacked_bar_graph.coffee +0 -0
- data/app/assets/javascripts/{dashboard.js → houston/dashboard.js} +1 -1
- data/app/assets/javascripts/{dashboard → houston/dashboard}/refresher.coffee +0 -0
- data/app/assets/javascripts/{vendor.js → houston/vendor.js} +0 -0
- data/app/assets/stylesheets/{application.css → houston/application.css} +0 -0
- data/app/assets/stylesheets/{application → houston/application}/ansi.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/commit.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/emoji.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/exceptions.scss +1 -1
- data/app/assets/stylesheets/{application → houston/application}/find_or_create_ticket.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/follow_up.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/forms.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/freight_train.css.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/full_screen.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/github_repos.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/infinite_scroll.scss +0 -0
- data/app/assets/stylesheets/houston/application/jobs.scss +5 -0
- data/app/assets/stylesheets/{application → houston/application}/keyboard_shortcuts.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/markdown.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/mobile.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/modals.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/navigation.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/new_ticket_view.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/omnibar.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/project_banner_buttons.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/project_tiles.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/projects.css.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/pull_requests.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/queue.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/release_form.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/releases.scss +1 -1
- data/app/assets/stylesheets/{application → houston/application}/sortable_table.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/sprint.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/tables.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/test.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/test_run.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/testing_report.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/ticket.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/ticket_modal.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/tickets.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/timeline.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/tips.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/typeahead.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/uploading.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/user_wall.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/users.scss +0 -0
- data/app/assets/stylesheets/{application → houston/application}/welcome.scss +0 -0
- data/app/assets/stylesheets/{core → houston/core}/alerts.scss +0 -0
- data/app/assets/stylesheets/{core → houston/core}/avatars.scss +0 -0
- data/app/assets/stylesheets/{core → houston/core}/burndown_chart.scss +0 -0
- data/app/assets/stylesheets/{core → houston/core}/colors.scss.erb +0 -0
- data/app/assets/stylesheets/{core → houston/core}/misc.scss +3 -0
- data/app/assets/stylesheets/{core → houston/core}/octicons-icons.scss +0 -0
- data/app/assets/stylesheets/{core → houston/core}/octicons.scss.erb +0 -0
- data/app/assets/stylesheets/{core → houston/core}/overrides.scss +0 -0
- data/app/assets/stylesheets/{core → houston/core}/roboto.scss.erb +0 -0
- data/app/assets/stylesheets/{core → houston/core}/scores.scss +0 -0
- data/app/assets/stylesheets/{dashboard.css → houston/dashboard.css} +0 -0
- data/app/assets/stylesheets/{dashboard → houston/dashboard}/dashboard.scss +0 -0
- data/app/assets/stylesheets/{print.css.scss → houston/print.css.scss} +0 -0
- data/app/assets/stylesheets/{variables.scss → houston/variables.scss} +0 -0
- data/app/assets/stylesheets/{vendor.css → houston/vendor.css} +0 -0
- data/app/concerns/houston/props.rb +96 -0
- data/app/concerns/project_adapter.rb +1 -1
- data/app/controllers/api/v1/measurements_controller.rb +32 -0
- data/app/controllers/application_controller.rb +1 -1
- data/app/controllers/authorizations_controller.rb +61 -0
- data/app/controllers/github/pulls_controller.rb +1 -1
- data/app/controllers/jobs_controller.rb +30 -1
- data/app/controllers/oauth/providers_controller.rb +45 -0
- data/app/controllers/project_options_controller.rb +2 -2
- data/app/controllers/projects_controller.rb +3 -3
- data/app/controllers/releases_controller.rb +3 -3
- data/app/controllers/user_options_controller.rb +2 -2
- data/app/controllers/users_controller.rb +1 -2
- data/app/helpers/application_helper.rb +49 -3
- data/app/helpers/email_helper.rb +19 -1
- data/app/helpers/release_helper.rb +1 -1
- data/app/helpers/ticket_helper.rb +0 -32
- data/app/interactors/cache_key_dependencies.rb +3 -8
- data/app/mailers/view_mailer.rb +8 -8
- data/app/models/authorization.rb +54 -0
- data/app/models/commit.rb +1 -1
- data/app/models/error.rb +14 -0
- data/app/models/github/comment_event.rb +4 -4
- data/app/models/github/pull_request.rb +39 -17
- data/app/models/github/pull_request_event.rb +1 -7
- data/app/models/job.rb +81 -0
- data/app/models/measurement.rb +2 -1
- data/app/models/oauth/provider.rb +35 -0
- data/app/models/project.rb +12 -3
- data/app/models/slackdown.rb +1 -1
- data/app/models/user.rb +28 -35
- data/app/presenters/measurements_presenter.rb +26 -0
- data/app/presenters/project_presenter.rb +1 -1
- data/app/views/authorizations/_form.html.erb +33 -0
- data/app/views/authorizations/edit.html.erb +7 -0
- data/app/views/authorizations/granted.html.erb +7 -0
- data/app/views/authorizations/index.html.erb +47 -0
- data/app/views/authorizations/new.html.erb +7 -0
- data/app/views/configuration_error/_no_ticket_tracker.html.erb +1 -1
- data/app/views/github/pulls/index.html.erb +5 -1
- data/app/views/jobs/index.html.erb +72 -0
- data/app/views/jobs/show.html.erb +25 -32
- data/app/views/layouts/_navigation.html.erb +1 -1
- data/app/views/layouts/application.html.erb +14 -15
- data/app/views/layouts/dashboard.html.erb +9 -10
- data/app/views/layouts/minimal.html.erb +14 -15
- data/app/views/layouts/naked.html.erb +52 -0
- data/app/views/layouts/naked_dashboard.html.erb +9 -10
- data/app/views/oauth/providers/_form.html.erb +54 -0
- data/app/views/oauth/providers/edit.html.erb +7 -0
- data/app/views/oauth/providers/index.html.erb +41 -0
- data/app/views/oauth/providers/new.html.erb +7 -0
- data/app/views/projects/_form.html.erb +8 -9
- data/app/views/projects/index.html.erb +1 -1
- data/app/views/users/_form.html.erb +20 -19
- data/bin/rails +1 -1
- data/config/application.rb +11 -10
- data/config/boot.rb +1 -2
- data/config/environments/development.rb +8 -6
- data/config/environments/production.rb +11 -16
- data/config/environments/test.rb +5 -5
- data/config/initializers/assets.rb +17 -7
- data/config/initializers/cookies_serializer.rb +1 -1
- data/config/initializers/houston_async.rb +22 -4
- data/config/initializers/houston_report_exception.rb +16 -11
- data/config/initializers/houston_scheduler.rb +1 -1
- data/config/initializers/houston_try.rb +12 -0
- data/config/routes.rb +29 -19
- data/db/fixtures/projects.yml +2 -2
- data/db/migrate/20160317140151_remove_limit_from_users_invitation_token.rb +9 -0
- data/db/migrate/20160419230411_create_oauth_providers.rb +14 -0
- data/db/migrate/20160420000616_create_authorizations.rb +16 -0
- data/db/migrate/20160507135209_create_jobs.rb +14 -0
- data/db/migrate/20160507135846_create_errors_2.rb +12 -0
- data/db/migrate/20160510233329_add_closed_at_and_merged_at_to_pull_requests.rb +7 -0
- data/db/migrate/20160625203412_convert_user_view_options_to_props.rb +21 -0
- data/db/migrate/20160625221840_convert_project_extended_attributes_to_props.rb +32 -0
- data/db/migrate/20160625230420_move_users_unfuddle_id_to_props.rb +14 -0
- data/db/structure.sql +247 -4
- data/{houston.gemspec → houston-core.gemspec} +8 -9
- data/lib/configuration.rb +15 -43
- data/lib/houston/version.rb +1 -1
- data/lib/houston_observer.rb +2 -7
- data/lib/parallel_enumerable.rb +2 -8
- data/templates/new-instance/app/assets/javascripts/.keep +0 -0
- data/templates/new-instance/app/assets/javascripts/application.js +13 -0
- data/templates/new-instance/app/assets/stylesheets/.keep +0 -0
- data/templates/new-instance/app/assets/stylesheets/application.css +13 -0
- data/templates/new-instance/app/controllers/.keep +0 -0
- data/templates/new-instance/app/models/.keep +0 -0
- data/templates/new-instance/app/views/.keep +0 -0
- data/templates/new-instance/config/alerts/errs.rb +1 -1
- data/templates/new-instance/config/jobs/purge_jobs.rb +3 -0
- data/templates/new-instance/config/main.rb +1 -0
- data/templates/new-instance/config/routes.rb +5 -0
- data/templates/new-instance/config/triggers/github/publish_comments_on_slack.rb +3 -3
- data/templates/new-instance/lib/houston/engine.rb +25 -0
- data/test/acceptance/creating_a_release_test.rb +1 -1
- data/test/integration/ci_integration_test.rb +2 -2
- data/test/integration/commits_api_test.rb +1 -1
- data/test/unit/controllers/hooks_controller_test.rb +1 -6
- data/test/unit/controllers/project_options_controller_test.rb +11 -11
- data/test/unit/controllers/user_options_controller_test.rb +13 -13
- data/test/unit/models/code_climate_coverage_report_test.rb +1 -1
- data/test/unit/models/commit_test.rb +4 -2
- data/test/unit/models/project_test.rb +5 -5
- data/test/unit/models/props_test.rb +57 -0
- data/test/unit/models/pull_request_test.rb +2 -2
- data/test/unit/models/test_run_test.rb +1 -1
- metadata +166 -151
- data/app/assets/javascripts/app/views/reports_view.coffee +0 -51
- data/app/controllers/oauth_consumers_controller.rb +0 -68
- data/app/controllers/reports_controller.rb +0 -215
- data/app/models/consumer_token.rb +0 -13
- data/app/models/github_token.rb +0 -8
- data/app/views/oauth_consumers/error.html.erb +0 -5
- data/app/views/oauth_consumers/index.html.erb +0 -29
- data/app/views/oauth_consumers/show.html.erb +0 -7
- data/app/views/reports/index.html.erb +0 -9
- data/app/views/reports/sprint.html.erb +0 -21
- data/app/views/reports/velocity.html.erb +0 -104
- data/config/initializers/oauth_consumers.rb +0 -18
- data/lib/patches/sprockets_output_path_for_assets.rb +0 -29
@@ -1,51 +0,0 @@
|
|
1
|
-
class @ReportsView extends Backbone.View
|
2
|
-
|
3
|
-
render: ->
|
4
|
-
@$el.html '''
|
5
|
-
<h3>Queue Size and Ticket Age</h3>
|
6
|
-
<div id="queue_age" class="graph"></div>
|
7
|
-
|
8
|
-
<h3>Cycle Time (days)</h3>
|
9
|
-
<div id="cycle_time" class="graph"></div>
|
10
|
-
|
11
|
-
<h3>Time-to-Release (days)</h3>
|
12
|
-
<div id="time_to_release" class="graph"></div>
|
13
|
-
|
14
|
-
<h3>Time-to-First-Test (hours)</h3>
|
15
|
-
<div id="time_to_first_test" class="graph"></div>
|
16
|
-
'''
|
17
|
-
$.getJSON "/reports/queue-age#{window.location.search}", (json)->
|
18
|
-
new Houston.StackedAreaGraph()
|
19
|
-
.selector('#queue_age')
|
20
|
-
.labels(['0-3wks', '3wks–3mos', '3-9mos', '9mos–2yrs', '> 2yrs'])
|
21
|
-
.colors([
|
22
|
-
'rgb(31, 119, 180)',
|
23
|
-
'rgb(71, 48, 129)',
|
24
|
-
'rgb(175, 76, 143)',
|
25
|
-
'rgb(236, 148, 52)',
|
26
|
-
'rgb(243, 210, 35)'
|
27
|
-
])
|
28
|
-
.data(json.data)
|
29
|
-
.addLine(json.line)
|
30
|
-
.render()
|
31
|
-
|
32
|
-
$.getJSON "/reports/cycle-time#{window.location.search}", (data)->
|
33
|
-
new Houston.StackedAreaGraph()
|
34
|
-
.selector('#cycle_time')
|
35
|
-
.labels(['cycle time'])
|
36
|
-
.data(data)
|
37
|
-
.render()
|
38
|
-
|
39
|
-
$.getJSON "/reports/time-to-release#{window.location.search}", (data)->
|
40
|
-
new Houston.StackedAreaGraph()
|
41
|
-
.selector('#time_to_release')
|
42
|
-
.labels(['time-to-close', 'time-to-release'])
|
43
|
-
.data(data)
|
44
|
-
.render()
|
45
|
-
|
46
|
-
$.getJSON "/reports/time-to-first-test#{window.location.search}", (data)->
|
47
|
-
new Houston.StackedAreaGraph()
|
48
|
-
.selector('#time_to_first_test')
|
49
|
-
.labels(['time-to-test'])
|
50
|
-
.data(data)
|
51
|
-
.render()
|
@@ -1,68 +0,0 @@
|
|
1
|
-
require 'oauth/controllers/consumer_controller'
|
2
|
-
class OauthConsumersController < ApplicationController
|
3
|
-
include Oauth::Controllers::ConsumerController
|
4
|
-
|
5
|
-
before_filter :authenticate_user!, only: :index
|
6
|
-
|
7
|
-
rescue_from OAuth2::Error do |exception|
|
8
|
-
@exception = exception
|
9
|
-
render template: "oauth_consumers/error"
|
10
|
-
end
|
11
|
-
|
12
|
-
def index
|
13
|
-
@consumer_tokens = ConsumerToken.where(user_id: current_user.id)
|
14
|
-
@services = OAUTH_CREDENTIALS.keys - @consumer_tokens.map { |c| c.class.service_name }
|
15
|
-
end
|
16
|
-
|
17
|
-
def callback
|
18
|
-
super
|
19
|
-
end
|
20
|
-
|
21
|
-
def callback2
|
22
|
-
super
|
23
|
-
end
|
24
|
-
|
25
|
-
def client
|
26
|
-
super
|
27
|
-
end
|
28
|
-
|
29
|
-
# for some reason oauth-plugin is broken and can't figure this out:
|
30
|
-
def callback2_oauth_consumer_url
|
31
|
-
root_url + "oauth_consumers/github/callback2"
|
32
|
-
end
|
33
|
-
|
34
|
-
|
35
|
-
protected
|
36
|
-
|
37
|
-
# Change this to decide where you want to redirect user to after callback is finished.
|
38
|
-
# params[:id] holds the service name so you could use this to redirect to various parts
|
39
|
-
# of your application depending on what service you're connecting to.
|
40
|
-
def go_back
|
41
|
-
redirect_to session.fetch("user.return_to", pull_requests_url)
|
42
|
-
end
|
43
|
-
|
44
|
-
# The plugin requires logged_in? to return true or false if the user is logged in. Uncomment and
|
45
|
-
# call your auth frameworks equivalent below if different. eg. for devise:
|
46
|
-
def logged_in?
|
47
|
-
user_signed_in?
|
48
|
-
end
|
49
|
-
|
50
|
-
# The plugin requires current_user to return the current logged in user. Uncomment and
|
51
|
-
# call your auth frameworks equivalent below if different.
|
52
|
-
# def current_user
|
53
|
-
# current_person
|
54
|
-
# end
|
55
|
-
|
56
|
-
# The plugin requires a way to log a user in. Call your auth frameworks equivalent below
|
57
|
-
# if different. eg. for devise:
|
58
|
-
def current_user=(user)
|
59
|
-
sign_in(user)
|
60
|
-
end
|
61
|
-
|
62
|
-
# Override this to deny the user or redirect to a login screen depending on your framework and app
|
63
|
-
# if different. eg. for devise:
|
64
|
-
def deny_access!
|
65
|
-
raise CanCan::AccessDenied
|
66
|
-
end
|
67
|
-
|
68
|
-
end
|
@@ -1,215 +0,0 @@
|
|
1
|
-
class ReportsController < ApplicationController
|
2
|
-
before_filter { authorize! :read, :reports }
|
3
|
-
before_filter :find_tickets, only: [:queue_age, :cycle_time, :time_to_first_test, :time_to_release]
|
4
|
-
layout "minimal"
|
5
|
-
|
6
|
-
attr_reader :tickets, :start_date, :end_date
|
7
|
-
|
8
|
-
def index
|
9
|
-
@title = "Reports"
|
10
|
-
end
|
11
|
-
|
12
|
-
def queue_age
|
13
|
-
results = benchmark("pluck") do
|
14
|
-
tickets.joins(<<-SQL)
|
15
|
-
LEFT OUTER JOIN generate_series('#{start_date}'::timestamp, '#{end_date}'::timestamp, '1 month') AS months(month)
|
16
|
-
ON tickets.created_at <= months.month
|
17
|
-
AND (tickets.closed_at IS NULL OR tickets.closed_at > months.month)
|
18
|
-
SQL
|
19
|
-
.where("months.month IS NOT NULL")
|
20
|
-
.reorder("months.month ASC")
|
21
|
-
.pluck("months.month::date", "extract('epoch' from month - tickets.created_at)")
|
22
|
-
end
|
23
|
-
|
24
|
-
results = benchmark("process") do
|
25
|
-
results \
|
26
|
-
.each_with_object(Hash.new { |h, k| h[k] = [] }) { |(date, age), map| map[date].push(age) }
|
27
|
-
.map { |date, ages| [date] + to_age_bins(ages) }
|
28
|
-
end
|
29
|
-
|
30
|
-
line = benchmark("line") do
|
31
|
-
tickets.closed
|
32
|
-
.group("date_trunc('month', closed_at)::date")
|
33
|
-
.reorder("date_trunc('month', closed_at)::date")
|
34
|
-
.pluck("date_trunc('month', closed_at)::date", "COUNT(id)")
|
35
|
-
.map { |(month, count)| {date: month, y: count} }
|
36
|
-
end
|
37
|
-
|
38
|
-
benchmark("present") do
|
39
|
-
render json: {data: results, line: line}
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def cycle_time
|
44
|
-
results = benchmark("pluck") do
|
45
|
-
tickets.joins(<<-SQL)
|
46
|
-
LEFT OUTER JOIN generate_series('#{start_date}'::timestamp, '#{end_date}'::timestamp, '1 month') AS months(month)
|
47
|
-
ON tickets.created_at <= months.month
|
48
|
-
AND (tickets.closed_at IS NULL OR tickets.closed_at > months.month)
|
49
|
-
SQL
|
50
|
-
.where("months.month IS NOT NULL")
|
51
|
-
.group("months.month")
|
52
|
-
.reorder("months.month ASC")
|
53
|
-
.pluck("months.month::date", "AVG(extract('epoch' from month - tickets.created_at)) / 86400")
|
54
|
-
end
|
55
|
-
|
56
|
-
benchmark("present") do
|
57
|
-
render json: results
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def time_to_release
|
62
|
-
results = benchmark("pluck") do
|
63
|
-
Ticket.connection.select_rows(<<-SQL)
|
64
|
-
SELECT
|
65
|
-
months.month::date,
|
66
|
-
AVG(extract('epoch' from q.closed_at - q.to_staging_at)) / 86400,
|
67
|
-
AVG(extract('epoch' from q.released_at - q.closed_at)) / 86400
|
68
|
-
FROM generate_series('#{start_date}'::timestamp, '#{end_date}'::timestamp, '1 month') AS months(month)
|
69
|
-
LEFT OUTER JOIN (
|
70
|
-
SELECT
|
71
|
-
to_staging.created_at "to_staging_at",
|
72
|
-
tickets.closed_at,
|
73
|
-
to_production.created_at "released_at"
|
74
|
-
FROM tickets
|
75
|
-
|
76
|
-
INNER JOIN (
|
77
|
-
SELECT rt1.ticket_id, MIN(r1.created_at) "created_at"
|
78
|
-
FROM releases r1
|
79
|
-
INNER JOIN releases_tickets rt1 ON rt1.release_id=r1.id
|
80
|
-
WHERE r1.environment_name = 'Staging'
|
81
|
-
GROUP BY rt1.ticket_id
|
82
|
-
) AS to_staging ON to_staging.ticket_id=tickets.id
|
83
|
-
|
84
|
-
INNER JOIN (
|
85
|
-
SELECT rt2.ticket_id, MIN(r2.created_at) "created_at"
|
86
|
-
FROM releases r2
|
87
|
-
INNER JOIN releases_tickets rt2 ON rt2.release_id=r2.id
|
88
|
-
WHERE r2.environment_name = 'Production'
|
89
|
-
GROUP BY rt2.ticket_id
|
90
|
-
) AS to_production ON to_production.ticket_id=tickets.id
|
91
|
-
|
92
|
-
) AS q
|
93
|
-
ON q.released_at >= months.month
|
94
|
-
AND q.released_at < (months.month + interval '1 month')
|
95
|
-
WHERE q.closed_at > q.to_staging_at
|
96
|
-
AND q.released_at > q.closed_at
|
97
|
-
GROUP BY months.month
|
98
|
-
ORDER BY months.month ASC
|
99
|
-
SQL
|
100
|
-
.map { |(date, v1, v2, v3)| [date.to_date, v1.to_i, v2.to_i, v3.to_i] }
|
101
|
-
end
|
102
|
-
|
103
|
-
benchmark("present") do
|
104
|
-
render json: results
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def time_to_first_test
|
109
|
-
results = benchmark("pluck") do
|
110
|
-
Ticket.connection.select_rows(<<-SQL)
|
111
|
-
SELECT
|
112
|
-
months.month::date,
|
113
|
-
AVG(extract('epoch' from q.testing_started_at - q.to_staging_at)) / 3600
|
114
|
-
FROM generate_series('#{start_date}'::timestamp, '#{end_date}'::timestamp, '1 month') AS months(month)
|
115
|
-
LEFT OUTER JOIN (
|
116
|
-
SELECT
|
117
|
-
to_staging.created_at "to_staging_at",
|
118
|
-
first_test.created_at "testing_started_at"
|
119
|
-
FROM tickets
|
120
|
-
|
121
|
-
INNER JOIN (
|
122
|
-
SELECT rt1.ticket_id, MIN(r1.created_at) "created_at"
|
123
|
-
FROM releases r1
|
124
|
-
INNER JOIN releases_tickets rt1 ON rt1.release_id=r1.id
|
125
|
-
WHERE r1.environment_name = 'Staging'
|
126
|
-
GROUP BY rt1.ticket_id
|
127
|
-
) AS to_staging ON to_staging.ticket_id=tickets.id
|
128
|
-
|
129
|
-
INNER JOIN (
|
130
|
-
SELECT tn.ticket_id, MIN(tn.created_at) "created_at"
|
131
|
-
FROM testing_notes tn
|
132
|
-
GROUP BY tn.ticket_id
|
133
|
-
) AS first_test ON first_test.ticket_id=tickets.id
|
134
|
-
|
135
|
-
) AS q
|
136
|
-
ON q.to_staging_at >= months.month
|
137
|
-
AND q.to_staging_at < (months.month + interval '1 month')
|
138
|
-
WHERE q.testing_started_at > q.to_staging_at
|
139
|
-
GROUP BY months.month
|
140
|
-
ORDER BY months.month ASC
|
141
|
-
SQL
|
142
|
-
.map { |(date, v1, v2, v3)| [date.to_date, v1.to_i, v2.to_i, v3.to_i] }
|
143
|
-
end
|
144
|
-
|
145
|
-
benchmark("present") do
|
146
|
-
render json: results
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
def velocity
|
153
|
-
@title = "Reports"
|
154
|
-
@tickets = ::Ticket.includes(:project, :tasks).estimated.closed
|
155
|
-
.select { |ticket| ticket.commit_time > 0 } # <-- speed up
|
156
|
-
end
|
157
|
-
|
158
|
-
def sprint
|
159
|
-
@title = "Sprint Reports"
|
160
|
-
@start_date = Date.parse(params.fetch(:since, "2014-05-18")) # when tasks were added
|
161
|
-
@start_date = Date.parse(params.fetch(:since, "2014-08-07"))
|
162
|
-
@end_date = Date.today
|
163
|
-
|
164
|
-
@users = []
|
165
|
-
([nil] + User.developers).each do |user|
|
166
|
-
data = SprintReport.new(user, start_date, end_date).to_a
|
167
|
-
|
168
|
-
next if data.all? { |(_, completed, missed)| (completed + missed).zero? }
|
169
|
-
average = data
|
170
|
-
.select { |_, completed, missed| (completed + missed) > 0 }
|
171
|
-
.avg { |(_, completed, _)| completed }
|
172
|
-
@users.push [(user ? user.name : "Team"), average, data] # <- data is: [<date>, <completed>, <missed>]
|
173
|
-
end
|
174
|
-
@users.sort_by! { |(_, average, _)| -average }
|
175
|
-
end
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
def tasks_excel
|
180
|
-
authorize! :read, "Report"
|
181
|
-
tasks = Task.completed
|
182
|
-
send_data TasksExcelPresenter.new(tasks),
|
183
|
-
type: :xlsx,
|
184
|
-
filename: "Tasks.xlsx",
|
185
|
-
disposition: "attachment"
|
186
|
-
end
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
private
|
191
|
-
|
192
|
-
CUTOFFS = [3.weeks, 3.months, 9.months, 2.years, 99.years].freeze
|
193
|
-
|
194
|
-
def to_age_bins(ages)
|
195
|
-
ages.each_with_object([0, 0, 0, 0, 0]) do |age, bins|
|
196
|
-
bins[to_bin(age)] += 1
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def to_bin(age)
|
201
|
-
CUTOFFS.each_with_index do |cutoff, i|
|
202
|
-
return i if age < cutoff
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def find_tickets
|
207
|
-
@tickets = Ticket.reorder(:created_at)
|
208
|
-
@tickets = tickets.where(project_id: params[:project_id]) if params[:project_id]
|
209
|
-
@tickets = tickets.where(project_id: params[:projects].split(",")) if params[:projects]
|
210
|
-
|
211
|
-
@start_date = params.fetch(:since, "2010-01-01")
|
212
|
-
@end_date = Date.today.strftime "%Y-%m-%d"
|
213
|
-
end
|
214
|
-
|
215
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
require 'oauth/models/consumers/token'
|
2
|
-
class ConsumerToken < ActiveRecord::Base
|
3
|
-
include Oauth::Models::Consumers::Token
|
4
|
-
|
5
|
-
# You can safely remove this callback if you don't allow login from any of your services
|
6
|
-
# before_create :create_user
|
7
|
-
|
8
|
-
# Modify this with class_name etc to match your application
|
9
|
-
belongs_to :user
|
10
|
-
|
11
|
-
validates_uniqueness_of :token
|
12
|
-
|
13
|
-
end
|
data/app/models/github_token.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
<h1>Services</h1>
|
2
|
-
|
3
|
-
<% if @consumer_tokens.empty? %>
|
4
|
-
<p>
|
5
|
-
You are currently not connected to any external services.
|
6
|
-
</p>
|
7
|
-
<% else %>
|
8
|
-
<p>
|
9
|
-
You are connected to the following services:
|
10
|
-
</p>
|
11
|
-
<ul>
|
12
|
-
<% @consumer_tokens.each do |token| %>
|
13
|
-
<li>
|
14
|
-
<%= link_to token.class.service_name.to_s.humanize, oauth_consumer_path(token.class.service_name) %>
|
15
|
-
</li>
|
16
|
-
<% end %>
|
17
|
-
</ul>
|
18
|
-
<% end %>
|
19
|
-
|
20
|
-
<% unless @services.empty? %>
|
21
|
-
<h3>You can connect to the following services:</h3>
|
22
|
-
<ul>
|
23
|
-
<% @services.each do |service| %>
|
24
|
-
<li>
|
25
|
-
<%= link_to service.to_s.humanize, oauth_consumer_path(service) %>
|
26
|
-
</li>
|
27
|
-
<% end %>
|
28
|
-
</ul>
|
29
|
-
<% end %>
|
@@ -1,21 +0,0 @@
|
|
1
|
-
<% @users.each_with_index do |(name, average, _), i| %>
|
2
|
-
<h3><%= name %> <small>(Average: <%= "%.1f" % average %>)</small></h3>
|
3
|
-
<div id="developer_<%= i %>" class="graph"></div>
|
4
|
-
<% end %>
|
5
|
-
|
6
|
-
<% content_for :javascripts do %>
|
7
|
-
<script type="text/javascript">
|
8
|
-
$(function() {
|
9
|
-
window.data = <%=raw @users.to_json %>;
|
10
|
-
for(var i=0; i<data.length; i++) {
|
11
|
-
new Houston.StackedBarGraph()
|
12
|
-
.selector('#developer_' + i)
|
13
|
-
.labels(['Completed', 'Missed'])
|
14
|
-
.colors(['rgb(31, 180, 61)', 'rgb(228, 45, 45)'])
|
15
|
-
.data(data[i][2])
|
16
|
-
.range([0, 60])
|
17
|
-
.render();
|
18
|
-
}
|
19
|
-
});
|
20
|
-
</script>
|
21
|
-
<% end %>
|
@@ -1,104 +0,0 @@
|
|
1
|
-
<div id="velocity"></div>
|
2
|
-
|
3
|
-
<% content_for :javascripts do %>
|
4
|
-
<script type="text/javascript">
|
5
|
-
$(function() {
|
6
|
-
window.data = <%=raw @tickets.map { |ticket|
|
7
|
-
project = ticket.project
|
8
|
-
{ projectTitle: project.name,
|
9
|
-
projectColor: project.color,
|
10
|
-
number: ticket.number,
|
11
|
-
estimated: ticket.effort,
|
12
|
-
actual: ticket.commit_time } }.to_json %>;
|
13
|
-
|
14
|
-
// call the method below
|
15
|
-
showScatterPlot(data);
|
16
|
-
|
17
|
-
function showScatterPlot(data) {
|
18
|
-
|
19
|
-
// just to have some space around items.
|
20
|
-
var margins = {
|
21
|
-
"left": 40,
|
22
|
-
"right": 30,
|
23
|
-
"top": 30,
|
24
|
-
"bottom": 30
|
25
|
-
};
|
26
|
-
|
27
|
-
var width = 800;
|
28
|
-
var height = 500;
|
29
|
-
|
30
|
-
// we add the SVG component to the scatter-load div
|
31
|
-
var svg = d3.select("#velocity")
|
32
|
-
.append("svg").attr("width", width).attr("height", height)
|
33
|
-
.append("g").attr("transform", "translate(" + margins.left + "," + margins.top + ")");
|
34
|
-
|
35
|
-
// this sets the scale that we're using for the X axis.
|
36
|
-
// the domain define the min and max variables to show. In this case, it's the min and max prices of items.
|
37
|
-
// this is made a compact piece of code due to d3.extent which gives back the max and min of the price variable within the dataset
|
38
|
-
var x = d3.scale.linear()
|
39
|
-
.domain(d3.extent(data, function (d) { return d.estimated; }))
|
40
|
-
.range([0, width - margins.left - margins.right]);
|
41
|
-
|
42
|
-
// this does the same as for the y axis but maps from the rating variable to the height to 0.
|
43
|
-
var y = d3.scale.linear()
|
44
|
-
.domain(d3.extent(data, function (d) { return d.actual; }))
|
45
|
-
.range([height - margins.top - margins.bottom, 0]);
|
46
|
-
|
47
|
-
// we add the axes SVG component. At this point, this is just a placeholder. The actual axis will be added in a bit
|
48
|
-
svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + y.range()[0] + ")");
|
49
|
-
svg.append("g").attr("class", "y axis");
|
50
|
-
|
51
|
-
// this is our X axis label. Nothing too special to see here.
|
52
|
-
svg.append("text")
|
53
|
-
.attr("fill", "#414241")
|
54
|
-
.attr("text-anchor", "end")
|
55
|
-
.attr("x", width - 70)
|
56
|
-
.attr("y", y.range()[0] - 6)
|
57
|
-
.text("Estimated Effort");
|
58
|
-
|
59
|
-
// this is our Y axis label.
|
60
|
-
svg.append("text")
|
61
|
-
.attr("fill", "#414241")
|
62
|
-
.attr("text-anchor", "end")
|
63
|
-
.attr("y", 6)
|
64
|
-
.attr("dy", ".71em")
|
65
|
-
.attr("transform", "rotate(-90)")
|
66
|
-
.text("Actual Hours");
|
67
|
-
|
68
|
-
// this is the actual definition of our x and y axes. The orientation refers to where the labels appear - for the x axis, below or above the line, and for the y axis, left or right of the line. Tick padding refers to how much space between the tick and the label. There are other parameters too - see https://github.com/mbostock/d3/wiki/SVG-Axes for more information
|
69
|
-
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickPadding(2);
|
70
|
-
var yAxis = d3.svg.axis().scale(y).orient("left").tickPadding(2);
|
71
|
-
|
72
|
-
// this is where we select the axis we created a few lines earlier. See how we select the axis item. in our svg we appended a g element with a x/y and axis class. To pull that back up, we do this svg select, then 'call' the appropriate axis object for rendering.
|
73
|
-
svg.selectAll("g.y.axis").call(yAxis);
|
74
|
-
svg.selectAll("g.x.axis").call(xAxis);
|
75
|
-
|
76
|
-
// now, we can get down to the data part, and drawing stuff. We are telling D3 that all nodes (g elements with class node) will have data attached to them. The 'key' we use (to let D3 know the uniqueness of items) will be the name. Not usually a great key, but fine for this example.
|
77
|
-
var point = svg.selectAll("g.node").data(data, function (d) { return d.number; });
|
78
|
-
|
79
|
-
// add the tooltip area to the webpage
|
80
|
-
var tooltip = d3.select("body").append("div")
|
81
|
-
.attr("class", "tooltip")
|
82
|
-
.style("opacity", 0);
|
83
|
-
|
84
|
-
// we 'enter' the data, making the SVG group (to contain a circle and text) with a class node. This corresponds with what we told the data it should be above.
|
85
|
-
var group = point.enter().append("g").attr("class", "node")
|
86
|
-
.attr('transform', function (d) { return "translate(" + x(d.estimated) + "," + y(d.actual) + ")"; });
|
87
|
-
|
88
|
-
group.append("circle")
|
89
|
-
.attr("r", 5)
|
90
|
-
.attr("class", "dot")
|
91
|
-
.style("fill", function (d) { return d.projectColor; })
|
92
|
-
.on("mouseover", function(d) {
|
93
|
-
tooltip.transition().duration(200).style("opacity", .9);
|
94
|
-
tooltip.html(d.projectTitle + " #" + d.number + "<br/>Estimated: " + d.estimated + "<br/>Actual: " + d.actual)
|
95
|
-
.style("left", (d3.event.pageX + 15) + "px")
|
96
|
-
.style("top", (d3.event.pageY - 15) + "px");
|
97
|
-
})
|
98
|
-
.on("mouseout", function(d) {
|
99
|
-
tooltip.transition().duration(500).style("opacity", 0);
|
100
|
-
});
|
101
|
-
}
|
102
|
-
});
|
103
|
-
</script>
|
104
|
-
<% end %>
|