Example #1
0
    def _render_editor(self, req, milestone):
        # Suggest a default due time of 18:00 in the user's timezone
        now = datetime.now(req.tz)
        default_due = datetime(now.year, now.month, now.day, 18)
        if now.hour > 18:
            default_due += timedelta(days=1)
        default_due = to_datetime(default_due, req.tz)

        data = {
            "milestone": milestone,
            "datetime_hint": get_datetime_format_hint(req.lc_time),
            "default_due": default_due,
            "milestone_groups": [],
        }

        if milestone.exists:
            req.perm(milestone.resource).require("MILESTONE_MODIFY")
            milestones = [
                m
                for m in Milestone.select(self.env)
                if m.name != milestone.name and "MILESTONE_VIEW" in req.perm(m.resource)
            ]
            data["milestone_groups"] = group_milestones(milestones, "TICKET_ADMIN" in req.perm)
        else:
            req.perm(milestone.resource).require("MILESTONE_CREATE")

        chrome = Chrome(self.env)
        chrome.add_jquery_ui(req)
        chrome.add_wiki_toolbars(req)
        return "milestone_edit.html", data, None
Example #2
0
    def _render_editor(self, req, milestone):
        data = {
            "milestone": milestone,
            "datetime_hint": get_datetime_format_hint(req.lc_time),
            "default_due": self.get_default_due(req),
            "milestone_groups": [],
        }

        if milestone.exists:
            req.perm(milestone.resource).require("MILESTONE_MODIFY")
            milestones = [
                m
                for m in Milestone.select(self.env)
                if m.name != milestone.name and "MILESTONE_VIEW" in req.perm(m.resource)
            ]
            data["milestone_groups"] = group_milestones(milestones, "TICKET_ADMIN" in req.perm)
            data["num_open_tickets"] = milestone.get_num_tickets(exclude_closed=True)
            data["retarget_to"] = self.default_retarget_to
        else:
            req.perm(milestone.resource).require("MILESTONE_CREATE")
            if milestone.name:
                add_notice(req, _("Milestone %(name)s does not exist. You can" " create it here.", name=milestone.name))

        chrome = Chrome(self.env)
        chrome.add_jquery_ui(req)
        chrome.add_wiki_toolbars(req)
        add_stylesheet(req, "common/css/roadmap.css")
        return "milestone_edit.html", data, None
    def _go_graph(self, req):
        # search all artifacts.
        artifacts = ArtifactManager.find_all(self.env)

        milestones = [m for m in Milestone.select(self.env) if "MILESTONE_VIEW" in req.perm(m.resource)]

        # add artifacts to milestone.
        for m in milestones:
            artifacts_of_m = [artifact for artifact in artifacts if artifact.milestone == m.name]
            setattr(m, "artifacts", artifacts_of_m)

        manager = SCMManager(self.env, req)
        nodes = manager.artifacts_to_nodes(artifacts)

        roots = manager.get_roots(nodes)

        gm = GraphManager(self.env)
        nodes = gm.to_graphnode(roots)

        gm.to_s(nodes)

        graph = gm.to_graphviz(nodes, milestones)

        self.env.log.info("graphviz graph:\n%s" % graph)

        data = {"milestones": milestones, "artifacts": artifacts, "graph": graph}
        return "releaseartifactgraph.html", data, None
Example #4
0
 def _modify_roadmap_page(self, req, template, data, content_type, is_active):
     """Insert roadmap.css + products breadcrumb
     """
     add_stylesheet(req, "dashboard/css/roadmap.css")
     self._add_products_general_breadcrumb(req, template, data, content_type, is_active)
     data["milestone_list"] = [m.name for m in Milestone.select(self.env)]
     req.chrome["ctxtnav"] = []
Example #5
0
    def _reindex_milestone(self, realm, feedback, finish_fb):
        resources = Milestone.select(self.env)

        def check(milestone, check):
            return True

        index = self.milestone_created
        return self._index(realm, resources, check, index, feedback, finish_fb)
