Mercurial > p > roundup > code
changeset 7971:fe0348bbe45b
issue2551353 - Add roundup-classhelper for 2.4.0 release
Changes to the classic template are not done yet. Still testing.
This commit has document updates and changes to rest.py.
rest.py:
add /rest/data/user/role endpoint to core so the user doesn't have
to add the /rest/roles endpoint via interfaces.py. It will only send
roles for a user with Admin role and there is no way to override
this currently.
acknowledgements.txt:
Added members of team3 to other contributors. Specified for all
other contributes what they worked on.
upgrading.txt:
added classhelper section and basic template change
directions. Linked to admin_guide for full directions.
admin_guide.txt:
documented install, translation, troubleshooting, config etc.
user_guide.txt:
added section on using the classhelper. Added reference to section
earlier in the doc. Added image for section.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Tue, 21 May 2024 01:17:28 -0400 |
| parents | b63fcfc2c984 |
| children | 425dd9854e34 |
| files | doc/acknowledgements.txt doc/admin_guide.txt doc/images/classhelper-issue-all.png doc/upgrading.txt doc/user_guide.txt roundup/rest.py |
| diffstat | 6 files changed, 465 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/doc/acknowledgements.txt Sat May 18 21:28:05 2024 -0400 +++ b/doc/acknowledgements.txt Tue May 21 01:17:28 2024 -0400 @@ -32,7 +32,10 @@ Other contributers -Norbert Schlemmer +Norbert Schlemmer - docker support + +Bharath Kanama, Nikunj Thakkar, Patel Malav - classhelper web +component development. 2.3 ---
--- a/doc/admin_guide.txt Sat May 18 21:28:05 2024 -0400 +++ b/doc/admin_guide.txt Tue May 21 01:17:28 2024 -0400 @@ -514,6 +514,323 @@ .. _CSP: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP +Classhelper Web Component +========================= + +Version 2.4.0 provides a new classhelper popup written as a +web component. By installing 3 files and adding a stanza to +``interfaces.py`` you can enable the new component. + +The `development of this component was done by team-03 +<https://github.com/UMB-CS-682-Team-03/tracker>`_ of the +Spring 2024 CS682 graduate software engineering SDL capstone +class at the University of Massachusetts - Boston. Their +documentation is copied/adapted below. + +File Installation +----------------- + +There are three files to install in your tracker. You can +copy them from the template directory for the classic +tracker. The location of the template file can be obtained +by running: ``roundup-admin templates`` and looking for the +path value of the classic template. If the path value is +``/path/to/template``, copy:: + + /path/to/template/html/classhelper.js + /path/to/template/html/classhelper.css + /path/to/template/html/_generic.translation + +to your tracker's html directory. + +.. : + remove in 2.5 release if /data/user/roles endpoint + in rest.py is successful. + + If you wish to search for user's by role, you have to do one more + step. Because roles are not an actual class (e.g. like status or + keyword), you have to set up a new REST endpoint for them. To do this, + copy:: + + + 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())]} + + into the file ``interfaces.py`` in your tracker's home + directory. You can create this file if it doesn't exist. + See the `REST documentation <rest.html>`_ for details on + ``interfaces.py``. + + The classic tracker does not have the ``/roles`` REST endpoint + configured. If you are creating a new tracker from the classic + template, it will show a text based search not a select/dropdown based + search. By modifying interfaces.py you will enable a dropdown search. + +Wrapping the Classic Classhelper +-------------------------------- + +To allow your users to select items in the web interface +using the new classhelper, you should edit the template files +in the ``html/`` subdirectory of your tracker. Where you see +code like:: + + <th i18n:translate="">Superseder</th> + <td> + <span tal:replace="structure + python:context.superseder.field(showid=1,size=20)" /> + <span tal:condition="context/is_edit_ok" + tal:replace="structure + python:db.issue.classhelp('id,title', + property='superseder', pagesize=100)" /> + [...] + </td> + +change it to wrap the classhelp span like this:: + + <th i18n:translate="">Superseder</th> + <td> + <span tal:replace="structure + python:context.superseder.field(showid=1,size=20)" /> + <roundup-classhelper + data-popup-title="Superseder Classhelper - {itemDesignator}" + data-search-with="title,status,keyword[]-name"> + <span tal:condition="context/is_edit_ok" + tal:replace="structure + python:db.issue.classhelp('id,title', + property='superseder', pagesize=100)" /> + </roundup-classhelper> + [...] + </td> + +which displays a three part classhelper. + + 1. the search pane includes a text search box for the `title` + and `status` properties and a dropdown for the keyword property, + sorted by name in descending order. + 2. the selection pane will show 100 search results per page. + It also allows the user to move to the next or previous result + page. + 3. the accumulator at the bottom shows all the selected items. It + also has buttons to accept the items or cancel the + classhelper, leaving the original page unchanged. + +Note that the user class is a little different because users without +an Admin role can't search for a user by Role. So we hide the Role +search element for non admin users. Starting with:: + + <th i18n:translate="">Nosy List</th> + <td> + <span tal:replace="structure context/nosy/field" /> + <span tal:condition="context/is_edit_ok" tal:replace="structure + python:db.user.classhelp('username,realname,address', + property='nosy', width='600'" /> + </td> + +wrap the classhelp span with ``<roundup-classhelper>`` like:: + + <th i18n:translate="">Nosy List</th> + <td> + <span tal:replace="structure context/nosy/field" /> + <roundup-classhelper tal:define="search string:name,phone,roles[]" + tal:attributes="data-search-with python:search + if request.user.hasRole('Admin') else + ','.join(search.split(',')[:-1])"> + <span tal:condition="context/is_edit_ok" tal:replace="structure + python:db.user.classhelp('username,realname,address', + property='nosy', width='600'" /> + </roundup-classhelper> + </td> + +The ``','.join(search.split(',')[:-1])`` removes the last element of +the search string (``roles[]``) if the user does not have the Admin +role. + +<roundup-classhelper> configuration +----------------------------------- + +There are two attributes used to configure the classhelper. + +data-popup-title: + * this attribute is optional. A reasonable default is + provided if it is missing. + * Adding ``data-popup-title`` changes the title of the popup + window with the value of the attribute. + * ``{itemDesignator}`` can be used inside the attribute value + to replace it with the current classhelper usage context. + E.G. ``data-popup-title="Nosy List Classhelper - {itemDesignator}"`` + will display the popup window title as ``Nosy List Classhelper - issue24`` + +data-search-with: + * this attribute is optional. If it is not set, a search + panel is not created provided to allow the users search + within the class. + * Adding ``data-search-with`` specifies the fields that can + be used for searching. + E.G. ``data-search-with="title,status,keyword"`` + * The search can be customized using the following syntax: + + * Adding ``[]`` at then end of a field (``"status[]"``) + will displays a dropdown for the "status" field + listing all the values the user can access. E.G.:: + + <roundup-classhelper + data-search-with="title,status[],keyword[]"> + <span tal:condition="context/is_edit_ok" + tal:replace="structure + python:db.issue.classhelp('id,title', + property='superseder', pagesize=100)" /> + </roundup-classhelper> + + will create a search pane with a text search for title + and dropdowns for status and keyword. + + * Adding a sort key after the ``[]`` allows you to + select the order of the elements in the dropdown. For + example ``keyword[]+name`` sorts the keyword + dropdown in ascending order by name. While + ``keyword[]-name`` sorts the keyword dropdown in + descending order by name. If the sort order is not + specified, the default order for the class is used. + +.. : + remove in 2.5 release if /data/user/roles endpoint + in rest.py is successful. + + * Note that the ``roles`` field for the user class is + special. If you have not modified ``interfaces.py``, + roles can not be displayed as a dropdown. It will be + displayed as a text search field instead. + +<roundup-classhelper> styling +----------------------------- + +The roundup-classhelper component uses minimal styling so it +can blend in with most trackers. If you want to change the +styling, you can modify the classhelper.css file in the html +directory. Even though roundup-classhelper is a web +component, it doesn't use the shadow DOM. If you don't know +what this means, it just means that it's easy to style. + +There is on trick however. Getting the web component to load +changes to the css file is a bit tricky. Basically the +browser caches the old file and you have to resort to tricks +to make it get a new copy of the file. + +One way to do this is to open to the ``classhelper.css`` +file in your browser and force refresh it. To do this: + + 1. Open the home page for your Roundup issue tracker in a + web browser. + + 2. In the address bar, append ``@@file/classhelper.css`` + to the end of your Roundup URL. For example, if your + Roundup URL is ``https://example.com/tracker/``, the + URL you should visit would be + ``https://example.com/tracker/@@file/classhelper.css``. + + 3. This will open the `classhelper.css` file in your browser. + + 4. Press ``Ctrl+Shift+R`` (on Windows and Linux) or + ``Cmd+Shift+R`` (on macOS). This triggers a hard + refresh of the page, which forces the browser to + reload the file and associated resources from the + server. + +This should resolve any issues caused by cached or outdated +files. It is possible that you have to open devtools and set +the disable cache option in the network panel in extreme +cases. + +Also during development, you might want to `set a very low +cache time +<customizing.html#changing-cache-control-headers>`_ for +classhelper.css using something like:: + + Client.Cache_Control['classhelper.css'] = "public, max-age=10" + + +Translations +------------ + +To set up translations for the <roundup-classhelper> +component, follow these steps. + + 1. Create a ``messages.pot`` file by running + ``roundup-gettext <tracker_home_directory>``. This + creates ``locale/messages.pot`` in your tracker's home + directory. It extracts all translatable strings from + your tracker. We will use it as a base template for the + new strings you want to translate. + 2. See if you already have a ``.po`` translation file for + your language in the tracker's locale/ directory. If you + don't, copy ``messages.pot`` to a .po file for the + language you want to translate. For example German + would be at ``de.po`` English would be at ``en.po`` + (for example if you want to change the ``apply`` button + to say ``Do It``. + + 3. Edit the new .po file. After the header, add the + translation entries for the <roundup-classhelper> + component. For example `next` and `submit` are + displayed in English when the rest of the interface is + in German. Add:: + + msgid "submit" + msgstr "gehen" + + msgid "next" + msgstr "nächste" + + msgid "name" + msgstr "name" + + Note: the value for `msgid` is case sensitive. You can + see the msgid for static strings by looking for + ``CLASSHELPER_TRANSLATION_KEYWORDS`` in classhelper.js. + + 4. Save the .po file. + + 5. Restart your Roundup instance. + +This should display the missing translations, for more +details refer to the `translation (i18n) section of the +developers documentation +<developers.html#extracting-translatable-messages>`_. + +Troubleshooting +--------------- + +The roundup-classhelper will fallback to using the classic +classhelper if: + + * the user doesn't have REST access + * the browser doesn't support web components + +It will display an alert modal dialog to the user before triggering +the classic classhelper as a fallback. A detailed error will be +printed to the browser console. The console is visible in devtools and +can be opened by pressing the ``F12`` key. + +You can disable the classhelper on a per URL basis by adding +``#classhelper-wc-toggle`` to the end of the URL. This will prevent +the web component from starting up. + +Also you can set ``DISABLE_CLASSHELP = true`` at the top of +classhelper.js to disable the classhelper without having to make any +changes to your templates. + Configuring native-fts Full Text Search =======================================
--- a/doc/upgrading.txt Sat May 18 21:28:05 2024 -0400 +++ b/doc/upgrading.txt Tue May 21 01:17:28 2024 -0400 @@ -250,6 +250,44 @@ .. _issue2551282: https://issues.roundup-tracker.org/issue2551282 .. _issue2551115: https://issues.roundup-tracker.org/issue2551115 +Add new classhelper to your templates (optional) +------------------------------------------------ + +The classic classhelper invoked by the ``(list)`` link in your +issue.item.html template can be greatly improved by wrapping the +links with the new web-component based ``roundup-classhelper``. + +The new classhelper: + + * allows you to select items from multiple pages + * is usable with a content security policy + * is more easily styled + +To deploy it, install the required files and wrap classhelp calls +in the new ``<roundup-classhelper>`` component. For example, +wrap:: + + <span tal:condition="context/is_edit_ok" tal:replace="structure + python:db.user.classhelp('username,realname,address', + property='nosy', width='600'" /> + +so it looks like:: + + <roundup-classhelper + data-search-with="username,phone,roles[]"> + + <span tal:condition="context/is_edit_ok" tal:replace="structure + python:db.user.classhelp('username,realname,address', + property='nosy', width='600')" /> + + </roundup-classhelper> + +to allow the user to search by: username, phone number and use a +select/dropdown to search by role. Full details about the +attributes and installation instructions can be found in the +`classhelper documentation`_ in the admin guide. + + Disable performance improvement for wsgi mode (optional) -------------------------------------------------------- @@ -2606,6 +2644,7 @@ .. _PostgreSQL's full text search: https://www.postgresql.org/docs/current/textsearch.html .. _`administration guide notes on native-fts`: admin_guide.html#configuring-native-fts-full-text-search .. _Configuring Compression: admin_guide.html#configuring-compression +.. _classhelper documentation: admin_guide.html#classhelper-web-component .. _Software Upgrade: admin_guide.html#software-upgrade .. _new search permissions for query in 1.4.17: upgrading-history.html#new-search-permissions-for-query-in-1-4-17
--- a/doc/user_guide.txt Sat May 18 21:28:05 2024 -0400 +++ b/doc/user_guide.txt Tue May 21 01:17:28 2024 -0400 @@ -155,6 +155,10 @@ ``keyword=-1`` match issues with no keywords set +When entering the value for a constrained property you may have a +helper (also called classhelper) available by clicking on a link +usally displayed as ``(list)``. See the section `Using the +Classhelper`_ for details. Date properties ~~~~~~~~~~~~~~~ @@ -440,6 +444,92 @@ .. _`documentation for configuring the native-fts`: admin_guide.html#configuring-native-fts-full-text-search +Using the Classhelper +--------------------- + +The classhelper makes finding the id number or name for linked items +easier. It is usually invoked from the parent window by clicking on +the ``(list)`` link. There are two classhelpers: classic and +component. This documentation discusses the newer component +classhelper available with Roundup 2.4.0 or newer. If there is a +problem with the component classhelper, it reports the problem and +falls back to using the classic classhelper. + +The component classhelper is displayed in a popup window. You can +interact with the original window by moving the popup out of the way +or minimizing it. If you don't see a popup, check to see if your +browser has disabled popup windows. + +The classhelper has three parts: + + 1. an optional search pane + 2. a selection pane + 3. an accumulator pane + +.. image:: images/classhelper-issue-all.png + :width: 675 + :height: 914 + :alt: Image of the new component classhelper popup. The image shows + the operating system window decorations with a title of "Superseder + Classhelper - issue2". Then it shows a search panel with options to + enter text to search issue titles or status and a select/dropdown + to search by a keyword on the issue. Below the options are search + and reset buttons. Below the search panel is a selection + panel that shows buttons to move to the previous or next pages and + says that it is displaying items 26 to 50. This is followed by a + scrollable table of issues where each row has a checkbox and the + box for issue 114 is checked. At the bottom of the window is a text + box that lists a number of issue numbers including 114. To the + right of the text box are apply and cancel buttons. + + +The search pane has text or select/dropdown fields to search for a +matching item. The image above shows a search for issues. The Title +and Status properties can be matched using a text input while the +Keyword property can be selected from a dropdown. Hitting enter while +in a text input will trigger a search and the results will be +displayed below the search pane in the selection pane. Tabbing to the +search button and pressing enter will also trigger a search. The reset +button will clear the search form. + +Below the search pane is the select pane. It lists the number of items +displayed (26-50) and includes two buttons to move to the previous or +next page of search results. If there is no search pane, this will +display a page of items from all the items in the class. Below the +pagination component is the scrollable selection table. Each row in +the table has a checkbox and one or more columns of data about the +item. Clicking on a row toggles the item's checkbox and adds or +removes the id or name for the item in the accumulator's +display. Arrow keys or tab/shift-tab can be used to scroll through +each item in the selection table. The space key or enter will +select/deselect the item. You can jump to the page controls using the +'<' and '>' keys. Once the page control button is focused, press enter +to trigger a page change. + +The bottom pane consists of a text input called the accumulator +display. It lists all the items that have been selected. The first two +items in this example were selected from the previous selection +page. Next to the display are the apply and cancel buttons. You can +jump to the apply button quickly by pressing Shift-Enter as long as +you are not in a search input. Once the apply button is focused, press +enter to copy the items in the display to the associated field on the +parent window. If you activate the cancel button or close the window +using the window decoration, the classhelper will close and not change +the parent window. + +The classhelper can also be used in read-only mode. In this mode, the +accumulator is not shown. Also the checkboxes are not displayed. To +close the classhelper in read-only mode use the window decoration or a +hotkey (e.g. control-w). + +You can have multiple classhelpers up at a time. The title on the +window identifies the property and item the classhelper will +modify. For example the image shows the superseder for issue2. + +Do not refresh the classhelper window using the ``F5`` key. This will +erase the contents of the window and you will have to close it and +invoke the link from the parent window again. + Access Controls ---------------
--- a/roundup/rest.py Sat May 18 21:28:05 2024 -0400 +++ b/roundup/rest.py Tue May 21 01:17:28 2024 -0400 @@ -1029,6 +1029,21 @@ self.client.setHeader("Allow", "OPTIONS, GET, POST") return 200, result + @Routing.route("/data/user/roles", 'GET') + @_data_decorator + def get_roles(self, input): + """Return all defined roles for users with Admin role. + The User class property roles is a string but simulate + it as a MultiLink to an actual Roles class. + """ + if not self.client.db.user.has_role(self.client.db.getuid(), "Admin"): + raise Unauthorised( + 'User does not have permission on "user.roles"') + + return 200, {"collection": + [{"id": rolename,"name": rolename} + for rolename in list(self.db.security.role.keys())]} + @Routing.route("/data/<:class_name>/<:item_id>", 'GET') @_data_decorator def get_element(self, class_name, item_id, input):
