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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +28 -0
  3. data/app/assets/javascripts/executions/base.js +9 -0
  4. data/app/assets/javascripts/executions/flatpickr.min.js +2 -0
  5. data/app/assets/stylesheets/executions/flatpickr.min.css +13 -0
  6. data/app/assets/stylesheets/executions/modify.scss +11 -6
  7. data/app/assets/stylesheets/executions/style.scss +153 -0
  8. data/app/controllers/rails_execution/labels_controller.rb +29 -0
  9. data/app/controllers/rails_execution/tasks_controller.rb +84 -24
  10. data/app/helpers/rails_execution/base_helper.rb +4 -2
  11. data/app/helpers/rails_execution/policy_helper.rb +16 -0
  12. data/app/helpers/rails_execution/rendering_helper.rb +28 -4
  13. data/app/models/rails_execution/label.rb +15 -0
  14. data/app/models/rails_execution/task.rb +71 -3
  15. data/app/models/rails_execution/task_label.rb +6 -0
  16. data/app/services/rails_execution/tasks/create_service.rb +62 -0
  17. data/app/services/rails_execution/tasks/filter_service.rb +49 -0
  18. data/app/views/rails_execution/shared/_paging.html.haml +1 -1
  19. data/app/views/rails_execution/tasks/_actions.html.haml +3 -1
  20. data/app/views/rails_execution/tasks/_activities.html.haml +16 -10
  21. data/app/views/rails_execution/tasks/_advanced_filter.html.haml +115 -0
  22. data/app/views/rails_execution/tasks/_attachment_files.html.haml +2 -11
  23. data/app/views/rails_execution/tasks/_attachments.html.haml +13 -0
  24. data/app/views/rails_execution/tasks/_form.html.haml +50 -14
  25. data/app/views/rails_execution/tasks/_label_collection_select.html.haml +20 -0
  26. data/app/views/rails_execution/tasks/_labels.html.haml +2 -0
  27. data/app/views/rails_execution/tasks/_new_label_modal_form.html.haml +24 -0
  28. data/app/views/rails_execution/tasks/_quick_filter.html.haml +25 -0
  29. data/app/views/rails_execution/tasks/_schedule.html.haml +22 -0
  30. data/app/views/rails_execution/tasks/_task.html.haml +22 -18
  31. data/app/views/rails_execution/tasks/closed.html.haml +2 -1
  32. data/app/views/rails_execution/tasks/completed.html.haml +2 -1
  33. data/app/views/rails_execution/tasks/index.html.haml +2 -1
  34. data/app/views/rails_execution/tasks/show.html.haml +3 -1
  35. data/config/routes.rb +2 -1
  36. data/lib/generators/rails_execution/templates/config.rb.tt +8 -0
  37. data/lib/generators/rails_execution/templates/install.rb.tt +24 -7
  38. data/lib/rails_execution/config.rb +20 -7
  39. data/lib/rails_execution/services/background_execution.rb +56 -0
  40. data/lib/rails_execution/services/create_scheduled_job.rb +61 -0
  41. data/lib/rails_execution/services/remove_scheduled_job.rb +20 -0
  42. data/lib/rails_execution/services/task_scheduler.rb +35 -0
  43. data/lib/rails_execution/version.rb +1 -1
  44. data/lib/rails_execution.rb +4 -0
  45. metadata +21 -2
