/** * This action demonstrates the usage of project milestone service and project responsible person * service in Cockpit. * * <p>Version 1.1 (Module Assembly - TC Cockpit Project Milestones Management Front End) update: - * Update {@link #updateProjectMilestone()} according to the new milestone service update. - Fix the * error handling of ajax based operation methods. * * @author GreatKevin * @version 1.1 */ public class ProjectMilestoneDemoAction extends BaseDirectStrutsAction { /** All the avaible milestone status. */ private static final List ALL_MILESTONE_STATUS = Arrays.asList(MilestoneStatus.values()); /** The date format used for display the milestone due date. */ private static final DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy"); /** The view data which stores all the projects of the user. */ private DashboardSearchResultsDTO viewData = new DashboardSearchResultsDTO(); /** The direct project id. */ private long directProjectId; /** The milestone id. */ private long milestoneId; /** The single milestone instance. */ private Milestone milestone; /** A list of project milestones. */ private List<Milestone> milestones; /** A list of milestone ids. */ private List<Long> milestoneIds; /** * The main execution, get all the user's projects. * * @throws Exception if error. */ @Override protected void executeAction() throws Exception { HttpServletRequest request = DirectUtils.getServletRequest(); SessionData sessionData = new SessionData(request.getSession()); TCSubject currentUser = getCurrentUser(); // Set projects data List<ProjectBriefDTO> projects = DataProvider.getUserProjects(currentUser.getUserId()); UserProjectsDTO userProjectsDTO = new UserProjectsDTO(); userProjectsDTO.setProjects(projects); getViewData().setUserProjects(userProjectsDTO); // Set current project contests ProjectBriefDTO currentProject = sessionData.getCurrentProjectContext(); if (currentProject != null) { List<TypedContestBriefDTO> contests = DataProvider.getProjectTypedContests(currentUser.getUserId(), currentProject.getId()); sessionData.setCurrentProjectContests(contests); } viewData.setProjects(DataProvider.searchUserProjects(currentUser, "")); } /** * Method to handle the ajax operation getProjectMilestones. Gets all the milestones of the * project and return as json. * * @return the result code. * @throws Exception if error. */ public String getProjectMilestones() throws Exception { List<Map<String, Object>> result = new LinkedList<Map<String, Object>>(); try { List<Milestone> all = getMilestoneService() .getAll(getDirectProjectId(), ALL_MILESTONE_STATUS, SortOrder.DESCENDING); for (Milestone m : all) { Map<String, Object> milestoneData = new HashMap<String, Object>(); milestoneData.put("id", m.getId()); milestoneData.put("name", m.getName()); milestoneData.put("description", m.getDescription()); milestoneData.put("dueDate", dateFormat.format(m.getDueDate())); String ownerName = "none"; Long ownerId = -1L; if (m.getOwners() != null && m.getOwners().size() > 0) { ownerName = m.getOwners().get(0).getName(); ownerId = m.getOwners().get(0).getUserId(); } milestoneData.put("ownerName", ownerName); milestoneData.put("ownerUserId", ownerId); milestoneData.put("notification", m.isSendNotifications()); milestoneData.put("completed", m.isCompleted()); milestoneData.put("status", m.getStatus().toString()); result.add(milestoneData); } setResult(result); } catch (Throwable e) { // set the error message into the ajax response if (getModel() != null) { setResult(e.getMessage()); } return ERROR; } return SUCCESS; } /** * Method to handle the ajax operation getProjectResponsiblePerson. * * <p>Gets all the responsible person of the project and return as json. * * @return the result code. * @throws Exception if error. */ public String getProjectResponsiblePerson() throws Exception { List<Map<String, Object>> result = new LinkedList<Map<String, Object>>(); try { List<ResponsiblePerson> allResponsiblePeople = getMilestoneResponsiblePersonService().getAllResponsiblePeople(getDirectProjectId()); for (ResponsiblePerson p : allResponsiblePeople) { Map<String, Object> data = new HashMap<String, Object>(); data.put("userId", p.getUserId()); data.put("name", p.getName()); result.add(data); } setResult(result); } catch (Throwable e) { // set the error message into the ajax response if (getModel() != null) { setResult(e); } return ERROR; } return SUCCESS; } /** * Method to handle the ajax operation removeProjectMilestone. * * <p>It removes the project mielstone specified by the milestone id. * * @return the result code. * @throws Exception if error. */ public String removeProjectMilestone() throws Exception { Map<String, Object> result = new HashMap<String, Object>(); try { getMilestoneService().delete(getMilestoneId()); result.put("operation", "remove"); result.put("milestoneId", getMilestoneId()); setResult(result); } catch (Throwable e) { // set the error message into the ajax response if (getModel() != null) { setResult(e.getMessage()); } return ERROR; } return SUCCESS; } /** * Method to handle the ajax operation addProjectMilestone. * * <p>It adds the new milestone specified by the Milestone object. * * @return the result code. * @throws Exception if error. */ public String addProjectMilestone() throws Exception { Map<String, Object> result = new HashMap<String, Object>(); try { long newMilestoneId = getMilestoneService().add(getMilestone()); result.put("operation", "add"); result.put("milestoneId", newMilestoneId); setResult(result); } catch (Throwable e) { // set the error message into the ajax response if (getModel() != null) { setResult(e.getMessage()); } return ERROR; } return SUCCESS; } /** * Method to handle the ajax operation updateProjectMilestone. * * <p>It updates the existing milestone specified by the Milestone object. * * @return the result code. * @throws Exception if error. */ public String updateProjectMilestone() throws Exception { Map<String, Object> result = new HashMap<String, Object>(); try { Milestone m = getMilestone(); getMilestoneService().update(m); result.put("operation", "update"); result.put("milestoneId", getMilestone().getId()); setResult(result); } catch (Throwable e) { // set the error message into the ajax response if (getModel() != null) { setResult(e); } return ERROR; } return SUCCESS; } /** * Method to handle the ajax operation addProjectMilestones. * * <p>It adds a list of project milestones in one batch. * * @return the result code. * @throws Exception if error. */ public String addProjectMilestones() throws Exception { Map<String, Object> result = new HashMap<String, Object>(); try { getMilestoneService().add(getMilestones()); result.put("operation", "addAll"); setResult(result); } catch (Throwable e) { // set the error message into the ajax response if (getModel() != null) { setResult(e); } return ERROR; } return SUCCESS; } /** * Removes multiple project milestones at one time. * * @return the result code. * @throws Exception if error. */ public String removeProjectMilestones() throws Exception { Map<String, Object> result = new HashMap<String, Object>(); try { getMilestoneService().delete(getMilestoneIds()); result.put("operation", "deleteAll"); setResult(result); } catch (Throwable e) { // set the error message into the ajax response if (getModel() != null) { setResult(e.getMessage()); } return ERROR; } return SUCCESS; } /** * Gets the view data. * * @return the view data. */ public DashboardSearchResultsDTO getViewData() { return viewData; } /** * Sets the view data. * * @param viewData the view data. */ public void setViewData(DashboardSearchResultsDTO viewData) { this.viewData = viewData; } /** * Gets the direct project id. * * @return the direct project id. */ public long getDirectProjectId() { return directProjectId; } /** * Sets the direct project id. * * @param directProjectId the direct project id. */ public void setDirectProjectId(long directProjectId) { this.directProjectId = directProjectId; } /** * Gets the project milestone. * * @return the project milestone. */ public Milestone getMilestone() { return milestone; } /** * Sets the project milestone. * * @param milestone the project milestone */ public void setMilestone(Milestone milestone) { this.milestone = milestone; } /** * Gets the list of project milestones. * * @return the list of project milestones. */ public List<Milestone> getMilestones() { return milestones; } /** * Sets the list of project milestones. * * @param milestones the list of project milestones. */ public void setMilestones(List<Milestone> milestones) { this.milestones = milestones; } /** * Gets the id of the milestone. * * @return the id of the milestone. */ public long getMilestoneId() { return milestoneId; } /** * Sets the id of the milestone. * * @param milestoneId the id of the milestone. */ public void setMilestoneId(long milestoneId) { this.milestoneId = milestoneId; } /** * Gets the list of milestone ids. * * @return the list of milestone ids. */ public List<Long> getMilestoneIds() { return milestoneIds; } /** * Sets the list of milestone ids. * * @param milestoneIds the list of milestone ids. */ public void setMilestoneIds(List<Long> milestoneIds) { this.milestoneIds = milestoneIds; } }
/** * This action class handles all the views for project milestone management: project milestone list * view, project milestone calendar view and multiple project milestones batch creation view. * * <p>Version 1.1 (Module Assembly - TC Cockpit Contest Milestone Association Milestone Page Update) * * <ul> * <li>Updated {@link #executeAction()} to remove the codes to get milestone list view data, it's * changed to get through ajax request * <li>Added method {@link #getProjectMilestoneListData()} to get the project milestone list data * </ul> * * <p>Version 1.2 (TopCoder Direct - Quick Bug Fixes 09.23) * * <ul> * <li>Updated {@link #getProjectResponsiblePerson()} to only retrieve for positive (valid) * project ID * </ul> * * @author GreatKevin, Veve * @version 1.2 */ public class ProjectMilestoneViewAction extends BaseDirectStrutsAction implements FormAction<ProjectMilestoneViewForm> { /** The default user handle color to display for the responsible person of the milestone. */ private static final String DEFAULT_USER_HANDLE_CLASS = "coderTextOrange"; /** The default user handle link for the responsible person of the milestone. */ private static final String DEFAULT_USER_HANDLE_URL = "javascript:;"; /** List of all the available milestone status. */ private static final List ALL_MILESTONE_STATUS = Arrays.asList(MilestoneStatus.values()); /** The date string format of milestone due date and completion date. */ private final DateFormat CALENDAR_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); /** The form data. */ private ProjectMilestoneViewForm formData = new ProjectMilestoneViewForm(); /** The view data. */ private ProjectMilestoneViewDTO viewData = new ProjectMilestoneViewDTO(); /** * Redirects to different view by checking the view type. * * @return the result code. * @throws Exception if there is error. */ @Override public String execute() throws Exception { String resultCode = super.execute(); if (SUCCESS.equals(resultCode)) { if (formData.getViewType().equals(ProjectMilestoneViewForm.LIST_VIEW)) { return ProjectMilestoneViewForm.LIST_VIEW; } if (formData.getViewType().equals(ProjectMilestoneViewForm.CALENDAR_VIEW)) { return ProjectMilestoneViewForm.CALENDAR_VIEW; } if (formData.getViewType().equals(ProjectMilestoneViewForm.MULTIPLE_CREATION_VIEW)) { return ProjectMilestoneViewForm.MULTIPLE_CREATION_VIEW; } } return resultCode; } /** * Main execution for the action, prepare different set of data according to the view type. * * @throws Exception if there is any error. */ @Override protected void executeAction() throws Exception { // set responsible person data final List<ResponsiblePerson> allResponsiblePeople = getMilestoneResponsiblePersonService() .getAllResponsiblePeople(getFormData().getProjectId()); getViewData().setResponsiblePersons(allResponsiblePeople); // right side bar data List<ProjectBriefDTO> projects = DataProvider.getUserProjects(getSessionData().getCurrentUserId()); List<TypedContestBriefDTO> contests = DataProvider.getProjectTypedContests( getSessionData().getCurrentUserId(), formData.getProjectId()); UserProjectsDTO userProjectsDTO = new UserProjectsDTO(); userProjectsDTO.setProjects(projects); viewData.setUserProjects(userProjectsDTO); // put project into the session if (contests.size() > 0) { getSessionData().setCurrentProjectContext(contests.get(0).getProject()); } else { for (ProjectBriefDTO p : projects) { if (p.getId() == getFormData().getProjectId()) { getSessionData().setCurrentProjectContext(p); break; } } } getSessionData().setCurrentSelectDirectProjectID(getFormData().getProjectId()); // set project contests getSessionData().setCurrentProjectContests(contests); } /** * Gets the responsible person list for the project. * * @return the result code. * @throws Exception if there is any error. */ public String getProjectResponsiblePerson() throws Exception { List<Map<String, Object>> result = new LinkedList<Map<String, Object>>(); try { if (getFormData().getProjectId() > 0) { List<Permission> permissionsByProject = getPermissionServiceFacade() .getPermissionsByProject( DirectUtils.getTCSubjectFromSession(), getFormData().getProjectId()); for (Permission p : permissionsByProject) { Map<String, Object> data = new HashMap<String, Object>(); data.put("userId", p.getUserId()); data.put("name", p.getUserHandle()); result.add(data); } } setResult(result); } catch (Throwable e) { // set the error message into the ajax response if (getModel() != null) { setResult(e); } return ERROR; } return SUCCESS; } /** * Gets the project milestones calendar dat via ajax. * * @return the result code. * @throws Exception if there is any error. */ public String getProjectMilestoneCalendarData() throws Exception { List<Map<String, Object>> result = new LinkedList<Map<String, Object>>(); try { final List<Milestone> allMilestones = this.getMilestoneService() .getAll(getFormData().getProjectId(), ALL_MILESTONE_STATUS, SortOrder.ASCENDING); for (Milestone m : allMilestones) { Map<String, Object> data = new HashMap<String, Object>(); data.put("title", m.getName()); data.put("status", m.getStatus().toString().toLowerCase()); if (m.isCompleted()) { data.put("start", CALENDAR_DATE_FORMAT.format(m.getCompletionDate())); } else { data.put("start", CALENDAR_DATE_FORMAT.format(m.getDueDate())); } data.put("description", m.getDescription()); if (m.getOwners() != null && m.getOwners().size() > 0) { ResponsiblePerson rp = m.getOwners().get(0); if (rp != null) { Map<String, Object> person = new HashMap<String, Object>(); person.put("name", rp.getName()); person.put("color", DEFAULT_USER_HANDLE_CLASS); person.put("url", DEFAULT_USER_HANDLE_URL); data.put("person", person); } } result.add(data); } setResult(result); } catch (Throwable e) { // set the error message into the ajax response if (getModel() != null) { setResult(e); } return ERROR; } return SUCCESS; } /** * Gets the project milestone list view data with ajax. * * @return the result code. * @throws Exception if there is any error. * @since 1.1 */ public String getProjectMilestoneListData() throws Exception { try { List<Milestone> overdueMilestones = getMilestoneService() .getAll( formData.getProjectId(), Arrays.asList(new MilestoneStatus[] {MilestoneStatus.OVERDUE}), SortOrder.ASCENDING); List<Milestone> upcomingMilestones = getMilestoneService() .getAll( formData.getProjectId(), Arrays.asList(new MilestoneStatus[] {MilestoneStatus.UPCOMING}), SortOrder.ASCENDING); List<Milestone> completedMilestones = getMilestoneService() .getAll( formData.getProjectId(), Arrays.asList(new MilestoneStatus[] {MilestoneStatus.COMPLETED}), SortOrder.DESCENDING); List<MilestoneContestDTO> milestoneContestAssociations = DataProvider.getMilestoneContestAssociations( formData.getProjectId(), 0, DirectUtils.getTCSubjectFromSession().getUserId()); Map<Long, List<MilestoneContestDTO>> tempMapping = new HashMap<Long, List<MilestoneContestDTO>>(); for (MilestoneContestDTO mcd : milestoneContestAssociations) { if (tempMapping.get(mcd.getMilestoneId()) == null) { tempMapping.put(mcd.getMilestoneId(), new ArrayList<MilestoneContestDTO>()); } tempMapping.get(mcd.getMilestoneId()).add(mcd); } Map<String, List<ProjectMilestoneDTO>> result = new HashMap<String, List<ProjectMilestoneDTO>>(); result.put("overdue", new ArrayList<ProjectMilestoneDTO>()); result.put("upcoming", new ArrayList<ProjectMilestoneDTO>()); result.put("completed", new ArrayList<ProjectMilestoneDTO>()); for (Milestone m : overdueMilestones) { ProjectMilestoneDTO pmd = new ProjectMilestoneDTO(); pmd.setMilestone(m); pmd.setContests(tempMapping.get(m.getId())); result.get("overdue").add(pmd); } for (Milestone m : upcomingMilestones) { ProjectMilestoneDTO pmd = new ProjectMilestoneDTO(); pmd.setMilestone(m); pmd.setContests(tempMapping.get(m.getId())); result.get("upcoming").add(pmd); } for (Milestone m : completedMilestones) { ProjectMilestoneDTO pmd = new ProjectMilestoneDTO(); pmd.setMilestone(m); pmd.setContests(tempMapping.get(m.getId())); result.get("completed").add(pmd); } ObjectMapper m = new ObjectMapper(); setResult(m.convertValue(result, Map.class)); } catch (Throwable e) { // set the error message into the ajax response if (getModel() != null) { setResult(e); } return ERROR; } return SUCCESS; } /** * Gets the form data. * * @return the form data. */ public ProjectMilestoneViewForm getFormData() { return formData; } /** * Sets the form data. * * @param formData the form data to set. */ public void setFormData(ProjectMilestoneViewForm formData) { this.formData = formData; } /** * Gets the view data. * * @return the view data. */ public ProjectMilestoneViewDTO getViewData() { return viewData; } /** * Sets the view data. * * @param viewData the view data to set. */ public void setViewData(ProjectMilestoneViewDTO viewData) { this.viewData = viewData; } }
/** * The action handles the ajax requests for roadmap part of the enterprise dashboard. * * <p>Version 1.1 (Release Assembly - TC Cockpit New Enterprise Dashboard Release 1) * * <ul> * <li>Add method {@link #getRoadmapCalendar()} to get roadmap calendar data via ajax. * </ul> * * @author GreatKevin * @version 1.1 */ public class DashboardRoadmapAction extends BaseDirectStrutsAction implements FormAction<EnterpriseDashboardFilterForm> { /** The date format to parse the date in the request. */ private static final DateFormat SOURCE_DATE_FORMAT = new SimpleDateFormat("MMM''yy"); /** The date format to format the milestone due date and completion date in response. */ private static final DateFormat MILESTONE_DATE_FORMAT = new SimpleDateFormat("MMMMM dd, yyyy"); /** The status and order filter used to filter and sort the project milestones. */ private static final Map<MilestoneStatus, SortOrder> filters = new HashMap<MilestoneStatus, SortOrder>(); /** * List of all the available milestone status. * * @since 1.1 */ private static final List ALL_MILESTONE_STATUS = Arrays.asList(MilestoneStatus.values()); /** * Logger for this class. * * @since 1.10.8 */ private static final Logger logger = Logger.getLogger(DashboardRoadmapAction.class); /** Static constructor */ static { // create the filters to send to milestone service filters.put(MilestoneStatus.OVERDUE, SortOrder.ASCENDING); filters.put(MilestoneStatus.UPCOMING, SortOrder.ASCENDING); filters.put(MilestoneStatus.COMPLETED, SortOrder.DESCENDING); } /** * The view data of the action. * * @since 1.1 */ private DashboardMilestoneCalendarDTO viewData = new DashboardMilestoneCalendarDTO(); /** The form data of the action. */ private EnterpriseDashboardFilterForm formData = new EnterpriseDashboardFilterForm(); /** * Gets the form data of the action. * * @return the form data of the action. */ public EnterpriseDashboardFilterForm getFormData() { return this.formData; } /** * Sets the form data of the action. * * @param formData the form data of the action. */ public void setFormData(EnterpriseDashboardFilterForm formData) { this.formData = formData; } /** * Gets view data for the roadmap calendar. * * @return the view data. * @since 1.1 */ public DashboardMilestoneCalendarDTO getViewData() { return viewData; } /** * Sets view data for the roadmap calendar * * @param viewData the view data. * @since 1.1 */ public void setViewData(DashboardMilestoneCalendarDTO viewData) { this.viewData = viewData; } /** * Empty, ajax requests are handled via action methods invocation. * * @throws Exception if there is any error. */ @Override protected void executeAction() throws Exception { // do nothing } /** * Handles the ajax request to get the data for enterprise dashboard road map. * * @return the result code. */ public String getProjectsMilestones() { try { Map<String, List<Map<String, String>>> result = new HashMap<String, List<Map<String, String>>>(); final Map<Long, String> projects = DataProvider.getEnterpriseDashboardFilteredProjects(getFormData()); if (projects == null || projects.size() == 0) { // if no projects, return empty result List<Map<String, String>> emptyList = new ArrayList<Map<String, String>>(); result.put("overdue", emptyList); result.put("upcoming", emptyList); result.put("completed", emptyList); setResult(result); return SUCCESS; } if (getMilestoneService() == null) { throw new IllegalStateException("The project milestone service is not initialized"); } Calendar calendar = Calendar.getInstance(); // get start date calendar.setTime(SOURCE_DATE_FORMAT.parse(getFormData().getStartMonth())); Date startDate = calendar.getTime(); // get end date calendar.setTime(SOURCE_DATE_FORMAT.parse(getFormData().getEndMonth())); calendar.add(Calendar.MONTH, 1); calendar.add(Calendar.SECOND, -1); Date endDate = calendar.getTime(); // get the milestones with milestone service final Map<MilestoneStatus, List<Milestone>> allForProjectsGroupedByStatus = getMilestoneService() .getAllForProjectsGroupedByStatus( new ArrayList<Long>(projects.keySet()), filters, startDate, endDate); // add overdue List<Map<String, String>> overdueList = new ArrayList<Map<String, String>>(); if (allForProjectsGroupedByStatus.get(MilestoneStatus.OVERDUE) != null && allForProjectsGroupedByStatus.get(MilestoneStatus.OVERDUE).size() > 0) { List<Milestone> overdueMilestones = allForProjectsGroupedByStatus.get(MilestoneStatus.OVERDUE); for (Milestone m : overdueMilestones) { overdueList.add(getMilestoneJsonData(m, projects)); } } result.put("overdue", overdueList); // add upcoming List<Map<String, String>> upcomingList = new ArrayList<Map<String, String>>(); if (allForProjectsGroupedByStatus.get(MilestoneStatus.UPCOMING) != null && allForProjectsGroupedByStatus.get(MilestoneStatus.UPCOMING).size() > 0) { List<Milestone> upcomingMilestones = allForProjectsGroupedByStatus.get(MilestoneStatus.UPCOMING); for (Milestone m : upcomingMilestones) { upcomingList.add(getMilestoneJsonData(m, projects)); } } result.put("upcoming", upcomingList); // add completed List<Map<String, String>> completedList = new ArrayList<Map<String, String>>(); if (allForProjectsGroupedByStatus.get(MilestoneStatus.COMPLETED) != null && allForProjectsGroupedByStatus.get(MilestoneStatus.COMPLETED).size() > 0) { List<Milestone> completedMilestones = allForProjectsGroupedByStatus.get(MilestoneStatus.COMPLETED); for (Milestone m : completedMilestones) { completedList.add(getMilestoneJsonData(m, projects)); } } result.put("completed", completedList); setResult(result); } catch (Throwable e) { e.printStackTrace(System.err); if (getModel() != null) { setResult(e); } } return SUCCESS; } /** * Gets the roadmap calendar json data via ajax. * * @return the result code. * @throws Exception if there is any error. * @since 1.1 */ public String getRoadmapCalendar() throws Exception { Map<String, List<Map<String, String>>> result = new HashMap<String, List<Map<String, String>>>(); final Map<Long, String> projects = DataProvider.getEnterpriseDashboardFilteredProjects(getFormData()); viewData.setProjects(projects); getViewData().setResponsiblePersonIds(new HashSet<Long>()); final List<Milestone> milestones; if (projects == null || projects.size() == 0) { milestones = new ArrayList<Milestone>(); } else { milestones = getMilestoneService() .getAllForProjects( new ArrayList<Long>(projects.keySet()), ALL_MILESTONE_STATUS, SortOrder.ASCENDING); } // extra all the responsible person user id from the milestone for (Milestone m : milestones) { m.setName(StringEscapeUtils.escapeHtml4(StringEscapeUtils.escapeJson(m.getName()))); m.setDescription( StringEscapeUtils.escapeHtml4(StringEscapeUtils.escapeJson(m.getDescription()))); if (m.getOwners() != null && m.getOwners().size() > 0) { getViewData().getResponsiblePersonIds().add(m.getOwners().get(0).getUserId()); } } // set the milestones viewData.setMilestones(milestones); return SUCCESS; } /** * Gets the map to represent a single milestone. * * @param m the milestone * @param projects the projects cache * @return the map representing a single milestone. */ private static Map<String, String> getMilestoneJsonData(Milestone m, Map<Long, String> projects) { Map<String, String> item = new HashMap<String, String>(); item.put("title", StringEscapeUtils.escapeHtml4(m.getName())); item.put("projectId", String.valueOf(m.getProjectId())); item.put("projectName", StringEscapeUtils.escapeHtml4(projects.get(m.getProjectId()))); item.put("description", StringEscapeUtils.escapeHtml4(m.getDescription())); item.put( "date", MILESTONE_DATE_FORMAT.format(m.isCompleted() ? m.getCompletionDate() : m.getDueDate())); return item; } }