foreman_host_reports 0.0.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -447
  3. data/app/controllers/api/v2/host_reports_controller.rb +39 -20
  4. data/app/controllers/concerns/foreman_host_reports/controller/hosts_controller_extensions.rb +18 -0
  5. data/app/controllers/concerns/foreman_host_reports/controller/parameters/host_report.rb +1 -1
  6. data/app/controllers/host_reports_controller.rb +32 -2
  7. data/app/helpers/concerns/foreman_host_reports/hosts_helper_extensions.rb +25 -0
  8. data/app/models/concerns/foreman_host_reports/host_extensions.rb +6 -0
  9. data/app/models/host_report.rb +15 -0
  10. data/app/models/host_status/host_report_status.rb +185 -0
  11. data/app/views/api/v2/host_reports/main.json.rabl +1 -2
  12. data/config/routes.rb +2 -4
  13. data/db/migrate/20220113064436_rename_status_summaries.rb +12 -0
  14. data/lib/foreman_host_reports/engine.rb +11 -5
  15. data/lib/foreman_host_reports/version.rb +1 -1
  16. data/test/controllers/api/v2/host_reports_controller_test.rb +30 -67
  17. data/test/factories/foreman_host_reports_factories.rb +13 -5
  18. data/test/model/host_report_status_test.rb +204 -0
  19. data/test/test_plugin_helper.rb +4 -2
  20. data/webpack/__mocks__/foremanReact/components/Pagination/index.js +2 -0
  21. data/webpack/fills.js +23 -0
  22. data/webpack/global_index.js +2 -0
  23. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters/statusFormatter.js +3 -4
  24. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/Components/StatusCell.js +1 -1
  25. data/webpack/src/Router/HostReports/IndexPage/Components/HostReportsTable/HostReportsTable.js +2 -10
  26. data/webpack/src/Router/HostReports/IndexPage/IndexPage.js +0 -1
  27. data/webpack/src/Router/HostReports/IndexPage/IndexPageActions.js +5 -4
  28. data/webpack/src/Router/HostReports/IndexPage/IndexPageHelpers.js +1 -1
  29. data/webpack/src/Router/HostReports/IndexPage/__tests__/HostReportsIndexPage.test.js +3 -4
  30. data/webpack/src/Router/HostReports/IndexPage/__tests__/__snapshots__/HostReportsIndexPage.test.js.snap +4 -6
  31. data/webpack/src/Router/HostReports/IndexPage/constants.js +4 -3
  32. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Ansible.js +62 -27
  33. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Components/EmptyLogsRow.js +27 -0
  34. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Components/RawMsgModal.js +41 -0
  35. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Puppet.js +38 -37
  36. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/index.js +10 -3
  37. data/webpack/src/Router/HostReports/ShowPage/Components/ReportLogsFilter/index.js +56 -65
  38. data/webpack/src/Router/HostReports/ShowPage/ShowPage.js +34 -8
  39. data/webpack/src/Router/HostReports/constants.js +2 -0
  40. data/webpack/src/components/ReportsTab/ReportsTable.js +117 -0
  41. data/webpack/src/components/ReportsTab/helpers.js +155 -0
  42. data/webpack/src/components/ReportsTab/index.js +132 -0
  43. metadata +18 -19
  44. data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +0 -4