@@ -0,0 +1,115 @@
1
+ = form_tag nil, method: 'get', id:'filter-form' do
2
+ .row.d-flex.g-2.pb-4.border-bottom
3
+ .col-md-6= render partial: 'quick_filter'
4
+ .col-md-6
5
+ .row.d-fex.justify-content-end
6
+ .col-auto#label-filter
7
+ .filter-select-container.h-100.text-center{'data-target': '#label-select-dropdown', 'focus-target': '#label_search_input'}
8
+ .p-2.fs-6
9
+ - if current_filtered_label
10
+ = render_label(current_filtered_label)
11
+ - else
12
+ %i.bi.bi-tag
13
+ Label
14
+ #label-select-dropdown.dropdown-menu.pt-0.mx-0.rounded-3.shadow.overflow-hidden
15
+ .p-2.mb-2.bg-light.border-bottom
16
+ = text_field_tag :label_search_input, nil, autocomplete: "false", placeholder: "Type to filter...", class: "form-control", type: "search"
17
+ = hidden_field_tag :label_id, params[:label_id]
18
+ %ul.label-list.list-unstyled.mb-0.overflow-auto
19
+ - if params[:label_id].present?
20
+ %li{ data: { name: '', id: '', 'input-target': '#label_id'}, class: 'filter-select-value'}
21
+ = link_to '#', class: 'dropdown-item d-flex align-items-center gap-2 py-2 text-danger' do
22
+ %i.bi.bi-dash-circle
23
+ Remove filter
24
+ - filter_labels.each do |label|
25
+ %li{ data: { name: label.name, id: label.id, 'input-target': '#label_id'}, class: 'filter-select-value'}
26
+ %a.dropdown-item.d-flex.align-items-center.gap-2.py-2{href: "#"}
27
+ %i.bi.bi-tag.color.label-colors.bg-none{ class: "lc-#{label.color}" }
28
+ %span.rounded-pill.badge.label-tag.label-colors{ class: "lc-#{label.color}" }= label.name
29
+ .col-auto#owner-filter
30
+ .filter-select-container{"data-target": "#owner-select-dropdown", "focus-target": "#owner_search_input"}
31
+ .p-2.fs-6
32
+ - if current_filtered_owner
33
+ = render_user_info(current_filtered_owner)
34
+ - else
35
+ %i.bi.bi-person
36
+ %span Owner
37
+ #owner-select-dropdown.dropdown-menu.pt-0.mx-0.rounded-3.shadow.overflow-hidden{style: "width: 280px;"}
38
+ .p-2.mb-2.bg-light.border-bottom
39
+ = text_field_tag :owner_search_input, nil, autocomplete: "false", placeholder: "Type to filter...", class: "form-control", type: "search"
40
+ = hidden_field_tag :owner_id, params[:owner_id]
41
+ %ul.owner-list.list-unstyled.mb-0.overflow-auto{ style: "height: 230px;" }
42
+ - if params[:owner_id].present?
43
+ %li{ data: { email: '', name: '', id: '', 'input-target': '#owner_id'}, class: 'filter-select-value'}
44
+ %a.dropdown-item.d-flex.align-items-center.gap-2.text-danger{href: "#"}
45
+ %span.p1-2
46
+ %i.bi.bi-person-dash.fs-2
47
+ %span.ms-1.fs-6 Remove filter
48
+ - filter_owners.each do |owner|
49
+ %li{ data: { email: owner.email, name: owner.name, id: owner.id, 'input-target': '#owner_id'}, class: 'filter-select-value'}
50
+ %a.dropdown-item.d-flex.align-items-center.gap-2{href: "#"}
51
+ = render_user_info(owner, avatar_size: '40x40')
52
+ .col-auto#search-input
53
+ .input-group.h-100
54
+ = text_field_tag :keyword, params[:keyword], placeholder: "Keyword", class: "form-control"
55
+ %button.input-group-text
56
+ %i.bi.bi-search
57
+
58
+ :javascript
59
+ $(document).ready(function() {
60
+ $('.filter-select-container').on('click', function() {
61
+ $($(this).attr('data-target')).show();
62
+ $($(this).attr('focus-target')).focus();
63
+ })
64
+
65
+ $(document).mouseup(function (e) {
66
+ if ($(e.target).closest("#owner-select-dropdown").length === 0) {
67
+ $("#owner-select-dropdown").hide();
68
+ }
69
+ if ($(e.target).closest("#label-select-dropdown").length === 0) {
70
+ $("#label-select-dropdown").hide();
71
+ }
72
+ });
73
+
74
+ $('#owner_search_input').on('input', function() {
75
+ var searchText = $(this).val().toLowerCase();
76
+
77
+ $('.owner-list li').each(function() {
78
+ var li = $(this);
79
+ var email = li.attr('data-email').toLowerCase();
80
+ var name = li.attr('data-name').toLowerCase();
81
+
82
+ if (email.indexOf(searchText) > -1 || name.indexOf(searchText) > -1) {
83
+ li.show();
84
+ } else {
85
+ li.hide();
86
+ }
87
+ });
88
+ });
89
+
90
+ $('#label_search_input').on('input', function() {
91
+ var searchText = $(this).val().toLowerCase();
92
+
93
+ $('.label-list li').each(function() {
94
+ var li = $(this);
95
+ var name = li.attr('data-name').toLowerCase();
96
+
97
+ if (name.indexOf(searchText) > -1) {
98
+ li.show();
99
+ } else {
100
+ li.hide();
101
+ }
102
+ });
103
+ });
104
+
105
+ $('.filter-select-value').on('click', function() {
106
+ $($(this).attr('data-input-target')).val($(this).attr('data-id'))
107
+ $('#filter-form').submit();
108
+ })
109
+
110
+ $('.label-tag').on('click', function() {
111
+ $('#label_id').val($(this).attr('data-id'));
112
+ $('#filter-form').submit();
113
+ })
114
+
115
+ });
@@ -1,12 +1,3 @@
1
- - if RailsExecution.configuration.file_upload && task_attachment_files.any?
1
+ - if task_attachment_files.any?
2
2
  = re_card_content do
