decidim-time_tracker 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +174 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/config/admin/decidim_time_tracker_manifest.css +2 -0
  6. data/app/assets/config/admin/decidim_time_tracker_manifest.js +1 -0
  7. data/app/assets/config/decidim_time_tracker_manifest.css +3 -0
  8. data/app/assets/config/decidim_time_tracker_manifest.js +1 -0
  9. data/app/assets/images/decidim/time_tracker/icon.svg +1 -0
  10. data/app/assets/javascripts/decidim/time_tracker/activity_ui.js.es6 +112 -0
  11. data/app/assets/javascripts/decidim/time_tracker/admin/assignations.js.es6 +35 -0
  12. data/app/assets/javascripts/decidim/time_tracker/reports/reports.js.es6 +19 -0
  13. data/app/assets/javascripts/decidim/time_tracker/time_tracker.js.es6 +82 -0
  14. data/app/assets/javascripts/decidim/time_tracker/timer_api.js.es6 +55 -0
  15. data/app/assets/stylesheets/decidim/time_tracker/_variables.scss +1 -0
  16. data/app/assets/stylesheets/decidim/time_tracker/admin/time_tracker.scss +59 -0
  17. data/app/assets/stylesheets/decidim/time_tracker/time_tracker.scss +118 -0
  18. data/app/cells/decidim/time_tracker/milestone/show.erb +42 -0
  19. data/app/cells/decidim/time_tracker/milestone_cell.rb +54 -0
  20. data/app/commands/decidim/time_tracker/admin/create_activity.rb +39 -0
  21. data/app/commands/decidim/time_tracker/admin/create_assignation.rb +75 -0
  22. data/app/commands/decidim/time_tracker/admin/create_task.rb +33 -0
  23. data/app/commands/decidim/time_tracker/admin/create_time_tracker.rb +76 -0
  24. data/app/commands/decidim/time_tracker/admin/destroy_activity.rb +43 -0
  25. data/app/commands/decidim/time_tracker/admin/destroy_assignation.rb +43 -0
  26. data/app/commands/decidim/time_tracker/admin/destroy_task.rb +43 -0
  27. data/app/commands/decidim/time_tracker/admin/export_time_tracker.rb +150 -0
  28. data/app/commands/decidim/time_tracker/admin/update_activity.rb +49 -0
  29. data/app/commands/decidim/time_tracker/admin/update_assignation.rb +42 -0
  30. data/app/commands/decidim/time_tracker/admin/update_task.rb +44 -0
  31. data/app/commands/decidim/time_tracker/create_milestone.rb +57 -0
  32. data/app/commands/decidim/time_tracker/create_request_assignation.rb +39 -0
  33. data/app/commands/decidim/time_tracker/start_time_event.rb +104 -0
  34. data/app/commands/decidim/time_tracker/stop_last_time_event.rb +45 -0
  35. data/app/controllers/concerns/decidim/time_tracker/admin/filterable_assignations.rb +50 -0
  36. data/app/controllers/decidim/time_tracker/activity_questionnaire_controller.rb +76 -0
  37. data/app/controllers/decidim/time_tracker/admin/activities_controller.rb +82 -0
  38. data/app/controllers/decidim/time_tracker/admin/activity_questionnaire_controller.rb +52 -0
  39. data/app/controllers/decidim/time_tracker/admin/application_controller.rb +22 -0
  40. data/app/controllers/decidim/time_tracker/admin/assignations_controller.rb +84 -0
  41. data/app/controllers/decidim/time_tracker/admin/assignee_questionnaire_controller.rb +58 -0
  42. data/app/controllers/decidim/time_tracker/admin/stats_controller.rb +24 -0
  43. data/app/controllers/decidim/time_tracker/admin/tasks_controller.rb +112 -0
  44. data/app/controllers/decidim/time_tracker/admin/time_tracker_exports_controller.rb +25 -0
  45. data/app/controllers/decidim/time_tracker/application_controller.rb +24 -0
  46. data/app/controllers/decidim/time_tracker/assignations_controller.rb +38 -0
  47. data/app/controllers/decidim/time_tracker/assignee_questionnaire_controller.rb +80 -0
  48. data/app/controllers/decidim/time_tracker/milestones_controller.rb +62 -0
  49. data/app/controllers/decidim/time_tracker/reports/user_controller.rb +38 -0
  50. data/app/controllers/decidim/time_tracker/time_events_controller.rb +68 -0
  51. data/app/controllers/decidim/time_tracker/time_tracker_controller.rb +43 -0
  52. data/app/forms/decidim/time_tracker/admin/activity_form.rb +27 -0
  53. data/app/forms/decidim/time_tracker/admin/assignation_form.rb +29 -0
  54. data/app/forms/decidim/time_tracker/admin/task_form.rb +22 -0
  55. data/app/forms/decidim/time_tracker/milestone_form.rb +32 -0
  56. data/app/forms/decidim/time_tracker/time_event_form.rb +47 -0
  57. data/app/helpers/decidim/time_tracker/admin/application_helper.rb +33 -0
  58. data/app/helpers/decidim/time_tracker/application_helper.rb +95 -0
  59. data/app/jobs/decidim/time_tracker/stop_counter_job.rb +29 -0
  60. data/app/models/decidim/time_tracker/activity.rb +135 -0
  61. data/app/models/decidim/time_tracker/application_record.rb +10 -0
  62. data/app/models/decidim/time_tracker/assignation.rb +68 -0
  63. data/app/models/decidim/time_tracker/assignee.rb +29 -0
  64. data/app/models/decidim/time_tracker/assignee_data.rb +19 -0
  65. data/app/models/decidim/time_tracker/milestone.rb +27 -0
  66. data/app/models/decidim/time_tracker/task.rb +44 -0
  67. data/app/models/decidim/time_tracker/time_event.rb +62 -0
  68. data/app/models/decidim/time_tracker/time_tracker.rb +46 -0
  69. data/app/models/decidim/time_tracker/tos_acceptance.rb +16 -0
  70. data/app/permissions/decidim/time_tracker/admin/permissions.rb +69 -0
  71. data/app/permissions/decidim/time_tracker/permissions.rb +69 -0
  72. data/app/presenters/decidim/time_tracker/admin_log/activity_presenter.rb +44 -0
  73. data/app/presenters/decidim/time_tracker/admin_log/assignation_presenter.rb +48 -0
  74. data/app/presenters/decidim/time_tracker/admin_log/task_presenter.rb +46 -0
  75. data/app/presenters/decidim/time_tracker/admin_log/value_types/activity_presenter.rb +28 -0
  76. data/app/presenters/decidim/time_tracker/admin_log/value_types/time_tracker_presenter.rb +28 -0
  77. data/app/views/decidim/time_tracker/admin/_breadcrumbs.html.erb +56 -0
  78. data/app/views/decidim/time_tracker/admin/activities/_form.html.erb +41 -0
  79. data/app/views/decidim/time_tracker/admin/activities/_index.html.erb +73 -0
  80. data/app/views/decidim/time_tracker/admin/activities/edit.html.erb +5 -0
  81. data/app/views/decidim/time_tracker/admin/activities/new.html.erb +5 -0
  82. data/app/views/decidim/time_tracker/admin/assignations/_form.html.erb +30 -0
  83. data/app/views/decidim/time_tracker/admin/assignations/index.html.erb +67 -0
  84. data/app/views/decidim/time_tracker/admin/assignations/new.html.erb +9 -0
  85. data/app/views/decidim/time_tracker/admin/questionnaires/_index.html.erb +27 -0
  86. data/app/views/decidim/time_tracker/admin/questionnaires/_questionnaire.html.erb +21 -0
  87. data/app/views/decidim/time_tracker/admin/stats/index.html.erb +50 -0
  88. data/app/views/decidim/time_tracker/admin/tasks/_assignations.html.erb +60 -0
  89. data/app/views/decidim/time_tracker/admin/tasks/_form.html.erb +7 -0
  90. data/app/views/decidim/time_tracker/admin/tasks/_index.html.erb +61 -0
  91. data/app/views/decidim/time_tracker/admin/tasks/edit.html.erb +16 -0
  92. data/app/views/decidim/time_tracker/admin/tasks/index.html.erb +5 -0
  93. data/app/views/decidim/time_tracker/admin/tasks/new.html.erb +12 -0
  94. data/app/views/decidim/time_tracker/milestones/index.html.erb +60 -0
  95. data/app/views/decidim/time_tracker/reports/user/index.html.erb +41 -0
  96. data/app/views/decidim/time_tracker/time_tracker/_activity.html.erb +43 -0
  97. data/app/views/decidim/time_tracker/time_tracker/_answer_questionnaire.html.erb +7 -0
  98. data/app/views/decidim/time_tracker/time_tracker/_assignee_data.html.erb +9 -0
  99. data/app/views/decidim/time_tracker/time_tracker/_callout_status.html.erb +3 -0
  100. data/app/views/decidim/time_tracker/time_tracker/_idle_activity.html.erb +27 -0
  101. data/app/views/decidim/time_tracker/time_tracker/_milestone_form.html.erb +35 -0
  102. data/app/views/decidim/time_tracker/time_tracker/_request_activity.html.erb +22 -0
  103. data/app/views/decidim/time_tracker/time_tracker/_sign_in.html.erb +5 -0
  104. data/app/views/decidim/time_tracker/time_tracker/index.html.erb +59 -0
  105. data/config/activity_questionnaire.yml +26 -0
  106. data/config/assignee_questionnaire.yml +37 -0
  107. data/config/i18n-tasks.yml +10 -0
  108. data/config/locales/ca.yml +394 -0
  109. data/config/locales/cs.yml +394 -0
  110. data/config/locales/en.yml +419 -0
  111. data/config/locales/es.yml +394 -0
  112. data/db/migrate/20200302115642_create_decidim_time_tracker_time_trackers.rb +10 -0
  113. data/db/migrate/20200302115654_create_decidim_time_tracker_tasks.rb +11 -0
  114. data/db/migrate/20200302115658_create_decidim_time_tracker_assignees.rb +12 -0
  115. data/db/migrate/20200302115700_create_decidim_time_tracker_activities.rb +19 -0
  116. data/db/migrate/20200302115708_create_decidim_time_tracker_milestones.rb +13 -0
  117. data/db/migrate/20200302115831_create_decidim_time_tracker_assignations.rb +16 -0
  118. data/db/migrate/20200302115840_create_decidim_time_tracker_time_events.rb +15 -0
  119. data/db/migrate/20201118153327_create_decidim_time_tracker_assignee_data.rb +10 -0
  120. data/db/migrate/20201223115130_create_decidim_time_tracker_tos_acceptances.rb +18 -0
  121. data/lib/decidim/time_tracker.rb +45 -0
  122. data/lib/decidim/time_tracker/admin.rb +10 -0
  123. data/lib/decidim/time_tracker/admin_engine.rb +51 -0
  124. data/lib/decidim/time_tracker/component.rb +248 -0
  125. data/lib/decidim/time_tracker/engine.rb +59 -0
  126. data/lib/decidim/time_tracker/reports.rb +10 -0
  127. data/lib/decidim/time_tracker/reports_engine.rb +34 -0
  128. data/lib/decidim/time_tracker/test/admin_log_presenter_examples.rb +34 -0
  129. data/lib/decidim/time_tracker/test/factories.rb +110 -0
  130. data/lib/decidim/time_tracker/test/time_tracker_context.rb +18 -0
  131. data/lib/decidim/time_tracker/time_tracker_activity_questionnaire_answers_serializer.rb +74 -0
  132. data/lib/decidim/time_tracker/version.rb +9 -0
  133. data/vendor/assets/javascripts/jsrender.min.js +4 -0
  134. metadata +276 -0