@@ -0,0 +1,117 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ TableComposable,
5
+ Thead,
6
+ Tbody,
7
+ Tr,
8
+ Th,
9
+ Td,
10
+ } from '@patternfly/react-table';
11
+ import {
12
+ EmptyState,
13
+ EmptyStateIcon,
14
+ Spinner,
15
+ Title,
16
+ Grid,
17
+ } from '@patternfly/react-core';
18
+ import { SearchIcon, ExclamationCircleIcon } from '@patternfly/react-icons';
19
+ import { STATUS } from 'foremanReact/constants';
20
+ import { translate as __ } from 'foremanReact/common/I18n';
21
+ import { getColumns } from './helpers';
22
+
23
+ const ReportsTable = ({ reports, status, fetchReports }) => {
24
+ const columns = getColumns(fetchReports);
25
+ let tableBody = null;
26
+ let tableHead = null;
27
+ let emptyState = null;
28
+
29
+ if (status === STATUS.RESOLVED) {
30
+ if (reports.length) {
31
+ tableHead = (
32
+ <Thead>
33
+ <Tr>
34
+ {columns.map(({ width, title }, columnIndex) => (
35
+ <Th key={columnIndex} width={width}>
36
+ {title}
37
+ </Th>
38
+ ))}
39
+ </Tr>
40
+ </Thead>
41
+ );
42
+ tableBody = (
43
+ <Tbody>
44
+ {reports.map((row, rowIndex) => (
45
+ <Tr key={rowIndex}>
46
+ {columns.map(({ title, formatter }, cellIndex) => (
47
+ <Td key={`${rowIndex}_${cellIndex}`} dataLabel={title}>
48
+ {formatter(row)}
49
+ </Td>
50
+ ))}
51
+ </Tr>
52
+ ))}
53
+ </Tbody>
54
+ );
55
+ } else {
56
+ emptyState = (
57
+ <Grid>
58
+ <EmptyState>
59
+ <EmptyStateIcon icon={SearchIcon} />
60
+ <Title size="lg" headingLevel="h4">
61
+ {__('No results found')}
62
+ </Title>
63
+ </EmptyState>
64
+ </Grid>
65
+ );
66
+ }
67
+ } else if (status === STATUS.FAILURE) {
68
+ emptyState = (
69
+ <Grid>
70
+ <EmptyState>
71
+ <EmptyStateIcon
72
+ icon={
73
+ <ExclamationCircleIcon color="var(--pf-global--palette--red-200)" />
74
+ }
75
+ />
76
+ <Title size="lg" headingLevel="h4">
77
+ {__('Something went wrong')}
78
+ </Title>
79
+ </EmptyState>
80
+ </Grid>
81
+ );
82
+ } else if (status === STATUS.PENDING) {
83
+ emptyState = (
84
+ <Grid>
85
+ <EmptyState>
86
+ <EmptyStateIcon variant="container" component={Spinner} />
87
+ <Title size="lg" headingLevel="h4">
88
+ {__('Loading')}
89
+ </Title>
90
+ </EmptyState>
91
+ </Grid>
92
+ );
93
+ }
94
+
95
+ return (
96
+ <React.Fragment>
97
+ <TableComposable aria-label="Reports table" variant="compact">
98
+ {tableHead}
99
+ {tableBody}
100
+ </TableComposable>
101
+ {emptyState}
102
+ </React.Fragment>
103
+ );
104
+ };
105
+
106
+ ReportsTable.propTypes = {
107
+ reports: PropTypes.array,
108
+ status: PropTypes.string,
109
+ fetchReports: PropTypes.func.isRequired,
110
+ };
111
+
112
+ ReportsTable.defaultProps = {
113
+ reports: [],
114
+ status: null,
115
+ };
116
+
117
+ export default ReportsTable;
@@ -0,0 +1,155 @@
1
+ /* eslint-disable camelcase */
2
+ /* eslint-disable react/prop-types */
3
+ import React, { useState } from 'react';
4
+ import { useDispatch } from 'react-redux';
5
+ import {
6
+ Dropdown,
7
+ DropdownItem,
8
+ KebabToggle,
9
+ FlexItem,
10
+ Flex,
11
+ Tooltip,
12
+ } from '@patternfly/react-core';
13
+ import {
14
+ ExclamationCircleIcon,
15
+ SyncAltIcon,
16
+ CheckCircleIcon,
17
+ BanIcon,
18
+ TagIcon,
19
+ } from '@patternfly/react-icons';
20
+ import { openConfirmModal } from 'foremanReact/components/ConfirmModal';
21
+ import { APIActions } from 'foremanReact/redux/API';
22
+ import { translate as __ } from 'foremanReact/common/I18n';
23
+ import { reportToShowFormatter } from '../../Router/HostReports/IndexPage/Components/HostReportsTable/Components/Formatters';
24
+
25
+ const FailedIcon = ({ label, withMargin = false }) => (
26
+ <span style={withMargin ? { marginRight: '15px' } : {}}>
27
+ <ExclamationCircleIcon color="var(--pf-global--palette--red-100)" /> {label}
28
+ </span>
29
+ );
30
+
31
+ const ChangedIcon = ({ label, withMargin = false }) => (
32
+ <span style={withMargin ? { marginRight: '15px' } : {}}>
33
+ <SyncAltIcon color="var(--pf-global--palette--orange-300)" /> {label}
34
+ </span>
35
+ );
36
+
37
+ const NochangeIcon = ({ label }) => (
38
+ <>
39
+ <CheckCircleIcon color="var(--pf-global--success-color--100)" /> {label}
40
+ </>
41
+ );
42
+
43
+ export const statusSummaryFormatter = ({ change, nochange, failure }) => {
44
+ const summary = [];
45
+ if (failure)
46
+ summary.push(<FailedIcon key="failed" label={failure} withMargin />);
47
+ if (change)
48
+ summary.push(<ChangedIcon key="changed" label={change} withMargin />);
49
+ if (nochange) summary.push(<NochangeIcon key="nochange" label={nochange} />);
50
+ return summary.length ? summary : '--';
51
+ };
52
+
53
+ export const globalStatusFormatter = ({ status }) => {
54
+ switch (status) {
55
+ case 'failure':
56
+ return <FailedIcon label={__('Fail')} />;
57
+ case 'change':
58
+ return <ChangedIcon label={__('Changed')} />;
59
+ case 'nochange':
60
+ return <NochangeIcon label={__('Unchanged')} />;
61
+ default:
62
+ return (
63
+ <>
64
+ <BanIcon /> {__('Empty')}
65
+ </>
66
+ );
67
+ }
68
+ };
69
+
70
+ export const keywordsFormatter = ({ keywords = [] }) => (
71
+ <>
72
+ <Tooltip content={<div>{keywords.join(',\n')}</div>}>
73
+ <TagIcon color={keywords.length ? '#6a6e73' : '#d2d2d2'} />{' '}
74
+ {keywords.length}
75
+ </Tooltip>
76
+ </>
77
+ );
78
+
79
+ export const ActionFormatter = ({ id, can_delete }, fetchReports) => {
80
+ const [isOpen, setOpen] = useState(false);
81
+ const dispatch = useDispatch();
82
+ const dispatchConfirm = () => {
83
+ dispatch(
84
+ openConfirmModal({
85
+ isWarning: true,
86
+ title: __('Delete report?'),
87
+ confirmButtonText: __('Delete report'),
88
+ onConfirm: () =>
89
+ dispatch(
90
+ APIActions.delete({
91
+ url: `/api/v2/host_reports/${id}`,
92
+ key: `report-${id}-DELETE`,
93
+ successToast: success => __('Report was successfully deleted'),
94
+ errorToast: error =>
95
+ __(`There was some issue deleting the report: ${error}`),
96
+ handleSuccess: fetchReports,
97
+ })
98
+ ),
99
+ message: __(
100
+ 'Are you sure you want to delete this report? This action is irreversible.'
101
+ ),
102
+ })
103
+ );
104
+ };
105
+ const dropdownItems = [
106
+ <DropdownItem
107
+ key="action"
108
+ component="button"
109
+ onClick={dispatchConfirm}
110
+ disabled={!can_delete}
111
+ >
112
+ {__('Delete')}
113
+ </DropdownItem>,
114
+ ];
115
+ return (
116
+ <Flex>
117
+ <FlexItem align={{ default: 'alignRight' }}>
118
+ <Dropdown
119
+ onSelect={v => setOpen(!v)}
120
+ toggle={<KebabToggle onToggle={setOpen} id="toggle-action" />}
121
+ isOpen={isOpen}
122
+ isPlain
123
+ dropdownItems={dropdownItems}
124
+ />
125
+ </FlexItem>
126
+ </Flex>
127
+ );
128
+ };
129
+
130
+ export const getColumns = fetchReports => [
131
+ {
132
+ title: __('Reported at'),
133
+ formatter: ({ reported_at, can_view, id }) =>
134
+ reportToShowFormatter()(reported_at, {
135
+ rowData: { canEdit: can_view, id },
136
+ }),
137
+ width: 25,
138
+ },
139
+ {
140
+ title: __('Status'),
141
+ formatter: globalStatusFormatter,
142
+ width: 25,
143
+ },
144
+ {
145
+ title: __('Summary'),
146
+ formatter: statusSummaryFormatter,
147
+ width: 25,
148
+ },
149
+ { title: __('Keywords'), formatter: keywordsFormatter, width: 15 },
150
+ {
151
+ title: null,
152
+ formatter: data => ActionFormatter(data, fetchReports),
153
+ width: 10,
154
+ },
155
+ ];
@@ -0,0 +1,132 @@
1
+ /* eslint-disable camelcase */
2
+ import React, { useEffect, useCallback } from 'react';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { useHistory } from 'react-router-dom';
5
+ import PropTypes from 'prop-types';
6
+ import URI from 'urijs';
7
+ import SearchBar from 'foremanReact/components/SearchBar';
8
+ import Pagination from 'foremanReact/components/Pagination';
9
+ import { get } from 'foremanReact/redux/API';
10
+ import { Grid, GridItem } from '@patternfly/react-core';
11
+ import {
12
+ selectAPIStatus,
13
+ selectAPIResponse,
14
+ } from 'foremanReact/redux/API/APISelectors';
15
+ import { useForemanSettings } from 'foremanReact/Root/Context/ForemanContext';
16
+ import { HOST_REPORTS_SEARCH_PROPS } from '../../Router/HostReports/IndexPage/constants';
17
+ import ReportsTable from './ReportsTable';
18
+
19
+ const ReportsTab = ({ hostName }) => {
20
+ const dispatch = useDispatch();
21
+ const history = useHistory();
22
+ const API_KEY = `get-reports-${hostName}`;
23
+ const { reports, itemCount } = useSelector(state =>
24
+ selectAPIResponse(state, API_KEY)
25
+ );
26
+ const { perPage: settingsPerPage = 20 } = useForemanSettings() || {};
27
+ const status = useSelector(state => selectAPIStatus(state, API_KEY));
28
+ const fetchReports = useCallback(
29
+ ({ search: searchParam, per_page: perPageParam, page: pageParam } = {}) => {
30
+ const {
31
+ page: urlPage,
32
+ perPage: urlPerPage,
33
+ search: urlSearch,
34
+ } = getUrlParams();
35
+ const search = searchParam !== undefined ? searchParam : urlSearch;
36
+ const page = pageParam || urlPage;
37
+ const per_page = perPageParam || urlPerPage;
38
+ dispatch(
39
+ get({
40
+ key: API_KEY,
41
+ url: '/host_reports',
42
+ params: {
43
+ page,
44
+ per_page,
45
+ search: getServerQuery(search),
46
+ },
47
+ })
48
+ );
49
+ updateUrl({ page, per_page, search });
50
+ },
51
+ [API_KEY, dispatch, getServerQuery, getUrlParams, updateUrl]
52
+ );
53
+
54
+ useEffect(() => {
55
+ fetchReports();
56
+ }, [fetchReports, history.location]);
57
+
58
+ const onPaginationChange = ({ page, per_page }) => {
59
+ const { search } = getUrlParams();
60
+ updateUrl({ page, per_page, search });
61
+ };
62
+
63
+ const getServerQuery = useCallback(
64
+ search => {
65
+ const serverQuery = [`host = ${hostName}`];
66
+ if (search) {
67
+ serverQuery.push(`AND (${search})`);
68
+ }
69
+ return serverQuery.join(' ').trim();
70
+ },
71
+ [hostName]
72
+ );
73
+
74
+ const getUrlParams = useCallback(() => {
75
+ const params = { page: 1, perPage: settingsPerPage, search: '' };
76
+ const urlSearch = history.location?.search;
77
+ const urlParams = urlSearch && new URLSearchParams(urlSearch);
78
+ if (urlParams) {
79
+ params.search = urlParams.get('search') || params.search;
80
+ params.page = Number(urlParams.get('page')) || params.page;
81
+ params.perPage = Number(urlParams.get('per_page')) || params.perPage;
82
+ }
83
+ return params;
84
+ }, [history.location, settingsPerPage]);
85
+
86
+ const updateUrl = useCallback(
87
+ ({ page, per_page, search = '' }) => {
88
+ const uri = new URI();
89
+ uri.search({ page, per_page, search });
90
+ history.push({ search: uri.search() });
91
+ },
92
+ [history]
93
+ );
94
+
95
+ return (
96
+ <Grid id="new_host_details_insights_tab" hasGutter>
97
+ <GridItem span={6}>
98
+ <SearchBar
99
+ data={HOST_REPORTS_SEARCH_PROPS}
100
+ onSearch={search => fetchReports({ search, page: 1 })}
101
+ />
102
+ </GridItem>
103
+ <GridItem span={6}>
104
+ <Pagination
105
+ variant="top"
106
+ itemCount={itemCount}
107
+ onChange={onPaginationChange}
108
+ />
109
+ </GridItem>
110
+ <GridItem>
111
+ <ReportsTable
112
+ reports={reports}
113
+ status={status}
114
+ fetchReports={fetchReports}
115
+ />
116
+ </GridItem>
117
+ <GridItem>
118
+ <Pagination
119
+ variant="bottom"
120
+ itemCount={itemCount}
121
+ onChange={onPaginationChange}
122
+ />
123
+ </GridItem>
124
+ </Grid>
125
+ );
126
+ };
127
+
128
+ ReportsTab.propTypes = {
129
+ hostName: PropTypes.string.isRequired,
130
+ };
131
+
132
+ export default ReportsTab;
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_host_reports
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lukas Zapletal
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-22 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rdoc
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
11
+ date: 2022-02-21 00:00:00.000000000 Z
12
+ dependencies: []
27
13
  description: Fast and efficient reporting capabilities
