foreman-tasks 11.0.7 → 12.0.0

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/views/foreman_tasks/api/tasks/dependency_summary.json.rabl +4 -0
  3. data/app/views/foreman_tasks/api/tasks/details.json.rabl +20 -0
  4. data/lib/foreman_tasks/engine.rb +6 -1
  5. data/lib/foreman_tasks/triggers.rb +4 -0
  6. data/lib/foreman_tasks/version.rb +1 -1
  7. data/lib/foreman_tasks.rb +24 -0
  8. data/test/unit/chaining_test.rb +62 -0
  9. data/webpack/ForemanTasks/Components/TaskDetails/Components/Dependencies.js +93 -0
  10. data/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js +7 -12
  11. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js +40 -20
  12. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Dependencies.test.js +92 -0
  13. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js +2 -3
  14. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap +90 -112
  15. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +13 -1
  16. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +6 -0
  17. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap +19 -0
  18. data/webpack/ForemanTasks/Components/TaskDetails/index.js +4 -0
  19. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/OtherInfo.js +5 -3
  20. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/OtherInfo.test.js +58 -9
  21. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.scss +0 -30
  22. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.test.js +190 -9
  23. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCardTable.js +70 -35
  24. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksLabelsRow/TasksLabelsRow.js +9 -14
  25. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksLabelsRow/TasksLabelsRow.scss +3 -23
  26. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksLabelsRow/TasksLabelsRow.test.js +33 -26
  27. data/webpack/ForemanTasks/Components/TasksTable/Components/ActionSelectButton.js +31 -30
  28. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/ActionSelectButton.test.js +78 -11
  29. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +40 -21
  30. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +64 -62
  31. metadata +6 -7
  32. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCardTable.test.js +0 -54
  33. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/__snapshots__/OtherInfo.test.js.snap +0 -30
  34. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/__snapshots__/StoppedTasksCard.test.js.snap +0 -107
  35. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/__snapshots__/StoppedTasksCardTable.test.js.snap +0 -960
  36. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksLabelsRow/__snapshots__/TasksLabelsRow.test.js.snap +0 -47
  37. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/__snapshots__/ActionSelectButton.test.js.snap +0 -43
@@ -2,7 +2,13 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { capitalize } from 'lodash';
4
4
  import classNames from 'classnames';
5
- import { Icon, Button } from 'patternfly-react';
5
+ import { Icon, Button } from '@patternfly/react-core';
6
+ import {
7
+ ExclamationCircleIcon,
8
+ ExclamationTriangleIcon,
9
+ CheckIcon,
10
+ } from '@patternfly/react-icons';
11
+ import { Table, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
6
12
  import { translate as __ } from 'foremanReact/common/I18n';
7
13
  import {
8
14
  TASKS_DASHBOARD_AVAILABLE_QUERY_STATES,
@@ -15,9 +21,21 @@ import {
15
21
  } from '../../../../TasksDashboardPropTypes';
16
22
 
17
23
  const resultIcons = {
18
- error: <Icon type="pf" name="error-circle-o" />,
19
- warning: <Icon type="pf" name="warning-triangle-o" />,
20
- success: <Icon type="pf" name="ok" />,
24
+ error: (
25
+ <Icon status="danger" size="sm">
26
+ <ExclamationCircleIcon />
27
+ </Icon>
28
+ ),
29
+ warning: (
30
+ <Icon status="warning" size="sm">
31
+ <ExclamationTriangleIcon />
32
+ </Icon>
33
+ ),
34
+ success: (
35
+ <Icon status="success" size="sm">
36
+ <CheckIcon />
37
+ </Icon>
38
+ ),
21
39
  };
22
40
 
23
41
  const StoppedTableCells = (data, query, time, updateQuery) =>
@@ -35,51 +53,68 @@ const StoppedTableCells = (data, query, time, updateQuery) =>
35
53
  state && !(state === STOPPED && !query.result) && !activeLast;
36
54
 
37
55
  return (
38
- <tr key={result}>
39
- <td>
40
- {resultIcons[result]}
41
- {capitalize(result)}
42
- </td>
43
- <td
56
+ <Tr key={result} ouiaId={`stopped-table-row-${result}`}>
57
+ <Td dataLabel={__('Result')}>
58
+ {resultIcons[result]} {capitalize(result)}
59
+ </Td>
60
+ <Td
61
+ dataLabel={__('Total')}
44
62
  className={classNames('data-col', {
45
63
  active: active && mode !== LAST,
46
64
  'not-focused': notFocusedTotal,
47
65
  })}
48
- onClick={() => updateQuery({ state: STOPPED, result })}
49
66
  >
50
- <Button bsStyle="link">{total}</Button>
51
- </td>
52
- <td
67
+ <Button
68
+ ouiaId={`stopped-table-button-${result}-total`}
69
+ variant="link"
70
+ onClick={() => updateQuery({ state: STOPPED, result })}
71
+ >
72
+ {total}
73
+ </Button>
74
+ </Td>
75
+ <Td
76
+ dataLabel={getQueryValueText(time)}
53
77
  className={classNames('data-col', {
54
78
  active: activeLast,
55
79
  'not-focused': notFocusedLast,
56
80
  })}
57
- onClick={() =>
58
- updateQuery({
59
- state: STOPPED,
60
- result,
61
- mode: LAST,
62
- time,
63
- })
64
- }
65
81
  >
66
- <Button bsStyle="link">{last}</Button>
67
- </td>
68
- </tr>
82
+ <Button
83
+ ouiaId={`stopped-table-button-${result}-${time}`}
84
+ onClick={() =>
85
+ updateQuery({
86
+ state: STOPPED,
87
+ result,
88
+ mode: LAST,
89
+ time,
90
+ })
91
+ }
92
+ variant="link"
93
+ >
94
+ {last}
95
+ </Button>
96
+ </Td>
97
+ </Tr>
69
98
  );
70
99
  });
