def velocity_chart(self):

        graph_width = 600
        graph_height = 300

        x_max = self.total_iterations
        y_max = max(max(self.estimate_stories_data()),
                    max(self.estimate_tasks_data()),
                    max(self.work_data()))

        chart = SimpleLineChart(graph_width, graph_height,
                                x_range=(1, x_max + 1), y_range=(0, y_max + 1))

        chart.add_data(self.estimate_stories_data())
        chart.add_data(self.estimate_tasks_data())
        chart.add_data(self.work_data())

        chart.set_grid(0, 100.0 / y_max + 1, 5, 5)
        chart.set_colours(['FF0000', '00FF00', '0000FF'])
        chart.set_legend([_('rough story estimates'),
                          _('task estimates'), _('worked')])
        chart.set_legend_position('b')
        chart.set_axis_labels(Axis.LEFT, ['', '', _('days')])
        chart.set_axis_labels(Axis.BOTTOM, range(1, x_max + 1))
        chart.set_axis_range(Axis.LEFT, 0, y_max + 1)

        return chart.get_url(data_class=ExtendedData)
    def links(self):
        """ This method returns all links that should be shown in the Portlet.
        The returned dataset is as follows:

        result = [{'url':'http://somewhere.com',
                   'title':'Somewhere',
                   'class':'odd/even'}]

        If no links are available it returns None

        """
        results = []
        if self.project:
            tracker = self.tracker_url()
            attachments = self.attachments_url()
            offers = self.offers_url()
            # chart = self.chart_url()
            results.append(dict(url=self.project_url, title=_(u'Project')))
            if tracker:
                results.append(dict(url=tracker, title=_(u'Issues')))
            if attachments:
                results.append(dict(url=attachments, title=_(u'Attachments')))
            if offers:
                results.append(dict(url=offers, title=_(u'Offer')))
            # if chart:
            #    results.append(dict(url=chart, title=_(u'Overview Chart')))
        for res in results:
            row = (results.index(res) + 1) % 2 and 'odd' or 'even'
            res['class'] = 'portletItem ' + row
        return results
    def velocity_chart(self):

        graph_width = 600
        graph_height = 300

        x_max = self.total_iterations
        y_max = max(max(self.estimate_stories_data()),
                    max(self.estimate_tasks_data()), max(self.work_data()))

        chart = SimpleLineChart(graph_width,
                                graph_height,
                                x_range=(1, x_max + 1),
                                y_range=(0, y_max + 1))

        chart.add_data(self.estimate_stories_data())
        chart.add_data(self.estimate_tasks_data())
        chart.add_data(self.work_data())

        chart.set_grid(0, 100.0 / y_max + 1, 5, 5)
        chart.set_colours(['FF0000', '00FF00', '0000FF'])
        chart.set_legend(
            [_('rough story estimates'),
             _('task estimates'),
             _('worked')])
        chart.set_legend_position('b')
        chart.set_axis_labels(Axis.LEFT, ['', '', _('days')])
        chart.set_axis_labels(Axis.BOTTOM, list(range(1, x_max + 1)))
        chart.set_axis_range(Axis.LEFT, 0, y_max + 1)

        return chart.get_url(data_class=ExtendedData)
    def links(self):
        """ This method returns all links that should be shown in the Portlet.
        The returned dataset is as follows:

        result = [{'url':'http://somewhere.com',
                   'title':'Somewhere',
                   'class':'odd/even'}]

        If no links are available it returns None

        """
        results = []
        if self.project:
            tracker = self.tracker_url()
            attachments = self.attachments_url()
            offers = self.offers_url()
            # chart = self.chart_url()
            results.append(dict(url=self.project_url, title=_('Project')))
            if tracker:
                results.append(dict(url=tracker, title=_('Issues')))
            if attachments:
                results.append(dict(url=attachments, title=_('Attachments')))
            if offers:
                results.append(dict(url=offers, title=_('Offer')))
            # if chart:
            #    results.append(dict(url=chart, title=_(u'Overview Chart')))
        for res in results:
            row = (results.index(res) + 1) % 2 and 'odd' or 'even'
            res['class'] = 'portletItem ' + row
        return results
