Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #22624 - User selectable columns model + api #5262

Merged
merged 1 commit into from Mar 22, 2018

Conversation

parthaa
Copy link
Contributor

@parthaa parthaa commented Feb 20, 2018

Adding initial bindings to user selectable columns. This model + api is
going to be used in work related Katello plugin's subscription pages.
The main intent of this PR is to provide a basic model where a user can
store "desirable or interested columns for a resource"
When the user for example says "I want to only see name column in
subscriptions" , we need model that would store that information
(similar to widgets or preferences.)
This commit facilitates that.

@theforeman-bot
Copy link
Member

Issues: #22624

class UserColumnsController < V2::BaseController
include Api::Version2

api :GET, "/user_columns/", N_("List of User columns for a user")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user, capitalization should be consistent

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about delete and update? Also I feel like GET /user_columns should return all the columns for each resource (would it ever make sense to get that info?) and GET /user_columns/:resource should return them for a resource

@ekohl
Copy link
Member

ekohl commented Feb 20, 2018

Would it be possible to replace https://github.com/GregSutcliffe/foreman_column_view with this?

param :resource, String, :required => true
param :columns, Array, :desc => N_("List of user selected columns")
def create
User.current.user_columns.where(:resource => params[:resource]).destroy_all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CREATE "updates" via this destroy_all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point will figure out a better way.


# Returns user columns representation as the hash object used in memory
def to_hash
{ :columns => columns, :resource => resource }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attr_exportable might be usable here instead, take a look at the exportable concern

@parthaa parthaa force-pushed the user-columns branch 4 times, most recently from 0efb615 to 7ab182e Compare February 21, 2018 23:47
@tstrachota
Copy link
Member

@parthaa do we already have some PR or branch with a table that uses this new functionality? I'm curious about how the UI part looks.

@waldenraines
Copy link

waldenraines commented Feb 22, 2018

@parthaa do we already have some PR or branch with a table that uses this new functionality? I'm curious about how the UI part looks.

@tstrachota we will be using this on the RH subscriptions page:

cff781297148a40076394701ba0c71de15cd4d4f

Due to time constraints we aren't planning on using this anywhere in foreman yet.

@parthaa
Copy link
Contributor Author

parthaa commented Mar 2, 2018

Here are the Actions implemented so far

  • Create

$ curl -k -u admin:changeme -H "Content-Type: application/json" -X POST -d '{"resource":"organizations", "columns":["name"]}' "https://localhost/api/v2/user_columns"

{"resource":"organizations","columns":["name"],"created_at":"2018-03-02 01:14:14 UTC","updated_at":"2018-03-02 01:14:14 UTC","user":{"id":4,"login":"admin","description":null}}

$ curl -k -u admin:changeme -H "Content-Type: application/json" -X POST -d '{"resource":"subscriptions", "columns":["name","account"]}' "https://localhost/api/v2/user_columns"

{"resource":"subscriptions","columns":["name","account"],"created_at":"2018-03-02 01:14:43 UTC","updated_at":"2018-03-02 01:14:43 UTC","user":{"id":4,"login":"admin","description":null}}

  • Index
$ curl -k -u admin:changeme -H "Content-Type: application/json" -X GET "https://localhost/api/v2/user_columns"
{
  "total": 2,
  "subtotal": 2,
  "page": 1,
  "per_page": 20,
  "search": null,
  "sort": {
    "by": null,
    "order": null
  },
  "results": [{"resource":"subscriptions","columns":["name","account"],"created_at":"2018-03-02 01:14:43 UTC","updated_at":"2018-03-02 01:14:43 UTC","user":{"id":4,"login":"admin","description":null}},{"resource":"organizations","columns":["name"],"created_at":"2018-03-02 01:14:14 UTC","updated_at":"2018-03-02 01:14:14 UTC","user":{"id":4,"login":"admin","description":null}}]
}

  • Information about an individual resource
$ curl -k -u admin:changeme -H "Content-Type: application/json" -X GET -d '{"resource":"subscriptions"}'   "https://localhost/api/v2/user_columns/columns"

{"results":["name","account"]}
  • Reset user columns for a given resource
$ curl -k -u admin:changeme -H "Content-Type: application/json" -X DELETE -d '{"resource":"subscriptions"}' "https://localhost/api/v2/user_columns/reset"
[]

#verified by
$ curl -k -u admin:changeme -H "Content-Type: application/json" -X GET -d '{"resource":"subscriptions"}'   "https://localhost/api/v2/user_columns/columns"
{"results":[]}

