foreman-tasks 0.15.6 → 0.15.7

Sign up to get free protection for your applications and to get access to all the features.
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