예제 #1
0
class ContainerWidget(WidgetBase):
    """Embed widgets positioned according to the rules defined by a layout.
    """
    def get_widget_params(self, name):
        """Return a dictionary containing arguments specification for
        the widget with specified name.
        """
        return {
            'layout': {
                'desc': """Name of layout used to arrange widgets""",
                'required': True,
            },
            'schema': {
                'desc': """Widgets and position (in JSON)""",
                'required': True,
                'type': JsonField()
            },
            'show_captions': {
                'desc': """Show widget titles""",
                'default': False,
            },
            'title': {
                'desc': """User-defined title""",
            },
        }

    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)

    def render_widget(self, name, context, options):
        """Count ocurrences of values assigned to given ticket field.
        """
        dbsys = DashboardSystem(self.env)
        req = context.req
        params = ('layout', 'schema', 'show_captions', 'title')
        layout, schema, show_captions, title = \
                self.bind_params(name, options, *params)
        lp = dbsys.resolve_layout(layout)
        dbmod = DashboardModule(self.env)
        layout_data = lp.expand_layout(layout, context, {
            'schema': schema,
            'embed': True
        })
        widgets = dbmod.expand_widget_data(req, schema)

        return layout_data['template'], \
                {
                    'title' : title,
                    'data' : dict(
                            context=context,
                            layout=schema,
                            widgets=widgets,
                            title='',
                            default={
                                    'height' : dbmod.default_widget_height or None
                                }
                        ),
                }, \
                context

    render_widget = pretty_wrapper(render_widget, check_widget_name)
예제 #2
0
class TicketQueryWidget(WidgetBase):
    """Display tickets matching a TracQuery using a grid
    """
    def get_widget_params(self, name):
        """Return a dictionary containing arguments specification for
        the widget with specified name.
        """
        return {
            'query': {
                'desc': """Query string""",
                'required': True,
            },
            'max': {
                'default': 0,
                'desc': """Limit the number of results displayed""",
                'type': int,
            },
            'page': {
                'desc': """Page number""",
                'type': int,
            },
            'title': {
                'desc': """Widget title""",
            },
        }

    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)

    def render_widget(self, name, context, options):
        """Execute custom query and render data using a grid
        """
        data = None
        req = context.req
        try:
            params = ('query', 'max', 'page', 'title')
            qstr, maxrows, page, title = self.bind_params(
                name, options, *params)

            fakereq = dummy_request(self.env, req.authname)
            fakereq.args = args = parse_qs(qstr)
            fakereq.arg_list = []
            for k, v in args.items():
                # Patch for 0.13
                fakereq.arg_list.extend((k, _v) for _v in v)
                try:
                    if len(v) == 1:
                        args[k] = v[0]
                except TypeError:
                    pass
            args.update({'page': page, 'max': maxrows})

            qrymdl = self.env[QueryModule]
            if qrymdl is None:
                raise TracError('Query module not available (disabled?)')
            data = qrymdl.process_request(fakereq)[1]
        except TracError, exc:
            if data is not None:
                exc.title = data.get('title', 'TracQuery')
            raise
        else:
예제 #3
0
class TicketRelationsWidget(WidgetBase):
    """Display ticket relations.
    """
    def get_widget_params(self, name):
        """Return a dictionary containing arguments specification for
        the widget with specified name.
        """
        return {
            'tid': {
                'desc': """Source ticket id""",
                'type': int
            },
            'max': {
                'desc': """Limit the number of relations displayed""",
                'type': int
            },
        }

    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)

    def render_widget(self, name, context, options):
        """Gather list of relations and render data in compact view
        """
        title = _('Related tickets')
        params = ('tid', 'max')
        tid, max_ = self.bind_params(name, options, *params)

        ticket = Ticket(self.env, tid)
        data = {
            'ticket': ticket,
            'relations': \
                RelationManagementModule(self.env).get_ticket_relations(ticket),
            'get_resource_shortname': get_resource_shortname,
            'get_resource_summary': get_resource_summary,
        }
        return 'widget_relations.html', {
            'title': title,
            'data': data,
        }, context

    render_widget = pretty_wrapper(render_widget, check_widget_name)