Example #6
0
    def test_select_milestones(self):
        self.env.db_transaction.executemany("INSERT INTO milestone (name) VALUES (%s)", [("1.0",), ("2.0",)])

        milestones = list(Milestone.select(self.env))
        self.assertEqual("1.0", milestones[0].name)
        assert milestones[0].exists
        self.assertEqual("2.0", milestones[1].name)
        assert milestones[1].exists
Example #7
0
    def _render_confirm(self, req, db, milestone):
        req.perm(milestone.resource).require("MILESTONE_DELETE")

        milestones = [
            m
            for m in Milestone.select(self.env, db=db)
            if m.name != milestone.name and "MILESTONE_VIEW" in req.perm(m.resource)
        ]
        data = {"milestone": milestone, "milestone_groups": group_milestones(milestones, "TICKET_ADMIN" in req.perm)}
        return "milestone_delete.html", data, None
Example #8
0
    def test_select_milestones(self):
        cursor = self.db.cursor()
        cursor.executemany("INSERT INTO milestone (name) VALUES (%s)", [("1.0",), ("2.0",)])
        cursor.close()

        milestones = list(Milestone.select(self.env))
        self.assertEqual("1.0", milestones[0].name)
        assert milestones[0].exists
        self.assertEqual("2.0", milestones[1].name)
        assert milestones[1].exists
Example #9
0
 def list_view(self, req, cat, page):
     data = {
         "view": "list",
         "sprints": self.sm.select(),
         "format_datetime": datefmt.format_datetime,
         "date_hint": datefmt.get_date_format_hint(),
         "datetime_hint": datefmt.get_datetime_format_hint(),
         "milestones": [m.name for m in Milestone.select(self.env)],
     }
     data.update(req.args)
     return "agilo_admin_sprint.html", data
Example #10
0
 def default_retarget_to(self):
     if self._default_retarget_to and not any(
         self._default_retarget_to == m.name for m in Milestone.select(self.env)
     ):
         self.log.warn(
             'Milestone "%s" does not exist. Update the '
             '"default_retarget_to" option in the [milestone] '
             "section of trac.ini",
             self._default_retarget_to,
         )
     return self._default_retarget_to
    def _go_list(self, req):
        # search all artifacts.
        artifacts = ArtifactManager.find_all(self.env)

        milestones = [m for m in Milestone.select(self.env) if "MILESTONE_VIEW" in req.perm(m.resource)]

        # add artifacts to milestone.
        for m in milestones:
            artifacts_of_m = [artifact for artifact in artifacts if artifact.milestone == m.name]
            setattr(m, "artifacts", artifacts_of_m)

        data = {"milestones": milestones, "artifacts": artifacts}
        return "releaseartifactgraph.html", data, None
Example #12
0
    def _modify_resource_breadcrumb(self, req, template, data, content_type, is_active):
        """Provides logic for breadcrumb resource permissions
        """
        if data and ("ticket" in data.keys()) and data["ticket"].exists:
            data["resourcepath_template"] = "bh_path_ticket.html"
            # determine path permissions
            for resname, permname in [("milestone", "MILESTONE_VIEW"), ("product", "PRODUCT_VIEW")]:
                res = Resource(resname, data["ticket"][resname])
                data["path_show_" + resname] = permname in req.perm(res)

            # add milestone list + current milestone to the breadcrumb
            data["milestone_list"] = [m.name for m in Milestone.select(self.env)]
            mname = data["ticket"]["milestone"]
            if mname:
                data["milestone"] = Milestone(self.env, mname)
