Merge branch 'master' into filtering-improvements

FEA: ItEr77S15FilteringEnhancements
This commit is contained in:
Lorenzo Tilve Álvaro 2013-02-13 12:19:17 +01:00
commit 35e1644cee
59 changed files with 3234 additions and 35 deletions

View file

@ -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;
}
}

View file

@ -67,19 +67,28 @@ public class ConfigurationBootstrap implements IConfigurationBootstrap {
public void loadRequiredData() {
loadRequiredDataSequences();
List<Configuration> 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() {

View file

@ -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.<br />
* Fills the attributes {@link Configuration#personalTimesheetsTypeOfWorkHours}
* and {@link JiraConfiguration#jiraConnectorTypeOfWorkHours} with a default
* values.<br />
*
* If possible it uses the "Default" {@link TypeOfWorkHours}, but if it doesn't
* exist, it uses the first {@link TypeOfWorkHours} found.<br />
@ -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);
}

View file

@ -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 <mrego@igalia.com>
*/
public interface IPersonalTimesheetsTypeOfWorkHoursBootstrap extends
public interface IConfigurationTypeOfWorkHoursBootstrap extends
IDataBootstrap {
void loadRequiredData();

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.business.common.entities;
import org.libreplan.business.common.BaseEntity;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
/**
* JiraConfiguration entity
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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:
* <ul>
* <li>A comma-separated list of labels</li>
* <li>A URL that will return a comma-separated list of labels</li>
* </ul>
*/
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;
}
}

View file

@ -149,6 +149,7 @@ public class TypeOfWorkHoursDAO extends IntegrationEntityDAO<TypeOfWorkHours>
checkHasHourCost(type);
checkHasWorkReportLine(type);
checkIsPersonalTimesheetsTypeOfWorkHours(type);
checkIsJiraConnectorTypeOfWorkHours(type);
}
private void checkHasWorkReportLine(TypeOfWorkHours type) {
@ -192,4 +193,15 @@ public class TypeOfWorkHoursDAO extends IntegrationEntityDAO<TypeOfWorkHours>
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);
}
}
}

View file

@ -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;
}
}

View file

@ -120,6 +120,8 @@ public class Order extends OrderLineGroup implements Comparable {
private Set<CustomerCommunication> customerCommunications = new HashSet<CustomerCommunication>();
private String importedLabel;
@Valid
private SortedSet<DeadlineCommunication> deliveringDates = new TreeSet<DeadlineCommunication>(
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;
}
}

View file

@ -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;
}

View file

@ -163,6 +163,11 @@ public class OrderLineGroup extends OrderElement implements
return getThis().isUpdatedFromTimesheets();
}
@Override
public boolean isJiraIssue() {
return getThis().isJiraIssue();
}
}
public static OrderLineGroup create() {

View file

@ -625,4 +625,9 @@ public abstract class OrderElementTemplate extends BaseEntity implements
return false;
}
@Override
public boolean isJiraIssue() {
return false;
}
}

View file

@ -105,6 +105,11 @@ public class OrderLineGroupTemplate extends OrderElementTemplate implements
return false;
}
@Override
public boolean isJiraIssue() {
return false;
}
}
public static OrderLineGroupTemplate createNew() {

View file

@ -62,4 +62,6 @@ public interface ITreeNode<T extends ITreeNode<T>> {
boolean isUpdatedFromTimesheets();
boolean isJiraIssue();
}

View file

@ -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;
}

View file

@ -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());
}
}

View file

@ -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);
}
}
}

View file