예제 #4
0
class TicketReportWidget(WidgetBase):
    """Display tickets in saved report using a grid
    """
    def get_widget_params(self, name):
        """Return a dictionary containing arguments specification for
        the widget with specified name.
        """
        return {
            'id': {
                'desc': """Report number""",
                'required': True,
                'type': int,
            },
            'page': {
                'default': 1,
                'desc': """Retrieve results in given page.""",
                'type': int,
            },
            'user': {
                'desc': """Render the report for a given user.""",
            },
        }

    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)

    def render_widget(self, name, context, options):
        """Execute stored report and render data using a grid
        """
        data = None
        req = context.req
        try:
            params = ('id', 'page', 'user')
            rptid, page, user = self.bind_params(name, options, *params)
            user = user or req.authname

            fakereq = dummy_request(self.env, req.authname)
            fakereq.args = {'page': page, 'user': user}
            del fakereq.redirect  # raise RequestDone as usual

            rptmdl = self.env[ReportModule]
            if rptmdl is None:
                raise TracError('Report module not available (disabled?)')
            if trac_version < trac_tags[0]:
                args = fakereq, self.env.get_db_cnx(), rptid
            else:
                args = fakereq, rptid
            data = rptmdl._render_view(*args)[1]
        except ResourceNotFound, exc:
            raise InvalidIdentifier(unicode(exc))
        except RequestDone:
            raise TracError('Cannot execute report. Redirection needed')
예제 #5
0
                                            'header' : h,
                                            'index' : hidx,
                                            'value' : t[h['col']]
                                        } \
                                    for hidx, h in enumerate(headers)]],
                                'id' : t['id'],
                                'resource' : Resource('ticket', t['id'])
                            } for t in tickets]) \
                                for group_value, tickets in data['groups'] ]))
            return 'widget_grid.html', \
                    {
                        'title' : title or _('Custom Query'),
                        'data' : data,
                        'ctxtnav' : [
                                tag.a(_('More'), 
                                    href=more_link_href)],
                        'altlinks' : fakereq.chrome.get('links', {}).get('alternate')
                    }, \
                    qryctx

    render_widget = pretty_wrapper(render_widget, check_widget_name)

#--------------------------------------
# Query functions and methods
#--------------------------------------

def exec_query(env, req, qstr='status!=closed'):
    """ Perform a ticket query, returning a list of ticket ID's. 
    """
    return Query.from_string(env, qstr).execute(req)
예제 #6
0
class ProductWidget(WidgetBase):
    """Display products available to the user.
    """

    def get_widget_params(self, name):
        """Return a dictionary containing arguments specification for
        the widget with specified name.
        """
        return {
            'max': {'desc': """Limit the number of products displayed""",
                    'type': int},
            'cols': {'desc': """Number of columns""",
                     'type': int}
        }

    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)

    COMMON_QUERY = 'order=priority&status=!closed&col=id&col=summary' \
                   '&col=owner&col=type&col=status&col=priority&col=product'

    def _get_product_info(self, product, href, resource, max_):
        penv = ProductEnvironment(self.env, product.prefix)
        results = []

        # some queries return a list/tuple, some a generator,
        # hence count() to get the result length
        def count(iter_):
            try:
                return len(iter_)
            except TypeError:
                return sum(1 for _ in iter_)

        query = resource['type'].select(penv)
        for q in itertools.islice(query, max_):
            q.url = href(resource['name'], q.name) \
                if resource.get('hrefurl') \
                else Query.from_string(penv,
                    '%s=%s&%s&col=%s' % (resource['name'], q.name,
                                         self.COMMON_QUERY, resource['name'])
            ).get_href(href)
            q.ticket_count = penv.db_query("""
                SELECT COUNT(*) FROM ticket WHERE ticket.%s='%s'
                AND ticket.status <> 'closed'
                """ % (resource['name'], q.name))[0][0]

            results.append(q)

        # add a '(No <milestone/component/version>)' entry if there are
        # tickets without an assigned resource in the product
        ticket_count = penv.db_query(
            """SELECT COUNT(*) FROM ticket WHERE %s=''
               AND status <> 'closed'""" % (resource['name'],))[0][0]
        if ticket_count != 0:
            q = resource['type'](penv)
            q.name = '(No %s)' % (resource['name'],)
            q.url = Query.from_string(penv,
               'status=!closed&col=id&col=summary&col=owner'
               '&col=status&col=priority&order=priority&%s='
               % (resource['name'],)
            ).get_href(href)
            q.ticket_count = ticket_count
            results.append(q)

        results.sort(key=lambda x: x.ticket_count, reverse=True)

        # add a link to the resource list if there are
        # more than max resources defined
        if count(query) > max_:
            q = resource['type'](penv)
            q.name = _('... more')
            q.ticket_count = None
            q.url = href(resource['name']) if resource.get('hrefurl') \
                else href.dashboard()
            results.append(q)

        return results

    def render_widget(self, name, context, options):
        """Gather product list and render data in compact view
        """
        data = {}
        req = context.req
        title = ''
        params = ('max', 'cols')
        max_, cols = self.bind_params(name, options, *params)

        if not isinstance(self.env, ProductEnvironment):
            for p in Product.select(self.env):
                if 'PRODUCT_VIEW' in req.perm(Neighborhood('product', p.prefix)):
                    penv = ProductEnvironment(self.env, p.prefix)
                    phref = ProductEnvironment.resolve_href(penv, self.env)
                    for resource in (
                        {'type': Milestone, 'name': 'milestone', 'hrefurl': True},
                        {'type': Component, 'name': 'component'},
                        {'type': Version, 'name': 'version'},
                    ):
                        setattr(p, resource['name'] + 's',
                                self._get_product_info(p, phref, resource, max_))
                    p.owner_link = Query.from_string(self.env,
                        'status!=closed&col=id&col=summary&col=owner'
                        '&col=status&col=priority&order=priority'
                        '&group=product&owner=%s' % (p._data['owner'] or '', )
                    ).get_href(phref)
                    p.href = phref()
                    data.setdefault('product_list', []).append(p)
            title = _('Products')

        data['colseq'] = itertools.cycle(xrange(cols - 1, -1, -1)) if cols \
            else itertools.repeat(1)

        return 'widget_product.html', {
            'title': title,
            'data': data,
            'ctxtnav': [tag.a(_('More'), href=req.href('products'))],
        }, context

    render_widget = pretty_wrapper(render_widget, check_widget_name)