Example #5
0
class XMControlPanel(ControlPanelForm):
    form_fields = FormFieldsets()
    label = _("eXtremeManagement maintenance")
    description = _(
        "Perform various maintenance tasks. "
        "If you know the totals are wrong, you can recalculate them here. "
        "This should normally not be needed, so do not touch this unless "
        "you know what you are doing.  Then again, it does not hurt, except "
        "that it takes a long time.")

    @form.action(
        _('label_recalculate_actual_hours',
          default='Recalculate actual hours'),
        name='recalculate_actual_hours',
        validator=null_validator)
    def recalculate_actual(self, action, data):
        """Recalculate the actual hours.

        Can be used in case the totals are wrong for some reason.
        """
        context = aq_inner(self.context)
        cat = getToolByName(context, 'portal_catalog')
        for portal_type in ('Booking', 'Task', 'PoiTask',
                            'Story', 'Iteration'):
            brains = cat(portal_type=portal_type)
            for brain in brains:
                obj = brain.getObject()
                anno = IActualHours(obj)
                anno.recalc()

    @form.action(
        _('label_recalculate_estimated_time',
          default='Recalculate estimated time'),
        name='recalculate_estimated_time',
        validator=null_validator)
    def recalculate_estimate(self, action, data):
        """Recalculate the estimates.

        Can be used in case the totals are wrong for some reason.
        """
        context = aq_inner(self.context)
        cat = getToolByName(context, 'portal_catalog')
        for portal_type in ('Task', 'PoiTask', 'Story', 'Iteration'):
            brains = cat(portal_type=portal_type)
            for brain in brains:
                obj = brain.getObject()
                anno = IEstimate(obj)
                anno.recalc()
Example #6
0
 def add_iteration(self):
     context = aq_inner(self.context)
     plone_commands = self.getCommandSet('plone')
     title = self.request.form.get('title')
     if not title:
         plone_commands.issuePortalMessage(_('Title is required'),
                                           msgtype='error')
         return None
     plone_utils = getToolByName(self.context, 'plone_utils')
     new_id = plone_utils.normalizeString(title)
     context.invokeFactory(type_name="Iteration", id=new_id, title=title)
     core = self.getCommandSet('core')
     zopecommands = self.getCommandSet('zope')
     zopecommands.refreshProvider('#iterationlist',
                                  name='xm.iteration_list')
     zopecommands.refreshProvider('#add-iteration',
                                  name='xm.iteration_form')
     plone_commands.issuePortalMessage(_('Iteration added.'),
                                       msgtype='info')
 def add_iteration(self):
     context = aq_inner(self.context)
     plone_commands = self.getCommandSet('plone')
     title = self.request.form.get('title')
     if not title:
         plone_commands.issuePortalMessage(_(u'Title is required'),
                                           msgtype='error')
         return None
     plone_utils = getToolByName(self.context, 'plone_utils')
     new_id = plone_utils.normalizeString(title)
     context.invokeFactory(type_name="Iteration", id=new_id, title=title)
     core = self.getCommandSet('core')
     zopecommands = self.getCommandSet('zope')
     zopecommands.refreshProvider(
         '#iterationlist', name='xm.iteration_list')
     zopecommands.refreshProvider(
         '#add-iteration', name='xm.iteration_form')
     plone_commands.issuePortalMessage(_(u'Iteration added.'),
                                       msgtype='info')
    def iterationbrain2dict(self, brain):
        """Get a dict with info from this iteration brain.
        """
        estimate = brain.estimate
        actual = brain.actual_time
        obj = brain.getObject()
        wf_id = 'eXtreme_Iteration_Workflow'  # fallback
        wfs = self.workflow.getWorkflowsFor(obj)
        if len(wfs):
            wf_id = wfs[0].id
        history = self.workflow.getHistoryOf(wf_id, obj)
        completion_date = None
        for item in history:
            if item['action'] == 'complete':
                completion_date = item['time']
        open_stories = len(
            self.catalog(
                path=brain.getPath(),
                portal_type='Story',
                review_state=['draft', 'estimated', 'in-progress', 'pending']))
        open_tasks = len(
            self.catalog(path=brain.getPath(),
                         portal_type=['Task', 'PoiTask'],
                         review_state=['open', 'to-do']))
        if open_stories or open_tasks:
            status_warning = _("msg_status_warning",
                               default="${open_stories} open stories and "
                               "${open_tasks} open tasks",
                               mapping=dict(open_stories=open_stories,
                                            open_tasks=open_tasks))
        else:
            status_warning = ''
        returnvalue = dict(
            iteration_url=brain.getURL(),
            iteration_title=brain.Title,
            iteration_description=brain.Description,
            icon=brain.getIcon,
            man_hours=brain.getManHours,
            raw_estimate=estimate,
            estimate=formatTime(estimate),
            raw_actual=actual,
            actual=formatTime(actual),
            start_date=obj.getStartDate(),
            end_date=obj.getEndDate(),
            completion_date=completion_date,
            brain=brain,
            open_stories=open_stories,
            open_tasks=open_tasks,
            status_warning=status_warning,
        )

        returnvalue.update(self.extra_dict(obj, brain))
        return returnvalue