3
- %h5.section-title.mb-3 Attachment files
4
- %ul.list-unstyled.mt-3
5
- - task_attachment_files.each do |name, url|
6
- %li.row.normal-text
7
- .col
8
- %i.bi.fs-3.text-muted{ class: "bi-file-text bi-filetype-#{url.split('.').last}" }
9
- = name
10
- .col-3.text-end
11
- %p.mb-0= link_to 'Download', url, class: 'text-muted'
12
- %small Filename: #{re_get_file_name(url)}
3
+ = render partial: 'attachments', locals: { task_attachment_files: }
@@ -0,0 +1,13 @@
1
+ %h5.section-title.mb-3 Attachment files
2
+ %ul.list-unstyled.mt-3
3
+ - task_attachment_files.each do |name, url|
4
+ %li.normal-text.row.g-0.align-items-center.py-1
5
+ .col-auto
6
+ %i.bi.fs-3.text-muted{ class: "bi-file-text bi-filetype-#{url.split('.').last}" }
7
+ .col.ps-2
8
+ %p.mb-0= name
9
+ %small.text-muted Filename: #{re_get_file_name(url)}
10
+ .col-auto.text-center
11
+ = link_to url, class: 'text-muted' do
12
+ %i.bi.bi-download.fs-5
13
+ %p.small.mb-0 Download
@@ -1,7 +1,7 @@
1
1
  = form_for task, method: task.new_record? ? :post : :patch, html: { enctype: 'multipart/form-data' } do |f|
2
2
  .row
3
3
  - if task.errors.any?
4
- .col-12.text-danger.small.mt-3= task.errors.full_messages.join("\n")
4
+ .col-12.text-danger.mt-3= task.errors.full_messages.join("\n")
5
5
 
6
6
  .col
7
7
  .mb-3.mt-4= f.text_field :title, { class: 'form-control', placeholder: 'Title', required: true }
@@ -14,19 +14,24 @@
14
14
  - if reviewers.any?
15
15
  = f.collection_select :task_review_ids, reviewers, :id, :name, { selected: task.reviewer_ids }, { multiple: true, class: 'select2 col-12' }
16
16
  - else
17
- %p.small.text-muted Nobody can add
18
-
17
+ %p.small.text-muted Nobody can be added
18
+ %h5.mb-2.mt-4.section-title
19
+ Labels
20
+ .float-end
21
+ = link_to '#', class: 'text-primary', 'data-bs-target': '#newLabelModalForm', 'data-bs-toggle': "modal" do
22
+ %i.bi.bi-plus-circle.me-1
23
+ Add
24
+ #labels.mb-3
25
+ = render partial: 'label_collection_select', locals: { selected_label_ids: task.label_ids }
26
+ - if RailsExecution.configuration.task_schedulable
27
+ %h5.mb-2.mt-4.section-title
28
+ Scheduler
29
+ %i.bi.bi-clock-fill.me-1.pointer-events
30
+ %a{:href => "#", id: "clear-schedule-btn"} clear
31
+ = f.datetime_local_field :scheduled_at, class: "form-control", placeholder: "Select Datetime"
32
+ = f.select :repeat_mode, options_for_select(task.repeat_mode_select_options.invert, f.object.repeat_mode), {}, class: "form-control", disabled: !task.scheduled_at?
19
33
  - if RailsExecution.configuration.file_upload