$ curl -k -u admin:changeme -H "Content-Type: application/json" -X GET -d '{"resource":"organizations"}'   "https://localhost/api/v2/user_columns/columns"
{"results":["name"]}

  • Destroy all user columns for a user
$ curl -k -u admin:changeme -H "Content-Type: application/json" -X DELETE    "https://localhost/api/v2/user_columns/destroy_all"
[]

# Verified By

$ curl -k -u admin:changeme -H "Content-Type: application/json" -X GET "https://localhost/api/v2/user_columns"
{
  "total": 0,
  "subtotal": 0,
  "page": 1,
  "per_page": 20,
  "search": null,
  "sort": {
    "by": null,
    "order": null
  },
  "results": []
}

@parthaa
Copy link
Contributor Author

parthaa commented Mar 2, 2018

Note: I have listed the intended workflow with the Katello subscriptions UI here
Katello/katello#7219 (comment)

@iNecas
Copy link
Member

iNecas commented Mar 2, 2018

This approach looks good to me. I would like to see more +1 from on this one, to make sure this doesn't go against any other plans folks have, as it's introducing new API point, that might be complicated to update later. Any other thoughts, @theforeman/core ?

@jlsherrill
Copy link
Contributor

This doesn't have any way to have default columns defined in any way, which means:

  1. fetching the colums for a fresh user would return nothing
  2. Setting invalid columns can't be validated

i think there needs to be a registry where the app or plugin can define what columns are available for a resource (and the default enablement for them).

@parthaa parthaa force-pushed the user-columns branch 2 times, most recently from 4fb9177 to bffd011 Compare March 3, 2018 21:40
@parthaa
Copy link
Contributor Author

parthaa commented Mar 3, 2018

This doesn't have any way to have default columns defined in any way, which means:

fetching the colums for a fresh user would return nothing
Setting invalid columns can't be validated
i think there needs to be a registry where the app or plugin can define what columns are available for a resource (and the default enablement for them).

Created a registry and a plugin api to make this work. Added validation and updated controller tests for this to work.
Check out -> https://github.com/Katello/katello/pull/7219/files#diff-84f0b46367dabf5e0710a0fe3aca7627R233 for more info o this.

@ShimShtein
Copy link
Member

Generally, the idea is very interesting. A couple of thoughts on the subject:

  1. How well will this play with graphQL? I know there's an effort to add support to it. /cc @ik5
  2. As far as I can see, this filter is not applied at the server level, meaning no optimization to queries. So I see it solves only eye clutter for the user, but not processing time.

@parthaa
Copy link
Contributor Author

parthaa commented Mar 5, 2018

How well will this play with graphQL? I know there's an effort to add support to it. /cc @ik5
As far as I can see, this filter is not applied at the server level, meaning no optimization to queries. So I see it solves only eye clutter for the user, but not processing time.

Good points. However this is not meant to replace graphql in terms of optimization. I would think this will work well in conjunction with graphql whenever that aspect is implemented in a mainstream way. For example if we have information on what columns we want, the api javascript will tell server what it exactly wants using GraphQL. This is purely for the UI (and possibly hammer in the future.) It is merely intended to provide and store enough information for that.

Copy link
Member

@timogoebel timogoebel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@parthaa: Thanks, two small comments:
Can we please not add an another registry but use one of the existing registries?

Doesn't it make sense to store this in the browser's local storage? We already use that for the "expand the nav" hamburger. And other tools like AWS do it in a similar way.

Copy link
Member

@ohadlevy ohadlevy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @parthaa, some comments inline.

I do agree with @timogoebel we should consider local storage, however i guess that we still need the definition of the columns somewhere..?

api :GET, "/user_columns/:resource", N_("List of user columns for a resource")
param :resource, String, :required => true
def columns
render :json => { root_node_name => UserColumn.formatted_columns(params[:resource])}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make more sense to reuse rabl?


api :GET, "/user_columns/:resource", N_("List of user columns for a resource")
param :resource, String, :required => true
def columns
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why deviate from REST? (index, create, update, show, destroy)?

api :DELETE, "/user_columns", N_("Delete columns for a resource")
param :resource, String, :required => true, :desc => N_("name of the resource")
def reset
process_response @user_column.destroy_all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will always remove all? just like destroy_all?

in general I would prefer to have one destroy method that get an array of ids, so it can delete one, many or all.

private

def find_user_column
@user_column = User.current.user_columns.where(:resource => params[:resource])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make sense to move to a scope? I see we use it in multiple places.

