foreman-tasks 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/js_tests.yml +5 -1
  3. data/app/controllers/foreman_tasks/api/tasks_controller.rb +19 -5
  4. data/app/lib/actions/entry_action.rb +8 -4
  5. data/app/lib/actions/helpers/lock.rb +11 -5
  6. data/app/lib/actions/middleware/keep_current_request_id.rb +4 -1
  7. data/app/lib/actions/middleware/keep_current_user.rb +11 -1
  8. data/app/lib/actions/observable_action.rb +80 -0
  9. data/app/models/foreman_tasks/concerns/action_subject.rb +0 -6
  10. data/app/models/foreman_tasks/link.rb +60 -0
  11. data/app/models/foreman_tasks/lock.rb +30 -128
  12. data/app/models/foreman_tasks/recurring_logic.rb +3 -3
  13. data/app/models/foreman_tasks/task.rb +20 -7
  14. data/app/models/foreman_tasks/task/search.rb +7 -6
  15. data/app/models/setting/foreman_tasks.rb +8 -8
  16. data/app/services/foreman_tasks/dashboard_table_filter.rb +5 -1
  17. data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
  18. data/app/views/foreman_tasks/api/tasks/details.json.rabl +5 -3
  19. data/app/views/foreman_tasks/layouts/react.html.erb +1 -2
  20. data/app/views/foreman_tasks/tasks/_lock_card.html.erb +10 -0
  21. data/app/views/foreman_tasks/tasks/dashboard/_latest_tasks_in_error_warning.html.erb +1 -1
  22. data/app/views/foreman_tasks/tasks/dashboard/_tasks_status.html.erb +1 -1
  23. data/app/views/foreman_tasks/tasks/show.html.erb +1 -6
  24. data/db/migrate/20181206123910_create_foreman_tasks_links.foreman_tasks.rb +26 -0
  25. data/db/migrate/20181206124952_migrate_non_exclusive_locks_to_links.foreman_tasks.rb +14 -0
  26. data/db/migrate/20181206131436_drop_old_locks.foreman_tasks.rb +20 -0
  27. data/db/migrate/20181206131627_make_locks_exclusive.foreman_tasks.rb +25 -0
  28. data/foreman-tasks.gemspec +1 -0
  29. data/lib/foreman_tasks/cleaner.rb +10 -0
  30. data/lib/foreman_tasks/engine.rb +5 -2
  31. data/lib/foreman_tasks/tasks/export_tasks.rake +2 -2
  32. data/lib/foreman_tasks/version.rb +1 -1
  33. data/package.json +6 -6
  34. data/test/controllers/api/tasks_controller_test.rb +10 -0
  35. data/test/controllers/tasks_controller_test.rb +1 -1
  36. data/test/unit/actions/action_with_sub_plans_test.rb +5 -2
  37. data/test/unit/cleaner_test.rb +4 -4
  38. data/test/unit/locking_test.rb +85 -0
  39. data/test/unit/recurring_logic_test.rb +6 -0
  40. data/test/unit/task_test.rb +10 -0
  41. data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +2 -2
  42. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +3 -2
  43. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +4 -4
  44. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +2 -0
  45. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +4 -1
  46. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss +14 -0
  47. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +3 -0
  48. data/webpack/ForemanTasks/Components/TaskDetails/index.js +2 -0
  49. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/ScheduledTasksCard.scss +4 -0
  50. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/OtherInfo.js +53 -0
  51. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/OtherInfo.test.js +14 -0
  52. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.js +27 -19
  53. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.scss +14 -0
  54. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.test.js +1 -34
  55. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/{StoppedTasksCardHelper.js → StoppedTasksCardTable.js} +28 -1
  56. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCardTable.test.js +54 -0
  57. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/__snapshots__/OtherInfo.test.js.snap +48 -0
  58. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/__snapshots__/StoppedTasksCard.test.js.snap +60 -1367
  59. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/__snapshots__/StoppedTasksCardTable.test.js.snap +960 -0
  60. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/__snapshots__/TasksCardsGrid.test.js.snap +14 -11
  61. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardConstants.js +2 -0
  62. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardSelectors.js +17 -11
  63. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboardSelectors.test.js +26 -14
  64. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboard.test.js.snap +14 -11
  65. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboardSelectors.test.js.snap +38 -22
  66. data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +0 -8
  67. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +13 -4
  68. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.scss +4 -13
  69. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +2 -4
  70. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/actionNameCellFormatter.test.js.snap +1 -0
  71. data/webpack/ForemanTasks/Components/TasksTable/formatters/actionNameCellFormatter.js +5 -1
  72. metadata +24 -11
  73. data/app/assets/stylesheets/foreman_tasks/tasks.scss +0 -9
  74. data/test/unit/lock_test.rb +0 -22