20
- %h5.mb-2.mt-4.section-title Attachment files
21
- %ul.list-unstyled.mt-3
22
- - task_attachment_files.each do |name, url|
23
- %li.row.normal-text
24
- .col
25
- %i.bi.fs-3.text-muted{ class: "bi-file-text bi-filetype-#{url.split('.').last}" }
26
- = name
27
- .col-6.text-end
28
- %p.mb-0= link_to 'Download', url, class: 'text-muted'
29
- %small Filename: #{re_get_file_name(url)}
34
+ .mt-4= render partial: 'attachments', locals: { task_attachment_files: }
30
35
  #attachment_files.mb-3
31
36
  %ul.list-unstyled= render partial: 'attachment_file_fields'
32
37
 
@@ -34,7 +39,6 @@
34
39
  %hr.smoothly
35
40
  = link_to 'More file', '#add-more-file', data: { fields_html: render(partial: 'attachment_file_fields') }, class: 'btn text-primary', id: 'js-add-more-file'
36
41
 
37
-
38
42
  - if task.new_record? || task.in_processing?
39
43
  .col-md-12.mb-3.mt-3
40
44
  .row
@@ -53,3 +57,35 @@
53
57
  = f.submit (task.new_record? ? 'Create' : 'Update'), class: 'btn btn-success'
54
58
 
55
59
  = render partial: 'form_scripts', locals: { task: task }