@ -208,6 +208,108 @@
columnDataType="INTEGER" />
</changeSet>
<!-- Jira configuration-->
<changeSet id="add-new-column-jira_activated" author="miciele">
<comment>
Add new column jira_activated with default value FALSE to configuration table
</comment>
<addColumn tableName="configuration">
<column name="jira_activated" type="BOOLEAN" />
</addColumn>
<addDefaultValue tableName="configuration" columnName="jira_activated"
defaultValueBoolean="FALSE" />
<addNotNullConstraint tableName="configuration"
columnName="jira_activated"
defaultNullValue="FALSE"
columnDataType="BOOLEAN" />
</changeSet>
<changeSet id="add-new-column-jira_url" author="miciele">
<comment>
Add new column jira_url in table configuration
</comment>
<addColumn tableName="configuration">
<column name="jira_url" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<changeSet id="add-new-column-jira_label_url" author="miciele">
<comment>
Add new column jira_label_url in table configuration
</comment>
<addColumn tableName="configuration">
<column name="jira_label_url" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<changeSet id="add-new-column-jira_user_id" author="miciele">
<comment>
Add new column jira_user_id in table configuration
</comment>
<addColumn tableName="configuration">
<column name="jira_user_id" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<changeSet id="add-new-column-jira_password" author="miciele">
<comment>
Add new column jira_user_id in table configuration
</comment>
<addColumn tableName="configuration">
<column name="jira_password" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<changeSet id="add-new-column-imported-label" author="miciele">
<comment>
Add new column imported_label in table order_table
</comment>
<addColumn tableName="order_table">
<column name="imported_label" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<changeSet id="add-jira_connector_type_of_work_hours-to-configuration"
author="mrego">
<comment>
Add new column jira_connector_type_of_work_hours to configuration
table.
</comment>
<addColumn tableName="configuration">
<column name="jira_connector_type_of_work_hours" type="BIGINT" />
</addColumn>
<addForeignKeyConstraint
constraintName="configuration_jira_connector_type_of_work_hours_fkey"
baseTableName="configuration"
baseColumnNames="jira_connector_type_of_work_hours"
referencedTableName="type_of_work_hours"
referencedColumnNames="id" />
</changeSet>
<changeSet id="rename-column-jira_label_url-in-configuration"
author="mrego">
<comment>
Rename column jira_label_url to jira_labels in configuration table
</comment>
<renameColumn tableName="configuration"
oldColumnName="jira_label_url"
newColumnName="jira_labels"
columnDataType="VARCHAR(255)" />
</changeSet>
<changeSet id="change-column-jira_labels-in-configuration-to-text"
author="mrego" dbms="postgresql">
<comment>Change column jira_labels in configuration to TEXT</comment>
<modifyDataType tableName="configuration" columnName="jira_labels"
newDataType="TEXT" />
</changeSet>
<changeSet id="change-column-jira_labels-in-configuration-to-text-in-mysql"
author="mrego" dbms="mysql">
<comment>Change column jira_labels in configuration to TEXT in MySQL</comment>
<sql>ALTER TABLE configuration MODIFY jira_labels TEXT</sql>
</changeSet>
<changeSet id="add-projects_filter_period_since-column-to-user_table"
author="ltilve">
<comment>Add column to store project filtering interval 'range since' parameter</comment>
@ -272,5 +374,4 @@
onDelete="SET NULL"/>
</changeSet>
</databaseChangeLog>

View file

@ -121,6 +121,17 @@
</component>
<!-- Jira configuration-->
<component name="jiraConfiguration" class="org.libreplan.business.common.entities.JiraConfiguration">
<property name="jiraActivated" column="jira_activated" not-null="true"/>
<property name="jiraUrl" column="jira_url"/>
<property name="jiraLabels" column="jira_labels" type="text"/>
<property name="jiraUserId" column="jira_user_id"/>
<property name="jiraPassword" column="jira_password"/>
<many-to-one name="jiraConnectorTypeOfWorkHours" cascade="none"
column="jira_connector_type_of_work_hours" />
</component>
</class>
</hibernate-mapping>

View file

@ -126,6 +126,9 @@
</type>
</property>
<!-- extra column to hold imported label -->
<property name="importedLabel" column="imported_label"/>
<!-- Not indexed -->
<many-to-one name="customer" access="field" class="org.libreplan.business.externalcompanies.entities.ExternalCompany"/>

View file