28
14
  email:
29
15
  - lukas-x@zapletalovi.com
@@ -34,11 +20,14 @@ files:
34
20
  - LICENSE
35
21
  - README.md
36
22
  - app/controllers/api/v2/host_reports_controller.rb
23
+ - app/controllers/concerns/foreman_host_reports/controller/hosts_controller_extensions.rb
37
24
  - app/controllers/concerns/foreman_host_reports/controller/parameters/host_report.rb
38
25
  - app/controllers/host_reports_controller.rb
26
+ - app/helpers/concerns/foreman_host_reports/hosts_helper_extensions.rb
39
27
  - app/models/concerns/foreman_host_reports/host_extensions.rb
40
28
  - app/models/concerns/foreman_host_reports/smart_proxy_extensions.rb
41
29
  - app/models/host_report.rb
30
+ - app/models/host_status/host_report_status.rb
42
31
  - app/models/report_keyword.rb
43
32
  - app/views/api/v2/host_reports/base.json.rabl
44
33
  - app/views/api/v2/host_reports/create.json.rabl
@@ -49,6 +38,7 @@ files:
49
38
  - db/migrate/20210112183526_add_host_reports.rb
50
39
  - db/migrate/20210616133601_create_report_keywords.rb
51
40
  - db/migrate/20211011141813_change_status_column.rb
