/* * 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.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.SuspendNotAllowedException; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zkplus.spring.SpringUtil; 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.Groupbox; import org.zkoss.zul.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; private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer; private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer; private IConnectorDAO connectorDAO; @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); jiraOrderElementSynchronizer = (IJiraOrderElementSynchronizer) SpringUtil.getBean("jiraOrderElementSynchronizer"); jiraTimesheetSynchronizer = (IJiraTimesheetSynchronizer) SpringUtil.getBean("jiraTimesheetSynchronizer"); connectorDAO = (IConnectorDAO) SpringUtil.getBean("connectorDAO"); comp.setAttribute("jiraSynchroniaztionController", this, true); loadComponentsEditWindow(); showOrHideJiraEditWindow(); updateOrderLastSyncInfoScreen(); } public void setOrderController(OrderCRUDController orderController) { this.orderController = orderController; } /** * 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()); return connector != null && 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); updateOrderLastSyncInfoScreen(); 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<>(); 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); } } private boolean isSyncSucceeded(SynchronizationInfo syncInfo) { return syncInfo != null && 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, event -> startSyncWithJira(comboJiraLabel.getValue())); cancelJiraSyncButton = (Button) comp.getFellow("cancelJiraSyncButton"); cancelJiraSyncButton.setLabel(_("Cancel")); cancelJiraSyncButton.addEventListener(Events.ON_CLICK, 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) { //TODO change deprecated method 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) ) { if ( --nRows <= 0 ) { break; } } } return new SimpleListModelExt(data); } public boolean entryMatchesText(String entry, String text) { return entry.toLowerCase().contains(text.toLowerCase()); } } }