rails_execution 0.1.5 → 0.1.7
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 +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')
|