Example #13
0
    def users(self, req):
        """hours for all users"""

        data = {"hours_format": hours_format}

        ### date data
        self.date_data(req, data)

        ### milestone data
        milestone = req.args.get("milestone")
        milestones = Milestone.select(self.env)
        data["milestones"] = milestones

        ### get the hours
        # trachours = TracHoursPlugin(self.env)
        # tickets = trachours.tickets_with_hours()
        hours = get_all_dict(
            self.env,
            """
            SELECT * FROM ticket_time
            WHERE time_started >= %s AND time_started < %s
            """,
            *[int(time.mktime(i.timetuple())) for i in (data["from_date"], data["to_date"])]
        )
        worker_hours = {}
        for entry in hours:
            worker = entry["worker"]
            if worker not in worker_hours:
                worker_hours[worker] = 0

            if milestone and milestone != Ticket(self.env, entry["ticket"]).values.get("milestone"):
                continue

            worker_hours[worker] += entry["seconds_worked"]

        worker_hours = [(worker, seconds / 3600.0) for worker, seconds in sorted(worker_hours.items())]
        data["worker_hours"] = worker_hours

        if req.args.get("format") == "csv":
            req.send(self.export_csv(req, data))

        # add_link(req, 'prev', self.get_href(query, args, context.href),
        #         _('Prev Week'))
        # add_link(req, 'next', self.get_href(query, args, context.href),
        #         _('Next Week'))
        # prevnext_nav(req, _('Prev Week'), _('Next Week'))

        return "hours_users.html", data, "text/html"
Example #14
0
    def detail_view(self, req, cat, page, name):
        sprint = self.sm.get(name=name)
        if not sprint or not sprint.exists:
            return req.redirect(req.href.admin(cat, page))

        data = {
            "view": "detail",
            "sprint": sprint,
            "teams": self.tm.select(),
            "format_datetime": datefmt.format_datetime,
            "date_hint": datefmt.get_date_format_hint(),
            "datetime_hint": datefmt.get_datetime_format_hint(),
            "milestones": [m.name for m in Milestone.select(self.env)],
        }
        data.update(req.args)
        add_script(req, "common/js/wikitoolbar.js")
        return "agilo_admin_sprint.html", data
Example #15
0
    def users(self, req):
        """hours for all users"""

        data = {"hours_format": hours_format}

        ### date data
        self.date_data(req, data)

        ### milestone data
        milestone = req.args.get("milestone")
        milestones = Milestone.select(self.env)
        data["milestones"] = milestones

        ### get the hours
        # trachours = TracHoursPlugin(self.env)
        # tickets = trachours.tickets_with_hours()
        hours = get_all_dict(
            self.env,
            "SELECT * FROM ticket_time WHERE time_started >= %s AND time_started < %s",
            *[int(time.mktime(i.timetuple())) for i in (data["from_date"], data["to_date"])]
        )
        worker_hours = {}
        for entry in hours:
            worker = entry["worker"]
            if worker not in worker_hours:
                worker_hours[worker] = 0

            if milestone:
                if milestone != Ticket(self.env, entry["ticket"]).values.get("milestone"):
                    continue

            worker_hours[worker] += entry["seconds_worked"]

        worker_hours = [(worker, seconds / 3600.0) for worker, seconds in sorted(worker_hours.items())]
        data["worker_hours"] = worker_hours

        if req.args.get("format") == "csv":
            buffer = StringIO()
            writer = csv.writer(buffer)
            format = "%B %d, %Y"
            title = "Hours for %s" % self.env.project_name
            writer.writerow([title, req.abs_href()])
            writer.writerow([])
            writer.writerow(["From", "To"])
            writer.writerow([data[i].strftime(format) for i in "from_date", "to_date"])
            if milestone:
Example #16
0
    def get_data_burndown(self, printer):
        self.log.debug("get_data_burndown")
        database = self.env.get_db_cnx()
        cursor = database.cursor()

        data = {"milestones": [], "date_hint": get_date_format_hint(), "printmilestone": printer}

        # get all milestones
        milestones = [m for m in Milestone.select(self.env, True, database)]

        # prepare data for each milestone
        p = re.compile("[0-9]+")
        for milest in milestones:
            self.log.debug(milest.completed)
            tickets = _get_tickets_for_milestone(self.env, database, milest.name, "history_size")
            self.log.debug(tickets)
            tasks = _get_tasks_for_milestone(self, database, milest.name)

            # calculate totalpoints of sprint
            totalpoints = 0
            for idx, ticket in enumerate(tickets):
                if ticket["history_size"] and p.match(ticket["history_size"]):
                    totalpoints += int(ticket["history_size"])

            # prepare graph data
            graphdata = {"points": totalpoints, "tasks": tasks, "printer": printer}
            self.log.debug(graphdata)

            # if totalpoints and total tasks equal the zero not display burndown chart
            if totalpoints > 0 and len(tasks) > 0:
                graph_image_path = _get_graphflash_sprintburndown(self, milest.name, graphdata)
            else:
                graph_image_path = None

            # data the all milestones
            d = {
                "milestone": milest,
                "tickets": tickets,
                "tasks": tasks,
                "graph": graph_image_path,
                "teste": totalpoints,
            }
            data["milestones"].append(d)

        return data
