Skip to content

Commit

Permalink
Fixes #35287 - Create column selector on host index page
Browse files Browse the repository at this point in the history
  • Loading branch information
pkoprda committed Aug 9, 2022
1 parent 50ea182 commit bb2e19f
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 0 deletions.
4 changes: 4 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ def searchable?
end
end

def filter_columns?
controller_name == 'hosts' && controller.action_name == 'index'
end

def auto_complete_controller_name
controller.respond_to?(:auto_complete_controller_name) ? controller.auto_complete_controller_name : controller_name
end
Expand Down
25 changes: 25 additions & 0 deletions app/helpers/selectable_columns_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,31 @@ def attr_from_callbacks(callbacks, subject)
.html_safe
end

def get_selected_columns
column_keys = []
Foreman::SelectableColumns::Storage.selected_by(User.current, auto_complete_controller_name).each do |column|
column_keys << column['key']
end
column_keys
end

def defined_columns
Foreman::SelectableColumns::Storage.defined_for(auto_complete_controller_name).each do |category|
category[:defaultExpanded] = true if category[:id] == 'general'
category[:children] = category.delete(:columns)
end
end

def mount_column_selector
url = "#{api_users_path}/#{User.current[:id]}/table_preferences/#{auto_complete_controller_name}"
react_component("ColumnSelector", data: {
url: url,
controller: auto_complete_controller_name,
selected: get_selected_columns,
defined: defined_columns,
})
end

private

def attributes(th_or_td)
Expand Down
6 changes: 6 additions & 0 deletions app/views/layouts/_application_content.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
<%= yield(:search_bar) %>&nbsp;
</div>
<div id="title_action" class= <%= searchable? ? "col-md-6" : "col-md-8" %>>
<% if filter_columns? %>
<div class="col-md-1">
<%= mount_column_selector %>
<%= yield(:column_selector) %>&nbsp;
</div>
<% end %>
<div class="btn-toolbar pull-right">
<%=h yield(:title_actions) %>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React, { useState } from 'react';
import { PropTypes } from 'prop-types';
import {
Button,
Modal,
ModalVariant,
TreeView,
} from '@patternfly/react-core';
import { translate as __ } from '../../common/I18n';
import './column-selector.scss';

const ColumnSelector = props => {
const {
data: { url, controller, selected, defined },
} = props;

const [isModalOpen, setModalOpen] = useState(false);
const [selectedColumns, setSelectedColumns] = useState(selected);

const onClose = event => {
setModalOpen(!isModalOpen);
};

const filterItems = (item, checkedItem) => {
if (item.id === checkedItem.id) {
return true;
}

if(item.children) {
return (
(item.children = item.children
.map(opt => Object.assign({}, opt))
.filter(column => filterItems(column, checkedItem))).length > 0
);
}
};

const flattenTree = tree => {
var result = [];
tree.forEach((item) => {
result.push(item);
if (item.children) {
result = result.concat(flattenTree(item.children));
}
});
return result;
};

const onCheck = (evt, treeViewItem) => {
const checked = evt.target.checked;
const checkedItemTree = defined.map(column => Object.assign({}, column))
.filter(item => filterItems(item, treeViewItem));
const flatCheckedItems = flattenTree(checkedItemTree);
};

const isChecked = dataItem => {
dataItem.checked = selected.some((item) => item === dataItem.id);
return dataItem.checked;
};
const areAllDescendantsChecked = dataItem =>
dataItem.children ? dataItem.children.every((column) => isChecked(column)) : null;
const areSomeDescendantsChecked = dataItem =>
dataItem.children ? dataItem.children.some((column) => isChecked(column)) : null;

const mapTree = item => {
const hasCheck = areAllDescendantsChecked(item);

if (hasCheck) {
item.checked = false;
} else {
const hasPartialCheck = areSomeDescendantsChecked(item);
if (hasPartialCheck) {
item.checked = null;
}
}

if (item.children) {
return {
...item,
children: item.children.map((column) => mapTree(column))
};
}

return item;
};

const mapped = defined.map((category) => mapTree(category));

return (
<div className="pf-c-select-input">
<div className="column-selector pf-c-input-group" id="column-selector">
<Button id="btn-filter" variant="tertiary" className="pull-left" onClick={() => onClose()}>
{__('Filter columns')}
</Button>
<Modal
variant={ModalVariant.small}
title={__('Filter columns')}
isOpen={isModalOpen}
onClose={onClose}
actions={[
<Button key="save" variant="primary">
{__('Save')}
</Button>,
<Button key="cancel" variant="secondary" onClick={() => onClose()}>
{__('Cancel')}
</Button>
]}
>
<TreeView
data={mapped}
onCheck={onCheck}
hasChecks
>
</TreeView>
</Modal>
</div>
</div>
);
};

ColumnSelector.propTypes = {
data: PropTypes.shape({
controller: PropTypes.string,
url: PropTypes.string,
selected: PropTypes.arrayOf(PropTypes.string),
defined: PropTypes.arrayOf(PropTypes.object),
}),
};

ColumnSelector.defaultProps = {
data: {
controller: null,
url: null,
selected: [],
defined: [],
},
};

export default ColumnSelector;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.column-selector {
width: 100%;

label {
font-size: 16px;
height: 36px;
border-bottom-color: var(--pf-global--BorderColor--200);
border-top-color: var(--pf-global--BorderColor--300);
border-left-color: var(--pf-global--BorderColor--300);
border-left-width: 0.7px;
border-top-width: 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ColumnSelector from './ColumnSelector';

export default ColumnSelector;
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import FactChart from './FactCharts';
import Pagination from './Pagination';
import AutoComplete from './AutoComplete';
import SearchBar from './SearchBar';
import ColumnSelector from './ColumnSelector';
import Layout from './Layout';
import EmptyState from './common/EmptyState';
import ComponentWrapper from './common/ComponentWrapper/ComponentWrapper';
Expand Down Expand Up @@ -118,6 +119,7 @@ const componentRegistry = {
const coreComponets = [
{ name: 'ReactApp', type: ReactApp },
{ name: 'SearchBar', type: SearchBar },
{ name: 'ColumnSelector', type: ColumnSelector },
{ name: 'AutoComplete', type: AutoComplete },
{ name: 'AreaChart', type: AreaChart },
{ name: 'DonutChart', type: DonutChart },
Expand Down

0 comments on commit bb2e19f

Please sign in to comment.