60
+ = render partial: 'new_label_modal_form'
61
+
62
+ :javascript
63
+ $(document).ready(function() {
64
+ $('#task_scheduled_at').change(function() {
65
+ var scheduledAtValue = $(this).val();
66
+ var modeSelect = $('#task_repeat_mode');
67
+ modeSelect.prop('disabled', false);
68
+
69
+ var scheduledDate = new Date(scheduledAtValue);
70
+ var currentMinutes = scheduledDate.getMinutes().toString().padStart(2, '0');
71
+ var currentHours = scheduledDate.getHours().toString().padStart(2, '0');
72
+ var currentDayOfWeek = scheduledDate.toLocaleDateString('en-US', { weekday: 'long' });
73
+ var currentDayOfMonth = scheduledDate.getDate();
74
+ var currentMonth = scheduledDate.toLocaleDateString('en-US', { month: 'long' });
75
+
76
+ modeSelect.find('option[value="hourly"]').text(`Hourly at minute ${scheduledDate.getMinutes()}`);
77
+ modeSelect.find('option[value="daily"]').text(`Daily at ${currentHours}:${currentMinutes}`);
78
+ modeSelect.find('option[value="weekly"]').text(`Weekly on ${currentDayOfWeek} at ${currentHours}:${currentMinutes}`);
79
+ modeSelect.find('option[value="monthly"]').text(`Monthly on ${currentDayOfMonth} at ${currentHours}:${currentMinutes}`);
80
+ modeSelect.find('option[value="annually"]').text(`Annually on ${currentMonth} ${currentDayOfMonth} at ${currentHours}:${currentMinutes}`);
81
+ modeSelect.find('option[value="weekdays"]').text(`Every weekday (Monday to Friday) at ${currentHours}:${currentMinutes}`);
82
+ });
83
+
84
+ $('#clear-schedule-btn').on('click', function() {
85
+ $('#task_scheduled_at').val('');
86
+
87
+ var modeSelect = $('#task_repeat_mode');
88
+ modeSelect.val('none');
89
+ modeSelect.prop('disabled', true);
90
+ });
91
+ });
@@ -0,0 +1,20 @@
1
+ = select_tag 'task_label_ids', options_for_select(normal_labels.map { |l| [l.name, l.id, { color: l.color }] }, selected_label_ids), { multiple: true, class: 'select2 col-12' }
2
+
3
+ :javascript
4
+ $('#task_label_ids').select2({
5
+ dropdownCssClass: 'labeling',
6
+ templateSelection: function(data, container) {
7
+ if (data.id == '') {
8
+ return data.text;
9
+ } else {
10
+ return $('<span class="label-colors lc-' + $(data.element).attr('color') + '">' + data.text + '</span>');
11
+ }
12
+ },
13
+ templateResult: function(data, container) {
14
+ if (data.id == '') {
15
+ return data.text;
16
+ } else {
17
+ return $('<span class="badge label-colors lc-' + $(data.element).attr('color') + '">' + data.text + '</span>');
18
+ }
19
+ }
20
+ });
@@ -0,0 +1,2 @@
1
+ - if current_task.labels.any?
2
+ .py-2.text-end= render_task_labels(current_task)
@@ -0,0 +1,24 @@
1
+ / Modal
2
+ = form_tag labels_path, method: :post, id:'new-label-form', remote: true do
3
+ .modal.fade{'aria-hidden' => 'true', 'aria-labelledby' => 'newLabelModalFormLabel', tabindex: '-1', id: 'newLabelModalForm'}
4
+ .modal-dialog
5
+ .modal-content
6
+ .modal-header
7
+ %h5#newLabelModalFormLabel.modal-title New label
8
+ %button.btn-close{'aria-label' => 'Close', 'data-bs-dismiss' => 'modal', type: 'button'}
9
+ .modal-body
10
+ = text_field_tag :label_name, nil, placeholder: 'Label name', class: 'form-control'
11
+ .modal-footer
12
+ %button.btn.btn-secondary{'data-bs-dismiss' => 'modal', type: 'button'} Close
13
+ %button.btn.btn-primary Create
14
+
15
+ :javascript
16
+ $(document).ready(function() {
17
+ $('#new-label-form').on('ajax:success', function(event, response) {
18
+ $('#newLabelModalForm').modal('hide');
19
+ $('#labels').html(response.updated_html);
20
+ this.reset(); // Reset the form
21
+ }).on('ajax:error', function(event, xhr, status, error) {
22
+ alert(xhr.responseText);
23
+ });
24
+ });
@@ -0,0 +1,25 @@
1
+ .d-flex.mt-2
2
+ %button.btn.quick-filter-btn{ type: 'button', 'data-target': '#my_task' }
3
+ - if params[:my_task] == '1'
4
+ %i.bi.bi-check-square
5
+ - else
6
+ %i.bi.bi-square
7
+ Only my tasks
8
+ = hidden_field_tag :my_task, params[:my_task]
9
+ %button.btn.quick-filter-btn{ type: 'button', 'data-target': '#recently_updated' }
10
+ - if params[:recently_updated] == '1'
11
+ %i.bi.bi-sort-numeric-down-alt
12
+ - else
13
+ %i.bi.bi-sort-numeric-down
14
+ Last updated
15
+ = hidden_field_tag :recently_updated, params[:recently_updated]
16
+
17
+ :javascript
18
+ $(document).ready(function() {
19
+ $('.quick-filter-btn').on('click', function() {
20
+ var isFilterActivatedInput = $($(this).attr('data-target'));
21
+ var isFilterActivated = isFilterActivatedInput.val() == '1' ? '0' : '1';
22
+ isFilterActivatedInput.val(isFilterActivated)
23
+ $('#filter-form').submit();
24
+ })
25
+ })
@@ -0,0 +1,22 @@
1
+ - if current_task.scheduled_at?
2
+ = re_card_content do
3
+ - if current_task.repeat_none?
4
+ %h5.section-title
5
+ Scheduled at
6
+ %i.bi.bi-clock-fill.me-2 :
7
+ = current_task.scheduled_at.strftime("%Y-%m-%d %H:%M")
8
+ - if current_task.scheduled_at.past?
9
+ %i.text-danger (EXPIRED)
10
+ - if current_task.repeatable?
11
+ %h5.section-title
12
+ Repeat mode:
13
+ %i.bi.bi-arrow-repeat.me-2 :
14
+ = current_task.repeat_mode_select_options[current_task.repeat_mode]
15
+ %h5.section-title
16
+ Next time at
17
+ %i.bi.bi-clock-fill.me-2 :
18
+ = current_task.next_time_at
19
+ - if current_task.jid
20
+ %h5.section-title
21
+ JID:
22
+ = current_task.jid
@@ -1,18 +1,22 @@
1
- %li.border-bottom.d-flex.pt-2.pb-2{ class: "task-is-#{task.status}" }
2
- .p-2.pt-3
3
- %span.fs-4.text-muted ##{task.id}
4
- .p-2
5
- = link_to task_path(task) do
6
- = render_owner_avatar(task.owner, size: '50x50')
7
- .p-2
8
- = link_to task_path(task) do
9
- %p.mb-0.text-black
10
- = task.title.truncate(100)
11
- - if task.syntax_status_good?
12
- %i.bi.bi-check2.text-success
13
- - elsif task.syntax_status_bad?
14
- %i.bi.bi-exclamation-octagon.text-danger
15
- %span.small.text-muted created on #{time_ago_in_words(task.created_at)} by #{render_owner_name(task.owner)}
16
- .p-2.ms-auto.text-end
17
- %span.me-1= task.comments.size
18
- %i.bi.bi-chat-left-dots
1
+ %li.border-bottom.py-2{ class: "task-is-#{task.status}" }
2
+ .row
3
+ .col.d-flex
4
+ .p-2.pt-3
5
+ %span.fs-4.text-muted ##{task.id}
6
+ .p-2
7
+ = link_to task_path(task) do
8
+ = render_owner_avatar(task.owner, size: '50x50')
9
+ .p-2
10
+ %p.mb-0.text-black
11
+ = link_to task_path(task), class: 'task-title' do
12
+ = task.title.truncate(100)
13
+ - if task.syntax_status_good?
14
+ %i.bi.bi-check2.text-success
15
+ - elsif task.syntax_status_bad?
16
+ %i.bi.bi-exclamation-octagon.text-danger
17
+ %span.small.text-muted created on #{time_ago_in_words(task.created_at)} by #{render_owner_name(task.owner)}
18
+ .col-auto.d-flex.align-items-center= render_task_labels(task)
19
+ .col-auto.d-flex.align-items-center.text-end.text-muted
20
+ %span.comments
21
+ %span.me-1= comments_count_by_task[task.id].to_i
22
+ %i.bi.bi-chat-left-dots
@@ -1,5 +1,6 @@
1
1
  = re_card_content do
