foreman-tasks 0.15.6 → 0.15.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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.tx/config +1 -1
  4. data/app/controllers/foreman_tasks/api/tasks_controller.rb +3 -3
  5. data/app/controllers/foreman_tasks/tasks_controller.rb +5 -5
  6. data/app/lib/actions/recurring_action.rb +2 -1
  7. data/app/models/foreman_tasks/task/summarizer.rb +5 -4
  8. data/app/services/foreman_tasks/dashboard_table_filter.rb +7 -2
  9. data/app/views/foreman_tasks/tasks/_details.html.erb +4 -4
  10. data/app/views/foreman_tasks/tasks/index.html.erb +26 -24
  11. data/lib/foreman_tasks/engine.rb +1 -1
  12. data/lib/foreman_tasks/version.rb +1 -1
  13. data/locale/en/foreman_tasks.po +41 -0
  14. data/locale/foreman_tasks.pot +167 -102
  15. data/test/controllers/api/tasks_controller_test.rb +37 -13
  16. data/test/controllers/tasks_controller_test.rb +27 -0
  17. data/test/unit/actions/recurring_action_test.rb +21 -0
  18. data/test/unit/dashboard_table_filter_test.rb +12 -0
  19. data/test/unit/summarizer_test.rb +1 -1
  20. data/webpack/ForemanTasks/Components/Chart/Chart.js +8 -1
  21. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.stories.js +0 -5
  22. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.js +1 -1
  23. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.stories.js +13 -11
  24. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/__snapshots__/TasksCardsGrid.test.js.snap +0 -16
  25. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboard.scss +21 -1
  26. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardHelper.js +18 -1
  27. data/webpack/stories/index.scss +1 -0
  28. metadata +2 -2
@@ -25,6 +25,13 @@ module ForemanTasks
25
25
  assert_response :missing
26
26
  assert_includes @response.body, 'Resource task not found by id'
27
27
  end
28
+
29
+ it 'does not show task the user is not allowed to see' do
30
+ setup_user('view', 'foreman_tasks', 'owner.id = current_user')
31
+ get :show, params: { id: FactoryBot.create(:some_task).id },
32
+ session: set_session_user(User.current)
33
+ assert_response :not_found
34
+ end
28
35
  end
29
36
 
30
37
  describe 'GET /api/tasks/summary' do
@@ -41,24 +48,41 @@ module ForemanTasks
41
48
  suspend
42
49
  end
43
50
  end
51
+
52
+ def self.while_suspended
53
+ triggered = ForemanTasks.trigger(DummyTestSummaryAction, true)
54
+ wait_for { ForemanTasks::Task.find_by(external_id: triggered.id).state == 'running' }
55
+ wait_for do
56
+ w = ForemanTasks.dynflow.world
57
+ w.persistence.load_step(triggered.id, 2, w).state == :suspended
58
+ end
59
+ yield
60
+ ForemanTasks.dynflow.world.event(triggered.id, 2, nil)
61
+ triggered.finished.wait
62
+ end
44
63
  end
45
64
 
46
65
  test_attributes :pid => 'bdcab413-a25d-4fe1-9db4-b50b5c31ebce'
47
66
  it 'get tasks summary' do
48
- triggered = ForemanTasks.trigger(DummyTestSummaryAction, true)
49
- wait_for { ForemanTasks::Task.find_by(external_id: triggered.id).state == 'running' }
50
- wait_for do
51
- w = ForemanTasks.dynflow.world
52
- w.persistence.load_step(triggered.id, 2, w).state == :suspended
67
+ DummyTestSummaryAction.while_suspended do
68
+ get :summary
69
+ assert_response :success
70
+ response = JSON.parse(@response.body)
71
+ assert_kind_of Array, response
72
+ assert_not response.empty?
73
+ assert_kind_of Hash, response[0]
74
+ end
75
+ end
76
+
77
+ it 'gets tasks summary only for tasks the user is allowed to see' do
78
+ DummyTestSummaryAction.while_suspended do
79
+ setup_user('view', 'foreman_tasks', 'owner.id = current_user')
80
+ get :summary
81
+ assert_response :success
82
+ response = JSON.parse(@response.body)
83
+ assert_kind_of Array, response
84
+ assert response.empty?
53
85
  end
