clockface 1.0.0.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +210 -0
- data/Rakefile +22 -0
- data/app/assets/config/clockface_manifest.js +2 -0
- data/app/assets/images/clockface/clockface.svg +34 -0
- data/app/assets/javascripts/clockface/application.js +17 -0
- data/app/assets/javascripts/clockface/flash.js +7 -0
- data/app/assets/javascripts/clockface/sorttable.js +494 -0
- data/app/assets/stylesheets/clockface/application.scss +80 -0
- data/app/assets/stylesheets/clockface/application/_fonts.scss +2 -0
- data/app/assets/stylesheets/clockface/application/colors.scss +8 -0
- data/app/assets/stylesheets/clockface/application/flash.scss +6 -0
- data/app/assets/stylesheets/clockface/application/footer.scss +37 -0
- data/app/assets/stylesheets/clockface/application/nav.scss +51 -0
- data/app/assets/stylesheets/clockface/events/delete.scss +45 -0
- data/app/assets/stylesheets/clockface/events/event_form.scss +62 -0
- data/app/assets/stylesheets/clockface/events/index.scss +56 -0
- data/app/assets/stylesheets/clockface/tasks/delete.scss +29 -0
- data/app/assets/stylesheets/clockface/tasks/index.scss +47 -0
- data/app/assets/stylesheets/clockface/tasks/task_form.scss +20 -0
- data/app/controllers/clockface/application_controller.rb +20 -0
- data/app/controllers/clockface/events_controller.rb +151 -0
- data/app/controllers/clockface/root_controller.rb +7 -0
- data/app/controllers/clockface/tasks_controller.rb +137 -0
- data/app/events/clockface/application_job.rb +4 -0
- data/app/helpers/clockface/application_helper.rb +4 -0
- data/app/helpers/clockface/config_helper.rb +32 -0
- data/app/helpers/clockface/events_helper.rb +37 -0
- data/app/helpers/clockface/logging_helper.rb +12 -0
- data/app/mailers/clockface/application_mailer.rb +6 -0
- data/app/models/clockface/application_record.rb +7 -0
- data/app/models/clockface/event.rb +179 -0
- data/app/models/clockface/task.rb +12 -0
- data/app/presenters/clockface/events_presenter.rb +48 -0
- data/app/services/clockface/event_validation_interactor.rb +35 -0
- data/app/services/clockface/task_validation_interactor.rb +25 -0
- data/app/views/clockface/application/_flash.html.erb +25 -0
- data/app/views/clockface/application/_footer.html.erb +15 -0
- data/app/views/clockface/application/_nav.html.erb +19 -0
- data/app/views/clockface/events/_event_form.html.erb +130 -0
- data/app/views/clockface/events/delete.html.erb +124 -0
- data/app/views/clockface/events/edit.html.erb +14 -0
- data/app/views/clockface/events/index.html.erb +108 -0
- data/app/views/clockface/events/new.html.erb +14 -0
- data/app/views/clockface/tasks/_task_form.html.erb +57 -0
- data/app/views/clockface/tasks/delete.html.erb +83 -0
- data/app/views/clockface/tasks/edit.html.erb +14 -0
- data/app/views/clockface/tasks/index.html.erb +70 -0
- data/app/views/clockface/tasks/new.html.erb +14 -0
- data/app/views/layouts/clockface/application.html.erb +27 -0
- data/config/locales/en.yml +158 -0
- data/config/routes.rb +15 -0
- data/db/migrate/20170528230549_create_clockface_tasks.rb +10 -0
- data/db/migrate/20170528234810_create_clockface_events.rb +20 -0
- data/lib/clockface.rb +135 -0
- data/lib/clockface/engine.rb +79 -0
- data/lib/clockface/version.rb +3 -0
- data/lib/clockwork/database_events/synchronizer.rb +73 -0
- data/lib/tasks/clockface_tasks.rake +4 -0
- metadata +199 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Clockface
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
include ConfigHelper
|
|
4
|
+
include LoggingHelper
|
|
5
|
+
|
|
6
|
+
CAPTCHA_LENGTH = 5
|
|
7
|
+
|
|
8
|
+
protect_from_forgery with: :exception
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def xhr_request?
|
|
13
|
+
(defined? request) && request.xhr?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def captcha_for(obj)
|
|
17
|
+
Digest::SHA1.hexdigest(obj.id.to_s).first(CAPTCHA_LENGTH)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
module Clockface
|
|
2
|
+
class EventsController < ApplicationController
|
|
3
|
+
CAPTCHA_LENGTH = 5
|
|
4
|
+
|
|
5
|
+
def index
|
|
6
|
+
@events = all_events.map { |event| Clockface::EventsPresenter.new(event) }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create
|
|
13
|
+
event = Clockface::Event.new(events_params_for_create)
|
|
14
|
+
validation = validate_event(event)
|
|
15
|
+
|
|
16
|
+
if validation.failure?
|
|
17
|
+
flash[:error] = validation.errors
|
|
18
|
+
redirect_to clockface.new_event_path
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
event.save
|
|
23
|
+
flash[:success] = t("clockface.events.create.success")
|
|
24
|
+
clockface_log(:info, "Created Event: #{event.inspect}")
|
|
25
|
+
redirect_to clockface.events_path
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def edit
|
|
29
|
+
@event = Clockface::Event.find_by_id(params[:id])
|
|
30
|
+
return if @event
|
|
31
|
+
|
|
32
|
+
redirect_to events_path
|
|
33
|
+
flash[:error] = t("clockface.events.edit.validation.invalid_id")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def update
|
|
37
|
+
event = Clockface::Event.find_by_id(params[:id])
|
|
38
|
+
|
|
39
|
+
unless event
|
|
40
|
+
flash[:error] =
|
|
41
|
+
t("clockface.events.update.event_not_found", id: params[:id])
|
|
42
|
+
redirect_to events_path
|
|
43
|
+
return
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
event.attributes = events_params_for_update
|
|
47
|
+
validation = validate_event(event)
|
|
48
|
+
|
|
49
|
+
if validation.success?
|
|
50
|
+
event.save
|
|
51
|
+
flash[:success] = t("clockface.events.update.success")
|
|
52
|
+
clockface_log(:info, "Updated Event: #{event.inspect}")
|
|
53
|
+
redirect_to clockface.events_path
|
|
54
|
+
else
|
|
55
|
+
flash[:error] = validation.errors
|
|
56
|
+
redirect_to clockface.edit_event_path(event)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete
|
|
61
|
+
event = Clockface::Event.find_by_id(params[:event_id])
|
|
62
|
+
|
|
63
|
+
unless event
|
|
64
|
+
redirect_to events_path
|
|
65
|
+
flash[:error] = t("clockface.events.delete.validation.invalid_id")
|
|
66
|
+
return
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
@event = Clockface::EventsPresenter.new(event)
|
|
70
|
+
@captcha = captcha_for(event)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def destroy
|
|
74
|
+
event = Clockface::Event.find_by_id(params[:id])
|
|
75
|
+
|
|
76
|
+
unless event
|
|
77
|
+
flash[:error] =
|
|
78
|
+
t("clockface.events.destroy.event_not_found", id: params[:id])
|
|
79
|
+
redirect_to events_path
|
|
80
|
+
return
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if (params[:captcha] || "") != captcha_for(event)
|
|
84
|
+
flash[:error] =
|
|
85
|
+
t("clockface.events.destroy.validation.incorrect_captcha")
|
|
86
|
+
redirect_to event_delete_path(event)
|
|
87
|
+
return
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
unless event.destroy
|
|
91
|
+
flash[:error] = t("clockface.events.destroy.failure")
|
|
92
|
+
redirect_to event_delete_path(event)
|
|
93
|
+
return
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
flash[:success] = t("clockface.events.destroy.success")
|
|
97
|
+
clockface_log(:info, "Destroyed Event: #{event.inspect}")
|
|
98
|
+
redirect_to events_path
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def all_events
|
|
104
|
+
Clockface::Event.includes(:task).order(:id)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def events_params_for_create
|
|
108
|
+
params.require(:event).permit(
|
|
109
|
+
:clockface_task_id,
|
|
110
|
+
:name,
|
|
111
|
+
:enabled,
|
|
112
|
+
:period_value,
|
|
113
|
+
:period_units,
|
|
114
|
+
:day_of_week,
|
|
115
|
+
:hour,
|
|
116
|
+
:minute,
|
|
117
|
+
:time_zone,
|
|
118
|
+
:if_condition
|
|
119
|
+
).tap do |params|
|
|
120
|
+
params[:hour] = nil if params[:hour] == "**"
|
|
121
|
+
params[:minute] = nil if params[:minute] == "**"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def events_params_for_update
|
|
126
|
+
params.require(:event).permit(
|
|
127
|
+
:name,
|
|
128
|
+
:enabled,
|
|
129
|
+
:period_value,
|
|
130
|
+
:period_units,
|
|
131
|
+
:day_of_week,
|
|
132
|
+
:hour,
|
|
133
|
+
:minute,
|
|
134
|
+
:time_zone,
|
|
135
|
+
:if_condition
|
|
136
|
+
).tap do |params|
|
|
137
|
+
params[:hour] = nil if params[:hour] == "**"
|
|
138
|
+
params[:minute] = nil if params[:minute] == "**"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def validate_event(event)
|
|
143
|
+
Clockface::EventValidationInteractor.
|
|
144
|
+
call(event: event, action: params[:action])
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def captcha_for(event)
|
|
148
|
+
Digest::SHA1.hexdigest(event.id.to_s).first(CAPTCHA_LENGTH)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
module Clockface
|
|
2
|
+
class TasksController < ApplicationController
|
|
3
|
+
def index
|
|
4
|
+
@tasks = all_tasks
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create
|
|
11
|
+
task = Clockface::Task.new(tasks_params_for_create)
|
|
12
|
+
validation = validate_task(task)
|
|
13
|
+
|
|
14
|
+
if validation.failure?
|
|
15
|
+
flash[:error] = validation.errors
|
|
16
|
+
redirect_to clockface.new_task_path
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
task.save
|
|
21
|
+
flash[:success] = t("clockface.tasks.create.success")
|
|
22
|
+
clockface_log(:info, "Created Task: #{task.inspect}")
|
|
23
|
+
redirect_to clockface.tasks_path
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def edit
|
|
27
|
+
@task = Clockface::Task.find_by_id(params[:id])
|
|
28
|
+
return if @task
|
|
29
|
+
|
|
30
|
+
redirect_to tasks_path
|
|
31
|
+
flash[:error] = t("clockface.tasks.edit.validation.invalid_id")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def update
|
|
35
|
+
task = Clockface::Task.find_by_id(params[:id])
|
|
36
|
+
|
|
37
|
+
unless task
|
|
38
|
+
flash[:error] =
|
|
39
|
+
t("clockface.tasks.update.task_not_found", id: params[:id])
|
|
40
|
+
redirect_to tasks_path
|
|
41
|
+
return
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
task.attributes = tasks_params_for_update
|
|
45
|
+
validation = validate_task(task)
|
|
46
|
+
|
|
47
|
+
if validation.success?
|
|
48
|
+
task.save
|
|
49
|
+
flash[:success] = t("clockface.tasks.update.success")
|
|
50
|
+
clockface_log(:info, "Updated Task: #{task.inspect}")
|
|
51
|
+
redirect_to clockface.tasks_path
|
|
52
|
+
else
|
|
53
|
+
flash[:error] = validation.errors
|
|
54
|
+
redirect_to clockface.edit_task_path(task)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def delete
|
|
59
|
+
@task = Clockface::Task.find_by_id(params[:task_id])
|
|
60
|
+
|
|
61
|
+
unless @task
|
|
62
|
+
redirect_to tasks_path
|
|
63
|
+
flash[:error] = t("clockface.tasks.delete.validation.invalid_id")
|
|
64
|
+
return
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
@captcha = captcha_for(@task)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def destroy
|
|
71
|
+
task = Clockface::Task.find_by_id(params[:id])
|
|
72
|
+
|
|
73
|
+
unless task
|
|
74
|
+
flash[:error] =
|
|
75
|
+
t("clockface.tasks.destroy.task_not_found", id: params[:id])
|
|
76
|
+
redirect_to tasks_path
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# TODO: Move this to interactor
|
|
81
|
+
|
|
82
|
+
if (params[:captcha] || "") != captcha_for(task)
|
|
83
|
+
flash[:error] =
|
|
84
|
+
t("clockface.tasks.destroy.validation.incorrect_captcha")
|
|
85
|
+
redirect_to task_delete_path(task)
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if task.events.any?
|
|
90
|
+
flash[:error] =
|
|
91
|
+
t(
|
|
92
|
+
"clockface.tasks.destroy.validation.events_exist",
|
|
93
|
+
count: task.events.count
|
|
94
|
+
)
|
|
95
|
+
redirect_to task_delete_path(task)
|
|
96
|
+
return
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
unless task.destroy
|
|
100
|
+
flash[:error] = t("clockface.tasks.destroy.failure")
|
|
101
|
+
redirect_to task_delete_path(task)
|
|
102
|
+
return
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
flash[:success] = t("clockface.tasks.destroy.success")
|
|
106
|
+
clockface_log(:info, "Destroyed Task: #{task.inspect}")
|
|
107
|
+
redirect_to tasks_path
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def all_tasks
|
|
113
|
+
Clockface::Task.includes(:events).order(:id)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def tasks_params_for_create
|
|
117
|
+
params.require(:task).permit(
|
|
118
|
+
:name,
|
|
119
|
+
:description,
|
|
120
|
+
:command
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def tasks_params_for_update
|
|
125
|
+
params.require(:task).permit(
|
|
126
|
+
:name,
|
|
127
|
+
:description,
|
|
128
|
+
:command
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def validate_task(task)
|
|
133
|
+
Clockface::TaskValidationInteractor.
|
|
134
|
+
call(task: task, action: params[:action])
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Clockface
|
|
2
|
+
module ConfigHelper
|
|
3
|
+
def clockface_time_zone
|
|
4
|
+
tz = Clockface::Engine.config.clockface.time_zone
|
|
5
|
+
ActiveSupport::TimeZone::MAPPING.key?(tz) ? tz : "UTC"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def clockface_tenant_list
|
|
9
|
+
Clockface::Engine.config.clockface.tenant_list
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def clockface_single_tenancy_enabled?
|
|
13
|
+
clockface_tenant_list.empty?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def clockface_multi_tenancy_enabled?
|
|
17
|
+
clockface_tenant_list.any?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def clockface_current_tenant
|
|
21
|
+
Clockface::Engine.config.clockface.current_tenant_proc.call
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clockface_execute_in_tenant(tenant_name, some_proc, proc_args = [])
|
|
25
|
+
Clockface::Engine.config.clockface.execute_in_tenant_proc.call(
|
|
26
|
+
tenant_name,
|
|
27
|
+
some_proc,
|
|
28
|
+
proc_args
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Clockface
|
|
2
|
+
module EventsHelper
|
|
3
|
+
include ConfigHelper
|
|
4
|
+
|
|
5
|
+
def event_form_select_options_for_name
|
|
6
|
+
Clockface::Task.order(:id).collect { |e| [e.name, e.id] }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def event_form_select_options_for_period_units
|
|
10
|
+
Clockface::Event::PERIOD_UNITS.map do |unit|
|
|
11
|
+
[t("datetime.units.#{unit}"), unit]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def event_form_select_options_for_day_of_week
|
|
16
|
+
t("date.day_names").each_with_index.map { |day, i| [day, i] }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def event_form_select_options_for_hour
|
|
20
|
+
(["**"] + (0..23).to_a).map { |h| [h.to_s.rjust(2, "0"), h] }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def event_form_select_options_for_minute
|
|
24
|
+
(["**"] + (0..59).to_a).map { |m| [m.to_s.rjust(2, "0"), m] }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def event_form_select_options_for_if_condition
|
|
28
|
+
Clockface::Event::IF_CONDITIONS.keys.map do |if_condition|
|
|
29
|
+
[
|
|
30
|
+
Clockface::Event.
|
|
31
|
+
human_attribute_name("if_condition.#{if_condition}"),
|
|
32
|
+
if_condition
|
|
33
|
+
]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Clockface
|
|
2
|
+
module LoggingHelper
|
|
3
|
+
def clockface_log(level, msg)
|
|
4
|
+
# Clockface logger can be a single `Logger` or an array of many `Logger`s
|
|
5
|
+
logs = Clockface::Engine.config.clockface.logger
|
|
6
|
+
logs = [logs] unless logs.is_a?(Array)
|
|
7
|
+
|
|
8
|
+
# Log to each individual logger
|
|
9
|
+
logs.each { |log| log.send(level, "[Clockface] #{msg}") }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
module Clockface
|
|
2
|
+
class Event < ApplicationRecord
|
|
3
|
+
extend Forwardable
|
|
4
|
+
|
|
5
|
+
PERIOD_UNITS = %w[seconds minutes hours days weeks months years].freeze
|
|
6
|
+
IF_CONDITIONS = {
|
|
7
|
+
"even_week" => ->(time) { time.strftime("%W").to_i.even? },
|
|
8
|
+
"odd_week" => ->(time) { time.strftime("%W").to_i.odd? },
|
|
9
|
+
"weekday" => ->(time) { (time.strftime("%a")[0] != "S") },
|
|
10
|
+
"first_of_month" => ->(time) { time.strftime("%-d").to_i == 1 },
|
|
11
|
+
"last_of_month" => ->(time) { (time + 1.day).strftime("%-d").to_i == 1 }
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
belongs_to(
|
|
15
|
+
:task,
|
|
16
|
+
foreign_key: "clockface_task_id",
|
|
17
|
+
class_name: "Clockface::Task"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
before_validation do
|
|
21
|
+
self[:enabled] = false if self[:enabled].nil?
|
|
22
|
+
self[:time_zone] = clockface_time_zone if self[:time_zone].blank?
|
|
23
|
+
self[:if_condition] = nil if self[:if_condition].blank?
|
|
24
|
+
default_tenant_if_needed
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# rubocop:disable LineLength
|
|
28
|
+
validates :period_value, presence: true, numericality: { greater_than: 0 }
|
|
29
|
+
validates :period_units, presence: true, inclusion: PERIOD_UNITS
|
|
30
|
+
validates :day_of_week, inclusion: { in: 0..6 }, allow_nil: true
|
|
31
|
+
validates :hour, inclusion: { in: 0..23 }, allow_nil: true
|
|
32
|
+
validates :minute, inclusion: { in: 0..59 }, allow_nil: true
|
|
33
|
+
validates :time_zone, inclusion: ActiveSupport::TimeZone::MAPPING.keys, allow_nil: true
|
|
34
|
+
validates :if_condition, inclusion: IF_CONDITIONS.keys, allow_nil: true
|
|
35
|
+
# rubocop:enable LineLength
|
|
36
|
+
|
|
37
|
+
with_options if: proc { clockface_multi_tenancy_enabled? } do |x|
|
|
38
|
+
x.validate :tenant_is_valid
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
with_options if: proc { !clockface_multi_tenancy_enabled? } do |x|
|
|
42
|
+
x.validates :tenant, absence: true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
validate :day_of_week_must_have_timestamp
|
|
46
|
+
|
|
47
|
+
def_delegators(
|
|
48
|
+
:task,
|
|
49
|
+
:name,
|
|
50
|
+
:description,
|
|
51
|
+
:command
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def self.find_duplicates_of(event)
|
|
55
|
+
Clockface::Event.where(
|
|
56
|
+
period_value: event.period_value,
|
|
57
|
+
period_units: event.period_units,
|
|
58
|
+
day_of_week: event.day_of_week,
|
|
59
|
+
hour: event.hour,
|
|
60
|
+
minute: event.minute,
|
|
61
|
+
time_zone: event.time_zone,
|
|
62
|
+
if_condition: event.if_condition
|
|
63
|
+
).where.not(id: event.id)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def period
|
|
67
|
+
# e.g. period_value: 2, period_units: weeks
|
|
68
|
+
# => 2.weeks
|
|
69
|
+
period_value.send(period_units.to_sym)
|
|
70
|
+
end
|
|
71
|
+
# Note: Clockwork refers to this value as the period internally, but
|
|
72
|
+
# `sync_database_events` expects this model to respond to `:frequency`
|
|
73
|
+
# Keep consistent with internal language by using period, but add alias
|
|
74
|
+
# for compatibility.
|
|
75
|
+
# It's also extra confusing since in most fields (e.g. Physics) the words
|
|
76
|
+
# 'period' and 'frequency' are inverses of each other... oh well.
|
|
77
|
+
alias frequency period
|
|
78
|
+
|
|
79
|
+
def at
|
|
80
|
+
return nil if self[:hour].nil? && self[:minute].nil?
|
|
81
|
+
|
|
82
|
+
[
|
|
83
|
+
at_formatted_day_of_week,
|
|
84
|
+
[at_formatted_hour, at_formatted_minute].join(":")
|
|
85
|
+
].compact.join(" ")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def tz
|
|
89
|
+
# Active Support stores a mapping between human readable and IANA time
|
|
90
|
+
# zones. These mappings can be found in `ActiveSupport::TimeZone::MAPPING`
|
|
91
|
+
#
|
|
92
|
+
# e.g.
|
|
93
|
+
#
|
|
94
|
+
# "Chennai" => "Asia/Kolkata"
|
|
95
|
+
# "Kathmandu" => "Asia/Kathmandu"
|
|
96
|
+
# "Tokyo" => "Asia/Tokyo"
|
|
97
|
+
#
|
|
98
|
+
# Since multiple human names can point to the same IANA time zone, we
|
|
99
|
+
# store the human readable name in the underlying `time_zone` DB field
|
|
100
|
+
#
|
|
101
|
+
# The Clockwork API dictates that the model must respond to `:tz` and
|
|
102
|
+
# return the IANA name (See `Clockwork::Event#convert_timezone`), so we
|
|
103
|
+
# convert the value using ActiveSupport Mapping first
|
|
104
|
+
#
|
|
105
|
+
ActiveSupport::TimeZone::MAPPING[self[:time_zone]]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def if?(time)
|
|
109
|
+
if self[:if_condition].present?
|
|
110
|
+
IF_CONDITIONS[self[:if_condition]].call(time)
|
|
111
|
+
else
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def if=(if_condition)
|
|
117
|
+
self[:if_condition] = if_condition
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def ignored_attributes
|
|
121
|
+
# Every time Clockwork reloads the models from the database it compares
|
|
122
|
+
# the before/after attributes to see if the model `has_changed?`. If any
|
|
123
|
+
# attributes have been changed, it reloads the task.
|
|
124
|
+
# The Clockwork API lets us selectively ignore some fields in this
|
|
125
|
+
# attribute comparison.
|
|
126
|
+
# Exclude `last_triggered_at` and `updated_at` since they will always
|
|
127
|
+
# change on each run
|
|
128
|
+
%i[last_triggered_at updated_at]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
# rubocop:disable Style/GuardClause
|
|
134
|
+
|
|
135
|
+
def default_tenant_if_needed
|
|
136
|
+
if clockface_multi_tenancy_enabled? && self[:tenant].blank?
|
|
137
|
+
self[:tenant] = clockface_current_tenant
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def tenant_is_valid
|
|
142
|
+
if self[:tenant] != clockface_current_tenant
|
|
143
|
+
errors.add(
|
|
144
|
+
:tenant,
|
|
145
|
+
I18n.t(
|
|
146
|
+
"activerecord.errors.models.clockface/event."\
|
|
147
|
+
"attributes.tenant.invalid"
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def day_of_week_must_have_timestamp
|
|
154
|
+
if self[:hour].nil? && self[:minute].nil? && !self[:day_of_week].nil?
|
|
155
|
+
errors.add(
|
|
156
|
+
:day_of_week,
|
|
157
|
+
I18n.t(
|
|
158
|
+
"activerecord.errors.models.clockface/event."\
|
|
159
|
+
"attributes.day_of_week.day_of_week_must_have_timestamp"
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# rubocop:enable Style/GuardClause
|
|
166
|
+
|
|
167
|
+
def at_formatted_day_of_week
|
|
168
|
+
Date::DAYNAMES[self[:day_of_week]] if self[:day_of_week].present?
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def at_formatted_hour
|
|
172
|
+
self[:hour].present? ? self[:hour].to_s.rjust(2, "0") : "**"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def at_formatted_minute
|
|
176
|
+
self[:minute].present? ? self[:minute].to_s.rjust(2, "0") : "**"
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|