@@ -0,0 +1,55 @@
1
+ // Controlls time events
2
+
3
+ class TimerApi { // eslint-disable-line no-unused-vars
4
+ constructor(startEndpoint, stopEndpoint) {
5
+ this.startEndpoint = startEndpoint;
6
+ this.stopEndpoint = stopEndpoint;
7
+ this.csrfToken = $('meta[name=csrf-token]').attr('content');
8
+ }
9
+
10
+ start() {
11
+ const promise = $.Deferred();
12
+
13
+ $.ajax({
14
+ method: "POST",
15
+ url: this.startEndpoint,
16
+ contentType: "application/json",
17
+ headers: {
18
+ 'X-CSRF-Token': this.csrfToken
19
+ },
20
+ success: (data) => {
21
+ promise.resolve(data);
22
+ },
23
+ error: (jq) => {
24
+ const error = (jq.responseJSON && jq.responseJSON.error) || "Unkown error starting timer";
25
+ console.error("error starting time", error);
26
+ promise.reject(error);
27
+ }
28
+ });
29
+
30
+ return promise;
31
+ }
32
+
33
+ stop() {
34
+ const promise = $.Deferred();
35
+
36
+ $.ajax({
37
+ method: "POST",
38
+ url: this.stopEndpoint,
39
+ contentType: "application/json",
40
+ headers: {
41
+ 'X-CSRF-Token': this.csrfToken
42
+ },
43
+ success: (data) => {
44
+ promise.resolve(data);
45
+ },
46
+ error: (jq) => {
47
+ const error = (jq.responseJSON && jq.responseJSON.error) || "Unkown error stopping timer";
48
+ console.error("error stopping time", error);
49
+ promise.reject(error);
50
+ }
51
+ });
52
+
53
+ return promise;
54
+ }
55
+ }
@@ -0,0 +1,59 @@
1
+ @import "decidim/variables";
2
+
3
+ .action-icon {
4
+ &-success {
5
+ .icon {
6
+ color: $success;
7
+ }
8
+ }
9
+ }
10
+
11
+ .breadcrumbs {
12
+ margin-bottom: 0;
13
+
14
+ li {
15
+ font-size: 1em;
16
+ }
17
+ }
18
+
19
+ .activity--inactive {
20
+ opacity: 0.5;
21
+ }
22
+
23
+ .assignation- {
24
+ &-pending {
25
+ .assignation--status {
26
+ background-color: $warning;
27
+ }
28
+ td {
29
+ background-color: transparentize($warning, 0.9);
30
+ }
31
+ }
32
+ &-accepted {
33
+ .assignation--status {
34
+ background-color: $success;
35
+ }
36
+ td {
37
+ background-color: transparentize($success, 0.9);
38
+ }
39
+ }
40
+
41
+ &-rejected td {
42
+ opacity: 0.5;
43
+ }
44
+ }
45
+
46
+ .help-text + .form-error {
47
+ margin-top: 0rem;
48
+ }
49
+
50
+ .time-tracker {
51
+ &--clock {
52
+ span:not(:last-child)::after {
53
+ content: " ";
54
+ }
55
+ .text-muted {
56
+ opacity: 0.5;
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,118 @@
1
+ @import "decidim/variables";
2
+ @import "decidim/utils/settings";
3
+ @import "decidim/utils/mixins";
4
+ @import "decidim/time_tracker/variables";
5
+
6
+ .time-tracker {
7
+ &-activity,
8
+ &-request {
9
+ background-color: white;
10
+ border: 1px solid #eee;
11
+ padding: 1rem;
12
+ margin-bottom: 1rem;
13
+ h2 {
14
+ color: var(--secondary);
15
+ }
16
+ .content {
17
+ @media only screen and (min-width: 38em) {
18
+ display: grid;
19
+ grid-auto-flow: column;
20
+ grid-template-columns: 66.6% 33.3%;
21
+ }
22
+ @media only screen and (min-width: 50em) {
23
+ grid-template-columns: 75% 25%;
24
+ }
25
+ .left,
26
+ .right {
27
+ display: flex;
28
+ flex-direction: column;
29
+ }
30
+ .top {
31
+ flex: 1;
32
+ }
33
+ .bottom {
34
+ padding: 0.5em 0;
35
+ text-align: center;
36
+ }
37
+ }
38
+ .tracker-buttons {
39
+ text-align: right;
40
+ .button {
41
+ margin-left: 1rem;
42
+ }
43
+ .button:last-child {
44
+ margin-left: 0;
45
+ }
46
+ }
47
+ .callout {
48
+ margin: 0;
49
+ }
50
+ .elapsed-time {
51
+ width: max-content;
52
+ .elapsed-time-clock {
53
+ font-size: 2rem;
54
+ }
55
+ }
56
+ .milestone {
57
+ margin: 1em;
58
+ padding-top: 1em;
59
+ border-top: 1px solid #eee;
60
+ }
61
+ }
62
+ &--report {
63
+ .card--list__annotation {
64
+ margin-right: 1rem;
65
+ }
66
+ .card--list__heading {
67
+ margin-bottom: 0.5rem;
68
+ }
69
+ }
70
+ &--clock {
71
+ span:not(:last-child)::after {
72
+ content: " ";
73
+ }
74
+ .text-muted {
75
+ opacity: 0.5;
76
+ }
77
+ }
78
+ &--activity {
79
+ &:hover {
80
+ .card--list__item {
81
+ transition: background-color 200ms;
82
+ background-color: #fbfbfb;
83
+ }
84
+ }
85
+ }
86
+ &--assignee-data {
87
+ margin-bottom: 3rem;
88
+ }
89
+ &--start-button {
90
+ margin-top: 2rem;
91
+ margin-bottom: 1rem;
92
+ &-wrapper {
93
+ display: flex;
94
+ justify-content: center;
95
+ align-items: center;
96
+ }
97
+ }
98
+ }
99
+
100
+ .card--milestone {
101
+ .card__image.empty {
102
+ background: $light-gray;
103
+ display: flex;
104
+ align-items: center;
105
+ justify-content: center;
106
+ & > svg {
107
+ fill: $medium-gray;
108
+ font-size: 2rem;
109
+ }
110
+ }
111
+ }
112
+
113
+ .show-milestones {
114
+ color: $secondary;
115
+ &:hover {
116
+ text-decoration: underline;
117
+ }
118
+ }
@@ -0,0 +1,42 @@
1
+ <div class="column">
2
+ <article class="card card--milestone">
3
+ <%= image %>
4
+ <div class="card__content">
5
+ <div class="card__header">
6
+ <%= title %>
7
+ </div>
8
+
9
+ <div class="card__text">
10
+ <div class="card__text--paragraph">
11
+ <% if list? %>
12
+ <h5>
13
+ <%= model.title %>
14
+ </h5>
15
+ <% end %>
16
+
17
+ <div class="text-ellipsis">
18
+ <%= model.description %>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </div>
23
+
24
+ <div class="card__footer">
25
+ <div class="card__support">
26
+ <% if list? %>
27
+ <div class="text"> <%= t(".time_dedicated") %></div>
28
+ <div class="text">
29
+ <strong><%= clockify_seconds(seconds_elapsed) %></strong>
30
+ </div>
31
+ <% else %>
32
+ <div class="text">
33
+ <%= model.created_at.strftime("%d %b %Y") %>
34
+ </div>
35
+ <div class="text">
36
+ <%= model.created_at.strftime("%H:%M") %>
37
+ </div>
38
+ <% end %>
39
+ </div>
40
+ </div>
41
+ </article>
42
+ </div>
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class MilestoneCell < Decidim::ViewModel
6
+ include Decidim::SanitizeHelper
7
+ include Decidim::CardHelper
8
+ include Decidim::IconHelper
9
+ include Decidim::TimeTracker::ApplicationHelper
10
+
11
+ view_paths << "#{Decidim::TimeTracker::Engine.root}/app/cells/decidim/time_tracker/milestone"
12
+
13
+ def show
14
+ render
15
+ end
16
+
17
+ def list?
18
+ options[:type] == :list
19
+ end
20
+
21
+ def title
22
+ return content_tag :strong, model.title unless list?
23
+
24
+ link_to milestones_path do
25
+ content_tag :h4, class: "card__title" do
26
+ t("title", user_name: model.user.name, scope: "decidim.time_tracker.milestone")
27
+ end
28
+ end
29
+ end
30
+
31
+ def image
32
+ image_url = model.attachments&.first&.url
33
+
34
+ if image_url.present?
35
+ link_to milestones_path, class: "card__link" do
36
+ image_tag image_url, class: "card__image"
37
+ end
38
+ else
39
+ content_tag :div, class: "card__image empty" do
40
+ icon "timer"
41
+ end
42
+ end
43
+ end
44
+
45
+ def seconds_elapsed
46
+ @seconds_elapsed ||= model.activity.user_seconds_elapsed(model.user)
47
+ end
48
+
49
+ def milestones_path
50
+ Decidim::EngineRouter.main_proxy(model.activity.task.component).milestones_path(nickname: model.user.nickname)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ module Admin
6
+ # A command with all the business logic when creating a activity
7
+ class CreateActivity < Rectify::Command
8
+ def initialize(form, task)
9
+ @form = form
10
+ @task = task
11
+ end
12
+
13
+ # Creates the activity if valid.
14
+ #
15
+ # Broadcasts :ok if successful, :invalid otherwise.
16
+ def call
17
+ return broadcast(:invalid) if @form.invalid?
18
+
19
+ create_activity
20
+ broadcast(:ok)
21
+ end
22
+
23
+ def create_activity
24
+ Decidim.traceability.create!(
25
+ Decidim::TimeTracker::Activity,
26
+ @form.current_user,
27
+ description: @form.description,
28
+ active: @form.active,
29
+ start_date: @form.start_date,
30
+ end_date: @form.end_date,
31
+ max_minutes_per_day: @form.max_minutes_per_day,
32
+ requests_start_at: @form.requests_start_at,
33
+ task: @task
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ module Admin
6
+ # A command with all the business logic when creating a assignation
7
+ class CreateAssignation < Rectify::Command
8
+ def initialize(form, activity)
9
+ @form = form
10
+ @activity = activity
11
+ end
12
+
13
+ # Creates the assignation if valid.
14
+ #
15
+ # Broadcasts :ok if successful, :invalid otherwise.
16
+ def call
17
+ return broadcast(:invalid) if @form.invalid?
18
+
19
+ ActiveRecord::Base.transaction do
20
+ @user ||= existing_user || new_user
21
+ create_assignation
22
+ end
23
+
24
+ broadcast(:ok)
25
+ end
26
+
27
+ def create_assignation
28
+ Decidim.traceability.create!(
29
+ Decidim::TimeTracker::Assignation,
30
+ @form.current_user,
31
+ activity: @activity,
32
+ user: @user,
33
+ status: :accepted,
34
+ invited_at: Time.current,
35
+ invited_by_user: @form.current_user
36
+ )
37
+ end
38
+
39
+ def existing_user
40
+ return @existing_user if defined?(@existing_user)
41
+
42
+ if @form.existing_user
43
+ @existing_user = @form.user
44
+ else
45
+ @existing_user = User.find_by(
46
+ email: @form.email,
47
+ organization: @activity.task.time_tracker.component.organization
48
+ )
49
+
50
+ InviteUserAgain.call(@existing_user, "invite_private_user") if @existing_user && !@existing_user.invitation_accepted?
51
+ end
52
+
53
+ @existing_user
54
+ end
55
+
56
+ def new_user
57
+ @new_user ||= InviteUser.call(user_form) do
58
+ on(:ok) do |user|
59
+ return user
60
+ end
61
+ end
62
+ end
63
+
64
+ def user_form
65
+ OpenStruct.new(name: @form.name,
66
+ email: @form.email.downcase,
67
+ organization: @activity.task.time_tracker.component.organization,
68
+ admin: false,
69
+ invited_by: @form.current_user,
70
+ invitation_instructions: "invite_private_user")
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end