@@ -514,6 +514,323 @@ handlers.
514
514
515
515
.. _CSP: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
516
516
517
+ Classhelper Web Component
518
+ =========================
519
+
520
+ Version 2.4.0 provides a new classhelper popup written as a
521
+ web component. By installing 3 files and adding a stanza to
522
+ ``interfaces.py`` you can enable the new component.
523
+
524
+ The `development of this component was done by team-03
525
+ <https://github.com/UMB-CS-682-Team-03/tracker>`_ of the
526
+ Spring 2024 CS682 graduate software engineering SDL capstone
527
+ class at the University of Massachusetts - Boston. Their
528
+ documentation is copied/adapted below.
529
+
530
+ File Installation
531
+ -----------------
532
+
533
+ There are three files to install in your tracker. You can
534
+ copy them from the template directory for the classic
535
+ tracker. The location of the template file can be obtained
536
+ by running: ``roundup-admin templates`` and looking for the
537
+ path value of the classic template. If the path value is
538
+ ``/path/to/template``, copy::
539
+
540
+ /path/to/template/html/classhelper.js
541
+ /path/to/template/html/classhelper.css
542
+ /path/to/template/html/_generic.translation
543
+
544
+ to your tracker's html directory.
545
+
546
+ .. :
547
+ remove in 2.5 release if /data/user/roles endpoint
548
+ in rest.py is successful.
549
+
550
+ If you wish to search for user's by role, you have to do one more
551
+ step. Because roles are not an actual class (e.g. like status or
552
+ keyword), you have to set up a new REST endpoint for them. To do this,
553
+ copy::
554
+
555
+
556
+ from roundup.rest import Routing, RestfulInstance, _data_decorator
557
+
558
+ class RestfulInstance:
559
+
560
+ @Routing.route("/roles", 'GET')
561
+ @_data_decorator
562
+ def get_roles(self, input):
563
+ """Return all defined roles. The User class property
564
+ roles is a string but simulate it as a MultiLink
565
+ to an actual Roles class.
566
+ """
567
+ return 200, {"collection":
568
+ [{"id": rolename,"name": rolename}
569
+ for rolename in list(self.db.security.role.keys())]}
570
+
571
+ into the file ``interfaces.py`` in your tracker's home
572
+ directory. You can create this file if it doesn't exist.
573
+ See the `REST documentation <rest.html>`_ for details on
574
+ ``interfaces.py``.
575
+
576
+ The classic tracker does not have the ``/roles`` REST endpoint
577
+ configured. If you are creating a new tracker from the classic
578
+ template, it will show a text based search not a select/dropdown based
579
+ search. By modifying interfaces.py you will enable a dropdown search.
580
+
581
+ Wrapping the Classic Classhelper
582
+ --------------------------------
583
+
584
+ To allow your users to select items in the web interface
585
+ using the new classhelper, you should edit the template files
586
+ in the ``html/`` subdirectory of your tracker. Where you see
587
+ code like::
588
+
589
+ <th i18n:translate="">Superseder</th>
590
+ <td>
591
+ <span tal:replace="structure
592
+ python:context.superseder.field(showid=1,size=20)" />
593
+ <span tal:condition="context/is_edit_ok"
594
+ tal:replace="structure
595
+ python:db.issue.classhelp('id,title',
596
+ property='superseder', pagesize=100)" />
597
+ [...]
598
+ </td>
599
+
600
+ change it to wrap the classhelp span like this::
601
+
602
+ <th i18n:translate="">Superseder</th>
603
+ <td>
604
+ <span tal:replace="structure
605
+ python:context.superseder.field(showid=1,size=20)" />
606
+ <roundup-classhelper
607
+ data-popup-title="Superseder Classhelper - {itemDesignator}"
608
+ data-search-with="title,status,keyword[]-name">
609
+ <span tal:condition="context/is_edit_ok"
610
+ tal:replace="structure
611
+ python:db.issue.classhelp('id,title',
612
+ property='superseder', pagesize=100)" />
613
+ </roundup-classhelper>
614
+ [...]
615
+ </td>
616
+
617
+ which displays a three part classhelper.
618
+
619
+ 1. the search pane includes a text search box for the `title`
620
+ and `status` properties and a dropdown for the keyword property,
621
+ sorted by name in descending order.
622
+ 2. the selection pane will show 100 search results per page.
623
+ It also allows the user to move to the next or previous result
624
+ page.
625
+ 3. the accumulator at the bottom shows all the selected items. It
626
+ also has buttons to accept the items or cancel the
627
+ classhelper, leaving the original page unchanged.
628
+
629
+ Note that the user class is a little different because users without
630
+ an Admin role can't search for a user by Role. So we hide the Role
631
+ search element for non admin users. Starting with::
632
+
633
+ <th i18n:translate="">Nosy List</th>
634
+ <td>
635
+ <span tal:replace="structure context/nosy/field" />
636
+ <span tal:condition="context/is_edit_ok" tal:replace="structure
637
+ python:db.user.classhelp('username,realname,address',
638
+ property='nosy', width='600'" />
639
+ </td>
640
+
641
+ wrap the classhelp span with ``<roundup-classhelper>`` like::
642
+
643
+ <th i18n:translate="">Nosy List</th>
644
+ <td>
645
+ <span tal:replace="structure context/nosy/field" />
646
+ <roundup-classhelper tal:define="search string:name,phone,roles[]"
647
+ tal:attributes="data-search-with python:search
648
+ if request.user.hasRole('Admin') else
649
+ ','.join(search.split(',')[:-1])">
650
+ <span tal:condition="context/is_edit_ok" tal:replace="structure
651
+ python:db.user.classhelp('username,realname,address',
652
+ property='nosy', width='600'" />
653
+ </roundup-classhelper>
654
+ </td>
655
+
656
+ The ``','.join(search.split(',')[:-1])`` removes the last element of
657
+ the search string (``roles[]``) if the user does not have the Admin
658
+ role.
659
+
660
+ <roundup-classhelper> configuration
661
+ -----------------------------------
662
+
663
+ There are two attributes used to configure the classhelper.
664
+
665
+ data-popup-title:
666
+ * this attribute is optional. A reasonable default is
667
+ provided if it is missing.
668
+ * Adding ``data-popup-title`` changes the title of the popup
669
+ window with the value of the attribute.
670
+ * ``{itemDesignator}`` can be used inside the attribute value
671
+ to replace it with the current classhelper usage context.
672
+ E.G. ``data-popup-title="Nosy List Classhelper - {itemDesignator}"``
673
+ will display the popup window title as ``Nosy List Classhelper - issue24``
674
+
675
+ data-search-with:
676
+ * this attribute is optional. If it is not set, a search
677
+ panel is not created provided to allow the users search
678
+ within the class.
679
+ * Adding ``data-search-with`` specifies the fields that can
680
+ be used for searching.
681
+ E.G. ``data-search-with="title,status,keyword"``
682
+ * The search can be customized using the following syntax:
683
+
684
+ * Adding ``[]`` at then end of a field (``"status[]"``)
685
+ will displays a dropdown for the "status" field
686
+ listing all the values the user can access. E.G.::
687
+
688
+ <roundup-classhelper
689
+ data-search-with="title,status[],keyword[]">
690
+ <span tal:condition="context/is_edit_ok"
691
+ tal:replace="structure
692
+ python:db.issue.classhelp('id,title',
693
+ property='superseder', pagesize=100)" />
694
+ </roundup-classhelper>
695
+
696
+ will create a search pane with a text search for title
697
+ and dropdowns for status and keyword.
698
+
699
+ * Adding a sort key after the ``[]`` allows you to
700
+ select the order of the elements in the dropdown. For
701
+ example ``keyword[]+name`` sorts the keyword
702
+ dropdown in ascending order by name. While
703
+ ``keyword[]-name`` sorts the keyword dropdown in
704
+ descending order by name. If the sort order is not
705
+ specified, the default order for the class is used.
706
+
707
+ .. :
708
+ remove in 2.5 release if /data/user/roles endpoint
709
+ in rest.py is successful.
710
+
711
+ * Note that the ``roles`` field for the user class is
712
+ special. If you have not modified ``interfaces.py``,
713
+ roles can not be displayed as a dropdown. It will be
714
+ displayed as a text search field instead.
715
+
716
+ <roundup-classhelper> styling
717
+ -----------------------------
718
+
719
+ The roundup-classhelper component uses minimal styling so it
720
+ can blend in with most trackers. If you want to change the
721
+ styling, you can modify the classhelper.css file in the html
722
+ directory. Even though roundup-classhelper is a web
723
+ component, it doesn't use the shadow DOM. If you don't know
724
+ what this means, it just means that it's easy to style.
725
+
726
+ There is on trick however. Getting the web component to load
727
+ changes to the css file is a bit tricky. Basically the
728
+ browser caches the old file and you have to resort to tricks
729
+ to make it get a new copy of the file.
730
+
731
+ One way to do this is to open to the ``classhelper.css``
732
+ file in your browser and force refresh it. To do this:
733
+
734
+ 1. Open the home page for your Roundup issue tracker in a
735
+ web browser.
736
+
737
+ 2. In the address bar, append ``@@file/classhelper.css``
738
+ to the end of your Roundup URL. For example, if your
739
+ Roundup URL is ``https://example.com/tracker/``, the
740
+ URL you should visit would be
741
+ ``https://example.com/tracker/@@file/classhelper.css``.
742
+
743
+ 3. This will open the `classhelper.css` file in your browser.
744
+
745
+ 4. Press ``Ctrl+Shift+R`` (on Windows and Linux) or
746
+ ``Cmd+Shift+R`` (on macOS). This triggers a hard
747
+ refresh of the page, which forces the browser to
748
+ reload the file and associated resources from the
749
+ server.
750
+
751
+ This should resolve any issues caused by cached or outdated
752
+ files. It is possible that you have to open devtools and set
753
+ the disable cache option in the network panel in extreme
754
+ cases.
755
+
756
+ Also during development, you might want to `set a very low
757
+ cache time
758
+ <customizing.html#changing-cache-control-headers>`_ for
759
+ classhelper.css using something like::
760
+
761
+ Client.Cache_Control['classhelper.css'] = "public, max-age=10"
762
+
763
+
764
+ Translations
765
+ ------------
766
+
767
+ To set up translations for the <roundup-classhelper>
768
+ component, follow these steps.
769
+
770
+ 1. Create a ``messages.pot`` file by running
771
+ ``roundup-gettext <tracker_home_directory>``. This
772
+ creates ``locale/messages.pot`` in your tracker's home
773
+ directory. It extracts all translatable strings from
774
+ your tracker. We will use it as a base template for the
775
+ new strings you want to translate.
776
+ 2. See if you already have a ``.po`` translation file for
777
+ your language in the tracker's locale/ directory. If you
778
+ don't, copy ``messages.pot`` to a .po file for the
779
+ language you want to translate. For example German
780
+ would be at ``de.po`` English would be at ``en.po``
781
+ (for example if you want to change the ``apply`` button
782
+ to say ``Do It``.
783
+
784
+ 3. Edit the new .po file. After the header, add the
785
+ translation entries for the <roundup-classhelper>
786
+ component. For example `next` and `submit` are
787
+ displayed in English when the rest of the interface is
788
+ in German. Add::
789
+
790
+ msgid "submit"
791
+ msgstr "gehen"
792
+
793
+ msgid "next"
794
+ msgstr "nächste"
795
+
796
+ msgid "name"
797
+ msgstr "name"
798
+
799
+ Note: the value for `msgid` is case sensitive. You can
800
+ see the msgid for static strings by looking for
801
+ ``CLASSHELPER_TRANSLATION_KEYWORDS`` in classhelper.js.
802
+
803
+ 4. Save the .po file.
804
+
805
+ 5. Restart your Roundup instance.
806
+
807
+ This should display the missing translations, for more
808
+ details refer to the `translation (i18n) section of the
809
+ developers documentation
810
+ <developers.html#extracting-translatable-messages>`_.
811
+
812
+ Troubleshooting
813
+ ---------------
814
+
815
+ The roundup-classhelper will fallback to using the classic
816
+ classhelper if:
817
+
818
+ * the user doesn't have REST access
819
+ * the browser doesn't support web components
820
+
821
+ It will display an alert modal dialog to the user before triggering
822
+ the classic classhelper as a fallback. A detailed error will be
823
+ printed to the browser console. The console is visible in devtools and
824
+ can be opened by pressing the ``F12`` key.
825
+
826
+ You can disable the classhelper on a per URL basis by adding
827
+ ``#classhelper-wc-toggle`` to the end of the URL. This will prevent
828
+ the web component from starting up.
829
+
830
+ Also you can set ``DISABLE_CLASSHELP = true`` at the top of
831
+ classhelper.js to disable the classhelper without having to make any
832
+ changes to your templates.
833
+
517
834
Configuring native-fts Full Text Search
518
835
=======================================
519
836
0 commit comments