Example #9
0
 def __call__(self):
     form = self.request.form
     title = form.get('title', '')
     if title == '':
         # status message
         return
     context = aq_inner(self.context)
     plone_utils = getToolByName(self.context, 'plone_utils')
     new_id = plone_utils.normalizeString(title)
     context.invokeFactory(type_name="Iteration", id=new_id, title=title)
     plone_utils.addPortalMessage(_('Iteration added.'))
     self.request.response.redirect(context.absolute_url() +
                                    '/@@planned-iterations')
 def __call__(self):
     form = self.request.form
     title = form.get('title', '')
     if title == '':
         # status message
         return
     context = aq_inner(self.context)
     plone_utils = getToolByName(self.context, 'plone_utils')
     new_id = plone_utils.normalizeString(title)
     context.invokeFactory(type_name="Iteration", id=new_id, title=title)
     plone_utils.addPortalMessage(_(u'Iteration added.'))
     self.request.response.redirect(
         context.absolute_url() + '/@@planned-iterations')
    def iterationbrain2dict(self, brain):
        """Get a dict with info from this iteration brain.
        """
        estimate = brain.estimate
        actual = brain.actual_time
        obj = brain.getObject()
        wf_id = 'eXtreme_Iteration_Workflow'  # fallback
        wfs = self.workflow.getWorkflowsFor(obj)
        if len(wfs):
            wf_id = wfs[0].id
        history = self.workflow.getHistoryOf(wf_id, obj)
        completion_date = None
        for item in history:
            if item['action'] == 'complete':
                completion_date = item['time']
        open_stories = len(self.catalog(
            path=brain.getPath(), portal_type='Story',
            review_state=['draft', 'estimated', 'in-progress', 'pending']))
        open_tasks = len(self.catalog(
            path=brain.getPath(), portal_type=['Task', 'PoiTask'],
            review_state=['open', 'to-do']))
        if open_stories or open_tasks:
            status_warning = _(
                u"msg_status_warning",
                default=u"${open_stories} open stories and "
                "${open_tasks} open tasks",
                mapping=dict(open_stories=open_stories, open_tasks=open_tasks))
        else:
            status_warning = ''
        returnvalue = dict(
            iteration_url=brain.getURL(),
            iteration_title=brain.Title,
            iteration_description=brain.Description,
            icon=brain.getIcon,
            man_hours=brain.getManHours,
            raw_estimate=estimate,
            estimate=formatTime(estimate),
            raw_actual=actual,
            actual=formatTime(actual),
            start_date=obj.getStartDate(),
            end_date=obj.getEndDate(),
            completion_date=completion_date,
            brain=brain,
            open_stories=open_stories,
            open_tasks=open_tasks,
            status_warning=status_warning,
        )

        returnvalue.update(self.extra_dict(obj, brain))
        return returnvalue
    def add_task(self):
        # We *really* need the inner acquisition chain for context
        # here.  Otherwise the aq_parent is the view instead of the
        # story, which means the totals for the story are not
        # recalculated.  Sneaky! :)
        context = aq_inner(self.context)
        plone_commands = self.getCommandSet('plone')
        title = self.request.form.get('title')
        if not title:
            plone_commands.issuePortalMessage(_(u'Title is required'),
                                              msgtype='error')
            return None
        assignees = self.request.form.get('assignees', [])
        description = self.request.form.get('description', '')
        create_task(context, title=title,
                    hours=self.request.form.get('hours'),
                    minutes=self.request.form.get('minutes'),
                    assignees=assignees, description=description)
        core = self.getCommandSet('core')
        zopecommands = self.getCommandSet('zope')

        # Refresh the tasks table
        selector = core.getCssSelector('.tasklist_table')
        zopecommands.refreshProvider(selector,
                                     name='xm.tasklist.simple')

        # Refresh the add task form
        selector = core.getHtmlIdSelector('add-task')
        zopecommands.refreshProvider(selector, name='xm.task_form')

        # Refresh the story details box provider
        zopecommands.refreshProvider('.xm-details',
                                     'xm.story.detailsbox')

        # Set a portal message to inform the user of the change.
        plone_commands.issuePortalMessage(_(u'Task added'),
                                          msgtype='info')
