From e342908ac20bcf791dc04f36ffb77d17515a7d68 Mon Sep 17 00:00:00 2001 From: Costantino Perciante Date: Mon, 21 Mar 2016 15:29:48 +0000 Subject: [PATCH] Buttons to delete and re-send the welcome email added and working git-svn-id: http://svn.research-infrastructures.eu/public/d4science/gcube/trunk/portlets/admin/create-users@125883 82a268e6-3cf1-43bd-a215-b396298e98cf --- pom.xml | 2 +- .../createusers/client/CreateUsersPanel.java | 34 +--- .../createusers/client/ui/AddUserForm.java | 27 ++- .../createusers/client/ui/AddUserForm.ui.xml | 8 +- .../client/ui/PopupPanelExtended.java | 9 - .../client/ui/RegisteredUsersTable.java | 182 ++++++++++++++---- .../createusers/server/CreateUsersImpl.java | 66 +++++-- src/main/webapp/images/loader.gif | Bin 0 -> 9511 bytes 8 files changed, 223 insertions(+), 105 deletions(-) delete mode 100644 src/main/java/org/gcube/portlets/admin/createusers/client/ui/PopupPanelExtended.java create mode 100644 src/main/webapp/images/loader.gif diff --git a/pom.xml b/pom.xml index 317ed07..138eda7 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ 2.7.0 - 6.2.5 + 6.0.6 1.7 1.7 diff --git a/src/main/java/org/gcube/portlets/admin/createusers/client/CreateUsersPanel.java b/src/main/java/org/gcube/portlets/admin/createusers/client/CreateUsersPanel.java index 1e68a95..8de69e4 100644 --- a/src/main/java/org/gcube/portlets/admin/createusers/client/CreateUsersPanel.java +++ b/src/main/java/org/gcube/portlets/admin/createusers/client/CreateUsersPanel.java @@ -11,11 +11,8 @@ import com.github.gwtbootstrap.client.ui.TabPane; import com.github.gwtbootstrap.client.ui.TabPanel; import com.github.gwtbootstrap.client.ui.constants.AlertType; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.VerticalPanel; @@ -36,9 +33,6 @@ public class CreateUsersPanel extends Composite { // registered users subpanel private TabPane registeredUsersSubPanel = new TabPane("Already Created Users") ; - - // TODO remove - private TabPane sendMailTabPane = new TabPane("Send Mail"); // Create a remote service proxy to talk to the server-side user manager service. private final HandleUsersServiceAsync userServices = GWT.create(HandleUsersService.class); @@ -62,32 +56,6 @@ public class CreateUsersPanel extends Composite { LoadingText loader = new LoadingText(); loader.setVisible(true); registeredUsersSubPanel.add(loader); - - // TODO remove - Button sendMail = new Button("Test send email"); - sendMail.addClickHandler(new ClickHandler() { - - @Override - public void onClick(ClickEvent event) { - GWT.log("send email requested"); - userServices.sendEmailToUser("costantino.perciante@isti.cnr.it", new AsyncCallback() { - - @Override - public void onSuccess(Void result) { - // TODO Auto-generated method stub - GWT.log("ok"); - } - - @Override - public void onFailure(Throwable caught) { - // TODO Auto-generated method stub - GWT.log("not ok"); - } - }); - } - }); - sendMailTabPane.add(sendMail); - navTabs.add(sendMailTabPane); // add stuff to the main panel navTabs.add(addUserSubPanel); @@ -115,7 +83,7 @@ public class CreateUsersPanel extends Composite { } GWT.log("List of registered users received!"); - registeredUsersTable = new RegisteredUsersTable(result, eventBus); + registeredUsersTable = new RegisteredUsersTable(result, eventBus, userServices); registeredUsersSubPanel.clear(); registeredUsersSubPanel.add(registeredUsersTable); } diff --git a/src/main/java/org/gcube/portlets/admin/createusers/client/ui/AddUserForm.java b/src/main/java/org/gcube/portlets/admin/createusers/client/ui/AddUserForm.java index 3b38c5c..d5734c0 100644 --- a/src/main/java/org/gcube/portlets/admin/createusers/client/ui/AddUserForm.java +++ b/src/main/java/org/gcube/portlets/admin/createusers/client/ui/AddUserForm.java @@ -9,6 +9,7 @@ import com.github.gwtbootstrap.client.ui.AlertBlock; import com.github.gwtbootstrap.client.ui.Button; import com.github.gwtbootstrap.client.ui.CheckBox; import com.github.gwtbootstrap.client.ui.Form; +import com.github.gwtbootstrap.client.ui.Image; import com.github.gwtbootstrap.client.ui.TextBox; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; @@ -32,6 +33,11 @@ public class AddUserForm extends Composite{ private static AddUserFormUiBinder uiBinder = GWT .create(AddUserFormUiBinder.class); + /** + * Path of the image to be shown during loading + */ + public static final String imagePath = GWT.getModuleBaseURL() + "../images/loader.gif"; + interface AddUserFormUiBinder extends UiBinder { } @@ -44,8 +50,8 @@ public class AddUserForm extends Composite{ @UiField CheckBox sendMailCheckbox; - // @UiField - // RadioButton maleCheckbox; + @UiField + Image performingRequest; @UiField TextBox emailTextbox; @@ -84,9 +90,12 @@ public class AddUserForm extends Composite{ this.registrationService = userServices; this.eventBus = eventBus; this.parent = parent; + + // set loader url + performingRequest.setUrl(imagePath); } - + @Override protected void onAttach() { super.onAttach(); @@ -141,6 +150,12 @@ public class AddUserForm extends Composite{ ); }else{ + + // show loading image + performingRequest.setVisible(true); + + // disable add button + submit.setEnabled(false); // remote service invocation registrationService.register( @@ -198,6 +213,12 @@ public class AddUserForm extends Composite{ } private void showAlertBlockThenHide(final AlertBlock alert, String msg, int hideAfterMs){ + + // hide loading image + performingRequest.setVisible(false); + + // enable button again + submit.setEnabled(true); // set text alert.setText(msg); diff --git a/src/main/java/org/gcube/portlets/admin/createusers/client/ui/AddUserForm.ui.xml b/src/main/java/org/gcube/portlets/admin/createusers/client/ui/AddUserForm.ui.xml index 6d60305..a64e045 100644 --- a/src/main/java/org/gcube/portlets/admin/createusers/client/ui/AddUserForm.ui.xml +++ b/src/main/java/org/gcube/portlets/admin/createusers/client/ui/AddUserForm.ui.xml @@ -75,10 +75,10 @@ b:id="surname" title="User's surname" ui:field="surnameTextbox" /> - Company: + Institution/Organization: - +
+ + diff --git a/src/main/java/org/gcube/portlets/admin/createusers/client/ui/PopupPanelExtended.java b/src/main/java/org/gcube/portlets/admin/createusers/client/ui/PopupPanelExtended.java deleted file mode 100644 index 0970533..0000000 --- a/src/main/java/org/gcube/portlets/admin/createusers/client/ui/PopupPanelExtended.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.gcube.portlets.admin.createusers.client.ui; - -import com.google.gwt.user.client.ui.PopupPanel; - -public class PopupPanelExtended extends PopupPanel { - - - -} diff --git a/src/main/java/org/gcube/portlets/admin/createusers/client/ui/RegisteredUsersTable.java b/src/main/java/org/gcube/portlets/admin/createusers/client/ui/RegisteredUsersTable.java index db2ebc8..fa210e8 100644 --- a/src/main/java/org/gcube/portlets/admin/createusers/client/ui/RegisteredUsersTable.java +++ b/src/main/java/org/gcube/portlets/admin/createusers/client/ui/RegisteredUsersTable.java @@ -3,24 +3,27 @@ import java.util.Comparator; import java.util.Date; import java.util.List; +import org.gcube.portlets.admin.createusers.client.HandleUsersServiceAsync; import org.gcube.portlets.admin.createusers.client.event.AddUserEvent; import org.gcube.portlets.admin.createusers.client.event.AddUserEventHandler; import org.gcube.portlets.admin.createusers.shared.VreUserBean; -import com.github.gwtbootstrap.client.ui.Button; import com.github.gwtbootstrap.client.ui.CellTable; +import com.google.gwt.cell.client.ButtonCell; import com.google.gwt.cell.client.Cell; import com.google.gwt.cell.client.TextCell; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.EventTarget; +import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.view.client.CellPreviewEvent; -import com.google.gwt.view.client.CellPreviewEvent.Handler; import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.Range; @@ -36,7 +39,7 @@ public class RegisteredUsersTable extends Composite { private CellTable table = new CellTable(1, tableRes); private final HandlerManager eventBus; - public RegisteredUsersTable(List registeredUsers, HandlerManager eventBus) { + public RegisteredUsersTable(List registeredUsers, HandlerManager eventBus, final HandleUsersServiceAsync userServices) { super(); initWidget(table); @@ -182,41 +185,136 @@ public class RegisteredUsersTable extends Composite { table.addColumnSortHandler(registrationDateColHandler); table.getColumnSortList().push(registrationDate); - // add row click handler - table.addCellPreviewHandler(new Handler() { + // delete option + Column deleteUser = new Column(new ButtonCell()) { @Override - public void onCellPreview(CellPreviewEvent event) { - boolean isClick = "click".equals(event.getNativeEvent().getType()); - if(isClick){ - - int rowIndex = event.getIndex(); - PopupPanel drop = new PopupPanel(); - drop.setGlassEnabled(true); - - // add buttons and events - VreUserBean rowBean = dataProvider.getList().get(rowIndex); - - Button deleteUserButton = new Button("Delete User"); - Button sendWelcomeButton = new Button("Send Email"); - - - if(!rowBean.isPasswordChanged()) - deleteUserButton.setEnabled(false); - - // add to drop - drop.add(deleteUserButton); - drop.add(sendWelcomeButton); - - // position and show - int left = table.getRowElement(rowIndex).getAbsoluteLeft(); - int top = table.getRowElement(rowIndex).getAbsoluteTop(); - drop.setPopupPosition(left, top); - drop.show(); - + public String getValue(VreUserBean object) { + return object.isPasswordChanged() ? "True" : "False"; // useless + } + + @Override + public void render(Cell.Context context, VreUserBean value, SafeHtmlBuilder sb){ + + if(value == null) + return; + + if(!value.isPasswordChanged()) + sb.appendHtmlConstant(""); + else + sb.appendHtmlConstant(""); + + } + + @Override + public void onBrowserEvent(Cell.Context context, final Element parent, final VreUserBean user, NativeEvent event) { + event.preventDefault(); + + if(!"click".equals(event.getType())) + return; + + EventTarget eventTarget = event.getEventTarget(); + + if(parent.getFirstChildElement().isOrHasChild(Element.as(eventTarget))){ + + // get the button and disable it + parent.getFirstChildElement().setPropertyBoolean("disabled", true); + + userServices.deleteInvitedUser(user.getEmail(), new AsyncCallback() { + + @Override + public void onSuccess(Boolean result) { + + // delete this row too + if(result){ + + dataProvider.getList().remove(user); + table.setVisibleRange(new Range(0, dataProvider.getList().size())); + table.setRowCount(dataProvider.getList().size(), true); + dataProvider.refresh(); + + Window.alert("Deleted user with email " + user.getEmail()); + + }else + Window.alert("Unable to delete this user, sorry!"); + + // enable the button again + parent.getFirstChildElement().setPropertyBoolean("disabled", false); + + } + + @Override + public void onFailure(Throwable caught) { + + Window.alert("Unable to delete this user, sorry!"); + + // enable the button again + parent.getFirstChildElement().setPropertyBoolean("disabled", false); + + } + }); + } + }; + }; + + // send email option + Column sendWelcomeMessage = new Column(new ButtonCell()) { + + @Override + public String getValue(VreUserBean object) { + return "Send Welcome"; // useless + } + + @Override + public void render(Cell.Context context, VreUserBean value, SafeHtmlBuilder sb){ + + if(value == null) + return; + + sb.appendHtmlConstant(""); + + } + + @Override + public void onBrowserEvent(Cell.Context context, final Element parent, final VreUserBean user, NativeEvent event) { + event.preventDefault(); + + if(!"click".equals(event.getType())) + return; + + EventTarget eventTarget = event.getEventTarget(); + + if(parent.getFirstChildElement().isOrHasChild(Element.as(eventTarget))){ + + // get the button and disable it + parent.getFirstChildElement().setPropertyBoolean("disabled", true); + + userServices.sendEmailToUser(user.getEmail(), new AsyncCallback() { + + @Override + public void onSuccess(Void result) { + + Window.alert("Welcome message sent to " + user.getEmail()); + + // get the button and enable it + parent.getFirstChildElement().setPropertyBoolean("disabled", false); + + } + + @Override + public void onFailure(Throwable caught) { + + Window.alert("Unable to send the welcome message to " + user.getEmail()); + + // get the button and enable it + parent.getFirstChildElement().setPropertyBoolean("disabled", false); + + } + }); } } - }); + }; + // add columns SafeHtmlBuilder builder = new SafeHtmlBuilder(); @@ -249,7 +347,16 @@ public class RegisteredUsersTable extends Composite { builder.appendEscaped("Registration date"); builder.appendHtmlConstant(""); table.addColumn(registrationDate, builder.toSafeHtml()); - + builder = new SafeHtmlBuilder(); + builder.appendHtmlConstant(""); + builder.appendEscaped("Delete"); + builder.appendHtmlConstant(""); + table.addColumn(deleteUser, builder.toSafeHtml()); + builder = new SafeHtmlBuilder(); + builder.appendHtmlConstant(""); + builder.appendEscaped("Send Welcome"); + builder.appendHtmlConstant(""); + table.addColumn(sendWelcomeMessage, builder.toSafeHtml()); } /** @@ -306,7 +413,6 @@ public class RegisteredUsersTable extends Composite { C getValue(VreUserBean user); } - /** * get a column * diff --git a/src/main/java/org/gcube/portlets/admin/createusers/server/CreateUsersImpl.java b/src/main/java/org/gcube/portlets/admin/createusers/server/CreateUsersImpl.java index 0fca4b6..8b7f3e3 100644 --- a/src/main/java/org/gcube/portlets/admin/createusers/server/CreateUsersImpl.java +++ b/src/main/java/org/gcube/portlets/admin/createusers/server/CreateUsersImpl.java @@ -15,7 +15,6 @@ import org.gcube.application.framework.core.session.ASLSession; import org.gcube.application.framework.core.session.SessionManager; import org.gcube.common.homelibrary.home.HomeLibrary; import org.gcube.common.portal.PortalContext; -import org.gcube.portal.custom.communitymanager.OrganizationsUtil; import org.gcube.portal.custom.scopemanager.scopehelper.ScopeHelper; import org.gcube.portlets.admin.createusers.client.HandleUsersService; import org.gcube.portlets.admin.createusers.shared.VreUserBean; @@ -34,6 +33,10 @@ import com.liferay.portal.kernel.dao.jdbc.DataAccess; import com.liferay.portal.kernel.exception.PortalException; import com.liferay.portal.kernel.exception.SystemException; import com.liferay.portal.kernel.mail.MailMessage; +import com.liferay.portal.kernel.util.GetterUtil; +import com.liferay.portal.kernel.util.PropsUtil; +import com.liferay.portal.model.Company; +import com.liferay.portal.service.CompanyLocalServiceUtil; import com.liferay.portal.service.UserLocalServiceUtil; import com.liferay.portal.util.PortalUtil; @@ -60,6 +63,8 @@ public class CreateUsersImpl extends RemoteServiceServlet implements HandleUsers private static final String FIELD_INSTITUTION = "institution_organization"; private static final String FIELD_REGISTRATION_DATE = "registration_date"; private static final String FIELD_VRE = "vre"; + + public static final String DEFAULT_COMPANY_WEB_ID = "liferay.com"; @Override public void init(){ @@ -180,18 +185,27 @@ public class CreateUsersImpl extends RemoteServiceServlet implements HandleUsers @Override public boolean deleteInvitedUser(String email) { - try{ + // if in dev mode return some samples + if (!isWithinPortal()) { - Connection con = DataAccess.getConnection(); - boolean deletedLiferay = deleteUserFromLiferay(email); - boolean deletedTable = deleteUserFromTable(email, con); - return deletedLiferay && deletedTable; + logger.debug("In dev mode."); + return false; - }catch(SQLException e){ - logger.debug("Error while trying to delete user with email = " + email, e); + }else{ + + try{ + + Connection con = DataAccess.getConnection(); + boolean deletedLiferay = deleteUserFromLiferay(email); + boolean deletedTable = deleteUserFromTable(email, con); + return deletedLiferay && deletedTable; + + }catch(SQLException e){ + logger.debug("Error while trying to delete user with email = " + email, e); + } + + return false; } - - return false; } @Override @@ -208,7 +222,7 @@ public class CreateUsersImpl extends RemoteServiceServlet implements HandleUsers InternetAddress from = new InternetAddress(emailSender); LiferayUserManager userManager = new LiferayUserManager(); - String portalUrl = PortalUtil.getPortalURL(OrganizationsUtil.getCompany().getVirtualHostname(), 443, true); + String portalUrl = PortalUtil.getPortalURL(getCompany().getVirtualHost(), 443, true); String username = userManager.getFullNameFromEmail(email); MailMessage mailMessage = new MailMessage(); @@ -216,14 +230,11 @@ public class CreateUsersImpl extends RemoteServiceServlet implements HandleUsers mailMessage.setTo(to); String body = "

Dear " + username + ",
" + "
" + - "Welcome! You recently created an account at http:// " + portalUrl + ". Your password is not sent by email for security purposes.
" + + "Welcome! You recently created an account at " + portalUrl + ". Your password is not sent by email for security purposes.
" + "
" + "Sincerely,
" + gatewayName + "
" + emailSender + "
" + portalUrl; - String subject = "http://" + portalUrl + ": Your New Account was created successfully!"; - - logger.debug("Body is " + body); - logger.debug("Subject is " + subject); + String subject = portalUrl + ": Your New Account was created successfully!"; mailMessage.setBody(body); mailMessage.setSubject(subject); @@ -365,7 +376,7 @@ public class CreateUsersImpl extends RemoteServiceServlet implements HandleUsers if (!isWithinPortal()) { logger.debug("In dev mode."); - toReturn.add(new VreUserBean("Dylan", "Dog", "ISTI-CNR", "dylan.dog@gmail.com", false, System.currentTimeMillis(), true)); + toReturn.add(new VreUserBean("Dylan", "Dog", "ISTI-CNR", "dylan.dog@gmail.com", true, System.currentTimeMillis(), true)); toReturn.add(new VreUserBean("Costantino", "Perciante", "ISTI-CNR", "costantino8@gmail.com", false, System.currentTimeMillis(), true)); return toReturn; @@ -451,7 +462,7 @@ public class CreateUsersImpl extends RemoteServiceServlet implements HandleUsers try{ - logger.debug("Going to delete user with email " + email); + logger.debug("Going to delete user with email " + email + " from the table of registered users"); String remove = "DELETE FROM " + REGISTERED_USERS_TABLE + " WHERE " + FIELD_EMAIL + "= ?"; PreparedStatement statementDelete = con.prepareStatement(remove); @@ -518,4 +529,23 @@ public class CreateUsersImpl extends RemoteServiceServlet implements HandleUsers } return toReturn; } + + public static Company getCompany() throws PortalException, SystemException { + return CompanyLocalServiceUtil.getCompanyByWebId(getDefaultCompanyWebId()); + } + /** + * + * @return the default company web-id (e.g. iMarine.eu) + */ + public static String getDefaultCompanyWebId() { + String defaultWebId = ""; + try { + defaultWebId = GetterUtil.getString(PropsUtil.get("company.default.web.id")); + } + catch (NullPointerException e) { + logger.error("Cound not find property company.default.web.id in portal.ext file returning default web id: " + DEFAULT_COMPANY_WEB_ID); + return DEFAULT_COMPANY_WEB_ID; + } + return defaultWebId; + } } diff --git a/src/main/webapp/images/loader.gif b/src/main/webapp/images/loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..e661c2f40033b3596139df81bc6d45c5369e5d86 GIT binary patch literal 9511 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 RJ|U=*dUUl4k+b&H{{h?hf|dXP literal 0 HcmV?d00001