foreman_templates 9.0.0 → 9.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/template_controller.rb +3 -3
  3. data/app/controllers/ui_template_syncs_controller.rb +9 -3
  4. data/app/services/foreman_templates/template_exporter.rb +1 -1
  5. data/app/services/foreman_templates/template_importer.rb +1 -1
  6. data/app/views/template_sync_settings/show.json.rabl +2 -2
  7. data/app/views/template_syncs/index.html.erb +14 -17
  8. data/app/views/ui_template_syncs/template_attrs.json.rabl +1 -1
  9. data/db/migrate/20211122154929_templates_settings_category_to_dsl.rb +5 -0
  10. data/lib/foreman_templates/engine.rb +76 -10
  11. data/lib/foreman_templates/version.rb +1 -1
  12. data/lib/foreman_templates.rb +15 -0
  13. data/lib/tasks/foreman_templates_tasks.rake +10 -9
  14. data/webpack/ForemanTemplates.js +17 -13
  15. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext.js +2 -0
  16. data/webpack/__mocks__/foremanReact/components/Pagination/index.js +2 -0
  17. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncForm.js +95 -140
  18. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncFormHelpers.js +43 -0
  19. data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/index.js +0 -4
  20. data/webpack/components/TemplateSyncResult/TemplateSyncResultHelpers.js +2 -2
  21. data/webpack/components/TemplateSyncResult/TemplateSyncResultReducer.js +1 -1
  22. data/webpack/components/TemplateSyncResult/__tests__/__snapshots__/TemplateSyncResultReducer.test.js.snap +2 -2
  23. data/webpack/components/TemplateSyncResult/components/SyncResultList.js +11 -6
  24. data/webpack/components/TemplateSyncResult/components/__tests__/SyncResultList.test.js +1 -1
  25. data/webpack/components/TemplateSyncResult/components/__tests__/__snapshots__/SyncResultList.test.js.snap +3 -9
  26. metadata +7 -5
  27. data/app/models/setting/template_sync.rb +0 -115
  28. data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20a8db347037219c9d8b4c09906b703e7c9370a1c5fd9775ce7d2261f82f690f
4
- data.tar.gz: 5bea2c74fb7d677dca8d203607ef51ade2de42e26b8e458c739e80240c3cd7bf
3
+ metadata.gz: bda8e472a4ae76b0f1480738e9059423a249c4156842deebc385baa2f3c2a5ca
4
+ data.tar.gz: bb04b38ce6791e62f90fcea5a177d38ca62402e6c3037e7f3ed85c416f91696c
5
5
  SHA512:
6
- metadata.gz: 40eab594f513b94c2deec58e35c4f03030379598db6a482bc201838b227bcfafd6f85e5c16eefab830da3d4950109b09d67a9a9032971a7059a1fcb87f3b0fee
7
- data.tar.gz: 4ca1502b153c698da0461087a1e8976ac1429def1d5532012639abf5724a6c010682161b0c1d8606f6d21eebd9df997dc0e1288abc4f3638f60edbd37124666b
6
+ metadata.gz: 3c35d3ca52df290a50a3e1788ea654a0588067ab02278a5b77829bfaec46d8c855ae36f554a608eaf8e9c5797b650d5be1a95ed4e7f8486c905e601068180fc5
7
+ data.tar.gz: b58a200b8374f22481e1397949cc4126315234b6238ce08895acedb628c826515ce9ab514b85942b8388350f0e8939b0d088ccc6641fc4b3b84a1fc65ef16961
@@ -17,9 +17,9 @@ module Api
17
17
 
18
18
  api :POST, "/templates/import/", N_("Initiate Import")
19
19
  param :prefix, String, :required => false, :desc => N_("The string all imported templates should begin with.")
20
- param :associate, Setting::TemplateSync.associate_types.keys, :required => false, :desc => N_("Associate to OS's, Locations & Organizations. Options are: always, new or never.")
20
+ param :associate, ForemanTemplates.associate_types.keys, :required => false, :desc => N_("Associate to OS's, Locations & Organizations. Options are: always, new or never.")
21
21
  param :force, :bool, :required => false, :desc => N_("Update templates that are locked")
22
- param :lock, Setting::TemplateSync.lock_types.keys + ["true", "false", "0", "1"], :required => false, :desc => N_("Lock imported templates")
22
+ param :lock, ForemanTemplates.lock_types.keys + ["true", "false", "0", "1"], :required => false, :desc => N_("Lock imported templates")
23
23
  param :verbose, :bool, :required => false, :desc => N_("Show template diff in response")
24
24
  param_group :foreman_template_sync_params
25
25
  param_group :taxonomies, ::Api::V2::BaseController
@@ -32,7 +32,7 @@ module Api
32
32
  end
33
33
 
34
34
  api :POST, "/templates/export", N_("Initiate Export")
35
- param :metadata_export_mode, Setting::TemplateSync.metadata_export_mode_types.keys, :required => false, :desc => N_("Specify how to handle metadata")
35
+ param :metadata_export_mode, ForemanTemplates.metadata_export_mode_types.keys, :required => false, :desc => N_("Specify how to handle metadata")
36
36
  param :commit_msg, String, :desc => N_("Custom commit message for templates export")