@@ -10,7 +10,7 @@ module ForemanTasks
10
10
  # rubocop:enable Naming/AccessorMethodName
11
11
 
12
12
  def linked_task(resource)
13
- FactoryBot.create(:some_task).tap { |t| ForemanTasks::Lock.link!(resource, t.id) }
13
+ FactoryBot.create(:some_task).tap { |t| ForemanTasks::Link.link!(resource, t) }
14
14
  end
15
15
 
16
16
  def in_taxonomy_scope(organization, location = nil)
@@ -34,8 +34,9 @@ module ForemanTasks
34
34
  describe Actions::ActionWithSubPlans do
35
35
  include ForemanTasks::TestHelpers::WithInThreadExecutor
36
36
 
37
+ let(:user) { FactoryBot.create(:user) }
38
+
37
39
  let(:task) do
38
- user = FactoryBot.create(:user)
39
40
  triggered = ForemanTasks.trigger(ParentAction, user)
40
41
  raise triggered.error if triggered.respond_to?(:error)
41
42
  triggered.finished.wait(30)
@@ -49,7 +50,9 @@ module ForemanTasks
49
50
 
50
51
  specify "the locks of the sub-plan don't colide with the locks of its parent" do
51
52
  child_task = task.sub_tasks.first
52
- assert(child_task.locks.any? { |lock| lock.name == 'write' }, "it's locks don't conflict with parent's")
53
+ assert_not(child_task.locks.any?, "the lock is ensured by the parent")
54
+ found = ForemanTasks::Link.for_resource(user).where(:task_id => child_task.id).any?
55
+ assert(found, "the action is linked properly")
53
56
  end
54
57
  end
55
58
  end
@@ -60,18 +60,18 @@ class TasksTest < ActiveSupport::TestCase
60
60
  task.started_at = task.ended_at = Time.zone.now
61
61
  task.save
62
62
  end]
63
- lock_to_delete = tasks_to_delete.first.locks.create(:name => 'read', :resource => User.current)
63
+ link_to_delete = tasks_to_delete.first.links.create(:resource => User.current)
64
64
 
65
65
  tasks_to_keep = [FactoryBot.create(:dynflow_task, :product_create_task)]
66
- lock_to_keep = tasks_to_keep.first.locks.create(:name => 'read', :resource => User.current)
66
+ link_to_keep = tasks_to_keep.first.links.create(:resource => User.current)
67
67
 
68
68
  cleaner.expects(:tasks_to_csv)
69
69
  cleaner.delete
70
70
  _(ForemanTasks::Task.where(id: tasks_to_delete)).must_be_empty
71
71
  _(ForemanTasks::Task.where(id: tasks_to_keep)).must_equal tasks_to_keep
72
72
 
73
- _(ForemanTasks::Lock.find_by(id: lock_to_delete.id)).must_be_nil
74
- _(ForemanTasks::Lock.find_by(id: lock_to_keep.id)).wont_be_nil
73
+ _(ForemanTasks::Link.find_by(id: link_to_delete.id)).must_be_nil
74
+ _(ForemanTasks::Link.find_by(id: link_to_keep.id)).wont_be_nil
75
75
  end
76
76
 
77
77
  it 'supports passing empty filter (just delete all)' do