Example #17
0
    def _render_confirm(self, req, milestone):
        req.perm(milestone.resource).require("MILESTONE_DELETE")

        milestones = [
            m
            for m in Milestone.select(self.env)
            if m.name != milestone.name and "MILESTONE_VIEW" in req.perm(m.resource)
        ]
        attachments = Attachment.select(self.env, self.realm, milestone.name)
        data = {
            "milestone": milestone,
            "milestone_groups": group_milestones(milestones, "TICKET_ADMIN" in req.perm),
            "num_tickets": milestone.get_num_tickets(),
            "retarget_to": self.default_retarget_to,
            "attachments": list(attachments),
        }
        add_stylesheet(req, "common/css/roadmap.css")
        return "milestone_delete.html", data, None
Example #18
0
    def process_admin_request(self, req, cat, page, path_info):
        envs = DatamoverSystem(self.env).all_environments()
        milestones = [m.name for m in Milestone.select(self.env)]

        if req.method == "POST":
            source_type = req.args.get("source")
            if not source_type or source_type not in ("milestone", "all"):
                raise TracError, "Source type not specified or invalid"
            source = req.args.get(source_type)
            dest = req.args.get("destination")
            action = None
            if "copy" in req.args.keys():
                action = "copy"
            elif "move" in req.args.keys():
                action = "move"
            else:
                raise TracError, "Action not specified or invalid"

            action_verb = {"copy": "Copied", "move": "Moved"}[action]

            milestone_filter = None
            if source_type == "milestone":
                in_milestones = req.args.getlist("milestone")
                milestone_filter = lambda c: c in in_milestones
            elif source_type == "all":
                milestone_filter = lambda c: True

            try:
                sel_milestones = [m for m in milestones if milestone_filter(m)]
                dest_db = _open_environment(dest).get_db_cnx()
                for milestone in sel_milestones:
                    copy_milestone(self.env, dest, milestone, dest_db)
                dest_db.commit()

                if action == "move":
                    for milestone in sel_milestones:
                        Milestone(self.env, milestone).delete()

                req.hdf["datamover.message"] = "%s milestones %s" % (action_verb, ", ".join(sel_milestones))
            except TracError, e:
                req.hdf["datamover.message"] = "An error has occured: \n" + str(e)
                self.log.warn(req.hdf["datamover.message"], exc_info=True)
Example #19
0
    def process_request(self, req):
        req.perm.require("ROADMAP_VIEW")

        show = req.args.getlist("show")
        if "all" in show:
            show = ["completed"]

        milestones = Milestone.select(self.env, "completed" in show)
        if "noduedate" in show:
            milestones = [m for m in milestones if m.due is not None or m.completed]
        milestones = [m for m in milestones if "MILESTONE_VIEW" in req.perm(m.resource)]

        stats = []
        queries = []

        for milestone in milestones:
            tickets = get_tickets_for_milestone(self.env, milestone=milestone.name, field="owner")
            tickets = apply_ticket_permissions(self.env, req, tickets)
            stat = get_ticket_stats(self.stats_provider, tickets)
            stats.append(milestone_stats_data(self.env, req, stat, milestone.name))
            # milestone['tickets'] = tickets # for the iCalendar view

        if req.args.get("format") == "ics":
            self._render_ics(req, milestones)
            return

        # FIXME should use the 'webcal:' scheme, probably
        username = None
        if req.authname and req.authname != "anonymous":
            username = req.authname
        icshref = req.href.roadmap(show=show, user=username, format="ics")
        add_link(req, "alternate", auth_link(req, icshref), _("iCalendar"), "text/calendar", "ics")

        data = {"milestones": milestones, "milestone_stats": stats, "queries": queries, "show": show}
        add_stylesheet(req, "common/css/roadmap.css")
        return "roadmap.html", data, None