37
37
  param_group :foreman_template_sync_params
38
38
  param_group :taxonomies, ::Api::V2::BaseController
@@ -8,8 +8,8 @@ class UiTemplateSyncsController < ApplicationController
8
8
  end
9
9
 
10
10
  def sync_settings
11
- import_settings = Setting.where :name => Setting::TemplateSync.import_setting_names(['verbose'])
12
- export_settings = Setting.where :name => Setting::TemplateSync.export_setting_names(['verbose'])
11
+ import_settings = setting_definitions(ForemanTemplates::IMPORT_SETTING_NAMES)
12
+ export_settings = setting_definitions(ForemanTemplates::EXPORT_SETTING_NAMES)
13
13
  @results = OpenStruct.new(:import => import_settings, :export => export_settings)
14
14
  end
15
15
 
@@ -43,6 +43,12 @@ class UiTemplateSyncsController < ApplicationController
43
43
  end
44
44
 
45
45
  def render_errors(messages, severity = 'danger')
46
- render :json => { :error => { :errors => { :base => messages }, :severity => severity } }, :status => :unprocessable_entity
46
+ render :json => { :error => { :errors => { :base => messages }, full_messages: messages, :severity => severity } }, :status => :unprocessable_entity
47
+ end
48
+
49
+ private
50
+
51
+ def setting_definitions(short_names)
52
+ short_names.map { |name| Foreman.settings.find("template_sync_#{name}") }
47
53
  end
48
54
  end
@@ -94,7 +94,7 @@ module ForemanTemplates
94
94
  def templates_to_dump
95
95
  find_templates.each do |template|
96
96
  if filter.present?
97
- exportable = template.name =~ /#{filter}/i ? !negate : negate
97
+ exportable = /#{filter}/i.match?(template.name) ? !negate : negate
98
98
  result = ExportResult.new(template, exportable)
99
99
  next @result_lines << result.matching_filter unless exportable
100
100
 
@@ -70,7 +70,7 @@ module ForemanTemplates
70
70
  @result_lines << parse_result.check_for_errors
71
71
  rescue NameError => e
72
72
  @result_lines << parse_result.name_error(e, metadata['model'])
73
- rescue => e
73
+ rescue StandardError => e
74
74
  @result_lines << parse_result.add_exception(e)
75
75
  end
76
76
  end
@@ -1,11 +1,11 @@
1
1
  object @setting
2
2
 
3
3
  node do |setting|
4
- { :name => setting.short_name }
4
+ { :name => setting.name.delete_prefix('template_sync_') }
5
5
  end
6
6
 
7
7
  attributes :id, :value, :description, :settings_type, :default, :full_name
8
8
 
9
9
  node do |setting|
10
- { :selection => setting.selection }
10
+ { :selection => (setting.select_values || {}).map { |key, label| { value: key, label: label } } }
11
11
  end
@@ -5,20 +5,17 @@
5
5
  <%= webpacked_plugins_css_for :foreman_templates %>
6
6
  <% end %>
7
7
 
8
- <div id="foreman-templates"/>
9
-
10
- <%= mount_react_component('ForemanTemplates',
11
- '#foreman-templates',
12
- { :apiUrls => {
13
- :exportUrl => export_ui_template_syncs_path,
14
- :syncSettingsUrl => sync_settings_ui_template_syncs_path,
15
- :importUrl => import_ui_template_syncs_path
16
- },
17
- :validationData => { :repo => ForemanTemplates::Action.repo_start_with },
18
- :editPaths => edit_paths,
19
- :fileRepoStartWith => ForemanTemplates::Action.file_repo_start_with,
20
- :userPermissions => {
21
- :import => authorized_for(:controller => :ui_template_syncs, :action => :import),
22
- :export => authorized_for(:controller => :ui_template_syncs, :action => :export)
23
- }
24
- }.to_json )%>
8
+ <%= react_component('ForemanTemplates',
9
+ { :apiUrls => {
10
+ :exportUrl => export_ui_template_syncs_path,
11
+ :syncSettingsUrl => sync_settings_ui_template_syncs_path,
12
+ :importUrl => import_ui_template_syncs_path
13
+ },
14
+ :validationData => { :repo => ForemanTemplates::Action.repo_start_with },
15
+ :editPaths => edit_paths,
16
+ :fileRepoStartWith => ForemanTemplates::Action.file_repo_start_with,
17
+ :userPermissions => {
18
+ :import => authorized_for(:controller => :ui_template_syncs, :action => :import),
19
+ :export => authorized_for(:controller => :ui_template_syncs, :action => :export)
20
+ }
21
+ })%>
@@ -11,7 +11,7 @@ node(:class_name) do |template|
11
11
  end
12
12
 
13
13
  node(:humanized_class_name) do |template|
14
- template.class.name.underscore.split('_').map { |part| part.capitalize }.join(' ')
14
+ template.class.name.underscore.split('_').map(&:capitalize).join(' ')
15
15
  end
16
16
 
17
17
  node(:can_edit) do |template|
