rails_execution 0.1.5 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -0
- data/app/assets/javascripts/executions/base.js +9 -0
- data/app/assets/javascripts/executions/flatpickr.min.js +2 -0
- data/app/assets/stylesheets/executions/flatpickr.min.css +13 -0
- data/app/assets/stylesheets/executions/modify.scss +11 -6
- data/app/assets/stylesheets/executions/style.scss +153 -0
- data/app/controllers/rails_execution/labels_controller.rb +29 -0
- data/app/controllers/rails_execution/tasks_controller.rb +84 -24
- data/app/helpers/rails_execution/base_helper.rb +4 -2
- data/app/helpers/rails_execution/policy_helper.rb +16 -0
- data/app/helpers/rails_execution/rendering_helper.rb +28 -4
- data/app/models/rails_execution/label.rb +15 -0
- data/app/models/rails_execution/task.rb +71 -3
- data/app/models/rails_execution/task_label.rb +6 -0
- data/app/services/rails_execution/tasks/create_service.rb +62 -0
- data/app/services/rails_execution/tasks/filter_service.rb +49 -0
- data/app/views/rails_execution/shared/_paging.html.haml +1 -1
- data/app/views/rails_execution/tasks/_actions.html.haml +3 -1
- data/app/views/rails_execution/tasks/_activities.html.haml +16 -10
- data/app/views/rails_execution/tasks/_advanced_filter.html.haml +115 -0
- data/app/views/rails_execution/tasks/_attachment_files.html.haml +2 -11
- data/app/views/rails_execution/tasks/_attachments.html.haml +13 -0
- data/app/views/rails_execution/tasks/_form.html.haml +50 -14
- data/app/views/rails_execution/tasks/_label_collection_select.html.haml +20 -0
- data/app/views/rails_execution/tasks/_labels.html.haml +2 -0
- data/app/views/rails_execution/tasks/_new_label_modal_form.html.haml +24 -0
- data/app/views/rails_execution/tasks/_quick_filter.html.haml +25 -0
- data/app/views/rails_execution/tasks/_schedule.html.haml +22 -0
- data/app/views/rails_execution/tasks/_task.html.haml +22 -18
- data/app/views/rails_execution/tasks/closed.html.haml +2 -1
- data/app/views/rails_execution/tasks/completed.html.haml +2 -1
- data/app/views/rails_execution/tasks/index.html.haml +2 -1
- data/app/views/rails_execution/tasks/show.html.haml +3 -1
- data/config/routes.rb +2 -1
- data/lib/generators/rails_execution/templates/config.rb.tt +8 -0
- data/lib/generators/rails_execution/templates/install.rb.tt +24 -7
- data/lib/rails_execution/config.rb +20 -7
- data/lib/rails_execution/services/background_execution.rb +56 -0
- data/lib/rails_execution/services/create_scheduled_job.rb +61 -0
- data/lib/rails_execution/services/remove_scheduled_job.rb +20 -0
- data/lib/rails_execution/services/task_scheduler.rb +35 -0
- data/lib/rails_execution/version.rb +1 -1
- data/lib/rails_execution.rb +4 -0
- metadata +21 -2
@@ -2,10 +2,12 @@
|
|
2
2
|
|
3
3
|
module RailsExecution
|
4
4
|
class TasksController < ::RailsExecution::BaseController
|
5
|
+
LIMITED_FILTER_LIST = 100
|
5
6
|
|
6
7
|
def index
|
7
8
|
paging = ::RailsExecution::Services::Paging.new(page: params[:page], per_page: params[:per_page])
|
8
|
-
processing_tasks = ::RailsExecution::Task.processing.
|
9
|
+
processing_tasks = ::RailsExecution::Task.processing.preload(:owner, :labels)
|
10
|
+
processing_tasks = ::RailsExecution::Tasks::FilterService.new(processing_tasks, params, current_owner&.id).call
|
9
11
|
@tasks = paging.call(processing_tasks)
|
10
12
|
end
|
11
13
|
|
@@ -18,21 +20,11 @@ module RailsExecution
|
|
18
20
|
def create
|
19
21
|
raise(::RailsExecution::AccessDeniedError, 'Create task') unless can_create_task?
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
description: params.dig(:task, :description),
|
27
|
-
script: params.dig(:task, :script),
|
28
|
-
})
|
29
|
-
@task.assign_reviewers(params.dig(:task, :task_review_ids).to_a)
|
30
|
-
@task.syntax_status = ::RailsExecution::Services::SyntaxChecker.new(@task.script).call ? 'good' : 'bad'
|
31
|
-
|
32
|
-
if @task.save
|
33
|
-
@task.add_files(params[:attachments]&.permit!.to_h, current_owner) if ::RailsExecution.configuration.file_upload
|
34
|
-
::RailsExecution.configuration.notifier.new(@task).after_create
|
35
|
-
flash[:notice] = 'Create the request is successful!'
|
23
|
+
result = ::RailsExecution::Tasks::CreateService.new(params, current_owner).call
|
24
|
+
@task = result.task
|
25
|
+
if result.error.nil?
|
26
|
+
::RailsExecution::Services::CreateScheduledJob.new(@task).call if in_solo_mode? && can_schedule_task?(@task)
|
27
|
+
flash[:notice] = 'Create request successfully!'
|
36
28
|
redirect_to action: :index
|
37
29
|
else
|
38
30
|
render action: :new
|
@@ -47,9 +39,12 @@ module RailsExecution
|
|
47
39
|
owner_id: current_owner&.id,
|
48
40
|
owner_type: ::RailsExecution.configuration.owner_model.to_s,
|
49
41
|
title: current_task.title,
|
42
|
+
scheduled_at: current_task.scheduled_at,
|
43
|
+
repeat_mode: current_task.repeat_mode,
|
50
44
|
description: current_task.description,
|
51
45
|
script: current_task.script,
|
52
46
|
})
|
47
|
+
@task.labels = current_task.labels
|
53
48
|
@task.syntax_status = ::RailsExecution::Services::SyntaxChecker.new(@task.script).call ? 'good' : 'bad'
|
54
49
|
|
55
50
|
render action: :new
|
@@ -65,6 +60,7 @@ module RailsExecution
|
|
65
60
|
end
|
66
61
|
|
67
62
|
if current_task.update(status: :closed)
|
63
|
+
::RailsExecution::Services::RemoveScheduledJob.new(current_task).call if can_remove_scheduled_job?(current_task)
|
68
64
|
current_task.activities.create(owner: current_owner, message: 'Closed the task')
|
69
65
|
::RailsExecution.configuration.notifier.new(current_task).after_close
|
70
66
|
redirect_to(action: :show) and return
|
@@ -84,20 +80,26 @@ module RailsExecution
|
|
84
80
|
raise(::RailsExecution::AccessDeniedError, 'Edit task') unless can_edit_task?(current_task)
|
85
81
|
|
86
82
|
@task = current_task
|
83
|
+
|
87
84
|
old_script = @task.script
|
88
85
|
old_reviewer_ids = @task.task_reviews.pluck(:owner_id)
|
89
86
|
checked_owner_ids = @task.task_reviews.checked.pluck(:owner_id)
|
87
|
+
old_scheduled_at = @task.scheduled_at
|
90
88
|
|
91
89
|
update_data = {
|
92
90
|
title: params.dig(:task, :title),
|
93
91
|
description: params.dig(:task, :description),
|
92
|
+
scheduled_at: Time.zone.parse(params.dig(:task, :scheduled_at).to_s),
|
93
|
+
repeat_mode: params.dig(:task, :repeat_mode),
|
94
94
|
}
|
95
95
|
|
96
96
|
update_data[:script] = params.dig(:task, :script) if @task.in_processing?
|
97
97
|
@task.assign_reviewers(params.dig(:task, :task_review_ids).to_a)
|
98
|
+
@task.assign_labels(params[:task_label_ids].to_a) if params.key?(:task_label_ids)
|
98
99
|
@task.syntax_status = ::RailsExecution::Services::SyntaxChecker.new(update_data[:script]).call ? 'good' : 'bad'
|
99
100
|
|
100
101
|
if @task.update(update_data)
|
102
|
+
::RailsExecution::Services::RemoveScheduledJob.new(@task).call if (old_script != @task.script || @task.scheduled_at != old_scheduled_at) && can_remove_scheduled_job?(@task)
|
101
103
|
@task.add_files(params[:attachments]&.permit!.to_h, current_owner) if ::RailsExecution.configuration.file_upload
|
102
104
|
@task.activities.create(owner: current_owner, message: 'Updated the Task')
|
103
105
|
::RailsExecution.configuration.notifier.new(@task).after_update_script(current_owner, checked_owner_ids) if old_script != @task.script
|
@@ -110,18 +112,21 @@ module RailsExecution
|
|
110
112
|
|
111
113
|
def completed
|
112
114
|
paging = ::RailsExecution::Services::Paging.new(page: params[:page], per_page: params[:per_page])
|
113
|
-
completed_tasks = ::RailsExecution::Task.is_completed.
|
115
|
+
completed_tasks = ::RailsExecution::Task.is_completed.preload(:owner, :labels)
|
116
|
+
completed_tasks = ::RailsExecution::Tasks::FilterService.new(completed_tasks, params, current_owner&.id).call
|
114
117
|
@tasks = paging.call(completed_tasks)
|
115
118
|
end
|
116
119
|
|
117
120
|
def closed
|
118
121
|
paging = ::RailsExecution::Services::Paging.new(page: params[:page], per_page: params[:per_page])
|
119
|
-
closed_tasks = ::RailsExecution::Task.is_closed.
|
122
|
+
closed_tasks = ::RailsExecution::Task.is_closed.preload(:owner, :labels)
|
123
|
+
closed_tasks = ::RailsExecution::Tasks::FilterService.new(closed_tasks, params, current_owner&.id).call
|
120
124
|
@tasks = paging.call(closed_tasks)
|
121
125
|
end
|
122
126
|
|
123
127
|
def reopen
|
124
128
|
if current_task.update(status: :created)
|
129
|
+
::RailsExecution::Services::CreateScheduledJob.new(current_task).call if in_solo_mode? && can_schedule_task?(current_task)
|
125
130
|
current_task.activities.create(owner: current_owner, message: 'Re-opened the Task')
|
126
131
|
::RailsExecution.configuration.notifier.new(current_task).after_reopen
|
127
132
|
flash[:notice] = 'Your task is re-opened'
|
@@ -133,6 +138,7 @@ module RailsExecution
|
|
133
138
|
|
134
139
|
def reject
|
135
140
|
if ::RailsExecution::Services::Approvement.new(current_task, reviewer: current_owner).reject
|
141
|
+
::RailsExecution::Services::RemoveScheduledJob.new(current_task).call if can_remove_scheduled_job?(current_task)
|
136
142
|
::RailsExecution.configuration.notifier.new(current_task).after_reject(current_owner)
|
137
143
|
flash[:notice] = 'Your decision is updated!'
|
138
144
|
else
|
@@ -143,10 +149,11 @@ module RailsExecution
|
|
143
149
|
|
144
150
|
def approve
|
145
151
|
if ::RailsExecution::Services::Approvement.new(current_task, reviewer: current_owner).approve
|
152
|
+
::RailsExecution::Services::CreateScheduledJob.new(current_task).call if can_schedule_task?(current_task)
|
146
153
|
::RailsExecution.configuration.notifier.new(current_task).after_approve(current_owner)
|
147
154
|
flash[:notice] = 'Your decision is updated!'
|
148
155
|
else
|
149
|
-
flash[:alert] = "Your decision
|
156
|
+
flash[:alert] = "Your decision isn't updated!"
|
150
157
|
end
|
151
158
|
redirect_to action: :show
|
152
159
|
end
|
@@ -159,7 +166,7 @@ module RailsExecution
|
|
159
166
|
|
160
167
|
execute_service = ::RailsExecution::Services::Execution.new(current_task)
|
161
168
|
if execute_service.call
|
162
|
-
current_task.update(status: :completed)
|
169
|
+
current_task.update(status: :completed) unless current_task.repeatable?
|
163
170
|
current_task.activities.create(owner: current_owner, message: 'Execute: The task is completed')
|
164
171
|
::RailsExecution.configuration.notifier.new(current_task).after_execute_success(current_owner)
|
165
172
|
flash[:notice] = 'This task is executed'
|
@@ -171,6 +178,15 @@ module RailsExecution
|
|
171
178
|
redirect_to(action: :show)
|
172
179
|
end
|
173
180
|
|
181
|
+
def execute_in_background
|
182
|
+
unless can_execute_task?(current_task)
|
183
|
+
flash[:alert] = "This task can't execute: #{how_to_executable(current_task)}"
|
184
|
+
redirect_to(action: :show) and return
|
185
|
+
end
|
186
|
+
RailsExecution::Services::BackgroundExecution.new(current_task, current_owner).setup
|
187
|
+
redirect_to(action: :show)
|
188
|
+
end
|
189
|
+
|
174
190
|
private
|
175
191
|
|
176
192
|
def current_task
|
@@ -179,14 +195,28 @@ module RailsExecution
|
|
179
195
|
helper_method :current_task
|
180
196
|
|
181
197
|
def reviewers
|
182
|
-
@reviewers
|
183
|
-
|
184
|
-
|
198
|
+
return @reviewers if defined?(@reviewers)
|
199
|
+
|
200
|
+
list = ::RailsExecution.configuration.reviewers.call
|
201
|
+
list = list.map { |reviewer| reviewer[:id] == current_owner.id ? nil : ::OpenStruct.new(reviewer) }
|
202
|
+
@reviewers = list.compact
|
185
203
|
end
|
186
204
|
helper_method :reviewers
|
187
205
|
|
206
|
+
def repeat_mode_options
|
207
|
+
@repeat_mode_options ||= {
|
208
|
+
'Does not repeat': :none,
|
209
|
+
'Daily': :daily,
|
210
|
+
'Weekly': :weekly,
|
211
|
+
'Monthly': :monthly,
|
212
|
+
'Anually': :anually,
|
213
|
+
'Every Weekday': :weekdays,
|
214
|
+
}
|
215
|
+
end
|
216
|
+
helper_method :repeat_mode_options
|
217
|
+
|
188
218
|
def task_logs
|
189
|
-
@task_logs ||= ::RailsExecution.configuration.logging_files.call(current_task)
|
219
|
+
@task_logs ||= ::RailsExecution.configuration.logging_files.call(current_task)
|
190
220
|
end
|
191
221
|
helper_method :task_logs
|
192
222
|
|
@@ -200,5 +230,35 @@ module RailsExecution
|
|
200
230
|
end
|
201
231
|
helper_method :reviewing_accounts
|
202
232
|
|
233
|
+
def filter_owners
|
234
|
+
@filter_owners ||= query_filter_owners
|
235
|
+
end
|
236
|
+
helper_method :filter_owners
|
237
|
+
|
238
|
+
def query_filter_owners
|
239
|
+
return ::RailsExecution.configuration.owner_model.constantize
|
240
|
+
.where(id: RailsExecution::Task.select(:owner_id).distinct)
|
241
|
+
.limit(LIMITED_FILTER_LIST)
|
242
|
+
end
|
243
|
+
|
244
|
+
def filter_labels
|
245
|
+
@filter_labels ||= RailsExecution::Label.order(:name)
|
246
|
+
end
|
247
|
+
helper_method :filter_labels
|
248
|
+
|
249
|
+
def current_filtered_owner
|
250
|
+
@current_filtered_owner ||= ::RailsExecution.configuration.owner_model.constantize.find_by(id: params[:owner_id])
|
251
|
+
end
|
252
|
+
helper_method :current_filtered_owner
|
253
|
+
|
254
|
+
def current_filtered_label
|
255
|
+
@current_filtered_label ||= ::RailsExecution::Label.find_by(id: params[:label_id])
|
256
|
+
end
|
257
|
+
helper_method :current_filtered_label
|
258
|
+
|
259
|
+
def comments_count_by_task
|
260
|
+
@comments_count_by_task ||= ::RailsExecution::Comment.where(task_id: @tasks.ids).group(:task_id).count
|
261
|
+
end
|
262
|
+
helper_method :comments_count_by_task
|
203
263
|
end
|
204
264
|
end
|
@@ -3,12 +3,14 @@ module RailsExecution
|
|
3
3
|
include ActionView::Helpers::AssetUrlHelper
|
4
4
|
|
5
5
|
def current_owner
|
6
|
-
return nil if in_solo_mode?
|
7
6
|
return @current_owner if defined?(@current_owner)
|
8
7
|
return nil if ::RailsExecution.configuration.owner_method.blank?
|
9
|
-
|
10
8
|
@current_owner = self.send(::RailsExecution.configuration.owner_method)
|
11
9
|
end
|
12
10
|
|
11
|
+
def normal_labels
|
12
|
+
@normal_labels ||= RailsExecution::Label.normal
|
13
|
+
end
|
14
|
+
|
13
15
|
end
|
14
16
|
end
|
@@ -51,6 +51,7 @@ module RailsExecution
|
|
51
51
|
return 'Script is empty' if task.script.blank?
|
52
52
|
return "Task is closed" if task.is_closed?
|
53
53
|
return "It's bad Syntax" if task.syntax_status_bad?
|
54
|
+
return "This task is being processed" if task.is_processing?
|
54
55
|
|
55
56
|
unless in_solo_mode?
|
56
57
|
return 'This task is not approved' unless task.is_approved?
|
@@ -60,5 +61,20 @@ module RailsExecution
|
|
60
61
|
return "Can't executable by app policy" unless ::RailsExecution.configuration.task_executable.call(current_owner, task)
|
61
62
|
end
|
62
63
|
|
64
|
+
def can_schedule_task?(task)
|
65
|
+
how_to_schedulable(task).blank?
|
66
|
+
end
|
67
|
+
|
68
|
+
def how_to_schedulable(task)
|
69
|
+
execute_message = how_to_executable(task)
|
70
|
+
return execute_message if execute_message
|
71
|
+
return "Time is not set for schedule" if task.scheduled_at.nil?
|
72
|
+
return "Scheduled at time in the past" if task.scheduled_at.past?
|
73
|
+
end
|
74
|
+
|
75
|
+
def can_remove_scheduled_job?(task)
|
76
|
+
task.jid.present?
|
77
|
+
end
|
78
|
+
|
63
79
|
end
|
64
80
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module RailsExecution
|
2
2
|
module RenderingHelper
|
3
|
+
AVATAR_CLASS = 'bd-placeholder-img flex-shrink-0 me-2 rounded'
|
4
|
+
TRUNCATE_FILE_NAME_LENGTH = 20
|
3
5
|
|
4
6
|
def render_user_info(user, avatar_size: '40x40')
|
5
7
|
content_tag :div, class: 'user-info' do
|
@@ -9,16 +11,16 @@ module RailsExecution
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def render_owner_avatar(owner, size: '32x32')
|
12
|
-
return
|
14
|
+
return image_tag(asset_path('executions/robot.png'), size: size, class: AVATAR_CLASS) if owner.blank?
|
13
15
|
|
14
16
|
avatar_url = RailsExecution.configuration.owner_avatar.call(owner)
|
15
17
|
return nil if avatar_url.blank?
|
16
18
|
|
17
|
-
image_tag avatar_url, size: size, class:
|
19
|
+
image_tag avatar_url, size: size, class: AVATAR_CLASS
|
18
20
|
end
|
19
21
|
|
20
22
|
def render_owner_name(owner)
|
21
|
-
return
|
23
|
+
return 'System' if owner.blank?
|
22
24
|
return nil if RailsExecution.configuration.owner_name_method.blank?
|
23
25
|
|
24
26
|
content_tag :span, owner.public_send(RailsExecution.configuration.owner_name_method)
|
@@ -39,6 +41,27 @@ module RailsExecution
|
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
44
|
+
def render_label(task_label)
|
45
|
+
content_tag :div do
|
46
|
+
concat content_tag :i, '', class: "bi bi-tag label-colors lc-#{task_label.color} bg-none"
|
47
|
+
concat content_tag :span, task_label.name, class: "badge rounded-pill mx-1 label-colors lc-#{task_label.color}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def render_task_labels(task)
|
52
|
+
# to_a and sort_by instead of .order for avoid N+1 query to sort task labels for each task
|
53
|
+
task.labels.to_a.sort_by(&:name).reduce(''.html_safe) do |result, label|
|
54
|
+
case label.name
|
55
|
+
when 'repeat'
|
56
|
+
result + content_tag(:span, "repeat: #{task.repeat_mode}", class: "badge rounded-pill mx-1 label-tag label-colors lc-#{label.color}", data: { id: label.id })
|
57
|
+
when 'scheduled'
|
58
|
+
result + content_tag(:span, 'scheduled', class: "badge rounded-pill mx-1 label-tag label-colors lc-#{label.color}", data: { id: label.id })
|
59
|
+
else
|
60
|
+
result + content_tag(:span, label.name, class: "badge rounded-pill mx-1 label-tag label-colors lc-#{label.color}", data: { id: label.id })
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
42
65
|
def task_reviewed_status(task)
|
43
66
|
@task_reviewed_status ||= {}
|
44
67
|
@task_reviewed_status[task] ||= task.task_reviews.find_by(owner_id: current_owner&.id)&.status&.inquiry
|
@@ -67,7 +90,7 @@ module RailsExecution
|
|
67
90
|
end
|
68
91
|
|
69
92
|
def re_get_file_name(url)
|
70
|
-
URI(url).path.split('/').last
|
93
|
+
URI(url).path.split('/').last&.truncate(TRUNCATE_FILE_NAME_LENGTH)
|
71
94
|
end
|
72
95
|
|
73
96
|
def re_badge_color(status)
|
@@ -86,6 +109,7 @@ module RailsExecution
|
|
86
109
|
rejected: 'danger',
|
87
110
|
approved: 'success',
|
88
111
|
reviewing: 'secondary',
|
112
|
+
processing: 'warning',
|
89
113
|
completed: 'success',
|
90
114
|
}[text] || 'secondary'
|
91
115
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class RailsExecution::Label < RailsExecution::AppModel
|
2
|
+
SPECIAL_LABLES = ['scheduled', 'repeat']
|
3
|
+
COLORS = 'red pink purple deep-purple indigo blue light-blue cyan teal green orange deep-orange brown grey blue-grey'.split
|
4
|
+
|
5
|
+
has_many :task_labels, class_name: 'RailsExecution::TaskLabel'
|
6
|
+
has_many :tasks, through: :task_labels
|
7
|
+
|
8
|
+
validates :name, presence: true, uniqueness: { case_sensitive: false }
|
9
|
+
scope :normal, -> { where.not(name: SPECIAL_LABLES) }
|
10
|
+
|
11
|
+
def color
|
12
|
+
color_index = self.name.to_s.chars.sum(&:ord) % COLORS.size
|
13
|
+
COLORS[color_index]
|
14
|
+
end
|
15
|
+
end
|
@@ -1,21 +1,29 @@
|
|
1
1
|
class RailsExecution::Task < RailsExecution::AppModel
|
2
2
|
|
3
|
-
PROCESSING_STATUSES = %w(created reviewing approved rejected)
|
3
|
+
PROCESSING_STATUSES = %w(created reviewing approved processing rejected)
|
4
|
+
ACTIVE_REPEAT_MODE = %w(hourly daily weekly monthly annually weekdays)
|
4
5
|
|
5
6
|
has_many :activities, class_name: 'RailsExecution::Activity'
|
6
7
|
has_many :comments, class_name: 'RailsExecution::Comment'
|
7
8
|
has_many :task_reviews, class_name: 'RailsExecution::TaskReview'
|
9
|
+
has_many :task_labels, class_name: 'RailsExecution::TaskLabel'
|
10
|
+
has_many :labels, through: :task_labels
|
8
11
|
attr_accessor :reviewer_ids
|
9
12
|
|
10
13
|
validates :title, presence: true
|
11
14
|
validates :status, presence: true
|
15
|
+
validate :validate_scheduled_at, if: :scheduled_at_changed?
|
16
|
+
|
17
|
+
before_validation :auto_assign_labels
|
12
18
|
|
13
19
|
enum status: {
|
14
20
|
created: 'created',
|
15
21
|
reviewing: 'reviewing',
|
16
22
|
approved: 'approved',
|
17
|
-
|
23
|
+
processing: 'processing', # Processing by background job
|
18
24
|
completed: 'completed',
|
25
|
+
failed: 'failed',
|
26
|
+
rejected: 'rejected',
|
19
27
|
closed: 'closed',
|
20
28
|
}, _prefix: :is
|
21
29
|
|
@@ -24,6 +32,16 @@ class RailsExecution::Task < RailsExecution::AppModel
|
|
24
32
|
good: 'good',
|
25
33
|
}, _prefix: true
|
26
34
|
|
35
|
+
enum repeat_mode: {
|
36
|
+
none: 'Does not repeat',
|
37
|
+
hourly: 'Hourly',
|
38
|
+
daily: 'Daily',
|
39
|
+
weekly: 'Weekly',
|
40
|
+
monthly: 'Monthly',
|
41
|
+
annually: 'Anually',
|
42
|
+
weekdays: 'Every weekday (Monday to Friday)',
|
43
|
+
}, _prefix: :repeat
|
44
|
+
|
27
45
|
scope :processing, -> { where(status: PROCESSING_STATUSES) }
|
28
46
|
|
29
47
|
before_update :re_assign_status
|
@@ -33,6 +51,14 @@ class RailsExecution::Task < RailsExecution::AppModel
|
|
33
51
|
PROCESSING_STATUSES.include?(self.status)
|
34
52
|
end
|
35
53
|
|
54
|
+
def scheduled?
|
55
|
+
self.scheduled_at? && self.repeat_none?
|
56
|
+
end
|
57
|
+
|
58
|
+
def repeatable?
|
59
|
+
ACTIVE_REPEAT_MODE.include?(self.repeat_mode)
|
60
|
+
end
|
61
|
+
|
36
62
|
def assign_reviewers(ids)
|
37
63
|
ids.each do |id|
|
38
64
|
next if id.blank?
|
@@ -45,6 +71,10 @@ class RailsExecution::Task < RailsExecution::AppModel
|
|
45
71
|
end
|
46
72
|
end
|
47
73
|
|
74
|
+
def assign_labels(ids)
|
75
|
+
self.labels = RailsExecution::Label.where(id: ids.uniq)
|
76
|
+
end
|
77
|
+
|
48
78
|
def reviewer_ids
|
49
79
|
self.task_reviews.pluck(:owner_id)
|
50
80
|
end
|
@@ -53,6 +83,23 @@ class RailsExecution::Task < RailsExecution::AppModel
|
|
53
83
|
::RailsExecution.configuration.file_uploader.new(self, attachments, owner: current_owner).call
|
54
84
|
end
|
55
85
|
|
86
|
+
def repeat_mode_select_options
|
87
|
+
return RailsExecution::Task.repeat_modes if scheduled_at.nil?
|
88
|
+
|
89
|
+
options = RailsExecution::Task.repeat_modes.dup
|
90
|
+
options['hourly'] = self.scheduled_at.strftime('Hourly at minute %-M')
|
91
|
+
options['daily'] = self.scheduled_at.strftime('Daily at %H:%M')
|
92
|
+
options['weekly'] = self.scheduled_at.strftime('Weekly on %A at %H:%M')
|
93
|
+
options['monthly'] = self.scheduled_at.strftime('Monthly on %-d at %H:%M')
|
94
|
+
options['annually'] = self.scheduled_at.strftime('Annually on %B %-d at %H:%M')
|
95
|
+
options['weekdays'] = self.scheduled_at.strftime('Every weekday (Monday to Friday) at %H:%M')
|
96
|
+
return options
|
97
|
+
end
|
98
|
+
|
99
|
+
def next_time_at
|
100
|
+
::RailsExecution::Services::CreateScheduledJob.new(self).calculate_next_time_at.strftime("%Y-%m-%d %H:%M")
|
101
|
+
end
|
102
|
+
|
56
103
|
private
|
57
104
|
|
58
105
|
def create_activity
|
@@ -62,10 +109,31 @@ class RailsExecution::Task < RailsExecution::AppModel
|
|
62
109
|
def re_assign_status
|
63
110
|
return if self.is_completed? || self.is_closed?
|
64
111
|
|
65
|
-
if self.is_approved? && self.script_changed?
|
112
|
+
if self.is_approved? && (self.script_changed? || self.scheduled_at_changed?)
|
66
113
|
self.status = :reviewing
|
67
114
|
self.task_reviews.update_all(status: :reviewing)
|
68
115
|
end
|
69
116
|
end
|
70
117
|
|
118
|
+
def validate_scheduled_at
|
119
|
+
return if self.scheduled_at.blank?
|
120
|
+
|
121
|
+
errors.add(:scheduled_at, "Input schedule time #{scheduled_at} is in the past for non-repeat mode") if scheduled_at.past? && repeat_none?
|
122
|
+
end
|
123
|
+
|
124
|
+
def auto_assign_labels
|
125
|
+
scheduled_label = RailsExecution::Label.find_or_create_by(name: :scheduled)
|
126
|
+
repeat_label = RailsExecution::Label.find_or_create_by(name: :repeat)
|
127
|
+
|
128
|
+
final_label_ids = self.label_ids - [scheduled_label.id, repeat_label.id]
|
129
|
+
|
130
|
+
if self.scheduled?
|
131
|
+
final_label_ids << scheduled_label.id
|
132
|
+
elsif self.repeatable?
|
133
|
+
final_label_ids << repeat_label.id
|
134
|
+
end
|
135
|
+
|
136
|
+
self.label_ids = final_label_ids
|
137
|
+
end
|
138
|
+
|
71
139
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class RailsExecution::Tasks::CreateService
|
2
|
+
|
3
|
+
def initialize(params, current_owner)
|
4
|
+
@params = params
|
5
|
+
@current_owner = current_owner
|
6
|
+
@task = nil
|
7
|
+
@error = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
initialize_task
|
12
|
+
create_task
|
13
|
+
rescue => e
|
14
|
+
@error = e.to_s
|
15
|
+
ensure
|
16
|
+
return self
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :error
|
20
|
+
attr_reader :task
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :params
|
25
|
+
attr_reader :current_owner
|
26
|
+
|
27
|
+
def initialize_task
|
28
|
+
@task = ::RailsExecution::Task.new({
|
29
|
+
status: :created,
|
30
|
+
owner_id: current_owner&.id,
|
31
|
+
owner_type: ::RailsExecution.configuration.owner_model.to_s,
|
32
|
+
title: params.dig(:task, :title),
|
33
|
+
description: params.dig(:task, :description),
|
34
|
+
script: params.dig(:task, :script),
|
35
|
+
scheduled_at: parsed_scheduled_at,
|
36
|
+
repeat_mode: params.dig(:task, :repeat_mode)
|
37
|
+
})
|
38
|
+
|
39
|
+
@task.assign_reviewers(params.dig(:task, :task_review_ids).to_a)
|
40
|
+
@task.assign_labels(params[:task_label_ids].to_a)
|
41
|
+
@task.syntax_status = ::RailsExecution::Services::SyntaxChecker.new(@task.script).call ? 'good' : 'bad'
|
42
|
+
end
|
43
|
+
|
44
|
+
def scheduled_at
|
45
|
+
@scheduled_at ||= params.dig(:task, :scheduled_at)
|
46
|
+
end
|
47
|
+
|
48
|
+
def parsed_scheduled_at
|
49
|
+
@parsed_scheduled_at ||= scheduled_at.blank? ? nil : Time.zone.parse(scheduled_at)
|
50
|
+
rescue
|
51
|
+
raise 'Invalid input for scheduling time'
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_task
|
55
|
+
if @task.save
|
56
|
+
@task.add_files(params[:attachments]&.permit!.to_h, current_owner) if ::RailsExecution.configuration.file_upload
|
57
|
+
::RailsExecution.configuration.notifier.new(@task).after_create
|
58
|
+
else
|
59
|
+
raise @task.errors.messages.values.flatten.first
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class RailsExecution::Tasks::FilterService
|
2
|
+
|
3
|
+
def initialize(tasks_query_object, params, current_user_id = nil)
|
4
|
+
@tasks_query_object = tasks_query_object
|
5
|
+
@params = params
|
6
|
+
@current_user_id = current_user_id
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
filter_by_owner
|
11
|
+
filter_by_label
|
12
|
+
filter_by_keyword
|
13
|
+
order_by_recently_updated
|
14
|
+
|
15
|
+
return @tasks_query_object.descending
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :params
|
21
|
+
attr_reader :current_user_id
|
22
|
+
|
23
|
+
def filter_by_owner
|
24
|
+
if params[:my_task] == '1' && current_user_id
|
25
|
+
@tasks_query_object = @tasks_query_object.where(owner_id: current_user_id)
|
26
|
+
elsif params[:owner_id].present?
|
27
|
+
@tasks_query_object = @tasks_query_object.where(owner_id: params[:owner_id])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def filter_by_label
|
32
|
+
return if params[:label_id].blank?
|
33
|
+
|
34
|
+
filtered_task_ids = RailsExecution::TaskLabel.where(label_id: params[:label_id]).select(:task_id)
|
35
|
+
@tasks_query_object = @tasks_query_object.where(id: filtered_task_ids)
|
36
|
+
end
|
37
|
+
|
38
|
+
def filter_by_keyword
|
39
|
+
return if params[:keyword].blank?
|
40
|
+
|
41
|
+
@tasks_query_object = @tasks_query_object.where("rails_execution_tasks.title LIKE ?", "%#{params[:keyword]}%")
|
42
|
+
end
|
43
|
+
|
44
|
+
def order_by_recently_updated
|
45
|
+
sort_by = params[:recently_updated] == '1' ? { updated_at: :desc } : { updated_at: :asc }
|
46
|
+
@tasks_query_object = @tasks_query_object.order(sort_by)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
- return if total_pages <= 1
|
2
2
|
- original_params = params.permit!.to_h
|
3
3
|
|
4
|
-
- gap_number =
|
4
|
+
- gap_number = 1
|
5
5
|
- page_numbers = (1..total_pages).to_a
|
6
6
|
- begin_paging = (1..(1 + gap_number)).to_a
|
7
7
|
- middle_paging = page_numbers & ((page - gap_number)..(page + gap_number)).to_a
|
@@ -2,7 +2,9 @@
|
|
2
2
|
= re_card_content do
|
3
3
|
.text-center
|
4
4
|
- if can_execute_task?(current_task)
|
5
|
-
= link_to 'Execute NOW!!!', execute_task_path(current_task), class: 'btn btn-primary', method: :patch, data: { confirm: 'Are you sure?' }
|
5
|
+
= link_to 'Execute NOW!!!', execute_task_path(current_task), class: 'btn btn-block btn-primary', method: :patch, data: { confirm: 'Are you sure?' }
|
6
|
+
- if RailsExecution.configuration.task_background
|
7
|
+
= link_to 'Execute in background!', execute_in_background_task_path(current_task), class: 'btn btn-block btn-secondary', method: :patch, data: { confirm: 'Are you sure?' }
|
6
8
|
- else
|
7
9
|
%p.text-danger.mb-0.fw-bold
|
8
10
|
%i.bi.bi-x-octagon.me-1
|
@@ -2,21 +2,27 @@
|
|
2
2
|
= re_card_content do
|
3
3
|
%h5.section-title Logs
|
4
4
|
%ul.list-unstyled#task-logs
|
5
|
-
- task_logs.each do |task_log|
|
6
|
-
%li.
|
7
|
-
.col.
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
- task_logs.each do |task_log, created_at|
|
6
|
+
%li.row.g-0.vetical-align-middle
|
7
|
+
.col.d-flex.align-items-center= re_get_file_name(task_log)
|
8
|
+
- if created_at
|
9
|
+
.col-auto.pe-3
|
10
|
+
.activitiy-time.text-muted.text-end
|
11
|
+
%span.fs13px= created_at.strftime('%Y/%m/%d')
|
12
|
+
%p.mb-0= created_at.strftime('%H:%M')
|
13
|
+
.col-auto.text-center
|
14
|
+
= link_to task_log, target: :_blank, class: 'text-muted click2download' do
|
15
|
+
%i.bi.bi-download.fs-5
|
16
|
+
%p.small.mb-0 Download
|
11
17
|
|
12
18
|
= re_card_content id: 'activities-section' do
|
13
19
|
%h5.section-title Activities
|
14
20
|
%ul.list-unstyled
|
15
21
|
- current_task.activities.descending.preload(:owner).each do |activitiy|
|
16
|
-
%li.row
|
22
|
+
%li.row.g-0.vetical-align-middle.mb-3
|
17
23
|
.col
|
18
24
|
= render_user_info(activitiy.owner, avatar_size: '20x20')
|
19
|
-
%p.normal-text.mt-1= activitiy.message
|
20
|
-
.col-
|
25
|
+
%p.mb-0.normal-text.mt-1= activitiy.message
|
26
|
+
.col-auto.text-end.activitiy-time
|
21
27
|
%span.fs13px.text-muted= activitiy.created_at.strftime('%Y/%m/%d')
|
22
|
-
%p.text-muted= activitiy.created_at.strftime('%H:%M')
|
28
|
+
%p.mb-0.text-muted= activitiy.created_at.strftime('%H:%M')
|