Example #20
0
    def select(env, include_completed=True, db=None):
        if not db:
            db = env.get_db_cnx()

        milestones = [StructuredMilestone(env, milestone) for milestone in Milestone.select(env, include_completed, db)]
        return StructuredMilestone.reorganize(milestones)
    def process_request(self, req):

        req.perm.assert_permission("BACKLOG_VIEW")

        ats = AgileToolsSystem(self.env)

        if req.get_header("X-Requested-With") == "XMLHttpRequest":

            if req.method == "POST":

                if not req.perm.has_permission("BACKLOG_ADMIN"):
                    return self._json_errors(req, ["BACKLOG_ADMIN permission required"])

                str_ticket = req.args.get("ticket")
                str_relative = req.args.get("relative", 0)
                direction = req.args.get("relative_direction")
                milestone = req.args.get("milestone")

                # Moving a single ticket position (and milestone)
                if str_ticket:
                    try:
                        int_ticket = int(str_ticket)
                        int_relative = int(str_relative)
                    except (TypeError, ValueError):
                        return self._json_errors(req, ["Invalid arguments"])

                    try:
                        ticket = Ticket(self.env, int_ticket)
                    except ResourceNotFound:
                        return self._json_errors(req, ["Not a valid ticket"])

                    response = {}

                    # Change ticket's milestone
                    if milestone is not None:
                        try:
                            self._save_ticket(req, ticket, milestone)
                            ticket = self._get_permitted_tickets(req, constraints={"id": [str(int_ticket)]})
                            response["tickets"] = self._get_ticket_data(req, ticket)
                        except ValueError as e:
                            return self._json_errors(req, e.message)

                    # Reposition ticket
                    if int_relative:
                        position = ats.position(int_relative, generate=True)
                        if direction == "after":
                            position += 1

                        ats.move(int_ticket, position, author=req.authname)
                        response["success"] = True

                    return self._json_send(req, response)

                # Dropping multiple tickets into a milestone
                elif all(k in req.args for k in ("tickets", "milestone", "changetimes")):

                    changetimes = req.args["changetimes"].split(",")
                    milestone = req.args["milestone"]

                    try:
                        ids = [int(tkt_id) for tkt_id in req.args["tickets"].split(",")]
                    except (ValueError, TypeError):
                        return self._json_errors(req, ["Invalid arguments"])

                    unique_errors = 0
                    errors_by_ticket = []
                    # List of [<ticket_id>, [<error>, ...]] lists
                    if len(ids) == len(changetimes):
                        for i, int_ticket in enumerate(ids):
                            # Valid ticket
                            try:
                                ticket = Ticket(self.env, int_ticket)
                            except ResourceNotFound:
                                errors_by_ticket.append([int_ticket, ["Not a valid ticket"]])
                            # Can be saved
                            try:
                                self._save_ticket(req, ticket, milestone, ts=changetimes[i])
                            except ValueError as e:
                                # Quirk: all errors amalgomated into single
                                # we keep track of count at each time so that
                                # we can split the list up to errors by
                                # individual tickets
                                errors_by_ticket.append([int_ticket, e.message[unique_errors:]])
                                unique_errors = len(e.message)
                                if len(errors_by_ticket) > 5:
                                    errors_by_ticket.append("More than 5 tickets failed " "validation, stopping.")
                                    break
                        if errors_by_ticket:
                            return self._json_errors(req, errors_by_ticket)
                        else:
                            # Client side makes additional request for all
                            # tickets after this
                            return self._json_send(req, {"success": True})
                    else:
                        return self._json_errors(req, ["Invalid arguments"])
                else:
                    return self._json_errors(req, ["Must provide a ticket"])
            else:
                # TODO make client side compatible with live updates
                milestone = req.args.get("milestone")
                from_iso = req.args.get("from")
                to_iso = req.args.get("to")

                if milestone is not None:
                    # Requesting an update
                    constr = {"milestone": [milestone]}
                    if from_iso and to_iso:
                        constr["changetime"] = [from_iso + ".." + to_iso]

                    tickets = self._get_permitted_tickets(req, constraints=constr)
                    formatted = self._get_ticket_data(req, tickets)
                    self._json_send(req, {"tickets": formatted})
                else:
                    self._json_errors(req, ["Invalid arguments"])

        else:
            add_script(req, "agiletools/js/jquery.history.js")
            add_script(req, "agiletools/js/update_model.js")
            add_script(req, "agiletools/js/backlog.js")
            add_stylesheet(req, "agiletools/css/backlog.css")

            milestones_select2 = Milestone.select_names_select2(self.env, include_complete=False)
            milestones_select2["results"].insert(
                0, {"children": [], "text": "Product Backlog", "id": "backlog", "is_backlog": True}
            )

            milestones_flat = [
                milestone.name
                for milestone in Milestone.select(self.env, include_completed=False, include_children=True)
            ]

            script_data = {
                "milestones": milestones_select2,
                "milestonesFlat": milestones_flat,
                "backlogAdmin": req.perm.has_permission("BACKLOG_ADMIN"),
            }

            add_script_data(req, script_data)
            data = {"top_level_milestones": Milestone.select(self.env)}
            # Just post the basic template, with a list of milestones
            # The JS will then make a request for tickets in no milestone
            # and tickets in the most imminent milestone
            # The client will be able to make subsequent requests to pull
            # tickets from other milestones and drop tickets into them
            return "backlog.html", data, None