2
2
  %h5.section-title Closed
3
+ = render partial: 'advanced_filter'
3
4
  %ul.list-unstyled
4
5
  - if @tasks.empty?
5
6
  %li
@@ -7,6 +8,6 @@
7
8
  %i.bi.bi-journal-code.me-2
8
9
  Welcome to add new Task
9
10
  - else
10
- = render partial: 'task', collection: @tasks
11
+ = render partial: 'task', collection: @tasks, locals: { comments_count_by_task: comments_count_by_task }
11
12
 
12
13
  = re_render_paging(@tasks)
@@ -1,5 +1,6 @@
1
1
  = re_card_content do
2
2
  %h5.section-title Completed
3
+ = render partial: 'advanced_filter'
3
4
  %ul.list-unstyled
4
5
  - if @tasks.empty?
5
6
  %li
@@ -7,6 +8,6 @@
7
8
  %i.bi.bi-journal-code.me-2
8
9
  Welcome to add new Task
9
10
  - else
10
- = render partial: 'task', collection: @tasks
11
+ = render partial: 'task', collection: @tasks, locals: { comments_count_by_task: comments_count_by_task }
11
12
 
12
13
  = re_render_paging(@tasks)
@@ -1,5 +1,6 @@
1
1
  = re_card_content do
2
2
  %h5.section-title In processing
3
+ = render partial: 'advanced_filter'
3
4
  %ul.list-unstyled
4
5
  - if @tasks.empty?
5
6
  %li
@@ -7,6 +8,6 @@
7
8
  %i.bi.bi-journal-code.me-2
8
9
  Welcome to add new Task
9
10
  - else
10
- = render partial: 'task', collection: @tasks
11
+ = render partial: 'task', collection: @tasks, locals: { comments_count_by_task: comments_count_by_task }
11
12
 
12
13
  = re_render_paging(@tasks)
@@ -23,8 +23,8 @@
23
23
  #description-html.normal-text
24
24
  - else
25
25
  %p.text-muted.text-center No description
26
+ = render partial: 'labels'
26
27
 
27
- = render partial: 'attachment_files'
28
28
  = render partial: 'script_content'
29
29
  = render partial: 'comments', locals: { new_comment: @new_comment }
30
30
 
@@ -35,7 +35,9 @@
35
35
  = render_user_info(current_task.owner, avatar_size: '40x40')