41
+ - db/migrate/20220113064436_rename_status_summaries.rb
52
42
  - db/seeds.d/60-reports_feature.rb
53
43
  - lib/foreman_host_reports.rb
54
44
  - lib/foreman_host_reports/engine.rb
@@ -61,6 +51,7 @@ files:
61
51
  - package.json
62
52
  - test/controllers/api/v2/host_reports_controller_test.rb
63
53
  - test/factories/foreman_host_reports_factories.rb
54
+ - test/model/host_report_status_test.rb
64
55
  - test/snapshots/foreman-web.json
65
56
  - test/test_plugin_helper.rb
66
57
  - test/unit/foreman_host_reports_test.rb
@@ -71,7 +62,7 @@ files:
71
62
  - webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js
72
63
  - webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js
73
64
  - webpack/__mocks__/foremanReact/components/ForemanModal/index.js
74
- - webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js
65
+ - webpack/__mocks__/foremanReact/components/Pagination/index.js
75
66
  - webpack/__mocks__/foremanReact/components/common/EmptyState.js
76
67
  - webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js
77
68
  - webpack/__mocks__/foremanReact/components/common/forms/ForemanForm.js
@@ -84,6 +75,7 @@ files:
84
75
  - webpack/__mocks__/foremanReact/redux/actions/toasts.js
