foreman-tasks 5.0.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/foreman_tasks/api/tasks_controller.rb +4 -4
  3. data/app/controllers/foreman_tasks/tasks_controller.rb +5 -4
  4. data/app/graphql/types/recurring_logic.rb +1 -0
  5. data/app/helpers/foreman_tasks/foreman_tasks_helper.rb +4 -1
  6. data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +2 -6
  7. data/app/lib/actions/trigger_proxy_batch.rb +79 -0
  8. data/app/models/foreman_tasks/recurring_logic.rb +8 -0
  9. data/app/models/foreman_tasks/task/dynflow_task.rb +8 -3
  10. data/app/models/foreman_tasks/task.rb +1 -0
  11. data/app/models/foreman_tasks/triggering.rb +12 -4
  12. data/app/views/foreman_tasks/api/tasks/show.json.rabl +1 -1
  13. data/app/views/foreman_tasks/layouts/react.html.erb +0 -1
  14. data/app/views/foreman_tasks/recurring_logics/index.html.erb +4 -2
  15. data/app/views/foreman_tasks/task_groups/recurring_logic_task_groups/_recurring_logic_task_group.html.erb +4 -0
  16. data/db/migrate/20210720115251_add_purpose_to_recurring_logic.rb +6 -0
  17. data/extra/foreman-tasks-cleanup.sh +127 -0
  18. data/extra/foreman-tasks-export.sh +117 -0
  19. data/lib/foreman_tasks/tasks/export_tasks.rake +92 -47
  20. data/lib/foreman_tasks/version.rb +1 -1
  21. data/test/controllers/api/tasks_controller_test.rb +29 -0
  22. data/test/controllers/tasks_controller_test.rb +19 -0
  23. data/test/unit/actions/trigger_proxy_batch_test.rb +59 -0
  24. data/test/unit/triggering_test.rb +22 -0
  25. data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.js +11 -4
  26. data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.test.js +27 -5
  27. data/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +8 -0
  28. data/webpack/ForemanTasks/Components/TasksTable/TasksTableActions.js +6 -1
  29. data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +2 -1
  30. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +22 -11
  31. data/webpack/ForemanTasks/Components/TasksTable/TasksTableReducer.js +17 -16
  32. data/webpack/ForemanTasks/Components/TasksTable/TasksTableSelectors.js +3 -0
  33. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableHelpers.test.js +1 -1
  34. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableReducer.test.js +3 -1
  35. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +12 -2
  36. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableReducer.test.js.snap +5 -0
  37. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/selectionHeaderCellFormatter.test.js.snap +1 -0
  38. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/selectionHeaderCellFormatter.test.js +1 -1
  39. data/webpack/ForemanTasks/Components/TasksTable/formatters/selectionHeaderCellFormatter.js +1 -0
  40. data/webpack/ForemanTasks/Components/TasksTable/index.js +2 -0
  41. metadata +8 -5
  42. data/app/services/foreman_tasks/dashboard_table_filter.rb +0 -56
  43. data/test/unit/dashboard_table_filter_test.rb +0 -77
@@ -15,22 +15,29 @@ export const convertDashboardQuery = query => {
15
15
  state,
16
16
  result,
17
17
  search,
18
+ ...rest
18
19
  } = query;
19
20
 
20
21
  const hours = timeToHoursNumber(timeHorizon);
21
22
  const timestamp = new Date(new Date() - hours * 60 * 60 * 1000);
22
23
  let dashboardTime = '';
23
24
  const stateQuery = state ? `state=${state}` : '';
24
- const resultQuery = result ? `result=${result}` : '';
25
+ let resultQuery = '';
26
+ if (result === 'other') {
27
+ resultQuery = 'result ^ (pending, cancelled)';
28
+ } else {
29
+ resultQuery = result ? `result=${result}` : '';
30
+ }
25
31
  if (timeMode === TASKS_DASHBOARD_JS_QUERY_MODES.RECENT) {
26
- dashboardTime = `(state_updated_at>${timestamp.toISOString()} or state_updated_at = NULL)`;
32
+ dashboardTime = `state_updated_at>${timestamp.toISOString()} or null? state_updated_at`;
27
33
  } else if (timeMode === TASKS_DASHBOARD_JS_QUERY_MODES.OLDER) {
28
- dashboardTime = `(state_updated_at>${timestamp.toISOString()})`;
34
+ dashboardTime = `state_updated_at<=${timestamp.toISOString()}`;
29
35
  }
