def _get_user_data_(self, req, milestone, field, results, fields):
        """Get data grouped by users. Includes extra user info."""
        ats = AgileToolsSystem(self.env)
        sp = SimplifiedPermissions(self.env)

        tickets_json = defaultdict(lambda: defaultdict(dict))

        all_users = []
        user_data = {}
        use_avatar = self.config.get('avatar','mode').lower() != 'off'

        # TODO: allow the task board to respect user groups
        for group, data in sp.group_memberships().items():
            for member in data['members']:
                if member.sid not in user_data:
                    all_users.append(member.sid);
                    user_data[member.sid] = {
                        'name': member.get("name", member.sid),
                        'avatar': use_avatar and req.href.avatar(member.sid) or None
                    }

        def name_for_sid(sid):
            return user_data[sid]["name"] if sid in user_data else sid

        options = [""] + sorted(all_users, key=name_for_sid)

        for result in results:
            ticket = Ticket(self.env, result['id'])
            filtered_result = dict((k, v)
                                   for k, v in result.iteritems()
                                   if k in fields)
            filtered_result['position'] = ats.position(result['id'])
            filtered_result['_changetime'] = to_utimestamp(result['changetime'])
            # we use Trac's to_json() (through add_script_data), so
            # we'll replace any types which can't be json serialised
            for k, v in filtered_result.items():
                if isinstance(v, datetime): filtered_result[k] = pretty_age(v)
            group_field_val = ticket.get_value_or_default(field["name"]) or ""
            tickets_json[group_field_val][result["id"]] = filtered_result

        return (field["name"], tickets_json, options, user_data)
    def _get_standard_data_(self, req, milestone, field, results, fields):
        """Get ticket information when no custom grouped-by method present."""
        ats = AgileToolsSystem(self.env)
        tickets_json = defaultdict(lambda: defaultdict(dict))

        # Allow for the unset option
        options = [""] + [option for option in field["options"]]

        for result in results:
            ticket = Ticket(self.env, result['id'])
            filtered_result = dict((k, v)
                                   for k, v in result.iteritems()
                                   if k in fields)
            filtered_result['position'] = ats.position(result['id'])
            filtered_result['_changetime'] = to_utimestamp(result['changetime'])
            # we use Trac's to_json() (through add_script_data), so
            # we'll replace any types which can't be json serialised
            for k, v in filtered_result.items():
                if isinstance(v, datetime): filtered_result[k] = pretty_age(v)
            group_field_val = ticket.get_value_or_default(field["name"]) or ""
            tickets_json[group_field_val][result["id"]] = filtered_result

        return (field["name"], tickets_json, options)
    def get_timeline_markup(self, req, call, maxrows=10):
        """
        Generates the markup needed when this component is called both 
        explicitly inside wiki pages and implicitly by the ISideBarBoxProvider.

        Note this code uses methods from the trac TimelineModule module.
        """

        chrome = Chrome(self.env)

        # last 14 days should be enough
        stop = datetime.now(req.tz)
        start = stop - timedelta(days=50)

        # use code from trac/timeline to generate event data
        timeline = TimelineModule(self.env)
        available_filters, filters = timeline.get_filters(req)
        include_authors, exclude_authors = timeline.authors()
        events = timeline.get_events(req, start, stop, filters, available_filters, 
                                     include_authors, exclude_authors, maxrows)
        show_gravatar = self.config.get('avatar','mode').lower() != 'off'

        # create the mark up
        context = Context.from_request(req)
        event_list = tag.ul(class_="timeline-list no-style")
        for event in events:
            event_title = event['render']('title', context)
            event_url = event['render']('url', context)
            event_list.append(tag.li(
                show_gravatar and tag.img(
                    src=req.href.avatar(event['author'] if event['author'] else 'anonymous'),
                    class_="avatar",
                ) or "",
                tag.span(
                    chrome.authorinfo(req, event['author']),
                    class_="author"
                ),
                tag.span(
                    pretty_age(event['date']),
                    class_="date",
                ),
                tag.div(
                    tag.i(class_="event-type fa fa-" + event['kind']),
                    tag.a(
                        event_title,
                        href=event_url,
                    ),
                    class_="event-summary"
                ),
                class_="cf"
            ))

        # if the markup is being generated via ISideBarBoxProvider we don't 
        # need to add a span3 class 
        div_classes = "box-sidebar"
        if call == "macro":
            div_classes += " span3 right"
        return tag.div(
                    tag.h3(
                        tag.i(
                            class_="fa fa-calendar"
                        ),
                        " Recent Project Activity"
                    ),
                    event_list,
                    class_=div_classes,
                )
    def _get_status_data(self, req, milestone, field, results, fields):
        """Get data grouped by WORKFLOW and status.

        It's not possible to show tickets in different workflows on the same
        taskboard, so we create an additional outer group for workflows.
        We then get the workflow with the most tickets, and show that first"""
        ats = AgileToolsSystem(self.env)
        loc = LogicaOrderController(self.env)

        # Data for status much more complex as we need to track the workflow
        tickets_json = defaultdict(lambda: defaultdict(dict))
        by_type = defaultdict(int)
        by_wf = defaultdict(int)
        wf_for_type = {}

        # Store the options required in order to complete an action
        # E.g. closing a ticket requires a resolution
        act_controls = {}

        for r in results:
            # Increment type statistics
            by_type[r['type']] += 1
            tkt = Ticket(self.env, r['id'])
            if r['type'] not in wf_for_type:
                wf_for_type[r['type']] = \
                    loc._get_workflow_for_typename(r['type'])
            wf = wf_for_type[r['type']]

            state = loc._determine_workflow_state(tkt, req=req)
            op = Operation(self.env, wf, state)
            filtered = dict((k, v)
                            for k, v in r.iteritems()
                            if k in fields)
            filtered['position'] = ats.position(r['id'])
            filtered['_changetime'] = to_utimestamp(r['changetime'])
            # we use Trac's to_json() (through add_script_data), so
            # we'll replace any types which can't be json serialised
            for k, v in filtered.items():
                if isinstance(v, datetime): filtered[k] = pretty_age(v)
            filtered['actions'] = self._get_status_actions(req, op, wf, state)
            # Collect all actions requiring further input
            self._update_controls(req, act_controls, filtered['actions'], tkt)

            tickets_json[wf.name][r["status"]][r["id"]] = filtered

        # Calculate number of tickets per workflow
        for ty in by_type:
            by_wf[wf_for_type[ty]] += by_type[ty]

        wf_statuses = dict((wf.name, wf.ordered_statuses) for wf in by_wf)

        # Retrieve Kanban-style status limits
        db = self.env.get_read_db()
        cursor = db.cursor()
        cursor.execute("""
            SELECT status, hardlimit FROM kanban_limits
            WHERE milestone = %s""", (milestone,))
        status_limits = dict((limit[0], limit[1]) for limit in cursor)

        # Initially show the most used workflow
        show_first = max(by_wf, key=lambda n: by_wf[n]).name
        return ("status", tickets_json, wf_statuses, status_limits, show_first, act_controls)