71
100
 
72
101
  export const StoppedTable = ({ data, query, time, updateQuery }) => (
73
- <table className="table table-bordered table-striped stopped-table">
74
- <thead>
75
- <tr>
76
- <th />
77
- <th>{__('Total')}</th>
78
- <th>{getQueryValueText(time)}</th>
79
- </tr>
80
- </thead>
81
- <tbody>{StoppedTableCells(data, query, time, updateQuery)}</tbody>
82
- </table>
102
+ <Table
103
+ ouiaId="stopped-table"
104
+ className="stopped-table"
105
+ variant="compact"
106
+ isStriped
107
+ aria-label={__('Stopped tasks by result')}
108
+ >
109
+ <Thead>
110
+ <Tr ouiaId="stopped-table-header">
111
+ <Th aria-label={__('Result')} />
112
+ <Th aria-label={__('Total')}>{__('Total')}</Th>
113
+ <Th aria-label={getQueryValueText(time)}>{getQueryValueText(time)}</Th>
114
+ </Tr>
115
+ </Thead>
116
+ <Tbody>{StoppedTableCells(data, query, time, updateQuery)}</Tbody>
117
+ </Table>
83
118
  );
84
119
 
85
120
  StoppedTable.propTypes = {
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Row, Button, Label } from 'patternfly-react';
3
+ import { Label, LabelGroup } from '@patternfly/react-core';
4
4
  import { noop } from 'foremanReact/common/helpers';
5
5
  import { translate as __ } from 'foremanReact/common/I18n';
6
6
 
@@ -24,23 +24,18 @@ const TasksLabelsRow = ({ query, updateQuery }) => {
24
24
  const queryEntries = Object.entries(query);
25
25
 
26
26
  return (
27
- <Row className="tasks-labels-row">
28
- <span className="title">{__('Active Filters:')}</span>
27
+ <LabelGroup
28
+ className="tasks-labels-row"
29
+ categoryName={__('Active Filters')}
30
+ isClosable
31
+ onClick={() => updateQuery({})}
32
+ >
29
33
  {queryEntries.map(([key, value]) => (
30
- <Label
31
- bsStyle="info"
32
- key={key}
33
- onRemoveClick={() => onDeleteClick(key)}
34
- >
34
+ <Label color="blue" key={key} onClose={() => onDeleteClick(key)}>
35
35
  {getLabelText(key, value)}
36
36
  </Label>
37
37
  ))}
38
- {queryEntries.length > 0 && (
39
- <Button bsStyle="link" onClick={() => updateQuery({})}>
40
- {__('Clear All Filters')}
41
- </Button>
42
- )}
43
- </Row>
38
+ </LabelGroup>
44
39
  );
45
40
  };
46
41
 
@@ -1,26 +1,6 @@
1
- @import 'foremanReact/common/variables';
2
-
3
1
  .tasks-labels-row {
4
- margin: 0;
5
- padding: 0 10px;
6
- .title {
7
- font-weight: 600;
8
- font-size: 13px;
9
- }
10
- .label {
11
- font-size: 100%;
12
-
13
- margin-left: 5px;
14
- margin-right: 5px;
15
- a {
16
- padding-left: 10px;
17
- }
18
- }
19
- .pficon-close {
20
- color: $color-pf-white;
21
- }
22
- .compound-label-pf {
23
- margin-left: 0;
24
- margin: 10px;
2
+ align-items: center;
3
+ ul {
4
+ margin: 0;
25
5
  }
26
6
  }
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- import { testComponentSnapshotsWithFixtures, shallow } from '@theforeman/test';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
3
4
 
4
5
  import { getQueryKeyText, getQueryValueText } from '../../TasksDashboardHelper';
5
6
  import TasksLabelsRow from './TasksLabelsRow';
@@ -9,46 +10,52 @@ jest.mock('../../TasksDashboardHelper');
9
10
  getQueryKeyText.mockImplementation(val => val);
10
11
  getQueryValueText.mockImplementation(val => val);
11
12
 
12
- const fixtures = {
13
- 'render with minimal props': {},
14
- 'render with props': {
15
- query: { some: 'query' },
16
- updateQuery: jest.fn(),
17
- },
18
- };
19
-
20
13
  describe('TasksLabelsRow', () => {
21
- describe('rendering', () =>
22
- testComponentSnapshotsWithFixtures(TasksLabelsRow, fixtures));
14
+ describe('rendering', () => {
15
+ it('renders nothing when query is empty', () => {
16
+ render(<TasksLabelsRow />);
17
+ expect(screen.queryByText('Active Filters')).not.toBeInTheDocument();
18
+ });
19
+
20
+ it('renders Active Filters category and label when query has entries', () => {
21
+ render(
22
+ <TasksLabelsRow query={{ some: 'query' }} updateQuery={jest.fn()} />
23
+ );
24
+ expect(screen.getByText('Active Filters')).toBeInTheDocument();
25
+ expect(screen.getByText('some = query')).toBeInTheDocument();
26
+ });
27
+ });
23
28
 
24
29
  describe('triggering', () => {
25
- it('should trigger updateQuery when label delete-click', () => {
30
+ it('calls updateQuery with remaining query when a label close button is clicked', () => {
26
31
  const updateQuery = jest.fn();
27
32
  const query = { some: 'query', someOther: 'some-query' };
28
33
 
29
- const component = shallow(
30
- <TasksLabelsRow query={query} updateQuery={updateQuery} />
31
- );
32
- const labels = component.find('Label');
33
-
34
- const firstLabel = labels.first();
35
- const secondLabel = labels.at(1);
34
+ render(<TasksLabelsRow query={query} updateQuery={updateQuery} />);
36
35
 
37
- firstLabel.simulate('removeClick');
36
+ const firstLabelCloseButton = screen.getByRole('button', {
37
+ name: /close some = query/i,
38
+ });
39
+ fireEvent.click(firstLabelCloseButton);
38
40
  expect(updateQuery).toHaveBeenCalledWith({ someOther: 'some-query' });
39
41
 
40
- secondLabel.simulate('removeClick');
42
+ const secondLabelCloseButton = screen.getByRole('button', {
43
+ name: /close someOther = some-query/i,
44
+ });
45
+ fireEvent.click(secondLabelCloseButton);
41
46
  expect(updateQuery).toHaveBeenCalledWith({ some: 'query' });
42
47
  });
43
48
 
44
- it('should trigger updateQuery when -clear-all- click', () => {
49
+ it('calls updateQuery with empty object when close label group button is clicked', () => {
45
50
  const updateQuery = jest.fn();
46
51
  const query = { some: 'query', someOther: 'some-query' };
47
52
 
48
- const component = shallow(
49
- <TasksLabelsRow query={query} updateQuery={updateQuery} />
50
- );
51
- component.find('Button').simulate('click');
53
+ render(<TasksLabelsRow query={query} updateQuery={updateQuery} />);
54
+
55
+ const closeGroupButton = screen.getByRole('button', {
56
+ name: /close label group/i,
57
+ });
58
+ fireEvent.click(closeGroupButton);
52
59
 
53
60
  expect(updateQuery).toHaveBeenCalledWith({});
54
61
  });
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
- import { DropdownButton, MenuItem } from 'patternfly-react';
3
2
  import PropTypes from 'prop-types';
3
+ import { SimpleDropdown } from '@patternfly/react-templates';
4
+ import { EllipsisVIcon } from '@patternfly/react-icons';
4
5
  import { translate as __ } from 'foremanReact/common/I18n';
5
6
 
6
7
  export const ActionSelectButton = ({
@@ -8,35 +9,35 @@ export const ActionSelectButton = ({
8
9
  onResume,
9
10
  onForceCancel,
10
11
  disabled,
11
- }) => (
12
- <DropdownButton
13
- title={__('Select Action')}
14
- disabled={disabled}
15
- id="selcted-action-type"
16
- >
17
- <MenuItem
18
- title={__('Cancel selected tasks')}
19
- onClick={onCancel}
20
- eventKey="1"
21
- >
22
- {__('Cancel Selected')}
23
- </MenuItem>
24
- <MenuItem
25
- title={__('Resume selected tasks')}
26
- onClick={onResume}
27
- eventKey="2"
28
- >
29
- {__('Resume Selected')}
30
- </MenuItem>
31
- <MenuItem
32
- title={__('Force Cancel selected tasks')}
33
- onClick={onForceCancel}
34
- eventKey="3"
35
- >
36
- {__('Force Cancel Selected')}
37
- </MenuItem>
38
- </DropdownButton>
39
- );
12
+ }) => {
13
+ const buttons = [
14
+ {
15
+ content: __('Cancel Selected'),
16
+ onClick: onCancel,
17
+ value: 1,
18
+ },
19
+ {
20
+ content: __('Resume Selected'),
21
+ onClick: onResume,
22
+ value: 2,
23
+ },
24
+ {
25
+ content: __('Force Cancel Selected'),
26
+ onClick: onForceCancel,
27
+ value: 3,
28
+ },
29
+ ];
30
+ return (
31
+ <SimpleDropdown
32
+ isDisabled={disabled}
33
+ ouiaId="tasks-table-action-select-dropdown"
34
+ toggleVariant="plain"
35
+ popperProps={{ position: 'right' }}
36
+ initialItems={buttons}
37
+ toggleContent={<EllipsisVIcon aria-hidden="true" />}
38
+ />
39
+ );
40
+ };
40
41
 
41
42
  ActionSelectButton.propTypes = {
42
43
  disabled: PropTypes.bool,
@@ -1,14 +1,81 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
3
4
  import { ActionSelectButton } from '../ActionSelectButton';
4
5
 
5
- const fixtures = {
6
- 'renders with minimal props': {
7
- onCancel: jest.fn(),
8
- onResume: jest.fn(),
9
- onForceCancel: jest.fn(),
10
- },
11
- };
6
+ const mockOnCancel = jest.fn();
7
+ const mockOnResume = jest.fn();
8
+ const mockOnForceCancel = jest.fn();
9
+
10
+ describe('ActionSelectButton', () => {
11
+ const renderComponent = (props = {}) => {
12
+ const defaultProps = {
13
+ onCancel: mockOnCancel,
14
+ onResume: mockOnResume,
15
+ onForceCancel: mockOnForceCancel,
16
+ disabled: false,
17
+ };
18
+ return render(<ActionSelectButton {...defaultProps} {...props} />);
19
+ };
20
+
21
+ beforeEach(() => {
22
+ jest.clearAllMocks();
23
+ });
24
+
25
+ it('renders with minimal props', () => {
26
+ renderComponent();
27
+
28
+ const toggle = screen.getByRole('button');
29
+ expect(toggle).toBeInTheDocument();
30
+ expect(toggle).not.toBeDisabled();
31
+ });
32
+
33
+ it('renders disabled when disabled prop is true', () => {
34
+ renderComponent({ disabled: true });
35
+
36
+ const toggle = screen.getByRole('button');
37
+ expect(toggle).toBeDisabled();
38
+ });
39
+
40
+ it('opens dropdown and shows action options when toggle is clicked', async () => {
41
+ renderComponent();
42
+ const toggle = screen.getByRole('button');
43
+ fireEvent.click(toggle);
44
+
45
+ await waitFor(() => {
46
+ expect(screen.getByText('Cancel Selected')).toBeInTheDocument();
47
+ expect(screen.getByText('Resume Selected')).toBeInTheDocument();
48
+ expect(screen.getByText('Force Cancel Selected')).toBeInTheDocument();
49
+ });
50
+ });
51
+
52
+ it('calls onCancel when Cancel Selected is clicked', async () => {
53
+ renderComponent();
54
+ fireEvent.click(screen.getByRole('button'));
55
+ await waitFor(() => {
56
+ expect(screen.getByText('Cancel Selected')).toBeInTheDocument();
57
+ });
58
+ fireEvent.click(screen.getByText('Cancel Selected'));
59
+ expect(mockOnCancel).toHaveBeenCalledTimes(1);
60
+ });
61
+
62
+ it('calls onResume when Resume Selected is clicked', async () => {
63
+ renderComponent();
64
+ fireEvent.click(screen.getByRole('button'));
65
+ await waitFor(() => {
66
+ expect(screen.getByText('Resume Selected')).toBeInTheDocument();
67
+ });
68
+ fireEvent.click(screen.getByText('Resume Selected'));
69
+ expect(mockOnResume).toHaveBeenCalledTimes(1);
70
+ });
12
71
 
13
- describe('ActionSelectButton', () =>
14
- testComponentSnapshotsWithFixtures(ActionSelectButton, fixtures));
72
+ it('calls onForceCancel when Force Cancel Selected is clicked', async () => {
73
+ renderComponent();
74
+ fireEvent.click(screen.getByRole('button'));
75
+ await waitFor(() => {
76
+ expect(screen.getByText('Force Cancel Selected')).toBeInTheDocument();
77
+ });
78
+ fireEvent.click(screen.getByText('Force Cancel Selected'));
79
+ expect(mockOnForceCancel).toHaveBeenCalledTimes(1);
80
+ });
81
+ });
@@ -2,11 +2,11 @@ import React, { useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import URI from 'urijs';
4
4
  import { getURIsearch } from 'foremanReact/common/urlHelpers';
5
- import { Spinner, Button, Icon } from 'patternfly-react';
5
+ import { Button, ToolbarItem, Spinner } from '@patternfly/react-core';
6
+ import { RedoIcon } from '@patternfly/react-icons';
6
7
  import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
7
8
  import { translate as __ } from 'foremanReact/common/I18n';
8
9
  import { getURIQuery } from 'foremanReact/common/helpers';
9
- import ExportButton from 'foremanReact/routes/common/PageLayout/components/ExportButton/ExportButton';
10
10
  import { STATUS } from 'foremanReact/constants';
11
11
  import TasksDashboard from '../TasksDashboard';
12
12
  import TasksTable from './TasksTable';
@@ -127,25 +127,44 @@ const TasksTablePage = ({
127
127
  header={createHeader(props.actionName)}
128
128
  breadcrumbOptions={getBreadcrumbs(props.actionName)}
129
129
  toolbarButtons={
130
- <React.Fragment>
131
- <Button onClick={() => props.reloadPage(url, props.parentTaskID)}>
132
- <Icon type="fa" name="refresh" /> {__('Refresh Data')}
133
- </Button>
134
- {props.status === STATUS.PENDING && <Spinner size="md" loading />}
135
- <ExportButton
136
- url={getCSVurl(history.location.pathname, uriQuery)}
137
- title={__('Export All')}
138
- />
139
- <ActionSelectButton
140
- disabled={
141
- !props.permissions.edit ||
142
- !(props.selectedRows.length || props.allRowsSelected)
143
- }
144
- onCancel={() => openModal(CANCEL_SELECTED_MODAL)}
145
- onResume={() => openModal(RESUME_SELECTED_MODAL)}
146
- onForceCancel={() => openModal(FORCE_UNLOCK_SELECTED_MODAL)}
147
- />
148
- </React.Fragment>
130
+ <>
131
+ <ToolbarItem>
132
+ <Button
133
+ ouiaId="tasks-table-refresh-data"
134
+ variant="primary"
135
+ onClick={() => props.reloadPage(url, props.parentTaskID)}
136
+ icon={<RedoIcon />}
137
+ >
138
+ {__('Refresh Data')}
139
+ </Button>
140
+ </ToolbarItem>
141
+ {props.status === STATUS.PENDING && (
142
+ <ToolbarItem>
143
+ <Spinner size="lg" />
144
+ </ToolbarItem>
145
+ )}
146
+ <ToolbarItem>
147
+ <Button
148
+ ouiaId="tasks-table-export-all"
149
+ variant="secondary"
150
+ component="a"
151
+ href={getCSVurl(history.location.pathname, uriQuery)}
152
+ >
153
+ {__('Export All')}
154
+ </Button>
155
+ </ToolbarItem>
156
+ <ToolbarItem>
157
+ <ActionSelectButton
158
+ disabled={
159
+ !props.permissions.edit ||
160
+ !(props.selectedRows.length || props.allRowsSelected)
161
+ }
162
+ onCancel={() => openModal(CANCEL_SELECTED_MODAL)}
163
+ onResume={() => openModal(RESUME_SELECTED_MODAL)}
164
+ onForceCancel={() => openModal(FORCE_UNLOCK_SELECTED_MODAL)}
165
+ />
166
+ </ToolbarItem>
167
+ </>
149
168
  }
150
169
  searchQuery={getURIsearch()}
151
170
  beforeToolbarComponent={