36
36
 
37
37
  = render partial: 'reviewers'
38
+ = render partial: 'schedule'
38
39
  = render partial: 'status'
40
+ = render partial: 'attachment_files'
39
41
  = render partial: 'actions'
40
42
  = render partial: 'activities'
41
43
 
data/config/routes.rb CHANGED
@@ -14,6 +14,7 @@ RailsExecution::Engine.routes.draw do
14
14
  patch :reject
15
15
  patch :approve
16
16
  patch :execute
17
+ patch :execute_in_background
17
18
  end
18
19
 
19
20
  collection do
@@ -23,5 +24,5 @@ RailsExecution::Engine.routes.draw do
23
24
 
24
25
  resources :comments, only: [:create, :update, :destroy]
25
26
  end
26
-
27
+ resources :labels, only: [:create]
27
28
  end
@@ -35,6 +35,14 @@ RailsExecution.configuration do |config|
35
35
  # '.csv': ['text/csv', 'text/plain'],
36
36
  # }
37
37
 
38
+ # config.task_schedulable = true
39
+ # config.task_scheduler = ->(scheduled_at, task_id) { }
40
+ # config.scheduled_task_remover = ->(task_id) { }
41
+
42
+ # config.task_background = true
43
+ # config.task_background_executor = ->(task_id) { }
44
+
45
+
38
46
  # Logger
39
47
  # Using Paperclip
40
48
  # config.logging = lambda do |log_file, task|
@@ -1,18 +1,27 @@
1
1
  class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
-
3
2
  def change
4
3
  create_table :rails_execution_tasks, force: true do |t|
5
4
  t.timestamps null: false
5
+ t.column :scheduled_at, :datetime
6
6
  t.column :title, :string, null: false
7
7
  t.column :status, :string, null: false
8
8
  t.column :syntax_status, :string, default: 'bad'
9
9
  t.column :description, :text
10
10
  t.column :script, :text
11
- t.column :owner_id, :integer
12
11
  t.column :owner_type, :string
12
+ t.column :owner_id, :integer
13
+ t.column :repeat_mode, :string, default: 'none'
14
+ t.column :jid, :string
13
15
  end
14
16
  add_index :rails_execution_tasks, :status
15
- add_index :rails_execution_tasks, [:owner_id, :owner_type], name: :owner_index
17
+ add_index :rails_execution_tasks, %i[owner_id owner_type], name: :owner_index
18
+
19
+ create_table :rails_execution_labels do |t|
20
+ t.timestamps
21
+ t.column :name, :string
22
+ end
23
+
24
+ add_index :rails_execution_labels, :name, unique: true
16
25
 
17
26
  create_table :rails_execution_comments, force: true do |t|
18
27
  t.timestamps null: false
@@ -22,7 +31,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
22
31
  t.column :owner_type, :string
23
32
  end
24
33
  add_index :rails_execution_comments, :task_id
25
- add_index :rails_execution_comments, [:owner_id, :owner_type], name: :owner_index
34
+ add_index :rails_execution_comments, %i[owner_id owner_type], name: :owner_index
26
35
 
27
36
  create_table :rails_execution_activities, force: true do |t|
28
37
  t.timestamps null: false
@@ -32,7 +41,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
32
41
  t.column :owner_type, :string
33
42
  end
34
43
  add_index :rails_execution_activities, :task_id
35
- add_index :rails_execution_activities, [:owner_id, :owner_type], name: :owner_index
44
+ add_index :rails_execution_activities, %i[owner_id owner_type], name: :owner_index
36
45
 
37
46
  create_table :rails_execution_task_reviews, force: true do |t|
38
47
  t.timestamps null: false
@@ -42,7 +51,15 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
42
51
  t.column :owner_type, :string
43
52
  end
44
53
  add_index :rails_execution_task_reviews, :task_id
45
- add_index :rails_execution_task_reviews, [:owner_id, :owner_type], name: :owner_index
46
- end
54
+ add_index :rails_execution_task_reviews, %i[owner_id owner_type], name: :owner_index
55
+
56
+ create_table :rails_execution_task_labels, force: true do |t|
57
+ t.timestamps null: false
58
+ t.column :task_id, :integer, null: false
59
+ t.column :label_id, :integer, null: false
60
+ end
47
61
 