@@ -0,0 +1,5 @@
1
+ class TemplatesSettingsCategoryToDsl < ActiveRecord::Migration[6.0]
2
+ def up
3
+ Setting.where(category: 'Setting::TemplateSync').update_all(category: 'Setting')
4
+ end
5
+ end
@@ -10,10 +10,6 @@ module ForemanTemplates
10
10
  class Engine < ::Rails::Engine
11
11
  engine_name 'foreman_templates'
12
12
 
13
- initializer 'foreman_templates.load_default_settings', :before => :load_config_initializers do
14
- require_dependency File.expand_path('../../app/models/setting/template_sync.rb', __dir__) if (Setting.table_exists? rescue(false))
15
- end
16
-
17
13
  initializer "foreman_templates.add_rabl_view_path" do
18
14
  Rabl.configure do |config|
19
15
  config.view_paths << ForemanTemplates::Engine.root.join('app', 'views')
@@ -28,10 +24,78 @@ module ForemanTemplates
28
24
 
29
25
  initializer 'foreman_templates.register_plugin', :before => :finisher_hook do
30
26
  Foreman::Plugin.register :foreman_templates do
31
- requires_foreman '>= 1.24'
27
+ requires_foreman '>= 3.2'
32
28
 
33
29
  apipie_documented_controllers ["#{ForemanTemplates::Engine.root}/app/controllers/api/v2/*.rb"]
34
30
 
31
+ settings do
32
+ category(:template_sync, N_('Template Sync')) do
33
+ setting('template_sync_verbose',
34
+ type: :boolean,
35
+ description: N_('Choose verbosity for Rake task importing templates'),
36
+ default: false,
37
+ full_name: N_('Verbosity'))
38
+ setting('template_sync_associate',
39
+ type: :string,
40
+ description: N_('Associate templates to OS, organization and location'),
41
+ default: 'new',
42
+ full_name: N_('Associate'),
43
+ collection: -> { ForemanTemplates.associate_types })
44
+ setting('template_sync_prefix',
45
+ type: :string,
46
+ description: N_('The string that will be added as prefix to imported templates'),
47
+ default: "",
48
+ full_name: N_('Prefix'))
49
+ setting('template_sync_dirname',
50
+ type: :string,
51
+ description: N_('The directory within the Git repo containing the templates'),
52
+ default: '/',
53
+ full_name: N_('Dirname'))
54
+ setting('template_sync_filter',
55
+ type: :string,
56
+ description: N_('Import/export names matching this regex (case-insensitive; snippets are not filtered)'),
57
+ default: '',
58
+ full_name: N_('Filter'))
59
+ setting('template_sync_repo',
60
+ type: :string,
61
+ description: N_('Target path to import/export. Different protocols can be used, for example /tmp/dir, git://example.com, https://example.com, ssh://example.com. When exporting to /tmp, note that production deployments may be configured to use private tmp.'),
62
+ default: 'https://github.com/theforeman/community-templates.git',
63
+ full_name: N_('Repo'))
64
+ setting('template_sync_negate',
65
+ type: :boolean,
66
+ description: N_('Negate the filter for import/export'),
67
+ default: false,
68
+ full_name: N_('Negate'))
69
+ setting('template_sync_branch',
70
+ type: :string,
71
+ description: N_('Default branch in Git repo'),
72
+ default: '',
73
+ full_name: N_('Branch'))
74
+ setting('template_sync_metadata_export_mode',
75
+ type: :string,
76
+ description: N_('Default metadata export mode, refresh re-renders metadata, keep will keep existing metadata, remove exports template without metadata'),
77
+ default: 'refresh',
78
+ full_name: N_('Metadata export mode'),
79
+ collection: -> { ForemanTemplates.metadata_export_mode_types })
80
+ setting('template_sync_force',
81
+ type: :boolean,
82
+ description: N_('Should importing overwrite locked templates?'),
83
+ default: false,
84
+ full_name: N_('Force import'))
85
+ setting('template_sync_lock',
86
+ type: :string,
87
+ description: N_('How to handle lock for imported templates?'),
88
+ default: 'keep',
89
+ full_name: N_('Lock templates'),
90
+ collection: -> { ForemanTemplates.lock_types })
91
+ setting('template_sync_commit_msg',
92
+ type: :string,
93
+ description: N_('Custom commit message for templates export'),
94
+ default: 'Templates export made by a Foreman user',
95
+ full_name: N_('Commit message'))
96
+ end
97
+ end
98
+
35
99
  security_block :templates do
