Merge branch 'master' into filtering-improvements
FEA: ItEr77S15FilteringEnhancements
This commit is contained in:
commit
35e1644cee
59 changed files with 3234 additions and 35 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,6 +163,11 @@ public class OrderLineGroup extends OrderElement implements
|
|||
return getThis().isUpdatedFromTimesheets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJiraIssue() {
|
||||
return getThis().isJiraIssue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static OrderLineGroup create() {
|
||||
|
|
|
|||
|
|
@ -625,4 +625,9 @@ public abstract class OrderElementTemplate extends BaseEntity implements
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJiraIssue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,11 @@ public class OrderLineGroupTemplate extends OrderElementTemplate implements
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJiraIssue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static OrderLineGroupTemplate createNew() {
|
||||
|
|
|
|||
|
|
@ -62,4 +62,6 @@ public interface ITreeNode<T extends ITreeNode<T>> {
|
|||
|
||||
boolean isUpdatedFromTimesheets();
|
||||
|
||||
boolean isJiraIssue();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
167
libreplan-webapp/src/main/java/org/libreplan/importers/JiraRESTClient.java
Executable file
167
libreplan-webapp/src/main/java/org/libreplan/importers/JiraRESTClient.java
Executable 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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
81
libreplan-webapp/src/main/java/org/libreplan/importers/jira/IssueDTO.java
Executable file
81
libreplan-webapp/src/main/java/org/libreplan/importers/jira/IssueDTO.java
Executable 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -156,4 +156,6 @@ public interface IOrderModel extends IIntegrationEntityModel {
|
|||
|
||||
User getUser();
|
||||
|
||||
boolean isJiraActivated();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>() {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
51
libreplan-webapp/src/main/webapp/orders/_jiraSyncInfo.zul
Normal file
51
libreplan-webapp/src/main/webapp/orders/_jiraSyncInfo.zul
Normal 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>
|
||||
|
|
@ -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')}" />
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
11
pom.xml
|
|
@ -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>
|
||||
|
|
|
|||
5
scripts/jira-connector/jira-conn.properties
Normal file
5
scripts/jira-connector/jira-conn.properties
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
url=http://domain:port/
|
||||
label_url=http://domain:port/jiralabel/jiralabels.php
|
||||
username=username
|
||||
password=password
|
||||
label=jira-label
|
||||
28
scripts/jira-connector/jiralabels.php
Executable file
28
scripts/jira-connector/jiralabels.php
Executable 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;
|
||||
?>
|
||||
Loading…
Add table
Reference in a new issue