jira and tim -connector: Refactoring OrderCRUDController and other improvements

- OrderCRUDController refactored
- Save or update for OrderSyncInfo instead of creating new instance for each sync
- Name change for TimImpExpInfo to SynchronizationInfo
- JiraSyncInfo is removed instead SynchronizationInfo is used
- All files which are affected by name changes are modified
- Translations added where applicable
- clean up unused variables etc
This commit is contained in:
miciele Ghiorghis 2013-03-19 17:33:34 +01:00 committed by Manuel Rego Casasnovas
parent cc712d4055
commit 2fc452d1b7
27 changed files with 688 additions and 508 deletions

View file

@ -59,4 +59,24 @@ public interface IOrderSyncInfoDAO extends IGenericDAO<OrderSyncInfo, Long> {
List<OrderSyncInfo> findLastSynchronizedInfosByOrderAndConnectorId(
Order order, String connectorId);
/**
* Searches and returns <code>{@link OrderSyncInfo}</code> for the specified
* <code>key</code> and <code>connectorId</code>
*
* @param key
* the unique key with in connector id
* @param connectorId
* the connector id
*/
OrderSyncInfo findByKeyAndConnectorId(String key, String connectorId);
/**
* Finds the {@link OrderSyncInfo}s for the specified
* <code>connectorId</code>
*
* @param connectorId
* the connectorId
* @return a list of OrderSyncInfo if found and null if not
*/
List<OrderSyncInfo> findByConnectorId(String connectorId);
}

View file

@ -62,4 +62,19 @@ public class OrderSyncInfoDAO extends GenericDAOHibernate<OrderSyncInfo, Long>
return criteria.list();
}
@Override
public OrderSyncInfo findByKeyAndConnectorId(String key, String connectorId) {
Criteria criteria = getSession().createCriteria(OrderSyncInfo.class);
criteria.add(Restrictions.eq("key", key));
criteria.add(Restrictions.eq("connectorId", connectorId));
return (OrderSyncInfo) criteria.uniqueResult();
}
@Override
public List<OrderSyncInfo> findByConnectorId(String connectorId) {
Criteria criteria = getSession().createCriteria(OrderSyncInfo.class);
criteria.add(Restrictions.eq("connectorId", connectorId));
return criteria.list();
}
}

View file

