diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/Configuration.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/Configuration.java index 2f05f98d4..caf2a1b02 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/common/entities/Configuration.java +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/Configuration.java @@ -104,6 +104,8 @@ public class Configuration extends BaseEntity { private Boolean generateCodeForExpenseSheets = true; + private JiraConfiguration jiraConfiguration; + /** * Currency code according to ISO-4217 (3 letters) */ @@ -503,4 +505,12 @@ public class Configuration extends BaseEntity { this.secondsPlanningWarning = secondsPlanningWarning; } + public JiraConfiguration getJiraConfiguration() { + return jiraConfiguration; + } + + public void setJiraConfiguration(JiraConfiguration jiraConfiguration) { + this.jiraConfiguration = jiraConfiguration; + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationBootstrap.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationBootstrap.java index 601391d6a..7f9f41c0b 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationBootstrap.java +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationBootstrap.java @@ -67,19 +67,28 @@ public class ConfigurationBootstrap implements IConfigurationBootstrap { public void loadRequiredData() { loadRequiredDataSequences(); - List list = configurationDAO.list(Configuration.class); - if (list.isEmpty()) { - Configuration configuration = Configuration.create(); + Configuration configuration = configurationDAO.getConfiguration(); + if (configuration == null) { + configuration = Configuration.create(); configuration.setDefaultCalendar(getDefaultCalendar()); configuration.setCompanyCode(COMPANY_CODE); - LDAPConfiguration ldapConfiguration = configuration - .getLdapConfiguration(); - if (null == configuration.getLdapConfiguration()) { - ldapConfiguration = LDAPConfiguration.create(); - } - configuration.setLdapConfiguration(ldapConfiguration); - configurationDAO.save(configuration); } + + LDAPConfiguration ldapConfiguration = configuration + .getLdapConfiguration(); + if (ldapConfiguration == null) { + ldapConfiguration = LDAPConfiguration.create(); + } + configuration.setLdapConfiguration(ldapConfiguration); + + JiraConfiguration jiraConfiguration = configuration + .getJiraConfiguration(); + if (jiraConfiguration == null) { + jiraConfiguration = JiraConfiguration.create(); + } + configuration.setJiraConfiguration(jiraConfiguration); + + configurationDAO.save(configuration); } public void loadRequiredDataSequences() { diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/PersonalTimesheetsTypeOfWorkHoursBootstrap.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationTypeOfWorkHoursBootstrap.java similarity index 87% rename from libreplan-business/src/main/java/org/libreplan/business/common/entities/PersonalTimesheetsTypeOfWorkHoursBootstrap.java rename to libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationTypeOfWorkHoursBootstrap.java index 4b338a165..90cfee44b 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/common/entities/PersonalTimesheetsTypeOfWorkHoursBootstrap.java +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationTypeOfWorkHoursBootstrap.java @@ -32,8 +32,9 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; /** - * Fills the attribute {@link Configuration#personalTimesheetsTypeOfWorkHours} - * with a default value.
+ * Fills the attributes {@link Configuration#personalTimesheetsTypeOfWorkHours} + * and {@link JiraConfiguration#jiraConnectorTypeOfWorkHours} with a default + * values.
* * If possible it uses the "Default" {@link TypeOfWorkHours}, but if it doesn't * exist, it uses the first {@link TypeOfWorkHours} found.
@@ -47,8 +48,8 @@ import org.springframework.transaction.annotation.Transactional; @Component @Scope("singleton") @BootstrapOrder(1) -public class PersonalTimesheetsTypeOfWorkHoursBootstrap implements - IPersonalTimesheetsTypeOfWorkHoursBootstrap { +public class ConfigurationTypeOfWorkHoursBootstrap implements + IConfigurationTypeOfWorkHoursBootstrap { @Autowired private IConfigurationDAO configurationDAO; @@ -74,6 +75,8 @@ public class PersonalTimesheetsTypeOfWorkHoursBootstrap implements } configuration.setPersonalTimesheetsTypeOfWorkHours(typeOfWorkHours); + configuration.getJiraConfiguration().setJiraConnectorTypeOfWorkHours( + typeOfWorkHours); configurationDAO.save(configuration); } diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/IPersonalTimesheetsTypeOfWorkHoursBootstrap.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/IConfigurationTypeOfWorkHoursBootstrap.java similarity index 87% rename from libreplan-business/src/main/java/org/libreplan/business/common/entities/IPersonalTimesheetsTypeOfWorkHoursBootstrap.java rename to libreplan-business/src/main/java/org/libreplan/business/common/entities/IConfigurationTypeOfWorkHoursBootstrap.java index 163ac00ee..3fccfb7b5 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/common/entities/IPersonalTimesheetsTypeOfWorkHoursBootstrap.java +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/IConfigurationTypeOfWorkHoursBootstrap.java @@ -22,11 +22,11 @@ package org.libreplan.business.common.entities; import org.libreplan.business.IDataBootstrap; /** - * Contract for {@link PersonalTimesheetsTypeOfWorkHoursBootstrap}. + * Contract for {@link ConfigurationTypeOfWorkHoursBootstrap}. * * @author Manuel Rego Casasnovas */ -public interface IPersonalTimesheetsTypeOfWorkHoursBootstrap extends +public interface IConfigurationTypeOfWorkHoursBootstrap extends IDataBootstrap { void loadRequiredData(); 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..f92b504ea --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/JiraConfiguration.java @@ -0,0 +1,114 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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; +import org.libreplan.business.costcategories.entities.TypeOfWorkHours; + +/** + * JiraConfiguration entity + * + * @author Miciele Ghiorghis + */ +public class JiraConfiguration extends BaseEntity { + + /** + * Code prefix for different entities integrated with JIRA. + */ + public static final String CODE_PREFIX = "JIRA-"; + + public static JiraConfiguration create() { + return create(new JiraConfiguration()); + } + + private boolean jiraActivated; + + private String jiraUrl; + + /** + * Stores one of the next 2 options: + *
    + *
  • A comma-separated list of labels
  • + *
  • A URL that will return a comma-separated list of labels
  • + *
+ */ + private String jiraLabels; + + private String jiraUserId; + + private String jiraPassword; + + private TypeOfWorkHours jiraConnectorTypeOfWorkHours; + + /** + * Constructor for Hibernate. Do not use! + */ + protected JiraConfiguration() { + } + + 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 getJiraLabels() { + return jiraLabels; + } + + public void setJiraLabels(String jiraLabels) { + this.jiraLabels = jiraLabels; + } + + 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; + } + + public TypeOfWorkHours getJiraConnectorTypeOfWorkHours() { + return jiraConnectorTypeOfWorkHours; + } + + public void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) { + jiraConnectorTypeOfWorkHours = typeOfWorkHours; + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/TypeOfWorkHoursDAO.java b/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/TypeOfWorkHoursDAO.java index f20dae7f5..59573128c 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/TypeOfWorkHoursDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/TypeOfWorkHoursDAO.java @@ -149,6 +149,7 @@ public class TypeOfWorkHoursDAO extends IntegrationEntityDAO checkHasHourCost(type); checkHasWorkReportLine(type); checkIsPersonalTimesheetsTypeOfWorkHours(type); + checkIsJiraConnectorTypeOfWorkHours(type); } private void checkHasWorkReportLine(TypeOfWorkHours type) { @@ -192,4 +193,15 @@ public class TypeOfWorkHoursDAO extends IntegrationEntityDAO return c.uniqueResult() != null; } + private void checkIsJiraConnectorTypeOfWorkHours(TypeOfWorkHours type) { + Configuration configuration = configurationDAO.getConfiguration(); + if (configuration.getJiraConfiguration() + .getJiraConnectorTypeOfWorkHours().getId().equals(type.getId())) { + throw ValidationException + .invalidValue( + "Cannot delete the type of work hours. It is configured as type of work hours for JIRA connector.", + type); + } + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/costcategories/entities/TypeOfWorkHours.java b/libreplan-business/src/main/java/org/libreplan/business/costcategories/entities/TypeOfWorkHours.java index 1f8c5bbb5..f26d72b22 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/costcategories/entities/TypeOfWorkHours.java +++ b/libreplan-business/src/main/java/org/libreplan/business/costcategories/entities/TypeOfWorkHours.java @@ -165,4 +165,18 @@ public class TypeOfWorkHours extends IntegrationEntity implements IHumanIdentifi return name; } + @AssertTrue(message = "type of work hours for JIRA connector cannot be disabled") + public boolean checkJiraConnectorTypeOfWorkHoursNotDisabled() { + if (!isNewObject() && !getEnabled()) { + TypeOfWorkHours typeOfWorkHours = Registry.getConfigurationDAO() + .getConfiguration().getJiraConfiguration() + .getJiraConnectorTypeOfWorkHours(); + if (typeOfWorkHours.getId().equals(getId())) { + return false; + } + } + + return true; + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/Order.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/Order.java index 11de57b17..b49d160c8 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/Order.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/Order.java @@ -120,6 +120,8 @@ public class Order extends OrderLineGroup implements Comparable { private Set customerCommunications = new HashSet(); + private String importedLabel; + @Valid private SortedSet deliveringDates = new TreeSet( new DeliverDateComparator()); @@ -691,4 +693,12 @@ public class Order extends OrderLineGroup implements Comparable { return true; } + public String getImportedLabel() { + return importedLabel; + } + + public void setImportedLabel(String importedLabel) { + this.importedLabel = importedLabel; + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java index 2e3b760e6..73fabd95e 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java @@ -52,6 +52,7 @@ import org.libreplan.business.advance.exceptions.DuplicateValueTrueReportGlobalA import org.libreplan.business.common.IntegrationEntity; import org.libreplan.business.common.Registry; import org.libreplan.business.common.daos.IIntegrationEntityDAO; +import org.libreplan.business.common.entities.JiraConfiguration; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.labels.entities.Label; import org.libreplan.business.materials.entities.MaterialAssignment; @@ -1662,6 +1663,14 @@ public abstract class OrderElement extends IntegrationEntity implements return effort.toFormattedString(); } + public boolean isJiraIssue() { + String code = getCode(); + if (code == null) { + return false; + } + return code.startsWith(JiraConfiguration.CODE_PREFIX); + } + public boolean isConvertedToContainer() { return false; } diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderLineGroup.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderLineGroup.java index 5d29b8146..9c9166d8c 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderLineGroup.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderLineGroup.java @@ -163,6 +163,11 @@ public class OrderLineGroup extends OrderElement implements return getThis().isUpdatedFromTimesheets(); } + @Override + public boolean isJiraIssue() { + return getThis().isJiraIssue(); + } + } public static OrderLineGroup create() { diff --git a/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderElementTemplate.java b/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderElementTemplate.java index 9d05919f3..7b5f9ec78 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderElementTemplate.java +++ b/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderElementTemplate.java @@ -625,4 +625,9 @@ public abstract class OrderElementTemplate extends BaseEntity implements return false; } + @Override + public boolean isJiraIssue() { + return false; + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderLineGroupTemplate.java b/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderLineGroupTemplate.java index f572dfa88..69ac9c75c 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderLineGroupTemplate.java +++ b/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderLineGroupTemplate.java @@ -105,6 +105,11 @@ public class OrderLineGroupTemplate extends OrderElementTemplate implements return false; } + @Override + public boolean isJiraIssue() { + return false; + } + } public static OrderLineGroupTemplate createNew() { diff --git a/libreplan-business/src/main/java/org/libreplan/business/trees/ITreeNode.java b/libreplan-business/src/main/java/org/libreplan/business/trees/ITreeNode.java index bd6facd23..ab61034c4 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/trees/ITreeNode.java +++ b/libreplan-business/src/main/java/org/libreplan/business/trees/ITreeNode.java @@ -62,4 +62,6 @@ public interface ITreeNode> { boolean isUpdatedFromTimesheets(); + boolean isJiraIssue(); + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/PredefinedWorkReportTypes.java b/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/PredefinedWorkReportTypes.java index f6d243cab..16c78b57a 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/PredefinedWorkReportTypes.java +++ b/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/PredefinedWorkReportTypes.java @@ -19,6 +19,8 @@ */ package org.libreplan.business.workreports.entities; +import org.libreplan.business.workreports.valueobjects.DescriptionField; + /** * Defines the default {@link WorkReportType WorkReportTypes}. * @@ -27,7 +29,9 @@ package org.libreplan.business.workreports.entities; */ public enum PredefinedWorkReportTypes { DEFAULT("Default", false, false, false), - PERSONAL_TIMESHEETS("Personal timesheets", false, true, false); + PERSONAL_TIMESHEETS("Personal timesheets", false, true, false), + JIRA_TIMESHEETS("JIRA timesheets", false, false, false, + DescriptionField.create("Comment", 255)); private WorkReportType workReportType; @@ -41,6 +45,15 @@ public enum PredefinedWorkReportTypes { .setOrderElementIsSharedInLines(orderElementIsSharedInLines); } + private PredefinedWorkReportTypes(String name, boolean dateIsSharedByLines, + boolean resourceIsSharedInLines, + boolean orderElementIsSharedInLines, + DescriptionField lineDescriptionField) { + this(name, dateIsSharedByLines, resourceIsSharedInLines, + orderElementIsSharedInLines); + workReportType.addDescriptionFieldToEndLine(lineDescriptionField); + } + public WorkReportType getWorkReportType() { return workReportType; } diff --git a/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportType.java b/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportType.java index f644dd7b5..38c7741f9 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportType.java +++ b/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportType.java @@ -532,4 +532,11 @@ public class WorkReportType extends IntegrationEntity implements IHumanIdentifia .getName()); } + public boolean isJiraTimesheetsType() { + if (StringUtils.isBlank(name)) { + return false; + } + return name.equals(PredefinedWorkReportTypes.JIRA_TIMESHEETS.getName()); + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportTypeBootstrap.java b/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportTypeBootstrap.java index a6bee16dd..d5bbfc4ab 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportTypeBootstrap.java +++ b/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportTypeBootstrap.java @@ -60,6 +60,7 @@ public class WorkReportTypeBootstrap implements IWorkReportTypeBootstrap { } } else { createPersonalTimesheetsWorkReportTypeIfNeeded(); + createJiraTimesheetsWorkReportTypeIfNeeded(); } } @@ -86,4 +87,16 @@ public class WorkReportTypeBootstrap implements IWorkReportTypeBootstrap { } } + private void createJiraTimesheetsWorkReportTypeIfNeeded() { + try { + workReportTypeDAO + .findUniqueByName(PredefinedWorkReportTypes.JIRA_TIMESHEETS + .getName()); + } catch (NonUniqueResultException e) { + throw new RuntimeException(e); + } catch (InstanceNotFoundException e) { + createAndSaveWorkReportType(PredefinedWorkReportTypes.JIRA_TIMESHEETS); + } + } + } diff --git a/libreplan-business/src/main/resources/db.changelog-1.3.xml b/libreplan-business/src/main/resources/db.changelog-1.3.xml index 06b0867f6..e75776065 100644 --- a/libreplan-business/src/main/resources/db.changelog-1.3.xml +++ b/libreplan-business/src/main/resources/db.changelog-1.3.xml @@ -208,6 +208,108 @@ columnDataType="INTEGER" /> + + + + Add new column jira_activated with default value FALSE to configuration table + + + + + + + + + + + Add new column jira_url in table configuration + + + + + + + + + Add new column jira_label_url in table configuration + + + + + + + + + Add new column jira_user_id in table configuration + + + + + + + + + Add new column jira_user_id in table configuration + + + + + + + + + Add new column imported_label in table order_table + + + + + + + + + Add new column jira_connector_type_of_work_hours to configuration + table. + + + + + + + + + + Rename column jira_label_url to jira_labels in configuration table + + + + + + Change column jira_labels in configuration to TEXT + + + + + Change column jira_labels in configuration to TEXT in MySQL + ALTER TABLE configuration MODIFY jira_labels TEXT + + Add column to store project filtering interval 'range since' parameter @@ -272,5 +374,4 @@ onDelete="SET NULL"/> - diff --git a/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Configuration.hbm.xml b/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Configuration.hbm.xml index afc2a504c..e06c71260 100644 --- a/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Configuration.hbm.xml +++ b/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Configuration.hbm.xml @@ -121,6 +121,17 @@ + + + + + + + + + + diff --git a/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml b/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml index cf8c70b5a..6905d51a0 100644 --- a/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml +++ b/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml @@ -126,6 +126,9 @@ + + + diff --git a/libreplan-webapp/pom.xml b/libreplan-webapp/pom.xml index 33e6c5d32..de70c4089 100644 --- a/libreplan-webapp/pom.xml +++ b/libreplan-webapp/pom.xml @@ -440,6 +440,14 @@ org.apache.cxf cxf-rt-frontend-jaxrs + + + org.codehaus.jackson + jackson-jaxrs + + + org.codehaus.jackson + jackson-xc 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..4f643118e --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraOrderElementSynchronizer.java @@ -0,0 +1,90 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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.IssueDTO; + +/** + * 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. + * + * FIXME: 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. More info: + * https://jira.atlassian.com/browse/JRA-29409 + * + * @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(List issues, Order order); + + /** + * returns synchronization info, success or fail info + */ + JiraSyncInfo getJiraSyncInfo(); + +} 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..9c263923c --- /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) 2013 St. Antoniusziekenhuis + * + * 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.IssueDTO; + +/** + * 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(); + +} 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..b723ae2c6 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizer.java @@ -0,0 +1,400 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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.math.RoundingMode; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +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.common.entities.JiraConfiguration; +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.business.workingday.EffortDuration; +import org.libreplan.importers.jira.IssueDTO; +import org.libreplan.importers.jira.StatusDTO; +import org.libreplan.importers.jira.TimeTrackingDTO; +import org.libreplan.importers.jira.WorkLogDTO; +import org.libreplan.importers.jira.WorkLogItemDTO; +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; + +/** + * Implementation of Synchronize order elements with jira issues + * + * @author Miciele Ghiorghis + */ +@Component +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchronizer { + + @Autowired + private IConfigurationDAO configurationDAO; + + private JiraSyncInfo jiraSyncInfo; + + + @Override + @Transactional(readOnly = true) + public List getAllJiraLabels() { + String jiraLabels = configurationDAO.getConfiguration() + .getJiraConfiguration().getJiraLabels(); + + String labels; + try { + new URL(jiraLabels); + labels = JiraRESTClient.getAllLables(jiraLabels); + } catch (MalformedURLException e) { + labels = jiraLabels; + } + return Arrays.asList(StringUtils.split(labels, ",")); + } + + @Override + @Transactional(readOnly = true) + public List getJiraIssues(String label) { + JiraConfiguration jiraConfiguration = configurationDAO + .getConfiguration().getJiraConfiguration(); + + String url = jiraConfiguration.getJiraUrl(); + String username = jiraConfiguration.getJiraUserId(); + String password = jiraConfiguration.getJiraPassword(); + + String path = JiraRESTClient.PATH_SEARCH; + String query = "labels=" + label; + + List issues = JiraRESTClient.getIssues(url, username, password, + path, query); + + return issues; + } + + @Override + @Transactional(readOnly = true) + public void syncOrderElementsWithJiraIssues(List issues, Order order) { + + jiraSyncInfo = new JiraSyncInfo(); + + for (IssueDTO issue : issues) { + String code = JiraConfiguration.CODE_PREFIX + order.getCode() + "-" + + issue.getKey(); + String name = issue.getFields().getSummary(); + + OrderLine orderLine = syncOrderLine(order, code, name); + if (orderLine == null) { + jiraSyncInfo.addSyncFailedReason("Order-element for '" + + issue.getKey() + "' issue not found"); + continue; + } + + EffortDuration loggedHours = getLoggedHours(issue.getFields() + .getTimetracking()); + EffortDuration estimatedHours = getEstimatedHours(issue.getFields() + .getTimetracking(), loggedHours); + + if (estimatedHours.isZero()) { + jiraSyncInfo.addSyncFailedReason("Estimated time for '" + + issue.getKey() + "' issue is 0"); + continue; + } + + syncHoursGroup(orderLine, code, estimatedHours.getHours()); + + syncProgressMeasurement(orderLine, issue, estimatedHours, + loggedHours); + } + + } + + + /** + * 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 OrderLine syncOrderLine(Order order, String code, + String name) { + OrderElement orderElement = order.getOrderElement(code); + if (orderElement != null && !orderElement.isLeaf()) { + return null; + } + + OrderLine orderLine = (OrderLine) orderElement; + if (orderLine == null) { + orderLine = OrderLine.create(); + orderLine.setCode(code); + order.add(orderLine); + } + orderLine.setName(name); + return orderLine; + } + + /** + * 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 orderLine + * an exist orderLine + * @param issue + * jira's issue to synchronize with progress assignment and + * measurement + */ + private void syncProgressMeasurement(OrderLine orderLine, IssueDTO issue, + EffortDuration estimatedHours, EffortDuration loggedHours) { + + WorkLogDTO workLog = issue.getFields().getWorklog(); + + if (workLog == null) { + jiraSyncInfo.addSyncFailedReason("No worklogs found for '" + + issue.getKey() + "' issue"); + return; + } + + List workLogItems = workLog.getWorklogs(); + if (workLogItems.isEmpty()) { + jiraSyncInfo.addSyncFailedReason("No worklog items found for '" + + issue.getKey() + "' issue"); + return; + } + + 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 = loggedHours.dividedByAndResultAsBigDecimal( + estimatedHours).multiply(new BigDecimal(100)); + } + + LocalDate latestWorkLogDate = LocalDate + .fromDateFields(getTheLatestWorkLoggedDate(workLogItems)); + + updateOrCreateProgressAssignmentAndMeasurement(orderLine, + percentage, latestWorkLogDate); + + } + + /** + * Get the estimated seconds from + * {@link TimeTrackingDTO#getRemainingEstimateSeconds()} plus logged hours or + * {@link TimeTrackingDTO#getOriginalEstimateSeconds()} and convert it to + * {@link EffortDuration} + * + * @param timeTracking + * where the estimated time to get from + * @param loggedHours + * hours already logged + * @return estimatedHours + */ + private EffortDuration getEstimatedHours(TimeTrackingDTO timeTracking, + EffortDuration loggedHours) { + if (timeTracking == null) { + return EffortDuration.zero(); + } + + Integer timeestimate = timeTracking.getRemainingEstimateSeconds(); + if (timeestimate != null && timeestimate > 0) { + return EffortDuration.seconds(timeestimate).plus(loggedHours); + } + + Integer timeoriginalestimate = timeTracking + .getOriginalEstimateSeconds(); + if (timeoriginalestimate != null) { + return EffortDuration.seconds(timeoriginalestimate); + } + return EffortDuration.zero(); + } + + /** + * Get the time spent in seconds from + * {@link TimeTrackingDTO#getTimeSpentSeconds()} and convert it to + * {@link EffortDuration} + * + * @param timeTracking + * where the timespent to get from + * @return timespent in hous + */ + private EffortDuration getLoggedHours(TimeTrackingDTO timeTracking) { + if (timeTracking == null) { + return EffortDuration.zero(); + } + + Integer timespentInSec = timeTracking.getTimeSpentSeconds(); + if (timespentInSec != null && timespentInSec > 0) { + return EffortDuration.seconds(timespentInSec); + } + + return EffortDuration.zero(); + } + + /** + * 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).setScale(2)); + directAdvanceAssignment.setAdvanceType(advanceType); + try { + orderElement.addAdvanceAssignment(directAdvanceAssignment); + } catch (DuplicateValueTrueReportGlobalAdvanceException e) { + // This couldn't happen as it has just created the + // directAdvanceAssignment with false as reportGlobalAdvance + throw new RuntimeException(e); + } catch (DuplicateAdvanceAssignmentForOrderElementException e) { + // This could happen if a parent or child of the current + // OrderElement has an advance of type PERCENTAGE + jiraSyncInfo + .addSyncFailedReason("Duplicate value AdvanceAssignment for order element of '" + + orderElement.getCode() + "'"); + return; + } + } + + AdvanceMeasurement advanceMeasurement = directAdvanceAssignment + .getAdvanceMeasurementAtExactDate(latestWorkLogDate); + if (advanceMeasurement == null) { + advanceMeasurement = AdvanceMeasurement.create(); + advanceMeasurement.setDate(latestWorkLogDate); + directAdvanceAssignment.addAdvanceMeasurements(advanceMeasurement); + } + + advanceMeasurement.setValue(percentage + .setScale(2, RoundingMode.HALF_UP)); + + DirectAdvanceAssignment spreadAdvanceAssignment = orderElement + .getReportGlobalAdvanceAssignment(); + if (spreadAdvanceAssignment != null) { + spreadAdvanceAssignment.setReportGlobalAdvance(false); + } + + directAdvanceAssignment.setReportGlobalAdvance(true); + } + + /** + * check if issue is closed + * + * @param status + * the status of the issue + * @return true if status is Closed + */ + private boolean isIssueClosed(StatusDTO status) { + if (status == null) { + return false; + } + return status.getName().equals("Closed"); + } + + /** + * 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 (WorkLogItemDTO workLogItem : workLogItems) { + if (workLogItem.getStarted() != null) { + dates.add(workLogItem.getStarted()); + } + } + return Collections.max(dates); + } + + @Override + public JiraSyncInfo getJiraSyncInfo() { + return jiraSyncInfo; + } + +} 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..bcea19b87 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraRESTClient.java @@ -0,0 +1,167 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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.Collections; +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.commons.lang.StringUtils; +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.IssueDTO; +import org.libreplan.importers.jira.SearchResultDTO; +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 { + + + /** + * Path for search operation in JIRA REST API + */ + public static final String PATH_SEARCH = "rest/api/latest/search"; + + /** + * Path for authenticate session in JIRA REST API + */ + public static final String PATH_AUTH_SESSION = "rest/auth/latest/session"; + + /** + * Path for issue operations in JIRA REST API + */ + public static final String PATH_ISSUE = "/rest/api/latest/issue/"; + + /** + * Fields to include in the response of rest/api/latest/search. + */ + private static final String FIELDS_TO_INCLUDE_IN_RESPONSE = "summary,status,timetracking,worklog"; + + /** + * Max number of issues to return(default is 50) + */ + private static final long MAX_RESULTS = 1000; + + 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 String with the list of labels sepparated by comma + */ + public static String getAllLables(String url) { + WebClient client = WebClient.create(url).accept(mediaTypes); + return client.get(String.class); + } + + /** + * Query Jira for all issues with the specified query parameter + * + * @param url + * the url(end point) + * @param username + * the user name + * @param password + * the password + * @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", MAX_RESULTS); + client.query("fields", + StringUtils.deleteWhitespace(FIELDS_TO_INCLUDE_IN_RESPONSE)); + + SearchResultDTO searchResult = client.get(SearchResultDTO.class); + + return 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); + + return WebClient.create(url, + Collections.singletonList(jacksonJaxbJsonProvider)).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(PATH_AUTH_SESSION); + + Util.addAuthorizationHeader(client, login, password); + Response response = client.get(); + + if (response.getStatus() != Status.OK.getStatusCode()) { + throw new RuntimeException("Authorization failed"); + } + } + +} 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..fecf80d95 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraSyncInfo.java @@ -0,0 +1,61 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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.Collections; +import java.util.List; + +/** + * Keeps track the synchronization info. + * + * @author Miciele Ghiorghis + */ +public class JiraSyncInfo { + + private List syncFailedReasons = new ArrayList(); + + /** + * 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() { + return Collections.unmodifiableList(syncFailedReasons); + } + +} 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..872ddb88a --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraTimesheetSynchronizer.java @@ -0,0 +1,348 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 java.util.Set; + +import org.hibernate.NonUniqueResultException; +import org.libreplan.business.common.IAdHocTransactionService; +import org.libreplan.business.common.daos.IConfigurationDAO; +import org.libreplan.business.common.entities.JiraConfiguration; +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.PredefinedWorkReportTypes; +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.IssueDTO; +import org.libreplan.importers.jira.WorkLogDTO; +import org.libreplan.importers.jira.WorkLogItemDTO; +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; + +/** + * Implementation of Synchronize timesheets with jira issues + * + * @author Miciele Ghiorghis + */ +@Component +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer { + + 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 IConfigurationDAO configurationDAO; + + @Autowired + private IAdHocTransactionService adHocTransactionService; + + @Override + @Transactional + public void syncJiraTimesheetWithJiraIssues(List issues, Order order) { + jiraSyncInfo = new JiraSyncInfo(); + + workReportType = getJiraTimesheetsWorkReportType(); + typeOfWorkHours = getTypeOfWorkHours(); + + workers = getWorkers(); + if (workers == null && workers.isEmpty()) { + jiraSyncInfo.addSyncFailedReason("No workers found"); + return; + } + + String code = order.getCode() + "-" + order.getImportedLabel(); + + WorkReport workReport = updateOrCreateWorkReport(code); + + for (IssueDTO issue : issues) { + WorkLogDTO 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 = JiraConfiguration.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); + } + } + } + } + + saveWorkReportIfNotEmpty(); + } + + private void saveWorkReportIfNotEmpty() { + 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); + workReport = workReportModel.getWorkReport(); + workReport.setCode(code); + } else { + workReportModel.initEdit(workReport); + } + workReportModel.setCodeAutogenerated(false); + + 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 (WorkLogItemDTO workLogItem : workLogItems) { + Resource resource = getWorker(workLogItem.getAuthor().getName()); + if (resource == null) { + continue; + } + + WorkReportLine workReportLine; + String code = orderElement.getCode() + "-" + workLogItem.getId(); + + try { + workReportLine = workReport.getWorkReportLineByCode(code); + } catch (InstanceNotFoundException e) { + workReportLine = WorkReportLine.create(workReport); + workReport.addWorkReportLine(workReportLine); + workReportLine.setCode(code); + } + + updateWorkReportLine(workReportLine, orderElement, workLogItem, + resource); + } + + } + + /** + * 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, WorkLogItemDTO workLogItem, + Resource resource) { + + int timeSpent = workLogItem.getTimeSpentSeconds().intValue(); + + workReportLine.setDate(workLogItem.getStarted()); + workReportLine.setResource(resource); + workReportLine.setOrderElement(orderElement); + workReportLine.setEffort(EffortDuration.seconds(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) { + DescriptionField descriptionField = workReportType.getLineFields().iterator().next(); + + Integer maxLenght = descriptionField.getLength(); + if (comment.length() > maxLenght) { + comment = comment.substring(0, maxLenght - 1); + } + + Set descriptionValues = workReportLine + .getDescriptionValues(); + if (descriptionValues.isEmpty()) { + descriptionValues.add(DescriptionValue.create( + descriptionField.getFieldName(), comment)); + } else { + descriptionValues.iterator().next().setValue(comment); + } + + workReportLine.setDescriptionValues(descriptionValues); + } + + /** + * Returns {@link WorkReportType} for JIRA connector + * + * @return WorkReportType for JIRA connector + */ + private WorkReportType getJiraTimesheetsWorkReportType() { + WorkReportType workReportType; + try { + workReportType = workReportTypeDAO + .findUniqueByName(PredefinedWorkReportTypes.JIRA_TIMESHEETS + .getName()); + } catch (NonUniqueResultException e) { + throw new RuntimeException(e); + } catch (InstanceNotFoundException e) { + throw new RuntimeException(e); + } + return workReportType; + } + + /** + * Returns {@link TypeOfWorkHours} configured for JIRA connector + * + * @return TypeOfWorkHours for JIRA connector + */ + private TypeOfWorkHours getTypeOfWorkHours() { + return configurationDAO.getConfiguration().getJiraConfiguration() + .getJiraConnectorTypeOfWorkHours(); + } + + /** + * 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.findByCode(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; + } +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/FieldDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/FieldDTO.java new file mode 100644 index 000000000..3f0f25e7c --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/FieldDTO.java @@ -0,0 +1,67 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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; + + +/** + * DTO representing a jira-issue Field + * + * @author Miciele Ghiorghis + */ +public class FieldDTO { + + private String summary; + private StatusDTO status; + private TimeTrackingDTO timetracking; + private WorkLogDTO worklog; + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public TimeTrackingDTO getTimetracking() { + return timetracking; + } + + public void setTimetracking(TimeTrackingDTO timetracking) { + this.timetracking = timetracking; + } + + public WorkLogDTO getWorklog() { + return worklog; + } + + public void setWorklog(WorkLogDTO worklog) { + this.worklog = worklog; + } + + public StatusDTO getStatus() { + return status; + } + + public void setStatus(StatusDTO status) { + this.status = status; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/IssueDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/IssueDTO.java new file mode 100755 index 000000000..9ba784a65 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/IssueDTO.java @@ -0,0 +1,81 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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; + + +/** + * DTO representing a jira-issue + * + * @author Miciele Ghiorghis + */ +public class IssueDTO { + + private String expand; + + private Integer id; + + private String key; + + private String self; + + private FieldDTO 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 FieldDTO getFields() { + return fields; + } + + public void setFields(FieldDTO fields) { + this.fields = fields; + } + + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/jira/LabelIssueDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/LabelIssueDTO.java new file mode 100755 index 000000000..a784f1db2 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/LabelIssueDTO.java @@ -0,0 +1,48 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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; + +/** + * DTO representing a jira-issue LabelIssue + * + * @author Miciele Ghiorghis + */ +public class LabelIssueDTO { + + 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/SearchResultDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/SearchResultDTO.java new file mode 100644 index 000000000..c41b7164f --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/SearchResultDTO.java @@ -0,0 +1,71 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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; + +import org.libreplan.business.orders.entities.OrderElement; + +/** + * DTO representing jira search result(response), it will be used for + * synchronization with {@link OrderElement} + * + * @author Miciele Ghiorghis + */ +public class SearchResultDTO { + + 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/StatusDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/StatusDTO.java new file mode 100644 index 000000000..38addb9ec --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/StatusDTO.java @@ -0,0 +1,57 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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; + + +/** + * DTO representing a jira-issue Status + * + * @author Miciele Ghiorghis + */ +public class StatusDTO { + 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/TimeTrackingDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/TimeTrackingDTO.java new file mode 100644 index 000000000..e5a1297ab --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/TimeTrackingDTO.java @@ -0,0 +1,60 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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; + + +/** + * DTO representing a jira-issue TimeTracking + * + * @author Miciele Ghiorghis + */ +public class TimeTrackingDTO { + + 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/WorkLogAuthorDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogAuthorDTO.java new file mode 100644 index 000000000..dcb957387 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogAuthorDTO.java @@ -0,0 +1,65 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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; + +/** + * DTO representing a jira-issue WorkLogAuthor + * + * @author Miciele Ghiorghis + */ +public class WorkLogAuthorDTO { + 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/WorkLogDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogDTO.java new file mode 100644 index 000000000..eafe48f4a --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogDTO.java @@ -0,0 +1,68 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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; + +/** + * DTO representing a jira-issue WorkLog + * + * @author Miciele Ghiorghis + */ +public class WorkLogDTO { + + 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/WorkLogItemDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogItemDTO.java new file mode 100644 index 000000000..49e70a6ae --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/jira/WorkLogItemDTO.java @@ -0,0 +1,104 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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; + +/** + * DTO representing a jira-issue WorkLogItem + * + * @author Miciele Ghiorghis + */ +public class WorkLogItemDTO { + + private WorkLogAuthorDTO author; + private Integer id; + private String self; + private Date created; + private Date started; + private Date updated; + private Integer timeSpentSeconds; + private String comment; + + public WorkLogAuthorDTO getAuthor() { + return author; + } + + public void setAuthor(WorkLogAuthorDTO 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; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationController.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationController.java index b98983ebc..09b2fa8e4 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationController.java @@ -32,19 +32,26 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.cxf.jaxrs.client.WebClient; import org.libreplan.business.calendars.entities.BaseCalendar; import org.libreplan.business.common.entities.Configuration; import org.libreplan.business.common.entities.EntityNameEnum; import org.libreplan.business.common.entities.EntitySequence; +import org.libreplan.business.common.entities.JiraConfiguration; import org.libreplan.business.common.entities.LDAPConfiguration; import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; import org.libreplan.business.common.entities.ProgressType; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.costcategories.entities.TypeOfWorkHours; import org.libreplan.business.users.entities.UserRole; +import org.libreplan.importers.JiraRESTClient; import org.libreplan.web.common.components.bandboxsearch.BandboxSearch; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.LdapTemplate; @@ -259,6 +266,43 @@ public class ConfigurationController extends GenericForwardComposer { } } + /** + * tests jira connection + */ + public void testJiraConnection() { + + JiraConfiguration jiraConfiguration = configurationModel + .getJiraConfiguration(); + + try { + + WebClient client = WebClient.create(jiraConfiguration.getJiraUrl()); + client.path(JiraRESTClient.PATH_AUTH_SESSION).accept( + MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML); + + org.libreplan.ws.common.impl.Util.addAuthorizationHeader(client, + jiraConfiguration.getJiraUserId(), + jiraConfiguration.getJiraPassword()); + + Response response = client.get(); + + if (response.getStatus() == Status.OK.getStatusCode()) { + messages.showMessage(Level.INFO, + _("JIRA connection was successful")); + } else { + LOG.info("Status code: " + response.getStatus()); + messages.showMessage(Level.ERROR, + _("Cannot connect to JIRA server")); + } + + } catch (Exception e) { + LOG.info(e); + messages.showMessage(Level.ERROR, + _("Cannot connect to JIRA server")); + } + + } + private boolean checkValidEntitySequenceRows() { Rows rows = entitySequencesGrid.getRows(); for (Row row : (List) rows.getChildren()) { @@ -774,6 +818,14 @@ public class ConfigurationController extends GenericForwardComposer { configurationModel.setLdapConfiguration(ldapConfiguration); } + public JiraConfiguration getJiraConfiguration() { + return configurationModel.getJiraConfiguration(); + } + + public void setJiraConfiguration(JiraConfiguration jiraConfiguration) { + configurationModel.setJiraConfiguration(jiraConfiguration); + } + public RowRenderer getAllUserRolesRenderer() { return new RowRenderer() { @Override @@ -924,4 +976,12 @@ public class ConfigurationController extends GenericForwardComposer { configurationModel.setSecondsPlanningWarning(secondsPlanningWarning); } + public TypeOfWorkHours getJiraConnectorTypeOfWorkHours() { + return configurationModel.getJiraConnectorTypeOfWorkHours(); + } + + public void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) { + configurationModel.setJiraConnectorTypeOfWorkHours(typeOfWorkHours); + } + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationModel.java index 8227a0788..bb0fd97b1 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationModel.java @@ -42,6 +42,7 @@ import org.libreplan.business.common.daos.IEntitySequenceDAO; import org.libreplan.business.common.entities.Configuration; import org.libreplan.business.common.entities.EntityNameEnum; import org.libreplan.business.common.entities.EntitySequence; +import org.libreplan.business.common.entities.JiraConfiguration; import org.libreplan.business.common.entities.LDAPConfiguration; import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; import org.libreplan.business.common.entities.ProgressType; @@ -106,7 +107,6 @@ public class ConfigurationModel implements IConfigurationModel { public void init() { this.configuration = getCurrentConfiguration(); initEntitySequences(); - initLdapConfiguration(); } private void initEntitySequences() { @@ -120,12 +120,6 @@ public class ConfigurationModel implements IConfigurationModel { } } - private void initLdapConfiguration() { - if (null == configuration.getLdapConfiguration()) { - configuration.setLdapConfiguration(LDAPConfiguration.create()); - } - } - private Configuration getCurrentConfiguration() { Configuration configuration = configurationDAO.getConfiguration(); if (configuration == null) { @@ -138,6 +132,8 @@ public class ConfigurationModel implements IConfigurationModel { private void forceLoad(Configuration configuration) { forceLoad(configuration.getDefaultCalendar()); forceLoad(configuration.getPersonalTimesheetsTypeOfWorkHours()); + forceLoad(configuration.getJiraConfiguration() + .getJiraConnectorTypeOfWorkHours()); } private void forceLoad(BaseCalendar calendar) { @@ -667,4 +663,36 @@ public class ConfigurationModel implements IConfigurationModel { configuration.setSecondsPlanningWarning(secondsPlanningWarning); } + @Override + public void setJiraConfiguration(JiraConfiguration jiraConfiguration) { + configuration.setJiraConfiguration(jiraConfiguration); + } + + @Override + public JiraConfiguration getJiraConfiguration() { + return configuration.getJiraConfiguration(); + } + + @Override + public TypeOfWorkHours getJiraConnectorTypeOfWorkHours() { + JiraConfiguration jiraConfiguration = configuration + .getJiraConfiguration(); + if (jiraConfiguration != null) { + return jiraConfiguration.getJiraConnectorTypeOfWorkHours(); + } + return null; + } + + @Override + public void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) { + if (configuration != null) { + JiraConfiguration jiraConfiguration = configuration + .getJiraConfiguration(); + if (jiraConfiguration != null) { + jiraConfiguration + .setJiraConnectorTypeOfWorkHours(typeOfWorkHours); + } + } + } + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/IConfigurationModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/IConfigurationModel.java index ab8e0a22f..71ffc5aca 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/common/IConfigurationModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/IConfigurationModel.java @@ -27,6 +27,7 @@ import java.util.Set; import org.libreplan.business.calendars.entities.BaseCalendar; import org.libreplan.business.common.entities.EntityNameEnum; import org.libreplan.business.common.entities.EntitySequence; +import org.libreplan.business.common.entities.JiraConfiguration; import org.libreplan.business.common.entities.LDAPConfiguration; import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; import org.libreplan.business.common.entities.ProgressType; @@ -185,4 +186,12 @@ public interface IConfigurationModel { void setSecondsPlanningWarning( Integer planningWarningExitWithoutSavingSeconds); + void setJiraConfiguration(JiraConfiguration jiraConfiguration); + + JiraConfiguration getJiraConfiguration(); + + TypeOfWorkHours getJiraConnectorTypeOfWorkHours(); + + void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours); + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/DetailsOrderElementController.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/DetailsOrderElementController.java index d94ba7829..07e5a3ca5 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/DetailsOrderElementController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/DetailsOrderElementController.java @@ -84,4 +84,15 @@ public class DetailsOrderElementController extends return Util.getMoneyFormat(); } + public boolean isCodeDisabled() { + if (isCodeAutogenerated()) { + return true; + } + OrderElement orderElement = orderElementModel.getOrderElement(); + if (orderElement == null) { + return false; + } + return orderElement.isJiraIssue(); + } + } \ No newline at end of file diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/IOrderModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/IOrderModel.java index 0892692f9..158fcd049 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/IOrderModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/IOrderModel.java @@ -156,4 +156,6 @@ public interface IOrderModel extends IIntegrationEntityModel { User getUser(); + boolean isJiraActivated(); + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderCRUDController.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderCRUDController.java index ef83bf1f6..0680c96b4 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderCRUDController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderCRUDController.java @@ -29,17 +29,20 @@ import java.util.ConcurrentModificationException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import javax.annotation.Resource; +import javax.ws.rs.WebApplicationException; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.joda.time.LocalDate; import org.libreplan.business.calendars.entities.BaseCalendar; +import org.libreplan.business.common.daos.IConfigurationDAO; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.externalcompanies.entities.DeadlineCommunication; import org.libreplan.business.externalcompanies.entities.DeliverDateComparator; @@ -55,6 +58,10 @@ import org.libreplan.business.resources.entities.Criterion; import org.libreplan.business.templates.entities.OrderTemplate; import org.libreplan.business.users.entities.User; import org.libreplan.business.users.entities.UserRole; +import org.libreplan.importers.IJiraOrderElementSynchronizer; +import org.libreplan.importers.IJiraTimesheetSynchronizer; +import org.libreplan.importers.JiraSyncInfo; +import org.libreplan.importers.jira.IssueDTO; import org.libreplan.web.common.ConfirmCloseUtil; import org.libreplan.web.common.FilterUtils; import org.libreplan.web.common.IMessagesForUser; @@ -85,6 +92,7 @@ import org.zkoss.ganttz.util.LongOperationFeedback; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Desktop; import org.zkoss.zk.ui.Executions; +import org.zkoss.zk.ui.SuspendNotAllowedException; import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; @@ -102,7 +110,9 @@ import org.zkoss.zul.Datebox; import org.zkoss.zul.Grid; import org.zkoss.zul.Hbox; import org.zkoss.zul.Label; +import org.zkoss.zul.ListModel; import org.zkoss.zul.Messagebox; +import org.zkoss.zul.Popup; import org.zkoss.zul.Row; import org.zkoss.zul.RowRenderer; import org.zkoss.zul.Rows; @@ -199,6 +209,16 @@ public class OrderCRUDController extends GenericForwardComposer { private EndDatesRenderer endDatesRenderer = new EndDatesRenderer(); + @Autowired + private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer; + + @Autowired + private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer; + + @Autowired + private IConfigurationDAO configurationDAO; + + @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); @@ -1854,4 +1874,167 @@ public class OrderCRUDController extends GenericForwardComposer { filterFinishDate.setValue(FilterUtils.readProjectsEndDate()); } + private Popup jirasyncPopup; + private Button startJiraSyncButton, cancelJiraSyncButton, syncWithJiraButton; + private Combobox comboJiraLabel; + + public boolean isJiraDeactivated() { + return !configurationDAO.getConfigurationWithReadOnlyTransaction() + .getJiraConfiguration().isJiraActivated(); + } + + public void syncWithJira() { + try { + List items = jiraOrderElementSynchronizer.getAllJiraLabels(); + + Textbox txtImportedLabel = (Textbox) editWindow + .getFellowIfAny("txtImportedLabel"); + + if (!(txtImportedLabel.getText()).isEmpty()) { + startSyncWithJira(txtImportedLabel.getText()); + return; + } + + setupJiraSyncPopup(editWindow, new SimpleListModelExt(items)); + + syncWithJiraButton = (Button) getCurrentTab().getFellow( + "syncWithJiraButton"); + + jirasyncPopup.open(syncWithJiraButton, "before_start"); + + } catch (WebApplicationException e) { + LOG.info(e); + messagesForUser.showMessage(Level.ERROR, + _("Cannot connect to JIRA server")); + } + } + + + public void startSyncWithJira(String label) { + try { + Order order = getOrder(); + + List issues = jiraOrderElementSynchronizer + .getJiraIssues(label); + + order.setCodeAutogenerated(false); + order.setImportedLabel(label); + + jiraOrderElementSynchronizer.syncOrderElementsWithJiraIssues( + issues, order); + + saveAndContinue(false); + if (jirasyncPopup != null) { + jirasyncPopup.close(); + } + + jiraTimesheetSynchronizer.syncJiraTimesheetWithJiraIssues(issues, + order); + + showSyncInfo(); + + // Reload order info in all tabs + Tab previousTab = getCurrentTab(); + initEdit(order); + selectTab(previousTab.getId()); + } catch (WebApplicationException e) { + LOG.info(e); + messagesForUser.showMessage(Level.ERROR, + _("Cannot connect to JIRA server")); + } + } + + private void showSyncInfo() { + Map args = new HashMap(); + + JiraSyncInfo jiraSyncInfoProgress = jiraOrderElementSynchronizer + .getJiraSyncInfo(); + args.put("showSyncProgressSuccess", + jiraSyncInfoProgress.isSyncSuccessful()); + args.put("jiraSyncProgressFailedReasons", new SimpleListModel( + jiraSyncInfoProgress.getSyncFailedReasons())); + + JiraSyncInfo jiraSyncInfoTimesheet = jiraTimesheetSynchronizer + .getJiraSyncInfo(); + args.put("showSyncTimesheetSuccess", + jiraSyncInfoTimesheet.isSyncSuccessful()); + args.put("jiraSyncTimesheetFailedReasons", new SimpleListModel( + jiraSyncInfoTimesheet.getSyncFailedReasons())); + + Window jiraSyncInfoWindow = (Window) Executions.createComponents( + "/orders/_jiraSyncInfo.zul", null, args); + + try { + jiraSyncInfoWindow.doModal(); + } catch (SuspendNotAllowedException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private void setupJiraSyncPopup(Component comp, ListModel model) { + + startJiraSyncButton = (Button) comp.getFellow("startJiraSyncButton"); + startJiraSyncButton.setLabel(_("Start sync")); + + startJiraSyncButton.addEventListener(Events.ON_CLICK, new EventListener() { + + @Override + public void onEvent(Event event) { + startSyncWithJira(comboJiraLabel.getValue()); + } + }); + cancelJiraSyncButton = (Button) comp.getFellow("cancelJiraSyncButton"); + cancelJiraSyncButton.setLabel(_("Cancel")); + cancelJiraSyncButton.addEventListener(Events.ON_CLICK, new EventListener() { + + @Override + public void onEvent(Event event) { + jirasyncPopup.close(); + } + }); + comboJiraLabel = (Combobox) comp.getFellowIfAny("comboJiraLabel"); + comboJiraLabel.setModel(model); + + jirasyncPopup = (Popup) comp.getFellow("jirasyncPopup"); + + } + + /** + * This class provides case insensitive search for the {@link Combobox}. + */ + private class SimpleListModelExt extends SimpleListModel { + + public SimpleListModelExt(List data) { + super(data); + } + + public ListModel getSubModel(Object value, int nRows) { + final String idx = value == null ? "" : objectToString(value); + if (nRows < 0) { + nRows = 10; + } + final LinkedList data = new LinkedList(); + for (int i = 0; i < getSize(); i++) { + if (idx.equals("") + || entryMatchesText(getElementAt(i).toString(), idx)) { + data.add(getElementAt(i)); + if (--nRows <= 0) { + break; + } + } + } + return new SimpleListModelExt(data); + } + + public boolean entryMatchesText(String entry, String text) { + return entry.toLowerCase().contains(text.toLowerCase()); + } + } + + public boolean isJiraActivated() { + return orderModel.isJiraActivated(); + } + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderElementTreeController.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderElementTreeController.java index 05bc45078..8c5976f60 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderElementTreeController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderElementTreeController.java @@ -37,6 +37,9 @@ import java.util.logging.Filter; import javax.annotation.Resource; import org.apache.commons.lang.StringUtils; +import org.libreplan.business.common.daos.IConfigurationDAO; +import org.libreplan.business.common.entities.EntitySequence; +import org.libreplan.business.common.entities.JiraConfiguration; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.OrderLine; @@ -59,6 +62,7 @@ import org.libreplan.web.orders.assigntemplates.TemplateFinderPopup.IOnResult; import org.libreplan.web.security.SecurityUtils; import org.libreplan.web.templates.IOrderTemplatesControllerEntryPoints; import org.libreplan.web.tree.TreeController; +import org.springframework.beans.factory.annotation.Autowired; import org.zkoss.ganttz.IPredicate; import org.zkoss.ganttz.util.ComponentsFinder; import org.zkoss.zk.ui.Component; @@ -66,6 +70,7 @@ import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zul.A; import org.zkoss.zul.Button; import org.zkoss.zul.Checkbox; import org.zkoss.zul.Constraint; @@ -83,6 +88,7 @@ import org.zkoss.zul.impl.api.InputElement; /** * Controller for {@link OrderElement} tree view of {@link Order} entities
+ * * @author Lorenzo Tilve Álvaro * @author Manuel Rego Casasnovas * @author Susana Montes Pedreira @@ -119,6 +125,9 @@ public class OrderElementTreeController extends TreeController { private Popup filterOptionsPopup; + @Autowired + private IConfigurationDAO configurationDAO; + public List getLabels() { return orderModel.getLabels(); } @@ -456,6 +465,14 @@ public class OrderElementTreeController extends TreeController { @Override protected void addCodeCell(final OrderElement orderElement) { + if (orderElement.isJiraIssue()) { + addHyperlink(orderElement); + } else { + addTextbox(orderElement); + } + } + + private void addTextbox(final OrderElement orderElement) { Textbox textBoxCode = new Textbox(); Util.bind(textBoxCode, new Util.Getter() { @Override @@ -489,6 +506,26 @@ public class OrderElementTreeController extends TreeController { putCodeTextbox(orderElement, textBoxCode); } + private void addHyperlink(final OrderElement orderElement) { + String code = orderElement.getCode(); + A hyperlink = new A(code); + + String jiraUrl = configurationDAO.getConfigurationWithReadOnlyTransaction().getJiraConfiguration().getJiraUrl(); + + String codeWithoutPrefix = StringUtils.removeStart(code, JiraConfiguration.CODE_PREFIX); + codeWithoutPrefix = StringUtils.removeStart(codeWithoutPrefix, + orderElement.getOrder().getCode() + + EntitySequence.CODE_SEPARATOR_CHILDREN); + + hyperlink.setHref(jiraUrl + "/browse/" + codeWithoutPrefix); + + if (orderModel.isCodeAutogenerated() || readOnly) { + hyperlink.setDisabled(true); + } + + addCell(hyperlink); + } + void addInitDateCell(final OrderElement currentOrderElement) { DynamicDatebox dinamicDatebox = new DynamicDatebox( new DynamicDatebox.Getter() { diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderModel.java index e57e0d932..c02101f4e 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderModel.java @@ -962,4 +962,11 @@ public class OrderModel extends IntegrationEntityModel implements IOrderModel { return user; } + @Override + @Transactional(readOnly = true) + public boolean isJiraActivated() { + return configurationDAO.getConfiguration().getJiraConfiguration() + .isJiraActivated(); + } + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/criterionrequirements/AssignedCriterionRequirementToOrderElementController.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/criterionrequirements/AssignedCriterionRequirementToOrderElementController.java index e619eb0b2..439046061 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/criterionrequirements/AssignedCriterionRequirementToOrderElementController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/criterionrequirements/AssignedCriterionRequirementToOrderElementController.java @@ -207,7 +207,9 @@ public class AssignedCriterionRequirementToOrderElementController extends } public boolean isEditableHoursGroup() { - return getElement() != null && getElement() instanceof OrderLine; + OrderElement element = getElement(); + return element != null && element instanceof OrderLine + && !element.isJiraIssue(); } } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeController.java b/libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeController.java index 7acb66f32..9f237c85b 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeController.java @@ -931,7 +931,7 @@ public abstract class TreeController> extends } private void updateCodeFor(T element) { - if (!readOnly) { + if (!readOnly && !element.isJiraIssue()) { Textbox textbox = codeTextboxByElement.get(element); textbox.setValue(getCodeHandler().getCodeFor(element)); } @@ -1006,7 +1006,7 @@ public abstract class TreeController> extends public void addHoursCell(final T currentElement) { Intbox intboxHours = buildHoursIntboxFor(currentElement); hoursIntBoxByElement.put(currentElement, intboxHours); - if (readOnly) { + if (readOnly || currentElement.isJiraIssue()) { intboxHours.setDisabled(true); } Treecell cellHours = addCell(intboxHours); diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/IWorkReportTypeModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/IWorkReportTypeModel.java index 7df4f44a2..9af6b736a 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/IWorkReportTypeModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/IWorkReportTypeModel.java @@ -56,7 +56,7 @@ public interface IWorkReportTypeModel extends IIntegrationEntityModel { * * @return A {@link List} of {@link WorkReportType} */ - List getWorkReportTypesExceptPersonalTimeSheets(); + List getWorkReportTypesExceptPersonalAndJiraTimesheets(); /** * Stores the current {@link WorkReportType}. diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportTypeCRUDController.java b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportTypeCRUDController.java index d4aba980b..c2cddb529 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportTypeCRUDController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportTypeCRUDController.java @@ -116,7 +116,7 @@ public class WorkReportTypeCRUDController extends BaseCRUDController getWorkReportTypes() { - return workReportTypeModel.getWorkReportTypesExceptPersonalTimeSheets(); + return workReportTypeModel.getWorkReportTypesExceptPersonalAndJiraTimesheets(); } public WorkReportType getWorkReportType() { diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportTypeModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportTypeModel.java index 7a242fa73..e39981e5a 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportTypeModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportTypeModel.java @@ -118,12 +118,15 @@ public class WorkReportTypeModel extends IntegrationEntityModel implements @Override @Transactional(readOnly = true) - public List getWorkReportTypesExceptPersonalTimeSheets() { + public List getWorkReportTypesExceptPersonalAndJiraTimesheets() { List list = workReportTypeDAO.list(WorkReportType.class); try { list.remove(workReportTypeDAO .findUniqueByName(PredefinedWorkReportTypes.PERSONAL_TIMESHEETS .getName())); + list.remove(workReportTypeDAO + .findUniqueByName(PredefinedWorkReportTypes.JIRA_TIMESHEETS + .getName())); } catch (NonUniqueResultException e) { throw new RuntimeException(e); } catch (InstanceNotFoundException e) { @@ -155,6 +158,10 @@ public class WorkReportTypeModel extends IntegrationEntityModel implements throw new IllegalArgumentException( "Personal timesheets timesheet template cannot be edited"); } + if (workReportType.isJiraTimesheetsType()) { + throw new IllegalArgumentException( + "Personal timesheets timesheet template cannot be edited"); + } setListing(false); editing = true; @@ -224,6 +231,10 @@ public class WorkReportTypeModel extends IntegrationEntityModel implements throw new IllegalArgumentException( "Personal timesheets timesheet template cannot be removed"); } + if (workReportType.isJiraTimesheetsType()) { + throw new IllegalArgumentException( + "Personal timesheets timesheet template cannot be removed"); + } try { workReportTypeDAO.remove(workReportType.getId()); diff --git a/libreplan-webapp/src/main/webapp/common/configuration.zul b/libreplan-webapp/src/main/webapp/common/configuration.zul index 411a78169..232e0ab69 100644 --- a/libreplan-webapp/src/main/webapp/common/configuration.zul +++ b/libreplan-webapp/src/main/webapp/common/configuration.zul @@ -42,6 +42,7 @@ + @@ -413,6 +414,48 @@ + + + + + + + + + + + + + + + + + + + + + + + +