Example #13
0
    def add_task(self):
        # We *really* need the inner acquisition chain for context
        # here.  Otherwise the aq_parent is the view instead of the
        # story, which means the totals for the story are not
        # recalculated.  Sneaky! :)
        context = aq_inner(self.context)
        plone_commands = self.getCommandSet('plone')
        title = self.request.form.get('title')
        if not title:
            plone_commands.issuePortalMessage(_('Title is required'),
                                              msgtype='error')
            return None
        assignees = self.request.form.get('assignees', [])
        description = self.request.form.get('description', '')
        create_task(context,
                    title=title,
                    hours=self.request.form.get('hours'),
                    minutes=self.request.form.get('minutes'),
                    assignees=assignees,
                    description=description)
        core = self.getCommandSet('core')
        zopecommands = self.getCommandSet('zope')

        # Refresh the tasks table
        selector = core.getCssSelector('.tasklist_table')
        zopecommands.refreshProvider(selector, name='xm.tasklist.simple')

        # Refresh the add task form
        selector = core.getHtmlIdSelector('add-task')
        zopecommands.refreshProvider(selector, name='xm.task_form')

        # Refresh the story details box provider
        zopecommands.refreshProvider('.xm-details', 'xm.story.detailsbox')

        # Set a portal message to inform the user of the change.
        plone_commands.issuePortalMessage(_('Task added'), msgtype='info')
    def move_story(self, source_id, target_id, story_id, index):
        core = self.getCommandSet('core')
        plone = self.getCommandSet('plone')
        cns = self.getCommandSet('cns')
        source, target, story = self.extract_objects(source_id, target_id,
                                                     story_id)
        if source == None:  # The rest is also None.
            plone.issuePortalMessage(_('Drag/drop uids incorrect'),
                                     msgtype='error')
            return

        logger.info('%s dragged from %s to %s', story, source, target)
        if source != target:
            # Source and target are different: move the story.
            # Apparently it's possible for this story to be 'locked' - we'd
            # better be ready for that...
            try:
                self.move(source, target, story)
            except ResourceLockedError:
                logger.info('Resource locked')
                IStatusMessage(self.request).addStatusMessage(_(
                    "Move failed: story locked. "
                    "Unlock the story to move it."),
                                                              type='error')
                cns.redirectRequest(story.absolute_url())
                return

            msg = _('label_moved_succesfully',
                    default="Moved story '${story}' to iteration '${target}'.",
                    mapping={
                        'story': story.Title(),
                        'target': target.Title()
                    })
            if target_id == 'unplanned_stories':
                msg = _(
                    'label_moved_to_unplanned_succesfully',
                    default="Moved story '${story}' to the unplanned list.",
                    mapping={'story': story.Title()})

            plone.issuePortalMessage(msg, msgtype='info')
            # We have to set the right kssattr again now that our parent has
            # changed.
            format = 'kssattr-source_id-%s'
            old = format % source_id
            new = format % target_id
            node = core.getHtmlIdSelector(story_id)
            core.removeClass(node, old)
            core.addClass(node, new)

        # Give the dragged object the right position.
        target.moveObjectToPosition(story.getId(), int(index))
        putils = getToolByName(self.context, 'plone_utils')
        putils.reindexOnReorder(target)
        logger.info('Story %s now has position %s in the target folder.',
                    story, index)

        if source == target:
            # We haven't moved the object to another iteration, so that didn't
            # give us a status message. To provide feedback that something
            # happened we'll say that the object's position has been modified.
            msg = _('label_order_updated_succesfully',
                    default=("The position of story '${story}' in the "
                             "iteration has been changed."),
                    mapping={'story': story.Title()})
            plone.issuePortalMessage(msg, msgtype='info')
