Ron and I are working on converting the Hardware Model page in Foreman to be fully React.
To make this transition smooth as possible and to understand the challenges, I am creating this topic.
This post will have all the things that we believe are necessary and required to do this including challenges that we are not sure how to tackle.
The hardware models page contains the following components
Title
Breadcrumbs switcher
Search box - input text box that contains the user search query
Search button - on click filters table (7)
Bookmarks - saved search queries
Create button - move to new model page to add a new model
TableView contains:
Table (7) - contains model records
Per page select box - sets the number of results per page (8)
Results label - contains how many items are displayed in the table out of the total results (9)
Note: strike-through-ed items mean that they already implemented in React
Hardware Model page Behavior
The per page select box - currently changing its value sends a request with the per_page parameter in URI and causes loading the entire page. This behavior will be changed: table (7) will be re-rendered accordingly to per_page without manipulating URI.
The search box
When typing a search query in the search box an ajax request is sent to the auto_complete_search endpoint for auto completion options
Clicking on search a search adding a search parameter to URI which ends up with loading the entire page. This behavior is going to be changed: filtering the entries in Redux and re-rendering the table accordingly
Clicking on one of the bookmarks loads the entire page and sets the search box value to the bookmark’s search query. Again, this behavior will be changed: re-render the search box and the table
Clicking on Delete action displays javascript’s confirmation dialog. Probably this will be replaced by Modal to give a consistent look
Clicking on an item in the table will render the Create Model page discribed below.
Hardware Model page action items
Replace search-box with pf-react
pf’s TypeAhead seems like the most suitable component for the job
Because of that, it’s yet not obvious how to set an initial search query which is required when a bookmark is chosen unless we use controlling selections however it makes the search box style different than what people are used.
Another way to solve this is (1) to fix this in react-bootstrap-typeahead or (2) to fork it and add our changes or (3) to write a workaround in pf
Replace table, per_page selector and results label with pf-reactTableView
api/v2/models returns a model list containing “name”, “id”, “vendor_clas” and “hardware_model” per item
That’s not good enough because hosts count is missing
One way to solve this is to do another API call for each model
api/v2/hosts?search=model=MODEL_NAME&thin=true
Pros - Naive
Cons - having many models causes too many API calls. In case of failure the user will have partial resolved data
Load all api/v2/hosts and map-reduce the results to get number of hosts with a given model.
Pros - avoid the API calls
Cons - computation on the client side, response size might be big and contain unnecessary information
Ohad suggested to render json format with all the needed data it looks like the right direction
Pros - avoid API calls, code reuse
# app/controllers/architecture_controller.rb
def index
respond_to do |format|
format.html
format.json {
# resource_data will get all the architectures with `host_count` and `operating systems`
render :json => {:results => resource_data()}
}
end
end
# app/views/architectures/index/html.erb
<div id="architectures"></div>
<%= mount_react_component("Architectures", "#architectures", results) %>
Adding confirmation modal on Delete instead of javascript confirmation dialog
Do more research on GraphQL - how does the HW Model schema look like and how to retrieve this data in the future.
Text field with the “Hardware Model” label - the hardware model. In the bottom there is descriptive information about the meaning of this field and how to retrieve it using the CLI
Text field with the “Vendor Class” label - model’s vendor class. In the bottom there is descriptive information about the meaning of this field and how to retrieve it using the CLI
TextArea with the “Information” label - useful information about the model. In the bottom there is descriptive information about the meaning of this field
Action buttons - Submit to create Hardware Model and Cancel to go back to the Hardware Models page
This should actually be enough to play with this in the index page.
The way I see it, we also need the following:
A mutation to create new models
A mutation to edit existing models
A field that provides the host count for the model
A test for the plural field (e.g. retrieve all models)
A totalCount field on the connection
That actually seems doable. I’ll see what I can do to help.
I’d also really like to see this page move to a dedicated react controller and use react-router for the navigation. mount_react_component always feels like you actually want to do it properly.
thanks @TimoGoebel! I wonder if in this case it would be enough to simply creating the model retrieval and use the current API for other actions (POST/PUT/DELETE) ? (e.g. in this case, I assume its easy to consume as a REST API too?) I do agree that for more complex objects with many more associations, this makes a lot of sense.
That should definitely be possible, you just have to bear in mind that graphql (or actually relay) recommends using global ids as id’s (instead of the database ids). But it should be trivial to process a global id to find the database id on the client.
I played with this a bit. I have a mutation ready to create models. I just need to add a guard to ensure the user is actually allowed to to that. Creating a mutation for update and delete should not be hard now.
I believe we can implement the host count as a connection (models have hosts) and add the count field to the connection. And we also need the count for pagingation. I need to play with that a little more, but it looks really powerful. In general, we should be there pretty fast.
Here is a sneak peak to show that it’s actually not that much code.
Is it a good time to start work towards single search form in Foreman header common across all pages? It would act like search everything (or at least the most important resources like hosts/hostgroups) but still as a context sensitive search - when on hardware model page, offer fields relevant to that page.
I have no idea how to do this technically on SPA, in Rails that would be easier job I think (each page is generated so the context is known). But I like the idea of common search UI API - one search component doing global search and local search.
Big [image: ] for global search but IMHO it should be a separate RFC
to keep focus.
I agree, it would also need design work too.
I have a wish list around search (including global search, history etc),
but imho it makes more sense to change technology first (e.g. move to a
react implementation) and then improve afterwards.
I have one concern regarding the client paginated table - this would mean fetching all the records during the initial load. It is probably not an issue for hardware models as their count is usually low but the same approach could cause problems for other resources such as reports. For this reason, we might need to consider using the server pagination on index pages.
Actually, this is another good reason for graphql. The ApolloJS client supports caching so that switching between pages is pretty fast if the data has been fetched once. Just saying
I agree with Ondrej, imho it was a mistake in the original doc.
we are going to store the results in redux anyway, do we really need to
store it again? (don’t forget that non turbolinks hits will reset the
session anyway…so its a long term value anyway)
We use server-side paginated tables on Katello redux pages already. It’s worth checking the repositories and subscriptions page.
Speaking about the index page, I was wondering if it makes sense to move the whole “Provisioning setup” menu that gathers multiple index pages to a dashboard-like page that would display all the information at one place.
I am so glad this thread got a lot of attention in the last two days.
Thank you for your feedback and if I could edit the original post, I would have done that.
Here I am going to share the plan to transform Hardware Models page to a React page
Again, any feedback is welcome, this time I will try to reply as soon as possible.
Thanks for you time.
Action items - tasks
Step 1 - Move to React components in the Hardware Model Page
Search box (Katello already did something similar here) - medium
Replace auto_complete_search with pf-react’s TypeAhead
In this part I am going to focus on how the state will be stored in Redux
"model_page": {
"page": number, // the current page
"per_page": number, // how many entities to display per page
"total": number, // total number of entities
"sort_by": string, // what column to sort
"sort_order": oneOf(["ASC", "DESC"]),
"results": [{
"name": string, // a link string if authorized otherwise just the name
"vendor_class": string,
"hardware_model": string,
"hosts_count": string, // a link to hosts with search query
}],
"error": ErrorObject,
"status": oneOf(["PENDING", "RESOLVED", "ERROR"]),
"searchQuery": string, // the search/filter query given in the search box
"searchOptions": array.of.string // options for auto completion
"searchStatus": oneOf(["PENDING", "RESOLVED", "ERROR"]),
}
Events, Actions and State
Here are three components and their flows from triggering an action to changing the state and rendering the component.
Search Box
When the user type the AUTO_COMPLETE_REQUEST action is triggered.
This action is doing an async call to models/auto_complete_search?search=[query] to get auto completion search
options.
{
type: "AUTO_COMPLETE_REQUEST",
}
Reducer sets the state:
{
search_status: "PENDING"
}
On AUTO_COMPLETE_REQUEST success a new action will be dispatched - AUTO_COMPLETE_SUCCESS.
Note Should think of a way to display auto completion error.
Search Button
On clicking the Search button SEARCH_REQUEST is being dispatched.
This action does an async call to api/models?search=[query]&page=[page]&per_page=[per_page] to
retrieve results from the server.
{
type: "SEARCH_REQUEST"
}
The reducer sets the following state:
{
status: "PENDING",
searchQuery: query
}
on SEARCH_REQUEST success the SEARCH_SUCCESS action is dispatched.
On changing pagination per page selector the PER_PAGE_CHANGE action is triggered.
While it is triggered, a request to the following is being sent api/models?search=[query]&per_page=[per_page]&page=[page]