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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/.tx/config +1 -1
- data/app/controllers/foreman_tasks/api/tasks_controller.rb +3 -3
- data/app/controllers/foreman_tasks/tasks_controller.rb +5 -5
- data/app/lib/actions/recurring_action.rb +2 -1
- data/app/models/foreman_tasks/task/summarizer.rb +5 -4
- data/app/services/foreman_tasks/dashboard_table_filter.rb +7 -2
- data/app/views/foreman_tasks/tasks/_details.html.erb +4 -4
- data/app/views/foreman_tasks/tasks/index.html.erb +26 -24
- data/lib/foreman_tasks/engine.rb +1 -1
- data/lib/foreman_tasks/version.rb +1 -1
- data/locale/en/foreman_tasks.po +41 -0
- data/locale/foreman_tasks.pot +167 -102
- data/test/controllers/api/tasks_controller_test.rb +37 -13
- data/test/controllers/tasks_controller_test.rb +27 -0
- data/test/unit/actions/recurring_action_test.rb +21 -0
- data/test/unit/dashboard_table_filter_test.rb +12 -0
- data/test/unit/summarizer_test.rb +1 -1
- data/webpack/ForemanTasks/Components/Chart/Chart.js +8 -1
- data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.stories.js +0 -5
- data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.js +1 -1
- data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.stories.js +13 -11
- data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/__snapshots__/TasksCardsGrid.test.js.snap +0 -16
- data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboard.scss +21 -1
- data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardHelper.js +18 -1
- data/webpack/stories/index.scss +1 -0
- 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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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',
|
@@ -38,7 +38,14 @@ class C3Chart extends React.Component {
|
|
38
38
|
|
39
39
|
destroyChart() {
|
40
40
|
try {
|
41
|
-
|
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: {
|
data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.js
CHANGED
@@ -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
|
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
|
-
<
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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: #
|
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
|
-
|
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
|
};
|
data/webpack/stories/index.scss
CHANGED
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.
|
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-
|
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
|