예제 #7
0
                                            'value' : t[h['col']]
                                        } \
                                    for hidx, h in enumerate(headers)]],
                                'id' : t['id'],
                                'resource' : Resource('ticket', t['id'])
                            } for t in tickets]) \
                                for group_value, tickets in data['groups'] ]))
            return 'widget_grid.html', \
                    {
                        'title' : title or _('Custom Query'),
                        'data' : data,
                        'ctxtnav' : [
                                tag.a(_('More'),
                                    href=query.get_href(req.href))],
                        'altlinks' : fakereq.chrome.get('links', {}).get('alternate')
                    }, \
                    qryctx

    render_widget = pretty_wrapper(render_widget, check_widget_name)


#--------------------------------------
# Query functions and methods
#--------------------------------------


def exec_query(env, req, qstr='status!=closed'):
    """ Perform a ticket query, returning a list of ticket ID's. 
    """
    return Query.from_string(env, qstr).execute(req)
예제 #8
0
class TicketFieldValuesWidget(WidgetBase):
    """Display a tag cloud representing frequency of values assigned to 
    ticket fields.
    """
    DASH_ITEM_HREF_MAP = {
        'milestone': ('milestone', ),
    }

    def get_widget_params(self, name):
        """Return a dictionary containing arguments specification for
        the widget with specified name.
        """
        return {
            'field': {
                'desc':
                """Target ticket field. """
                """Required if no group in `query`.""",
            },
            'query': {
                'desc': """TracQuery used to filter target tickets.""",
            },
            'title': {
                'desc': """Widget title""",
            },
            'verbose': {
                'desc': """Show frequency next to each value""",
                'default': False,
                'type': bool,
            },
            'threshold': {
                'desc': """Filter items having smaller frequency""",
                'type': int,
            },
            'max': {
                'default': 0,
                'desc': """Limit the number of items displayed""",
                'type': int
            },
            'view': {
                'desc': """Display mode. Should be one of the following

                            - `list` : Unordered value list (default)
                            - `cloud` : Similar to tag cloud
                            """,
                'default': 'list',
                'type': EnumField('list', 'cloud', 'table', 'compact'),
            },
        }

    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)

    def render_widget(self, name, context, options):
        """Count ocurrences of values assigned to given ticket field.
        """
        req = context.req
        params = ('field', 'query', 'verbose', 'threshold', 'max', 'title',
                  'view')
        fieldnm, query, verbose, threshold, maxitems, title, view = \
                self.bind_params(name, options, *params)

        field_maps = {
            'type': {
                'admin_url': 'type',
                'title': 'Types',
            },
            'status': {
                'admin_url': None,
                'title': 'Statuses',
            },
            'priority': {
                'admin_url': 'priority',
                'title': 'Priorities',
            },
            'milestone': {
                'admin_url': 'milestones',
                'title': 'Milestones',
            },
            'component': {
                'admin_url': 'components',
                'title': 'Components',
            },
            'version': {
                'admin_url': 'versions',
                'title': 'Versions',
            },
            'severity': {
                'admin_url': 'severity',
                'title': 'Severities',
            },
            'resolution': {
                'admin_url': 'resolution',
                'title': 'Resolutions',
            },
        }
        _field = []

        def check_field_name():
            if fieldnm is None:
                raise InvalidWidgetArgument('field', 'Missing ticket field')
            tsys = self.env[TicketSystem]
            if tsys is None:
                raise TracError(_('Error loading ticket system (disabled?)'))
            for field in tsys.get_ticket_fields():
                if field['name'] == fieldnm:
                    _field.append(field)
                    break
            else:
                if fieldnm in field_maps:
                    admin_suffix = field_maps.get(fieldnm)['admin_url']
                    if 'TICKET_ADMIN' in req.perm and admin_suffix is not None:
                        hint = _(
                            'You can add one or more '
                            '<a href="%(url)s">here</a>.',
                            url=req.href.admin('ticket', admin_suffix))
                    else:
                        hint = _(
                            'Contact your administrator for further details')
                    return 'widget_alert.html', \
                            {
                                'title' : Markup(_('%(field)s',
                                            field=field_maps[fieldnm]['title'])),
                                'data' : dict(msgtype='info',
                                    msglabel="Note",
                                    msgbody=Markup(_('''No values are
                                        defined for ticket field
                                        <em>%(field)s</em>. %(hint)s''',
                                        field=fieldnm, hint=hint))
                                    )
                            }, context
                else:
                    raise InvalidWidgetArgument(
                        'field', 'Unknown ticket field %s' % (fieldnm, ))
            return None

        if query is None:
            data = check_field_name()
            if data is not None:
                return data
            field = _field[0]
            if field.get('custom'):
                sql = "SELECT COALESCE(value, ''), count(COALESCE(value, ''))" \
                        " FROM ticket_custom " \
                        " WHERE name='%(name)s' GROUP BY COALESCE(value, '')"
            else:
                sql = "SELECT COALESCE(%(name)s, ''), " \
                        "count(COALESCE(%(name)s, '')) FROM ticket " \
                        "GROUP BY COALESCE(%(name)s, '')"
            sql = sql % field
            # TODO : Implement threshold and max

            db_query = req.perm.env.db_query \
                if isinstance(req.perm.env, ProductEnvironment) \
                else req.perm.env.db_direct_query
            with db_query as db:
                cursor = db.cursor()
                cursor.execute(sql)
                items = cursor.fetchall()

            QUERY_COLS = [
                'id', 'summary', 'owner', 'type', 'status', 'priority'
            ]
            item_link = lambda item: req.href.query(col=QUERY_COLS + [fieldnm],
                                                    **{fieldnm: item[0]})
        else:
            query = Query.from_string(self.env, query, group=fieldnm)
            if query.group is None:
                data = check_field_name()
                if data is not None:
                    return data
                raise InvalidWidgetArgument(
                    'field', 'Invalid ticket field for ticket groups')

            fieldnm = query.group
            sql, v = query.get_sql()
            sql = "SELECT COALESCE(%(name)s, '') , count(COALESCE(%(name)s, ''))"\
                    "FROM (%(sql)s) AS foo GROUP BY COALESCE(%(name)s, '')" % \
                    { 'name' : fieldnm, 'sql' : sql }
            db = self.env.get_db_cnx()
            try:
                cursor = db.cursor()
                cursor.execute(sql, v)
                items = cursor.fetchall()
            finally:
                cursor.close()

            query_href = query.get_href(req.href)
            item_link= lambda item: query_href + \
                    '&' + unicode_urlencode([(fieldnm, item[0])])

        if fieldnm in self.DASH_ITEM_HREF_MAP:

            def dash_item_link(item):
                if item[0]:
                    args = self.DASH_ITEM_HREF_MAP[fieldnm] + (item[0], )
                    return req.href(*args)
                else:
                    return item_link(item)
        else:
            dash_item_link = item_link

        if title is None:
            heading = _(fieldnm.capitalize())
        else:
            heading = None

        return 'widget_cloud.html', \
                {
                    'title' : title,
                    'data' : dict(
                            bounds=minmax(items, lambda x: x[1]),
                            item_link=dash_item_link,
                            heading=heading,
                            items=items,
                            verbose=verbose,
                            view=view,
                        ),
                }, \
                context

    render_widget = pretty_wrapper(render_widget, check_widget_name)
