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.
Files changed (232) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile.lock +53 -63
  4. data/app/adapters/houston/adapters/error_tracker/errbit_adapter.rb +2 -2
  5. data/app/adapters/houston/adapters/ticket_tracker/github_adapter.rb +1 -1
  6. data/app/adapters/houston/adapters/ticket_tracker/unfuddle_adapter.rb +5 -5
  7. data/app/adapters/houston/adapters/version_control/git_adapter.rb +4 -4
  8. data/app/assets/javascripts/{app → houston/app}/boot.coffee +0 -0
  9. data/app/assets/javascripts/{app → houston/app}/emoji.coffee.erb +0 -0
  10. data/app/assets/javascripts/{app → houston/app}/infinite_scroll.coffee +0 -0
  11. data/app/assets/javascripts/{app → houston/app}/models/commit.coffee +0 -0
  12. data/app/assets/javascripts/{app → houston/app}/models/release.coffee +0 -0
  13. data/app/assets/javascripts/{app → houston/app}/models/task.coffee +0 -0
  14. data/app/assets/javascripts/{app → houston/app}/models/tester.coffee +0 -0
  15. data/app/assets/javascripts/{app → houston/app}/models/testing_note.coffee +0 -0
  16. data/app/assets/javascripts/{app → houston/app}/models/ticket.coffee +0 -0
  17. data/app/assets/javascripts/{app → houston/app}/models/user.coffee +0 -0
  18. data/app/assets/javascripts/{app → houston/app}/mousetrap-bind-scoped.js +0 -0
  19. data/app/assets/javascripts/{app → houston/app}/releases.coffee +0 -0
  20. data/app/assets/javascripts/{app → houston/app}/table_row_expander.coffee +0 -0
  21. data/app/assets/javascripts/{app → houston/app}/ticket_tracker_refresh.coffee +0 -0
  22. data/app/assets/javascripts/{app → houston/app}/views/_show_sprint_view.coffee +0 -0
  23. data/app/assets/javascripts/{app → houston/app}/views/_tickets_view.coffee +0 -0
  24. data/app/assets/javascripts/{app → houston/app}/views/all_tickets_view.coffee +0 -0
  25. data/app/assets/javascripts/{app → houston/app}/views/commit_view.coffee +0 -0
  26. data/app/assets/javascripts/{app → houston/app}/views/edit_sprint_view.coffee +0 -0
  27. data/app/assets/javascripts/{app → houston/app}/views/edit_ticket_view.coffee +0 -0
  28. data/app/assets/javascripts/{app → houston/app}/views/find_or_create_ticket_view.coffee +0 -0
  29. data/app/assets/javascripts/{app → houston/app}/views/keyboard_shortcuts_modal.coffee +0 -0
  30. data/app/assets/javascripts/{app → houston/app}/views/new_ticket_modal.coffee +0 -0
  31. data/app/assets/javascripts/{app → houston/app}/views/new_ticket_view.coffee +0 -0
  32. data/app/assets/javascripts/{app → houston/app}/views/problems_view.coffee +0 -0
  33. data/app/assets/javascripts/{app → houston/app}/views/testing_note_view.coffee +0 -0
  34. data/app/assets/javascripts/{app → houston/app}/views/testing_report_view.coffee +0 -0
  35. data/app/assets/javascripts/{app → houston/app}/views/testing_ticket_view.coffee +0 -0
  36. data/app/assets/javascripts/{app → houston/app}/views/ticket_modal_view.coffee +0 -0
  37. data/app/assets/javascripts/{application.js → houston/application.js} +1 -1
  38. data/app/assets/javascripts/{core → houston/core}/app.coffee +0 -0
  39. data/app/assets/javascripts/{core → houston/core}/burndown_chart.coffee +0 -0
  40. data/app/assets/javascripts/{core → houston/core}/core_ext/array.coffee +0 -0
  41. data/app/assets/javascripts/{core → houston/core}/core_ext/date.coffee +0 -0
  42. data/app/assets/javascripts/{core → houston/core}/core_ext/number.coffee +0 -0
  43. data/app/assets/javascripts/{core → houston/core}/errors.coffee +1 -1
  44. data/app/assets/javascripts/{core → houston/core}/handlebars_helpers.coffee +0 -0
  45. data/app/assets/javascripts/{core → houston/core}/helpers.coffee +0 -0
  46. data/app/assets/javascripts/{core → houston/core}/jquery_extensions.coffee +5 -3
  47. data/app/assets/javascripts/{core → houston/core}/stacked_area_graph.coffee +0 -0
  48. data/app/assets/javascripts/{core → houston/core}/stacked_bar_graph.coffee +0 -0
  49. data/app/assets/javascripts/{dashboard.js → houston/dashboard.js} +1 -1
  50. data/app/assets/javascripts/{dashboard → houston/dashboard}/refresher.coffee +0 -0
  51. data/app/assets/javascripts/{vendor.js → houston/vendor.js} +0 -0
  52. data/app/assets/stylesheets/{application.css → houston/application.css} +0 -0
  53. data/app/assets/stylesheets/{application → houston/application}/ansi.scss +0 -0
  54. data/app/assets/stylesheets/{application → houston/application}/commit.scss +0 -0
  55. data/app/assets/stylesheets/{application → houston/application}/emoji.scss +0 -0
  56. data/app/assets/stylesheets/{application → houston/application}/exceptions.scss +1 -1
  57. data/app/assets/stylesheets/{application → houston/application}/find_or_create_ticket.scss +0 -0
  58. data/app/assets/stylesheets/{application → houston/application}/follow_up.scss +0 -0
  59. data/app/assets/stylesheets/{application → houston/application}/forms.scss +0 -0
  60. data/app/assets/stylesheets/{application → houston/application}/freight_train.css.scss +0 -0
  61. data/app/assets/stylesheets/{application → houston/application}/full_screen.scss +0 -0
  62. data/app/assets/stylesheets/{application → houston/application}/github_repos.scss +0 -0
  63. data/app/assets/stylesheets/{application → houston/application}/infinite_scroll.scss +0 -0
  64. data/app/assets/stylesheets/houston/application/jobs.scss +5 -0
  65. data/app/assets/stylesheets/{application → houston/application}/keyboard_shortcuts.scss +0 -0
  66. data/app/assets/stylesheets/{application → houston/application}/markdown.scss +0 -0
  67. data/app/assets/stylesheets/{application → houston/application}/mobile.scss +0 -0
  68. data/app/assets/stylesheets/{application → houston/application}/modals.scss +0 -0
  69. data/app/assets/stylesheets/{application → houston/application}/navigation.scss +0 -0
  70. data/app/assets/stylesheets/{application → houston/application}/new_ticket_view.scss +0 -0
  71. data/app/assets/stylesheets/{application → houston/application}/omnibar.scss +0 -0
  72. data/app/assets/stylesheets/{application → houston/application}/project_banner_buttons.scss +0 -0
  73. data/app/assets/stylesheets/{application → houston/application}/project_tiles.scss +0 -0
  74. data/app/assets/stylesheets/{application → houston/application}/projects.css.scss +0 -0
  75. data/app/assets/stylesheets/{application → houston/application}/pull_requests.scss +0 -0
  76. data/app/assets/stylesheets/{application → houston/application}/queue.scss +0 -0
  77. data/app/assets/stylesheets/{application → houston/application}/release_form.scss +0 -0
  78. data/app/assets/stylesheets/{application → houston/application}/releases.scss +1 -1
  79. data/app/assets/stylesheets/{application → houston/application}/sortable_table.scss +0 -0
  80. data/app/assets/stylesheets/{application → houston/application}/sprint.scss +0 -0
  81. data/app/assets/stylesheets/{application → houston/application}/tables.scss +0 -0
  82. data/app/assets/stylesheets/{application → houston/application}/test.scss +0 -0
  83. data/app/assets/stylesheets/{application → houston/application}/test_run.scss +0 -0
  84. data/app/assets/stylesheets/{application → houston/application}/testing_report.scss +0 -0
  85. data/app/assets/stylesheets/{application → houston/application}/ticket.scss +0 -0
  86. data/app/assets/stylesheets/{application → houston/application}/ticket_modal.scss +0 -0
  87. data/app/assets/stylesheets/{application → houston/application}/tickets.scss +0 -0
  88. data/app/assets/stylesheets/{application → houston/application}/timeline.scss +0 -0
  89. data/app/assets/stylesheets/{application → houston/application}/tips.scss +0 -0
  90. data/app/assets/stylesheets/{application → houston/application}/typeahead.scss +0 -0
  91. data/app/assets/stylesheets/{application → houston/application}/uploading.scss +0 -0
  92. data/app/assets/stylesheets/{application → houston/application}/user_wall.scss +0 -0
  93. data/app/assets/stylesheets/{application → houston/application}/users.scss +0 -0
  94. data/app/assets/stylesheets/{application → houston/application}/welcome.scss +0 -0
  95. data/app/assets/stylesheets/{core → houston/core}/alerts.scss +0 -0
  96. data/app/assets/stylesheets/{core → houston/core}/avatars.scss +0 -0
  97. data/app/assets/stylesheets/{core → houston/core}/burndown_chart.scss +0 -0
  98. data/app/assets/stylesheets/{core → houston/core}/colors.scss.erb +0 -0
  99. data/app/assets/stylesheets/{core → houston/core}/misc.scss +3 -0
  100. data/app/assets/stylesheets/{core → houston/core}/octicons-icons.scss +0 -0
  101. data/app/assets/stylesheets/{core → houston/core}/octicons.scss.erb +0 -0
  102. data/app/assets/stylesheets/{core → houston/core}/overrides.scss +0 -0
  103. data/app/assets/stylesheets/{core → houston/core}/roboto.scss.erb +0 -0
  104. data/app/assets/stylesheets/{core → houston/core}/scores.scss +0 -0
  105. data/app/assets/stylesheets/{dashboard.css → houston/dashboard.css} +0 -0
  106. data/app/assets/stylesheets/{dashboard → houston/dashboard}/dashboard.scss +0 -0
  107. data/app/assets/stylesheets/{print.css.scss → houston/print.css.scss} +0 -0
  108. data/app/assets/stylesheets/{variables.scss → houston/variables.scss} +0 -0
  109. data/app/assets/stylesheets/{vendor.css → houston/vendor.css} +0 -0
  110. data/app/concerns/houston/props.rb +96 -0
  111. data/app/concerns/project_adapter.rb +1 -1
  112. data/app/controllers/api/v1/measurements_controller.rb +32 -0
  113. data/app/controllers/application_controller.rb +1 -1
  114. data/app/controllers/authorizations_controller.rb +61 -0
  115. data/app/controllers/github/pulls_controller.rb +1 -1
  116. data/app/controllers/jobs_controller.rb +30 -1
  117. data/app/controllers/oauth/providers_controller.rb +45 -0
  118. data/app/controllers/project_options_controller.rb +2 -2
  119. data/app/controllers/projects_controller.rb +3 -3
  120. data/app/controllers/releases_controller.rb +3 -3
  121. data/app/controllers/user_options_controller.rb +2 -2
  122. data/app/controllers/users_controller.rb +1 -2
  123. data/app/helpers/application_helper.rb +49 -3
  124. data/app/helpers/email_helper.rb +19 -1
  125. data/app/helpers/release_helper.rb +1 -1
  126. data/app/helpers/ticket_helper.rb +0 -32
  127. data/app/interactors/cache_key_dependencies.rb +3 -8
  128. data/app/mailers/view_mailer.rb +8 -8
  129. data/app/models/authorization.rb +54 -0
  130. data/app/models/commit.rb +1 -1
  131. data/app/models/error.rb +14 -0
  132. data/app/models/github/comment_event.rb +4 -4
  133. data/app/models/github/pull_request.rb +39 -17
  134. data/app/models/github/pull_request_event.rb +1 -7
  135. data/app/models/job.rb +81 -0
  136. data/app/models/measurement.rb +2 -1
  137. data/app/models/oauth/provider.rb +35 -0
  138. data/app/models/project.rb +12 -3
  139. data/app/models/slackdown.rb +1 -1
  140. data/app/models/user.rb +28 -35
  141. data/app/presenters/measurements_presenter.rb +26 -0
  142. data/app/presenters/project_presenter.rb +1 -1
  143. data/app/views/authorizations/_form.html.erb +33 -0
  144. data/app/views/authorizations/edit.html.erb +7 -0
  145. data/app/views/authorizations/granted.html.erb +7 -0
  146. data/app/views/authorizations/index.html.erb +47 -0
  147. data/app/views/authorizations/new.html.erb +7 -0
  148. data/app/views/configuration_error/_no_ticket_tracker.html.erb +1 -1
  149. data/app/views/github/pulls/index.html.erb +5 -1
  150. data/app/views/jobs/index.html.erb +72 -0
  151. data/app/views/jobs/show.html.erb +25 -32
  152. data/app/views/layouts/_navigation.html.erb +1 -1
  153. data/app/views/layouts/application.html.erb +14 -15
  154. data/app/views/layouts/dashboard.html.erb +9 -10
  155. data/app/views/layouts/minimal.html.erb +14 -15
  156. data/app/views/layouts/naked.html.erb +52 -0
  157. data/app/views/layouts/naked_dashboard.html.erb +9 -10
  158. data/app/views/oauth/providers/_form.html.erb +54 -0
  159. data/app/views/oauth/providers/edit.html.erb +7 -0
  160. data/app/views/oauth/providers/index.html.erb +41 -0
  161. data/app/views/oauth/providers/new.html.erb +7 -0
  162. data/app/views/projects/_form.html.erb +8 -9
  163. data/app/views/projects/index.html.erb +1 -1
  164. data/app/views/users/_form.html.erb +20 -19
  165. data/bin/rails +1 -1
  166. data/config/application.rb +11 -10
  167. data/config/boot.rb +1 -2
  168. data/config/environments/development.rb +8 -6
  169. data/config/environments/production.rb +11 -16
  170. data/config/environments/test.rb +5 -5
  171. data/config/initializers/assets.rb +17 -7
  172. data/config/initializers/cookies_serializer.rb +1 -1
  173. data/config/initializers/houston_async.rb +22 -4
  174. data/config/initializers/houston_report_exception.rb +16 -11
  175. data/config/initializers/houston_scheduler.rb +1 -1
  176. data/config/initializers/houston_try.rb +12 -0
  177. data/config/routes.rb +29 -19
  178. data/db/fixtures/projects.yml +2 -2
  179. data/db/migrate/20160317140151_remove_limit_from_users_invitation_token.rb +9 -0
  180. data/db/migrate/20160419230411_create_oauth_providers.rb +14 -0
  181. data/db/migrate/20160420000616_create_authorizations.rb +16 -0
  182. data/db/migrate/20160507135209_create_jobs.rb +14 -0
  183. data/db/migrate/20160507135846_create_errors_2.rb +12 -0
  184. data/db/migrate/20160510233329_add_closed_at_and_merged_at_to_pull_requests.rb +7 -0
  185. data/db/migrate/20160625203412_convert_user_view_options_to_props.rb +21 -0
  186. data/db/migrate/20160625221840_convert_project_extended_attributes_to_props.rb +32 -0
  187. data/db/migrate/20160625230420_move_users_unfuddle_id_to_props.rb +14 -0
  188. data/db/structure.sql +247 -4
  189. data/{houston.gemspec → houston-core.gemspec} +8 -9
  190. data/lib/configuration.rb +15 -43
  191. data/lib/houston/version.rb +1 -1
  192. data/lib/houston_observer.rb +2 -7
  193. data/lib/parallel_enumerable.rb +2 -8
  194. data/templates/new-instance/app/assets/javascripts/.keep +0 -0
  195. data/templates/new-instance/app/assets/javascripts/application.js +13 -0
  196. data/templates/new-instance/app/assets/stylesheets/.keep +0 -0
  197. data/templates/new-instance/app/assets/stylesheets/application.css +13 -0
  198. data/templates/new-instance/app/controllers/.keep +0 -0
  199. data/templates/new-instance/app/models/.keep +0 -0
  200. data/templates/new-instance/app/views/.keep +0 -0
  201. data/templates/new-instance/config/alerts/errs.rb +1 -1
  202. data/templates/new-instance/config/jobs/purge_jobs.rb +3 -0
  203. data/templates/new-instance/config/main.rb +1 -0
  204. data/templates/new-instance/config/routes.rb +5 -0
  205. data/templates/new-instance/config/triggers/github/publish_comments_on_slack.rb +3 -3
  206. data/templates/new-instance/lib/houston/engine.rb +25 -0
  207. data/test/acceptance/creating_a_release_test.rb +1 -1
  208. data/test/integration/ci_integration_test.rb +2 -2
  209. data/test/integration/commits_api_test.rb +1 -1
  210. data/test/unit/controllers/hooks_controller_test.rb +1 -6
  211. data/test/unit/controllers/project_options_controller_test.rb +11 -11
  212. data/test/unit/controllers/user_options_controller_test.rb +13 -13
  213. data/test/unit/models/code_climate_coverage_report_test.rb +1 -1
  214. data/test/unit/models/commit_test.rb +4 -2
  215. data/test/unit/models/project_test.rb +5 -5
  216. data/test/unit/models/props_test.rb +57 -0
  217. data/test/unit/models/pull_request_test.rb +2 -2
  218. data/test/unit/models/test_run_test.rb +1 -1
  219. metadata +166 -151
  220. data/app/assets/javascripts/app/views/reports_view.coffee +0 -51
  221. data/app/controllers/oauth_consumers_controller.rb +0 -68
  222. data/app/controllers/reports_controller.rb +0 -215
  223. data/app/models/consumer_token.rb +0 -13
  224. data/app/models/github_token.rb +0 -8
  225. data/app/views/oauth_consumers/error.html.erb +0 -5
  226. data/app/views/oauth_consumers/index.html.erb +0 -29
  227. data/app/views/oauth_consumers/show.html.erb +0 -7
  228. data/app/views/reports/index.html.erb +0 -9
  229. data/app/views/reports/sprint.html.erb +0 -21
  230. data/app/views/reports/velocity.html.erb +0 -104
  231. data/config/initializers/oauth_consumers.rb +0 -18
  232. 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
@@ -1,8 +0,0 @@
1
- class GithubToken < Oauth2Token
2
-
3
- # skip refresh!
4
- def ensure_access
5
- self.class.find_or_create_from_access_token user, self
6
- end
7
-
8
- end
@@ -1,5 +0,0 @@
1
- <h2>Broken Oauth :-(</h2>
2
-
3
- <p>
4
- <%= @exception.code %>: <%= @exception.description %>
5
- </p>
@@ -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,7 +0,0 @@
1
- <h1>You are already Connected to <%=params[:id].humanize%></h1>
2
- <% form_tag oauth_consumer_path(params[:id]),:method=>:delete do %>
3
- <%=submit_tag "Disconnect" %>
4
- or
5
- <%=submit_tag "Reconnect" %>
6
- if you experienced a problem.
7
- <% end %>
@@ -1,9 +0,0 @@
1
- <div id="reports"></div>
2
-
3
- <% content_for :javascripts do %>
4
- <script type="text/javascript">
5
- $(function() {
6
- new ReportsView({el: $('#reports')[0]}).render();
7
- });
8
- </script>
9
- <% 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 %>