36
100
  permission :import_templates, {
37
101
  :"api/v2/template" => [:import],
@@ -49,15 +113,17 @@ module ForemanTemplates
49
113
  add_all_permissions_to_default_roles
50
114
 
51
115
  menu :top_menu, :template_sync,
52
- :url_hash => { :controller => :template_syncs, :action => :index },
53
- :caption => N_('Sync Templates'),
54
- :parent => :hosts_menu,
55
- :before => :ptables,
56
- :turbolinks => false
116
+ :url_hash => { :controller => :template_syncs, :action => :index },
117
+ :caption => N_('Sync Templates'),
118
+ :parent => :hosts_menu,
119
+ :before => :ptables,
120
+ :turbolinks => false
57
121
  end
58
122
  end
59
123
 
60
124
  config.to_prepare do
125
+ Setting::NOT_STRIPPED << 'template_sync_prefix'
126
+
61
127
  Template.include ForemanTemplates::TemplateExtensions
62
128
  end
63
129
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanTemplates
2
- VERSION = '9.0.0'.freeze
2
+ VERSION = '9.2.0'.freeze
3
3
  end
@@ -1,4 +1,19 @@
1
1
  require 'foreman_templates/engine'
2
2
 
3
3
  module ForemanTemplates
4
+ BASE_SETTING_NAMES = %w(repo branch dirname filter negate).freeze
5
+ IMPORT_SETTING_NAMES = (BASE_SETTING_NAMES | %w(prefix associate force lock)).freeze
6
+ EXPORT_SETTING_NAMES = (BASE_SETTING_NAMES | %w(metadata_export_mode commit_msg)).freeze
7
+
8
+ def self.associate_types
9
+ { 'always' => _('Always'), 'new' => _('New'), 'never' => _('Never') }
10
+ end
11
+
12
+ def self.lock_types
13
+ { 'lock' => _('Lock'), 'keep_lock_new' => _('Keep, lock new'), 'keep' => _('Keep, do not lock new'), 'unlock' => _('Unlock') }
14
+ end
15
+
16
+ def self.metadata_export_mode_types
17
+ { 'refresh' => _('Refresh'), 'keep' => _('Keep'), 'remove' => _('Remove') }
18
+ end
4
19
  end
@@ -20,14 +20,14 @@ namespace :templates do
20
20
  verbose = ENV['verbose']
21
21
 
22
22
  results = ForemanTemplates::TemplateImporter.new({
23
- verbose: verbose,
24
- repo: ENV['repo'],
25
- branch: ENV['branch'],
26
- prefix: ENV['prefix'],
27
- dirname: ENV['dirname'],
28
- filter: ENV['filter'],
23
+ verbose: verbose,
24
+ repo: ENV['repo'],
25
+ branch: ENV['branch'],
26
+ prefix: ENV['prefix'],
27
+ dirname: ENV['dirname'],
28
+ filter: ENV['filter'],
29
29
  associate: ENV['associate'],
30
- lock: ENV['lock'],
30
+ lock: ENV['lock'],
31
31
  }).import!
32
32
  pp(results[:results].map { |result| result.to_h(verbose) })
33
33
  end
@@ -58,8 +58,8 @@ namespace :templates do
58
58
  # * negate => negate query [false]
59
59
  # * prefix => The string all templates to purge should ( or not ) begin with [Community ]
60
60
  # * verbose => Print extra information during the run [false]
61
- negate: ENV['negate'],
62
- prefix: ENV['prefix'],
61
+ negate: ENV['negate'],
62
+ prefix: ENV['prefix'],
63
63
  verbose: ENV['verbose'],
64
64
  }).purge!
65
65
  end
@@ -89,6 +89,7 @@ namespace :foreman_templates do
89
89
  begin
90
90
  require 'rubocop/rake_task'
91
91
  RuboCop::RakeTask.new(:rubocop_foreman_templates) do |task|
92
+ task.options = ['--config', ForemanTemplates::Engine.root.join('.rubocop.yml').to_s]
92
93
  task.patterns = ["#{ForemanTemplates::Engine.root}/app/**/*.rb",
93
94
  "#{ForemanTemplates::Engine.root}/lib/**/*.rb",
94
95
  "#{ForemanTemplates::Engine.root}/test/**/*.rb"]
@@ -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,2 @@
1
+ export const useForemanOrganization = () => {};
2
+ export const useForemanLocation = () => {};
@@ -0,0 +1,2 @@
1
+ const Pagination = () => jest.fn();
2
+ export default Pagination;
@@ -1,159 +1,116 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { compose } from 'redux';
4
4
 
5
5
  import ForemanForm from 'foremanReact/components/common/forms/ForemanForm';
6
- import * as Yup from 'yup';
6
+ import {
7
+ useForemanLocation,
8
+ useForemanOrganization,
9
+ } from 'foremanReact/Root/Context/ForemanContext';
10
+
7
11
  import SyncSettingsFields from '../SyncSettingFields';
8
12
  import SyncTypeRadios from '../SyncTypeRadios';