예제 #9
0
class TicketGroupStatsWidget(WidgetBase):
    """Display progress bar illustrating statistics gathered on a group
    of tickets.
    """
    def get_widget_params(self, name):
        """Return a dictionary containing arguments specification for
        the widget with specified name.
        """
        return {
            'query': {
                'default': 'status!=closed',
                'desc': """Query string""",
            },
            'stats_provider': {
                'desc': """Name of the component implementing
        `ITicketGroupStatsProvider`, which is used to collect statistics 
        on groups of tickets.""",
                'default': 'DefaultTicketGroupStatsProvider'
            },
            'skin': {
                'desc':
                """Look and feel of the progress bar""",
                'type':
                EnumField('info', 'success', 'warning', 'danger',
                          'info-stripped', 'success-stripped',
                          'warning-stripped', 'danger-stripped')
            },
            'title': {
                'desc': """Widget title""",
            },
            'legend': {
                'desc': """Text on top of the progress bar""",
            },
            'desc': {
                'desc': """Descriptive (wiki) text""",
            },
            'view': {
                'desc': """Display mode to render progress info""",
                'type': EnumField('compact', 'standard')
            },
        }

    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)

    def render_widget(self, name, context, options):
        """Prepare ticket stats
        """
        req = context.req
        params = ('query', 'stats_provider', 'skin', 'title', 'legend', 'desc',
                  'view')
        qstr, pnm, skin, title, legend, desc, view = \
                self.bind_params(name, options, *params)
        statsp = resolve_ep_class(ITicketGroupStatsProvider,
                                  self,
                                  pnm,
                                  default=RoadmapModule(
                                      self.env).stats_provider)
        if skin is not None:
            skin = (skin or '').split('-', 2)

        tickets = exec_query(self.env, req, qstr)
        tickets = apply_ticket_permissions(self.env, req, tickets)
        stat = get_ticket_stats(statsp, tickets)

        add_stylesheet(req, 'dashboard/css/bootstrap.css')
        add_stylesheet(req, 'dashboard/css/bootstrap-responsive.css')
        add_stylesheet(req, 'dashboard/css/roadmap.css')
        return 'widget_progress.html', \
                {
                    'title' : title,
                    'data' : dict(
                            desc=desc, legend=legend, bar_styles=skin,
                            stats=stat, view=view,
                        ),
                }, \
                context

    render_widget = pretty_wrapper(render_widget, check_widget_name)
