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
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()
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
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')
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')
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')
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')