Skip to content

Commit

Permalink
Fixes #24041 - Move HW Models Table to pf-react
Browse files Browse the repository at this point in the history
  • Loading branch information
boaz0 committed Feb 17, 2019
1 parent ad1a01d commit bb015d2
Show file tree
Hide file tree
Showing 50 changed files with 1,129 additions and 54 deletions.
33 changes: 4 additions & 29 deletions app/views/models/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,7 @@

<% title_actions new_link(_("Create Model")) %>

<table class="<%= table_css_classes 'table-fixed' %>">
<thead>
<tr>
<th class="col-md-4"><%= sort :name, :as => s_("Model|Name") %></th>
<th class="col-md-3"><%= sort :vendor_class, :as => _("Vendor Class") %></th>
<th class="col-md-3"><%= sort :hardware_model, :as => s_("Model|Hardware Model") %></th>
<th class="col-md-1"><%= _("Hosts") %></th>
<th><%= _('Actions') %></th>
</tr>
</thead>
<tbody>
<% @models.each do |model| %>
<tr>
<td class="ellipsis">
<%= link_to_if_authorized model.name,
hash_for_edit_model_path(:id => model).merge(:auth_object => model, :authorizer => authorizer) %></td>
<td class="ellipsis"><%= model.vendor_class %></td>
<td class="ellipsis"><%= model.hardware_model %></td>
<td class='ra'><%= link_to hosts_count[model], hosts_path(:search => "model = \"#{model}\"") %></td>
<td class="col-md-1">
<%= action_buttons(display_delete_if_authorized(hash_for_model_path(:id => model).
merge(:auth_object => model, :authorizer => authorizer),
:confirm => _("Delete %s?") % model.name)) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= will_paginate_with_info @models %>
<div id="models_table"></div>
<%= mount_react_component("ModelsTable", "#models_table", {
pagination: react_pagination_props(@models, "models-pagination")
}.to_json) %>
1 change: 0 additions & 1 deletion test/controllers/models_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

class ModelsControllerTest < ActionController::TestCase
basic_pagination_per_page_test
basic_pagination_rendered_test

def test_index
get :index, session: set_session_user
Expand Down
20 changes: 17 additions & 3 deletions test/integration/model_js_test.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
require 'integration_test_helper'

class ModelJSTest < IntegrationTestWithJavascript
test "index page" do
assert_index_page(models_path, "Hardware Models", "Create Model")
class ModelIntegrationTest < IntegrationTestWithJavascript
test "create new page" do
assert_new_button(models_path, "Create Model", new_model_path)
fill_in "model_name", :with => "IBM 123"
fill_in "model_hardware_model", :with => "IBMabcde"
fill_in "model_vendor_class", :with => "ABCDE"
fill_in "model_info", :with => "description text"
assert_submit_button(models_path)
assert page.has_link? "IBM 123"
end

test "edit page" do
visit models_path
click_link "KVM"
fill_in "model_name", :with => "RHEV 123"
assert_submit_button(models_path)
assert page.has_link? 'RHEV 123'
end
end
21 changes: 0 additions & 21 deletions test/integration/model_test.rb

This file was deleted.

8 changes: 8 additions & 0 deletions webpack/assets/javascripts/react_app/common/helpers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import debounce from 'lodash/debounce';
import URI from 'urijs';
import { translate as __ } from './I18n';

/**
Expand Down Expand Up @@ -72,6 +73,13 @@ export const translateObject = obj =>
*/
export const translateArray = arr => arr.map(str => __(str));

/**
* Return the query in URL as Objects where keys are
* the parameters and the values are the parameters' values.
* @param {String} url - the URL
*/
export const getURIQuery = url => new URI(url).query(true);

