Display a jsGantt chart in a wiki page
A plugin which allows Trac ticket data to be displayed in a jsGantt chart in a wiki page. Tasks and milestones are links to the corresponding ticket or milestone.
Configurable field names allow integration with other plugins such as MasterTicketsPlugin (for dependencies), SubticketsPlugin (for parent/child relationships) and TimingAndEstimationPlugin (for estimated and total hours).
Over time, the project management aspects of the Gantt chart have been isolated in the
TracPM module which has grown to include a query helper (which allows finding tickets based on dependencies) and a ticket change listener (which reschedules tickets based on various constraints) and other support functions. Ultimately, these features -- which really have nothing to do with the Gantt chart -- will be moved to a separate
[[TracJSGanttChart(sample=1)]] displays the sample project from jsgantt.com.
[[TracJSGanttChart(milestone=Test)]] displays all the tickets in the Test milestone.
[[TracJSGanttChart(goal=1234,schedule=asap)]] finds all the tickets required for ticket 1234 (by following the Required For dependency) and schedules them As Soon As Possible. In a ticket description or comment, the ticket ID may be replaced by
[[TracJSGanttChart(root=self)]] (when used in a ticket description or comment) finds all the tickets which are descendants of the current ticket (by following the parent/child relationship) and scheduled them As Late As Possible (the default algorithm). (
self can be replaced by one or more pipe-delimited ticket IDs as for the
id option, e.g.,
[[TracJSGanttChart(scheduled=1,display=owner:chrisn)]] find all the scheduled tickets and display only those owned by user
The chart display can be controlled with the following macro arguments:
| ||Caption to place to right of tasks: None, || |
| ||Show (|| |
| ||Field to use to color tasks. Useful fields are || |
| ||Format to display dates: || |
| ||0.10||Filter for limiting display of tickets. ||None|
| ||0.10||Resolve resource conflicts (1) or not (0) when scheduling tickets.||0|
| ||Show (|| |
| ||Show (|| |
| ||0.9||Show (|| |
| ||Initial display format, one of those listed in || |
| ||Formats to show for Gantt chart. A pipe-separated list of || |
| ||0.9||Ticket(s) to show predecessors of. When using something like MasterTicketsPlugin to maintain ticket dependencies, you may create a Gantt showing a ticket and all of its predecessors with |
Multiple goals may be provided like
When used in a ticket description or comment,
| ||Hours worked per day|| |
| ||Width (in pixels) of left table. The one contains task names, etc. on the left of the Gantt chart.||None|
| ||0.8||Show milestones for displayed tickets (|| |
| ||How many levels of task hierarchy to show open. || |
| ||Show (|| |
| ||Ticket(s) to show descendants of. When using something like Subtickets plugin to maintain a tree of tickets and subtickets, you may create a Gantt showing a ticket and all of its descendants with |
Multiple roots may be provided like
When used in a ticket description or comment,
| ||Display (|| |
| ||Schedule algorithm: as-last-as-possible (|| |
| ||Show (|| |
| ||Show (|| |
| ||0.8||Map (|| |
| ||Order of fields used to sort tickets before display. ||wbs|
| ||Date to scroll to when chart is initially drawn. A date in ||None|
| ||How to handle clicks on task in the chart. JSGantt style opening a dedicated window (|
Site-wide defaults for macro arguments may be set in the
[trac-jsgantt] section of
option.<opt> overrides the built-in default for
<opt> from the table above.
All other macro arguments are treated as TracQuery specification, eg
milestone=MS1|MS2, to control which tickets are displayed.
The TracPM module provides several interfaces that can be implemented to adapt the Project Management (PM) features to local business rules. The TracPM module will be a separate plugin in the future. These interfaces are defined in
TracPM defers decisions about ticket ordering to an
- Before any comparison is done,
ITaskSorter.prepareTasks()is called so that complex keys can be precomputed, external data can be prefetched, etc. to make the comparisons faster and easier.
prepareTasks()is passed a hash of ticket (tasks). The index of the hash is the ticket ID. The elements of the hash are hashes of Trac ticket attributes. They are not Trac ticket objects.
prepareTasks()can add to or modify attributes of the ticket. The changes or additions will be available through the life of the scheduling process but will be safely removed before the tickets are returned to the caller.
- When making decisions about which ticket to schedule first, TracPM sorts the candidate tickets with
compareTasks()is passed two hashes, the attributes of the two tickets to compare. Any attributes added or changed by
prepareTasks()are available to
class ITaskSorter(Interface): # Process task list to precompute keys or otherwise make # compareTasks() more efficient. def prepareTasks(self, ticketsByID): """Called to prepare tasks for sorting.""" # Provide a compare function for sorting tasks. # Maybe be used as cmp argument for sorted(), and list.sort(). # Returns -1 if t1 < t2, 0 if they are equal, 1 if t1 > t2. def compareTasks(self, t1, t2): """Called to compare two tasks"""
TracPM provides two implementations of
prepareTasks()prefetches the numeric priority values and gives an average priority to any ticket without one.
compareTasks()compares tickets based only on their numeric priority
prepareTasks()prefetches the numeric priority values and computes an "effective priority" for each ticket that takes into account its parent's ticket's priority. A high priority child of a low priority parent has a lower effective priority than a low priority child of a high priority parent.
compareTasks()compares tickets based only on their effective priority.
SimpleSorter is used by default if no sorter is enabled in
ProjectSorter are both derived from
BaseSorter which provides several functions which may be useful in implementing custom sorters.
_buildEnumMap(field)gets numeric values from the Trac database for enums such as priority and severity. The built-in sorters use it to retrieve values for the priority field in their constructor. It returns a hash, containing numeric values and indexed by name (e.g., 'major').
averageEnum()computes the average value in the hash returned by
compareOneField(field, t1, t2)compares two tickets based on just one field.
TracPM defers decisions about resource availability to an
- When scheduling a task, TracPM calls
IResourceCalendar.hoursAvailable()to determine how much of a task can be done on a date.
class IResourceCalendar(Interface): # Return the number of hours available for the resource on the # specified date. def hoursAvailable(self, date, resource = None): """Called to see how many hours are available on date"""
TracPM provides one implementation of
hoursAvailable()returns 8 for Monday through Friday and 0 for Saturday and Sunday.
TeamCalendarPlugin provides an implementation which returns 0 hours for any day that a developer is marked unavailable in the calendar.
An interface for task schedulers exists, but is not yet well documented.
class ITaskScheduler(Interface): # Schedule each the ticket in tickets with consideration for # dependencies, estimated work, hours per day, etc. # # Assumes tickets is a list returned by TracPM.query(). # # On exit, each ticket has a start and finish that can be accessed # with TracPM.start() and finish(). No other changes are made. def scheduleTasks(self, options, tickets): """Called to schedule tasks"""
TicketRescheduler module is enabled,
TracPM maintains private tables with task scheduling information. The
schedule table has the current schedule:
Column | Type | Modifiers --------+---------+----------- ticket | integer | not null start | bigint | finish | bigint | Indexes: "schedule_pkey" PRIMARY KEY, btree (ticket) "schedule_ticket_idx" btree (ticket)
schedule_change table holds a record of changes in the schedule over time:
Column | Type | Modifiers -----------+---------+----------- ticket | integer | not null time | bigint | not null oldstart | bigint | oldfinish | bigint | newstart | bigint | newfinish | bigint | Indexes: "schedule_change_pk" PRIMARY KEY, btree (ticket, "time") "schedule_change_ticket_idx" btree (ticket) "schedule_change_time_idx" btree ("time")
It is possible, but not yet supported, to use the data in
schedule_change to reconstruct a baseline schedule for an effective date and then report changes from that baseline.
These tables are maintained by a ticket change listener which looks for changes to fields which might affect schedule then:
- identifies all tickets that were active (required for an open milestone or an active ticket of the configured milestone ticket type)
- identifies all tickets that are now active (as a result of the changes)
- removes any tickets that were active but are no longer from
schedule(updating the history in
- runs the scheduling algorithm on all tickets that are now active
- writes any changes to
schedule(updating history in
With this data in place, you can show a Gantt chart of scheduled tickets with dates as in the database (
[[TracJSGanttChart(scheduled=1)]]) or create reports of the scheduled tickets.
SELECT p.value AS __color__, t.id AS ticket, summary, priority, description AS _description, est.value AS est, act.value AS act, -- This bit is specific to PostgreSQL (SELECT to_date(to_timestamp((sched.finish/1000000))::text, 'YYYY-MM-DD')) AS due, status FROM ticket AS t INNER JOIN schedule AS sched ON (t.id = sched.ticket) INNER JOIN enum AS p ON (p.name = t.priority AND p.type = 'priority') LEFT OUTER JOIN ticket_custom AS est ON (t.id = est.ticket AND est.name = 'estimatedhours') LEFT OUTER JOIN ticket_custom AS act ON (t.id = act.ticket AND act.name = 'totalhours') WHERE t.owner = '$USER' AND status <> 'closed' ORDER BY due NULLS FIRST, p.value
Existing bugs and feature requests for TracJsGanttPlugin are here.
If you have any issues, create a new ticket.
The following is a list of known issues:
- Scroll bar can be longer than vRightTable
- Shift of taskbar in graph
- if fields.start and fields.finish not set in trac.ini, use datetime of 'accepted' and 'closed'
- start date in chart different to set start date in ticket
- Tickets with parent don't layout chronologically
- [Patch] Milestones with no due date are shown one day late
- with IE9: chart doesn't resize to web page width
- Can't handle collaborative working
- Miscellaneous refactoring and minor clean ups
- Dependencies from MasterTickets not handled correctly for "goal="
- In ALAP scheduling, today is not a valid end date if no dates are assigned
- schedule=asap: Partially completed tasks should have unworked hours starting today
- Hours to do on chart not over weekends
- Nothing displayed. Always sends sample data
- Scheduling Ignores Milestones
- NameError: global name 'fieldname' is not defined
- Scheduling sometimes fails completely
- Problem with TracJsGanttPlugin - no gantt
- Parameter openLevel works not
- Filter for milestone doesn't work with umlauts
- [PATCH] if the milestone due date is the only given date at all, ALAP scheduling must use it as end date of last successor ticket
- in ASAP mode: end date of ticket 1 is after start date of ticket 2 although 2 depends on 1
- partially completed tasks should be remainghours long
- execute() takes at least 2 arguments (1 given)
- Closed tickets are scheduled to future with resource leveling
- Weird results when using planned_finish
- Macro TracJSGanttChart fails when using Trac 1.1.1's date ticket fields
- Cannot batch modify tickets
- PATCH: better resize the chart to be smaller than to overlap other page elements
- TypeError: not all arguments converted during string formatting
- TypeError: object of type 'NoneType' has no len()
- doResourceLeveling chart-parameter breaks ticket termination at its milestone
- ProgrammingError: You cannot execute SELECT statements in executemany().
- Collapse/Uncollapse works only for first Gantt displayed in same page
- Create a NEW ticket -> rescheduleTickets OperationalError: near ",": syntax error
- jsGantt needs priorities, ticket types and severities
- query for bubbled up finish date - for use in Time Reports
- trac-jsgantt versus trac-jsGantt
- Rescheduling not working?
- tracpm.py - TicketRescheduler has undefined variables linkFieldsName and linkFieldsNames
- Chart as Milestone description hiding filter box
- Cannot display multiple Gantt chart
- Display a legend of task colors and their meaning, eg which milestone or owner they represent.
- Allow some tasks to be open or closed by default.
- Display critical path.
- Display slack time.
Download the zipped source from here.
The following installation steps apply:
- Install globally with:
sudo easy_install https://trac-hacks.org/svn/tracjsganttplugin/0.11/
- Enable the plugin by updating your TracIni file (
../conf/trac.ini) as follows:
[components] tracjsgantt.* = enabled
- Configure the project management support for the plugin in its own configuration section, placed into
trac.inifile as follows:
[TracPM] # To work with TimingAndEstimationPlugin for percent complete, define ticket fields to use as the data source for: fields.estimate = estimatedhours fields.worked = totalhours # Each unit in estimate is 1/8 of a day days_per_estimate = 0.125 # To work with MasterTicketsPlugin for dependencies, define ticket fields to use as the data source for predecessor (pred) and successor (succ). fields.pred = blockedby fields.succ = blocking # Alternatively, configure a pred-succ relation like: # relation.pred-succ = mastertickets,source,dest # This causes TracPM to query the mastertickets table rather than # parse blockedby and blocking custom fields. This can be somewhat # faster. # To work with SubticketsPlugin for parent/child relationships, # Ticket field to use as the data source for the parent fields.parent = parents # Alternatively, configure a parent-child relation like: # relation.parent-child = subtickets,parent,child # This causes TracPM to query the subtickets table rather than parse # the `parents` custom field. This can be somewhat faster. # When using SubticketsPlugin via a parent-child relation, do not # configure a parent_format (next). # To work with ChildTickets plugin parent_format, '#%s', # Format of ticket IDs in parent field (default: %s). parent_format = %s # Custom fields for start and due dates # Ticket field to use as the data source for start date (default: None). fields.start = userstart # Ticket field to use as the data source for finish date (default: None). fields.finish = userfinish # Format for ''start'' and ''finish'' date strings (default: '%Y-%m-%d') date_format = %Y-%m-%d # Ticket type for milestone-like tickets (default: 'milestone'). # Used to be milestone_type, that setting is now deprecated. goal_ticket_type = milestone # Ticket field to use as the data source for the percent complete column (default: None). fields.percent = complete # Hours represented by each unit of estimated work (default: 1). hours_per_estimate = 1 # Default work for an unestimated task, same units as estimate (default: 4.0). default_estimate = 4.0 # How much work may be remaining when a task goes over estimate, same units as estimate (default: 0.0). estimate_pad = 0.0
- See configuration details below for explanations and more options.
- Additionally, site-wide defaults for macro arguments may be set at [trac-jsgantt] section. More details about them see #Arguments section.
[trac-jsgantt] option.formats = day|week|month|quarter option.format = month ## How and which 'columns' to show option.lwidth = 300 option.res = 0 option.dur = 0 option.comp = 0 option.startDate = 0 option.endDate = 1 option.dateDisplay = yyyy-mm-dd ## How and what to show on Gantt graph option.showdep = 1 option.expandClosedTickets = 1 option.schedule = asap option.openLevel = 0 option.colorBy = priority option.userMap = 0 option.omitMilestones = 0 option.caption = Resource option.hoursPerDay = 8.0
- Restart web server on command line:
sudo /etc/init.d/apache2 restart
TracJsGanttPlugin is intended to be flexible enough to get data from plugins by configuring the field names for those plugins in
trac.ini. It is known to work with TimingAndEstimationPlugin (for estimated and total hours), MasterTicketsPlugin (for FS dependencies), and SubticketsPlugin for parent/child relationships. Custom fields for start and finish date are also supported.
All of the
fields.* items name custom fields which may contain data for the Gantt.
fields.workedare both configured, the plugin attempts to display (100 *
fields.estimate) as the percent complete. The example works with TimingAndEstimationPlugin. Alternatively, if
percentis configured, the plugin attempts to display it as the percent complete (it should be a number from 0 to 100). If none of those are configured, all tasks will be marked as 0% complete.
fields.succare configured the plugin uses them to determine the task dependencies. The example works with MasterTicketsPlugin. If these fields are not configured, no dependencies are shown.
fields.parentis configured, it is the field which holds the parent ticket number. The example works with SubticketsPlugin. If this field is not configured, no parent/child relationship will be displayed. If it is configured, the Gantt can be collapsed by the user to show or hide subtasks. The
parent_formatdetermines the format of the content of the
parentfield. Use "%s" (default) for SubticketsPlugin, or "#%s" for ChildTicketsPlugin.
fields.finishare configured, the plugin uses them to set task start and finish dates. The
date_formatfield is a Python
strptime()format specifier which describes the contents of
fields.finish. If these fields are not configured, all tasks end today and have a 1-day duration.
fields.finishare both configured, and
fields.startis not configured or not on the ticket, the plugin attempts to determine the start of the task from
start = fields.finish - fields.estimatewith consideration for weekends and hours per day.
goal_ticket_typemay be used to have a custom ticket type show up as milestones on the chart. If this field is not specified, only Trac milestones are displayed as milestones.