9
-
10
- const redirectToResult = history => () =>
11
- history.push({ pathname: '/template_syncs/result' });
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
- {}
13
+ import { redirectToResult, syncFormSchema } from './NewTemplateSyncFormHelpers';
14
+
15
+ const NewTemplateSyncForm = ({
16
+ error,
17
+ submitForm,
18
+ importSettings,
19
+ exportSettings,
20
+ history,
21
+ validationData,
22
+ importUrl,
23
+ exportUrl,
24
+ initialValues,
25
+ userPermissions,
26
+ }) => {
27
+ const allowedSyncType = (currentUserPermissions, radioAttrs) =>
28
+ currentUserPermissions[radioAttrs.permission];
29
+
30
+ const radioButtons = [
31
+ { label: 'Import', value: 'import', permission: 'import' },
32
+ { label: 'Export', value: 'export', permission: 'export' },
33
+ ];
34
+
35
+ const [syncType, setSyncType] = useState(
36
+ radioButtons.find(radioAttrs =>
37
+ allowedSyncType(userPermissions, radioAttrs)
38
+ ).value
45
39
  );
46
40
 
47
- return Yup.object().shape({
48
- [syncType]: Yup.object().shape(schema),
49
- });
50
- };
51
-
52
- class NewTemplateSyncForm extends React.Component {
53
- allowedSyncType = (userPermissions, radioAttrs) =>
54
- this.props.userPermissions[radioAttrs.permission];
55
-
56
- constructor(props) {
57
- super(props);
58
-
59
- this.radioButtons = [
60
- { label: 'Import', value: 'import', permission: 'import' },
61
- { label: 'Export', value: 'export', permission: 'export' },
62
- ];
63
-
64
- this.state = {
65
- syncType: this.radioButtons.find(radioAttrs =>
66
- this.allowedSyncType(props.userPermissions, radioAttrs)
67
- ).value,
68
- };
69
- }
70
-
71
- updateSyncType = event => {
72
- this.setState({ syncType: event.target.value });
41
+ const updateSyncType = event => {
42
+ setSyncType(event.target.value);
73
43
  };
74
44
 
75
- permitRadioButtons = buttons =>
45
+ const permitRadioButtons = buttons =>
76
46
  buttons.filter(buttonAttrs =>
77
- this.allowedSyncType(this.props.userPermissions, buttonAttrs)
47
+ allowedSyncType(userPermissions, buttonAttrs)
78
48
  );
79
49
 
80
- initRadioButtons = syncType =>
81
- this.permitRadioButtons(this.radioButtons).map(buttonAttrs => ({
50
+ const initRadioButtons = templateSyncType =>
51
+ permitRadioButtons(radioButtons).map(buttonAttrs => ({
82
52
  get checked() {
83
- return this.value === syncType;
53
+ return buttonAttrs.value === templateSyncType;
84
54
  },
85
- onChange: this.updateSyncType,
55
+ onChange: updateSyncType,
86
56
  ...buttonAttrs,
87
57
  }));
88
58
 
89
- render() {
90
- const {
91
- error,
92
- submitForm,
93
- importSettings,
94
- exportSettings,
95
- history,
96
- validationData,
97
- importUrl,
98
- exportUrl,
99
- initialValues,
100
- currentLocation,
101
- currentOrganization,
102
- } = this.props;
103
-
104
- const addTaxParams = (key, currentTax) => params => {
105
- if (currentTax.id) {
106
- 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);
116
-
117
- return (
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
- )}
139
- onCancel={redirectToResult(history)}
140
- error={error}
141
- >
142
- <SyncTypeRadios
143
- name="syncType"
144
- controlLabel="Action type"
145
- radios={this.initRadioButtons(this.state.syncType)}
146
- />
147
- <SyncSettingsFields
148
- importSettings={importSettings}
149
- exportSettings={exportSettings}
150
- syncType={this.state.syncType}
151
- resetField={resetToDefault}
152
- />
153
- </ForemanForm>
154
- );
155
- }
156
- }
59
+ const addTaxParams = (key, currentTax) => params => {
60
+ if (currentTax && currentTax.id) {
61
+ return { ...params, [key]: [currentTax.id] };
62
+ }
63
+ return params;
64
+ };
65
+
66
+ const addOrgParams = addTaxParams(
67
+ 'organization_ids',
68
+ useForemanOrganization()
69
+ );
70
+ const addLocParams = addTaxParams('location_ids', useForemanLocation());
71
+
72
+ const resetToDefault = (fieldName, fieldValue) => resetFn =>
73
+ resetFn(fieldName, fieldValue);
74
+
75
+ const handleSubmit = (values, actions) => {
76
+ const url = syncType === 'import' ? importUrl : exportUrl;
77
+ return submitForm({
78
+ url,
79
+ values: compose(addLocParams, addOrgParams)(values[syncType]),
80
+ message: `Templates were ${syncType}ed.`,
81
+ item: 'TemplateSync',
82
+ actions,
83
+ successCallback: () =>
84
+ history.replace({ pathname: '/template_syncs/result' }),
85
+ });
86
+ };
87
+
88
+ return (
89
+ <ForemanForm
90
+ onSubmit={handleSubmit}
91
+ initialValues={initialValues}
92
+ validationSchema={syncFormSchema(
93
+ syncType,
94
+ { import: importSettings, export: exportSettings },
95
+ validationData
96
+ )}
97
+ onCancel={redirectToResult(history)}
98
+ error={error}
99
+ >
100
+ <SyncTypeRadios
101
+ name="syncType"
102
+ controlLabel="Action type"
103
+ radios={initRadioButtons(syncType)}
104
+ />
105
+ <SyncSettingsFields
106
+ importSettings={importSettings}
107
+ exportSettings={exportSettings}
108
+ syncType={syncType}
109
+ resetField={resetToDefault}
110
+ />
111
+ </ForemanForm>
112
+ );
113
+ };
157
114
 
158
115
  NewTemplateSyncForm.propTypes = {
159
116
  importSettings: PropTypes.array,
@@ -166,8 +123,6 @@ NewTemplateSyncForm.propTypes = {
166
123
  exportUrl: PropTypes.string.isRequired,
167
124
  importUrl: PropTypes.string.isRequired,
168
125
  submitForm: PropTypes.func.isRequired,
169
- currentLocation: PropTypes.object.isRequired,
170
- currentOrganization: PropTypes.object.isRequired,
171
126
  };
172
127
 
173
128
  NewTemplateSyncForm.defaultProps = {
@@ -0,0 +1,43 @@
1
+ import * as Yup from 'yup';
2
+
3
+ export const redirectToResult = history => () =>
4
+ history.push({ pathname: '/template_syncs/result' });
5
+
6
+ const repoFormat = formatAry => value => {
7
+ if (value === undefined) {
8
+ return true;
9
+ }
10
+
11
+ const valid = formatAry
12
+ .map(item => value.startsWith(item))
13
+ .reduce((memo, item) => item || memo, false);
14
+
15
+ return value && valid;
16
+ };
17
+
18
+ export const syncFormSchema = (syncType, settingsObj, validationData) => {
19
+ const schema = (settingsObj[syncType].asMutable() || []).reduce(
20
+ (memo, setting) => {
21
+ if (setting.name === 'repo') {
22
+ return {
23
+ ...memo,
24
+ repo: Yup.string()
25
+ .test(
26
+ 'repo-format',
27
+ `Invalid repo format, must start with one of: ${validationData.repo.join(
28
+ ', '
29
+ )}`,
30
+ repoFormat(validationData.repo)
31
+ )
32
+ .required("can't be blank"),
33
+ };
34
+ }
35
+ return memo;
36
+ },
37
+ {}
38
+ );
39
+
40
+ return Yup.object().shape({
41
+ [syncType]: Yup.object().shape(schema),
42
+ });
43
+ };
@@ -2,8 +2,6 @@ import { connect } from 'react-redux';
2
2
 
3
3
  import * as FormActions from 'foremanReact/redux/actions/common/forms';
4
4
 
5
- import { selectLayout } from 'foremanReact/components/Layout/LayoutSelectors';
6
-
7
5
  import NewTemplateSyncForm from './NewTemplateSyncForm';
8
6
 
9
7
  import {
@@ -24,8 +22,6 @@ const mapStateToProps = (state, ownProps) => {
24
22
  initialValues: { ...initialFormValues },
25
23
  importSettings,
26
24
  exportSettings,
27
- currentOrganization: selectLayout(state).currentOrganization,
28
- currentLocation: selectLayout(state).currentLocation,
29
25
  };
30
26
  };
31
27
 
@@ -1,6 +1,6 @@
1
1
  // move to some sort of pagination helper in core
2
2
  export const templatesPage = (templates, pagination) => {
3
- const offset = (pagination.page - 1) * pagination.perPage;
3
+ const offset = (pagination.page - 1) * pagination.per_page;
4
4
 
5
- return templates.slice(offset, offset + pagination.perPage);
5
+ return templates.slice(offset, offset + pagination.per_page);
6
6
  };
@@ -12,7 +12,7 @@ export const initialState = Immutable({
12
12
 
13
13
  pagination: {
14
14
  page: 1,
15
- perPage: 20,
15
+ per_page: 20,
16
16
  },
17
17
  });
18
18
 
@@ -4,7 +4,7 @@ exports[`TemplateSyncResultReducer should return initial state 1`] = `
4
4
  Object {
5
5
  "pagination": Object {
6
6
  "page": 1,
7
- "perPage": 20,
7
+ "per_page": 20,
8
8
  },
9
9
  "resultAction": "",
10
10
  "templates": Array [],
@@ -27,7 +27,7 @@ Object {
27
27
  "branch": "master",
28
28
  "pagination": Object {
29
29
  "page": 1,
30
- "perPage": 20,
30
+ "per_page": 20,
31
31
  },
32
32
  "repo": "https://github.com/theforeman/community-templates.git",
33
33
  "resultAction": "import",
@@ -2,13 +2,19 @@ import React from 'react';
2
2
  import { ListView } from 'patternfly-react';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
- import Pagination from 'foremanReact/components/Pagination/PaginationWrapper';
5
+ import Pagination from 'foremanReact/components/Pagination';
6
6
 
7
7
  import SyncedTemplate from './SyncedTemplate';
8
8
  import { templatesPage } from '../TemplateSyncResultHelpers';
9
9
  import ListViewHeader from './ListViewHeader';
10
10
 
11
- const SyncResultList = ({ pagination, pageChange, templates, editPaths }) => (
11
+ const SyncResultList = ({
12
+ pagination,
13
+ pagination: { page, per_page: perPage },
14
+ pageChange,
15
+ templates,
16
+ editPaths,
17
+ }) => (
12
18
  <ListView>
13
19
  <ListViewHeader />
14
20
  {templatesPage(templates, pagination).map((template, idx) => (
@@ -19,11 +25,10 @@ const SyncResultList = ({ pagination, pageChange, templates, editPaths }) => (
19
25
  />
20
26
  ))}
21
27
  <Pagination
22
- viewType="list"
23
28
  itemCount={templates.length}
24
- pagination={pagination}
25
29
  onChange={pageChange}
26
- dropdownButtonId="template-sync-result-dropdown"
30
+ page={page}
31
+ perPage={perPage}
27
32
  />
28
33
  </ListView>
29
34
  );
@@ -31,7 +36,7 @@ const SyncResultList = ({ pagination, pageChange, templates, editPaths }) => (
31
36
  SyncResultList.propTypes = {
32
37
  pagination: PropTypes.shape({
33
38
  page: PropTypes.number,
34
- perPage: PropTypes.number,
39
+ per_page: PropTypes.number,
35
40
  }).isRequired,
36
41
  pageChange: PropTypes.func.isRequired,
37
42
  templates: PropTypes.array.isRequired,
@@ -18,7 +18,7 @@ const fixtures = {
18
18
  editPaths,
19
19
  pagination: {
20
20
  page: 1,
21
- perPage: 20,
21
+ per_page: 20,
22
22
  },
23
23
  },
24
24
  };
@@ -88,17 +88,11 @@ exports[`SyncResultList should render 1`] = `
88
88
  }
89
89
  }
90
90
  />
91
- <PaginationWrapper
92
- dropdownButtonId="template-sync-result-dropdown"
91
+ <Pagination
93
92
  itemCount={5}
94
93
  onChange={[Function]}
95
- pagination={
96
- Object {
97
- "page": 1,
98
- "perPage": 20,
99
- }
100
- }
101
- viewType="list"
94
+ page={1}
95
+ perPage={20}
102
96
  />
103
97
  </ListView>
104
98
  `;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_templates
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.0.0
4
+ version: 9.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Sutcliffe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-18 00:00:00.000000000 Z
11
+ date: 2022-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diffy
@@ -69,7 +69,6 @@ files:
69
69
  - app/controllers/ui_template_syncs_controller.rb
70
70
  - app/helpers/foreman_templates_helper.rb
71
71
  - app/models/concerns/foreman_templates/template_extensions.rb
72
- - app/models/setting/template_sync.rb
73
72
  - app/services/foreman_templates/action.rb
74
73
  - app/services/foreman_templates/cleaner.rb
75
74
  - app/services/foreman_templates/export_result.rb
@@ -89,6 +88,7 @@ files:
89
88
  - app/views/ui_template_syncs/template_import_results.json.rabl
90
89
  - config/routes.rb
91
90
  - db/migrate/20180627134929_change_lock_setting.rb
91
+ - db/migrate/20211122154929_templates_settings_category_to_dsl.rb
92
92
  - lib/foreman_templates.rb
93
93
  - lib/foreman_templates/engine.rb
94
94
  - lib/foreman_templates/version.rb
@@ -96,9 +96,10 @@ files:
96
96
  - package.json
97
97
  - webpack/ForemanTemplates.js
98
98
  - webpack/Routes.js
99
+ - webpack/__mocks__/foremanReact/Root/Context/ForemanContext.js
99
100
  - webpack/__mocks__/foremanReact/common/helpers.js
100
101
  - webpack/__mocks__/foremanReact/components/Layout/LayoutSelectors.js
101
- - webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js
102
+ - webpack/__mocks__/foremanReact/components/Pagination/index.js
102
103
  - webpack/__mocks__/foremanReact/components/common/forms/CommonForm.js
103
104
  - webpack/__mocks__/foremanReact/components/common/forms/ForemanForm.js
104
105
  - webpack/__mocks__/foremanReact/components/common/forms/TextField.js
@@ -120,6 +121,7 @@ files:
120
121
  - webpack/components/NewTemplateSync/__tests__/__snapshots__/NewTemplateSyncSelectors.test.js.snap
121
122
  - webpack/components/NewTemplateSync/components/ButtonTooltip.js
122
123
  - webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncForm.js
124
+ - webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncFormHelpers.js
123
125
  - webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncFormSelectors.js
124
126
  - webpack/components/NewTemplateSync/components/NewTemplateSyncForm/__tests__/NewTemplateSyncFormSelectors.test.js
125
127
  - webpack/components/NewTemplateSync/components/NewTemplateSyncForm/__tests__/__snapshots__/NewTemplateSyncFormSelectors.test.js.snap
@@ -198,7 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
200
  - !ruby/object:Gem::Version
199
201
  version: '0'
200
202
  requirements: []
201
- rubygems_version: 3.0.8
203
+ rubygems_version: 3.1.2
202
204
  signing_key:
203
205
  specification_version: 4
204
206
  summary: Template-syncing engine for Foreman
@@ -1,115 +0,0 @@
1
- class Setting
2
- class TemplateSync < ::Setting
3
- self.include_root_in_json = false
4
-
5
- def self.common_stripped_names
6
- %w(verbose repo branch dirname filter negate)
7
- end
8
-
9
- def self.import_stripped_names
10
- %w(prefix associate force lock)
11
- end
12
-
13
- def self.export_stripped_names
14
- %w(metadata_export_mode commit_msg)
15
- end
16
-
17
- def self.import_setting_names(except = [])
18
- map_prefix omit_settings(common_stripped_names + import_stripped_names, except)
19
- end
20
-
21
- def self.export_setting_names(except = [])
22
- map_prefix omit_settings(common_stripped_names + export_stripped_names, except)
23
- end
24
-
25
- def self.map_prefix(stripped_names)
26
- stripped_names.map { |item| "template_sync_#{item}" }
27
- end
28
-
29
- def self.omit_settings(setting_names, except_array)
30
- setting_names.reject { |name| except_array.include? name }
31
- end
32
-
33
- def self.associate_types
34
- {
35
- 'always' => _('Always'),
36
- 'new' => _('New'),
37
- 'never' => _('Never')
38
- }
39
- end
40
-
41
- def self.lock_types
42
- {
43
- 'lock' => _('Lock'),
44
- 'keep_lock_new' => _('Keep, lock new'),
45
- 'keep' => _('Keep, do not lock new'),
46
- 'unlock' => _('Unlock')
47
- }
48
- end
49
-
50
- def self.metadata_export_mode_types
51
- {
52
- 'refresh' => _('Refresh'),
53
- 'keep' => _('Keep'),
54
- 'remove' => _('Remove')
55
- }
56
- end
57
-
58
- def short_name
59
- name.split('template_sync_').last
60
- end
61
-
62
- def selection
63
- selection_method = name.split('template_sync_').last.concat('_types')
64
- return transformed_selection(selection_method) if self.class.respond_to?(selection_method)
65
-
66
- []
67
- end
68
-
69
- def self.load_defaults
70
- return unless super
71
-
72
- %w(template_sync_filter template_sync_branch template_sync_prefix).each { |s| Setting::BLANK_ATTRS << s }
73
- Setting::NOT_STRIPPED << 'template_sync_prefix'
74
-
75
- self.transaction do
76
- [
77
- self.set('template_sync_verbose', N_('Choose verbosity for Rake task importing templates'), false, N_('Verbosity')),
78
- self.set('template_sync_associate', N_('Associate templates to OS, organization and location'), 'new', N_('Associate'), nil, { :collection => Proc.new { self.associate_types } }),
79
- self.set('template_sync_prefix', N_('The string that will be added as prefix to imported templates'), "", N_('Prefix')),
80
- self.set('template_sync_dirname', N_('The directory within the Git repo containing the templates'), '/', N_('Dirname')),
81
- self.set('template_sync_filter', N_('Import/export names matching this regex (case-insensitive; snippets are not filtered)'), '', N_('Filter')),
82
- self.set('template_sync_repo', N_('Target path to import/export. Different protocols can be used, for example /tmp/dir, git://example.com, https://example.com, ssh://example.com. When exporting to /tmp, note that production deployments may be configured to use private tmp.'), 'https://github.com/theforeman/community-templates.git', N_('Repo')),
83
- self.set('template_sync_negate', N_('Negate the filter for import/export'), false, N_('Negate')),
84
- self.set('template_sync_branch', N_('Default branch in Git repo'), '', N_('Branch')),
85
- self.set('template_sync_metadata_export_mode', N_('Default metadata export mode, refresh re-renders metadata, keep will keep existing metadata, remove exports template without metadata'), 'refresh', N_('Metadata export mode'), nil, { :collection => Proc.new { self.metadata_export_mode_types } }),
86
- self.set('template_sync_force', N_('Should importing overwrite locked templates?'), false, N_('Force import')),
87
- self.set('template_sync_lock', N_('How to handle lock for imported templates?'), 'keep', N_('Lock templates'), nil, { :collection => Proc.new { self.lock_types } }),
88
- self.set('template_sync_commit_msg', N_('Custom commit message for templates export'), 'Templates export made by a Foreman user', N_('Commit message'))
89
- ].compact.each { |s| self.create! s.update(:category => "Setting::TemplateSync") }
90
- end
91
-
92
- true
93
- end
94
-
95
- def validate_template_sync_associate(record)
96
- values = record.class.associate_types.keys
97
- if record.value && !values.include?(record.value)
98
- record.errors[:base] << (_("template_sync_associate must be one of %s") % values.join(', '))
99
- end
100
- end
101
-
102
- def validate_template_sync_metadata_export_mode(record)
103
- values = record.class.metadata_export_mode_types.keys
104
- if record.value && !values.include?(record.value)
105
- record.errors[:base] << (_("template_sync_metadata_export_mode must be one of %s") % values.join(', '))
106
- end
107
- end
108
-
109
- private
110
-
111
- def transformed_selection(selection_method)
112
- self.class.public_send(selection_method).map { |key, translated| { :value => key, :label => translated } }
113
- end
114
- end
115
- end
@@ -1,2 +0,0 @@
1
- const PaginationWrapper = () => jest.fn();
2
- export default PaginationWrapper;