export default {
bindMethods,
noop,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';
import { LoadingState } from 'patternfly-react';
import PropTypes from 'prop-types';
import { Table } from '../common/table';
import { STATUS } from '../../constants';
import MessageBox from '../common/MessageBox';
import { translate as __ } from '../../common/I18n';
import createModelsTableSchema from './ModelsTableSchema';
import { getURIQuery } from '../../common/helpers';
import Pagination from '../Pagination/Pagination';

class ModelsTable extends React.Component {
componentDidMount() {
this.props.getTableItems('models', getURIQuery(window.location.href));
}

render() {
const {
getTableItems,
sortBy,
sortOrder,
error,
status,
results,
data: { pagination },
} = this.props;

const renderTable =
status === STATUS.ERROR ? (
<MessageBox
key="models-table-error"
icontype="error-circle-o"
msg={__(`Could not receive data: ${error && error.message}`)}
/>
) : (
<React.Fragment>
<Table
key="models-table"
columns={createModelsTableSchema(getTableItems, sortBy, sortOrder)}
rows={results}
/>
<div id="pagination">
<Pagination data={pagination} />
</div>
</React.Fragment>
);

return (
<LoadingState
size="lg"
loading={status === STATUS.PENDING}
timeout={2500}
>
{renderTable}
</LoadingState>
);
}
}

ModelsTable.propTypes = {
data: PropTypes.shape({
pagination: PropTypes.shape({
viewType: PropTypes.string,
perPageOptions: PropTypes.arrayOf(PropTypes.number),
itemCount: PropTypes.number,
perPage: PropTypes.number,
}).isRequired,
}).isRequired,
results: PropTypes.array.isRequired,
getTableItems: PropTypes.func.isRequired,
status: PropTypes.oneOf(Object.keys(STATUS)),
sortBy: PropTypes.string,
sortOrder: PropTypes.string,
error: PropTypes.object,
};

ModelsTable.defaultProps = {
status: STATUS.PENDING,
sortBy: '',
sortOrder: '',
error: null,
};

export default ModelsTable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import { shallow } from 'enzyme';
import ModelsTable from './ModelsTable';
import { Table } from '../common/table';
import MessageBox from '../common/MessageBox';

const data = {
pagination: {
vieType: 'table',
perPageOptions: [1, 2, 3],
itemCount: 5,
perPage: 1,
},
};

describe('ModelsTable', () => {
it('render table on sucess', () => {
const getModelItems = jest.fn().mockReturnValue([]);
const view = shallow(
<ModelsTable results={[]} getTableItems={getModelItems} data={data} />
);
expect(getModelItems.mock.calls).toHaveLength(1);
expect(view.find(Table)).toHaveLength(1);
});

it('render error message box on failure', () => {
const view = shallow(
<ModelsTable
getTableItems={jest.fn(() => [])}
results={[]}
error={Error('some error message')}
status="ERROR"
data={data}
/>
);
expect(view.find(MessageBox)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import createTableActionTypes from '../common/table/actionsHelpers/actionTypeCreator';

export const MODELS_TABLE_CONTROLLER = 'models';
export const MODELS_TABLE_ACTION_TYPES = createTableActionTypes(
MODELS_TABLE_CONTROLLER
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Immutable from 'seamless-immutable';
import { STATUS } from '../../constants';
import { MODELS_TABLE_ACTION_TYPES } from './ModelsTableConstants';

const initState = Immutable({
error: null,
sortBy: '',
sortOrder: '',
results: [],
status: STATUS.PENDING,
});
export default (state = initState, action) => {
const { REQUEST, FAILURE, SUCCESS } = MODELS_TABLE_ACTION_TYPES;
switch (action.type) {
case REQUEST:
return state.set('status', STATUS.PENDING);
case SUCCESS:
return Immutable.merge(state, {
error: null,
status: STATUS.RESOLVED,
results: action.payload.results,
sortBy: action.payload.sort.by,
sortOrder: action.payload.sort.order,
});
case FAILURE:
return Immutable.merge(state, {
error: action.payload.error,
status: STATUS.ERROR,
results: [],
});
default:
return state;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { testReducerSnapshotWithFixtures } from '../../common/testHelpers';
import reducer from './ModelsTableReducer';
import { MODELS_TABLE_ACTION_TYPES } from './ModelsTableConstants';

const fixtures = {
'should return initial state': {},
'should handle MODELS_TABLE_REQUEST': {
action: {
type: MODELS_TABLE_ACTION_TYPES.REQUEST,
},
},
'should handle MODELS_TABLE_SUCCESS': {
action: {
type: MODELS_TABLE_ACTION_TYPES.SUCCESS,
payload: {
search: 'name=model',
results: [{ id: 23, name: 'model' }],
page: 1,
per_page: 5,
total: 20,
sort: { by: 'name', order: 'ASC' },
},
},
},
'should handle MODELS_TABLE_FAILURE': {
action: {
type: MODELS_TABLE_ACTION_TYPES.FAILURE,
payload: {
error: new Error('ooops!'),
},
},
},
};

describe('ModelsTable reducer', () =>
testReducerSnapshotWithFixtures(reducer, fixtures));
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
sortControllerFactory,
column,
sortableColumn,
headerFormatterWithProps,
cellFormatterWithProps,
nameCellFormatter,
hostsCountCellFormatter,
deleteActionCellFormatter,
cellFormatter,
} from '../common/table';

/**
* Generate a table schema to the Hardware Models page.
* @param {Function} apiCall a Redux async action that fetches and stores table data in Redux.
* See actions/common/getTableItemsAction.
* @param {String} by by which column the table is sorted.
* If none then set it to undefined/null.
* @param {String} order in what order to sort a column. If none then set it to undefined/null.
* Otherwise, 'ASC' for ascending and 'DESC' for descending
* @return {Array}
*/
const createModelsTableSchema = (apiCall, by, order) => {
const sortController = sortControllerFactory(apiCall, by, order);
return [
sortableColumn('name', __('Name'), 4, sortController, [
nameCellFormatter('models'),
]),
sortableColumn('vendor_class', __('Vendor Class'), 3, sortController),
sortableColumn('hardware_model', __('Hardware Model'), 3, sortController),
column(
'hosts_count',
__('Hosts'),
[headerFormatterWithProps],
[hostsCountCellFormatter('model'), cellFormatterWithProps],
{ className: 'col-md-1' },
{ align: 'right' }
),
column(
'actions',
__('Actions'),
[headerFormatterWithProps],
[deleteActionCellFormatter('models'), cellFormatter]
),
];
};

export default createModelsTableSchema;
Loading

0 comments on commit bb015d2

Please sign in to comment.