예제 #10
0
class TimelineWidget(WidgetBase):
    """Display activity feed.
    """
    default_count = IntOption(
        'widget_activity',
        'limit',
        25,
        """Maximum number of items displayed by default""",
        doc_domain='bhdashboard')

    event_filters = ExtensionPoint(ITimelineEventsFilter)

    _filters_map = None

    @property
    def filters_map(self):
        """Quick access to timeline events filters to be applied for a
        given timeline provider.
        """
        if self._filters_map is None:
            self._filters_map = {}
            for _filter in self.event_filters:
                providers = _filter.supported_providers()
                if providers is None:
                    providers = [None]
                for p in providers:
                    self._filters_map.setdefault(p, []).append(_filter)
        return self._filters_map

    def get_widget_params(self, name):
        """Return a dictionary containing arguments specification for
        the widget with specified name.
        """
        return {
            'from': {
                'desc': """Display events before this date""",
                'type': DateField(),  # TODO: Custom datetime format
            },
            'daysback': {
                'desc': """Event time window""",
                'type': int,
            },
            'precision': {
                'desc': """Time precision""",
                'type': EnumField('second', 'minute', 'hour')
            },
            'doneby': {
                'desc': """Filter events related to user""",
            },
            'filters': {
                'desc': """Event filters""",
                'type': ListField()
            },
            'max': {
                'desc': """Limit the number of events displayed""",
                'type': int
            },
            'realm': {
                'desc': """Resource realm. Used to filter events""",
            },
            'id': {
                'desc': """Resource ID. Used to filter events""",
            },
        }

    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)

    def render_widget(self, name, context, options):
        """Gather timeline events and render data in compact view
        """
        data = None
        req = context.req
        try:
            timemdl = self.env[TimelineModule]
            admin_page = tag.a(_("administration page."),
                               title=_("Plugin Administration Page"),
                               href=req.href.admin('general/plugin'))
            if timemdl is None:
                return 'widget_alert.html', {
                    'title': _("Activity"),
                    'data': {
                        'msglabel':
                        _("Warning"),
                        'msgbody':
                        tag_(
                            "The TimelineWidget is disabled because the "
                            "Timeline component is not available. "
                            "Is the component disabled? "
                            "You can enable from the %(page)s",
                            page=admin_page),
                        'dismiss':
                        False,
                    }
                }, context

            params = ('from', 'daysback', 'doneby', 'precision', 'filters',
                      'max', 'realm', 'id')
            start, days, user, precision, filters, count, realm, rid = \
                self.bind_params(name, options, *params)
            if context.resource.realm == 'ticket':
                if days is None:
                    # calculate a long enough time daysback
                    ticket = Ticket(self.env, context.resource.id)
                    ticket_age = datetime.now(utc) - ticket.time_created
                    days = ticket_age.days + 1
                if count is None:
                    # ignore short count for ticket feeds
                    count = 0
            if count is None:
                count = self.default_count

            fakereq = dummy_request(self.env, req.authname)
            fakereq.args = {
                'author': user or '',
                'daysback': days or '',
                'max': count,
                'precision': precision,
                'user': user
            }
            if filters:
                fakereq.args.update(dict((k, True) for k in filters))
            if start is not None:
                fakereq.args['from'] = start.strftime('%x %X')

            wcontext = context.child()
            if (realm, rid) != (None, None):
                # Override rendering context
                resource = Resource(realm, rid)
                if resource_exists(self.env, resource) or \
                        realm == rid == '':
                    wcontext = context.child(resource)
                    wcontext.req = req
                else:
                    self.log.warning("TimelineWidget: Resource %s not found",
                                     resource)
            # FIXME: Filter also if existence check is not conclusive ?
            if resource_exists(self.env, wcontext.resource):
                module = FilteredTimeline(self.env, wcontext)
                self.log.debug('Filtering timeline events for %s',
                               wcontext.resource)
            else:
                module = timemdl
            data = module.process_request(fakereq)[1]
        except TracError, exc:
            if data is not None:
                exc.title = data.get('title', _('Activity'))
            raise
        else:
