foreman_templates 7.0.6 → 9.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/template_controller.rb +8 -2
  3. data/app/controllers/concerns/foreman/controller/parameters/template_params.rb +22 -2
  4. data/app/controllers/ui_template_syncs_controller.rb +2 -2
  5. data/app/models/setting/template_sync.rb +12 -2
  6. data/app/services/foreman_templates/export_result.rb +20 -29
  7. data/app/services/foreman_templates/template_exporter.rb +40 -32
  8. data/app/services/foreman_templates/template_importer.rb +20 -4
  9. data/app/views/template_syncs/index.html.erb +20 -19
  10. data/app/views/ui_template_syncs/template_export_result.rabl +4 -4
  11. data/db/migrate/20180627134929_change_lock_setting.rb +5 -0
  12. data/lib/foreman_templates/engine.rb +6 -0
  13. data/lib/foreman_templates/version.rb +1 -1
  14. data/package.json +15 -21
  15. data/webpack/ForemanTemplates.js +17 -13
  16. data/webpack/__mocks__/foremanReact/components/Layout/LayoutSelectors.js +4 -0
  17. data/webpack/__mocks__/foremanReact/components/common/forms/ForemanForm.js +2 -0
  18. data/webpack/components/NewTemplateSync/__fixtures__/templateSyncSettings.fixtures.js +4 -4
  19. data/webpack/components/NewTemplateSync/__tests__/__snapshots__/NewTemplateSync.test.js.snap +2 -2
  20. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncForm.js +89 -53
  21. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncFormSelectors.js +10 -13
  22. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/__tests__/NewTemplateSyncFormSelectors.test.js +1 -19
  23. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/__tests__/__snapshots__/NewTemplateSyncFormSelectors.test.js.snap +7 -36
  24. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/index.js +7 -16
  25. data/webpack/components/NewTemplateSync/components/SyncSettingField.js +5 -11
  26. data/webpack/components/NewTemplateSync/components/SyncSettingFields.js +8 -25
  27. data/webpack/components/NewTemplateSync/components/TextButtonField/index.js +27 -20
  28. data/webpack/components/NewTemplateSync/components/__tests__/SyncSettingField.test.js +2 -1
  29. data/webpack/components/NewTemplateSync/components/__tests__/SyncSettingFields.test.js +1 -0
  30. data/webpack/components/NewTemplateSync/components/__tests__/TextButtonField.test.js +4 -4
  31. data/webpack/components/NewTemplateSync/components/__tests__/__snapshots__/SyncSettingField.test.js.snap +18 -27
  32. data/webpack/components/NewTemplateSync/components/__tests__/__snapshots__/SyncSettingFields.test.js.snap +5 -3
  33. data/webpack/components/NewTemplateSync/components/__tests__/__snapshots__/TextButtonField.test.js.snap +8 -91
  34. data/webpack/components/TemplateSyncResult/__fixtures__/templateSyncResult.fixtures.js +2 -2
  35. data/webpack/components/TemplateSyncResult/__tests__/__snapshots__/TemplateSyncResult.test.js.snap +3 -1
  36. data/webpack/components/TemplateSyncResult/__tests__/__snapshots__/TemplateSyncResultReducer.test.js.snap +3 -1
  37. data/webpack/components/TemplateSyncResult/components/SyncResultList.js +2 -2
  38. data/webpack/components/TemplateSyncResult/components/SyncedTemplate/__snapshots__/helpers.test.js.snap +37 -0
  39. data/webpack/components/TemplateSyncResult/components/SyncedTemplate/helpers.js +21 -11
  40. data/webpack/components/TemplateSyncResult/components/SyncedTemplate/helpers.test.js +21 -0
  41. data/webpack/components/TemplateSyncResult/components/__tests__/__snapshots__/SyncResultList.test.js.snap +8 -6
  42. data/webpack/components/TemplateSyncResult/components/__tests__/__snapshots__/SyncedTemplate.test.js.snap +39 -15
  43. data/webpack/testSetup.js +2 -1
  44. metadata +8 -8
  45. data/webpack/__mocks__/foremanReact/components/common/forms/Form.js +0 -2
  46. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncFormConstants.js +0 -1
  47. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/__tests__/NewTemplateSyncForm.test.js +0 -42
  48. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/__tests__/__snapshots__/NewTemplateSyncForm.test.js.snap +0 -186
@@ -1,3 +1,3 @@
1
1
  module ForemanTemplates
2
- VERSION = '7.0.6'.freeze
2
+ VERSION = '9.0.2'.freeze
3
3
  end