@ -440,6 +440,14 @@
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-xc</artifactId>
</dependency>
<!-- ZK Timeplot -->
<dependency>

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
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<String> getAllJiraLabels();
/**
* Get all jira issues based on the specified <code>label</code> parameter
* from jira RESTFul web service
*
* @param label
* search criteria for jira issues
*
* @return list of jira issues
*/
List<IssueDTO> 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 <code>issues</code> and check if an
* {@link OrderElement} of the given <code>order</code> 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 <code>order</code> 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<IssueDTO> issues, Order order);
/**
* returns synchronization info, success or fail info
*/
JiraSyncInfo getJiraSyncInfo();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
public interface IJiraTimesheetSynchronizer {
/**
* Synchronize jira timesheet with the specified jira <code>issues</code> .
*
*
* Loop through all jira <code>issues</code> 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<IssueDTO> issues, Order order);
/**
* returns synchronization info, success or fail info
*/
JiraSyncInfo getJiraSyncInfo();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchronizer {
@Autowired
private IConfigurationDAO configurationDAO;
private JiraSyncInfo jiraSyncInfo;
@Override
@Transactional(readOnly = true)
public List<String> 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<IssueDTO> 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<IssueDTO> issues = JiraRESTClient.getIssues(url, username, password,
path, query);
return issues;
}
@Override
@Transactional(readOnly = true)
public void syncOrderElementsWithJiraIssues(List<IssueDTO> 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 <code>order</code> If
* it is, update <code>OrderLine.name</code> with the specified parameter
* <code>name</code> (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 <code>orderLine</code>.
* If it is, update <code>HoursGroup.workingHours</code> with the specified
* parameter <code>workingHours</code>. 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<WorkLogItemDTO> 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 <code>workLogItems</code> and get the latest date
*
* @param workLogItems
* list of workLogItems
* @return latest date
*/
private Date getTheLatestWorkLoggedDate(List<WorkLogItemDTO> workLogItems) {
List<Date> dates = new ArrayList<Date>();
for (WorkLogItemDTO workLogItem : workLogItems) {
if (workLogItem.getStarted() != null) {
dates.add(workLogItem.getStarted());
}
}
return Collections.max(dates);
}
@Override
public JiraSyncInfo getJiraSyncInfo() {
return jiraSyncInfo;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
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<IssueDTO> 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");
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Keeps track the synchronization info.
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class JiraSyncInfo {
private List<String> syncFailedReasons = new ArrayList<String>();
/**
* Add the specified <code>reason</code> 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<String> getSyncFailedReasons() {
return Collections.unmodifiableList(syncFailedReasons);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
private JiraSyncInfo jiraSyncInfo;
private List<Worker> 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<IssueDTO> 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<WorkLogItemDTO> 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 <code>workReport</code>
*
* @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<WorkLogItemDTO> 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 <code>workLogItem</code>
*
* @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 <code>workReportLine</code>
*
* @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<DescriptionValue> 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>code</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<Worker> getWorkers() {
return workerDAO.findAll();
}
/**
* Searches for {@link Worker} for the specified parameter <code>nif</code>
*
* @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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers.jira;
/**
* DTO representing a jira-issue Field
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers.jira;
/**
* DTO representing a jira-issue
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers.jira;
/**
* DTO representing a jira-issue LabelIssue
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class SearchResultDTO {
private Integer startAt;
private Integer maxResults;
private Integer total;
private List<IssueDTO> 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<IssueDTO> getIssues() {
return issues;
}
public void setIssues(List<IssueDTO> issues) {
this.issues = issues;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers.jira;
/**
* DTO representing a jira-issue Status
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers.jira;
/**
* DTO representing a jira-issue TimeTracking
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers.jira;
/**
* DTO representing a jira-issue WorkLogAuthor
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers.jira;
import java.util.List;
/**
* DTO representing a jira-issue WorkLog
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class WorkLogDTO {
private Integer startAt;
private Integer maxResults;
private Integer total;
private List<WorkLogItemDTO> 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<WorkLogItemDTO> getWorklogs() {
return worklogs;
}
public void setWorklogs(List<WorkLogItemDTO> worklogs) {
this.worklogs = worklogs;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers.jira;
import java.util.Date;
/**
* DTO representing a jira-issue WorkLogItem
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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;
}
}

View file

@ -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<Row>) 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);
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -156,4 +156,6 @@ public interface IOrderModel extends IIntegrationEntityModel {
User getUser();
boolean isJiraActivated();
}

View file

@ -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<String> 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<IssueDTO> 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<String, Object> args = new HashMap<String, Object>();
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();
}
}

View file

@ -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 <br />
*
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
* @author Manuel Rego Casasnovas <mrego@igalia.com>
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
@ -119,6 +125,9 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
private Popup filterOptionsPopup;
@Autowired
private IConfigurationDAO configurationDAO;
public List<org.libreplan.business.labels.entities.Label> getLabels() {
return orderModel.getLabels();
}
@ -456,6 +465,14 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
@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<String>() {
@Override
@ -489,6 +506,26 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
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<Date>() {

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -931,7 +931,7 @@ public abstract class TreeController<T extends ITreeNode<T>> 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<T extends ITreeNode<T>> 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);

View file

@ -56,7 +56,7 @@ public interface IWorkReportTypeModel extends IIntegrationEntityModel {
*
* @return A {@link List} of {@link WorkReportType}
*/
List<WorkReportType> getWorkReportTypesExceptPersonalTimeSheets();
List<WorkReportType> getWorkReportTypesExceptPersonalAndJiraTimesheets();
/**
* Stores the current {@link WorkReportType}.

View file

@ -116,7 +116,7 @@ public class WorkReportTypeCRUDController extends BaseCRUDController<WorkReportT
private OrderedFieldsAndLabelsRowRenderer orderedFieldsAndLabesRowRenderer = new OrderedFieldsAndLabelsRowRenderer();
public List<WorkReportType> getWorkReportTypes() {
return workReportTypeModel.getWorkReportTypesExceptPersonalTimeSheets();
return workReportTypeModel.getWorkReportTypesExceptPersonalAndJiraTimesheets();
}
public WorkReportType getWorkReportType() {

View file

@ -118,12 +118,15 @@ public class WorkReportTypeModel extends IntegrationEntityModel implements
@Override
@Transactional(readOnly = true)
public List<WorkReportType> getWorkReportTypesExceptPersonalTimeSheets() {
public List<WorkReportType> getWorkReportTypesExceptPersonalAndJiraTimesheets() {
List<WorkReportType> 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());

View file

@ -42,6 +42,7 @@
<tab label="${i18n:_('Main preferences')}" />
<tab label="${i18n:_('Entity sequences')}" />
<tab label="${i18n:_('LDAP configuration')}" />
<tab label="${i18n:_('JIRA connector')}" />
</tabs>
<tabpanels>
<tabpanel id="panelConfiguration">
@ -413,6 +414,48 @@
</vbox>
</groupbox>
</tabpanel>
<tabpanel id="panelJiraConfiguration">
<grid>
<columns>
<column width="200px" />
<column />
</columns>
<rows>
<row>
<label value="${i18n:_('Activated')}" />
<checkbox id="jiraActivated" checked="@{configurationController.jiraConfiguration.jiraActivated}" width="300px" />
</row>
<row>
<label value="${i18n:_('JIRA URL')}" />
<textbox id="jiraURL" value="@{configurationController.jiraConfiguration.jiraUrl}" width="300px"/>
</row>
<row>
<label value="${i18n:_('JIRA sync account')}" />
<textbox id="jiraSyncAccount" value="@{configurationController.jiraConfiguration.jiraUserId}" width="300px"/>
</row>
<row>
<label value="${i18n:_('JIRA sync password')}" />
<textbox id="jiraSyncPassword" value="@{configurationController.jiraConfiguration.jiraPassword}" type="password" width="300px"/>
</row>
<row>
<label value="${i18n:_('JIRA labels: comma-separated list of labels or URL')}" />
<textbox id="jiraLabels" value="@{configurationController.jiraConfiguration.jiraLabels}" width="600px"/>
</row>
<row>
<label value="${i18n:_('Hours type for JIRA connector')}" />
<bandboxSearch
finder="TypeOfWorkHoursBandboxFinder"
selectedElement="@{configurationController.jiraConnectorTypeOfWorkHours, access='both'}" />
</row>
</rows>
</grid>
<separator />
<button label="${i18n:_('Test connection')}"
onClick="configurationController.testJiraConnection()" />
<separator />
</tabpanel>
</tabpanels>
</tabbox>
<hbox>

View file

@ -281,6 +281,32 @@
</grid>
</groupbox>
</groupbox>
<groupbox style="margin-top: 5px" closable="false"
visible="@{controller.jiraActivated}">
<caption label="${i18n:_('JIRA import information')}" />
<separator spacing="10px"/>
<hbox width="100%">
<separator spacing="10px" width="100%"/>
<grid fixedLayout="true" hflex="1">
<columns>
<column width="200px" />
<column />
</columns>
<rows>
<row>
<label value="${i18n:_('JIRA label')}" width="50px"/>
<hbox>
<textbox id="txtImportedLabel" value="@{controller.order.importedLabel}"
width="350px" disabled="true"/>
<button label="${i18n:_('Sync with JIRA')}" id="syncWithJiraButton" disabled="@{controller.jiraDeactivated}"
onClick="controller.syncWithJira()" />
</hbox>
</row>
</rows>
</grid>
</hbox>
</groupbox>
</tabpanel>
<tabpanel>
<listOrderElementHours id="orderElementHours" fulfill="tabAssignedHours.onSelect"/>
@ -306,4 +332,15 @@
</tabpanel>
</tabpanels>
</tabbox>
<popup id="jirasyncPopup" sclass="finder-popup">
<groupbox mold="3d" closable="false" width="420px">
<caption id="jiraSyncCaption" label="Select a label" />
<combobox id="comboJiraLabel" autodrop="true" width="400px"/>
<separator/>
<hbox>
<button id="startJiraSyncButton" />
<button id="cancelJiraSyncButton" />
</hbox>
</groupbox>
</popup>
</window>

View file

@ -0,0 +1,51 @@
<!--
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 <http://www.gnu.org/licenses/>.
-->
<window id="winJiraSyncInfo" title="${i18n:_('LibrePlan: Jira synchronization info')}"
width="500px" border="normal" mode="modal">
<div>
<vbox>
<label value="${i18n:_('Synchronization of order elements with JIRA issues was successful and project has been updated accordingly')}" sclass="remarked" />
</vbox>
<separator spacing="20px"/>
<vbox>
<label value="${i18n:_('Synchronization of progress assignment was successful')}" sclass="remarked" if="${arg.showSyncProgressSuccess}"/>
<vbox if="${not arg.showSyncProgressSuccess}">
<label value="${i18n:_('Synchronization of progress assignment is not completed for the following reasons:')}" />
<listbox model="${arg.jiraSyncProgressFailedReasons}"/>
</vbox>
</vbox>
<separator spacing="20px"/>
<vbox>
<label value="${i18n:_('Synchronization of timesheets was successful')}" sclass="remarked" if="${arg.showSyncTimesheetSuccess}"/>
<vbox if="${not arg.showSyncTimesheetSuccess}">
<label value="${i18n:_('Synchronization of timesheets is not completed for the following reasons:')}" />
<listbox model="${arg.jiraSyncTimesheetFailedReasons}"/>
</vbox>
</vbox>
</div>
<button id="closeBtn" label="${i18n:_('Close')}" onClick="winJiraSyncInfo.detach()"
sclass="cancel-button global-action"/>
</window>

View file

@ -39,7 +39,7 @@
<textbox id="code"
value="@{detailsController.orderElement.code}"
constraint="no empty:${i18n:_('cannot be empty')}" width="150px"
disabled="@{detailsController.isCodeAutogenerated}" />
disabled="@{detailsController.codeDisabled}" />
</row>
<row>
<label value="${i18n:_('Starting date')}" />

View file

@ -0,0 +1,235 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.libreplan.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE;
import static org.libreplan.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE;
import static org.libreplan.web.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_FILE;
import static org.libreplan.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE;
import static org.libreplan.web.test.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import javax.annotation.Resource;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.libreplan.business.IDataBootstrap;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.orders.daos.IOrderDAO;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.scenarios.IScenarioManager;
import org.libreplan.business.scenarios.entities.OrderVersion;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.importers.jira.IssueDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* Test for {@link JiraOrderElementSynchronizer }
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE,
WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE,
WEBAPP_SPRING_SECURITY_CONFIG_FILE,
WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE })
@Transactional
public class JiraOrderElementSynchronizerTest {
@Resource
private IDataBootstrap defaultAdvanceTypesBootstrapListener;
@Resource
private IDataBootstrap scenariosBootstrap;
@Resource
private IDataBootstrap configurationBootstrap;
@Autowired
private IAdHocTransactionService transactionService;
@Autowired
private IConfigurationDAO configurationDAO;
@Autowired
private IScenarioManager scenarioManager;
private List<IssueDTO> issues;
@Autowired
private IOrderDAO orderDAO;
@Autowired
private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer;
@Before
public void loadRequiredaData() {
IOnTransaction<Void> load = new IOnTransaction<Void>() {
@Override
public Void execute() {
defaultAdvanceTypesBootstrapListener.loadRequiredData();
configurationBootstrap.loadRequiredData();
scenariosBootstrap.loadRequiredData();
issues = getJiraIssues();
return null;
}
};
transactionService.runOnAnotherTransaction(load);
}
private List<IssueDTO> getJiraIssues() {
List<IssueDTO> issues = new ArrayList<IssueDTO>();
try {
Properties properties = loadProperties();
issues = JiraRESTClient.getIssues(properties.getProperty("url"),
properties.getProperty("username"),
properties.getProperty("password"),
JiraRESTClient.PATH_SEARCH,
getJiraLabel(properties.getProperty("label")));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
return issues;
}
private Properties loadProperties() throws FileNotFoundException,
IOException {
String filename = System.getProperty("user.dir")
+ "/../scripts/jira-connector/jira-conn.properties";
Properties properties = new Properties();
properties.load(new FileInputStream(filename));
return properties;
}
private String getJiraLabel(String label) {
return "labels=" + label;
}
private Order givenOrder() {
return transactionService
.runOnAnotherTransaction(new IOnTransaction<Order>() {
@Override
public Order execute() {
return givenValidOrderAlreadyStored();
}
});
}
private Order givenValidOrderAlreadyStored() {
Order order = Order.create();
order.setCode(UUID.randomUUID().toString());
order.setName("Order name " + UUID.randomUUID());
order.setInitDate(new Date());
order.setCalendar(configurationDAO.getConfiguration()
.getDefaultCalendar());
OrderVersion version = setupVersionUsing(scenarioManager, order);
order.useSchedulingDataFor(version);
orderDAO.save(order);
orderDAO.flush();
try {
return orderDAO.find(order.getId());
} catch (InstanceNotFoundException e) {
return null;
}
}
private Order givenOrderWithValidOrderLines() {
return transactionService
.runOnAnotherTransaction(new IOnTransaction<Order>() {
@Override
public Order execute() {
return givenValidOrderWithValidOrderLinesAlreadyStored();
}
});
}
private Order givenValidOrderWithValidOrderLinesAlreadyStored() {
Order order = givenOrder();
jiraOrderElementSynchronizer.syncOrderElementsWithJiraIssues(issues, order);
order.dontPoseAsTransientObjectAnymore();
orderDAO.saveWithoutValidating(order);
orderDAO.flush();
try {
return orderDAO.find(order.getId());
} catch (InstanceNotFoundException e) {
return null;
}
}
private OrderVersion setupVersionUsing(IScenarioManager scenarioManager,
Order order) {
Scenario current = scenarioManager.getCurrent();
OrderVersion result = OrderVersion.createInitialVersion(current);
order.setVersionForScenario(current, result);
return result;
}
@Test
@Ignore("Only working if you have a JIRA server configured")
public void testSyncOrderElementsOfAnExistingOrderWithNoOrderLines() {
Order order = givenOrder();
jiraOrderElementSynchronizer.syncOrderElementsWithJiraIssues(issues, order);
assertEquals(order.getOrderElements().size(), issues.size());
assertTrue(order.getOrderElements().get(0).getHoursGroups().size() > 0);
assertTrue(!order.getAdvancePercentage().equals(BigDecimal.ZERO));
}
@Test
@Ignore("Only working if you have a JIRA server configured")
public void testReSyncOrderElementsOfAnExistingOrderWithOrderLines() {
Order order = givenOrderWithValidOrderLines();
Integer workingHours = order.getWorkHours();
jiraOrderElementSynchronizer.syncOrderElementsWithJiraIssues(issues, order);
assertEquals(order.getOrderElements().size(), issues.size());
assertEquals(workingHours.intValue(), order.getWorkHours().intValue());
}
}

View file

@ -0,0 +1,105 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers;
import static org.junit.Assert.assertTrue;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import javax.ws.rs.WebApplicationException;
import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.libreplan.importers.jira.IssueDTO;
/**
* Test for {@link JiraRESTClient }
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class JiraRESTClientTest {
private Properties properties = null;
@Before
public void loadProperties() throws FileNotFoundException, IOException {
String filename = System.getProperty("user.dir")
+ "/../scripts/jira-connector/jira-conn.properties";
properties = new Properties();
properties.load(new FileInputStream(filename));
}
private String getJiraLabel(String label) {
return "labels=" + label;
}
@Test
@Ignore("Only working if you have a JIRA server configured")
public void testGetAllLablesFromValidLabelUrl() {
String labels = JiraRESTClient.getAllLables(properties
.getProperty("label_url"));
List<String> result = Arrays.asList(StringUtils.split(labels, ","));
assertTrue(result.size() > 0);
}
@Test(expected = WebApplicationException.class)
public void testGetAllLablesFromInValidLabelUrl() {
JiraRESTClient.getAllLables("");
}
@Test
@Ignore("Only working if you have a JIRA server configured")
public void testGetIssuesForValidLabelAndAuthorizedUser() {
List<IssueDTO> issues = JiraRESTClient.getIssues(
properties.getProperty("url"),
properties.getProperty("username"),
properties.getProperty("password"), JiraRESTClient.PATH_SEARCH,
getJiraLabel(properties.getProperty("label")));
assertTrue(issues.size() > 0);
}
@Test(expected = RuntimeException.class)
public void testGetIssuesForValidLabelButUnAuthorizedUser() {
JiraRESTClient.getIssues(properties.getProperty("url"), "", "",
JiraRESTClient.PATH_SEARCH,
getJiraLabel(properties.getProperty("label")));
}
@Test
@Ignore("Only working if you have a JIRA server configured")
public void testGetIssuesForEmptyLabel() {
List<IssueDTO> issues = JiraRESTClient.getIssues(
properties.getProperty("url"),
properties.getProperty("username"),
properties.getProperty("password"), JiraRESTClient.PATH_SEARCH,
"");
assertTrue(issues.size() > 0);
}
}

View file

@ -0,0 +1,231 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers;
import static org.junit.Assert.assertTrue;
import static org.libreplan.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE;
import static org.libreplan.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE;
import static org.libreplan.web.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_FILE;
import static org.libreplan.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE;
import static org.libreplan.web.test.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import javax.annotation.Resource;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.libreplan.business.IDataBootstrap;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.orders.daos.IOrderDAO;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.scenarios.IScenarioManager;
import org.libreplan.business.scenarios.entities.OrderVersion;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.workreports.entities.IWorkReportTypeBootstrap;
import org.libreplan.importers.jira.IssueDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* Test for {@link JiraTimesheetSynchronizer}
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE,
WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE,
WEBAPP_SPRING_SECURITY_CONFIG_FILE,
WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE })
@Transactional
public class JiraTimesheetSynchronizerTest {
@Resource
private IDataBootstrap defaultAdvanceTypesBootstrapListener;
@Resource
private IDataBootstrap scenariosBootstrap;
@Resource
private IDataBootstrap configurationBootstrap;
@Resource
private IWorkReportTypeBootstrap workReportTypeBootstrap;
@Autowired
private IAdHocTransactionService transactionService;
@Autowired
private IConfigurationDAO configurationDAO;
@Autowired
private IScenarioManager scenarioManager;
private List<IssueDTO> issues;
@Autowired
private IOrderDAO orderDAO;
@Autowired
private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer;
@Autowired
private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer;
@Before
public void loadRequiredaData() {
IOnTransaction<Void> load = new IOnTransaction<Void>() {
@Override
public Void execute() {
defaultAdvanceTypesBootstrapListener.loadRequiredData();
configurationBootstrap.loadRequiredData();
scenariosBootstrap.loadRequiredData();
workReportTypeBootstrap.loadRequiredData();
issues = getJiraIssues();
return null;
}
};
transactionService.runOnAnotherTransaction(load);
}
private List<IssueDTO> getJiraIssues() {
List<IssueDTO> issues = new ArrayList<IssueDTO>();
try {
Properties properties = loadProperties();
issues = JiraRESTClient.getIssues(properties.getProperty("url"),
properties.getProperty("username"),
properties.getProperty("password"),
JiraRESTClient.PATH_SEARCH,
getJiraLabel(properties.getProperty("label")));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
return issues;
}
private Properties loadProperties() throws FileNotFoundException,
IOException {
String filename = System.getProperty("user.dir")
+ "/../scripts/jira-connector/jira-conn.properties";
Properties properties = new Properties();
properties.load(new FileInputStream(filename));
return properties;
}
private String getJiraLabel(String label) {
return "labels=" + label;
}
private Order givenOrder() {
return transactionService
.runOnAnotherTransaction(new IOnTransaction<Order>() {
@Override
public Order execute() {
return givenValidOrderAlreadyStored();
}
});
}
private Order givenValidOrderAlreadyStored() {
Order order = Order.create();
order.setCode(UUID.randomUUID().toString());
order.setName("Order name " + UUID.randomUUID());
order.setInitDate(new Date());
order.setCalendar(configurationDAO.getConfiguration()
.getDefaultCalendar());
OrderVersion version = setupVersionUsing(scenarioManager, order);
order.useSchedulingDataFor(version);
orderDAO.save(order);
orderDAO.flush();
try {
return orderDAO.find(order.getId());
} catch (InstanceNotFoundException e) {
return null;
}
}
private Order givenOrderWithValidOrderLines() {
return transactionService
.runOnAnotherTransaction(new IOnTransaction<Order>() {
@Override
public Order execute() {
return givenValidOrderWithValidOrderLinesAlreadyStored();
}
});
}
private Order givenValidOrderWithValidOrderLinesAlreadyStored() {
Order order = givenOrder();
jiraOrderElementSynchronizer.syncOrderElementsWithJiraIssues(issues,
order);
order.dontPoseAsTransientObjectAnymore();
orderDAO.saveWithoutValidating(order);
orderDAO.flush();
try {
return orderDAO.find(order.getId());
} catch (InstanceNotFoundException e) {
return null;
}
}
private OrderVersion setupVersionUsing(IScenarioManager scenarioManager,
Order order) {
Scenario current = scenarioManager.getCurrent();
OrderVersion result = OrderVersion.createInitialVersion(current);
order.setVersionForScenario(current, result);
return result;
}
@Test
@Ignore("Only working if you have a JIRA server configured")
public void testSyncJiraTimesheet() {
Order order = givenOrderWithValidOrderLines();
jiraTimesheetSynchronizer
.syncJiraTimesheetWithJiraIssues(issues, order);
assertTrue(order.getWorkReportLines(false).size() > 0);
}
}

View file

@ -40,7 +40,7 @@ import org.junit.runner.RunWith;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.entities.IConfigurationBootstrap;
import org.libreplan.business.common.entities.IPersonalTimesheetsTypeOfWorkHoursBootstrap;
import org.libreplan.business.common.entities.IConfigurationTypeOfWorkHoursBootstrap;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO;
import org.libreplan.business.costcategories.entities.ITypeOfWorkHoursBootstrap;
@ -86,7 +86,7 @@ public class TypeOfWorkHoursServiceTest {
private ITypeOfWorkHoursBootstrap typeOfWorkHoursBootstrap;
@Autowired
private IPersonalTimesheetsTypeOfWorkHoursBootstrap personalTimesheetsTypeOfWorkHoursBootstrap;
private IConfigurationTypeOfWorkHoursBootstrap configurationTypeOfWorkHoursBootstrap;
@Before
public void loadRequiredData() {
@ -96,7 +96,7 @@ public class TypeOfWorkHoursServiceTest {
public Void execute() {
configurationBootstrap.loadRequiredData();
typeOfWorkHoursBootstrap.loadRequiredData();
personalTimesheetsTypeOfWorkHoursBootstrap.loadRequiredData();
configurationTypeOfWorkHoursBootstrap.loadRequiredData();
return null;
}
});

11
pom.xml
View file

@ -722,6 +722,17 @@
</exclusion>
</exclusions>
</dependency>
<!-- jackson provider -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.9</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-xc</artifactId>
<version>1.9.9</version>
</dependency>
<!-- ZK Timeplot -->
<dependency>
<groupId>org.zkoss.zkforge</groupId>

View file

@ -0,0 +1,5 @@
url=http://domain:port/
label_url=http://domain:port/jiralabel/jiralabels.php
username=username
password=password
label=jira-label

View file

@ -0,0 +1,28 @@
<?php
$host="domain:port";
$dbuser="username";
$dbpass="password";
$dbname="jiradb";
$conn = mysql_connect($host, $dbuser, $dbpass)
or die("Connection Failed");
mysql_select_db($dbname, $conn) or die ($dbname . " Database not found. ");
$query = "SELECT DISTINCT label FROM label";
$result = mysql_db_query($dbname, $query) or die("Failed Query " . $query);
$resp = "";
while($row = mysql_fetch_row($result)) {
$label = $row[0];
$label = str_replace("\r\n", "", $label);
$resp .= $label . ",";
}
mysql_close($conn);
$resp = substr_replace($resp ,"",-1);
echo $resp;
?>