30
36
  const newQuery = [stateQuery, resultQuery, search, dashboardTime]
31
37
  .filter(Boolean)
38
+ .map(q => `(${q})`)
32
39
  .join(' and ');
33
- return newQuery;
40
+ return newQuery ? { search: newQuery, ...rest } : rest;
34
41
  };
35
42
 
36
43
  export const resumeToastInfo = {
@@ -27,9 +27,18 @@ describe('convertDashboardQuery', () => {
27
27
  result: 'error',
28
28
  search: 'action~job',
29
29
  };
30
- expect(convertDashboardQuery(query)).toEqual(
31
- 'state=stopped and result=error and action~job and (state_updated_at>2020-05-01T11:01:58.135Z or state_updated_at = NULL)'
32
- );
30
+ const expected =
31
+ '(state=stopped) and (result=error) and (action~job) and (state_updated_at>2020-05-01T11:01:58.135Z or null? state_updated_at)';
32
+
33
+ expect(convertDashboardQuery(query)).toEqual({ search: expected });
34
+
35
+ const query2 = {
36
+ ...query,
37
+ time_mode: TASKS_DASHBOARD_JS_QUERY_MODES.OLDER,
38
+ };
39
+ const expected2 =
40
+ '(state=stopped) and (result=error) and (action~job) and (state_updated_at<=2020-05-01T11:01:58.135Z)';
41
+ expect(convertDashboardQuery(query2)).toEqual({ search: expected2 });
33
42
  // Cleanup
34
43
  global.Date = realDate;
35
44
  });
@@ -37,10 +46,23 @@ describe('convertDashboardQuery', () => {
37
46
  const query = {
38
47
  search: 'action~job',
39
48
  };
40
- expect(convertDashboardQuery(query)).toEqual('action~job');
49
+ expect(convertDashboardQuery(query)).toEqual({ search: '(action~job)' });
41
50
  });
42
51
  it('convertDashboardQuery should work with no query', () => {
43
52
  const query = {};
44
- expect(convertDashboardQuery(query)).toEqual('');
53
+ expect(convertDashboardQuery(query)).toEqual({});
54
+ });
55
+ it('convertDashboardQuery should not override unknown keys', () => {
56
+ const query = { weather: 'nice', search: 'okay', number: 7 };
57
+ expect(convertDashboardQuery(query)).toEqual({
58
+ ...query,
59
+ search: '(okay)',
60
+ });
61
+ });
62
+ it('convertDashboardQuery should expand other result', () => {
63
+ const query = { result: 'other' };
64
+ expect(convertDashboardQuery(query)).toEqual({
65
+ search: '(result ^ (pending, cancelled))',
66
+ });
45
67
  });
46
68
  });
@@ -27,6 +27,7 @@ const TasksTable = ({
27
27
  openClickedModal,
28
28
  openModal,
29
29
  allRowsSelected,
30
+ permissions,
30
31
  }) => {
31
32
  const { search, pathname } = history.location;
32
33
  const url = pathname + search;
@@ -59,6 +60,7 @@ const TasksTable = ({
59
60
  },
60
61
  isSelected: ({ rowData }) =>
61
62
  allRowsSelected || selectedRows.includes(rowData.id),
63
+ permissions,
62
64
  };
63
65
  };
64
66
 
@@ -162,6 +164,9 @@ TasksTable.propTypes = {
162
164
  unselectRow: PropTypes.func.isRequired,
163
165
  openModal: PropTypes.func.isRequired,
164
166
  allRowsSelected: PropTypes.bool,
167
+ permissions: PropTypes.shape({
168
+ edit: PropTypes.bool,
169
+ }),
165
170
  };
166
171
 