85
76
  - webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js
86
77
  - webpack/__mocks__/foremanReact/routes/common/PageLayout/components/ExportButton/ExportButton.js
78
+ - webpack/fills.js
87
79
  - webpack/global_index.js
88
80
  - webpack/global_test_setup.js
89
81
  - webpack/index.js
@@ -116,6 +108,8 @@ files:
116
108
  - webpack/src/Router/HostReports/ShowPage/Components/HostReportMetrics/HostReportMetrics.scss
117
109
  - webpack/src/Router/HostReports/ShowPage/Components/HostReportMetrics/index.js
118
110
  - webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Ansible.js
111
+ - webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Components/EmptyLogsRow.js
112
+ - webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Components/RawMsgModal.js
119
113
  - webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/Puppet.js
120
114
  - webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/helpers.js
121
115
  - webpack/src/Router/HostReports/ShowPage/Components/ReportLogs/index.js
@@ -125,10 +119,14 @@ files:
125
119
  - webpack/src/Router/HostReports/ShowPage/index.js
126
120
  - webpack/src/Router/HostReports/constants.js
127
121
  - webpack/src/Router/routes.js
122
+ - webpack/src/components/ReportsTab/ReportsTable.js
123
+ - webpack/src/components/ReportsTab/helpers.js
124
+ - webpack/src/components/ReportsTab/index.js
128
125
  homepage: https://github.com/theforeman/foreman_host_reports
129
126
  licenses:
130
127
  - GPL-3.0
131
- metadata: {}
128
+ metadata:
129
+ is_foreman_plugin: 'true'
132
130
  post_install_message:
133
131
  rdoc_options: []
134
132
  require_paths:
@@ -153,5 +151,6 @@ test_files:
153
151
  - test/unit/foreman_host_reports_test.rb
154
152
  - test/controllers/api/v2/host_reports_controller_test.rb
155
153
  - test/snapshots/foreman-web.json
154
+ - test/model/host_report_status_test.rb
156
155
  - test/test_plugin_helper.rb
157
156
  - webpack/src/Router/HostReports/IndexPage/__tests__/HostReportsIndexPage.test.js
@@ -1,4 +0,0 @@
1
- import React from 'react';
2
-
3
- const PaginationWrapper = () => <></>;
4
- export default PaginationWrapper;