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.
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