@@ -0,0 +1,85 @@
1
+ require 'ostruct'
2
+ require 'foreman_tasks_test_helper'
3
+
4
+ module ForemanTasks
5
+ class LockTest < ::ActiveSupport::TestCase
6
+ describe ::ForemanTasks::Lock::LockConflict do
7
+ class FakeLockConflict < ForemanTasks::Lock::LockConflict
8
+ def _(val)
9
+ val.freeze
10
+ end
11
+ end
12
+
13
+ it 'does not modify frozen strings' do
14
+ required_lock = OpenStruct.new(:name => 'my_lock')
15
+ # Before #21770 the next line would raise
16
+ # RuntimeError: can't modify frozen String
17
+ conflict = FakeLockConflict.new(required_lock, [])
18
+ assert conflict._('this should be frozen').frozen?
19
+ end
20
+ end
21
+
22
+ describe 'locking and linking' do
23
+ before { [Lock, Link].each(&:destroy_all) }
24
+ let(:task1) { FactoryBot.create(:some_task) }
25
+ let(:task2) { FactoryBot.create(:some_task) }
26
+ let(:resource) { FactoryBot.create(:user) }
27
+
28
+ describe Lock do
29
+ it 'can lock a resource for a single task' do
30
+ Lock.lock!(resource, task1)
31
+ end
32
+
33
+ it 'can lock a resource for a single task only once' do
34
+ Lock.lock!(resource, task1)
35
+ _(Lock.for_resource(resource).count).must_equal 1
36
+ Lock.lock!(resource, task1)
37
+ _(Lock.for_resource(resource).count).must_equal 1
38
+ end
39
+
40
+ it 'cannot lock a resource for multiple tasks' do
41
+ lock = Lock.lock!(resource, task1)
42
+ _(Lock.colliding_locks(resource, task2)).must_equal [lock]
43
+ assert_raises Lock::LockConflict do
44
+ Lock.lock!(resource, task2)
45
+ end
46
+ end
47
+
48
+ it 'raises LockConflict when enforced by db' do
49
+ lock = Lock.lock!(resource, task1)
50
+ Lock.any_instance
51
+ .expects(:colliding_locks).twice.returns([], [lock])
52
+ exception = assert_raises Lock::LockConflict do
53
+ Lock.lock!(resource, task2)
54
+ end
55
+ _(exception.message).must_match(/#{lock.task_id}/)
56
+ end
57
+
58
+ it 'creates a link when creating a lock for a resource' do
59
+ Lock.lock!(resource, task1)
60
+ link = Link.for_resource(resource).first
61
+ _(link.task_id).must_equal task1.id
62
+ end
63
+ end
64
+
65
+ describe Link do
66
+ it 'can link a resource to a single task' do
67
+ Link.link!(resource, task1)
68
+ end
69
+
70
+ it 'can link a resource for a single task only once' do
71
+ Link.link!(resource, task1)
72
+ _(Link.for_resource(resource).count).must_equal 1
73
+ Link.link!(resource, task1)
74
+ _(Link.for_resource(resource).count).must_equal 1
75
+ end
76
+
77
+ it 'can link a resource to multiple tasks' do
78
+ Link.link!(resource, task1)
79
+ Link.link!(resource, task2)
80
+ _(Link.for_resource(resource).count).must_equal 2
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -148,6 +148,12 @@ class RecurringLogicsTest < ActiveSupport::TestCase
148
148
  assert ForemanTasks.dynflow.world.persistence.load_delayed_plan(task.execution_plan.id).frozen
149
149
  end
150
150
 
151
+ it 'handles if the task has been deleted' do
152
+ logic.tasks.find_by(:state => 'scheduled').destroy
153
+ logic.update!(:enabled => false)
154
+ assert_equal 'disabled', logic.state
155
+ end
156
+
151
157
  it 'properly re-enables on disable' do
152
158
  logic.update!(:enabled => false)
153
159
  logic.update!(:enabled => true)
@@ -129,6 +129,16 @@ class TasksTest < ActiveSupport::TestCase
129
129
  _ { proc { ForemanTasks::Task.search_for('duration = "25 potatoes"') } }.must_raise ScopedSearch::QueryNotSupported
130
130
  end
131
131
  end
132
+
133
+ context 'by taxonomies' do
134
+ test 'can search by taxonomies using IN' do
135
+ assert_nothing_raised(PG::SyntaxError) { ForemanTasks::Task.search_for('location_id ^ (1)').first }
136
+ assert_nothing_raised(PG::SyntaxError) { ForemanTasks::Task.search_for('organization_id ^ (1)').first }
137
+ assert_nothing_raised(PG::SyntaxError) { ForemanTasks::Task.search_for('location_id ^ (1,2)').first }
138
+ assert_nothing_raised(PG::SyntaxError) { ForemanTasks::Task.search_for('organization_id ^ (1,2)').first }
139
+ assert_nothing_raised(PG::SyntaxError) { ForemanTasks::Task.search_for('organization_id = 1').first }
140
+ end
141
+ end
132
142
  end
133
143
 
134
144
  describe 'users' do
@@ -21,10 +21,10 @@ const Locks = ({ locks }) => (
21
21
  lock.exclusive ? 'fa-lock' : 'fa-unlock-alt'
22
22
  }`}
23
23
  />
24
- {lock.name}
24
+ {lock.resource_type}
25
25
  </Card.Title>
26
26
  <Card.Body>
27
- {`${lock.resource_type} id:${lock.resource_id}`}
27
+ {`id:${lock.resource_id}`}
28
28
  <br />
29
29
  </Card.Body>
30
30
  </Card>
@@ -69,6 +69,7 @@ class TaskInfo extends Component {
69
69
  {
70
70
  title: 'Name',
71
71
  value: action || __('N/A'),
72
+ className: 'details-name',
72
73
  },
73
74
  {
74
75
  title: 'Start at',
@@ -129,7 +130,7 @@ class TaskInfo extends Component {
129
130
  {__(items[0].title)}:
130
131
  </span>
131
132
  </Col>
132
- <Col md={5} sm={6}>
133
+ <Col md={5} sm={6} className={items[0].className}>
133
134
  <span>{items[0].value}</span>
134
135
  </Col>
135
136
  <Col md={2} sm={6}>
@@ -137,7 +138,7 @@ class TaskInfo extends Component {
137
138
  {__(items[1].title)}:
138
139
  </span>
139
140
  </Col>
140
- <Col md={3} sm={6}>
141
+ <Col md={3} sm={6} className={items[1].className}>
141
142
  {items[1].value}
142
143
  </Col>
143
144
  </Row>
@@ -39,12 +39,12 @@ exports[`Locks rendering render with Props 1`] = `
39
39
  <span
40
40
  className="fa fa-unlock-alt"
41
41
  />
42
- task_owner
42
+ User
43
43
  </CardTitle>
44
44
  <CardBody
45
45
  className=""
46
46
  >
47
- User id:4
47
+ id:4
48
48
  <br />
49
49
  </CardBody>
50
50
  </Card>
@@ -71,12 +71,12 @@ exports[`Locks rendering render with Props 1`] = `
71
71
  <span
72
72
  className="fa fa-unlock-alt"
73
73
  />
74
- task_owner2
74
+ User
75
75
  </CardTitle>
76
76
  <CardBody
77
77
  className=""
78
78
  >
79
- User id:2
79
+ id:2
80
80
  <br />
81
81
  </CardBody>
82
82
  </Card>
@@ -27,6 +27,7 @@ exports[`TaskInfo rendering render with Props 1`] = `
27
27
  </Col>
28
28
  <Col
29
29
  bsClass="col"
30
+ className="details-name"
30
31
  componentClass="div"
31
32
  md={5}
32
33
  sm={6}
@@ -330,6 +331,7 @@ exports[`TaskInfo rendering render without Props 1`] = `
330
331
  </Col>
331
332
  <Col
332
333
  bsClass="col"
334
+ className="details-name"
333
335
  componentClass="div"
334
336
  md={5}
335
337
  sm={6}
@@ -19,6 +19,7 @@ const TaskDetails = ({
19
19
  failedSteps,
20
20
  runningSteps,
21
21
  locks,
22
+ links,
22
23
  cancelStep,
23
24
  taskReloadStart,
24
25
  taskReloadStop,
@@ -87,7 +88,7 @@ const TaskDetails = ({
87
88
  <Errors executionPlan={executionPlan} failedSteps={failedSteps} />
88
89
  </Tab>
89
90
  <Tab eventKey={4} disabled={isLoading} title={__('Locks')}>
90
- <Locks locks={locks} />
91
+ <Locks locks={locks.concat(links)} />
91
92
  </Tab>
92
93
  <Tab eventKey={5} disabled={isLoading} title={__('Raw')}>
93
94
  <Raw
@@ -114,6 +115,7 @@ TaskDetails.propTypes = {
114
115
  APIerror: PropTypes.object,
115
116
  taskReloadStop: PropTypes.func.isRequired,
116
117
  taskReloadStart: PropTypes.func.isRequired,
118
+ links: PropTypes.array,
117
119
  ...Task.propTypes,
118
120
  ...Errors.propTypes,
119
121
  ...Locks.propTypes,
@@ -124,6 +126,7 @@ TaskDetails.defaultProps = {
124
126
  runningSteps: [],
125
127
  APIerror: null,
126
128
  status: STATUS.PENDING,
129
+ links: [],
127
130
  ...Task.defaultProps,
128
131
  ...RunningSteps.defaultProps,
129
132
  ...Errors.defaultProps,
@@ -49,4 +49,18 @@
49
49
  .dynflow-button > span {
50
50
  pointer-events: auto;
51
51
  }
52
+
53
+ pre {
54
+ white-space: pre-wrap;
55
+ word-break: normal;
56
+ }
57
+
58
+ .param-name {
59
+ display: inline-block;
60
+ width: 10em;
61
+ }
62
+
63
+ .details-name {
64
+ overflow-wrap: anywhere;
65
+ }
52
66
  }
@@ -70,6 +70,9 @@ export const selectHasSubTasks = state =>
70
70
  export const selectLocks = state =>
71
71
  selectTaskDetailsResponse(state).locks || [];
72
72
 
73
+ export const selectLinks = state =>
74
+ selectTaskDetailsResponse(state).links || [];
75
+
73
76
  export const selectUsernamePath = state =>
74
77
  selectTaskDetailsResponse(state)?.username_path;
75
78
 
@@ -9,6 +9,7 @@ import {
9
9
  selectStartBefore,
10
10
  selectStartedAt,
11
11
  selectLocks,
12
+ selectLinks,
12
13
  selectInput,
13
14
  selectOutput,
14
15
  selectResumable,
@@ -56,6 +57,7 @@ const mapStateToProps = state => ({
56
57
  help: selectHelp(state),
57
58
  hasSubTasks: selectHasSubTasks(state),
58
59
  locks: selectLocks(state),
60
+ links: selectLinks(state),
59
61
  usernamePath: selectUsernamePath(state),
60
62
  action: selectAction(state),
61
63
  state: selectState(state),
@@ -1,15 +1,19 @@
1
1
  .scheduled-tasks-card {
2
2
  text-align: center;
3
+
3
4
  .scheduled-data {
5
+ margin-top: 30px;
4
6
  padding-right: 15px;
5
7
  cursor: pointer;
6
8
  font-size: 40px;
7
9
  font-weight: 300;
8
10
  transition: font-weight 50ms ease-in;
11
+
9
12
  p {
10
13
  font-size: 20px;
11
14
  margin: 0;
12
15
  }
16
+
13
17
  * {
14
18
  margin: 10px;
15
19
  }
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import classNames from 'classnames';
4
+
5
+ import { Icon, Button, OverlayTrigger, Tooltip } from 'patternfly-react';
6
+ import { translate as __ } from 'foremanReact/common/I18n';
7
+ import {
8
+ TASKS_DASHBOARD_AVAILABLE_QUERY_STATES,
9
+ TASKS_DASHBOARD_AVAILABLE_QUERY_RESULTS,
10
+ } from '../../../../TasksDashboardConstants';
11
+ import { queryPropType } from '../../../../TasksDashboardPropTypes';
12
+
13
+ const tooltip = (
14
+ <Tooltip id="stopped-tooltip">
15
+ {__('Other includes all stopped tasks that are cancelled or pending')}
16
+ </Tooltip>
17
+ );
18
+
19
+ export const OtherInfo = ({ updateQuery, otherCount, query }) => {
20
+ const { OTHER } = TASKS_DASHBOARD_AVAILABLE_QUERY_RESULTS;
21
+ const { STOPPED } = TASKS_DASHBOARD_AVAILABLE_QUERY_STATES;
22
+ const active = query.state === STOPPED && query.result === OTHER;
23
+ return (
24
+ <span className={classNames(active && 'other-active')}>
25
+ <OverlayTrigger
26
+ overlay={tooltip}
27
+ trigger={['hover', 'focus']}
28
+ placement="bottom"
29
+ >
30
+ <span>
31
+ <Icon type="pf" name="info" />
32
+ <span>{__('Other:')} </span>
33
+ </span>
34
+ </OverlayTrigger>
35
+ <Button
36
+ bsStyle="link"
37
+ onClick={() =>
38
+ updateQuery({
39
+ state: STOPPED,
40
+ result: OTHER,
41
+ })
42
+ }
43
+ >
44
+ {otherCount}
45
+ </Button>
46
+ </span>
47
+ );
48
+ };
49
+ OtherInfo.propTypes = {
50
+ updateQuery: PropTypes.func.isRequired,
51
+ otherCount: PropTypes.number.isRequired,
52
+ query: queryPropType.isRequired,
53
+ };
@@ -0,0 +1,14 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+
3
+ import { OtherInfo } from './OtherInfo';
4
+
5
+ const fixtures = {
6
+ render: {
7
+ updateQuery: jest.fn,
8
+ otherCount: 7,
9
+ query: { state: 'STOPPED', result: 'OTHER' },
10
+ },
11
+ };
12
+
13
+ describe('OtherInfo', () =>
14
+ testComponentSnapshotsWithFixtures(OtherInfo, fixtures));
@@ -4,8 +4,8 @@ import { Card } from 'patternfly-react';
4
4
  import classNames from 'classnames';
5
5
  import { noop } from 'foremanReact/common/helpers';
6
6
  import { translate as __ } from 'foremanReact/common/I18n';
7
-
8
- import { StoppedTable } from './StoppedTasksCardHelper';
7
+ import { OtherInfo } from './OtherInfo';
8
+ import { StoppedTable } from './StoppedTasksCardTable';
9
9
  import {
10
10
  timePropType,
11
11
  queryPropType,
@@ -14,7 +14,6 @@ import {
14
14
  TASKS_DASHBOARD_AVAILABLE_TIMES,
15
15
  TASKS_DASHBOARD_AVAILABLE_QUERY_STATES,
16
16
  } from '../../../../TasksDashboardConstants';
17
- import { getQueryValueText } from '../../../../TasksDashboardHelper';
18
17
  import './StoppedTasksCard.scss';
19
18
 
20
19
  const StoppedTasksCard = ({
@@ -44,16 +43,19 @@ const StoppedTasksCard = ({
44
43
  {__('Stopped')}
45
44
  </Card.Title>
46
45
  <Card.Body>
47
- <table className="table table-bordered table-striped stopped-table">
48
- <thead>
49
- <tr>
50
- <th />
51
- <th>{__('Total')}</th>
52
- <th>{getQueryValueText(time)}</th>
53
- </tr>
54
- </thead>
55
- <tbody>{StoppedTable(data, query, time, updateQuery)}</tbody>
56
- </table>
46
+ <React.Fragment>
47
+ <StoppedTable
48
+ data={data.results}
49
+ query={query}
50
+ time={time}
51
+ updateQuery={updateQuery}
52
+ />
53
+ <OtherInfo
54
+ updateQuery={updateQuery}
55
+ otherCount={data.other}
56
+ query={query}
57
+ />
58
+ </React.Fragment>
57
59
  </Card.Body>
58
60
  </Card>
59
61
  );
@@ -66,9 +68,12 @@ const resultPropType = PropTypes.shape({
66
68
 
67
69
  StoppedTasksCard.propTypes = {
68
70
  data: PropTypes.shape({
69
- error: resultPropType.isRequired,
70
- warning: resultPropType.isRequired,
71
- success: resultPropType.isRequired,
71
+ results: PropTypes.shape({
72
+ error: resultPropType.isRequired,
73
+ warning: resultPropType.isRequired,
74
+ success: resultPropType.isRequired,
75
+ }),
76
+ other: PropTypes.number,
72
77
  }),
73
78
  time: timePropType,
74
79
  query: queryPropType,
@@ -78,9 +83,12 @@ StoppedTasksCard.propTypes = {
78
83
 
79
84
  StoppedTasksCard.defaultProps = {
80
85
  data: {
81
- error: { total: 0, last: 0 },
82
- warning: { total: 0, last: 0 },
83
- success: { total: 0, last: 0 },
86
+ results: {
87
+ error: { total: 0, last: 0 },
88
+ warning: { total: 0, last: 0 },
89
+ success: { total: 0, last: 0 },
90
+ },
91
+ other: 0,
84
92
  },
85
93
  time: TASKS_DASHBOARD_AVAILABLE_TIMES.H24,
86
94
  query: {},