Example #1
0
    def render_macro(self, req, name, content):
        db = self.env.get_db_cnx()
        # prepare options
        options, query_args = parse_options(db, content,
                                            copy.copy(DEFAULT_OPTIONS))

        query_args[self.estimation_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)

        sum = 0.0
        estimations = {}
        for ticket in tickets:
            try:
                estimation = float(ticket[self.estimation_field])
                owner = ticket['owner']
                sum += estimation
                if estimations.has_key(owner):
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                pass

        estimations_string = []
        labels = []
        for owner, estimation in estimations.iteritems():
            labels.append(
                "%s %s%s" %
                (owner, str(int(estimation)), self.estimation_suffix))
            estimations_string.append(str(int(estimation)))

        # Title
        title = 'Workload'

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %s%s (%s workdays left)' % (
                int(sum), self.estimation_suffix, days_remaining)

        return Markup(
            "<img src=\"http://chart.apis.google.com/chart?"
            "chs=%sx%s"
            "&amp;chd=t:%s"
            "&amp;cht=p3"
            "&amp;chtt=%s"
            "&amp;chl=%s"
            "&amp;chco=%s\" "
            "alt=\'Workload Chart\' />" %
            (options['width'], options['height'], ",".join(estimations_string),
             title, "|".join(labels), options['color']))
Example #2
0
    def render_macro(self, req, name, content):
        db = self.env.get_db_cnx()
        # prepare options
        options, query_args = parse_options(db, content, copy.copy(DEFAULT_OPTIONS))
        
        query_args[self.estimation_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)
        
        sum = 0.0
        estimations = {}
        for ticket in tickets:
            try:
                estimation = float(ticket[self.estimation_field])
                owner = ticket['owner']
                sum += estimation
                if estimations.has_key(owner):
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                pass

        estimations_string = []
        labels = []
        for owner, estimation in estimations.iteritems():
            labels.append("%s %s%s" % (owner, str(int(estimation)), self.estimation_suffix))
            estimations_string.append(str(int(estimation)))
            
        # Title
        title = 'Workload'

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %s%s (%s workdays left)' % (int(sum), self.estimation_suffix, days_remaining)                
                
        return Markup("<img src=\"http://chart.apis.google.com/chart?"
               "chs=%sx%s" 
               "&amp;chd=t:%s"
               "&amp;cht=p3"
               "&amp;chtt=%s"
               "&amp;chl=%s"
               "&amp;chco=%s\" "
               "alt=\'Workload Chart\' />" 
               % (options['width'], options['height'], ",".join(estimations_string), 
                  title, "|".join(labels), options['color']))
Example #3
0
    def render_macro(self, req, name, content):
        _, options = parse_args(content, strict=False)

        # we have to add custom estimation field to query so that field is added to
        # resulting ticket list
        options[self.estimation_field + "!"] = None

        tickets = execute_query(self.env, req, options)
        
        sum = 0.0
        for t in tickets:
            try:
                sum += float(t[self.estimation_field])
            except:
                pass

        return "%s" % int(sum)
Example #4
0
    def render_macro(self, req, name, content):
        _, options = parse_args(content, strict=False)

        # we have to add custom estimation field to query so that field is added to
        # resulting ticket list
        options[self.estimation_field + "!"] = None

        tickets = execute_query(self.env, req, options)

        sum = 0.0
        for t in tickets:
            try:
                sum += float(t[self.estimation_field])
            except:
                pass

        return "%s" % int(sum)
Example #5
0
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        _ignore, options = parse_args(content, strict=False)

        # we have to add custom estimated field to query so that field is added to
        # resulting ticket list
        options[self.estimation_field + "!"] = None

        tickets = execute_query(self.env, req, options)

        sum = 0.0
        for t in tickets:
            try:
                sum += float(t[self.estimation_field])
            except:
                pass

        return "%g" % round(sum, 2)
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        _ignore, options = parse_args(content, strict=False)

        # we have to add custom estimated field to query so that field is added to
        # resulting ticket list
        options[self.estimation_field + "!"] = None

        tickets = execute_query(self.env, req, options)

        sum = 0.0
        for t in tickets:
            try:
                sum += float(t[self.estimation_field])
            except:
                pass

        return "%g" % round(sum, 2)