예제 #11
0
class TimelineWidget(WidgetBase):
    """Display activity feed.
    """
    default_count = IntOption('widget_activity', 'limit', 25, 
                        """Maximum number of items displayed by default""")

    def get_widget_params(self, name):
        """Return a dictionary containing arguments specification for
        the widget with specified name.
        """
        return {
                'from' : {
                        'desc' : """Display events before this date""",
                        'type' : DateField(), # TODO: Custom datetime format
                    },
                'daysback' : {
                        'desc' : """Event time window""",
                        'type' : int, 
                    },
                'precision' : {
                        'desc' : """Time precision""",
                        'type' : EnumField('second', 'minute', 'hour')
                    },
                'doneby' : {
                        'desc' : """Filter events related to user""",
                    },
                'filters' : {
                        'desc' : """Event filters""",
                        'type' : ListField()
                    },
                'max' : {
                        'desc' : """Limit the number of events displayed""",
                        'type' : int
                    },
            }
    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)

    def render_widget(self, name, context, options):
        """Gather timeline events and render data in compact view
        """
        data = None
        req = context.req
        try:
            params = ('from', 'daysback', 'doneby', 'precision', 'filters', \
                        'max')
            start, days, user, precision, filters, count = \
                    self.bind_params(name, options, *params)
            if count is None:
                count = self.default_count

            fakereq = dummy_request(self.env, req.authname)
            fakereq.args = {
                    'author' : user or '',
                    'daysback' : days or '',
                    'max' : count,
                    'precision' : precision,
                    'user' : user
                }
            if start is not None:
                fakereq.args['from'] = start.strftime('%x %X')

            timemdl = self.env[TimelineModule]
            if timemdl is None :
                raise TracError('Timeline module not available (disabled?)')

            data = timemdl.process_request(fakereq)[1]
        except TracError, exc:
            if data is not None:
                exc.title = data.get('title', 'TracReports')
            raise
        else: