foreman_statistics 0.1.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/foreman_statistics/api/v2/statistics_controller.rb +9 -1
  3. data/app/controllers/foreman_statistics/api/v2/trends_controller.rb +8 -0
  4. data/app/jobs/foreman_statistics/trend_counter_job.rb +23 -0
  5. data/app/views/foreman_statistics/layouts/application_react.html.erb +1 -2
  6. data/app/views/foreman_statistics/trends/_empty_data.html.erb +0 -1
  7. data/app/views/foreman_statistics/trends/index.html.erb +3 -12
  8. data/app/views/foreman_statistics/trends/welcome.html.erb +1 -1
  9. data/db/migrate/20200605153005_migrate_core_types.rb +6 -2
  10. data/lib/foreman_statistics/engine.rb +13 -1
  11. data/lib/foreman_statistics/version.rb +1 -1
  12. data/lib/tasks/foreman_statistics_tasks.rake +3 -2
  13. data/locale/action_names.rb +5 -0
  14. data/locale/en/LC_MESSAGES/foreman_statistics.mo +0 -0
  15. data/locale/en/foreman_statistics.edit.po +258 -0
  16. data/locale/en/foreman_statistics.po +181 -2
  17. data/locale/en/foreman_statistics.po.time_stamp +0 -0
  18. data/locale/foreman_statistics.pot +265 -8
  19. data/locale/gemspec.rb +1 -1
  20. data/package.json +42 -0
  21. data/test/functional/foreman_statistics/api/v2/statistics_controller_test.rb +1 -1
  22. data/test/functional/foreman_statistics/api/v2/trends_controller_test.rb +1 -1
  23. data/test/functional/foreman_statistics/statistics_controller_test.rb +5 -7
  24. data/test/functional/foreman_statistics/trends_controller_test.rb +3 -3
  25. data/test/models/foreman_statistics/trend_counter_test.rb +1 -1
  26. data/test/models/foreman_statistics/trend_test.rb +1 -1
  27. data/test/{test_plugin_helper.rb → test_statistics_helper.rb} +0 -0
  28. data/test/unit/foreman_statistics/access_permissions_test.rb +1 -1
  29. data/test/unit/foreman_statistics/statistics_test.rb +1 -1
  30. data/test/unit/foreman_statistics_test.rb +1 -1
  31. data/test/unit/tasks/foreman_statistics_tasks_test.rb +1 -1
  32. data/webpack/__mocks__/foremanReact/API.js +7 -0
  33. data/webpack/__mocks__/foremanReact/common/HOC.js +24 -0
  34. data/webpack/__mocks__/foremanReact/common/I18n.js +3 -0
  35. data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
  36. data/webpack/__mocks__/foremanReact/common/urlHelpers.js +1 -0
  37. data/webpack/__mocks__/foremanReact/components/ChartBox/index.js +2 -0
  38. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js +2 -0
  39. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js +10 -0
  40. data/webpack/__mocks__/foremanReact/components/ForemanModal/index.js +4 -0
  41. data/webpack/__mocks__/foremanReact/components/Layout/LayoutActions.js +2 -0
  42. data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +2 -0
  43. data/webpack/__mocks__/foremanReact/components/common/EmptyState.js +5 -0
  44. data/webpack/__mocks__/foremanReact/components/common/MessageBox.js +4 -0
  45. data/webpack/__mocks__/foremanReact/components/common/dates/LongDateTime.js +5 -0
  46. data/webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js +3 -0
  47. data/webpack/__mocks__/foremanReact/components/common/table.js +5 -0
  48. data/webpack/__mocks__/foremanReact/components/common/table/actionsHelpers/actionTypeCreator.js +7 -0
  49. data/webpack/__mocks__/foremanReact/constants.js +24 -0
  50. data/webpack/__mocks__/foremanReact/readme.md +11 -0
  51. data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +8 -0
  52. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  53. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/components/ExportButton/ExportButton.js +5 -0
  54. data/webpack/__mocks__/foremanReact/routes/common/reducerHOC/withDataReducer.js +35 -0
  55. data/webpack/fills_index.js +15 -0
  56. data/webpack/index.js +21 -0
  57. data/webpack/src/Components/StatisticsChartsList/StatisticsChartsList.fixtures.js +19 -0
  58. data/webpack/src/Components/StatisticsChartsList/StatisticsChartsList.test.js +18 -0
  59. data/webpack/src/Components/StatisticsChartsList/StatisticsChartsListStyles.scss +28 -0
  60. data/webpack/src/Components/StatisticsChartsList/__snapshots__/StatisticsChartsList.test.js.snap +42 -0
  61. data/webpack/src/Components/StatisticsChartsList/index.js +32 -0
  62. data/webpack/src/ForemanStatistics.js +11 -0
  63. data/webpack/src/Router/StatisticsPage/Statistics/Statistics.js +27 -0
  64. data/webpack/src/Router/StatisticsPage/StatisticsPage.fixtures.js +11 -0
  65. data/webpack/src/Router/StatisticsPage/StatisticsPage.js +21 -0
  66. data/webpack/src/Router/StatisticsPage/StatisticsPageActions.js +43 -0
  67. data/webpack/src/Router/StatisticsPage/StatisticsPageSelectors.js +13 -0
  68. data/webpack/src/Router/StatisticsPage/__tests__/StatisticsPage.test.js +12 -0
  69. data/webpack/src/Router/StatisticsPage/__tests__/StatisticsPageActions.test.js +27 -0
  70. data/webpack/src/Router/StatisticsPage/__tests__/StatisticsPageSelectors.test.js +34 -0
  71. data/webpack/src/Router/StatisticsPage/__tests__/__snapshots__/StatisticsPage.test.js.snap +32 -0
  72. data/webpack/src/Router/StatisticsPage/__tests__/__snapshots__/StatisticsPageActions.test.js.snap +54 -0
  73. data/webpack/src/Router/StatisticsPage/__tests__/__snapshots__/StatisticsPageSelectors.test.js.snap +35 -0
  74. data/webpack/src/Router/StatisticsPage/constants.js +4 -0
  75. data/webpack/src/Router/StatisticsPage/index.js +36 -0
  76. data/webpack/src/Router/__snapshots__/routes.test.js.snap +47 -0
  77. data/webpack/src/Router/index.js +14 -0
  78. data/webpack/src/Router/routes.js +11 -0
  79. data/webpack/src/Router/routes.test.js +27 -0
  80. data/webpack/src/index.js +1 -0
  81. data/webpack/src/reducers.js +9 -0
  82. data/webpack/src/trends.js +7 -0
  83. data/webpack/src/trends.test.js +44 -0
  84. metadata +67 -9
@@ -0,0 +1,18 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+
3
+ import StatisticsChartsList from './';
4
+ import { statisticsData } from './StatisticsChartsList.fixtures';
5
+
6
+ const fixtures = {
7
+ 'should render no panels for empty data': {
8
+ data: {},
9
+ },
10
+ 'should render two panels for fixtures data': {
11
+ data: statisticsData,
12
+ },
13
+ };
14
+
15
+ describe('StatisticsChartsList', () => {
16
+ describe('rendering', () =>
17
+ testComponentSnapshotsWithFixtures(StatisticsChartsList, fixtures));
18
+ });
@@ -0,0 +1,28 @@
1
+ .statistics-charts-list-root {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ justify-content: space-around;
5
+ padding-top: 20px;
6
+ .chart-box {
7
+ height: 312px;
8
+ width: 280px;
9
+ display: -ms-flexbox;
10
+ display: flex;
11
+ -ms-flex-direction: column;
12
+ flex-direction: column;
13
+ min-height: 312px;
14
+
15
+ .card-pf-heading {
16
+ -ms-flex: 0 0 30px;
17
+ flex: 0 0 30px;
18
+ }
19
+
20
+ .card-pf-body {
21
+ display: -ms-flexbox;
22
+ display: flex;
23
+ -ms-flex: 1;
24
+ flex: 1;
25
+ margin: 0;
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,42 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`StatisticsChartsList rendering should render no panels for empty data 1`] = `
4
+ <div
5
+ className="statistics-charts-list-root cards-pf"
6
+ />
7
+ `;
8
+
9
+ exports[`StatisticsChartsList rendering should render two panels for fixtures data 1`] = `
10
+ <div
11
+ className="statistics-charts-list-root cards-pf"
12
+ >
13
+ <ConnectedChartBox
14
+ chart={
15
+ Object {
16
+ "id": "operatingsystem",
17
+ "search": "/hosts?search=os_title=~VAL~",
18
+ "title": "OS Distribution",
19
+ "url": "statistics/operatingsystem",
20
+ }
21
+ }
22
+ key="operatingsystem"
23
+ noDataMsg="No data available"
24
+ tip="Expand the chart"
25
+ type="donut"
26
+ />
27
+ <ConnectedChartBox
28
+ chart={
29
+ Object {
30
+ "id": "architecture",
31
+ "search": "/hosts?search=facts.architecture=~VAL~",
32
+ "title": "Architecture Distribution",
33
+ "url": "statistics/architecture",
34
+ }
35
+ }
36
+ key="architecture"
37
+ noDataMsg="No data available"
38
+ tip="Expand the chart"
39
+ type="donut"
40
+ />
41
+ </div>
42
+ `;
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import ConnectedChartBox from 'foremanReact/components/ChartBox';
6
+ import './StatisticsChartsListStyles.scss';
7
+
8
+ const StatisticsChartsList = ({ data }) => {
9
+ const chartBoxes = Object.values(data).map(chart => (
10
+ <ConnectedChartBox
11
+ key={chart.id}
12
+ type="donut"
13
+ chart={chart}
14
+ noDataMsg={__('No data available')}
15
+ tip={__('Expand the chart')}
16
+ />
17
+ ));
18
+
19
+ return (
20
+ <div className="statistics-charts-list-root cards-pf">{chartBoxes}</div>
21
+ );
22
+ };
23
+
24
+ StatisticsChartsList.propTypes = {
25
+ data: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
26
+ };
27
+
28
+ StatisticsChartsList.defaultProps = {
29
+ data: [],
30
+ };
31
+
32
+ export default StatisticsChartsList;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { BrowserRouter } from 'react-router-dom';
3
+ import ForemanStatisticsRoute from './Router';
4
+
5
+ const ForemanStatistics = () => (
6
+ <BrowserRouter>
7
+ <ForemanStatisticsRoute />
8
+ </BrowserRouter>
9
+ );
10
+
11
+ export default ForemanStatistics;
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { withRenderHandler } from 'foremanReact/common/HOC';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import { EmptyStatePattern } from 'foremanReact/components/common/EmptyState';
6
+ import StatisticsChartsList from '../../../Components/StatisticsChartsList';
7
+
8
+ const Statistics = ({ statisticsMeta }) => {
9
+ if (!statisticsMeta) {
10
+ return (
11
+ <EmptyStatePattern
12
+ icon="info"
13
+ header={__('No Charts To Load')}
14
+ description=""
15
+ />
16
+ );
17
+ }
18
+ return <StatisticsChartsList data={statisticsMeta} />;
19
+ };
20
+
21
+ Statistics.propTypes = {
22
+ statisticsMeta: PropTypes.array.isRequired,
23
+ };
24
+
25
+ export default withRenderHandler({
26
+ Component: Statistics,
27
+ });
@@ -0,0 +1,11 @@
1
+ import { noop } from 'foremanReact/common/helpers';
2
+ import { statisticsMeta } from '../../Components/StatisticsChartsList/StatisticsChartsList.fixtures';
3
+
4
+ export const statisticsProps = {
5
+ statisticsMeta,
6
+ isLoading: false,
7
+ hasData: true,
8
+ hasError: false,
9
+ message: {},
10
+ getStatisticsMeta: noop,
11
+ };
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
5
+ import Statistics from './Statistics/Statistics';
6
+
7
+ const StatisticsPage = ({ statisticsMeta, ...props }) => (
8
+ <PageLayout header={__('Statistics')} searchable={false}>
9
+ <Statistics statisticsMeta={statisticsMeta} {...props} />
10
+ </PageLayout>
11
+ );
12
+
13
+ StatisticsPage.propTypes = {
14
+ statisticsMeta: PropTypes.array,
15
+ };
16
+
17
+ StatisticsPage.defaultProps = {
18
+ statisticsMeta: [],
19
+ };
20
+
21
+ export default StatisticsPage;
@@ -0,0 +1,43 @@
1
+ import API from 'foremanReact/API';
2
+
3
+ import {
4
+ STATISTICS_PAGE_DATA_RESOLVED,
5
+ STATISTICS_PAGE_DATA_FAILED,
6
+ STATISTICS_PAGE_HIDE_LOADING,
7
+ STATISTICS_PAGE_URL,
8
+ } from './constants';
9
+
10
+ export const getStatisticsMeta = (
11
+ url = STATISTICS_PAGE_URL
12
+ ) => async dispatch => {
13
+ const onFetchSuccess = ({ data }) => {
14
+ dispatch(hideLoading());
15
+ dispatch({
16
+ type: STATISTICS_PAGE_DATA_RESOLVED,
17
+ payload: { metadata: data.charts, hasData: data.charts.length > 0 },
18
+ });
19
+ };
20
+
21
+ const onFetchError = ({ message }) => {
22
+ dispatch(hideLoading());
23
+ dispatch({
24
+ type: STATISTICS_PAGE_DATA_FAILED,
25
+ payload: {
26
+ message: {
27
+ type: 'error',
28
+ text: message,
29
+ },
30
+ },
31
+ });
32
+ };
33
+ try {
34
+ const response = await API.get(url);
35
+ return onFetchSuccess(response);
36
+ } catch (error) {
37
+ return onFetchError(error);
38
+ }
39
+ };
40
+
41
+ const hideLoading = () => ({
42
+ type: STATISTICS_PAGE_HIDE_LOADING,
43
+ });
@@ -0,0 +1,13 @@
1
+ export const selectStatisticsPage = state =>
2
+ state.foremanStatistics.statisticsPage;
3
+
4
+ export const selectStatisticsMetadata = state =>
5
+ selectStatisticsPage(state).metadata;
6
+ export const selectStatisticsIsLoading = state =>
7
+ selectStatisticsPage(state).isLoading;
8
+ export const selectStatisticsMessage = state =>
9
+ selectStatisticsPage(state).message;
10
+ export const selectStatisticsHasError = state =>
11
+ selectStatisticsPage(state).hasError;
12
+ export const selectStatisticsHasMetadata = state =>
13
+ selectStatisticsPage(state).hasData;
@@ -0,0 +1,12 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+ import { statisticsProps } from '../StatisticsPage.fixtures';
3
+ import StatisticsPage from '../StatisticsPage';
4
+
5
+ const fixtures = {
6
+ 'render with props': statisticsProps,
7
+ };
8
+
9
+ describe('StatisticsPage', () => {
10
+ describe('rendering', () =>
11
+ testComponentSnapshotsWithFixtures(StatisticsPage, fixtures));
12
+ });
@@ -0,0 +1,27 @@
1
+ import API from 'foremanReact/API';
2
+
3
+ import { testActionSnapshotWithFixtures } from '@theforeman/test';
4
+ import { getStatisticsMeta } from '../StatisticsPageActions';
5
+ import { statisticsProps } from '../StatisticsPage.fixtures';
6
+
7
+ jest.mock('foremanReact/API');
8
+
9
+ const runStatisticsAction = (callback, props, serverMock) => {
10
+ API.get.mockImplementation(serverMock);
11
+
12
+ return callback(props);
13
+ };
14
+
15
+ const fixtures = {
16
+ 'should fetch statisticsMeta': () =>
17
+ runStatisticsAction(getStatisticsMeta, {}, async () => ({
18
+ data: { charts: statisticsProps.statisticsMeta },
19
+ })),
20
+ 'should fetch statisticsMeta and fail': () =>
21
+ runStatisticsAction(getStatisticsMeta, {}, async () => {
22
+ throw new Error('some-error');
23
+ }),
24
+ };
25
+
26
+ describe('StatisticsPage actions', () =>
27
+ testActionSnapshotWithFixtures(fixtures));
@@ -0,0 +1,34 @@
1
+ import { testSelectorsSnapshotWithFixtures } from '@theforeman/test';
2
+ import {
3
+ selectStatisticsPage,
4
+ selectStatisticsMetadata,
5
+ selectStatisticsHasMetadata,
6
+ selectStatisticsIsLoading,
7
+ selectStatisticsMessage,
8
+ selectStatisticsHasError,
9
+ } from '../StatisticsPageSelectors';
10
+ import { statisticsProps } from '../StatisticsPage.fixtures';
11
+
12
+ const state = {
13
+ foremanStatistics: {
14
+ statisticsPage: {
15
+ ...statisticsProps,
16
+ },
17
+ },
18
+ };
19
+
20
+ const fixtures = {
21
+ 'should return StatisticsPage': () => selectStatisticsPage(state),
22
+ 'should return StatisticsHasMetadata': () =>
23
+ selectStatisticsHasMetadata(state),
24
+ 'should return StatisticsPage statisticsMeta': () =>
25
+ selectStatisticsMetadata(state),
26
+ 'should return StatisticsPage isLoading': () =>
27
+ selectStatisticsIsLoading(state),
28
+ 'should return StatisticsPage Message': () => selectStatisticsMessage(state),
29
+ 'should return StatisticsPage hasError': () =>
30
+ selectStatisticsHasError(state),
31
+ };
32
+
33
+ describe('StatisticsPage selectors', () =>
34
+ testSelectorsSnapshotWithFixtures(fixtures));
@@ -0,0 +1,32 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`StatisticsPage rendering render with props 1`] = `
4
+ <PageLayout
5
+ header="Statistics"
6
+ searchable={false}
7
+ >
8
+ <Component
9
+ getStatisticsMeta={[Function]}
10
+ hasData={true}
11
+ hasError={false}
12
+ isLoading={false}
13
+ message={Object {}}
14
+ statisticsMeta={
15
+ Array [
16
+ Object {
17
+ "id": "operatingsystem",
18
+ "search": "/hosts?search=os_title=~VAL~",
19
+ "title": "OS Distribution",
20
+ "url": "statistics/operatingsystem",
21
+ },
22
+ Object {
23
+ "id": "architecture",
24
+ "search": "/hosts?search=facts.architecture=~VAL~",
25
+ "title": "Architecture Distribution",
26
+ "url": "statistics/architecture",
27
+ },
28
+ ]
29
+ }
30
+ />
31
+ </PageLayout>
32
+ `;
@@ -0,0 +1,54 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`StatisticsPage actions should fetch statisticsMeta 1`] = `
4
+ Array [
5
+ Array [
6
+ Object {
7
+ "type": "STATISTICS_PAGE_HIDE_LOADING",
8
+ },
9
+ ],
10
+ Array [
11
+ Object {
12
+ "payload": Object {
13
+ "hasData": true,
14
+ "metadata": Array [
15
+ Object {
16
+ "id": "operatingsystem",
17
+ "search": "/hosts?search=os_title=~VAL~",
18
+ "title": "OS Distribution",
19
+ "url": "statistics/operatingsystem",
20
+ },
21
+ Object {
22
+ "id": "architecture",
23
+ "search": "/hosts?search=facts.architecture=~VAL~",
24
+ "title": "Architecture Distribution",
25
+ "url": "statistics/architecture",
26
+ },
27
+ ],
28
+ },
29
+ "type": "STATISTICS_PAGE_DATA_RESOLVED",
30
+ },
31
+ ],
32
+ ]
33
+ `;
34
+
35
+ exports[`StatisticsPage actions should fetch statisticsMeta and fail 1`] = `
36
+ Array [
37
+ Array [
38
+ Object {
39
+ "type": "STATISTICS_PAGE_HIDE_LOADING",
40
+ },
41
+ ],
42
+ Array [
43
+ Object {
44
+ "payload": Object {
45
+ "message": Object {
46
+ "text": "some-error",
47
+ "type": "error",
48
+ },
49
+ },
50
+ "type": "STATISTICS_PAGE_FETCH_FAILED",
51
+ },
52
+ ],
53
+ ]
54
+ `;
@@ -0,0 +1,35 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`StatisticsPage selectors should return StatisticsHasMetadata 1`] = `true`;
4
+
5
+ exports[`StatisticsPage selectors should return StatisticsPage 1`] = `
6
+ Object {
7
+ "getStatisticsMeta": [Function],
8
+ "hasData": true,
9
+ "hasError": false,
10
+ "isLoading": false,
11
+ "message": Object {},
12
+ "statisticsMeta": Array [
13
+ Object {
14
+ "id": "operatingsystem",
15
+ "search": "/hosts?search=os_title=~VAL~",
16
+ "title": "OS Distribution",
17
+ "url": "statistics/operatingsystem",
18
+ },
19
+ Object {
20
+ "id": "architecture",
21
+ "search": "/hosts?search=facts.architecture=~VAL~",
22
+ "title": "Architecture Distribution",
23
+ "url": "statistics/architecture",
24
+ },
25
+ ],
26
+ }
27
+ `;
28
+
29
+ exports[`StatisticsPage selectors should return StatisticsPage Message 1`] = `Object {}`;
30
+
31
+ exports[`StatisticsPage selectors should return StatisticsPage hasError 1`] = `false`;
32
+
33
+ exports[`StatisticsPage selectors should return StatisticsPage isLoading 1`] = `false`;
34
+
35
+ exports[`StatisticsPage selectors should return StatisticsPage statisticsMeta 1`] = `undefined`;