data/package.json CHANGED
@@ -20,33 +20,24 @@
20
20
  "url": "https://projects.theforeman.org"
21
21
  },
22
22
  "homepage": "https://github.com/theforeman/foreman_templates",
23
- "dependencies": {
24
- "@theforeman/vendor": "^1.7.0"
23
+ "peerDependencies": {
24
+ "@theforeman/vendor": ">= 3.3.2"
25
25
  },
26
26
  "devDependencies": {
27
- "@theforeman/vendor-dev": "^1.7.0",
28
- "babel-cli": "^6.10.1",
29
- "babel-core": "^6.26.3",
30
- "babel-eslint": "^8.2.1",
31
- "babel-loader": "^7.1.1",
32
- "babel-plugin-transform-class-properties": "^6.24.1",
33
- "babel-plugin-transform-object-assign": "^6.22.0",
34
- "babel-plugin-transform-object-rest-spread": "^6.26.0",
35
- "babel-preset-env": "^1.6.0",
36
- "babel-preset-react": "^6.24.1",
27
+ "@babel/core": "^7.7.0",
28
+ "@theforeman/env": "^3.3.2",
29
+ "@theforeman/builder": "^4.0.2",
30
+ "@theforeman/vendor-dev": "^3.3.2",
31
+ "babel-eslint": "^10.0.0",
32
+ "babel-jest": "^24.9.0",
37
33
  "enzyme": "^3.7.0",
38
34
  "enzyme-adapter-react-16": "^1.7.0",
39
35
  "enzyme-to-json": "^3.3.5",
40
- "eslint": "^4.18.1",
41
- "eslint-config-airbnb": "^16.0.0",
42
- "eslint-plugin-import": "^2.8.0",
43
- "eslint-plugin-jest": "^21.2.0",
44
- "eslint-plugin-jsx-a11y": "^6.0.2",
45
- "eslint-plugin-patternfly-react": "^0.2.1",
46
- "eslint-plugin-react": "^7.4.0",
36
+ "eslint": "^6.8.0",
37
+ "eslint-plugin-patternfly-react": "^0.3.0",
47
38
  "identity-obj-proxy": "^3.0.0",
48
- "jest": "^23.6.0",
49
- "prettier": "^1.16.4",
39
+ "jest": "^24.9.0",
40
+ "prettier": "^1.19.1",
50
41
  "react-redux-test-utils": "^0.1.1"
51
42
  },
52
43
  "jest": {
@@ -56,6 +47,9 @@
56
47
  "node_modules",
57
48
  "webpack"
58
49
  ],
50
+ "transform": {
51
+ "^.+\\.js$": "babel-jest"
52
+ },
59
53
  "setupFiles": [
60
54
  "raf/polyfill",
61
55
  "./webpack/testSetup.js"
@@ -4,26 +4,30 @@ import PropTypes from 'prop-types';
4
4
 
5
5
  import Routes from './Routes';
6
6
 
7
- const ForemanTemplates = ({ data }) => (
7
+ const ForemanTemplates = ({
8
+ apiUrls,
9
+ validationData,
10
+ fileRepoStartWith,
11
+ userPermissions,
12
+ editPaths,
13
+ }) => (
8
14
  <Router>
9
15
  <Routes
10
- apiUrls={data.apiUrls}
11
- validationData={data.validationData}
12
- editPaths={data.editPaths}
13
- fileRepoStartWith={data.fileRepoStartWith}
14
- userPermissions={data.userPermissions}
16
+ apiUrls={apiUrls}
17
+ validationData={validationData}
18
+ editPaths={editPaths}
19
+ fileRepoStartWith={fileRepoStartWith}
20
+ userPermissions={userPermissions}
15
21
  />
16
22
  </Router>
17
23
  );
18
24
 
19
25
  ForemanTemplates.propTypes = {
20
- data: PropTypes.shape({
21
- apiUrls: PropTypes.object,
22
- validationData: PropTypes.object,
23
- editPaths: PropTypes.object,
24
- userPermissions: PropTypes.object,
25
- fileRepoStartWith: PropTypes.array,
26
- }).isRequired,
26
+ apiUrls: PropTypes.object.isRequired,
27
+ validationData: PropTypes.object.isRequired,
28
+ editPaths: PropTypes.object.isRequired,
29
+ userPermissions: PropTypes.object.isRequired,
30
+ fileRepoStartWith: PropTypes.array.isRequired,
27
31
  };
28
32
 
29
33
  export default ForemanTemplates;
@@ -0,0 +1,4 @@
1
+ export const selectLayout = jest.fn(() => ({
2
+ currentOrganization: {},
3
+ currentLocation: {},
4
+ }));
@@ -0,0 +1,2 @@
1
+ const ForemanForm = () => jest.fn();
2
+ export default ForemanForm;
@@ -1,5 +1,7 @@
1
1
  import Immutable from 'seamless-immutable';
2
2
 
3
+ import { transformInitialValues } from '../components/NewTemplateSyncForm/NewTemplateSyncFormSelectors';
4
+
3
5
  export const associateSetting = Immutable({
4
6
  id: 45,
5
7
  value: 'new',
@@ -65,10 +67,8 @@ export const registeredExportSettings = {
65
67
  };
66
68
 
67
69
  export const initialValues = {
68
- initial: importSettings.concat(exportSettings).reduce((memo, item) => {
69
- memo[item.name] = item.value;
70
- return memo;
71
- }, {}),
70
+ import: transformInitialValues(importSettings),
71
+ export: transformInitialValues(exportSettings),
72
72
  };
73
73
 
74
74
  export const stateFactory = obj => ({
@@ -12,7 +12,7 @@ exports[`NewTemplateSync should render when loaded 1`] = `
12
12
  header="Import or Export Templates"
13
13
  searchable={false}
14
14
  >
15
- <Connect(ReduxForm)
15
+ <Connect(NewTemplateSyncForm)
16
16
  history={Object {}}
17
17
  userPermissions={
18
18
  Object {
@@ -38,7 +38,7 @@ exports[`NewTemplateSync should render when loading 1`] = `
38
38
  header="Import or Export Templates"
39
39
  searchable={false}
40
40
  >
41
- <Connect(ReduxForm)
41
+ <Connect(NewTemplateSyncForm)
42
42
  history={Object {}}
43
43
  userPermissions={
44
44
  Object {
@@ -1,37 +1,54 @@
1
1
  import React from 'react';
2
- import { change } from 'redux-form';
3
2
  import PropTypes from 'prop-types';
3
+ import { compose } from 'redux';
4
4
 
5
- import Form from 'foremanReact/components/common/forms/Form';
6
-
5
+ import ForemanForm from 'foremanReact/components/common/forms/ForemanForm';
6
+ import * as Yup from 'yup';
7
7
  import SyncSettingsFields from '../SyncSettingFields';
8
8
  import SyncTypeRadios from '../SyncTypeRadios';
9
- import { NEW_TEMPLATE_SYNC_FORM_NAME } from './NewTemplateSyncFormConstants';
10
-
11
- const submit = syncType => (formValues, dispatch, props) => {
12
- const { submitForm, importUrl, exportUrl, history, currentFields } = props;
13
- const url = syncType === 'import' ? importUrl : exportUrl;
14
- const currentFieldNames = Object.keys(currentFields);
15
- const postValues = Object.keys(formValues).reduce((memo, key) => {
16
- if (currentFieldNames.includes(key)) {
17
- memo[key] = formValues[key];
18
- }
19
- return memo;
20
- }, {});
21
-
22
- return submitForm({
23
- url,
24
- values: postValues,
25
- message: `Templates were ${syncType}ed.`,
26
- item: 'TemplateSync',
27
- }).then(args => {
28
- history.replace({ pathname: '/template_syncs/result' });
29
- });
30
- };
31
9
 
32
10
  const redirectToResult = history => () =>
33
11
  history.push({ pathname: '/template_syncs/result' });
34
12
 
13
+ const repoFormat = formatAry => value => {
14
+ if (value === undefined) {
15
+ return true;
16
+ }
17
+
18
+ const valid = formatAry
19
+ .map(item => value.startsWith(item))
20
+ .reduce((memo, item) => item || memo, false);
21
+
22
+ return value && valid;
23
+ };
24
+
25
+ const syncFormSchema = (syncType, settingsObj, validationData) => {
26
+ const schema = (settingsObj[syncType].asMutable() || []).reduce(
27
+ (memo, setting) => {
28
+ if (setting.name === 'repo') {
29
+ return {
30
+ ...memo,
31
+ repo: Yup.string()
32
+ .test(
33
+ 'repo-format',
34
+ `Invalid repo format, must start with one of: ${validationData.repo.join(
35
+ ', '
36
+ )}`,
37
+ repoFormat(validationData.repo)
38
+ )
39
+ .required("can't be blank"),
40
+ };
41
+ }
42
+ return memo;
43
+ },
44
+ {}
45
+ );
46
+
47
+ return Yup.object().shape({
48
+ [syncType]: Yup.object().shape(schema),
49
+ });
50
+ };
51
+
35
52
  class NewTemplateSyncForm extends React.Component {
36
53
  allowedSyncType = (userPermissions, radioAttrs) =>
37
54
  this.props.userPermissions[radioAttrs.permission];
@@ -71,50 +88,69 @@ class NewTemplateSyncForm extends React.Component {
71
88
 
72
89
  render() {
73
90
  const {
74
- submitting,
75
91
  error,
76
- handleSubmit,
92
+ submitForm,
77
93
  importSettings,
78
94
  exportSettings,
79
- dispatch,
80
95
  history,
81
96
  validationData,
82
- valid,
97
+ importUrl,
98
+ exportUrl,
99
+ initialValues,
100
+ currentLocation,
101
+ currentOrganization,
83
102
  } = this.props;
84
103
 
85
- const resetToDefault = ((dispatchFn, changeFn, nameOfForm) => (
86
- fieldName,
87
- value
88
- ) => {
89
- dispatchFn(changeFn(nameOfForm, fieldName, value));
90
- })(dispatch, change, NEW_TEMPLATE_SYNC_FORM_NAME);
104
+ const addTaxParams = (key, currentTax) => params => {
105
+ if (currentTax && currentTax.id) {
106
+ return { ...params, [key]: [currentTax.id] };
107
+ }
108
+ return params;
109
+ };
110
+
111
+ const addOrgParams = addTaxParams('organization_ids', currentOrganization);
112
+ const addLocParams = addTaxParams('location_ids', currentLocation);
113
+
114
+ const resetToDefault = (fieldName, fieldValue) => resetFn =>
115
+ resetFn(fieldName, fieldValue);
91
116
 
92
117
  return (
93
- <Form
94
- onSubmit={handleSubmit(submit(this.state.syncType))}
95
- disabled={submitting || (!valid && !error)}
96
- submitting={submitting}
97
- error={error}
118
+ <ForemanForm
119
+ onSubmit={(values, actions) => {
120
+ const url = this.state.syncType === 'import' ? importUrl : exportUrl;
121
+ return submitForm({
122
+ url,
123
+ values: compose(
124
+ addLocParams,
125
+ addOrgParams
126
+ )(values[this.state.syncType]),
127
+ message: `Templates were ${this.state.syncType}ed.`,
128
+ item: 'TemplateSync',
129
+ }).then(args => {
130
+ history.replace({ pathname: '/template_syncs/result' });
131
+ });
132
+ }}
133
+ initialValues={initialValues}
134
+ validationSchema={syncFormSchema(
135
+ this.state.syncType,
136
+ { import: importSettings, export: exportSettings },
137
+ validationData
138
+ )}
98
139
  onCancel={redirectToResult(history)}
99
- errorTitle={
100
- error && error.severity === 'danger' ? __('Error! ') : __('Warning! ')
101
- }
140
+ error={error}
102
141
  >
103
142
  <SyncTypeRadios
104
143
  name="syncType"
105
144
  controlLabel="Action type"
106
145
  radios={this.initRadioButtons(this.state.syncType)}
107
- disabled={submitting}
108
146
  />
109
147
  <SyncSettingsFields
110
148
  importSettings={importSettings}
111
149
  exportSettings={exportSettings}
112
150
  syncType={this.state.syncType}
113
151
  resetField={resetToDefault}
114
- disabled={submitting}
115
- validationData={validationData}
116
152
  />
117
- </Form>
153
+ </ForemanForm>
118
154
  );
119
155
  }
120
156
  }
@@ -123,13 +159,15 @@ NewTemplateSyncForm.propTypes = {
123
159
  importSettings: PropTypes.array,
124
160
  exportSettings: PropTypes.array,
125
161
  userPermissions: PropTypes.object.isRequired,
126
- submitting: PropTypes.bool,
127
162
  error: PropTypes.object,
128
- handleSubmit: PropTypes.func.isRequired,
129
- dispatch: PropTypes.func,
130
163
  history: PropTypes.object,
131
164
  validationData: PropTypes.object,
132
- valid: PropTypes.bool.isRequired,
165
+ initialValues: PropTypes.object.isRequired,
166
+ exportUrl: PropTypes.string.isRequired,
167
+ importUrl: PropTypes.string.isRequired,
168
+ submitForm: PropTypes.func.isRequired,
169
+ currentLocation: PropTypes.object.isRequired,
170
+ currentOrganization: PropTypes.object.isRequired,
133
171
  };
134
172
 
135
173
  NewTemplateSyncForm.defaultProps = {
@@ -137,8 +175,6 @@ NewTemplateSyncForm.defaultProps = {
137
175
  exportSettings: [],
138
176
  validationData: {},
139
177
  error: undefined,
140
- dispatch: () => {},
141
- submitting: false,
142
178
  history: {},
143
179
  };
144
180
 
@@ -5,20 +5,17 @@ import {
5
5
  selectExportSettings,
6
6
  } from '../../NewTemplateSyncSelectors';
7
7
 
8
+ export const transformInitialValues = settings =>
9
+ settings.reduce(
10
+ (memo, item) => Object.assign(memo, { [item.name]: item.value }),
11
+ {}
12
+ );
13
+
8
14
  export const selectInitialFormValues = createSelector(
9
15
  selectImportSettings,
10
16
  selectExportSettings,
11
- (importSettings, exportSettings) =>
12
- importSettings
13
- .concat(exportSettings)
14
- .reduce(
15
- (memo, item) => Object.assign(memo, { [item.name]: item.value }),
16
- {}
17
- )
17
+ (importSettings, exportSettings) => ({
18
+ import: transformInitialValues(importSettings),
19
+ export: transformInitialValues(exportSettings),
20
+ })
18
21
  );
19
-
20
- const selectFormState = (formName, state) =>
21
- state.form && state.form[formName] ? state.form[formName] : {};
22
-
23
- export const selectRegisteredFields = (formName, state) =>
24
- selectFormState(formName, state).registeredFields || {};
@@ -1,35 +1,17 @@
1
1
  import { testSelectorsSnapshotWithFixtures } from 'react-redux-test-utils';
2
2
 
3
- import { NEW_TEMPLATE_SYNC_FORM_NAME } from '../NewTemplateSyncFormConstants';
4
3
  import {
5
- registeredImportSettings,
6
- initialValues,
7
4
  stateFactory,
8
5
  importSettings,
9
6
  exportSettings,
10
7
  } from '../../../__fixtures__/templateSyncSettings.fixtures';
11
8
 
12
- import {
13
- selectInitialFormValues,
14
- selectRegisteredFields,
15
- } from '../NewTemplateSyncFormSelectors';
16
-
17
- const formStateFactory = obj => ({
18
- form: {
19
- [NEW_TEMPLATE_SYNC_FORM_NAME]: obj,
20
- },
21
- });
9
+ import { selectInitialFormValues } from '../NewTemplateSyncFormSelectors';
22
10
 
23
11
  const fixtures = {
24
- 'should return registered fields': () =>
25
- selectRegisteredFields(
26
- NEW_TEMPLATE_SYNC_FORM_NAME,
27
- formStateFactory(registeredImportSettings)
28
- ),
29
12
  'should return initial form values': () =>
30
13
  selectInitialFormValues({
31
14
  ...stateFactory({ importSettings, exportSettings }),
32
- ...formStateFactory(initialValues),
33
15
  }),
34
16
  };
35
17
 
@@ -2,43 +2,14 @@
2
2
 
3
3
  exports[`NewTemplateSyncFormSelectors should return initial form values 1`] = `
4
4
  Object {
5
- "associate": "new",
6
- "filter": "",
7
- "force": false,
8
- "negate": false,
9
- "repo": "https://github.com/theforeman/community-templates.git",
10
- }
11
- `;
12
-
13
- exports[`NewTemplateSyncFormSelectors should return registered fields 1`] = `
14
- Object {
15
- "associate": Object {
16
- "description": "Associate templates to OS, organization and location",
17
- "id": 45,
18
- "name": "associate",
19
- "selection": Array [
20
- Object {
21
- "label": "New",
22
- "value": "new",
23
- },
24
- Object {
25
- "label": "Never",
26
- "value": "never",
27
- },
28
- Object {
29
- "label": "Always",
30
- "value": "always",
31
- },
32
- ],
33
- "settingsType": "string",
34
- "value": "new",
5
+ "export": Object {
6
+ "filter": "",
7
+ "negate": false,
8
+ "repo": "https://github.com/theforeman/community-templates.git",
35
9
  },
36
- "force": Object {
37
- "description": "Should importing overwrite locked templates?",
38
- "id": 46,
39
- "name": "force",
40
- "settingsType": "bool",
41
- "value": false,
10
+ "import": Object {
11
+ "associate": "new",
12
+ "force": false,
42
13
  },
43
14
  }
44
15
  `;