Commit 8a9d270a authored by Romain Bioteau's avatar Romain Bioteau Committed by GitHub
Browse files

feat(deploy) Open applications after deploy (#1848)

Closes BST-407
parent 5d057085
......@@ -75,8 +75,15 @@ Import-Package: javax.annotation;version="1.2.0",
javax.inject;version="1.0.0",
org.bonitasoft.engine.api,
org.bonitasoft.engine.api.result,
org.bonitasoft.engine.bpm,
org.bonitasoft.engine.bpm.bar,
org.bonitasoft.engine.business.application,
org.bonitasoft.engine.exception,
org.bonitasoft.engine.identity,
org.bonitasoft.engine.page,
org.bonitasoft.engine.platform,
org.bonitasoft.engine.profile,
org.bonitasoft.engine.search,
org.bonitasoft.engine.session,
org.eclipse.e4.core.di.annotations,
org.eclipse.e4.ui.workbench.renderers.swt,
......
......@@ -92,5 +92,7 @@ appDescriptorUnknownLayoutPage=Application descriptor '%s': Application layout '
appDescriptorUnknownTheme=Application descriptor '%s': Application theme '%s' has not been found.
deploySuccessMsg=All artifacts has been deployed successfully.
validateProcess=Validate before deploy
portalAppName=Portal
youCanOpenApp=You can open an application
artifactCounter=%s artifacts selected to be deployed
pagesAndLayouts=Pages & Layouts
......@@ -17,7 +17,10 @@ package org.bonitasoft.studio.application.handler;
import static org.bonitasoft.studio.ui.wizard.WizardBuilder.newWizard;
import static org.bonitasoft.studio.ui.wizard.WizardPageBuilder.newPage;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
......@@ -26,6 +29,10 @@ import java.util.stream.Collectors;
import javax.inject.Named;
import org.bonitasoft.engine.exception.BonitaHomeNotSetException;
import org.bonitasoft.engine.exception.ServerAPIException;
import org.bonitasoft.engine.exception.UnknownAPITypeException;
import org.bonitasoft.engine.platform.LoginException;
import org.bonitasoft.engine.session.APISession;
import org.bonitasoft.studio.application.ApplicationPlugin;
import org.bonitasoft.studio.application.i18n.Messages;
......@@ -33,17 +40,23 @@ import org.bonitasoft.studio.application.operation.BuildProjectOperation;
import org.bonitasoft.studio.application.operation.DeployProjectOperation;
import org.bonitasoft.studio.application.operation.DeployTenantResourcesOperation;
import org.bonitasoft.studio.application.operation.ValidateProjectOperation;
import org.bonitasoft.studio.application.ui.control.DeploySuccessDialog;
import org.bonitasoft.studio.application.ui.control.DeployedAppContentProvider;
import org.bonitasoft.studio.application.ui.control.RepositoryModelBuilder;
import org.bonitasoft.studio.application.ui.control.SelectArtifactToDeployPage;
import org.bonitasoft.studio.application.ui.control.model.Artifact;
import org.bonitasoft.studio.application.ui.control.model.BuildableArtifact;
import org.bonitasoft.studio.application.ui.control.model.RepositoryModel;
import org.bonitasoft.studio.application.ui.control.model.TenantArtifact;
import org.bonitasoft.studio.common.jface.BonitaErrorDialog;
import org.bonitasoft.studio.common.log.BonitaStudioLog;
import org.bonitasoft.studio.common.repository.Repository;
import org.bonitasoft.studio.common.repository.RepositoryAccessor;
import org.bonitasoft.studio.common.repository.model.DeployOptions;
import org.bonitasoft.studio.configuration.EnvironmentProviderFactory;
import org.bonitasoft.studio.engine.BOSEngineManager;
import org.bonitasoft.studio.engine.operation.GetApiSessionOperation;
import org.bonitasoft.studio.preferences.browser.OpenBrowserOperation;
import org.bonitasoft.studio.ui.dialog.MultiStatusDialog;
import org.bonitasoft.studio.ui.wizard.WizardBuilder;
import org.eclipse.core.runtime.IProgressMonitor;
......@@ -53,7 +66,6 @@ import org.eclipse.core.runtime.Status;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.e4.ui.services.IServiceConstants;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.IWizardContainer;
import org.eclipse.swt.widgets.Shell;
......@@ -181,13 +193,37 @@ public class DeployArtifactsHandler {
multiStatusDialog.setLevel(IStatus.WARNING);
multiStatusDialog.open();
} else {
MessageDialog.openInformation(activeShell, Messages.deployStatus, Messages.deploySuccessMsg);
try {
openSuccessDialog(activeShell, status);
} catch (LoginException | BonitaHomeNotSetException | ServerAPIException | UnknownAPITypeException e) {
BonitaStudioLog.error(e);
new BonitaErrorDialog(activeShell, Messages.deployErrorTitle, e.getMessage(), e).open();
}
}
} else {
StatusManager.getManager().handle(status, StatusManager.SHOW);
}
}
private void openSuccessDialog(Shell activeShell, IStatus status)
throws LoginException, BonitaHomeNotSetException, ServerAPIException, UnknownAPITypeException {
APISession session = BOSEngineManager.getInstance().loginDefaultTenant(Repository.NULL_PROGRESS_MONITOR);
DeployedAppContentProvider contentProvider = new DeployedAppContentProvider(status,
BOSEngineManager.getInstance().getApplicationAPI(session),
BOSEngineManager.getInstance().getProfileAPI(session),
BOSEngineManager.getInstance().getIdentityAPI(session));
if (IDialogConstants.OPEN_ID == DeploySuccessDialog.open(activeShell, contentProvider)) {
try {
new OpenBrowserOperation(contentProvider.getSelectedURL()).execute();
} catch (MalformedURLException | UnsupportedEncodingException | URISyntaxException e) {
BonitaStudioLog.error(e);
}
}
if(session != null) {
BOSEngineManager.getInstance().logoutDefaultTenant(session);
}
}
private IStatus performBuild(RepositoryAccessor repositoryAccessor,
Collection<Artifact> artifactsToDeploy, IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
......
......@@ -129,6 +129,8 @@ public class Messages extends NLS {
public static String appDescriptorUnknownTheme;
public static String deploySuccessMsg;
public static String validateProcess;
public static String portalAppName;
public static String youCanOpenApp;
public static String artifactCounter;
public static String pagesAndLayouts;
......
......@@ -17,8 +17,6 @@ package org.bonitasoft.studio.application.operation;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.util.HashSet;
import java.util.Set;
import org.bonitasoft.engine.api.ApplicationAPI;
import org.bonitasoft.engine.api.result.ExecutionResult;
......@@ -44,10 +42,6 @@ public class DeployProjectOperation implements IRunnableWithStatus {
private MultiStatus status = new MultiStatus(ApplicationPlugin.PLUGIN_ID, -1, null, null);
private IPath archivePath;
private APISession session;
private static final Set<StatusCode> FILTERED_STATUS_CODE = new HashSet<>();
static {
FILTERED_STATUS_CODE.add(StatusCode.LIVING_APP_DEPLOYMENT);
}
public DeployProjectOperation(APISession session, IPath archivePath) {
this.session = session;
......@@ -62,7 +56,6 @@ public class DeployProjectOperation implements IRunnableWithStatus {
ExecutionResult result = applicationAPI
.deployApplication(Files.readAllBytes(archivePath.toFile().toPath()));
result.getAllStatus().stream()
.filter(status -> !FILTERED_STATUS_CODE.contains(status.getCode()))
.filter(status -> !(status.getCode() == StatusCode.PROCESS_DEPLOYMENT_IMPOSSIBLE_UNRESOLVED && status.getContext().get(StatusContext.PROCESS_RESOLUTION_PROBLEM_DESCRIPTION_KEY) == null))
.map(DeployStatusMapper.instance())
.forEach(status::add);
......
......@@ -30,7 +30,7 @@ public class DeployStatusMapper extends EngineStatusMapper {
}
return INSTANCE;
}
@Override
public String localizedMessage(Status status) {
StatusContext context = status.getContext();
......@@ -60,6 +60,8 @@ public class DeployStatusMapper extends EngineStatusMapper {
return String.format(Messages.appDescriptorUnknownTheme,
context.get(StatusContext.LIVING_APPLICATION_TOKEN_KEY),
context.get(StatusContext.LIVING_APPLICATION_INVALID_ELEMENT_NAME));
case LIVING_APP_DEPLOYMENT:
return (String) context.get(StatusContext.LIVING_APPLICATION_TOKEN_KEY);
default:
return super.localizedMessage(status);
}
......
/**
* Copyright (C) 2019 Bonitasoft S.A.
* Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2.0 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.bonitasoft.studio.application.ui.control;
import org.bonitasoft.studio.application.i18n.Messages;
import org.bonitasoft.studio.common.repository.core.ActiveOrganizationProvider;
import org.bonitasoft.studio.ui.widget.ComboWidget;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.PojoProperties;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
public class DeploySuccessDialog extends MessageDialog {
public static int open(Shell parentShell, DeployedAppContentProvider contentProvider) {
return new DeploySuccessDialog(parentShell, Messages.deployStatus, Messages.deploySuccessMsg,
MessageDialog.INFORMATION, contentProvider).open();
}
private DeployedAppContentProvider contentProvider;
private DataBindingContext ctx;
public DeploySuccessDialog(Shell parentShell, String dialogTitle, String dialogMessage, int dialogImageType,
DeployedAppContentProvider contentProvider) {
super(parentShell, dialogTitle, null, dialogMessage, dialogImageType, 1,
new String[] { IDialogConstants.CLOSE_LABEL, IDialogConstants.OPEN_LABEL });
this.contentProvider = contentProvider;
}
@Override
protected Control createCustomArea(Composite parent) {
ctx = new DataBindingContext();
Composite container = new Composite(parent, SWT.NONE);
container.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
container.setLayout(GridLayoutFactory.fillDefaults().create());
new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL)
.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
new ComboWidget.Builder()
.withLabel(Messages.youCanOpenApp)
.labelAbove()
.fill()
.grabHorizontalSpace()
.readOnly()
.withMessage(String.format("%s : %s", org.bonitasoft.studio.actors.i18n.Messages.defaultUser, new ActiveOrganizationProvider().getDefaultUser()))
.withItems(contentProvider.getItems())
.bindTo(PojoProperties.value("selection").observe(contentProvider))
.inContext(ctx)
.createIn(container);
return container;
}
@Override
public boolean close() {
ctx.dispose();
return super.close();
}
@Override
protected void buttonPressed(int buttonId) {
super.buttonPressed(buttonId);
switch (buttonId) {
case 0:
setReturnCode(IDialogConstants.CLOSE_ID);
break;
default:
setReturnCode(IDialogConstants.OPEN_ID);
break;
}
}
}
/**
* Copyright (C) 2019 Bonitasoft S.A.
* Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2.0 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.bonitasoft.studio.application.ui.control;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.bonitasoft.engine.api.ApplicationAPI;
import org.bonitasoft.engine.api.ProfileAPI;
import org.bonitasoft.engine.api.UserAPI;
import org.bonitasoft.engine.api.result.StatusCode;
import org.bonitasoft.engine.business.application.Application;
import org.bonitasoft.engine.business.application.ApplicationSearchDescriptor;
import org.bonitasoft.engine.exception.SearchException;
import org.bonitasoft.engine.identity.UserNotFoundException;
import org.bonitasoft.engine.profile.Profile;
import org.bonitasoft.engine.profile.ProfileCriterion;
import org.bonitasoft.engine.profile.ProfileNotFoundException;
import org.bonitasoft.engine.search.SearchOptionsBuilder;
import org.bonitasoft.studio.application.i18n.Messages;
import org.bonitasoft.studio.common.log.BonitaStudioLog;
import org.bonitasoft.studio.common.repository.Repository;
import org.bonitasoft.studio.common.repository.core.ActiveOrganizationProvider;
import org.bonitasoft.studio.engine.operation.ApplicationURLBuilder;
import org.bonitasoft.studio.engine.operation.PortalURLBuilder;
import org.eclipse.core.runtime.IStatus;
public class DeployedAppContentProvider {
private String selection;
private List<ApplicationItem> applications;
public DeployedAppContentProvider(IStatus status, ApplicationAPI appAPI, ProfileAPI profileAPI,
UserAPI userAPI) {
this.applications = appFromStatus(status, appAPI, profileAPI, userAPI);
this.applications.addAll(portalApplications(profileAPI, userAPI));
this.selection = applications.stream()
.findFirst()
.map(ApplicationItem::toString)
.orElseThrow(() -> new IllegalStateException("No application found."));
}
private Collection<? extends ApplicationItem> portalApplications(ProfileAPI profileAPI, UserAPI userAPI) {
return getDefaultUserProfiles(profileAPI, userAPI).stream()
.map(profile -> createPortalApplication(profile))
.sorted()
.collect(Collectors.toList());
}
private ApplicationItem createPortalApplication(Profile profile) {
ApplicationItem applicationItem = new ApplicationItem();
applicationItem.setName(Messages.portalAppName);
applicationItem.setProfileName(profile.getName());
applicationItem.setProfileId(profile.getId());
try {
applicationItem.setURL(new PortalURLBuilder().withProfile(profile.getId()).toURL(Repository.NULL_PROGRESS_MONITOR));
} catch (MalformedURLException | UnsupportedEncodingException | URISyntaxException e) {
BonitaStudioLog.error(e);
return null;
}
return applicationItem;
}
private List<Profile> getDefaultUserProfiles(ProfileAPI profileAPI, UserAPI userAPI) {
try {
return profileAPI.getProfilesForUser(userAPI.getUserByUserName(getDefaultUsername()).getId(), 0,
Integer.MAX_VALUE, ProfileCriterion.NAME_ASC);
} catch (UserNotFoundException e) {
BonitaStudioLog.error(e);
return Collections.emptyList();
}
}
private String getDefaultUsername() {
return new ActiveOrganizationProvider().getDefaultUser();
}
private List<ApplicationItem> appFromStatus(IStatus status, ApplicationAPI appAPI, ProfileAPI profileAPI,
UserAPI userAPI) {
return Stream.of(status.getChildren())
.filter(s -> Objects.equals(s.getCode(), StatusCode.LIVING_APP_DEPLOYMENT.ordinal()))
.map(IStatus::getMessage)
.map(appToken -> createApplicationItem(appToken, appAPI, profileAPI, userAPI))
.filter(Objects::nonNull)
.sorted()
.collect(Collectors.toList());
}
public String[] getItems() {
List<String> appTokens = applications.stream()
.map(Object::toString)
.collect(Collectors.toList());
return appTokens.toArray(new String[] {});
}
private ApplicationItem createApplicationItem(String appToken, ApplicationAPI appAPI, ProfileAPI profileAPI,
UserAPI userAPI) {
try {
List<Application> apps = appAPI.searchApplications(new SearchOptionsBuilder(0, 1)
.filter(ApplicationSearchDescriptor.TOKEN, appToken)
.done()).getResult();
if (!apps.isEmpty()) {
Application application = apps.get(0);
if (getDefaultUserProfiles(profileAPI, userAPI).stream().map(Profile::getId)
.anyMatch(application.getProfileId()::equals)) {
ApplicationItem applicationItem = new ApplicationItem();
applicationItem.setName(application.getDisplayName());
applicationItem.setProfileId(application.getProfileId());
applicationItem.setProfileName(profileAPI.getProfile(application.getProfileId()).getName());
applicationItem.setURL(new ApplicationURLBuilder(appToken)
.toURL(Repository.NULL_PROGRESS_MONITOR));
return applicationItem;
}
}
} catch (SearchException | ProfileNotFoundException | MalformedURLException | UnsupportedEncodingException
| URISyntaxException e) {
BonitaStudioLog.error(e);
}
return null;
}
public void setSelection(String selection) {
this.selection = selection;
}
public String getSelection() {
return selection;
}
public URL getSelectedURL() throws MalformedURLException, UnsupportedEncodingException, URISyntaxException {
return getAppURL(getSelection());
}
private URL getAppURL(String displayName) {
return applications.stream()
.filter(app -> Objects.equals(displayName, app.toString()))
.findFirst()
.map(ApplicationItem::getUrl)
.orElseThrow(() -> new IllegalStateException(
String.format("Application not found for %s", displayName)));
}
class ApplicationItem implements Comparable<ApplicationItem>{
private String name;
private String profileName;
private long profileId;
private URL url;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getProfileName() {
return profileName;
}
public void setProfileName(String profileName) {
this.profileName = profileName;
}
public long getProfileId() {
return profileId;
}
public void setProfileId(long profileId) {
this.profileId = profileId;
}
public void setURL(URL url) {
this.url = url;
}
public URL getUrl() {
return url;
}
@Override
public String toString() {
return String.format("%s -- %s", getName(), getProfileName());
}
@Override
public int compareTo(ApplicationItem item) {
return toString().compareTo(item.toString());
}
}
}
......@@ -60,7 +60,7 @@ public class CaseDetailURLBuilderTest {
}
/**
* Test method for {@link org.bonitasoft.studio.engine.operation.ApplicationURLBuilder#toURL(org.eclipse.core.runtime.IProgressMonitor)}.
* Test method for {@link org.bonitasoft.studio.engine.operation.ProcessInstantiationFormURLBuilder#toURL(org.eclipse.core.runtime.IProgressMonitor)}.
*/
@Test
public void shouldToURL_RetursAValidURL() throws Exception {
......
......@@ -56,7 +56,7 @@ public class PortalURLBuilderTest {
/**
* Test method for
* {@link org.bonitasoft.studio.engine.operation.ApplicationURLBuilder#toURL(org.eclipse.core.runtime.IProgressMonitor)}
* {@link org.bonitasoft.studio.engine.operation.ProcessInstantiationFormURLBuilder#toURL(org.eclipse.core.runtime.IProgressMonitor)}
* .
*/
@Test
......
......@@ -36,9 +36,9 @@ import org.junit.Test;
* @author Romain Bioteau
*
*/
public class ApplicationURLBuilderTest {
public class ProcessInstantiationFormURLBuilderTest {
private ApplicationURLBuilder applicationURLBuilder;
private ProcessInstantiationFormURLBuilder applicationURLBuilder;
private String loginURL;
/**
......@@ -49,7 +49,7 @@ public class ApplicationURLBuilderTest {
final AbstractProcess process = ProcessFactory.eINSTANCE.createPool();
process.setName("testPool with space /and slash");
process.setVersion("1.0");
applicationURLBuilder = spy(new ApplicationURLBuilder(process, ConfigurationPreferenceConstants.DEFAULT_CONFIGURATION, 12L));
applicationURLBuilder = spy(new ProcessInstantiationFormURLBuilder(process, ConfigurationPreferenceConstants.DEFAULT_CONFIGURATION, 12L));
doReturn("fr").when(applicationURLBuilder).getWebLocale();
doReturn("william.jobs").when(applicationURLBuilder).getDefaultUsername();
doReturn("bpm").when(applicationURLBuilder).getDefaultPassword();
......@@ -58,7 +58,7 @@ public class ApplicationURLBuilderTest {
}
/**
* Test method for {@link org.bonitasoft.studio.engine.operation.ApplicationURLBuilder#toURL(org.eclipse.core.runtime.IProgressMonitor)}.
* Test method for {@link org.bonitasoft.studio.engine.operation.ProcessInstantiationFormURLBuilder#toURL(org.eclipse.core.runtime.IProgressMonitor)}.
*/
@Test
public void shouldToURL_RetursAValidURL() throws Exception {
......@@ -81,7 +81,7 @@ public class ApplicationURLBuilderTest {
}
/**
* Test method for {@link org.bonitasoft.studio.engine.operation.ApplicationURLBuilder#toURL(org.eclipse.core.runtime.IProgressMonitor)}.
* Test method for {@link org.bonitasoft.studio.engine.operation.ProcessInstantiationFormURLBuilder#toURL(org.eclipse.core.runtime.IProgressMonitor)}.
*/
@Test
public void shouldToURL_RetursAValidURLForSpecifConf() throws Exception {
......
</