62
+ add_index :rails_execution_task_labels, :task_id
63
+ add_index :rails_execution_task_labels, :label_id
64
+ end
48
65
  end
@@ -35,6 +35,11 @@ module RailsExecution
35
35
  attr_accessor :file_upload
36
36
  attr_accessor :file_uploader
37
37
  attr_accessor :file_reader
38
+ attr_accessor :task_schedulable
39
+ attr_accessor :task_scheduler # lambda
40
+ attr_accessor :scheduled_task_remover # lambda
41
+ attr_accessor :task_background
42
+ attr_accessor :task_background_executor # lambda
38
43
  attr_accessor :acceptable_file_types
39
44
 
40
45
  # Logger
@@ -59,17 +64,25 @@ module RailsExecution
59
64
  self.acceptable_file_types = DEFAULT_FILE_TYPES
60
65
  self.file_uploader = ::RailsExecution::Files::Uploader
61
66
  self.file_reader = ::RailsExecution::Files::Reader
67
+
68
+ self.task_schedulable = false
69
+ self.task_scheduler = ->(_scheduled_at, _task_id) { nil }
70
+ self.scheduled_task_remover = ->(_task_id) { nil }
71
+
72
+ self.task_background = false
73
+ self.task_background_executor = ->(_task_id) { nil }
74
+
62
75
  self.per_page = DEFAULT_PER_PAGE
63
76
  self.reviewers = -> { [] }
64
77
 
65
- self.task_creatable = -> (_user) { true }
66
- self.task_editable = -> (_user, _task) { true }
67
- self.task_closable = -> (_user, _task) { true }
68
- self.task_approvable = -> (_user, _task) { true }
69
- self.task_executable = -> (_user, _task) { true }
78
+ self.task_creatable = ->(_user) { true }
79
+ self.task_editable = ->(_user, _task) { true }
80
+ self.task_closable = ->(_user, _task) { true }
81
+ self.task_approvable = ->(_user, _task) { true }
82
+ self.task_executable = ->(_user, _task) { true }
70
83
 
71
- self.logging = -> (_log_file, _task) { }
72
- self.logging_files = -> (_task) { [] }
84
+ self.logging = ->(_log_file, _task) {}
85
+ self.logging_files = ->(_task) { [] }
73
86
 
74
87
  self.notifier = ::RailsExecution::Services::Notifier
75
88
  end
@@ -0,0 +1,56 @@
1
+ module RailsExecution
2
+ module Services
3
+ class BackgroundExecution
4
+
5
+ def self.call(task_id)
6
+ self.new(::RailsExecution::Task.find(task_id)).call
7
+ end
8
+
9
+ def initialize(task, owner = nil)
10
+ @task = task
11
+ @owner = owner
12
+ end
13
+
14
+ def call
15
+ task.with_lock do
16
+ execute_service = ::RailsExecution::Services::Execution.new(task)
17
+ if execute_service.call
18
+ task.activities.create(message: 'Execute: The task is processed successfully by background job')
19
+
20
+ if task.repeatable?
21
+ task.update(status: :approved)
22
+ task.activities.create(owner: owner, message: 'Set status to approved because the task is repeatable')
23
+ else
24
+ task.update(status: :completed)
25
+ task.activities.create(owner: owner, message: 'Set status to completed because the task is not repeatable')
26
+ end
27
+
28
+ ::RailsExecution.configuration.notifier.new(task).after_execute_success(owner)
29
+ else
30
+ task.activities.create(message: 'Execute: The task failed to be processed by the background job')
31
+
32
+ task.update(status: :approved)
33
+ task.activities.create(owner: owner, message: 'Set task status to approved because the task is failed')
34
+ ::RailsExecution.configuration.notifier.new(task).after_execute_fail(owner)
35
+ end
36
+ end
37
+ end
38
+
39
+ def setup
40
+ task.with_lock do
41
+ unless task.is_processing?
42
+ task.update(status: :processing)
43
+ task.activities.create(owner: owner, message: 'Process the task with background job')
44
+ ::RailsExecution.configuration.task_background_executor.call(task.id)
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :task
52
+ attr_reader :owner
53
+
54
+ end
55
+ end
56
+ end