Example #22
0
 def _sorted_milestones(self):
     """ Return a sorted list of active milestones. """
     milestones = Milestone.select(self.env, include_completed=False)
     return [m.name for m in milestones]
Example #23
0
    def _render_view(self, req, milestone):
        milestone_groups = []
        available_groups = []
        component_group_available = False
        ticket_fields = TicketSystem(self.env).get_ticket_fields()

        # collect fields that can be used for grouping
        for field in ticket_fields:
            if field["type"] == "select" and field["name"] != "milestone" or field["name"] in ("owner", "reporter"):
                available_groups.append({"name": field["name"], "label": field["label"]})
                if field["name"] == "component":
                    component_group_available = True

        # determine the field currently used for grouping
        by = None
        if component_group_available:
            by = "component"
        elif available_groups:
            by = available_groups[0]["name"]
        by = req.args.get("by", by)

        tickets = get_tickets_for_milestone(self.env, milestone=milestone.name, field=by)
        tickets = apply_ticket_permissions(self.env, req, tickets)
        stat = get_ticket_stats(self.stats_provider, tickets)

        context = web_context(req, milestone.resource)
        data = {
            "context": context,
            "milestone": milestone,
            "attachments": AttachmentModule(self.env).attachment_data(context),
            "available_groups": available_groups,
            "grouped_by": by,
            "groups": milestone_groups,
        }
        data.update(milestone_stats_data(self.env, req, stat, milestone.name))

        if by:

            def per_group_stats_data(gstat, group_name):
                return milestone_stats_data(self.env, req, gstat, milestone.name, by, group_name)

            milestone_groups.extend(
                grouped_stats_data(self.env, self.stats_provider, tickets, by, per_group_stats_data)
            )

        add_stylesheet(req, "common/css/roadmap.css")
        add_script(req, "common/js/folding.js")

        def add_milestone_link(rel, milestone):
            href = req.href.milestone(milestone.name, by=req.args.get("by"))
            add_link(req, rel, href, _('Milestone "%(name)s"', name=milestone.name))

        milestones = [m for m in Milestone.select(self.env) if "MILESTONE_VIEW" in req.perm(m.resource)]
        idx = [i for i, m in enumerate(milestones) if m.name == milestone.name]
        if idx:
            idx = idx[0]
            if idx > 0:
                add_milestone_link("first", milestones[0])
                add_milestone_link("prev", milestones[idx - 1])
            if idx < len(milestones) - 1:
                add_milestone_link("next", milestones[idx + 1])
                add_milestone_link("last", milestones[-1])
        prevnext_nav(req, _("Previous Milestone"), _("Next Milestone"), _("Back to Roadmap"))

        return "milestone_view.html", data, None
Example #24
0
 def _get_milestones(self):
     return Milestone.select(self.env, include_completed=False)