Example #7
0
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        _ignore, options = parse_args(content, strict=False)

        # we have to add custom remaining field to query so that field is added to
        # resulting ticket list
        options[self.remaining_field + "!"] = None

        # ignore closed tickets
        options['status!'] = "|".join(self.closed_states)

        tickets = execute_query(self.env, req, options)

        sum = 0.0
        for t in tickets:
            try:
                sum += float(t[self.remaining_field])
            except:
                pass

        return "%g" % round(sum, 2)
    def _calculate_timetable(self, options, query_args, req):
        db = self.env.get_db_cnx()

        # create dictionary with entry for each day of the required time period
        timetable = {}

        current_date = options['startdate']
        while current_date <= options['enddate']:
            timetable[current_date] = Decimal(0)
            current_date += timedelta(days=1)

        # get current values for all tickets within milestone and sprints

        query_args[self.estimation_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)

        # add the open effort for each ticket for each day to the timetable

        for t in tickets:

            # Record the current (latest) status and estimate, and ticket
            # creation date

            creation_date = t['time'].date()
            latest_status = t['status']
            latest_estimate = self._cast_estimate(t[self.estimation_field])
            if latest_estimate is None:
                latest_estimate = Decimal(0)

            # Fetch change history for status and effort fields for this ticket
            history_cursor = db.cursor()
            history_cursor.execute(
                "SELECT "
                "DISTINCT c.field as field, c.time AS time, c.oldvalue as oldvalue, c.newvalue as newvalue "
                "FROM ticket t, ticket_change c "
                "WHERE t.id = %s and c.ticket = t.id and (c.field=%s or c.field='status')"
                "ORDER BY c.time ASC", [t['id'], self.estimation_field])

            # Build up two dictionaries, mapping dates when effort/status
            # changed, to the latest effort/status on that day (in case of
            # several changes on the same day). Also record the oldest known
            # effort/status, i.e. that at the time of ticket creation

            estimate_history = {}
            status_history = {}

            earliest_estimate = None
            earliest_status = None

            for row in history_cursor:
                row_field, row_time, row_old, row_new = row
                event_date = from_timestamp(row_time).date()
                if row_field == self.estimation_field:
                    new_value = self._cast_estimate(row_new)
                    if new_value is not None:
                        estimate_history[event_date] = new_value
                    if earliest_estimate is None:
                        earliest_estimate = self._cast_estimate(row_old)
                elif row_field == 'status':
                    status_history[event_date] = row_new
                    if earliest_status is None:
                        earliest_status = row_old

            # If we don't know already (i.e. the ticket effort/status was
            # not changed on the creation date), set the effort on the
            # creation date. It may be that we don't have an "earliest"
            # estimate/status, because it was never changed. In this case,
            # use the current (latest) value.

            if not creation_date in estimate_history:
                if earliest_estimate is not None:
                    estimate_history[creation_date] = earliest_estimate
                else:
                    estimate_history[creation_date] = latest_estimate
            if not creation_date in status_history:
                if earliest_status is not None:
                    status_history[creation_date] = earliest_status
                else:
                    status_history[creation_date] = latest_status

            # Finally estimates to the timetable. Treat any period where the
            # ticket was closed as estimate 0. We need to loop from ticket
            # creation date, not just from the timetable start date, since
            # it's possible that the ticket was changed between these two
            # dates.

            current_date = creation_date
            current_estimate = None
            is_open = None

            while current_date <= options['enddate']:
                if current_date in status_history:
                    is_open = (status_history[current_date]
                               not in self.closed_states)

                if current_date in estimate_history:
                    current_estimate = estimate_history[current_date]

                if current_date >= options['startdate'] and is_open:
                    timetable[current_date] += current_estimate

                current_date += timedelta(days=1)

        return timetable
    def expand_macro(self, formatter, name, content, args=None):
        req = formatter.req
        # prepare options
        options, query_args = parse_options(self.env, content,
                                            copy.copy(DEFAULT_OPTIONS))

        query_args[self.remaining_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)

        sum = 0.0
        estimations = {}
        for ticket in tickets:
            if ticket['status'] in self.closed_states:
                continue
            try:
                estimation = float(ticket[self.remaining_field])
                owner = ticket['owner']
                sum += estimation
                if owner in estimations:
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                pass

        estimations_string = []
        labels = []
        for owner, estimation in estimations.iteritems():
            # Note: Unconditional obfuscation of owner in case it represents
            # an email adress, and as the chart API doesn't support SSL
            # (plain http transfer only, from either client or server).
            labels.append("%s %g%s" % (obfuscate_email_address(owner),
                                       round(estimation, 2),
                                       self.estimation_suffix))
            estimations_string.append(str(int(estimation)))

        # Title
        title = 'Workload'

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %g%s (~%s workdays left)' % (round(sum, 2),
                                                    self.estimation_suffix,
                                                    days_remaining)

        chart_args = unicode_urlencode(
            {'chs': '%sx%s' % (options['width'], options['height']),
             'chf': 'bg,s,00000000',
             'chd': 't:%s' % ",".join(estimations_string),
             'cht': 'p3',
             'chtt': title,
             'chl': "|".join(labels),
             'chco': options['color']})
        self.log.debug("WorkloadChart data: %s", chart_args)
        if self.serverside_charts:
            return tag.image(
                src="%s?data=%s" % (req.href.estimationtools('chart'),
                                    unicode_quote(chart_args)),
                alt="Workload Chart (server)")
        else:
            return tag.image(
                src="http://chart.googleapis.com/chart?%s" % chart_args,
                alt="Workload Chart (client)")
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        db = self.env.get_db_cnx()
        # prepare options
        options, query_args = parse_options(db, content, copy.copy(DEFAULT_OPTIONS))

        query_args[self.estimation_field + "!"] = None
        query_args['col'] = '|'.join([self.totalhours_field, 'owner'])
        tickets = execute_query(self.env, req, query_args)

        sum = 0.0
        estimations = {}
        for ticket in tickets:
            if ticket['status'] in self.closed_states:
                continue
            try:
                estimation = float(ticket[self.estimation_field]) - float(ticket[self.totalhours_field])
                owner = ticket['owner']
                sum += estimation
                if estimations.has_key(owner):
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                pass

        data = []
        data.append(['Owner', 'Workload'])

        for owner, estimation in estimations.iteritems():
            estimation = max(0, estimation)
            label = "%s %g%s" % (obfuscate_email_address(owner),
                            round(estimation, 2),
                            self.estimation_suffix)
            data.append([label, float(estimation)])

        # Title
        title = 'Workload'

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %g%s (~%s workdays left)' % (round(sum, 2),
                                    self.estimation_suffix, days_remaining)

        element_id = 'chart-%d' % random.randint(0, 0xffffffff)
        args = {
            'containerId': element_id,
            'chartType': 'PieChart',
            'options': {
                'width': int(options['width']),
                'height': int(options['height']),
                'title': title,
                'legend': { 'position': 'labeled' },
                'pieSliceText': 'none',
                'tooltip': 'percentage',
            },
        }
        script = "EstimationCharts.push(function() {\n"
        script += 'var data=' + to_json(data) + ";\n"
        script += 'var args=' + to_json(args) + ";\n"
        script += 'DrawWorkloadChart(data, args);'
        script += '});'

        return tag.div(tag.div(id=element_id), tag.script(script))
    def _calculate_timetable_spent(self, options, query_args, req):
        # create dictionary with entry for each day of the required time period
        timetable = {}

        current_date = options['startdate']
        while current_date <= options['enddate']:
            timetable[current_date] = Decimal(0)
            current_date += timedelta(days=1)

        # get current values for all tickets within milestone and sprints

        query_args[self.spent_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)

        # add the open effort for each ticket for each day to the timetable

        for t in tickets:

            # Record the current (latest) status and estimate, and ticket
            # creation date

            creation_date = t['time'].date()
            latest_status = t['status']
            latest_estimate = self._cast_estimate(t[self.spent_field])
            if latest_estimate is None:
                latest_estimate = Decimal(0)

            # Fetch change history for status and effort fields for this ticket
            # Build up two dictionaries, mapping dates when effort/status
            # changed, to the latest effort/status on that day (in case of
            # several changes on the same day). Also record the oldest known
            # effort/status, i.e. that at the time of ticket creation

            estimate_history = {}
            status_history = {}

            earliest_estimate = None
            earliest_status = None

            for row in self.env.db_query("""
                    SELECT DISTINCT c.field AS field, c.time AS time,
                                    c.oldvalue AS oldvalue, c.newvalue AS newvalue
                    FROM ticket t, ticket_change c
                    WHERE t.id = %s AND c.ticket = t.id AND
                          (c.field=%s OR c.field='status')
                    ORDER BY c.time ASC
                    """, [t['id'], self.spent_field]):
                row_field, row_time, row_old, row_new = row
                event_date = from_utimestamp(row_time).date()
                if row_field == self.spent_field:
                    new_value = self._cast_estimate(row_new)
                    if new_value is not None:
                        estimate_history[event_date] = new_value
                    if earliest_estimate is None:
                        earliest_estimate = self._cast_estimate(row_old)
                elif row_field == 'status':
                    status_history[event_date] = row_new
                    if earliest_status is None:
                        earliest_status = row_old

            # If we don't know already (i.e. the ticket effort/status was
            # not changed on the creation date), set the effort on the
            # creation date. It may be that we don't have an "earliest"
            # estimate/status, because it was never changed. In this case,
            # use the current (latest) value.

            if creation_date not in estimate_history:
                if earliest_estimate is not None:
                    estimate_history[creation_date] = earliest_estimate
                else:
                    estimate_history[creation_date] = latest_estimate
            if creation_date not in status_history:
                if earliest_status is not None:
                    status_history[creation_date] = earliest_status
                else:
                    status_history[creation_date] = latest_status

            # Finally estimates to the timetable. Treat any period where the
            # ticket was closed as estimate 0. We need to loop from ticket
            # creation date, not just from the timetable start date, since
            # it's possible that the ticket was changed between these two
            # dates.

            current_date = creation_date
            current_estimate = None
            is_open = None

            while current_date <= options['enddate']:
                if current_date in status_history:
                    is_open = (
                        status_history[current_date] not in self.closed_states)

                if current_date in estimate_history:
                    current_estimate = estimate_history[current_date]

                if current_date >= options['startdate'] and is_open:
                    timetable[current_date] += current_estimate

                current_date += timedelta(days=1)

        return timetable
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        db = self.env.get_db_cnx()
        # prepare options
        options, query_args = parse_options(db, content,
                                            copy.copy(DEFAULT_OPTIONS))

        query_args[self.estimation_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)

        sum = 0.0
        estimations = {}
        for ticket in tickets:
            if ticket['status'] in self.closed_states:
                continue
            try:
                estimation = float(ticket[self.estimation_field] or 0.0)

                if options.get('remainingworkload'):

                    completion_cursor = db.cursor()

                    completion_cursor.execute(
                        "SELECT t.value AS totalhours, c.value AS complete, d.value AS due_close FROM ticket tk LEFT JOIN ticket_custom t ON (tk.id = t.ticket AND t.name = 'totalhours') LEFT JOIN ticket_custom c ON (tk.id = c.ticket AND c.name = 'complete') LEFT JOIN ticket_custom d ON (tk.id = d.ticket AND d.name = 'due_close') WHERE tk.id = %s"
                        % ticket['id'])

                    for row in completion_cursor:

                        ticket['totalhours'], ticket['complete'], ticket[
                            'due_close'] = row
                        break

                    # skip ticket ticket if due date is later than 'enddate':
                    if options.get('showdueonly'):

                        if not ticket['due_close']:
                            continue  # skip tickets with empty ETA when in 'showdueonly' mode
                        due_close = parse_date(ticket['due_close'],
                                               ["%Y/%m/%d"])
                        startdate = options.get('startdate')
                        enddate = options.get('enddate')

                        if startdate and startdate > due_close:
                            continue  # skip tickets with ETA in the past

                        if enddate and enddate < due_close:
                            continue  # skip tickets with ETA in the future

                        pass

                    totalhours = float(ticket['totalhours'] or 0.0)

                    completed = (float(ticket['complete'] or 0.0) /
                                 100) * estimation
                    completed_hours = min(estimation,
                                          max(totalhours, completed))

                    estimation -= completed_hours

                    pass

                owner = ticket['owner']

                sum += estimation
                if estimations.has_key(owner):
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                raise

        # Title
        title = 'Workload'

        days_remaining = None

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %g%s (~%s workdays left)' % (round(
                sum, 2), self.estimation_suffix, days_remaining)

        estimations_string = []
        labels = []
        workhoursperday = max(float(options.get('workhoursperday')), 0.0)
        chts = '000000'

        for owner, estimation in estimations.iteritems():
            # Note: Unconditional obfuscation of owner in case it represents
            # an email adress, and as the chart API doesn't support SSL
            # (plain http transfer only, from either client or server).
            label = "%s %g%s" % (obfuscate_email_address(owner),
                                 round(estimation, 2), self.estimation_suffix)

            if days_remaining != None:

                user_remaining_hours = days_remaining * workhoursperday

                if not user_remaining_hours or (estimation /
                                                user_remaining_hours) > 1:
                    label = "%s (~%g hours left)!" % (
                        label, round(user_remaining_hours, 2)
                    )  # user does not have enough hours left
                    chts = 'FF0000'  # set chart title style to red
                    pass
                pass

            labels.append(label)

            estimations_string.append(str(int(estimation)))

            pass

        chart_args = unicode_urlencode({
            'chs':
            '%sx%s' % (options['width'], options['height']),
            'chf':
            'bg,s,00000000',
            'chd':
            't:%s' % ",".join(estimations_string),
            'cht':
            'p3',
            'chtt':
            title,
            'chts':
            chts,
            'chl':
            "|".join(labels),
            'chco':
            options['color']
        })

        self.log.debug("WorkloadChart data: %s" % repr(chart_args))
        if self.serverside_charts:
            return tag.image(
                src="%s?data=%s" %
                (req.href.estimationtools('chart'), unicode_quote(chart_args)),
                alt="Workload Chart (server)")
        else:
            return tag.image(src="https://chart.googleapis.com/chart?%s" %
                             chart_args,
                             alt="Workload Chart (client)")