@ -28,8 +28,8 @@ import org.libreplan.business.common.BaseEntity;
/**
* OrderSyncInfo entity. This entity holds order synchronization info. Each time
* that order synchronization is successfully completed, an instance of this
* entity is created and saved to DB to hold the synchronized info. This info is
* then displayed in UI.
* entity is created or updated and saved to DB to hold the synchronized info.
* This info is then displayed in UI.
*
* This entity contains the following fields:
* <ul>
@ -49,10 +49,12 @@ public class OrderSyncInfo extends BaseEntity {
private String connectorId;
private Order order;
public static OrderSyncInfo create(Order order, String connectorId) {
public static OrderSyncInfo create(String key, Order order,
String connectorId) {
Validate.notEmpty(key);
Validate.notNull(order);
Validate.notEmpty(connectorId);
return create(new OrderSyncInfo(order, connectorId));
return create(new OrderSyncInfo(key, order, connectorId));
}
/**
@ -61,8 +63,9 @@ public class OrderSyncInfo extends BaseEntity {
protected OrderSyncInfo() {
}
private OrderSyncInfo(Order order, String connectorId) {
private OrderSyncInfo(String key, Order order, String connectorId) {
this.lastSyncDate = new Date();
this.key = key;
this.order = order;
this.connectorId = connectorId;
}

View file

@ -213,7 +213,7 @@
<comment>Create new table order_sync_info</comment>
<createTable tableName="order_sync_info">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="order_sync_info_pkey"/>
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
@ -288,10 +288,10 @@
<constraints nullable="false" />
</column>
<column name="job_group" type="VARCHAR(255)" >
<constraints nullable="false" primaryKey="true" />
<constraints nullable="false" />
</column>
<column name="job_name" type="VARCHAR(255)" >
<constraints nullable="false" primaryKey="true" />
<constraints nullable="false" />
</column>
<column name="cron_expression" type="VARCHAR(255)" >
<constraints nullable="false" />

View file

@ -48,7 +48,7 @@ public class ExportTimesheetToTimJob extends QuartzJobBean {
try {
exportTimesheetsToTim.exportTimesheets();
LOG.info("Export scuccessful: "
+ exportTimesheetsToTim.getExportProcessInfo()
+ exportTimesheetsToTim.getSynchronizationInfo()
.isSuccessful());
} catch (ConnectorException e) {
LOG.error("Export timesheet to Tim failed", e);

View file

@ -32,20 +32,17 @@ import org.apache.commons.logging.LogFactory;
import org.joda.time.LocalDate;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.common.entities.PredefinedConnectorProperties;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.orders.daos.IOrderDAO;
import org.libreplan.business.orders.daos.IOrderSyncInfoDAO;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.business.resources.daos.IWorkerDAO;
import org.libreplan.business.resources.entities.Worker;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.entities.WorkReportLine;
import org.libreplan.importers.tim.DurationDTO;
import org.libreplan.importers.tim.PersonDTO;
@ -76,12 +73,6 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
@Autowired
private IWorkerDAO workerDAO;
@Autowired
private IWorkReportLineDAO workReportLineDAO;
@Autowired
private IConfigurationDAO configurationDAO;
@Autowired
IOrderSyncInfoDAO orderSyncInfoDAO;
@ -91,10 +82,7 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
@Autowired
private IConnectorDAO connectorDAO;
@Autowired
private IOrderDAO orderDAO;
private TimImpExpInfo timImpExpInfo;
private SynchronizationInfo synchronizationInfo;
@Override
@Transactional(readOnly = true)
@ -108,23 +96,19 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
_("Connection values of Tim connector are invalid"));
}
timImpExpInfo = new TimImpExpInfo(_("Export"));
synchronizationInfo = new SynchronizationInfo(_("Export"));
List<Order> orders = orderDAO.getOrders();
for (Order order : orders) {
OrderSyncInfo orderSyncInfo = getOrderLastSyncInfo(order);
if (orderSyncInfo == null) {
LOG.warn("Order '" + order.getName()
+ "' is not yet synchronized");
timImpExpInfo
.addFailedReason(_(
"Order '{0}' is not yet synchronized",
order.getName()));
} else {
LOG.info("Exporting '" + order.getName() + "'");
exportTimesheets(orderSyncInfo.getKey(),
orderSyncInfo.getOrder(), connector);
}
List<OrderSyncInfo> orderSyncInfos = orderSyncInfoDAO.findByConnectorId(PredefinedConnectors.TIM.getName());
if (orderSyncInfos == null || orderSyncInfos.isEmpty()) {
LOG.warn("No items found in 'OrderSyncInfo' to export to Tim");
synchronizationInfo.addFailedReason(_("No items found in 'OrderSyncInfo' to export to Tim"));
return;
}
for (OrderSyncInfo orderSyncInfo : orderSyncInfos) {
LOG.info("Exporting '" + orderSyncInfo.getOrder().getName() + "'");
exportTimesheets(orderSyncInfo.getKey(), orderSyncInfo.getOrder(),
connector);
}
}
@ -133,10 +117,10 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
public void exportTimesheets(String productCode, Order order)
throws ConnectorException {
if (productCode == null || productCode.isEmpty()) {
throw new RuntimeException("Product code should not be empty");
throw new ConnectorException(_("Product code should not be empty"));
}
if (order == null) {
throw new RuntimeException("Order should not be empty");
throw new ConnectorException(_("Order should not be empty"));
}
Connector connector = getTimConnector();
@ -149,7 +133,7 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
_("Connection values of Tim connector are invalid"));
}
timImpExpInfo = new TimImpExpInfo(_("Export"));
synchronizationInfo = new SynchronizationInfo(_("Export"));
exportTimesheets(productCode, order, connector);
}
@ -187,8 +171,8 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
if (workReportLines == null || workReportLines.isEmpty()) {
LOG.warn("No work reportlines are found for order: '"
+ order.getName() + "'");
timImpExpInfo.addFailedReason(_(
"No work reportlines are found for order: '{0}'",
synchronizationInfo.addFailedReason(_(
"No work reportlines are found for order: \"{0}\"",
order.getName()));
return;
}
@ -205,7 +189,7 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
if (timeRegistrationDTOs.isEmpty()) {
LOG.warn("Unable to crate timeregistration for request");
timImpExpInfo
synchronizationInfo
.addFailedReason(_("Unable to crate time registration for request"));
return;
}
@ -219,15 +203,15 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
if (timeRegistrationResponseDTO == null) {
LOG.error("No response or exception in response");
timImpExpInfo
.addFailedReason("No response or exception in response");
synchronizationInfo
.addFailedReason(_("No response or exception in response"));
return;
}
if (isRefsListEmpty(timeRegistrationResponseDTO.getRefs())) {
LOG.warn("Registration response with empty refs");
timImpExpInfo
.addFailedReason("Registration response with empty refs");
synchronizationInfo
.addFailedReason(_("Registration response with empty refs"));
return;
}
saveSyncInfoOnAnotherTransaction(productCode, order);
@ -262,9 +246,14 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
.runOnAnotherTransaction(new IOnTransaction<Void>() {
@Override
public Void execute() {
OrderSyncInfo orderSyncInfo = OrderSyncInfo.create(
order, PredefinedConnectors.TIM.getName());
orderSyncInfo.setKey(productCode);
OrderSyncInfo orderSyncInfo = orderSyncInfoDAO
.findByKeyAndConnectorId(productCode,
PredefinedConnectors.TIM.getName());
if (orderSyncInfo == null) {
orderSyncInfo = OrderSyncInfo.create(productCode,
order, PredefinedConnectors.TIM.getName());
}
orderSyncInfo.setLastSyncDate(new Date());
orderSyncInfoDAO.save(orderSyncInfo);
return null;
}
@ -288,7 +277,7 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
worker = workerDAO.findByCode(workerCode);
} catch (InstanceNotFoundException e) {
LOG.warn("Worker '" + workerCode + "' not found");
timImpExpInfo.addFailedReason(_("Worker '{0}' not found",
synchronizationInfo.addFailedReason(_("Worker \"{0}\" not found",
workerCode));
return null;
}
@ -334,8 +323,8 @@ public class ExportTimesheetsToTim implements IExportTimesheetsToTim {
}
@Override
public TimImpExpInfo getExportProcessInfo() {
return timImpExpInfo;
public SynchronizationInfo getSynchronizationInfo() {
return synchronizationInfo;
}
}

View file

@ -69,8 +69,8 @@ public interface IExportTimesheetsToTim {
OrderSyncInfo getOrderLastSyncInfo(Order order);
/**
* Returns export process info, success of fail info
* Returns synchronization info, success of fail info
*/
TimImpExpInfo getExportProcessInfo();
SynchronizationInfo getSynchronizationInfo();
}

View file

@ -46,7 +46,7 @@ public interface IImportRosterFromTim {
void importRosters() throws ConnectorException;
/**
* Returns import process info, success of fail info
* Returns synchronization info, success of fail info
*/
TimImpExpInfo getImportProcessInfo();
SynchronizationInfo getSynchronizationInfo();
}

View file

@ -50,8 +50,10 @@ public interface IJiraOrderElementSynchronizer {
* https://jira.atlassian.com/browse/JRA-29409
*
* @return A list of labels
* @throws ConnectorException
* if connector not found
*/
List<String> getAllJiraLabels();
List<String> getAllJiraLabels() throws ConnectorException;
/**
* Get all jira issues based on the specified <code>label</code> parameter
@ -108,6 +110,6 @@ public interface IJiraOrderElementSynchronizer {
/**
* returns synchronization info, success or fail info
*/
JiraSyncInfo getJiraSyncInfo();
SynchronizationInfo getSynchronizationInfo();
}

View file

@ -59,6 +59,6 @@ public interface IJiraTimesheetSynchronizer {
/**
* returns synchronization info, success or fail info
*/
JiraSyncInfo getJiraSyncInfo();
SynchronizationInfo getSynchronizationInfo();
}

View file