def ensure_columns_valid
valid_columns = ::UserColumnRegistry::Manager.columns(resource) || []
valid_column_names = valid_columns.map(&:name)
invalid_columns = columns.select{ |col| !valid_column_names.include? col.to_s }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of looping over all columns, you can use something like

invalid = columns - valid_common_names

current_user_columns = nil

if User.current.present?
uc = User.current.user_columns.where(:resource => resource).first
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: I find variable uc name confusing, how about simple columns?
consider using scope instead of where.


if User.current.present?
uc = User.current.user_columns.where(:resource => resource).first
current_user_columns = uc.columns if uc && uc.columns
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use uc.try(:columns)


registry_columns.map do |column|
formatted_column = column.to_hash
formatted_column[:enabled] = if current_user_columns.nil?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since columns is serialized, empty string will probably be better to use .blank? vs .nil?

@@ -720,4 +720,8 @@
map.permission :revoke_personal_access_tokens,
:"api/v2/personal_access_tokens" => [:destroy]
end

permission_set.security_block :user_columns do |map|
map.permission :access_user_columns, {:"api/v2/user_columns" => [:index, :create, :columns, :reset, :destroy_all]}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make more sense to be api/v2/user/id/columns?

@waldenraines
Copy link

Doesn't it make sense to store this in the browser's local storage?

@timogoebel @ohadlevy persisting the column selections gives a better user experience because it would be rather confusing for a user to have to select their preferred columns again after initially setting them if they user another browser or otherwise clear the local storage. Also if we store this in the browser's local storage then the columns will be different in the CLI.

We also want to expand this in the future to support other table configuration features such as column ordering. Not persisting these items would be frustrating to users who want to customize their table view once and have it shown the same from thereon out.

@waldenraines
Copy link

/cc @Rohoover

@parthaa parthaa force-pushed the user-columns branch 2 times, most recently from f9886d3 to 4b6450c Compare March 19, 2018 13:14
@parthaa
Copy link
Contributor Author

parthaa commented Mar 19, 2018

@timogoebel @ares @ohadlevy had a chat with @tbrisker and decided to remove the access_table_preferences permission. At this point it seems unnecessary since only User.current is allowed to mess with this table for their own preferences (similar to dashboard). Hope that works for everyone. If permissions is indeed desired in the future we can always add them later.

@timogoebel
Copy link
Member

Hope that works for everyone.

Works for me. :-)

@@ -42,7 +42,7 @@ class User < ApplicationRecord
has_many :widgets, :dependent => :destroy
has_many :ssh_keys, :dependent => :destroy
has_many :personal_access_tokens, :dependent => :destroy

has_many :table_preferences, :dependent => :destroy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding inverse_of here?

@tbrisker
Copy link
Member

@parthaa looks like test/unit/foreman/access_permissions_test.rb needs to be modified to skip checking for permissions on the new endpoints. The other tests seem to be just transient failures.

@parthaa
Copy link
Contributor Author

parthaa commented Mar 20, 2018

@tbrisker updated.

Copy link
Member

@tbrisker tbrisker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, any concerns @ares or @ohadlevy ?

@ares
Copy link
Member

ares commented Mar 20, 2018

not from my side, I'm fine with the last change

@table_preferences = @user.table_preferences
end

api :GET, "/users/:user_id/table_preferences/:name", N_("List of table preferences for a table")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the description confusing, how about "Table preferences details" ? (but not a list of table for table)

@@ -0,0 +1,10 @@
class TablePreference < ApplicationRecord
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we care about auditing or search?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, this is just UI preferences, not anything that has impact on the system. It's similar to the dashboard widget settings which we don't audit or search.

Adding initial bindings to user selectable columns. This model + api is
going to be used in work related Katello plugin's subscription pages.
The main intesnt of this PR is to provide a basic model where a user can
store "desirable or interested columns for a resource"
When the user for example says "I want to only see name column in
subscriptions" , we need model that would store that information
(similar to widgets or preferences.)
This commit facilitates that.
@mmoll
Copy link
Contributor

mmoll commented Mar 22, 2018

@ohadlevy all good now? :)

@ohadlevy ohadlevy merged commit e73b804 into theforeman:develop Mar 22, 2018
@ohadlevy
Copy link
Member

Thanks!

@parthaa parthaa deleted the user-columns branch March 22, 2018 21:08
@parthaa
Copy link
Contributor Author

parthaa commented May 21, 2018

Gist connected to Usage -> https://gist.github.com/parthaa/36ead0ff6962d6dac33412a1c38e94f7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet