trac-subcomponents-1.3.3/0000755000175500017550000000000014513331133015073 5ustar debacledebacletrac-subcomponents-1.3.3/LICENSE0000644000175500017550000000212512740353600016103 0ustar debacledebacleThe MIT License (MIT) Copyright (c) 2006-2012 John Drinkwater, Niels Sascha Reedijk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. trac-subcomponents-1.3.3/ReadMe.rst0000644000175500017550000000242312740353600016766 0ustar debacledebacleSubcomponents in Trac ===================== The current version is ready for Trac 1.0. There are older, unmaintained, versions for Trac 0.12 and Trac 0.11. What is it? ----------- This plugin alters Trac's behavior so that the interface supports multiple layers of components. In project with lots of components, rearranging these components into several layers can clear up the list of components. What is it not? --------------- The component does not change the data model of the components; it merely manipulates the user interface. So a component ``Web`` with the subcomponents ``Backend`` and ``Frontend`` will be stored in the database as ``Web/Backend`` and ``Web/Frontend``. Installation ------------ To install the module see the TracPlugins page on http://trac.edgewall.org/wiki/TracPlugins. After activating the plugin in the configuration file or through the plugins page, it will be activated without any further configuration. Using subcomponents ------------------- To create components with subcomponents, then you have to add these using the standard component admin page. If you enter the following components: * ``Web`` * ``Web/Frontend`` * ``Web/Backend`` Then the user interface will show the ``Frontend`` and ``Backend`` as a subcomponent of the ``Web`` component. trac-subcomponents-1.3.3/test/0000755000175500017550000000000012740353600016055 5ustar debacledebacletrac-subcomponents-1.3.3/test/Test.rst0000644000175500017550000000654612740353600017541 0ustar debacledebacle======================================= Testing to Prepare for New Trac Version ======================================= Both the Javascript code and the Trac plugin perform manipulation of the original Trac HTML output. With each new version of Trac the hardcoded elements need to be tested and verified. Testing of the python code -------------------------- The web_ui.py code manipulates the following aspects: * pre_process_request - path starts with '/admin/ticket/component' + This overrides the default Trac component save procedure + **Check** if the path of the admin page has changed + **Check** if Trac's procedure for saving a component has changed. See trac/ticket/admin.py _render_admin_panel + **Test** if renaming children still works * post_process_request - path starts with '/ticket' or '/newticket' + This adds the javascript to relevant ticket pages + **Test** if the javascript is actually added + **Check** if the ticket paths are still correct - template is "query.html" + This adds the option of searching for tickets with a specific parent component. This needs the ^ operator + **Check** if the 'begins with' operator actually shows up in the Custom Query page. + **Check** if the template filename has not changed + **Test** whether selecting partial components works - template is "milestone_view.html" + This adds the option to group the open tickets by component. The code then makes sure that it is sorted by parent component, instead of all the separate child components + **Check** if the template filename has not changed + **Test** whether the view actually work + **Test** whether clicking on a component name will send you to a query page where the parent component name has the begins with operator set + **Check** if the ticket count for each master component is correct * filter_stream - filename is "admin_components.html" + This adds the Rename children checkbox to the admin panel + **Check** if the name of the template has not changed + **Check** if the checkbox is in the panel on the right place (below the name edit) - path starts with '/query' + This adds the javascript to the query page. + **Check** if the path of the query page did not change + **Test** if the javascript is actually added Testing of the Javascript ------------------------- * initialiseComponents() - Query page: add filters + **Check** if the XPath to the filter selects is correct - Query page: batch modify + **Check** if the XPath to the batch modify is correct - Query page: existing filters + **Check** if the XPath to existing filters is correct - Ticket/Newticket page: component field + **Check** if the XPath to the field is correct * convertQueryComponent() - **Check** if the XPath to the filter is still correct * convertBatchModifyComponent() - **Check** if the XPath to the component field in the batch modify section is correct Using the init-test-env.py tool ------------------------------- In order to help test the right combination of components and subcomponents it is possible to use the init-test-env.py tool to create a test environment with various default components for testing. The components have a descriptive name which should give an indication of how it functions. trac-subcomponents-1.3.3/test/init-test-env.py0000644000175500017550000000242312740353600021136 0ustar debacledebaclefrom trac.admin.console import TracAdmin from trac.util.text import printout, printerr import sys if __name__ == "__main__": if len(sys.argv) != 2: printerr("Usage: %s \nSupply the path where to create the test environment" % (sys.argv[0])) sys.exit(-1) admin = TracAdmin(sys.argv[1]) admin.onecmd("initenv \"trac-subcomponents test environment\" sqlite:db/trac.db") admin.onecmd("permission add anonymous TRAC_ADMIN") components = ("NoSubcomponents", "SuperComponent", "SuperComponent/SubComponent1", "SuperComponent/SubComponent2", "ForcedSubcomponent/ForcedSub1/ForcedSubSub", "LeafTest", "LeafTest/HasEmptyLeaf", "LeafTest/HasEmptyLeaf/Sub1", "LeafTest/HasEmptyLeaf/Sub2", "LeafTest/NoEmptyLeaf/Sub1", "LeafTest/NoEmptyLeaf/Sub2", "SixSubLevels/s1/s2/s3/s4/s5/s6") for component in components: admin.onecmd("component add %s nobody" % (component,)) printout(""" The test environment is set up at %s. You can run tracd to test this environment. Please make sure that the trac-subcomponents plugin is loaded, either by putting the plugin in %s/plugins, by making it available in general or by manipulation PYTHON_PATH. """ % (sys.argv[1], sys.argv[1])) sys.exit(0) trac-subcomponents-1.3.3/subcomponents/0000755000175500017550000000000014034141612017771 5ustar debacledebacletrac-subcomponents-1.3.3/subcomponents/htdocs/0000755000175500017550000000000014034131310021247 5ustar debacledebacletrac-subcomponents-1.3.3/subcomponents/htdocs/componentselect.js0000644000175500017550000002536614034131310025023 0ustar debacledebacle/* * Copyright 2006, John Drinkwater * Distributed under the terms of the MIT License. * Please note, this script isn't as DOM friendly as I would like, * but IE, as always, has problems with box, replaces it with a hidden input box, and as many boxes original: the  Also rename children'); element.insertAfter(jQuery('div.field')[1]); } // This function creates a MutationObserver that will catch all newly created filters // on the query page and converts the component filter into a subcomponent list function monitorQueryComponents() { var target = jQuery('table.trac-clause')[0]; // Create an observer instance var observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { var newNodes = mutation.addedNodes; // DOM NodeList if (newNodes !== null) { // If there are new nodes added var selectNodes = jQuery(newNodes).find('select'); // jQuery set selectNodes.each(function () { if (this.name.match(/[0-9]+_component$/g)) convertComponentSelect(this, true) }); } }); }); // We need to observe not only the direct children, but also the subtree below those children. // The reason is that if the Component filter is added for the first time, it creates a direct // new `tbody`, but if an additional component filter is added (creating an or clause), it creates // a `tr` within that tbody. We also want to capture those. observer.observe(target, {childList: true, subtree: true}); } function convertBatchModifyComponent() { jQuery('#batchmod_component td.batchmod_property select').each(function () { if (this.name == "batchmod_value_component") convertComponentSelect(this, false); }); } function initialiseComponents() { // Query page: use the existence of the add_filter_* element to detect we are on the query // page, and set up some code for that. if (jQuery('[id^=add_filter_]').length) monitorQueryComponents(); // Query page: batch modify if (jQuery('#add_batchmod_field').length) jQuery('#add_batchmod_field').change(convertBatchModifyComponent); // Query page: existing filters jQuery('tr.component td.filter select').each(function () { convertComponentSelect(this, true) }); // Ticket/Newticket page: component field // Original comment: Opera picks up .names in getElementById(), hence it being at the end now if (jQuery('#field-component').length) { convertComponentSelect(jQuery('#field-component')[0], false); // For the new ticket page // In Trac 1.2.x, the property white-space: nowrap is set, which will cause the subcomponent select // boxes to not wrap. See https://dev.haiku-os.org/ticket/13333 // In Trac 1.3.x and later this is fixed. Set this property explicitly on the element. jQuery('#field-component').parent().css("white-space", "normal"); } // Component Admin: add the [ ] rename children checkbox when applicable if (typeof rename_children !== 'undefined' && rename_children) addRenameChildrenCheckbox(); // Add the reverse function to jQuery, used by convertComponentSelect() jQuery.fn.reverse = [].reverse; } jQuery(document).ready(initialiseComponents); trac-subcomponents-1.3.3/subcomponents/__init__.py0000644000175500017550000000000012740100320022061 0ustar debacledebacletrac-subcomponents-1.3.3/subcomponents/components_view.py0000644000175500017550000000671214034141612023570 0ustar debacledebacle# -*- coding: utf-8 -*- from trac.core import Component, implements from trac.ticket import model from trac.util.html import html from trac.web.chrome import INavigationContributor, ITemplateProvider, \ add_stylesheet from trac.perm import IPermissionRequestor from trac.web.api import IRequestHandler from trac.web.chrome import Chrome, web_context from trac.wiki import format_to_html class ComponentsViewModule(Component): """Adds a separate end-user page that lists all components.""" implements(INavigationContributor, IPermissionRequestor, IRequestHandler, ITemplateProvider) # IPermissionRequestor methods. def get_permission_actions(self): return ['COMPONENT_VIEW'] # INavigationContributor methods def get_active_navigation_item(self, req): return 'components' def get_navigation_items(self, req): if 'COMPONENT_VIEW' in req.perm: yield ('mainnav', 'components', html.a('Components', href=req.href.components())) # IRequestHandler methods def match_request(self, req): return req.path_info == '/components' def process_request(self, req): req.perm.require('COMPONENT_VIEW') component_names = [] subcomponents = [] for component in model.Component.select(self.env): component_names.append(component.name) active_tickets = 0 active_tickets_wo_milestone = 0 for id_, milestone in self.env.db_query(""" SELECT id, milestone FROM ticket WHERE status <> 'closed' AND component=%s """, (component.name,)): active_tickets += 1 if not milestone: active_tickets_wo_milestone += 1 subname, sublevel = \ self.get_subcomponent_name(component.name, component_names) description = format_to_html(self.env, web_context(req), component.description, True) subcomponents.append({ 'name': component.name, 'subname': subname, 'subcomponent_level': sublevel, 'description': description, 'active_tickets': active_tickets, 'active_tickets_without_milestone': active_tickets_wo_milestone, }) data = { 'components': subcomponents, 'no_milestone': 'no_milestone' in req.args, 'hide_description': 'hide_description' in req.args } add_stylesheet(req, 'subcomponents/subcomponents.css') if hasattr(Chrome, 'jenv'): return 'components_jinja.html', data else: return 'components.html', data, None def get_htdocs_dirs(self): from pkg_resources import resource_filename return [('usermanual', resource_filename(__name__, 'htdocs'))] def get_templates_dirs(self): from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] def get_subcomponent_name(self, name, component_names): subname = name for component in component_names: if not name.startswith(component + '/'): continue sub = name[len(component):].lstrip('/') if len(sub) < len(subname): subname = sub sublevel = name[:-len(subname)].count('/') return subname, sublevel trac-subcomponents-1.3.3/subcomponents/web_ui.py0000644000175500017550000001471214034135541021626 0ustar debacledebacle # # Copyright 2009-2019, Niels Sascha Reedijk # All rights reserved. Distributed under the terms of the MIT License. # from pkg_resources import resource_filename from trac.core import * from trac.ticket import model from trac.util.text import unicode_quote_plus from trac.web.api import IRequestFilter from trac.web.chrome import ITemplateProvider, add_notice, add_script, add_script_data from trac.util.translation import _ class SubComponentsModule(Component): """Implements subcomponents in Trac's interface.""" implements(IRequestFilter, ITemplateProvider) # IRequestFilter methods def pre_process_request(self, req, handler): if req.path_info.startswith('/admin/ticket/components/'): if req.method == 'POST' and 'renamechildren' in req.args: if req.args.get('renamechildren') != 'on': return handler # Let trac handle this update # First process the parent component. parent_component_name = req.path_info[25:] # strip '/admin/ticket/components/' from the beginning parent_component = model.Component(self.env, parent_component_name) parent_component.name = req.args.get('name') parent_component.owner = req.args.get('owner') parent_component.description = req.args.get('description') try: parent_component.update() except self.env.db_exc.IntegrityError: raise TracError(_('The component "%(name)s" already ' 'exists.', name=parent_component_name)) # Now update the child components child_components = self._get_component_children( parent_component_name) for component in child_components: component.name = component.name.replace( parent_component_name, req.args.get('name'), 1) component.update() add_notice(req, _("Your changes have been saved.")) req.redirect(req.href.admin('ticket', 'components')) return handler def post_process_request(self, req, template, data, content_type): if req.path_info.startswith('/ticket/') or \ req.path_info.startswith('/newticket') or \ req.path_info.startswith('/query'): add_script(req, 'subcomponents/componentselect.js') if template == 'query.html': # Allow users to query for parent components and include all subs data['modes']['select'].insert(0, {'name': "begins with", 'value': "^"}) if template == 'milestone_view.html': # Group components in the milestone view by base component. if data['grouped_by'] == 'component': newgroups = [] newcomponents = [] for component in data['groups']: componentname = component['name'].split('/')[0] if componentname not in newcomponents: # This component is not yet in the new list of components, add it. newcomponents.append(componentname) # Fix URLs to the querys (we use unicode_quote_plus to replace the '/' # with something URL safe (like the hrefs are) new_hrefs = [] for interval_href in component['interval_hrefs']: new_hrefs.append(interval_href.replace( unicode_quote_plus(component['name']), '^' + componentname)) component['stats_href'] = component[ 'stats_href'].replace( unicode_quote_plus(component['name']), '^' + componentname) component['interval_hrefs'] = new_hrefs # Set the name to the base name (in case this # originally is a subcomponent. component['name'] = componentname newgroups.append(component) else: # This is a subcomponent. Add the stats to the main component. # Note that above two lists are created. Whenever an # item is added to one, an analogous one is added to # the other. This code uses that logic. corecomponent = newgroups[ newcomponents.index(componentname)] mergedstats = corecomponent['stats'] newstats = component['stats'] # Bear with me as we go to this mess that is the group stats # (or of course this hack, depending on who's viewpoint). # First merge the totals mergedstats.count += newstats.count # The stats are divided in intervals, merge these. for i, interval in enumerate(mergedstats.intervals): newinterval = newstats.intervals[i] interval['count'] += newinterval['count'] mergedstats.refresh_calcs() # Now store the new milestone component groups data['groups'] = newgroups if template == "admin_components.html" and data['view'] == 'detail': if len(self._get_component_children(data['component'].name)) > 0: add_script(req, 'subcomponents/componentselect.js') add_script_data(req, {"rename_children": True}) return template, data, content_type # ITemplateProvider methods def get_htdocs_dirs(self): return [('subcomponents', resource_filename(__name__, 'htdocs'))] def get_templates_dirs(self): return [] # Other functions def _get_component_children(self, name): components = model.Component.select(self.env) result = [] # We need the slash otherwise the parent is also found if the parent name was extended, # e.g. Root -> RootFoo. # See #13996 for component in components: if component.name.startswith(name + '/') and component.name != name: result.append(component) return result trac-subcomponents-1.3.3/subcomponents/templates/0000755000175500017550000000000014034141612021767 5ustar debacledebacletrac-subcomponents-1.3.3/subcomponents/templates/components_jinja.html0000644000175500017550000000411414034141612026215 0ustar debacledebacle# extends 'layout.html' # block title ${_("Components")} ${ super() } # endblock title # block head ${ super() } # endblock # block content

${_("Components")}

# if not hide_description: # endif # for component in components: # if not hide_description: # endif # endfor
${_("Name")} ${_("Tickets")}${_("Description")}
${component.subname} # if no_milestone: ${component.active_tickets_without_milestone} # else: ${component.active_tickets} # endif add ticket ${component.description}
# endblock content trac-subcomponents-1.3.3/subcomponents/templates/components.html0000644000175500017550000000474413065646372025073 0ustar debacledebacle Components

Components

${componentrow("even")} ${componentrow("odd")}
Name Tickets Description
${component.subname} ${component.active_tickets_without_milestone} ${component.active_tickets} add ticket ${component.description}
trac-subcomponents-1.3.3/.hgignore0000644000175500017550000000006312740100334016672 0ustar debacledebaclesyntax: glob build dist *.egg-info .DS_Store *.pyc trac-subcomponents-1.3.3/setup.cfg0000644000175500017550000000003313606273004016714 0ustar debacledebacle[egg_info] tag_build = dev trac-subcomponents-1.3.3/setup.py0000644000175500017550000000314214034141612016604 0ustar debacledebacle#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2009, Niels Sascha Reedijk # All rights reserved. Distributed under the terms of the MIT License. # from setuptools import setup setup( name='TracSubcomponents', version='1.3.3', packages=['subcomponents'], package_data={'subcomponents': [ 'htdocs/*.js', 'templates/*.html', ]}, author='Niels Sascha Reedijk', author_email='niels.reedijk@gmail.com', description='Provides support for subcomponents in the interface.', license='MIT', keywords='trac plugin ticket query components', url='https://trac-hacks.org/wiki/SubcomponentsPlugin', classifiers=[ 'Framework :: Trac', # 'Development Status :: 1 - Planning', # 'Development Status :: 2 - Pre-Alpha', # 'Development Status :: 3 - Alpha', # 'Development Status :: 4 - Beta', 'Development Status :: 5 - Production/Stable', # 'Development Status :: 6 - Mature', # 'Development Status :: 7 - Inactive', 'Environment :: Web Environment', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', ], install_requires=['Trac>=1.0dev', ], entry_points={ 'trac.plugins': [ 'subcomponents.components_view = subcomponents.components_view', 'subcomponents.web_ui = subcomponents.web_ui', ] } )