Example #25
0
    def _render_view(self, req, db):

        showall = req.args.get("show") == "all"
        showmetrics = req.args.get("showmetrics") == "true"

        # Get list of milestone object for the project
        milestones = list(Milestone.select(self.env, showall, db))
        stats = []
        queries = []

        self.env.log.info("getting milestones statistics")
        for milestone in milestones:
            tickets = get_tickets_for_milestone(self.env, db, milestone.name, "owner")
            stat = get_ticket_stats(self.stats_provider, tickets)
            stats.append(milestone_stats_data(self.env, req, stat, milestone.name))

        project = {"name": self.env.project_name, "description": self.env.project_description}

        data = {
            "context": Context.from_request(req),
            "milestones": milestones,
            "milestone_stats": stats,
            "queries": queries,
            "showall": showall,
            "showmetrics": showmetrics,
            "project": project,
            "yui_base_url": self.yui_base_url,
        }

        self.env.log.info("getting project statistics")

        # Get project progress stats
        query = Query.from_string(self.env, "max=0&order=id")
        tickets = query.execute(req)
        proj_stat = get_ticket_stats(self.stats_provider, tickets)

        data["proj_progress_stat"] = {
            "stats": proj_stat,
            "stats_href": req.href.query(proj_stat.qry_args),
            "interval_hrefs": [req.href.query(interval["qry_args"]) for interval in proj_stat.intervals],
        }

        ticket_ids = [t["id"] for t in tickets]
        closed_stat = self.stats_provider.get_ticket_resolution_group_stats(ticket_ids)

        data["proj_closed_stat"] = {
            "stats": closed_stat,
            "stats_href": req.href.query(closed_stat.qry_args),
            "interval_hrefs": [req.href.query(interval["qry_args"]) for interval in closed_stat.intervals],
        }

        tkt_frequency_stats = {}
        tkt_duration_stats = {}
        bmi_stats = []
        daily_backlog_chart = {}
        today = to_datetime(None)

        if showmetrics:
            self.env.log.info("getting ticket metrics")
            tkt_group_metrics = TicketGroupMetrics(self.env, ticket_ids)

            tkt_frequency_stats = tkt_group_metrics.get_frequency_metrics_stats()
            tkt_duration_stats = tkt_group_metrics.get_duration_metrics_stats()

            # stat for this month
            first_day = datetime(today.year, today.month, 1, tzinfo=utc)
            last_day = last_day_of_month(today.year, today.month)
            bmi_stats.append(tkt_group_metrics.get_bmi_monthly_stats(first_day, last_day))

            # stat for last month
            last_day = first_day - timedelta(days=1)
            first_day = datetime(last_day.year, last_day.month, 1, tzinfo=utc)
            bmi_stats.append(tkt_group_metrics.get_bmi_monthly_stats(first_day, last_day))

            # get daily backlog history
            last_day = datetime(today.year, today.month, today.day, tzinfo=utc)
            first_day = last_day - timedelta(days=DAYS_BACK)
            self.env.log.info("getting backlog history")
            backlog_history = tkt_group_metrics.get_daily_backlog_history(first_day, last_day)
            daily_backlog_chart = tkt_group_metrics.get_daily_backlog_chart(backlog_history)

        # Get dialy commits history
        last_day = datetime(today.year, today.month, today.day, tzinfo=utc)
        first_day = last_day - timedelta(days=DAYS_BACK)
        changeset_group_stats = ChangesetsStats(self.env, first_day, last_day)
        commits_by_date = changeset_group_stats.get_commit_by_date()
        commits_by_date_chart = changeset_group_stats.get_commit_by_date_chart(commits_by_date)

        data["project_bmi_stats"] = bmi_stats
        # self.env.log.info(bmi_stats)
        data["ticket_frequency_stats"] = tkt_frequency_stats
        data["ticket_duration_stats"] = tkt_duration_stats
        data["ds_daily_backlog"] = daily_backlog_chart
        data["ds_commit_by_date"] = commits_by_date_chart

        add_stylesheet(req, "pd/css/dashboard.css")
        add_stylesheet(req, "common/css/report.css")

        return ("pdashboard.html", data, None)