167
172
  TasksTable.defaultProps = {
@@ -173,6 +178,9 @@ TasksTable.defaultProps = {
173
178
  },
174
179
  selectedRows: [],
175
180
  allRowsSelected: false,
181
+ permissions: {
182
+ edit: false,
183
+ },
176
184
  };
177
185
 
178
186
  export default TasksTable;
@@ -18,9 +18,14 @@ import {
18
18
  resumeTaskRequest,
19
19
  forceCancelTaskRequest,
20
20
  } from '../TaskActions';
21
+ import { convertDashboardQuery } from '../TaskActions/TaskActionHelpers';
21
22
 
22
23
  export const getTableItems = url =>
23
- getTableItemsAction(TASKS_TABLE_ID, getURIQuery(url), getApiPathname(url));
24
+ getTableItemsAction(
25
+ TASKS_TABLE_ID,
26
+ convertDashboardQuery(getURIQuery(url)),
27
+ getApiPathname(url)
28
+ );
24
29
 
25
30
  export const reloadPage = (url, parentTaskID) => dispatch => {
26
31
  dispatch(getTableItems(url));
@@ -2,6 +2,7 @@ import URI from 'urijs';
2
2
  import { translate as __, documentLocale } from 'foremanReact/common/I18n';
3
3
  import humanizeDuration from 'humanize-duration';
4
4
  import { isoCompatibleDate } from 'foremanReact/common/helpers';
5
+ import { convertDashboardQuery } from '../TaskActions/TaskActionHelpers';
5
6
 
6
7
  export const updateURlQuery = (query, history) => {
7
8
  const uri = new URI(history.location.pathname + history.location.search);
@@ -17,7 +18,7 @@ export const getApiPathname = url => {
17
18
  export const getCSVurl = (path, query) => {
18
19
  let url = new URI(path);
19
20
  url = url.pathname(`${url.pathname()}.csv`);
20
- url.addSearch(query);
21
+ url.addSearch(convertDashboardQuery(query));
21
22
  return url.toString();
22
23
  };
23
24
 
@@ -77,11 +77,14 @@ const TasksTablePage = ({
77
77
  </Button>
78
78
  {props.status === STATUS.PENDING && <Spinner size="md" loading />}
79
79
  <ExportButton
80
- url={getCSVurl(url, uriQuery)}
80
+ url={getCSVurl(history.location.pathname, uriQuery)}
81
81
  title={__('Export All')}
82
82
  />
83
83
  <ActionSelectButton
84
- disabled={!(props.selectedRows.length || props.allRowsSelected)}
84
+ disabled={
85
+ !props.permissions.edit ||
86
+ !(props.selectedRows.length || props.allRowsSelected)
87
+ }
85
88
  onCancel={() => openModal(CANCEL_SELECTED_MODAL)}
86
89
  onResume={() => openModal(RESUME_SELECTED_MODAL)}
87
90
  onForceCancel={() => openModal(FORCE_UNLOCK_SELECTED_MODAL)}
@@ -94,15 +97,17 @@ const TasksTablePage = ({
94
97
  }
95
98
  >
96
99
  <React.Fragment>
97
- {showSelectAll && props.itemCount >= props.pagination.perPage && (
98
- <SelectAllAlert
99
- itemCount={props.itemCount}
100
- perPage={props.pagination.perPage}
101
- selectAllRows={selectAllRows}
102
- unselectAllRows={props.unselectAllRows}
103
- allRowsSelected={props.allRowsSelected}
104
- />
105
- )}
100
+ {props.permissions.edit &&
101
+ showSelectAll &&
102
+ props.itemCount >= props.pagination.perPage && (
103
+ <SelectAllAlert
104
+ itemCount={props.itemCount}
105
+ perPage={props.pagination.perPage}
106
+ selectAllRows={selectAllRows}
107
+ unselectAllRows={props.unselectAllRows}
108
+ allRowsSelected={props.allRowsSelected}
109
+ />
110
+ )}
106
111
  <TasksTable history={history} {...props} openModal={openModal} />
107
112
  </React.Fragment>
108
113
  </PageLayout>
@@ -131,6 +136,9 @@ TasksTablePage.propTypes = {
131
136
  showSelectAll: PropTypes.bool,
132
137
  unselectAllRows: PropTypes.func.isRequired,
133
138
  reloadPage: PropTypes.func.isRequired,
139
+ permissions: PropTypes.shape({
140
+ edit: PropTypes.bool,
141
+ }),
134
142
  };
135
143
 
136
144
  TasksTablePage.defaultProps = {
@@ -146,6 +154,9 @@ TasksTablePage.defaultProps = {
146
154
  createHeader: () => __('Tasks'),
147
155
  showSelectAll: false,
148
156
  modalID: '',
157
+ permissions: {
158
+ edit: false,
159
+ },
149
160
  };
150
161
 
151
162
  export default TasksTablePage;
@@ -19,8 +19,13 @@ const initialState = Immutable({
19
19
 
20
20
  export const TasksTableQueryReducer = (state = initialState, action) => {
21
21
  const { type, payload, response } = action;
22
- const { subtotal, page, per_page: perPageString, action_name: actionName } =
23
- response || {};
22
+ const {
23
+ subtotal,
24
+ page,
25
+ per_page: perPageString,
26
+ action_name: actionName,
27
+ can_edit: canEdit,
28
+ } = response || {};
24
29
  const ACTION_TYPES = createTableActionTypes(TASKS_TABLE_ID);
25
30
  switch (type) {
26
31
  case SELECT_ALL_ROWS:
@@ -34,26 +39,22 @@ export const TasksTableQueryReducer = (state = initialState, action) => {
34
39
  perPage: Number(perPageString),
35
40
  },
36
41
  selectedRows: [],
42
+ permissions: {
43
+ edit: canEdit,
44
+ },
37
45
  });
38
46
  case SELECT_ROWS:
39
47
  return state.set('selectedRows', union(payload, state.selectedRows));
40
48
  case OPEN_SELECT_ALL:
41
49
  return state.set('showSelectAll', true);
42
50
  case UNSELECT_ROWS:
43
- if (state.allRowsSelected) {
44
- // User can unselect rows if only the page rows are selected
45
- return state
46
- .set(
47
- 'selectedRows',
48
- payload.results.map(row => row.id).filter(row => row !== payload.id)
49
- )
50
- .set('allRowsSelected', false)
51
- .set('showSelectAll', false);
52
- }
53
- return state.set(
54
- 'selectedRows',
55
- state.selectedRows.filter(row => row !== payload.id)
56
- );
51
+ return state
52
+ .set(
53
+ 'selectedRows',
54
+ state.selectedRows.filter(row => row !== payload.id)
55
+ )
56
+ .set('showSelectAll', false)
57
+ .set('allRowsSelected', false);
57
58
  case UNSELECT_ALL_ROWS:
58
59
  return state
59
60
  .set('selectedRows', [])
@@ -24,6 +24,9 @@ export const selectActionName = state =>
24
24
  export const selectSelectedRows = state =>
25
25
  selectTasksTableQuery(state).selectedRows || [];
26
26
 
27
+ export const selectPermissions = state =>
28
+ selectTasksTableQuery(state).permissions || { edit: false };
29
+
27
30
  export const selectResults = createSelector(
28
31
  selectTasksTableContent,
29
32
  ({ results }) =>
@@ -32,7 +32,7 @@ describe('getCSVurl', () => {
32
32
  const url = '/foreman_tasks/tasks';
33
33
  const query = { state: 'stopped' };
34
34
  expect(getCSVurl(url, query)).toEqual(
35
- '/foreman_tasks/tasks.csv?state=stopped'
35
+ '/foreman_tasks/tasks.csv?search=%28state%3Dstopped%29'
36
36
  );
37
37
  });
38
38
  it('should return currect url for subtasks', () => {
@@ -73,7 +73,9 @@ const fixtures = {
73
73
  },
74
74
  },
75
75
  'should handle UNSELECT_ROWS with all rows selected': {
76
- state: Immutable({ tasksTableQuery: { allRowsSelected: true } }),
76
+ state: Immutable({
77
+ tasksTableQuery: { allRowsSelected: true, selectedRows: [3, 4, 5] },
78
+ }),
77
79
  action: {
78
80
  type: UNSELECT_ROWS,
79
81
  payload: { id: [4], results: [{ id: 3 }, { id: 4 }, { id: 5 }] },
@@ -92,7 +92,7 @@ exports[`TasksTablePage rendering render with Breadcrubs and edit permissions 1`
92
92
  />
93
93
  <ExportButton
94
94
  title="Export All"
95
- url="/foreman_tasks/tasks.csv?action=%22some-name%22&state=stopped"
95
+ url="/foreman_tasks/tasks.csv?search=%28state%3Dstopped%29"
96
96
  />
97
97
  <ActionSelectButton
98
98
  disabled={true}
@@ -126,6 +126,11 @@ exports[`TasksTablePage rendering render with Breadcrubs and edit permissions 1`
126
126
  }
127
127
  }
128
128
  parentTaskID={null}
129
+ permissions={
130
+ Object {
131
+ "edit": false,
132
+ }
133
+ }
129
134
  reloadPage={[MockFunction]}
130
135
  results={
131
136
  Array [
@@ -227,7 +232,7 @@ exports[`TasksTablePage rendering render with minimal props 1`] = `
227
232
  />
228
233
  <ExportButton
229
234
  title="Export All"
230
- url="/foreman_tasks/tasks.csv?action=%22some-name%22&state=stopped"
235
+ url="/foreman_tasks/tasks.csv?search=%28state%3Dstopped%29"
231
236
  />
232
237
  <ActionSelectButton
233
238
  disabled={true}
@@ -261,6 +266,11 @@ exports[`TasksTablePage rendering render with minimal props 1`] = `
261
266
  }
262
267
  }
263
268
  parentTaskID={null}
269
+ permissions={
270
+ Object {
271
+ "edit": false,
272
+ }
273
+ }
264
274
  reloadPage={[MockFunction]}
265
275
  results={
266
276
  Array [
@@ -59,6 +59,9 @@ Object {
59
59
  "page": 3,
60
60
  "perPage": 12,
61
61
  },
62
+ "permissions": Object {
63
+ "edit": undefined,
64
+ },
62
65
  "selectedRows": Array [],
63
66
  },
64
67
  }
@@ -77,7 +80,9 @@ Object {
77
80
  exports[`TasksTableReducer reducer should handle UNSELECT_ROWS 1`] = `
78
81
  Object {
79
82
  "tasksTableQuery": Object {
83
+ "allRowsSelected": false,
80
84
  "selectedRows": Array [],
85
+ "showSelectAll": false,
81
86
  },
82
87
  }
83
88
  `;
@@ -3,6 +3,7 @@
3
3
  exports[`selectionHeaderCellFormatter render 1`] = `
4
4
  <TableSelectionHeaderCell
5
5
  checked={true}
6
+ disabled={false}
6
7
  id="selectAll"
7
8
  label="some-label"
8
9
  onChange={[Function]}
@@ -4,7 +4,7 @@ describe('selectionHeaderCellFormatter', () => {
4
4
  it('render', () => {
5
5
  expect(
6
6
  selectionHeaderCellFormatter(
7
- { allPageSelected: () => true },
7
+ { allPageSelected: () => true, permissions: { edit: true } },
8
8
  'some-label'
9
9
  )
10
10
  ).toMatchSnapshot();
@@ -5,6 +5,7 @@ export default (selectionController, label) => (
5
5
  <TableSelectionHeaderCell
6
6
  label={label}
7
7
  checked={selectionController.allPageSelected()}
8
+ disabled={!selectionController.permissions.edit}
8
9
  onChange={selectionController.selectPage}
9
10
  />
10
11
  );
@@ -16,6 +16,7 @@ import {
16
16
  selectAllRowsSelected,
17
17
  selectShowSelectAll,
18
18
  selectModalID,
19
+ selectPermissions,
19
20
  } from './TasksTableSelectors';
20
21
 
21
22
  const mapStateToProps = state => ({
@@ -30,6 +31,7 @@ const mapStateToProps = state => ({
30
31
  allRowsSelected: selectAllRowsSelected(state),
31
32
  showSelectAll: selectShowSelectAll(state),
32
33
  modalID: selectModalID(state),
34
+ permissions: selectPermissions(state),
33
35
  });
34
36
 
35
37
  const mapDispatchToProps = dispatch =>
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: 5.0.0
4
+ version: 5.1.0
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: 2021-06-15 00:00:00.000000000 Z
11
+ date: 2021-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dynflow
@@ -165,6 +165,7 @@ files:
165
165
  - app/lib/actions/proxy_action.rb
166
166
  - app/lib/actions/recurring_action.rb
167
167
  - app/lib/actions/serializers/active_record_serializer.rb
168
+ - app/lib/actions/trigger_proxy_batch.rb
168
169
  - app/lib/foreman_tasks/concerns/polling_action_extensions.rb
169
170
  - app/lib/proxy_api/foreman_dynflow/dynflow_proxy.rb
170
171
  - app/models/foreman_tasks/concerns/action_subject.rb
@@ -187,7 +188,6 @@ files:
187
188
  - app/models/foreman_tasks/task_groups/recurring_logic_task_group.rb
188
189
  - app/models/foreman_tasks/triggering.rb
189
190
  - app/models/setting/foreman_tasks.rb
190
- - app/services/foreman_tasks/dashboard_table_filter.rb
191
191
  - app/services/foreman_tasks/proxy_selector.rb
192
192
  - app/services/foreman_tasks/troubleshooting_help_generator.rb
193
193
  - app/services/ui_notifications/tasks.rb
@@ -249,6 +249,7 @@ files:
249
249
  - db/migrate/20200517215015_rename_bookmarks_controller.rb
250
250
  - db/migrate/20200519093217_drop_dynflow_allow_dangerous_actions_setting.foreman_tasks.rb
251
251
  - db/migrate/20200611090846_add_task_lock_index_on_resource_type_and_task_id.rb
252
+ - db/migrate/20210720115251_add_purpose_to_recurring_logic.rb
252
253
  - db/seeds.d/20-foreman_tasks_permissions.rb
253
254
  - db/seeds.d/30-notification_blueprints.rb
254
255
  - db/seeds.d/60-dynflow_proxy_feature.rb
@@ -257,6 +258,8 @@ files:
257
258
  - deploy/foreman-tasks.sysconfig
258
259
  - extra/dynflow-debug.sh
259
260
  - extra/dynflow-executor.example
261
+ - extra/foreman-tasks-cleanup.sh
262
+ - extra/foreman-tasks-export.sh
260
263
  - foreman-tasks.gemspec
261
264
  - gemfile.d/foreman-tasks.rb
262
265
  - lib/foreman-tasks.rb
@@ -323,9 +326,9 @@ files:
323
326
  - test/unit/actions/bulk_action_test.rb
324
327
  - test/unit/actions/proxy_action_test.rb
325
328
  - test/unit/actions/recurring_action_test.rb
329
+ - test/unit/actions/trigger_proxy_batch_test.rb
326
330
  - test/unit/cleaner_test.rb
327
331
  - test/unit/config/environment.rb
328
- - test/unit/dashboard_table_filter_test.rb
329
332
  - test/unit/dynflow_console_authorizer_test.rb
330
333
  - test/unit/locking_test.rb
331
334
  - test/unit/proxy_selector_test.rb
@@ -637,9 +640,9 @@ test_files:
637
640
  - test/unit/actions/bulk_action_test.rb
638
641
  - test/unit/actions/proxy_action_test.rb
639
642
  - test/unit/actions/recurring_action_test.rb
643
+ - test/unit/actions/trigger_proxy_batch_test.rb
640
644
  - test/unit/cleaner_test.rb
641
645
  - test/unit/config/environment.rb
642
- - test/unit/dashboard_table_filter_test.rb
643
646
  - test/unit/dynflow_console_authorizer_test.rb
644
647
  - test/unit/locking_test.rb
645
648
  - test/unit/proxy_selector_test.rb