diff --git a/app/assets/javascripts/miq_tree.js b/app/assets/javascripts/miq_tree.js
index 5cd98122e5d..ec4653c3fed 100644
--- a/app/assets/javascripts/miq_tree.js
+++ b/app/assets/javascripts/miq_tree.js
@@ -120,25 +120,6 @@ function miqExpandParentNodes(treename, selected_node) {
}
}
-// OnCheck handler for the tags trees
-function miqOnCheckProvTags(node, treename) {
- var tree = miqTreeObject(treename);
- // Allow only one node among siblings to be checked
- if (node.state.checked) {
- var siblings = $.grep(tree.getParents(node)[0].nodes, function(item) {
- return item.key !== node.key;
- });
- tree.uncheckNode(siblings, {silent: true });
- }
-
- var all_checked = tree.getChecked().map(function(item) {
- return encodeURIComponent(item.key);
- });
-
- miqJqueryRequest(ManageIQ.tree.checkUrl + '?ids_checked=' + all_checked);
- return true;
-}
-
function miqOnClickSelectRbacTreeNode(id) {
var tree = 'rbac_tree';
miqTreeExpandNode(tree, 'xx-' + id.split('-')[0]);
@@ -433,7 +414,6 @@ function miqTreeEventSafeEval(func) {
'miqOnCheckGeneric',
'miqOnClickMenuRoles',
'miqOnCheckProtect',
- 'miqOnCheckProvTags',
'miqOnCheckSections',
'miqOnCheckUserFilters',
'miqOnClickAutomate',
diff --git a/app/controllers/application_controller/miq_request_methods.rb b/app/controllers/application_controller/miq_request_methods.rb
index 21cc3fef383..5ef5f292ab9 100644
--- a/app/controllers/application_controller/miq_request_methods.rb
+++ b/app/controllers/application_controller/miq_request_methods.rb
@@ -651,7 +651,7 @@ def prov_req_submit
# Get variables from provisioning form
def prov_get_form_vars
if params[:ids_checked] # User checked/unchecked a tree node
- ids = params[:ids_checked].split(",")
+ ids = params[:ids_checked]
# for some reason if tree is not expanded clicking on radiobuttons this.getAllChecked() sends up extra blanks
@edit.store_path(:new, tag_symbol_for_workflow, ids.select(&:present?).collect(&:to_i))
end
@@ -984,69 +984,36 @@ def workflow_instance_from_vars(req)
end
def build_tags_tree(wf, vm_tags, edit_mode)
- tags = wf.send("allowed_tags")
- @curr_tag = nil
- # Build the default filters tree for the search views
- all_tags = [] # Array to hold all CIs
- kids_checked = false
- tags.each_with_index do |t, i| # Go thru all of the Searches
- if @curr_tag.blank? || @curr_tag != t[:name]
- if @curr_tag != t[:name] && @ci_node
- @ci_node[:expand] = true if kids_checked
- kids_checked = false
- @ci_node[:children] = @ci_kids if @ci_kids.present?
- all_tags.push(@ci_node) if @ci_kids.present?
- end
- @curr_tag = t[:name]
- @ci_node = {} # Build the ci node
- @ci_node[:key] = t[:id].to_s
- @ci_node[:title] = t[:description]
- @ci_node[:title] += " *" if t[:single_value]
- @ci_node[:tooltip] = t[:description]
- @ci_node[:addClass] = "cfme-no-cursor-node" # No cursor pointer
- @ci_node[:icon] = 'pficon pficon-folder-close'
- @ci_node[:hideCheckbox] = @ci_node[:cfmeNoClick] = true
- @ci_node[:addClass] = "cfme-bold-node" # Show node as different
- @ci_kids = []
- end
- if @curr_tag.present? && @curr_tag == t[:name]
- t[:children].each do |c|
- temp = {}
- temp[:key] = c[0].to_s
- # only add cfme_parent_key for single value tags, need to use in JS onclick handler
- temp[:selectable] = false
- temp[:title] = temp[:tooltip] = c[1][:description]
- temp[:addClass] = "cfme-no-cursor-node"
- temp[:icon] = 'fa fa-tag'
- if edit_mode # Don't show checkboxes/radio buttons in non-edit mode
- if vm_tags && vm_tags.include?(c[0].to_i)
- temp[:select] = true
- kids_checked = true
- else
- temp[:select] = false
- end
- if @edit && @edit[:current][tag_symbol_for_workflow] != @edit[:new][tag_symbol_for_workflow]
- # checking to see if id is in current but not in new, change them to blue OR if id is in current but deleted from new
- if (!@edit[:current][tag_symbol_for_workflow].include?(c[0].to_i) && @edit[:new][tag_symbol_for_workflow].include?(c[0].to_i)) ||
- (!@edit[:new][tag_symbol_for_workflow].include?(c[0].to_i) && @edit[:current][tag_symbol_for_workflow].include?(c[0].to_i))
- temp[:addClass] = "cfme-blue-bold-node"
- end
- end
- @ci_kids.push(temp) unless @ci_kids.include?(temp)
- else
- temp[:hideCheckbox] = true
- @ci_kids.push(temp) unless @ci_kids.include?(temp) || !vm_tags.include?(c[0].to_i)
- end
- end
- end
- if i == tags.length - 1 # Adding last node
- @ci_node[:expand] = true if kids_checked
- kids_checked = false
- @ci_node[:children] = @ci_kids if @ci_kids.present?
- all_tags.push(@ci_node) if @ci_kids.present?
- end
+ # for some reason @tags is set in wf, and it is changed by map bellow which causes bugs
+ wf.instance_variable_set(:@tags, nil)
+ tags = wf.allowed_tags.map do |cat|
+ {
+ :values => cat[:children].map do |tag|
+ {:id => tag.first, :description => tag.second[:description]}
+ end,
+ :id => cat[:name],
+ :description => cat[:description],
+ :singleValue => cat[:single_value],
+ }
end
- @all_tags_tree = TreeBuilder.convert_bs_tree(all_tags).to_json # Add ci node array to root of tree
+
+ assignments = Classification.find(vm_tags)
+ assigned_tags = assignments.map do |tag|
+ {
+ :description => tag.parent.description,
+ :id => tag.parent.name,
+ :singleValue => tag.parent.single_value,
+ :values => ->(arr, single_value) { single_value ? [arr.last] : arr }.call(
+ assignments.select do |assignment|
+ assignment.parent.name == tag.parent.name
+ end,
+ tag.parent.single_value
+ ).map do |assignment|
+ { :description => assignment.description, :id => assignment.id }
+ end
+ }
+ end.uniq
+ @tags = {:tags => tags, :assignedTags => assigned_tags, :affectedItems => []}
end
def build_template_filter
diff --git a/app/controllers/vm_common.rb b/app/controllers/vm_common.rb
index 3a43ed8bcc5..436c77c889d 100644
--- a/app/controllers/vm_common.rb
+++ b/app/controllers/vm_common.rb
@@ -1193,7 +1193,6 @@ def replace_right_cell(options = {})
locals[:submit_button] = @sb[:action] != 'miq_request_new' # need submit button on the screen
locals[:continue_button] = @sb[:action] == 'miq_request_new' # need continue button on the screen
update_buttons(locals) if @edit && @edit[:buttons].present?
- presenter[:clear_tree_cookies] = "all_tags_tree"
end
if ['snapshot_add'].include?(@sb[:action])
diff --git a/app/javascript/components/taggingWrapper.jsx b/app/javascript/components/taggingWrapper.jsx
index 1c818a0f104..568521845f7 100644
--- a/app/javascript/components/taggingWrapper.jsx
+++ b/app/javascript/components/taggingWrapper.jsx
@@ -5,6 +5,26 @@ import { Spinner } from 'patternfly-react';
import { TaggingWithButtonsConnected, TaggingConnected, taggingApp } from '@manageiq/react-ui-components/dist/tagging';
import { http } from '../http_api';
+const params = (type = 'default', state, tag = {}) => ({
+ provision: {
+ id: "new",
+ ids_checked: [state.tagging.appState.assignedTags.map(t => t.values.map(val => val.id)).flat(), tag.tagValue.id].flat(),
+ tree_typ: 'tags'
+ },
+ default: {
+ id: state.tagging.appState.affectedItems[0] || "new",
+ cat: tag.tagCategory.id,
+ val: tag.tagValue.id,
+ check: 1,
+ tree_typ: 'tags'
+ }
+})[type]
+
+const onDelete = (type = 'default', params = [], deleted_element) => ({
+ provision: {...params, check: 0, ids_checked: params.ids_checked.filter(element => element !== deleted_element) },
+ default: params,
+})[type]
+
class TaggingWrapper extends React.Component {
constructor(props) {
super(props);
@@ -22,7 +42,7 @@ class TaggingWrapper extends React.Component {
render() {
if (!this.props.isLoaded) return ;
const { urls, options, tagging } = this.props;
- return (options && options.hideButtons && || || );
}
}
@@ -65,21 +85,21 @@ TaggingWrapper.propTypes = {
urls: PropTypes.shape({
cancel_url: PropTypes.string.isRequired,
save_url: PropTypes.string.isRequired,
- }).isRequired,
+ }),
tags: PropTypes.shape({
tags: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number.isRequired,
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
description: PropTypes.string.isRequired,
values: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number.isRequired,
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
description: PropTypes.string.isRequired,
}).isRequired).isRequired,
})).isRequired,
assignedTags: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number.isRequired,
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
description: PropTypes.string.isRequired,
values: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number.isRequired,
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
description: PropTypes.string.isRequired,
}).isRequired).isRequired,
})).isRequired,
diff --git a/app/javascript/miq-redux/middleware.js b/app/javascript/miq-redux/middleware.js
index ad15b1d1906..e7c0f802162 100644
--- a/app/javascript/miq-redux/middleware.js
+++ b/app/javascript/miq-redux/middleware.js
@@ -5,11 +5,12 @@ import promiseMiddleware from 'redux-promise-middleware';
export const taggingMiddleware = store => next => action => {
const { type, meta, tagCategory, tag } = action;
if (meta && meta.url) {
- const params = {id: store.getState().tagging.appState.affectedItems[0], cat: tag.tagCategory.id, val: tag.tagValue.id, check: 1, tree_typ: 'tags' };
+ let params = meta.params(meta.type, store.getState(), tag);
if (type === 'UI-COMPONENTS_TAGGING_TOGGLE_TAG_VALUE_CHANGE') {
- $.post(meta.url, params)
+ $.post({url: meta.url, data: JSON.stringify(params), contentType: "application/json"})
} else if (type === 'UI-COMPONENTS_TAGGING_DELETE_ASSIGNED_TAG') {
- $.post(meta.url, {...params, check: 0})
+ params = meta.onDelete(meta.type, params, tag.tagValue.id);
+ $.post({url: meta.url, data: JSON.stringify(params), contentType: "application/json"})
}
}
let result = next(action)
diff --git a/app/javascript/spec/miq-redux/middleware.spec.js b/app/javascript/spec/miq-redux/middleware.spec.js
index 19e1c164daa..93dd5dbadd3 100644
--- a/app/javascript/spec/miq-redux/middleware.spec.js
+++ b/app/javascript/spec/miq-redux/middleware.spec.js
@@ -2,6 +2,22 @@ import { taggingMiddleware } from '../../miq-redux/middleware'; //this is your m
// const next = jest.fn(); // middleware needs those as parameters, usually calling next(action) at the end to proceed
// const store = jest.fn();
+const params = (type = 'default', state, tag = {}) => ({
+ provision: {
+ id: "new",
+ ids_checked: [],
+ tree_typ: 'tags'
+ },
+ default: {
+ id: [],
+ cat: tag.tagCategory.id,
+ val: tag.tagValue.id,
+ check: 1,
+ tree_typ: 'tags'
+ }
+})[type]
+
+const onDelete = (x, y, z) => ({...y, check: 0});
it('passes the intercepted action to next', () => {
@@ -23,15 +39,16 @@ it('calls post for UI-COMPONENTS_TAGGING_TOGGLE_TAG_VALUE_CHANGE action', () =>
getState: () => ({
tagging: {
appState: {
- affectedItems: [{}]
+ affectedItems: [{}],
+ assignedTags: []
}
}
})
};
- const action = { type: 'UI-COMPONENTS_TAGGING_TOGGLE_TAG_VALUE_CHANGE', meta: { url: 'url/bla' }, tag: {tagCategory: {id: 1}, tagValue: { id:2 }}}
+ const action = { type: 'UI-COMPONENTS_TAGGING_TOGGLE_TAG_VALUE_CHANGE', meta: { url: 'url/bla', params: params }, tag: {tagCategory: {id: 1}, tagValue: { id:2 }}}
taggingMiddleware(store)(next)(action);
- expect(next.mock.calls).toEqual( [[{"meta": {"url": "url/bla"}, "tag": {"tagCategory": {"id": 1}, "tagValue": {"id": 2}}, "type": "UI-COMPONENTS_TAGGING_TOGGLE_TAG_VALUE_CHANGE"}]]);
- expect(spy).toHaveBeenCalledWith("url/bla", {"cat": 1, "check": 1, "id": {}, "tree_typ": "tags", "val": 2});
+ expect(next.mock.calls).toEqual( [[{"meta": {"url": "url/bla", "params": params}, "tag": {"tagCategory": {"id": 1}, "tagValue": {"id": 2}}, "type": "UI-COMPONENTS_TAGGING_TOGGLE_TAG_VALUE_CHANGE"}]]);
+ expect(spy).toHaveBeenCalledWith({"contentType": "application/json", "data": "{\"id\":[],\"cat\":1,\"val\":2,\"check\":1,\"tree_typ\":\"tags\"}", "url": "url/bla"});
});
@@ -48,8 +65,8 @@ it('calls post for UI-COMPONENTS_TAGGING_DELETE_ASSIGNED_TAG action', () => {
}
})
};
- const action = { type: 'UI-COMPONENTS_TAGGING_DELETE_ASSIGNED_TAG', meta: { url: 'url/bla' }, tag: {tagCategory: {id: 1}, tagValue: { id:2 }}};
+ const action = { type: 'UI-COMPONENTS_TAGGING_DELETE_ASSIGNED_TAG', meta: { url: 'url/bla', params: params, onDelete: onDelete, type: 'default' }, tag: {tagCategory: {id: 1}, tagValue: { id:2 }}};
taggingMiddleware(store)(next)(action);
- expect(next.mock.calls).toEqual( [[{"meta": {"url": "url/bla"}, "tag": {"tagCategory": {"id": 1}, "tagValue": {"id": 2}}, "type": 'UI-COMPONENTS_TAGGING_DELETE_ASSIGNED_TAG'}]]);
- expect(spy).toHaveBeenCalledWith("url/bla", {"cat": 1, "check": 0, "id": {}, "tree_typ": "tags", "val": 2});
+ expect(next.mock.calls).toEqual( [[{"meta": {"url": "url/bla", params: params, onDelete: onDelete, type: 'default'}, "tag": {"tagCategory": {"id": 1}, "tagValue": {"id": 2}}, "type": 'UI-COMPONENTS_TAGGING_DELETE_ASSIGNED_TAG'}]]);
+ expect(spy).toHaveBeenCalledWith({"contentType": "application/json", "data": "{\"id\":[],\"cat\":1,\"val\":2,\"check\":0,\"tree_typ\":\"tags\"}", "url": "url/bla"});
});
diff --git a/app/views/miq_request/_prov_edit.html.haml b/app/views/miq_request/_prov_edit.html.haml
index d16c81014e3..d4d53c63b86 100644
--- a/app/views/miq_request/_prov_edit.html.haml
+++ b/app/views/miq_request/_prov_edit.html.haml
@@ -4,5 +4,3 @@
- unless @edit[:explorer]
= render :partial => 'miq_request/prov_form_buttons'
= _("Note: Fields marked with * are required.")
-:javascript
- miqDeleteTreeCookies('all_tags_tree')
diff --git a/app/views/miq_request/_prov_field.html.haml b/app/views/miq_request/_prov_field.html.haml
index 41c995f1b06..55c687f9e08 100644
--- a/app/views/miq_request/_prov_field.html.haml
+++ b/app/views/miq_request/_prov_field.html.haml
@@ -7,7 +7,6 @@
- return if field_hash.blank?
- field_id = dialog.to_s + "__" + field.to_s
- disabled ||= false
-
- unless [:hide, :ignore].include?(field_hash[:display])
:javascript
// Create from/to date JS vars to limit calendar starting from
@@ -290,26 +289,13 @@
= field_hash[:notes]
- elsif [:tag_ids, :vm_tags].include?(field)
-# tree control for tags fields
- .col-md-8
- #all_tags_treebox.treeview-pf-hover.treeview-pf-select{:style => "color:#000; overflow: hidden;"}
- - if @edit && @edit[:req_id]
- - check = @edit[:req_id]
- - elsif @miq_request
- - check = @miq_request.id
- - else
- - check = 'new'
- = render(:partial => "layouts/tree",
- :locals => {:tree_id => "all_tags_treebox",
- :tree_name => "all_tags_tree",
- :bs_tree => @all_tags_tree,
- :oncheck => "miqOnCheckProvTags",
- :check_url => "/miq_request/prov_field_changed/#{check}",
- :checkboxes => true})
+ .col-md-10
+ = react('TaggingWrapperConnected',
+ :tags => @tags,
+ :options => { :type => 'provision', :hideHeaders => true, :hideButtons => true, :url => url_for_only_path(:action => 'prov_field_changed')})
- unless field_hash[:notes_display] == :hide || field_hash[:notes].blank?
-# Display notes if available
= field_hash[:notes]
- .note
- = _("* Only a single value can be assigned from these Tag Categories")
- elsif [:attached_ds, :iso_image_id, :placement_availability_zone,
:placement_cluster_name, :placement_dc_name, :placement_ds_name,
:placement_ems_name, :placement_host_name, :placement_rp_name,
diff --git a/package.json b/package.json
index 6a95ea09632..9f7c966f564 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"dependencies": {
"@data-driven-forms/pf3-component-mapper": "^1.13.3",
"@data-driven-forms/react-form-renderer": "^1.13.3",
- "@manageiq/react-ui-components": "~0.11.27",
+ "@manageiq/react-ui-components": "~0.11.35",
"@manageiq/ui-components": "~1.2.10",
"@pf3/select": "~1.12.6",
"@pf3/timeline": "~1.0.8",