Example #15
0
 def title(self):
     """This property is used to give the title of the portlet in the
     "manage portlets" screen.
     """
     return _('Project Management')
 def title(self):
     """This property is used to give the title of the portlet in the
     "manage portlets" screen.
     """
     return _('Create tasks from issues')
 def title(self):
     """This property is used to give the title of the portlet in the
     "manage portlets" screen.
     """
     return _(u'Related XM Tasks')
 def title(self):
     """This property is used to give the title of the portlet in the
     "manage portlets" screen.
     """
     return _('box_stories', default='Pending Stories')
 def title(self):
     """This property is used to give the title of the portlet in the
     "manage portlets" screen.
     """
     return _(u'Iterations')
 def title(self):
     """This property is used to give the title of the portlet in the
     "manage portlets" screen.
     """
     return _(u'box_stories', default=u'Pending Stories')
 def title(self):
     """This property is used to give the title of the portlet in the
     "manage portlets" screen.
     """
     return _(u'Project Management')
Example #22
0
 def title(self):
     """This property is used to give the title of the portlet in the
     "manage portlets" screen.
     """
     return _('Related XM Tasks')
    def move_story(self, source_id, target_id, story_id, index):
        core = self.getCommandSet('core')
        plone = self.getCommandSet('plone')
        cns = self.getCommandSet('cns')
        source, target, story = self.extract_objects(source_id,
                                                     target_id,
                                                     story_id)
        if source == None:  # The rest is also None.
            plone.issuePortalMessage(_(u'Drag/drop uids incorrect'),
                                     msgtype='error')
            return

        logger.info('%s dragged from %s to %s', story, source, target)
        if source != target:
            # Source and target are different: move the story.
            # Apparently it's possible for this story to be 'locked' - we'd
            # better be ready for that...
            try:
                self.move(source, target, story)
            except ResourceLockedError:
                logger.info('Resource locked')
                IStatusMessage(self.request).addStatusMessage(
                    _(u"Move failed: story locked. "
                      u"Unlock the story to move it."),
                    type='error')
                cns.redirectRequest(story.absolute_url())
                return

            msg = _(u'label_moved_succesfully',
                    default=u"Moved story '${story}' to iteration '${target}'.",
                    mapping={'story': story.Title(),
                             'target': target.Title()})
            if target_id == 'unplanned_stories':
                msg = _(
                    u'label_moved_to_unplanned_succesfully',
                    default=u"Moved story '${story}' to the unplanned list.",
                    mapping={'story': story.Title()})

            plone.issuePortalMessage(msg, msgtype='info')
            # We have to set the right kssattr again now that our parent has
            # changed.
            format = 'kssattr-source_id-%s'
            old = format % source_id
            new = format % target_id
            node = core.getHtmlIdSelector(story_id)
            core.removeClass(node, old)
            core.addClass(node, new)

        # Give the dragged object the right position.
        target.moveObjectToPosition(story.getId(), int(index))
        putils = getToolByName(self.context, 'plone_utils')
        putils.reindexOnReorder(target)
        logger.info('Story %s now has position %s in the target folder.',
                    story, index)

        if source == target:
            # We haven't moved the object to another iteration, so that didn't
            # give us a status message. To provide feedback that something
            # happened we'll say that the object's position has been modified.
            msg = _(u'label_order_updated_succesfully',
                    default=(u"The position of story '${story}' in the "
                             u"iteration has been changed."),
                    mapping={'story': story.Title()})
            plone.issuePortalMessage(msg, msgtype='info')
 def title(self):
     """This property is used to give the title of the portlet in the
     "manage portlets" screen.
     """
     return _('Iterations')