@ -40,14 +40,12 @@ import org.libreplan.business.calendars.entities.PredefinedCalendarExceptionType
import org.libreplan.business.calendars.entities.ResourceCalendar;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.common.entities.PredefinedConnectorProperties;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.resources.daos.IResourceDAO;
import org.libreplan.business.resources.daos.IWorkerDAO;
import org.libreplan.business.resources.entities.Worker;
import org.libreplan.business.workingday.EffortDuration;
@ -62,7 +60,6 @@ import org.libreplan.importers.tim.RosterDTO;
import org.libreplan.importers.tim.RosterRequestDTO;
import org.libreplan.importers.tim.RosterResponseDTO;
import org.libreplan.web.calendars.IBaseCalendarModel;
import org.libreplan.web.resources.worker.IWorkerModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
@ -81,18 +78,9 @@ public class ImportRosterFromTim implements IImportRosterFromTim {
private static final Log LOG = LogFactory.getLog(ImportRosterFromTim.class);
@Autowired
private IConfigurationDAO configurationDAO;
@Autowired
private IWorkerDAO workerDAO;
@Autowired
private IResourceDAO resourceDAO;
@Autowired
private IWorkerModel workerModel;
@Autowired
private IConnectorDAO connectorDAO;
@ -106,7 +94,7 @@ public class ImportRosterFromTim implements IImportRosterFromTim {
@Qualifier("subclass")
private IBaseCalendarModel baseCalendarModel;
private TimImpExpInfo timImpExpInfo;
private SynchronizationInfo synchronizationInfo;
/**
* Search criteria for roster exception days in RESPONSE message
@ -168,7 +156,7 @@ public class ImportRosterFromTim implements IImportRosterFromTim {
String[] departmentIdsArray = StringUtils.stripAll(StringUtils.split(
departmentIds, ","));
timImpExpInfo = new TimImpExpInfo(_("Import"));
synchronizationInfo = new SynchronizationInfo(_("Import"));
for (String department : departmentIdsArray) {
LOG.info("Department: " + department);
@ -183,8 +171,9 @@ public class ImportRosterFromTim implements IImportRosterFromTim {
productivityFactor);
} else {
LOG.error("No valid response for department " + department);
timImpExpInfo.addFailedReason(_(
"No valid response for department '{0}'", department));
synchronizationInfo.addFailedReason(_(
"No valid response for department \"{0}\"",
department));
}
}
}
@ -208,7 +197,7 @@ public class ImportRosterFromTim implements IImportRosterFromTim {
updateCalendarException(rosterExceptions);
} else {
LOG.info("No roster-exceptions found in the response");
timImpExpInfo
synchronizationInfo
.addFailedReason(_("No roster-exceptions found in the response"));
}
return null;
@ -237,7 +226,8 @@ public class ImportRosterFromTim implements IImportRosterFromTim {
worker = workerDAO.findUniqueByNif(workerCode);
} catch (InstanceNotFoundException e) {
LOG.warn("Worker '" + workerCode + "' not found");
timImpExpInfo.addFailedReason(_("Worker '{0}' not found",
synchronizationInfo.addFailedReason(_(
"Worker \"{0}\" not found",
workerCode));
}
if (worker != null) {
@ -353,7 +343,7 @@ public class ImportRosterFromTim implements IImportRosterFromTim {
private CalendarExceptionType getCalendarExceptionType(String name) {
if (name == null || name.isEmpty()) {
LOG.error("Exception name should not be empty");
timImpExpInfo
synchronizationInfo
.addFailedReason(_("Exception name should not be empty"));
return null;
}
@ -369,7 +359,7 @@ public class ImportRosterFromTim implements IImportRosterFromTim {
return calendarExceptionTypeDAO.findUniqueByName(nameToSearch);
} catch (InstanceNotFoundException e) {
LOG.error("Calendar exceptionType not found", e);
timImpExpInfo
synchronizationInfo
.addFailedReason(_("Calendar exception day not found"));
}
return null;
@ -465,7 +455,7 @@ public class ImportRosterFromTim implements IImportRosterFromTim {
}
@Override
public TimImpExpInfo getImportProcessInfo() {
return timImpExpInfo;
public SynchronizationInfo getSynchronizationInfo() {
return synchronizationInfo;
}
}

View file

@ -53,7 +53,7 @@ public class ImportRosterFromTimJob extends QuartzJobBean {
try {
importRosterFromTim.importRosters();
LOG.info("Import scuccessful: "
+ importRosterFromTim.getImportProcessInfo().isSuccessful());
+ importRosterFromTim.getSynchronizationInfo().isSuccessful());
} catch (ConnectorException e) {
LOG.error("Import roster from Tim failed", e);
}

View file

@ -72,7 +72,7 @@ import org.springframework.transaction.annotation.Transactional;
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchronizer {
private JiraSyncInfo jiraSyncInfo;
private SynchronizationInfo synchronizationInfo;
@Autowired
private IConnectorDAO connectorDAO;
@ -82,10 +82,10 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
@Override
@Transactional(readOnly = true)
public List<String> getAllJiraLabels() {
public List<String> getAllJiraLabels() throws ConnectorException {
Connector connector = getJiraConnector();
if (connector == null) {
return null;
throw new ConnectorException(_("JIRA connector not found"));
}
String jiraLabels = connector.getPropertiesAsMap().get(
@ -137,7 +137,7 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
@Transactional(readOnly = true)
public void syncOrderElementsWithJiraIssues(List<IssueDTO> issues, Order order) {
jiraSyncInfo = new JiraSyncInfo();
synchronizationInfo = new SynchronizationInfo(_("Synchronization"));
for (IssueDTO issue : issues) {
String code = PredefinedConnectorProperties.JIRA_CODE_PREFIX
@ -147,8 +147,9 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
OrderLine orderLine = syncOrderLine(order, code, name);
if (orderLine == null) {
jiraSyncInfo.addSyncFailedReason("Order-element for '"
+ issue.getKey() + "' issue not found");
synchronizationInfo.addFailedReason(_(
"Order-element for \"{0}\" issue not found",
issue.getKey()));
continue;
}
@ -158,8 +159,9 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
.getTimetracking(), loggedHours);
if (estimatedHours.isZero()) {
jiraSyncInfo.addSyncFailedReason("Estimated time for '"
+ issue.getKey() + "' issue is 0");
synchronizationInfo.addFailedReason(_(
"Estimated time for \"{0}\" issue is 0",
issue.getKey()));
continue;
}
@ -246,15 +248,16 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
WorkLogDTO workLog = issue.getFields().getWorklog();
if (workLog == null) {
jiraSyncInfo.addSyncFailedReason("No worklogs found for '"
+ issue.getKey() + "' issue");
synchronizationInfo.addFailedReason(_(
"No worklogs found for \"{0}\" issue", issue.getKey()));
return;
}
List<WorkLogItemDTO> workLogItems = workLog.getWorklogs();
if (workLogItems.isEmpty()) {
jiraSyncInfo.addSyncFailedReason("No worklog items found for '"
+ issue.getKey() + "' issue");
synchronizationInfo.addFailedReason(_(
"No worklog items found for \"{0}\" issue",
issue.getKey()));
return;
}
@ -363,9 +366,10 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
} 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() + "'");
synchronizationInfo
.addFailedReason(_(
"Duplicate value AdvanceAssignment for order element of \"{0}\"",
orderElement.getCode()));
return;
}
}
@ -422,8 +426,8 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
}
@Override
public JiraSyncInfo getJiraSyncInfo() {
return jiraSyncInfo;
public SynchronizationInfo getSynchronizationInfo() {
return synchronizationInfo;
}
/**
@ -437,9 +441,13 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
@Override
@Transactional
public void saveSyncInfo(String key, Order order) {
OrderSyncInfo orderSyncInfo = OrderSyncInfo.create(order,
PredefinedConnectors.JIRA.getName());
orderSyncInfo.setKey(key);
OrderSyncInfo orderSyncInfo = orderSyncInfoDAO.findByKeyAndConnectorId(
key, PredefinedConnectors.JIRA.getName());
if (orderSyncInfo == null) {
orderSyncInfo = OrderSyncInfo.create(key, order,
PredefinedConnectors.JIRA.getName());
}
orderSyncInfo.setLastSyncDate(new Date());
orderSyncInfoDAO.save(orderSyncInfo);
}

View file

@ -1,61 +0,0 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2013 St. Antoniusziekenhuis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Keeps track the synchronization info.
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class JiraSyncInfo {
private List<String> syncFailedReasons = new ArrayList<String>();
/**
* Add the specified <code>reason</code> to syncFailedReasons list
*
* @param reason
* reason why synchronizition failed
*/
public void addSyncFailedReason(String reason) {
syncFailedReasons.add(reason);
}
/**
* Is synchronization successful
*
* @return
*/
public boolean isSyncSuccessful() {
return syncFailedReasons.isEmpty();
}
/**
* returns reasons why synchronization is failed
*/
public List<String> getSyncFailedReasons() {
return Collections.unmodifiableList(syncFailedReasons);
}
}

View file

@ -44,7 +44,6 @@ 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;
@ -71,7 +70,7 @@ import org.springframework.transaction.annotation.Transactional;
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
private JiraSyncInfo jiraSyncInfo;
private SynchronizationInfo synchronizationInfo;
private List<Worker> workers;
@ -88,9 +87,6 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
@Autowired
private IWorkReportDAO workReportDAO;
@Autowired
private IWorkReportLineDAO workReportLineDAO;
@Autowired
private IWorkReportModel workReportModel;
@ -109,14 +105,14 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
@Override
@Transactional
public void syncJiraTimesheetWithJiraIssues(List<IssueDTO> issues, Order order) throws ConnectorException {
jiraSyncInfo = new JiraSyncInfo();
synchronizationInfo = new SynchronizationInfo(_("Synchronization"));
workReportType = getJiraTimesheetsWorkReportType();
typeOfWorkHours = getTypeOfWorkHours();
workers = getWorkers();
if (workers == null && workers.isEmpty()) {
jiraSyncInfo.addSyncFailedReason("No workers found");
synchronizationInfo.addFailedReason(_("No workers found"));
return;
}
@ -124,13 +120,15 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
.findLastSynchronizedInfoByOrderAndConnectorId(order,
PredefinedConnectors.JIRA.getName());
if (orderSyncInfo == null) {
jiraSyncInfo.addSyncFailedReason("Order '" + order.getName()
+ "' not found. Order probalbly not synchronized");
synchronizationInfo.addFailedReason(_(
"Order \"{0}\" not found. Order probalbly not synchronized",
order.getName()));
return;
}
if (StringUtils.isBlank(orderSyncInfo.getKey())) {
jiraSyncInfo.addSyncFailedReason("Key for Order '"
+ order.getName() + "' is empty");
synchronizationInfo.addFailedReason(_(
"Key for Order \"{0}\" is empty",
order.getName()));
return;
}
@ -141,14 +139,14 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
for (IssueDTO issue : issues) {
WorkLogDTO worklog = issue.getFields().getWorklog();
if (worklog == null) {
jiraSyncInfo.addSyncFailedReason("No worklogs found for '"
+ issue.getKey() + "'");
synchronizationInfo.addFailedReason(_(
"No worklogs found for \"{0}\" key", issue.getKey()));
} else {
List<WorkLogItemDTO> workLogItems = worklog.getWorklogs();
if (workLogItems == null || workLogItems.isEmpty()) {
jiraSyncInfo
.addSyncFailedReason("No worklog items found for '"
+ issue.getKey() + "' issue");
synchronizationInfo.addFailedReason(_(
"No worklog items found for \"{0}\" issue",
issue.getKey()));
} else {
String codeOrderElement = PredefinedConnectorProperties.JIRA_CODE_PREFIX
@ -157,8 +155,8 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
OrderElement orderElement = order.getOrderElement(codeOrderElement);
if (orderElement == null) {
jiraSyncInfo.addSyncFailedReason("Order element("
+ code + ") not found");
synchronizationInfo.addFailedReason(_(
"Order element \"{0}\" not found", code));
} else {
updateOrCreateWorkReportLineAndAddToWorkReport(workReport, orderElement,
workLogItems);
@ -382,13 +380,13 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
return worker;
}
}
jiraSyncInfo.addSyncFailedReason("Worker('" + nif + "') not found");
synchronizationInfo.addFailedReason(_("Worker \"{0}\" not found", nif));
return null;
}
@Override
public JiraSyncInfo getJiraSyncInfo() {
return jiraSyncInfo;
public SynchronizationInfo getSynchronizationInfo() {
return synchronizationInfo;
}
}

View file

@ -24,14 +24,15 @@ import java.util.Collections;
import java.util.List;
/**
* Keeps track the success/failure of Tim's import and/or export process
* Keeps track the success/failure of synchronization process
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class TimImpExpInfo {
public class SynchronizationInfo {
/**
* action, import or export process
* The action, a unique key for example synchronization, import or export
* etc action
*/
private String action;
@ -40,7 +41,7 @@ public class TimImpExpInfo {
*/
private List<String> failedReasons = new ArrayList<String>();
public TimImpExpInfo(String action) {
public SynchronizationInfo(String action) {
this.action = action;
}
@ -55,14 +56,14 @@ public class TimImpExpInfo {
* Adds the specified <code>reason</code> to <code>failedReasons<code> list
*
* @param reason
* reason why import/export failed
* reason why synchronization is failed
*/
public void addFailedReason(String reason) {
failedReasons.add(reason);
}
/**
* Is import or export succeeded
* Is synchronization succeeded
*
* @return true if <code>failedReasons</code> is empty
*/
@ -71,7 +72,7 @@ public class TimImpExpInfo {
}
/**
* returns reasons why import or export failed
* returns reasons why synchronization is failed
*/
public List<String> getFailedReasons() {
return Collections.unmodifiableList(failedReasons);

View file

@ -26,7 +26,7 @@ import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
import org.libreplan.business.common.entities.PredefinedConnectorProperties;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.importers.TimImpExpInfo;
import org.libreplan.importers.SynchronizationInfo;
/**
* Contract for {@link JobSchedulerModel}.
@ -64,9 +64,9 @@ public interface IJobSchedulerModel {
throws ConnectorException;
/**
* Returns import/export info. Failure or success info
* Returns synchronization info. Failure or success info
*/
TimImpExpInfo getImportExportInfo();
SynchronizationInfo getSynchronizationInfo();
/**
* Prepares for create a new {@link JobSchedulerConfiguration}.

View file

@ -36,7 +36,7 @@ import org.libreplan.business.common.entities.JobClassNameEnum;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.importers.TimImpExpInfo;
import org.libreplan.importers.SynchronizationInfo;
import org.quartz.CronExpression;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
@ -212,11 +212,11 @@ public class JobSchedulerController extends
private void shwoImpExpInfo() {
Map<String, Object> args = new HashMap<String, Object>();
TimImpExpInfo timImpExpInfo = jobSchedulerModel.getImportExportInfo();
args.put("action", timImpExpInfo.getAction());
args.put("showSuccess", timImpExpInfo.isSuccessful());
SynchronizationInfo synchronizationInfo = jobSchedulerModel.getSynchronizationInfo();
args.put("action", synchronizationInfo.getAction());
args.put("showSuccess", synchronizationInfo.isSuccessful());
args.put("failedReasons",
new SimpleListModel(timImpExpInfo.getFailedReasons()));
new SimpleListModel(synchronizationInfo.getFailedReasons()));
Window timImpExpInfoWindow = (Window) Executions.createComponents(
"/orders/_timImpExpInfo.zul", null, args);

View file

@ -32,7 +32,7 @@ import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.importers.IExportTimesheetsToTim;
import org.libreplan.importers.IImportRosterFromTim;
import org.libreplan.importers.ISchedulerManager;
import org.libreplan.importers.TimImpExpInfo;
import org.libreplan.importers.SynchronizationInfo;
import org.libreplan.web.common.concurrentdetection.OnConcurrentModification;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
@ -69,7 +69,7 @@ public class JobSchedulerModel implements IJobSchedulerModel {
@Autowired
private IExportTimesheetsToTim exportTimesheetsToTim;
private TimImpExpInfo timImpExpInfo;
private SynchronizationInfo synchronizationInfo;
@Override
@Transactional(readOnly = true)
@ -89,19 +89,19 @@ public class JobSchedulerModel implements IJobSchedulerModel {
String name = jobSchedulerConfiguration.getJobClassName().getName();
if (name.equals(JobClassNameEnum.IMPORT_ROSTER_FROM_TIM_JOB.getName())) {
importRosterFromTim.importRosters();
timImpExpInfo = importRosterFromTim.getImportProcessInfo();
synchronizationInfo = importRosterFromTim.getSynchronizationInfo();
return;
}
if (name.equals(JobClassNameEnum.EXPORT_TIMESHEET_TO_TIM_JOB.getName())) {
exportTimesheetsToTim.exportTimesheets();
timImpExpInfo = exportTimesheetsToTim.getExportProcessInfo();
synchronizationInfo = exportTimesheetsToTim.getSynchronizationInfo();
return;
}
}
@Override
public TimImpExpInfo getImportExportInfo() {
return timImpExpInfo;
public SynchronizationInfo getSynchronizationInfo() {
return synchronizationInfo;
}
@Override

View file

@ -0,0 +1,359 @@
/*
* 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.web.orders;
import static org.libreplan.web.I18nHelper._;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.ws.rs.WebApplicationException;
import org.apache.commons.logging.LogFactory;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.importers.IJiraOrderElementSynchronizer;
import org.libreplan.importers.IJiraTimesheetSynchronizer;
import org.libreplan.importers.SynchronizationInfo;
import org.libreplan.importers.jira.IssueDTO;
import org.libreplan.web.common.IMessagesForUser;
import org.libreplan.web.common.Level;
import org.libreplan.web.common.MessagesForUser;
import org.libreplan.web.common.Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.SuspendNotAllowedException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.Popup;
import org.zkoss.zul.SimpleListModel;
import org.zkoss.zul.Tab;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.api.Groupbox;
import org.zkoss.zul.api.Window;
/**
* Controller for JIRA synchronization
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class JiraSynchronizationController extends GenericForwardComposer {
private static final org.apache.commons.logging.Log LOG = LogFactory
.getLog(JiraSynchronizationController.class);
private OrderCRUDController orderController;
private Window editWindow;
private Groupbox jiraGroupBox;
private Popup jirasyncPopup;
private Button startJiraSyncButton, cancelJiraSyncButton,
syncWithJiraButton;
private Textbox txtImportedLabel, txtLastSyncDate;
private Combobox comboJiraLabel;
private IMessagesForUser messagesForUser;
private Component messagesContainer;
@Autowired
private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer;
@Autowired
private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer;
@Autowired
private IConnectorDAO connectorDAO;
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
comp.setVariable("jiraSynchroniaztionController", this, true);
loadComponentsEditWindow();
showOrHideJiraEditWindow();
updateOrderLastSyncInfoScreen();
}
/**
* Returns current {@link Order}
*/
private Order getOrder() {
return orderController.getOrder();
}
private void loadComponentsEditWindow() {
txtLastSyncDate = (Textbox) editWindow
.getFellowIfAny("txtLastSyncDate");
txtImportedLabel = (Textbox) editWindow
.getFellowIfAny("txtImportedLabel");
jiraGroupBox = (Groupbox) editWindow.getFellowIfAny("jiraGroupBox");
syncWithJiraButton = (Button) editWindow
.getFellow("syncWithJiraButton");
messagesForUser = new MessagesForUser(messagesContainer);
}
/**
* Show or hide <code>JiraEditWindow</code> based on JIRA
* {@link Connector#isActivated()}
*/
private void showOrHideJiraEditWindow() {
jiraGroupBox.setVisible(isJiraActivated());
}
/**
* Updates the UI text last synchronized date and the text imported label
*/
private void updateOrderLastSyncInfoScreen() {
OrderSyncInfo orderSyncInfo = jiraOrderElementSynchronizer
.getOrderLastSyncInfo(getOrder());
if (orderSyncInfo != null) {
txtLastSyncDate.setValue(Util.formatDateTime(orderSyncInfo
.getLastSyncDate()));
txtImportedLabel.setValue(orderSyncInfo.getKey());
}
}
/**
* Returns true if jira is Activated. Used to show/hide Jira edit window
*/
public boolean isJiraActivated() {
Connector connector = connectorDAO
.findUniqueByName(PredefinedConnectors.JIRA.getName());
if (connector == null) {
return false;
}
return connector.isActivated();
}
/**
* Synchronize with Jira
*/
public void syncWithJira() {
try {
List<String> items = jiraOrderElementSynchronizer
.getAllJiraLabels();
if (!(txtImportedLabel.getText()).isEmpty()) {
startSyncWithJira(txtImportedLabel.getText());
return;
}
setupJiraSyncPopup(editWindow, new SimpleListModelExt(items));
jirasyncPopup.open(syncWithJiraButton, "before_start");
} catch (ConnectorException e) {
messagesForUser.showMessage(Level.ERROR,
_("Failed: {0}", e.getMessage()));
} catch (WebApplicationException e) {
LOG.info(e);
messagesForUser.showMessage(Level.ERROR,
_("Cannot connect to JIRA server"));
}
}
/**
* Start synchronize with jira for the specified <code>label</code>
*
* @param label
* the jira label
*/
public void startSyncWithJira(String label) {
try {
Order order = getOrder();
List<IssueDTO> issues = jiraOrderElementSynchronizer
.getJiraIssues(label);
if (issues == null || issues.isEmpty()) {
messagesForUser.showMessage(Level.ERROR,
_("No JIRA issues to import"));
return;
}
order.setCodeAutogenerated(false);
jiraOrderElementSynchronizer.syncOrderElementsWithJiraIssues(
issues, order);
orderController.saveAndContinue(false);
jiraOrderElementSynchronizer.saveSyncInfo(label, order);
if (jirasyncPopup != null) {
jirasyncPopup.close();
}
jiraTimesheetSynchronizer.syncJiraTimesheetWithJiraIssues(issues,
order);
showSyncInfo();
// Reload order info in all tabs
Tab previousTab = orderController.getCurrentTab();
orderController.initEdit(order);
orderController.selectTab(previousTab.getId());
} catch (ConnectorException e) {
messagesForUser.showMessage(Level.ERROR,
_("Failed: {0}", e.getMessage()));
} catch (WebApplicationException e) {
LOG.info(e);
messagesForUser.showMessage(Level.ERROR,
_("Cannot connect to JIRA server"));
}
}
/**
* Shows the success or failure info of synchronization
*/
private void showSyncInfo() {
Map<String, Object> args = new HashMap<String, Object>();
SynchronizationInfo syncOrderElementInfo = jiraOrderElementSynchronizer
.getSynchronizationInfo();
boolean succeeded = isSyncSucceeded(syncOrderElementInfo);
args.put("syncOrderElementSuccess", succeeded);
if (syncOrderElementInfo != null) {
args.put("syncOrderElementFailedReasons", new SimpleListModel(
syncOrderElementInfo.getFailedReasons()));
}
SynchronizationInfo jiraSyncInfoTimesheet = jiraTimesheetSynchronizer
.getSynchronizationInfo();
succeeded = isSyncSucceeded(jiraSyncInfoTimesheet);
args.put("syncTimesheetSuccess", succeeded);
if (jiraSyncInfoTimesheet != null) {
args.put("syncTimesheetFailedReasons", new SimpleListModel(
jiraSyncInfoTimesheet.getFailedReasons()));
}
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 boolean isSyncSucceeded(SynchronizationInfo syncInfo) {
if (syncInfo == null) {
return false;
}
return syncInfo.isSuccessful();
}
/**
* Setups the pop-up components
*
* @param comp
* the compenent(editWidnow)
* @param model
* labels list model to render the combobox
* <code>comboJiraLabel</code>
*/
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());
}
}
}

View file

@ -27,22 +27,16 @@ 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.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.externalcompanies.entities.DeadlineCommunication;
import org.libreplan.business.externalcompanies.entities.DeliverDateComparator;
@ -53,14 +47,9 @@ import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.Order.SchedulingMode;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderStatusEnum;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.business.planner.entities.PositionConstraintType;
import org.libreplan.business.templates.entities.OrderTemplate;
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.IMessagesForUser;
import org.libreplan.web.common.Level;
@ -88,7 +77,6 @@ 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;
@ -106,9 +94,7 @@ 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;
@ -198,6 +184,8 @@ public class OrderCRUDController extends GenericForwardComposer {
private ProjectDetailsController projectDetailsController;
private JiraSynchronizationController jiraSynchronizationController;
private TimSynchronizationController timSynchronizationController;
@Autowired
@ -207,16 +195,6 @@ public class OrderCRUDController extends GenericForwardComposer {
private EndDatesRenderer endDatesRenderer = new EndDatesRenderer();
@Autowired
private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer;
@Autowired
private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer;
@Autowired
private IConnectorDAO connectorDAO;
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
@ -693,7 +671,7 @@ public class OrderCRUDController extends GenericForwardComposer {
saveAndContinue(true);
}
private void saveAndContinue(boolean showSaveMessage) {
protected void saveAndContinue(boolean showSaveMessage) {
Order order = orderModel.getOrder();
final boolean isNewObject = order.isNewObject();
@ -802,11 +780,11 @@ public class OrderCRUDController extends GenericForwardComposer {
}
}
private Tab getCurrentTab() {
protected Tab getCurrentTab() {
return selectedTab;
}
private void selectTab(String str) {
protected void selectTab(String str) {
Tab tab = (Tab) editWindow.getFellowIfAny(str);
if (tab != null) {
tab.setSelected(true);
@ -1042,6 +1020,7 @@ public class OrderCRUDController extends GenericForwardComposer {
initializeCustomerComponent();
reloadOrderDetailsTab();
orderDatesHandler.chooseCurrentSchedulingMode();
setupJiraSynchronizationController();
setupTimSynchronizationController();
}
@ -1706,196 +1685,29 @@ public class OrderCRUDController extends GenericForwardComposer {
return Util.getCurrencySymbol();
}
private Popup jirasyncPopup;
private Button startJiraSyncButton, cancelJiraSyncButton, syncWithJiraButton;
private Combobox comboJiraLabel;
public boolean isJiraActivated() {
Connector connector = connectorDAO
.findUniqueByName(PredefinedConnectors.JIRA.getName());
if (connector == null) {
return false;
/**
* Setup the connector, JiraSynchronization controller
*/
public void setupJiraSynchronizationController() {
if (jiraSynchronizationController == null) {
jiraSynchronizationController = new JiraSynchronizationController();
}
return connector.isActivated();
}
public boolean isJiraDeactivated() {
return !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);
if (issues == null || issues.isEmpty()) {
messagesForUser.showMessage(Level.ERROR,
_("No JIRA issues to import"));
return;
}
order.setCodeAutogenerated(false);
jiraOrderElementSynchronizer.syncOrderElementsWithJiraIssues(
issues, order);
saveAndContinue(false);
jiraOrderElementSynchronizer.saveSyncInfo(label, order);
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"));
} catch (ConnectorException e) {
messagesForUser.showMessage(Level.ERROR,
_("Failed: {0}", e.getMessage()));
}
}
public OrderSyncInfo getOrderLastSyncInfo() {
return jiraOrderElementSynchronizer.getOrderLastSyncInfo(getOrder());
}
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) {
jiraSynchronizationController.doAfterCompose(editWindow);
} catch (Exception 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}.
* Setup the connector, TimSynchronization controller
*/
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 void setupTimSynchronizationController() {
if (timSynchronizationController == null) {
timSynchronizationController = new TimSynchronizationController();
}
try {
timSynchronizationController.doAfterCompose(self
.getFellow("editOrderElement"));
timSynchronizationController.doAfterCompose(editWindow);
} catch (Exception e) {
throw new RuntimeException(e);
}

View file

@ -29,9 +29,10 @@ import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.importers.IExportTimesheetsToTim;
import org.libreplan.importers.TimImpExpInfo;
import org.libreplan.importers.SynchronizationInfo;
import org.libreplan.web.common.IMessagesForUser;
import org.libreplan.web.common.Level;
import org.libreplan.web.common.MessagesForUser;
@ -44,6 +45,7 @@ import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Label;
import org.zkoss.zul.SimpleListModel;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.api.Groupbox;
import org.zkoss.zul.api.Window;
/**
@ -58,7 +60,12 @@ public class TimSynchronizationController extends GenericForwardComposer {
private OrderCRUDController orderController;
private Window editWindow;
private Groupbox timGroupBox;
private Textbox txtProductCode;
private Label labelProductCode, labelLastSyncDate;
@Autowired
@ -75,19 +82,67 @@ public class TimSynchronizationController extends GenericForwardComposer {
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
comp.setVariable("timSynchronizationController", this, true);
txtProductCode = (Textbox) comp.getFellowIfAny("txtProductCode");
labelLastSyncDate = (Label) comp.getFellowIfAny("labelLastSyncDate");
labelProductCode = (Label) comp.getFellowIfAny("labelProductCode");
messagesForUser = new MessagesForUser(messagesContainer);
loadComponentsEditWindow(comp);
showOrHideTimEditWindow();
updateOrderLastSyncInfoScreen();
}
/**
* Returns current {@link Order}
*/
private Order getOrder() {
return orderController.getOrder();
}
private void loadComponentsEditWindow(Component comp) {
txtProductCode = (Textbox) comp.getFellowIfAny("txtProductCode");
labelLastSyncDate = (Label) comp
.getFellowIfAny("labelLastSyncDate");
labelProductCode = (Label) comp
.getFellowIfAny("labelProductCode");
timGroupBox = (Groupbox) comp.getFellowIfAny("timGroupBox");
messagesForUser = new MessagesForUser(messagesContainer);
}
/**
* Show or hide <code>TimEditWindow</code> based on Tim
* {@link Connector#isActivated()}
*/
private void showOrHideTimEditWindow() {
timGroupBox.setVisible(isTimActivated());
}
/**
* Updates the UI text last synchronized date and the text product code
*/
private void updateOrderLastSyncInfoScreen() {
OrderSyncInfo orderSyncInfo = exportTimesheetsToTim
.getOrderLastSyncInfo(getOrder());
if (orderSyncInfo != null) {
labelLastSyncDate.setValue(Util.formatDateTime(orderSyncInfo
.getLastSyncDate()));
labelProductCode.setValue("(" + orderSyncInfo.getKey() + ")");
}
}
/**
* Returns true if Tim is Activated. Used to show/hide Tim edit window
*/
public boolean isTimActivated() {
Connector connector = connectorDAO
.findUniqueByName(PredefinedConnectors.TIM.getName());
if (connector == null) {
return false;
}
return connector.isActivated();
}
public void startExportToTim() {
LOG.info("startExportToTim(): " + orderController.getOrder().getName());
txtProductCode.setConstraint("no empty:" + _("cannot be empty"));
try {
exportTimesheetsToTim.exportTimesheets(txtProductCode.getValue(),
orderController.getOrder());
getOrder());
updateOrderLastSyncInfoScreen();
@ -99,37 +154,15 @@ public class TimSynchronizationController extends GenericForwardComposer {
}
}
private void updateOrderLastSyncInfoScreen() {
OrderSyncInfo orderSyncInfo = exportTimesheetsToTim
.getOrderLastSyncInfo(orderController.getOrder());
if (orderSyncInfo != null) {
if (labelLastSyncDate != null) {
labelLastSyncDate.setValue(Util.formatDateTime(orderSyncInfo
.getLastSyncDate()));
}
if (labelProductCode != null) {
labelProductCode.setValue("(" + orderSyncInfo.getKey() + ")");
}
}
}
public boolean isTimActivated() {
Connector connector = connectorDAO
.findUniqueByName(PredefinedConnectors.TIM.getName());
if (connector == null) {
return false;
}
return connector.isActivated();
}
private void shwoImpExpInfo() {
Map<String, Object> args = new HashMap<String, Object>();
TimImpExpInfo timImpExpInfo = exportTimesheetsToTim.getExportProcessInfo();
args.put("action", timImpExpInfo.getAction());
args.put("showSuccess", timImpExpInfo.isSuccessful());
SynchronizationInfo synchronizationInfo = exportTimesheetsToTim.getSynchronizationInfo();
args.put("action", synchronizationInfo.getAction());
args.put("showSuccess", synchronizationInfo.isSuccessful());
args.put("failedReasons",
new SimpleListModel(timImpExpInfo.getFailedReasons()));
new SimpleListModel(synchronizationInfo.getFailedReasons()));
Window timImpExpInfoWindow = (Window) Executions.createComponents(
"/orders/_timImpExpInfo.zul", null, args);

View file

@ -32,6 +32,7 @@
macroURI="/orders/components/_listOrderElementMaterials.zul"?>
<?component name="listOrderElementTaskQualityForms" inline="true" macroURI="_listOrderElementTaskQualityForms.zul"?>
<?component name="listOrderElementAuthorizations" inline="true" macroURI="_listOrderElementAuthorizations.zul"?>
<?component name="jiraOrderElementSynchronizer" inline="true" macroURI="components/_jiraOrderElementSync.zul"?>
<?component name="timOrderTimesheetSynchronizer" inline="true" macroURI="components/_timOrderTimesheetSync.zul"?>
<window id="${arg.top_id}">
@ -282,34 +283,8 @@
</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 value="@{controller.orderLastSyncInfo.lastSyncDate}" width="130px" disabled="true"/>
<textbox id="txtImportedLabel" value="@{controller.orderLastSyncInfo.key}"
width="220px" disabled="true"/>
<button label="${i18n:_('Sync with JIRA')}" id="syncWithJiraButton" disabled="@{controller.jiraDeactivated}"
onClick="controller.syncWithJira()" />
</hbox>
</row>
</rows>
</grid>
</hbox>
</groupbox>
<timOrderTimesheetSynchronizer id="timOrderTimesheetSynchronizer" />
<jiraOrderElementSynchronizer id="jiraOrderElementSynchronizer" fulfill="tabGeneralData.onSelect"/>
<timOrderTimesheetSynchronizer id="timOrderTimesheetSynchronizer" fulfill="tabGeneralData.onSelect" />
</tabpanel>
<tabpanel>
<listOrderElementHours id="orderElementHours" fulfill="tabAssignedHours.onSelect"/>
@ -335,15 +310,4 @@
</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" constraint="no empty:${i18n:_('cannot be empty')}"/>
<separator/>
<hbox>
<button id="startJiraSyncButton" />
<button id="cancelJiraSyncButton" />
</hbox>
</groupbox>
</popup>
</window>

View file

@ -19,32 +19,22 @@
<window id="winJiraSyncInfo" title="${i18n:_('LibrePlan: Jira synchronization info')}"
width="500px" border="normal" mode="modal">
<div>
<div height="250px" style="overflow:auto">
<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}"/>
<label value="${i18n:_('Synchronization order elements with JIRA issues was successful')}" sclass="remarked" if="${arg.syncOrderElementSuccess}"/>
<vbox if="${not arg.syncOrderElementSuccess}">
<label value="${i18n:_('Synchronization order elements with JIRA issues was not completed for the following reasons:')}" />
<listbox model="${arg.syncOrderElementFailedReasons}"/>
</vbox>
</vbox>
<separator spacing="20px"/>
<vbox>
<label value="${i18n:_('Synchronization of timesheets was successful')}" sclass="remarked" if="${arg.showSyncTimesheetSuccess}"/>
<vbox if="${not arg.showSyncTimesheetSuccess}">
<separator spacing="10px"/>
<vbox if="${arg.syncOrderElementSuccess}">
<label value="${i18n:_('Synchronization of timesheets was successful')}" sclass="remarked" if="${arg.syncTimesheetSuccess}"/>
<vbox if="${not arg.syncTimesheetSuccess}">
<label value="${i18n:_('Synchronization of timesheets is not completed for the following reasons:')}" />
<listbox model="${arg.jiraSyncTimesheetFailedReasons}"/>
<listbox model="${arg.syncTimesheetFailedReasons}"/>
</vbox>
</vbox>
</div>
<button id="closeBtn" label="${i18n:_('Close')}" onClick="winJiraSyncInfo.detach()"
sclass="cancel-button global-action"/>

View file

@ -0,0 +1,58 @@
<!--
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/>.
-->
<groupbox id="jiraGroupBox"
style="margin-top: 5px" closable="false"
visible="@{jiraSynchroniaztionController.jiraActivated}">
<caption label="${i18n:_('Jira sync 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="txtLastSyncDate" value="" width="130px" disabled="true"/>
<textbox id="txtImportedLabel" value=""
width="220px" disabled="true"/>
<button label="${i18n:_('Sync with JIRA')}" id="syncWithJiraButton"
onClick="jiraSynchroniaztionController.syncWithJira()" />
</hbox>
</row>
</rows>
</grid>
</hbox>
<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" constraint="no empty:${i18n:_('cannot be empty')}"/>
<separator/>
<hbox>
<button id="startJiraSyncButton" />
<button id="cancelJiraSyncButton" />
</hbox>
</groupbox>
</popup>
</groupbox>

View file

@ -17,8 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<groupbox id="${arg.id}"
apply="org.libreplan.web.orders.TimSynchronizationController"
<groupbox id="timGroupBox"
style="margin-top: 5px" closable="false"
visible="@{timSynchronizationController.timActivated}">
<caption label="${i18n:_('Tim sync information')}" />

View file

@ -163,7 +163,7 @@ public class ExportTimesheetsToTimTest {
throws ConnectorException {
Order order = givenOrder();
exportTimesheetsToTim.exportTimesheets("5160", order);
boolean result = exportTimesheetsToTim.getExportProcessInfo()
boolean result = exportTimesheetsToTim.getSynchronizationInfo()
.isSuccessful();
if (!result) {
fail("Export timesheets to tim failed");
@ -171,14 +171,14 @@ public class ExportTimesheetsToTimTest {
assertTrue(result);
}
@Test(expected = RuntimeException.class)
@Test(expected = ConnectorException.class)
public void testExportTimesheetsToTimWithInvalidCode()
throws ConnectorException {
Order order = givenOrder();
exportTimesheetsToTim.exportTimesheets("", order);
}
@Test(expected = RuntimeException.class)
@Test(expected = ConnectorException.class)
public void testExportTimesheetsToTimWithOrderNull()
throws ConnectorException {
exportTimesheetsToTim.exportTimesheets("5160", null);