Example #13
0
    def _calculate_timetable(self, options, query_args, req):
        db = self.env.get_db_cnx()

        estimation_field = self.estimation_field
        initial_estimation_field = self.initial_estimation_field or estimation_field
        one_field = (estimation_field == initial_estimation_field)

        # create dictionary with entry for each day of the required time period
        timetable = {}
        delta = {}

        current_date = options['startdate']
        max_date = current_date
        while current_date <= options['enddate']:
            timetable[current_date] = Decimal(0)
            delta[current_date] = Decimal(0)
            max_date = current_date
            current_date += timedelta(days=options.get('interval_days', 1))

        # Ensure we have today's date in the timetable as well, in case interval_days
        # caused us to stop before it.
        if options['today'] <= options['enddate']:
            timetable[options['today']] = Decimal(0)
            delta[options['today']] = Decimal(0)

        # get current values for all tickets within milestone and sprints

        query_args[estimation_field + "!"] = None
        if estimation_field != initial_estimation_field:
            query_args[initial_estimation_field + "!"] = None
        query_args['status' + "!"] = None

        tickets = execute_query(self.env, req, query_args)

        # add the open effort for each ticket for each day to the timetable

        for t in tickets:

            # Record the current (latest) status and estimate, and ticket
            # creation date

            creation_date = t['time'].date()
            latest_status = t['status']
            latest_estimate = self._cast_estimate(t[estimation_field])
            if not latest_estimate:
                latest_estimate = Decimal(0)
            latest_initial_estimate = self._cast_estimate(
                t[initial_estimation_field])
            if not latest_initial_estimate:
                latest_initial_estimate = Decimal(0)

            # Fetch change history for status and effort fields for this ticket
            history_cursor = db.cursor()
            history_cursor.execute(
                "SELECT "
                "DISTINCT c.field as field, c.time AS time, c.oldvalue as oldvalue, c.newvalue as newvalue "
                "FROM ticket t, ticket_change c "
                "WHERE t.id = %s and c.ticket = t.id and (c.field=%s or c.field=%s or c.field='status')"
                "ORDER BY c.time ASC",
                [t['id'], estimation_field, initial_estimation_field])

            # Build up two dictionaries, mapping dates when effort/status
            # changed, to the latest effort/status on that day (in case of
            # several changes on the same day). Also record the oldest known
            # effort/status, i.e. that at the time of ticket creation

            estimate_history = {}
            initial_estimate_history = {}
            status_history = {}

            earliest_estimate = None
            earliest_initial_estimate = None
            earliest_status = None

            for row in history_cursor:
                row_field, row_time, row_old, row_new = row
                event_date = datetime.fromtimestamp(row_time, utc).date()
                if row_field == estimation_field:
                    new_value = self._cast_estimate(row_new)
                    if new_value is not None:
                        estimate_history[event_date] = new_value
                    if earliest_estimate is None:
                        earliest_estimate = self._cast_estimate(row_old)
                # note: not using elif since estimation_field and initial_estimate_field could be the same!
                if row_field == initial_estimation_field:
                    new_value = self._cast_estimate(row_new)
                    if new_value is not None:
                        initial_estimate_history[event_date] = new_value
                    if earliest_initial_estimate is None:
                        earliest_initial_estimate = self._cast_estimate(
                            row_old)
                if row_field == 'status':
                    status_history[event_date] = row_new
                    if earliest_status is None:
                        earliest_status = row_old

            # If we don't know already (i.e. the ticket effort/status was
            # not changed on the creation date), set the effort on the
            # creation date. It may be that we don't have an "earliest"
            # estimate/status, because it was never changed. In this case,
            # use the current (latest) value.

            if not creation_date in estimate_history:
                if earliest_estimate is not None:
                    estimate_history[creation_date] = earliest_estimate
                else:
                    estimate_history[creation_date] = latest_estimate

            if not creation_date in initial_estimate_history:
                if earliest_initial_estimate is not None:
                    initial_estimate_history[
                        creation_date] = earliest_initial_estimate
                else:
                    initial_estimate_history[
                        creation_date] = latest_initial_estimate

            if not creation_date in status_history:
                if earliest_status is not None:
                    status_history[creation_date] = earliest_status
                else:
                    status_history[creation_date] = latest_status

            # There is a risk that the history table is messed up. Trust
            # the ticket/ticket_custom tables more.

            estimate_history[options['today']] = latest_estimate
            initial_estimate_history[
                options['today']] = latest_initial_estimate
            status_history[options['today']] = latest_status

            # Finally add estimates to the timetable. Treat any period where the
            # ticket was closed as estimate 0. We need to loop from ticket
            # creation date, not just from the timetable start date, since
            # it's possible that the ticket was changed between these two
            # dates.

            current_date = creation_date
            current_estimate = None
            previous_initial_estimate = Decimal(0)
            is_open = None

            while current_date <= options['enddate']:

                if current_date in status_history:
                    is_open = (status_history[current_date]
                               not in self.closed_states)

                if current_date in estimate_history:
                    current_estimate = estimate_history[current_date]

                if current_date in initial_estimate_history:
                    effort_delta = initial_estimate_history[
                        current_date] - previous_initial_estimate
                    if effort_delta != Decimal(0):
                        previous_initial_estimate = initial_estimate_history[
                            current_date]
                        if current_date > options[
                                'startdate'] and current_date in delta:
                            delta[current_date] += effort_delta

                if current_date in timetable and current_date >= options[
                        'startdate'] and is_open:
                    timetable[current_date] += current_estimate

                current_date += timedelta(days=1)

        return timetable, delta
Example #14
0
    def _calculate_timetable(self, options, query_args, req):
        db = self.env.get_db_cnx()

        estimation_field = self.estimation_field
        initial_estimation_field = self.initial_estimation_field or estimation_field
        one_field = (estimation_field == initial_estimation_field)

        # create dictionary with entry for each day of the required time period
        timetable = {}
        delta = {}
        
        current_date = options['startdate']
        max_date = current_date
        while current_date <= options['enddate']:
            timetable[current_date] = Decimal(0)
            delta[current_date] = Decimal(0)
            max_date = current_date
            current_date += timedelta(days=options.get('interval_days', 1))

        # Ensure we have today's date in the timetable as well, in case interval_days
        # caused us to stop before it.
        if options['today'] <= options['enddate']:
            timetable[options['today']] = Decimal(0)
            delta[options['today']] = Decimal(0)

        # get current values for all tickets within milestone and sprints     
        
        query_args[estimation_field + "!"] = None
        if estimation_field != initial_estimation_field:
            query_args[initial_estimation_field + "!"] = None
        query_args['status' + "!"] = None

        tickets = execute_query(self.env, req, query_args)

        # add the open effort for each ticket for each day to the timetable

        for t in tickets:
            
            # Record the current (latest) status and estimate, and ticket
            # creation date
            
            creation_date = t['time'].date()
            latest_status = t['status']
            latest_estimate = self._cast_estimate(t[estimation_field])
            if not latest_estimate:
                latest_estimate = Decimal(0)
            latest_initial_estimate = self._cast_estimate(t[initial_estimation_field])
            if not latest_initial_estimate:
                latest_initial_estimate = Decimal(0)
            
            # Fetch change history for status and effort fields for this ticket
            history_cursor = db.cursor()
            history_cursor.execute("SELECT " 
                "DISTINCT c.field as field, c.time AS time, c.oldvalue as oldvalue, c.newvalue as newvalue " 
                "FROM ticket t, ticket_change c "
                "WHERE t.id = %s and c.ticket = t.id and (c.field=%s or c.field=%s or c.field='status')"
                "ORDER BY c.time ASC", [t['id'], estimation_field, initial_estimation_field])
            
            # Build up two dictionaries, mapping dates when effort/status
            # changed, to the latest effort/status on that day (in case of
            # several changes on the same day). Also record the oldest known
            # effort/status, i.e. that at the time of ticket creation
            
            estimate_history = {}
            initial_estimate_history = {}
            status_history = {}
            
            earliest_estimate = None
            earliest_initial_estimate = None
            earliest_status = None
            
            for row in history_cursor:
                row_field, row_time, row_old, row_new = row
                event_date = datetime.fromtimestamp(row_time, utc).date()
                if row_field == estimation_field:
                    new_value = self._cast_estimate(row_new)
                    if new_value is not None:
                        estimate_history[event_date] = new_value
                    if earliest_estimate is None:
                        earliest_estimate = self._cast_estimate(row_old)
                # note: not using elif since estimation_field and initial_estimate_field could be the same!
                if row_field == initial_estimation_field:
                    new_value = self._cast_estimate(row_new)
                    if new_value is not None:
                        initial_estimate_history[event_date] = new_value
                    if earliest_initial_estimate is None:
                        earliest_initial_estimate = self._cast_estimate(row_old)
                if row_field == 'status':
                    status_history[event_date] = row_new
                    if earliest_status is None:
                        earliest_status = row_old
            
            # If we don't know already (i.e. the ticket effort/status was 
            # not changed on the creation date), set the effort on the
            # creation date. It may be that we don't have an "earliest"
            # estimate/status, because it was never changed. In this case,
            # use the current (latest) value.
            
            if not creation_date in estimate_history:
                if earliest_estimate is not None:
                    estimate_history[creation_date] = earliest_estimate
                else:
                    estimate_history[creation_date] = latest_estimate
            
            if not creation_date in initial_estimate_history:
                if earliest_initial_estimate is not None:
                    initial_estimate_history[creation_date] = earliest_initial_estimate
                else:
                    initial_estimate_history[creation_date] = latest_initial_estimate
            
            if not creation_date in status_history:
                if earliest_status is not None:
                    status_history[creation_date] = earliest_status
                else:
                    status_history[creation_date] = latest_status
            
            # There is a risk that the history table is messed up. Trust
            # the ticket/ticket_custom tables more.
            
            estimate_history[options['today']] = latest_estimate
            initial_estimate_history[options['today']] = latest_initial_estimate
            status_history[options['today']] = latest_status
            
            # Finally add estimates to the timetable. Treat any period where the
            # ticket was closed as estimate 0. We need to loop from ticket
            # creation date, not just from the timetable start date, since
            # it's possible that the ticket was changed between these two
            # dates.

            current_date = creation_date
            current_estimate = None
            previous_initial_estimate = Decimal(0)
            is_open = None

            while current_date <= options['enddate']:
            
                if current_date in status_history:
                    is_open = (status_history[current_date] not in self.closed_states)
            
                if current_date in estimate_history:
                    current_estimate = estimate_history[current_date]
            
                if current_date in initial_estimate_history:
                    effort_delta = initial_estimate_history[current_date] - previous_initial_estimate
                    if effort_delta != Decimal(0):
                        previous_initial_estimate = initial_estimate_history[current_date]                    
                        if current_date > options['startdate'] and current_date in delta:
                            delta[current_date] += effort_delta

                if current_date in timetable and current_date >= options['startdate'] and is_open:
                    timetable[current_date] += current_estimate
            
                current_date += timedelta(days=1)
        
        return timetable, delta