UMB-CS-682-Team-03 / tracker

0 stars 0 forks source link

Make roles search for user in classhelper a dropdown/select element #33

Closed rouilj closed 4 months ago

rouilj commented 4 months ago

From issue #27: https://github.com/UMB-CS-682-Team-03/tracker/issues/27#issuecomment-2073851761

====

[...]roles are a property, but they are not a linked class that you can query. The answer is hinted at by:

https://roundup-tracker.org/docs/rest.html#controlling-access-to-backend-data

by adding a REST endpoint. I am not sure if you can use that method to add:

tracker.../data/user/role

I'll try it. However, you can set up searching on an issue by title (text search) and keyword, status or priority as dropdowns as keyword, status and priority are true classes.

====

From: https://github.com/UMB-CS-682-Team-03/tracker/issues/27#issuecomment-2073922241

====

I can't get '/rest/data/user/role' or '/rest/data/role' to work, but I do have '/rest/role' working.

Create the file interfaces.py in your tracker home directory and add:

from roundup.rest import Routing, RestfulInstance,  _data_decorator

class RestfulInstance:

    @Routing.route("/roles", 'GET')
    @_data_decorator
    def get_roles(self, input):
        """Return all defined roles. The User class property
           roles is a string but simulate it as a MultiLink
           to an actual Roles class.
        """
        return 200, {"collection": [ {"id": rolename, "name": rolename}
                  for rolename in list(self.db.security.role.keys())]}

Restart your tracker and query:

http://localhost:8080/demo/rest/roles

which returns a list of roles like:

{
    "data": {
        "collection": [
            {
                "id": "user",
                "name": "user"
            },
            {
                "id": "admin",
                "name": "admin"
            },
            {
                "id": "anonymous",
                "name": "anonymous"
            }
        ]
    }
}

Note that the id can't be interpreted as an integer because we are faking a real database class. The shape of the data can be made simpler if we want. I chose this shape to match what you see when querying a real class endpoint like /rest/data/status.

====

Note you need to hit the endpoint /demo/rest/roles not /demo/rest/data/roles. This may be an issue as I think the existing REST query methods assume the /rest/data/ component of the URL.

rouilj commented 4 months ago

The thought of reversing the order of the regular expression list used to handle rest url's occurred to me in the standup this morning.

Adding this patch to roundup/rest.py:

--- a/roundup/rest.py   Sun Apr 21 21:50:36 2024 -0400
+++ b/roundup/rest.py   Wed Apr 24 18:37:35 2024 -0400
@@ -399,7 +399,9 @@

         # find the rule match the path
         # then get handler match the method
-        for path_regex, funcs in cls.__route_map.values():
+        seq = [(path_regex, funcs) for path_regex, funcs
+                in  cls.__route_map.values()]
+        for path_regex, funcs in reversed(seq):
             # use compiled regex to find rule
             match_obj = path_regex.match(path)
             if match_obj:

along with changing the rest addition above to:

   @Routing.route("/data/roles", 'GET')
    @_data_decorator
    def get_roles(self, input):
   [...]

allows http://tracker.example.org/demo/rest/data/roles to return what wash shown above for /roles. But this will only work for Python 3.7 or newer where the order of keys (and values) is the insertion order: first in, first out.

This is not always true of other Pythons and while defined as the standard seems like an implementation detail. If you can make /rest work it would be best. Hard coding a match between roles[] and '/roles' would work. Note that roles still means a text search not a dropdown.

Alternatively a lookup table with searchWith token and endpoint would work. So you can use:

   resolve_searchWith = {
      'roles[]': '/rest/roles'
   }

to make it possible to add additional endpoints for other property tokens in the future (this may be a case of YAGNI)? In this case I don't see a need to implement the sort order. There is nothing to use for sorting anyway. If you want alphabetical order, change the method to:

return 200, {"collection": [ {"id": rolename, "name": rolename}
                  for rolename in sorted(list(self.db.security.role.keys()))]}

All roles are lowercase, so replace "name": rolename with "name": rolename.capitalize()

rouilj commented 4 months ago

Upstream ticket: https://issues.roundup-tracker.org/issue2551344

rouilj commented 4 months ago

LGTM