54
- get :summary
55
- assert_response :success
56
- response = JSON.parse(@response.body)
57
- assert_kind_of Array, response
58
- assert_not response.empty?
59
- assert_kind_of Hash, response[0]
60
- ForemanTasks.dynflow.world.event(triggered.id, 2, nil)
61
- triggered.finished.wait
62
86
  end
63
87
  end
64
88
 
@@ -32,6 +32,15 @@ module ForemanTasks
32
32
  response = JSON.parse(@response.body)
33
33
  assert response['running']
34
34
  end
35
+
36
+ it 'shows summary only for the tasks the user is allowed to see' do
37
+ setup_user('view', 'foreman_tasks', 'owner.id = current_user')
38
+ FactoryBot.create(:some_task)
39
+ get(:summary, params: { recent_timeframe: 24 }, session: set_session_user(User.current))
40
+ assert_response :success
41
+ response = JSON.parse(@response.body)
42
+ assert_equal 0, response['stopped']['total']
43
+ end
35
44
  end
36
45
 
37
46
  it 'supports csv export' do
@@ -42,6 +51,24 @@ module ForemanTasks
42
51
  assert_include response.body.lines[1], 'Some action'
43
52
  end
44
53
 
54
+ describe 'show' do
55
+ it 'does not allow user without permissions to see task details' do
56
+ setup_user('view', 'foreman_tasks', 'owner.id = current_user')
57
+ get :show, params: { id: FactoryBot.create(:some_task).id },
58
+ session: set_session_user(User.current)
59
+ assert_response :not_found
60
+ end
61
+ end
62
+
63
+ describe 'sub_tasks' do
64
+ it 'does not allow user without permissions to see task details' do
65
+ setup_user('view', 'foreman_tasks', 'owner.id = current_user')
66
+ get :sub_tasks, params: { id: FactoryBot.create(:some_task).id },
67
+ session: set_session_user(User.current)
68
+ assert_response :not_found
69
+ end
70
+ end
71
+
45
72
  describe 'taxonomy scoping' do
46
73
  let(:organizations) { (0..1).map { FactoryBot.create(:organization) } }
47
74
  let(:tasks) { organizations.map { |o| linked_task(o) } + [FactoryBot.create(:some_task)] }
@@ -34,6 +34,14 @@ module ForemanTasks
34
34
  logic
35
35
  end
36
36
 
37
+ let(:past_recurring_logic) do
38
+ cronline = "* * * * *"
39
+ logic = ForemanTasks::RecurringLogic.new_from_cronline(cronline)
40
+ logic.state = 'active'
41
+ logic.save!
42
+ logic
43
+ end
44
+
37
45
  let(:args) { [false, [1, 2, 3]] }
38
46
 
39
47
  let(:recurring_task) do
@@ -103,6 +111,19 @@ module ForemanTasks
103
111
  ::Logging.mdc['request'] = old_id
104
112
  end
105
113
  end
114
+
115
+ specify 'it does not trigger tasks in the past' do
116
+ delay_options = past_recurring_logic.generate_delay_options
117
+ delay_options[:start_at] = Time.zone.now - 1.week
118
+ task = ForemanTasks.delay HookedAction, delay_options, *args
119
+ past_recurring_logic.tasks.count.must_equal 1
120
+
121
+ task.execution_plan.delay_record.plan
122
+ # Post planning, a new task should be scheduled
123
+ past_recurring_logic.tasks.count.must_equal 2
124
+ # The scheduled task should have the start date according to cron in future.
125
+ assert_equal (Time.zone.now + 1.minute).change(:sec => 0), past_recurring_logic.tasks.where(:state => "scheduled").first.start_at
126
+ end
106
127
  end
107
128
  end
108
129
  end
@@ -48,6 +48,18 @@ class DashboardTableFilterTest < ActiveSupport::TestCase
48
48
  end
49
49
  end
50
50
 
51
+ describe 'recent week time horizon' do
52
+ let(:params) do
53
+ { state: 'running',
54
+ time_horizon: 'week',
55
+ time_mode: 'recent' }
56
+ end
57
+
58
+ it 'filters' do
59
+ filtered_scope.count.must_equal @tasks_builder.distribution['running'][:recent]
60
+ end
61
+ end
62
+
51
63
  describe 'older' do
52
64
  let(:params) do
53
65
  { state: 'running',
@@ -12,7 +12,7 @@ class SummarizerTest < ActiveSupport::TestCase
12
12
  end
13
13
 
14
14
  let :subject do
15
- ForemanTasks::Task::Summarizer.new
15
+ ForemanTasks::Task::Summarizer.new(ForemanTasks::Task)
16
16
  end
17
17
 
18
18
  let :expected do
@@ -38,7 +38,14 @@ class C3Chart extends React.Component {
38
38
 
39
39
  destroyChart() {
40
40
  try {
41
- this.chart = this.chart.destroy();
41
+ // A workaround for a case, where the chart might be still in transition
42
+ // phase while unmounting/destroying - destroying right away leads
43
+ // to issue described in https://github.com/bcbcarl/react-c3js/issues/22.
44
+ // Delaying the destroy a bit seems to resolve the issue.
45
+ // The chart API methods are already bind explicitly, therefore we don't need
46
+ // any special handling when passing the function.
47
+ setTimeout(this.chart.destroy, 1000);
48
+ this.chart = null;
42
49
  } catch (err) {
43
50
  throw new Error('Internal C3 error', err);
44
51
  }
@@ -38,11 +38,6 @@ storiesOf('TasksDashboard/TasksCardsGrid', module)
38
38
  );
39
39
  return (
40
40
  <div>
41
- <link
42
- rel="stylesheet"
43
- type="text/css"
44
- href="https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly-additions.min.css"
45
- />
46
41
  <StoppedTasksCard
47
42
  data={{
48
43
  error: {
@@ -23,7 +23,7 @@ const TasksCardsGrid = ({ time, query, data, updateQuery }) => (
23
23
  [TASKS_DASHBOARD_AVAILABLE_QUERY_STATES.STOPPED, StoppedTasksCard],
24
24
  [TASKS_DASHBOARD_AVAILABLE_QUERY_STATES.SCHEDULED, ScheduledTasksCard],
25
25
  ].map(([key, Card]) => (
26
- <CardGrid.Col sm={6} lg={3} key={key}>
26
+ <CardGrid.Col key={key}>
27
27
  <Card
28
28
  matchHeight
29
29
  data={data[key]}
@@ -37,16 +37,18 @@ storiesOf('TasksDashboard/TasksCardsGrid', module)
37
37
  );
38
38
 
39
39
  return (
40
- <TasksCardsGrid
41
- time={selectTime}
42
- query={{
43
- state: selectState,
44
- result: selectResult,
45
- mode: selectMode,
46
- time: selectTime,
47
- }}
48
- data={object('data', MOCKED_DATA)}
49
- updateQuery={action('updateQuery')}
50
- />
40
+ <div>
41
+ <TasksCardsGrid
42
+ time={selectTime}
43
+ query={{
44
+ state: selectState,
45
+ result: selectResult,
46
+ mode: selectMode,
47
+ time: selectTime,
48
+ }}
49
+ data={object('data', MOCKED_DATA)}
50
+ updateQuery={action('updateQuery')}
51
+ />
52
+ </div>
51
53
  );
52
54
  });
@@ -14,8 +14,6 @@ exports[`TasksCardsGrid render with minimal props 1`] = `
14
14
  bsClass="col"
15
15
  componentClass="div"
16
16
  key="running"
17
- lg={3}
18
- sm={6}
19
17
  >
20
18
  <RunningTasksCard
21
19
  className=""
@@ -35,8 +33,6 @@ exports[`TasksCardsGrid render with minimal props 1`] = `
35
33
  bsClass="col"
36
34
  componentClass="div"
37
35
  key="paused"
38
- lg={3}
39
- sm={6}
40
36
  >
41
37
  <PausedTasksCard
42
38
  className=""
@@ -56,8 +52,6 @@ exports[`TasksCardsGrid render with minimal props 1`] = `
56
52
  bsClass="col"
57
53
  componentClass="div"
58
54
  key="stopped"
59
- lg={3}
60
- sm={6}
61
55
  >
62
56
  <StoppedTasksCard
63
57
  className=""
@@ -87,8 +81,6 @@ exports[`TasksCardsGrid render with minimal props 1`] = `
87
81
  bsClass="col"
88
82
  componentClass="div"
89
83
  key="scheduled"
90
- lg={3}
91
- sm={6}
92
84
  >
93
85
  <ScheduledTasksCard
94
86
  className=""
@@ -117,8 +109,6 @@ exports[`TasksCardsGrid render with props 1`] = `
117
109
  bsClass="col"
118
110
  componentClass="div"
119
111
  key="running"
120
- lg={3}
121
- sm={6}
122
112
  >
123
113
  <RunningTasksCard
124
114
  className=""
@@ -142,8 +132,6 @@ exports[`TasksCardsGrid render with props 1`] = `
142
132
  bsClass="col"
143
133
  componentClass="div"
144
134
  key="paused"
145
- lg={3}
146
- sm={6}
147
135
  >
148
136
  <PausedTasksCard
149
137
  className=""
@@ -167,8 +155,6 @@ exports[`TasksCardsGrid render with props 1`] = `
167
155
  bsClass="col"
168
156
  componentClass="div"
169
157
  key="stopped"
170
- lg={3}
171
- sm={6}
172
158
  >
173
159
  <StoppedTasksCard
174
160
  className=""
@@ -202,8 +188,6 @@ exports[`TasksCardsGrid render with props 1`] = `
202
188
  bsClass="col"
203
189
  componentClass="div"
204
190
  key="scheduled"
205
- lg={3}
206
- sm={6}
207
191
  >
208
192
  <ScheduledTasksCard
209
193
  className=""
@@ -1,6 +1,26 @@
1
+ @import '~bootstrap-sass/assets/stylesheets/bootstrap/_variables';
2
+ @import '~bootstrap-sass/assets/stylesheets/bootstrap/_mixins';
3
+
4
+ @mixin create-tasks-dashboard-column($columns: 12, $screen-min: 0, $gutter: $grid-gutter-width) {
5
+ @media (min-width: $screen-min) {
6
+ width: percentage(($columns / $grid-columns));
7
+ float: left;
8
+ position: relative;
9
+ min-height: 1px;
10
+ padding-right: ($gutter / 2);
11
+ padding-left: ($gutter / 2);
12
+ }
13
+ }
14
+
1
15
  .tasks-dashboard-grid {
2
16
  min-height: 200px;
3
- background-color: #F5F5F5;
17
+ background-color: #f5f5f5;
4
18
  margin: 5px -60px 20px -60px;
5
19
  padding: 20px 50px;
20
+
21
+ .row > div {
22
+ @include create-tasks-dashboard-column(12, 0);
23
+ @include create-tasks-dashboard-column(6, 900px);
24
+ @include create-tasks-dashboard-column(3, 1500px);
25
+ }
6
26
  }
@@ -73,6 +73,23 @@ export const resolveQuery = query => {
73
73
  const { search } = uri.query(true);
74
74
 
75
75
  const data = { search, ...uriQuery, page: 1 };
76
+ const { $, tfm } = window;
77
+
76
78
  uri.query(URI.buildQuery(data, true));
77
- window.location.href = uri.toString();
79
+ tfm.tools.showSpinner();
80
+ $.ajax({
81
+ type: 'get',
82
+ url: uri.toString(),
83
+ success(result) {
84
+ const res = $(`<div>${result}</div>`);
85
+
86
+ $('#tasks-table').html(res.find('#tasks-table'));
87
+ },
88
+ error({ statusText }) {
89
+ $('#tasks-table').html(statusText);
90
+ },
91
+ complete(result) {
92
+ tfm.tools.hideSpinner();
93
+ },
94
+ });
78
95
  };
@@ -4,3 +4,4 @@ $icon-font-path: '~patternfly/dist/fonts/';
4
4
 
5
5
  @import '~patternfly/dist/sass/patternfly';
6
6
  @import '~patternfly-react/dist/sass/patternfly-react';
7
+ @import '~font-awesome-sass/assets/stylesheets/_font-awesome';
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.6
4
+ version: 0.15.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-31 00:00:00.000000000 Z
11
+ date: 2019-07-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: foreman-tasks-core