From 6af732164ab7f18356fc4eab2f2f69a2ea851605 Mon Sep 17 00:00:00 2001 From: Miciele Ghiorghis Date: Wed, 24 Oct 2012 10:47:10 +0200 Subject: [PATCH 01/88] Jira-integration: Jira configuration entity --- .../common/entities/JiraConfiguration.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100755 libreplan-business/src/main/java/org/libreplan/business/common/entities/JiraConfiguration.java diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/JiraConfiguration.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/JiraConfiguration.java new file mode 100755 index 000000000..5acdd6992 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/JiraConfiguration.java @@ -0,0 +1,82 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.business.common.entities; + +import org.libreplan.business.common.BaseEntity; + +public class JiraConfiguration extends BaseEntity { + + public static JiraConfiguration create() { + return create(new JiraConfiguration()); + } + + private boolean jiraActivated; + + private String jiraUrl; + + private String jiraLabelUrl; + + private String jiraUserId; + + private String jiraPassword; + + + public boolean isJiraActivated() { + return jiraActivated; + } + + public void setJiraActivated(boolean jiraActivated) { + this.jiraActivated = jiraActivated; + } + + public String getJiraUrl() { + return jiraUrl; + } + + public void setJiraUrl(String jiraUrl) { + this.jiraUrl = jiraUrl; + } + + public String getJiraLabelUrl() { + return jiraLabelUrl; + } + + public void setJiraLabelUrl(String jiraLabelUrl) { + this.jiraLabelUrl = jiraLabelUrl; + } + + public String getJiraUserId() { + return jiraUserId; + } + + public void setJiraUserId(String jiraUserId) { + this.jiraUserId = jiraUserId; + } + + public String getJiraPassword() { + return jiraPassword; + } + + public void setJiraPassword(String jiraPassword) { + this.jiraPassword = jiraPassword; + } + + +} From a8f56820f992825f51c3310b6dd402a849186be2 Mon Sep 17 00:00:00 2001 From: Miciele Ghiorghis Date: Wed, 24 Oct 2012 11:30:29 +0200 Subject: [PATCH 02/88] Jira-integration: synchronize order-elements with jira issues An interface that JiraOrderElementSynchronizer implements. It synchronizes order-elements inclusive progress assignments and measurements of an existing order with jira issues --- .../IJiraOrderElementSynchronizer.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100755 libreplan-webapp/src/main/java/org/libreplan/importers/IJiraOrderElementSynchronizer.java diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraOrderElementSynchronizer.java b/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraOrderElementSynchronizer.java new file mode 100755 index 000000000..40d2deb0e --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraOrderElementSynchronizer.java @@ -0,0 +1,89 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers; + +import java.util.List; + +import org.libreplan.business.advance.entities.AdvanceMeasurement; +import org.libreplan.business.advance.entities.DirectAdvanceAssignment; +import org.libreplan.business.orders.entities.Order; +import org.libreplan.business.orders.entities.OrderElement; +import org.libreplan.importers.jira.Issue; + +/** + * Synchronize order elements inclusive progress assignments and measurements of + * an existing order with Jira issues. + * + * Jira issues will be retrieved from Jira RESTful web service using + * {@link JiraRESTClient} + * + * @author Miciele Ghiorghis + */ +public interface IJiraOrderElementSynchronizer { + + /** + * Gets all distinct jira lables from an external 'php' script. + * + * This is because at this moment Jira doesn't support Labels request. As + * workaround we build a simple php script to do the query in Jira database + * and returns a comma separated string(labels). Once Jira supports the + * labels request this method will be modified. + * + * @return A list of labels + */ + List getAllJiraLabels(); + + /** + * Get all jira issues based on the specified label parameter + * from jira RESTFul web service + * + * @param label + * search criteria for jira issues + * + * @return list of jira issues + */ + List getJiraIssues(String label); + + /** + * Synchronizes the list of {@link OrderElement}s, + * {@link DirectAdvanceAssignment}s and {@link AdvanceMeasurement}s of the + * given {@link Order} with jira issues. + * + * Loops through all jira issues and check if an + * {@link OrderElement} of the given order exists. If it + * exists, update the {@link OrderElement} with the issue item. If not + * create new {@link OrderElement}, update it with the issue item and add to + * the order and start synchronization of + * {@link DirectAdvanceAssignment} and {@link AdvanceMeasurement} + * + * @param order + * an existing order where its orderElements will be synchronized + * with jira issues + * @param issues + * jira issues + */ + void syncOrderElementsWithJiraIssues(Order order, List issues); + + /** + * returns synchronization info, success or fail info + */ + JiraSyncInfo getJiraSyncInfo(); + +} From b51905911fb1c0995947c5adc9c52f7bab068a43 Mon Sep 17 00:00:00 2001 From: Miciele Ghiorghis Date: Wed, 24 Oct 2012 11:53:27 +0200 Subject: [PATCH 03/88] Jira-integration: synchronize the timesheets of order-tasks with jira issues An interface that JiraTimesheetSynchronizer implements. It synchronizes the timesheets of order-tasks of an existing order with jira issues A WorkReportType object with the name 'jira-connector' must exist and configured properly prior to start synchronization of timesheets --- .../importers/IJiraTimesheetSynchronizer.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 libreplan-webapp/src/main/java/org/libreplan/importers/IJiraTimesheetSynchronizer.java diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraTimesheetSynchronizer.java b/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraTimesheetSynchronizer.java new file mode 100644 index 000000000..93d5a32b2 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraTimesheetSynchronizer.java @@ -0,0 +1,62 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers; + +import java.util.List; + +import org.libreplan.business.orders.entities.Order; +import org.libreplan.business.workreports.entities.WorkReportType; +import org.libreplan.importers.jira.Issue; + +/** + * Synchronize the timesheets of order tasks of an existing order with jira + * issues. + * + * A {@link WorkReportType} with the name "jira-connector" must also be exist + * and configured properly prior to start synchronization. + * + * Jira issues will be retrieved from Jira RESTful web service during + * synchronization of order elements + * + * @author Miciele Ghiorghis + */ +public interface IJiraTimesheetSynchronizer { + + /** + * Synchronize jira timesheet with the specified jira issues . + * + * + * Loop through all jira issues and check if timesheet is + * already exist for the specified issue item. If it is, update the + * timesheet with that issue item. If not create new one + * + * @param issues + * the jira issues + * @param order + * an existing order + */ + void syncJiraTimesheetWithJiraIssues(List issues, Order order); + + /** + * returns synchronization info, success or fail info + */ + JiraSyncInfo getJiraSyncInfo(); + +} From 23aef762a529ca487ebf0469dcca4cd4d359794b Mon Sep 17 00:00:00 2001 From: Miciele Ghiorghis Date: Wed, 24 Oct 2012 11:58:09 +0200 Subject: [PATCH 04/88] Jira-integration: synchronize order-elements with jira issues An implementation of the interface IJiraOrderElementSynchronizer. It synchronizes order-elements inclusive progress assignments and measurements of an existing order with jira issues. Loops through all jira issues and creates or updates order-lines/order-elements, hoursgroup, progress assignments and measurements --- .../JiraOrderElementSynchronizer.java | 410 ++++++++++++++++++ 1 file changed, 410 insertions(+) create mode 100755 libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizer.java diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizer.java b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizer.java new file mode 100755 index 000000000..31dad458a --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizer.java @@ -0,0 +1,410 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.joda.time.LocalDate; +import org.libreplan.business.advance.bootstrap.PredefinedAdvancedTypes; +import org.libreplan.business.advance.entities.AdvanceMeasurement; +import org.libreplan.business.advance.entities.AdvanceType; +import org.libreplan.business.advance.entities.DirectAdvanceAssignment; +import org.libreplan.business.advance.exceptions.DuplicateAdvanceAssignmentForOrderElementException; +import org.libreplan.business.advance.exceptions.DuplicateValueTrueReportGlobalAdvanceException; +import org.libreplan.business.common.daos.IConfigurationDAO; +import org.libreplan.business.orders.entities.HoursGroup; +import org.libreplan.business.orders.entities.Order; +import org.libreplan.business.orders.entities.OrderElement; +import org.libreplan.business.orders.entities.OrderLine; +import org.libreplan.importers.jira.Issue; +import org.libreplan.importers.jira.Status; +import org.libreplan.importers.jira.TimeTracking; +import org.libreplan.importers.jira.WorkLog; +import org.libreplan.importers.jira.WorkLogItem; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchronizer { + + private static final String CODE_PREFIX = "JIRA-"; + + @Autowired + private IConfigurationDAO configurationDAO; + + private JiraSyncInfo jiraSyncInfo; + + + @Override + @Transactional(readOnly = true) + public List getAllJiraLabels() { + String jiraLabelUrl = configurationDAO + .getConfigurationWithReadOnlyTransaction() + .getJiraConfiguration().getJiraLabelUrl(); + + return JiraRESTClient.getAllLables(jiraLabelUrl); + } + + @Override + @Transactional(readOnly = true) + public List getJiraIssues(String label) { + + String url = configurationDAO.getConfigurationWithReadOnlyTransaction() + .getJiraConfiguration() + .getJiraUrl(); + + String username = configurationDAO + .getConfigurationWithReadOnlyTransaction() + .getJiraConfiguration().getJiraUserId(); + + String password = configurationDAO + .getConfigurationWithReadOnlyTransaction() + .getJiraConfiguration().getJiraPassword(); + + String path = "rest/api/latest/search"; + String query = "labels=" + label; + + List issues = JiraRESTClient.getIssues(url, username, password, + path, query); + + return issues; + } + + @Override + @Transactional(readOnly = true) + public void syncOrderElementsWithJiraIssues(Order order, List issues) { + + jiraSyncInfo = new JiraSyncInfo(); + + for (Issue issue : issues) { + String code = CODE_PREFIX + order.getCode() + "-" + issue.getKey(); + String name = issue.getFields().getSummary(); + + syncOrderLine(order, code, name); + + + syncHoursGroup( + (OrderLine) order.getOrderElement(code), code, + getEstimatedHours(issue.getFields().getTimetracking())); + + syncPorgressMeasurement(order.getOrderElement(code), issue); + } + + } + + + /** + * Synchronize orderline + * + * check if orderLine is already exist for the given order If + * it is, update OrderLine.name with the specified parameter + * name (jira's name could be changed). If not, create new + * {@link OrderLine} and add to {@link Order} + * + * @param order + * an existing order + * @param code + * unique code for orderLine + * @param name + * name for the orderLine to be added or updated + */ + private void syncOrderLine(Order order, String code, + String name) { + OrderLine orderLine = (OrderLine) order.getOrderElement(code); + if (orderLine == null) { + orderLine = OrderLine.create(); + orderLine.setCode(code); + order.add(orderLine); + } + orderLine.setName(name); + } + + /** + * Synchronize hoursgroup + * + * Check if hoursGroup already exist for the given orderLine. + * If it is, update HoursGroup.workingHours with the specified + * parameter workingHours. If not, create new + * {@link HoursGroup} and add to the {@link OrderLine} + * + * @param orderLine + * an existing orderline + * @param code + * unique code for hoursgroup + * @param workingHours + * the working hours(jira's timetracking) + */ + private void syncHoursGroup(OrderLine orderLine, String code, + Integer workingHours) { + HoursGroup hoursGroup = orderLine.getHoursGroup(code); + if (hoursGroup == null) { + hoursGroup = HoursGroup.create(orderLine); + hoursGroup.setCode(code); + orderLine.addHoursGroup(hoursGroup); + } + + hoursGroup.setWorkingHours(workingHours); + } + + /** + * Synchronize progress assignment and measurement + * + * @param orderElement + * an exist orderElement + * @param issue + * jira's issue to synchronize with progress assignment and + * measurement + */ + private void syncPorgressMeasurement(OrderElement orderElement, Issue issue) { + + WorkLog workLog = issue.getFields().getWorklog(); + + if (workLog == null) { + jiraSyncInfo.addSyncFailedReason("No worklogs found for '" + + issue.getKey() + "' issue"); + return; + } + if (orderElement == null) { + jiraSyncInfo.addSyncFailedReason("Order-element for '" + + issue.getKey() + "' issue not found"); + return; + } + + List workLogItems = workLog.getWorklogs(); + if (workLogItems.isEmpty()) { + jiraSyncInfo.addSyncFailedReason("No worklog items found for '" + + issue.getKey() + "' issue"); + return; + } + + Integer estimatedHours = getEstimatedHours(issue.getFields() + .getTimetracking()); + + if (estimatedHours == 0) { + jiraSyncInfo.addSyncFailedReason("Estimated time for '" + + issue.getKey() + "' issue is 0"); + return; + } + + Integer loggedHours = getLoggedHours(issue.getFields() + .getTimetracking()); + + BigDecimal percentage; + + // if status is closed, the progress percentage is 100% regardless the + // loggedHours and estimatedHours + + if (isIssueClosed(issue.getFields().getStatus())) { + percentage = new BigDecimal(100); + } else { + percentage = calculatePercentage(estimatedHours, loggedHours); + } + + LocalDate latestWorkLogDate = LocalDate + .fromDateFields(getTheLatestWorkLoggedDate(workLogItems)); + + updateOrCreateProgressAssignmentAndMeasurement(orderElement, + percentage, latestWorkLogDate); + + } + + /** + * Get the estimated seconds from + * {@link TimeTracking#getRemainingEstimateSeconds()} or + * {@link TimeTracking#getOriginalEstimateSeconds()} and convert it to hours + * + * @param timeTracking + * where the estimated time to get from + * @return estimatedHours + */ + private Integer getEstimatedHours(TimeTracking timeTracking) { + if (timeTracking == null) { + return 0; + } + + Integer timeestimate = timeTracking.getRemainingEstimateSeconds(); + if (timeestimate != null && timeestimate > 0) { + return secondsToHours(timeestimate); + } + + Integer timeoriginalestimate = timeTracking + .getOriginalEstimateSeconds(); + if (timeoriginalestimate != null) { + return secondsToHours(timeoriginalestimate); + } + return 0; + } + + /** + * Get the time spent in seconds from + * {@link TimeTracking#getTimeSpentSeconds()} and convert it to hours + * + * @param timeTracking + * where the timespent to get from + * @return timespent in hous + */ + private Integer getLoggedHours(TimeTracking timeTracking) { + if (timeTracking == null) { + return 0; + } + + Integer timespentInSec = timeTracking.getTimeSpentSeconds(); + if (timespentInSec != null && timespentInSec > 0) { + return secondsToHours(timespentInSec); + } + + return 0; + } + + /** + * Convert seconds to hours + * + * @param seconds + * + * @return hours + */ + private Integer secondsToHours(Integer seconds) { + return Math.round(seconds / 3600); + } + + /** + * updates {@link DirectAdvanceAssignment} and {@link AdvanceMeasurement} if + * they already exist, otherwise create new one + * + * @param orderElement + * an existing orderElement + * @param percentage + * percentage for advanced measurement + * @param latestWorkLogDate + * date for advanced measurement + */ + private void updateOrCreateProgressAssignmentAndMeasurement( + OrderElement orderElement, BigDecimal percentage, + LocalDate latestWorkLogDate) { + + AdvanceType advanceType = PredefinedAdvancedTypes.PERCENTAGE.getType(); + + DirectAdvanceAssignment directAdvanceAssignment = orderElement + .getDirectAdvanceAssignmentByType(advanceType); + if (directAdvanceAssignment == null) { + directAdvanceAssignment = DirectAdvanceAssignment.create(false, + new BigDecimal(100)); + directAdvanceAssignment.setAdvanceType(advanceType); + } + directAdvanceAssignment.setOrderElement(orderElement); + + AdvanceMeasurement advanceMeasurement = directAdvanceAssignment + .getAdvanceMeasurementAtExactDate(latestWorkLogDate); + if (advanceMeasurement == null) { + advanceMeasurement = AdvanceMeasurement.create(); + } + + advanceMeasurement.setValue(percentage); + advanceMeasurement.setDate(latestWorkLogDate); + + directAdvanceAssignment.addAdvanceMeasurements(advanceMeasurement); + + advanceMeasurement.setAdvanceAssignment(directAdvanceAssignment); + + if (directAdvanceAssignment.isNewObject()) { + try { + directAdvanceAssignment.getOrderElement().addAdvanceAssignment( + directAdvanceAssignment); + } catch (DuplicateValueTrueReportGlobalAdvanceException e) { + jiraSyncInfo + .addSyncFailedReason("Duplicate value ReportGlobablAdvance for '" + + orderElement.getCode() + "'"); + } catch (DuplicateAdvanceAssignmentForOrderElementException e) { + jiraSyncInfo + .addSyncFailedReason("Duplicate value AdvanceAssignment for order element of '" + + orderElement.getCode() + "'"); + } + } + + } + + /** + * check if issue is closed + * + * @param status + * the status of the issue + * @return true if status is Closed + */ + private boolean isIssueClosed(Status status) { + if (status == null) { + return false; + } + return status.getName().equals("Closed"); + } + + /** + * Calculate percentage based on loggedHours and + * estimatedHours + * + * @param estimatedHours + * the estimated hours + * @param loggedHours + * time spent so far + * + * @return the calculated percentage + */ + private BigDecimal calculatePercentage(Integer estimatedHours, + Integer loggedHours) { + + if (estimatedHours == 0) { + return BigDecimal.ZERO; + } + + double percentage = (loggedHours * 100) + / (loggedHours + estimatedHours); + return new BigDecimal(percentage); + } + + /** + * Loop through all workLogItems and get the latest date + * + * @param workLogItems + * list of workLogItems + * @return latest date + */ + private Date getTheLatestWorkLoggedDate(List workLogItems) { + List dates = new ArrayList(); + for (WorkLogItem workLogItem : workLogItems) { + if (workLogItem.getStarted() != null) { + dates.add(workLogItem.getStarted()); + } + } + return Collections.max(dates); + } + + @Override + public JiraSyncInfo getJiraSyncInfo() { + return jiraSyncInfo; + } + +} From 13b649fb936b75ad91c0cef3b8fb641c455f6d51 Mon Sep 17 00:00:00 2001 From: Miciele Ghiorghis Date: Wed, 24 Oct 2012 12:05:22 +0200 Subject: [PATCH 05/88] Jira-integration: synchronize the timesheets with jira issues An implementation of the interface IJiraTimesheetSynchronizer. It synchronizes the timesheets of order-tasks of an existing order with jira issues. Loops through all jira issues and creates or updates timesheets(WorkReports) for an existing order. As a pre condition a WorkReportType with the name 'jira-connector' must be created and configured properly prior to synchronization of timesheets. --- .../importers/JiraTimesheetSynchronizer.java | 370 ++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 libreplan-webapp/src/main/java/org/libreplan/importers/JiraTimesheetSynchronizer.java diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraTimesheetSynchronizer.java b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraTimesheetSynchronizer.java new file mode 100644 index 000000000..0d3f6ceab --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraTimesheetSynchronizer.java @@ -0,0 +1,370 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.NonUniqueResultException; +import org.libreplan.business.common.IAdHocTransactionService; +import org.libreplan.business.common.exceptions.InstanceNotFoundException; +import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO; +import org.libreplan.business.costcategories.entities.TypeOfWorkHours; +import org.libreplan.business.orders.entities.Order; +import org.libreplan.business.orders.entities.OrderElement; +import org.libreplan.business.resources.daos.IWorkerDAO; +import org.libreplan.business.resources.entities.Resource; +import org.libreplan.business.resources.entities.Worker; +import org.libreplan.business.workingday.EffortDuration; +import org.libreplan.business.workreports.daos.IWorkReportDAO; +import org.libreplan.business.workreports.daos.IWorkReportLineDAO; +import org.libreplan.business.workreports.daos.IWorkReportTypeDAO; +import org.libreplan.business.workreports.entities.WorkReport; +import org.libreplan.business.workreports.entities.WorkReportLine; +import org.libreplan.business.workreports.entities.WorkReportType; +import org.libreplan.business.workreports.valueobjects.DescriptionField; +import org.libreplan.business.workreports.valueobjects.DescriptionValue; +import org.libreplan.importers.jira.Issue; +import org.libreplan.importers.jira.WorkLog; +import org.libreplan.importers.jira.WorkLogItem; +import org.libreplan.web.workreports.IWorkReportModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer { + + private static String CODE_PREFIX = "JIRA-"; + + private JiraSyncInfo jiraSyncInfo; + + private List workers; + + private WorkReportType workReportType; + + private TypeOfWorkHours typeOfWorkHours; + + @Autowired + private IWorkerDAO workerDAO; + + @Autowired + private IWorkReportTypeDAO workReportTypeDAO; + + @Autowired + private IWorkReportDAO workReportDAO; + + @Autowired + private IWorkReportLineDAO workReportLineDAO; + + @Autowired + private IWorkReportModel workReportModel; + + @Autowired + private ITypeOfWorkHoursDAO typeOfWorkHoursDAO; + + @Autowired + private IAdHocTransactionService adHocTransactionService; + + @Override + @Transactional + public void syncJiraTimesheetWithJiraIssues(List issues, Order order) { + startSync(issues, order); + + } + + /** + * Start synchronization of timesheets + * + * @param issues + * the jira issues + * @param order + * an existing order + */ + private void startSync(List issues, Order order) { + + jiraSyncInfo = new JiraSyncInfo(); + + workReportType = findWorkReportType("Jira-connector"); + if (workReportType == null) { + return; + } + + typeOfWorkHours = findTypeOfWorkHours("Default"); + if (typeOfWorkHours == null) { + return; + } + + workers = getWorkers(); + if (workers == null && workers.isEmpty()) { + jiraSyncInfo.addSyncFailedReason("No workers found"); + return; + } + + String code = order.getCode() + " " + order.getImportedLabel(); + + WorkReport workReport = updateOrCreateWorkReport(code); + + for (Issue issue : issues) { + WorkLog worklog = issue.getFields().getWorklog(); + if (worklog == null) { + jiraSyncInfo.addSyncFailedReason("No worklogs found for '" + + issue.getKey() + "'"); + } else { + List workLogItems = worklog.getWorklogs(); + if (workLogItems == null || workLogItems.isEmpty()) { + jiraSyncInfo + .addSyncFailedReason("No worklog items found for '" + + issue.getKey() + "' issue"); + } else { + + String codeOrderElement = CODE_PREFIX + order.getCode() + "-" + + issue.getKey(); + + OrderElement orderElement = order.getOrderElement(codeOrderElement); + + if (orderElement == null) { + jiraSyncInfo.addSyncFailedReason("Order element(" + + code + ") not found"); + } else { + updateOrCreateWorkReportLineAndAddToWorkReport(workReport, orderElement, + workLogItems); + } + } + } + } + + if (workReportModel.getWorkReport().getWorkReportLines().size() > 0) { + workReportModel.confirmSave(); + } + } + + /** + * Updates {@link WorkReport} if exist, if not creates new one + * + * @param code + * search criteria for workReport + * @return the workReport + */ + private WorkReport updateOrCreateWorkReport(String code) { + WorkReport workReport = findWorkReport(code); + if (workReport == null) { + workReportModel.initCreate(workReportType); + } else { + workReportModel.initEdit(workReport); + } + workReportModel.setCodeAutogenerated(false); + + workReport = workReportModel.getWorkReport(); + workReport.setCode(code); + return workReport; + } + + /** + * Updates {@link WorkReportLine} if exist. If not creates new one and adds + * to workReport + * + * @param workReport + * an existing or new created workReport + * @param orderElement + * the orderElement + * @param workLogItems + * jira's workLog items to be added to workReportLine + */ + private void updateOrCreateWorkReportLineAndAddToWorkReport(WorkReport workReport, + OrderElement orderElement, + List workLogItems) { + + for (WorkLogItem workLogItem : workLogItems) { + WorkReportLine workReportLine; + try { + workReportLine = workReport + .getWorkReportLineByCode(orderElement.getCode() + "-" + + workLogItem.getId()); + } catch (InstanceNotFoundException e) { + workReportLine = WorkReportLine.create(workReport); + } + + Resource resource = getWorker(workLogItem.getAuthor().getName()); + if (resource != null) { + + updateWorkReportLine(workReportLine, orderElement, + workLogItem, resource); + if (workReportLine.isNewObject()) { + workReport.addWorkReportLine(workReportLine); + } + } + } + + } + + /** + * Updates {@link WorkReportLine} with workLogItem + * + * @param workReportLine + * workReportLine to be updated + * @param orderElement + * the orderElement + * @param workLogItem + * workLogItem to update the workReportLine + * @param resource + * the resource + */ + private void updateWorkReportLine(WorkReportLine workReportLine, + OrderElement orderElement, WorkLogItem workLogItem, + Resource resource) { + + String code = orderElement.getCode() + "-" + workLogItem.getId(); + int timeSpent = workLogItem.getTimeSpentSeconds().intValue(); + + workReportLine.setCode(code); + workReportLine.setDate(workLogItem.getStarted()); + workReportLine.setResource(resource); + workReportLine.setOrderElement(orderElement); + workReportLine.setEffort(EffortDuration + .hours(EffortDuration.Granularity.HOURS + .convertFromSeconds(timeSpent))); + workReportLine.setTypeOfWorkHours(typeOfWorkHours); + + updateOrCreateDescriptionValuesAndAddToWorkReportLine(workReportLine, + workLogItem.getComment()); + } + + /** + * Updates {@link DescriptionValue} if exist. if not creates new one and + * adds to workReportLine + * + * @param workReportLine + * workReprtLinew where descriptionvalues to be added to + * @param comment + * the description value + */ + private void updateOrCreateDescriptionValuesAndAddToWorkReportLine(WorkReportLine workReportLine, + String comment) { + Set descriptionValues = new HashSet(); + for (DescriptionField descriptionField : workReportType.getLineFields()) { + DescriptionValue descriptionValue; + try { + descriptionValue = workReportLine + .getDescriptionValueByFieldName(descriptionField + .getFieldName()); + descriptionValue.setValue(comment.substring(0, + Math.min(comment.length(), 254))); + } catch (InstanceNotFoundException e) { + descriptionValue = DescriptionValue.create( + descriptionField.getFieldName(), comment); + } + descriptionValues.add(descriptionValue); + } + workReportLine.setDescriptionValues(descriptionValues); + } + + /** + * Searches for {@link WorkReportType} for the specified parameter + * name + * + * @param name + * unique name + * @return WorkReportType if found, null otherwise + */ + private WorkReportType findWorkReportType(String name) { + try { + return workReportTypeDAO.findUniqueByName(name); + } catch (NonUniqueResultException e) { + jiraSyncInfo + .addSyncFailedReason("Work report type 'Jira-connector' is not unique"); + } catch (InstanceNotFoundException e) { + jiraSyncInfo + .addSyncFailedReason("Work report type 'Jira-connector' not found"); + } + return null; + } + + /** + * Searches for {@link TypeOfWorkHours} for the specified parameter + * name + * + * @param name + * unique name + * @return TypeOfWorkHours if found, null otherwise + */ + private TypeOfWorkHours findTypeOfWorkHours(String name) { + try { + return typeOfWorkHoursDAO.findUniqueByName(name); + } catch (InstanceNotFoundException e) { + jiraSyncInfo.addSyncFailedReason("Type of workhours '" + name + + "' not found"); + } + return null; + } + + /** + * Searches for {@link WorkReport} for the specified parameter + * code + * + * @param code + * unique code + * @return workReportType if found, null otherwise + */ + private WorkReport findWorkReport(String code) { + try { + return workReportDAO.findByCodeAnotherTransaction(code); + } catch (InstanceNotFoundException e) { + } + return null; + } + + + /** + * Gets all libreplan workers + * + * @return list of workers + */ + private List getWorkers() { + return workerDAO.findAll(); + } + + /** + * Searches for {@link Worker} for the specified parameter nif + * + * @param nif + * unique id + * @return worker if found, null otherwise + */ + private Worker getWorker(String nif) { + for (Worker worker : workers) { + if (worker.getNif().equals(nif)) { + return worker; + } + } + jiraSyncInfo.addSyncFailedReason("Worker('" + nif + "') not found"); + return null; + } + + + @Override + public JiraSyncInfo getJiraSyncInfo() { + return jiraSyncInfo; + } +} From 887d0ae65011c20d35d918944b443cdee5487e31 Mon Sep 17 00:00:00 2001 From: Miciele Ghiorghis Date: Wed, 24 Oct 2012 12:13:02 +0200 Subject: [PATCH 06/88] Jira-integration: a jira client to interact with jira RESTful web service Interacts with Jira RESTful web service and supports only get method. Moreover it does basic authentication check using org.libreplan.ws.common.impl.Util.addAuthorizationHeader() --- .../libreplan/importers/JiraRESTClient.java | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100755 libreplan-webapp/src/main/java/org/libreplan/importers/JiraRESTClient.java diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraRESTClient.java b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraRESTClient.java new file mode 100755 index 000000000..b47410320 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraRESTClient.java @@ -0,0 +1,156 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.apache.cxf.jaxrs.client.WebClient; +import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider; +import org.codehaus.jackson.map.DeserializationConfig.Feature; +import org.libreplan.importers.jira.Issue; +import org.libreplan.importers.jira.SearchResult; +import org.libreplan.ws.cert.NaiveTrustProvider; +import org.libreplan.ws.common.impl.Util; + +/** + * Client to interact with Jira RESTful web service. + * + * @author Miciele Ghiorghis + */ +public class JiraRESTClient { + + private static final MediaType[] mediaTypes = new MediaType[] { + MediaType.valueOf(MediaType.APPLICATION_JSON), + MediaType.valueOf(MediaType.APPLICATION_XML) }; + + + /** + * Queries Jira for all labels + * + * @param url + * the url from where to fetch data + * @return List of labels + */ + public static List getAllLables(String url) { + WebClient client = WebClient.create(url).accept(mediaTypes); + String labels = client.get(String.class); + return Arrays.asList(labels.split("\\s*,\\s*")); + } + + /** + * Query Jira for all issues with the specified query parameter + * + * @param path + * the path segment + * @param query + * the query + * @return List of jira Issues + */ + public static List getIssues(String url, String username, + String password, String path, String query) { + + WebClient client = createClient(url); + + checkAutherization(client, username, password); + + client.back(true);// Go to baseURI + client.path(path); + if (!query.isEmpty()) { + client.query("jql", query); + client.query("maxResults", 1000); + } + SearchResult searchResult = client.get(SearchResult.class); + + return getIssuesDetails(client, searchResult.getIssues()); + } + + /** + * Creates WebClient + * + * @param url + * the url + * @return the created WebClient + */ + private static WebClient createClient(String url) { + + JacksonJaxbJsonProvider jacksonJaxbJsonProvider = new JacksonJaxbJsonProvider(); + jacksonJaxbJsonProvider.configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, + false); + List providers = new ArrayList(); + + providers.add(jacksonJaxbJsonProvider); + + return WebClient.create(url, providers).accept(mediaTypes); + + } + + /** + * Request jira for authorization check + * + * @param client + * jira client + * @param login + * login name + * @param password + * login password + */ + private static void checkAutherization(WebClient client, String login, + String password) { + NaiveTrustProvider.setAlwaysTrust(true); + + client.path("rest/auth/latest/session"); + + Util.addAuthorizationHeader(client, login, password); + Response response = client.get(); + + if (response.getStatus() != Status.OK.getStatusCode()) { + throw new RuntimeException("Authorization failed"); + } + } + + /** + * Iterate through issues and get issue details + * + * @param client + * the jira client + * @param issues + * jira issues + * + * @return List of jira issue details + */ + private static List getIssuesDetails(WebClient client, List issues) { + + client.back(true); + client.path("/rest/api/latest/issue/"); + + List issueDetails = new ArrayList(); + for (Issue issue : issues) { + issueDetails.add(client.path(issue.getId()).get(Issue.class)); + client.back(false); + } + return issueDetails; + } +} From 3174f886faeaae8ad120d492d52b0a51010208d3 Mon Sep 17 00:00:00 2001 From: Miciele Ghiorghis Date: Wed, 24 Oct 2012 12:26:07 +0200 Subject: [PATCH 07/88] Jira-integration: non persistent beans to map jira's REST response --- .../org/libreplan/importers/jira/Field.java | 61 ++++++++++++ .../org/libreplan/importers/jira/Issue.java | 75 ++++++++++++++ .../libreplan/importers/jira/LabelIssue.java | 43 ++++++++ .../importers/jira/SearchResult.java | 63 ++++++++++++ .../org/libreplan/importers/jira/Status.java | 51 ++++++++++ .../importers/jira/TimeTracking.java | 55 +++++++++++ .../org/libreplan/importers/jira/WorkLog.java | 63 ++++++++++++ .../importers/jira/WorkLogAuthor.java | 60 +++++++++++ .../libreplan/importers/jira/WorkLogItem.java | 99 +++++++++++++++++++ 9 files changed, 570 insertions(+) create mode 100644 libreplan-webapp/src/main/java/org/libreplan/importers/jira/Field.java create mode 100755 libreplan-webapp/src/main/java/org/libreplan/importers/jira/Issue.java create mode 100755 libreplan-webapp/src/main/java/org/libreplan/importers/jira/LabelIssue.java create mode 100644 libreplan-webapp/src/main/java/org/libreplan/importers/jira/SearchResult.java create mode 100644 libreplan-webapp/src/main/java/org/libreplan/importers/jira/Status.java create mode 100644 libreplan-webapp/src/main/java/org/libreplan/importers/jira/TimeTracking.java create mode 100644 libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLog.java create mode 100644 libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogAuthor.java create mode 100644 libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogItem.java diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/Field.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/Field.java new file mode 100644 index 000000000..a7925549f --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/Field.java @@ -0,0 +1,61 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers.jira; + +public class Field { + + String summary; + Status status; + TimeTracking timetracking; + WorkLog worklog; + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public TimeTracking getTimetracking() { + return timetracking; + } + + public void setTimetracking(TimeTracking timetracking) { + this.timetracking = timetracking; + } + + public WorkLog getWorklog() { + return worklog; + } + + public void setWorklog(WorkLog worklog) { + this.worklog = worklog; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/Issue.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/Issue.java new file mode 100755 index 000000000..868da708a --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/Issue.java @@ -0,0 +1,75 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers.jira; + +public class Issue { + + private String expand; + + private Integer id; + + private String key; + + private String self; + + private Field fields; + + public String getExpand() { + return expand; + } + + public void setExpand(String expand) { + this.expand = expand; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getSelf() { + return self; + } + + public void setSelf(String self) { + this.self = self; + } + + public Field getFields() { + return fields; + } + + public void setFields(Field fields) { + this.fields = fields; + } + + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/LabelIssue.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/LabelIssue.java new file mode 100755 index 000000000..d3fc1abdb --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/LabelIssue.java @@ -0,0 +1,43 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers.jira; + +public class LabelIssue { + + String key; + String self; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getSelf() { + return self; + } + + public void setSelf(String self) { + this.self = self; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/SearchResult.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/SearchResult.java new file mode 100644 index 000000000..43e55347b --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/SearchResult.java @@ -0,0 +1,63 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers.jira; + +import java.util.List; + +public class SearchResult { + + private Integer startAt; + private Integer maxResults; + private Integer total; + private List issues; + + public Integer getStartAt() { + return startAt; + } + + public void setStartAt(Integer startAt) { + this.startAt = startAt; + } + + public Integer getMaxResults() { + return maxResults; + } + + public void setMaxResults(Integer maxResults) { + this.maxResults = maxResults; + } + + public Integer getTotal() { + return total; + } + + public void setTotal(Integer total) { + this.total = total; + } + + public List getIssues() { + return issues; + } + + public void setIssues(List issues) { + this.issues = issues; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/Status.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/Status.java new file mode 100644 index 000000000..8cd5c7635 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/Status.java @@ -0,0 +1,51 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers.jira; + +public class Status { + Integer id; + String name; + String self; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSelf() { + return self; + } + + public void setSelf(String self) { + this.self = self; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/TimeTracking.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/TimeTracking.java new file mode 100644 index 000000000..8e1e42388 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/TimeTracking.java @@ -0,0 +1,55 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers.jira; + + +public class TimeTracking { + + private Integer originalEstimateSeconds; + private Integer remainingEstimateSeconds; + private Integer timeSpentSeconds; + + public Integer getOriginalEstimateSeconds() { + return originalEstimateSeconds; + } + + public void setOriginalEstimateSeconds(Integer originalEstimateSeconds) { + this.originalEstimateSeconds = originalEstimateSeconds; + } + + public Integer getRemainingEstimateSeconds() { + return remainingEstimateSeconds; + } + + public void setRemainingEstimateSeconds(Integer remainingEstimateSeconds) { + this.remainingEstimateSeconds = remainingEstimateSeconds; + } + + public Integer getTimeSpentSeconds() { + return timeSpentSeconds; + } + + public void setTimeSpentSeconds(Integer timeSpentSeconds) { + this.timeSpentSeconds = timeSpentSeconds; + } + + + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLog.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLog.java new file mode 100644 index 000000000..f8988d8bd --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLog.java @@ -0,0 +1,63 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers.jira; + +import java.util.List; + +public class WorkLog { + + private Integer startAt; + private Integer maxResults; + private Integer total; + private List worklogs; + + public Integer getStartAt() { + return startAt; + } + + public void setStartAt(Integer startAt) { + this.startAt = startAt; + } + + public Integer getMaxResults() { + return maxResults; + } + + public void setMaxResults(Integer maxResults) { + this.maxResults = maxResults; + } + + public Integer getTotal() { + return total; + } + + public void setTotal(Integer total) { + this.total = total; + } + + public List getWorklogs() { + return worklogs; + } + + public void setWorklogs(List worklogs) { + this.worklogs = worklogs; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogAuthor.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogAuthor.java new file mode 100644 index 000000000..6f0148084 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogAuthor.java @@ -0,0 +1,60 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers.jira; + +public class WorkLogAuthor { + private boolean active; + private String displayName; + private String name; + private String self; + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSelf() { + return self; + } + + public void setSelf(String self) { + this.self = self; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogItem.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogItem.java new file mode 100644 index 000000000..2c14a5571 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogItem.java @@ -0,0 +1,99 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers.jira; + +import java.util.Date; + +public class WorkLogItem { + + private WorkLogAuthor author; + private Integer id; + private String self; + private Date created; + private Date started; + private Date updated; + private Integer timeSpentSeconds; + private String comment; + + public WorkLogAuthor getAuthor() { + return author; + } + + public void setAuthor(WorkLogAuthor author) { + this.author = author; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSelf() { + return self; + } + + public void setSelf(String self) { + this.self = self; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getStarted() { + return started; + } + + public void setStarted(Date started) { + this.started = started; + } + + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } + + public Integer getTimeSpentSeconds() { + return timeSpentSeconds; + } + + public void setTimeSpentSeconds(Integer timeSpentSeconds) { + this.timeSpentSeconds = timeSpentSeconds; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + +} From f78eb2a8c4ef5117c1c2bd2e833edd9c24b0b675 Mon Sep 17 00:00:00 2001 From: Miciele Ghiorghis Date: Wed, 24 Oct 2012 12:52:11 +0200 Subject: [PATCH 08/88] Jira-integration: Keeps track the synchronization info. --- .../org/libreplan/importers/JiraSyncInfo.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 libreplan-webapp/src/main/java/org/libreplan/importers/JiraSyncInfo.java diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraSyncInfo.java b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraSyncInfo.java new file mode 100644 index 000000000..4ad510821 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraSyncInfo.java @@ -0,0 +1,64 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.importers; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Keeps track the synchronization info. + * + * @author Miciele Ghiorghis + */ +public class JiraSyncInfo { + + private Set syncFailedReasons = new HashSet(); + + /** + * Add the specified reason to syncFailedReasons list + * + * @param reason + * reason why synchronizition failed + */ + public void addSyncFailedReason(String reason) { + syncFailedReasons.add(reason); + } + + /** + * Is synchronization successful + * + * @return + */ + public boolean isSyncSuccessful() { + return syncFailedReasons.isEmpty(); + } + + /** + * returns reasons why synchronization is failed + */ + public List getSyncFailedReasons() { + List failedReasons = new ArrayList(); + failedReasons.addAll(syncFailedReasons); + return failedReasons; + } + +} From 59f9f0e62843f27b2065d0c1193d09726708addd Mon Sep 17 00:00:00 2001 From: Miciele Ghiorghis Date: Wed, 24 Oct 2012 12:54:15 +0200 Subject: [PATCH 09/88] Jira-integration: modal dialog to show the synchronization's success or failer info --- .../src/main/webapp/orders/_jiraSyncInfo.zul | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 libreplan-webapp/src/main/webapp/orders/_jiraSyncInfo.zul diff --git a/libreplan-webapp/src/main/webapp/orders/_jiraSyncInfo.zul b/libreplan-webapp/src/main/webapp/orders/_jiraSyncInfo.zul new file mode 100644 index 000000000..5767ed6bf --- /dev/null +++ b/libreplan-webapp/src/main/webapp/orders/_jiraSyncInfo.zul @@ -0,0 +1,50 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + +
+