From eeca542074f7b12c6ab9ad2d56f4e5e4beed78a6 Mon Sep 17 00:00:00 2001 From: Romain Bioteau Date: Thu, 25 Feb 2021 14:33:30 +0100 Subject: [PATCH] feat(cloud): add organizationUnitId input (#9) * adapt all definition to have an organizationUnitId input for cloud authentication * update definition version * improve error management --- pom.xml | 14 ++-- .../uipath/UIPathAddToQueueConnector.java | 4 +- .../connector/uipath/UIPathConnector.java | 71 +++++++++++++------ .../uipath/UIPathGetJobConnector.java | 2 +- .../uipath/UIPathStartJobsConnector.java | 34 +++++++-- .../connector/uipath/model/StartInfo.java | 4 ++ .../connector/uipath/model/Strategy.java | 2 +- .../uipath-add-queueItem.def | 2 + src/main/resources-filtered/uipath-getjob.def | 10 ++- .../resources-filtered/uipath-startjob.def | 18 ++++- .../resources/uipath-add-queueItem.properties | 4 +- src/main/resources/uipath-getjob.properties | 6 +- src/main/resources/uipath-startjob.properties | 11 ++- .../connector/uipath/UIPathConnectorTest.java | 3 +- 14 files changed, 140 insertions(+), 45 deletions(-) diff --git a/pom.xml b/pom.xml index c1949f4..2693cac 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.bonitasoft.connectors bonita-connector-uipath - 2.1.2-SNAPSHOT + 2.2.0-SNAPSHOT Bonita UIPath Connector UIPath Connector for Bonita @@ -39,21 +39,21 @@ uipath-add-queueItem - 2.0.0 + 2.1.0 ${uipath-add-queueItem.def.id}-impl ${project.version} org.bonitasoft.engine.connector.uipath.UIPathAddToQueueConnector uipath-getjob - 2.0.0 + 2.1.0 ${uipath-getjob.def.id}-impl ${project.version} org.bonitasoft.engine.connector.uipath.UIPathGetJobConnector uipath-startjob - 2.0.0 + 2.1.0 ${uipath-startjob.def.id}-impl ${project.version} org.bonitasoft.engine.connector.uipath.UIPathStartJobsConnector @@ -82,7 +82,7 @@ ${java.version} 3.1.1 2.1.1 - 2.4.16 + 3.0.7 3.0.0-M4 1.6.8 3.2.0 @@ -219,8 +219,8 @@ org.codehaus.groovy - groovy-all - ${groovy-all.version} + groovy + ${groovy.version} diff --git a/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathAddToQueueConnector.java b/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathAddToQueueConnector.java index 69b9750..bb71bda 100644 --- a/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathAddToQueueConnector.java +++ b/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathAddToQueueConnector.java @@ -150,7 +150,9 @@ Optional getDeferDate() { QueueItem addToQueue(String token, AddToQueueRequest request) throws IOException, ConnectorException { Response response = getService().addQueueItem(createAuthenticationHeaders(token), request).execute(); if (!response.isSuccessful()) { - throw new ConnectorException(response.errorBody().string()); + throw new ConnectorException(String.format("Failed to add item to queue: %s -%s", + response.code(), + getErrorMessage(response))); } return response.body(); } diff --git a/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathConnector.java b/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathConnector.java index 4dd532f..b4370f9 100644 --- a/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathConnector.java +++ b/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathConnector.java @@ -15,7 +15,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.Interceptor; import okhttp3.OkHttpClient; +import okhttp3.Request; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Response; import retrofit2.Retrofit; @@ -25,27 +27,35 @@ public abstract class UIPathConnector extends AbstractConnector { private static final Logger LOGGER = LoggerFactory.getLogger(UIPathConnector.class.getName()); - private static final String CLOUD_ORCHESTRATOR_BASE_URL = "https://platform.uipath.com"; - private static final String HEADER_TENANT_NAME = "X-UIPATH-TenantName"; - private static final String HEADER_AUTHORIZATION_NAME = "Authorization"; static final String CLOUD = "cloud"; - // classic parameters static final String URL = "url"; static final String USER = "user"; static final String PASSWORD = "password"; static final String TENANT = "tenant"; - // cloud parameters static final String ACCOUNT_LOGICAL_NAME = "accountLogicalName"; static final String TENANT_LOGICAL_NAME = "tenantLogicalName"; static final String USER_KEY = "userKey"; static final String CLIENT_ID = "clientId"; + static final String ORGANIZATION_UNIT_ID = "organizationUnitId"; + + private static final String CONTENT_TYPE = "Content-Type"; + private static final String APPLICATION_JSON = "application/json"; + + private static final String CLOUD_ORCHESTRATOR_BASE_URL = "https://cloud.uipath.com"; + private static final String TENANT_NAME_HEADER = "X-UIPATH-TenantName"; + private static final String X_UIPATH_ORGANIZATION_UNIT_ID_HEADER = "X-UIPATH-OrganizationUnitId"; + private static final String AUTHORIZATION_NAME_HEADER = "Authorization"; protected UIPathService service; protected ObjectMapper mapper = new ObjectMapper(); + private static String appendTraillingSlash(String url) { + return url.endsWith("/") ? url : url + "/"; + } + @Override public void validateInputParameters() throws ConnectorValidationException { checkCloudInput(); @@ -54,6 +64,7 @@ public void validateInputParameters() throws ConnectorValidationException { checkMandatoryStringInput(TENANT_LOGICAL_NAME); checkMandatoryStringInput(USER_KEY); checkMandatoryStringInput(CLIENT_ID); + checkMandatoryStringInput(ORGANIZATION_UNIT_ID); } else { checkMandatoryStringInput(URL); checkMandatoryStringInput(TENANT); @@ -106,8 +117,8 @@ String authenticate() throws ConnectorException { try { if (isCloud()) { Map headers = new HashMap<>(); - headers.put("Content-Type", "application/json"); - headers.put(HEADER_TENANT_NAME, getTenantLogicalName()); + headers.put(CONTENT_TYPE, APPLICATION_JSON); + headers.put(TENANT_NAME_HEADER, getTenantLogicalName()); CloudAuthentication cloudAuthentication = new CloudAuthentication("refresh_token", getClientId(), getUserKey()); response = service.authenticateInCloud(headers, cloudAuthentication).execute(); @@ -121,44 +132,58 @@ String authenticate() throws ConnectorException { e); } if (!response.isSuccessful()) { - try { - throw new ConnectorException(response.errorBody().string()); - } catch (IOException e) { - throw new ConnectorException("Failed to read response body.", e); - } + throw new ConnectorException(String.format("Authentication failed: %s - %s", + response.code(), + getErrorMessage(response))); } return isCloud() ? response.body().get("access_token") : response.body().get("result"); } + protected String getErrorMessage(Response response) { + try { + return response.errorBody().string(); + } catch (IOException e) { + return null; + } + } + protected Map createAuthenticationHeaders(String token) { Map headers = new HashMap<>(); - headers.put(HEADER_AUTHORIZATION_NAME, buildTokenHeader(token)); + headers.put(AUTHORIZATION_NAME_HEADER, buildTokenHeader(token)); if (isCloud()) { - headers.put(HEADER_TENANT_NAME, getTenantLogicalName()); + headers.put(TENANT_NAME_HEADER, getTenantLogicalName()); } return headers; } protected UIPathService createService() { if (service == null) { - OkHttpClient client = null; + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); + Interceptor jsonHeaderInterceptor = chain -> { + Request.Builder requestBuilder = chain.request().newBuilder(); + requestBuilder.header(CONTENT_TYPE, APPLICATION_JSON); + if (isCloud()) { + requestBuilder.header(X_UIPATH_ORGANIZATION_UNIT_ID_HEADER, getOrganizationUnitId()); + } + return chain.proceed(requestBuilder.build()); + }; + clientBuilder.addInterceptor(jsonHeaderInterceptor); if (LOGGER.isDebugEnabled()) { - HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); - interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); - client = new OkHttpClient.Builder().addInterceptor(interceptor).build(); + HttpLoggingInterceptor loggerInterceptor = new HttpLoggingInterceptor(); + loggerInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + clientBuilder.addInterceptor(loggerInterceptor); } - + OkHttpClient client = clientBuilder.build(); Builder retrofitBuilder = new Retrofit.Builder() + .client(client) .addConverterFactory(new WrappedAttributeConverter(mapper)) .addConverterFactory(JacksonConverterFactory.create()) .baseUrl(getUrl()); - if (client != null) { retrofitBuilder.client(client); } - service = retrofitBuilder.build().create(UIPathService.class); } return service; @@ -174,8 +199,8 @@ protected Map toMap(Object inputParameter) { return result; } - private static String appendTraillingSlash(String url) { - return url.endsWith("/") ? url : url + "/"; + String getOrganizationUnitId() { + return (String) getInputParameter(ORGANIZATION_UNIT_ID); } String getTenant() { diff --git a/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathGetJobConnector.java b/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathGetJobConnector.java index 81900f9..359aa9b 100644 --- a/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathGetJobConnector.java +++ b/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathGetJobConnector.java @@ -61,7 +61,7 @@ String getJobId() { Job job(long id, String token) throws IOException, ConnectorException { Response response = getService().job(createAuthenticationHeaders(token), id).execute(); if (!response.isSuccessful()) { - throw new ConnectorException(response.errorBody().string()); + throw new ConnectorException(String.format("Failed to retrieve Job with id='%s': %s - %s:", id, response.code(), getErrorMessage(response)) ); } return response.body(); } diff --git a/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathStartJobsConnector.java b/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathStartJobsConnector.java index 2267b71..e7f5d54 100644 --- a/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathStartJobsConnector.java +++ b/src/main/java/org/bonitasoft/engine/connector/uipath/UIPathStartJobsConnector.java @@ -51,6 +51,8 @@ public class UIPathStartJobsConnector extends UIPathConnector { static final String STRATEGY = "strategy"; static final String INPUT_ARGS = "inputArguments"; static final String STARTED_JOBS_OUTPUT = "startedJobs"; + static final String RUNTIME_TYPE = "runtimeType"; + static final String SOURCE = "source"; @Override public void validateInputParameters() throws ConnectorValidationException { @@ -61,7 +63,7 @@ public void validateInputParameters() throws ConnectorValidationException { checkArgsInput(); } - void checkJobsCountIfJobsCountStrategy() throws ConnectorValidationException { + void checkRobotsIfSpecific() throws ConnectorValidationException { Optional strategy = getStrategy(); if (strategy.filter(Strategy.SPECIFIC.toString()::equals).isPresent()) { Optional> robots = getRobots(); @@ -71,7 +73,7 @@ void checkJobsCountIfJobsCountStrategy() throws ConnectorValidationException { } } - void checkRobotsIfSpecific() throws ConnectorValidationException { + void checkJobsCountIfJobsCountStrategy() throws ConnectorValidationException { Optional strategy = getStrategy(); if (strategy.filter(Strategy.JOBS_COUNT.toString()::equals).isPresent()) { Optional jobsCount = getJobsCount(); @@ -102,6 +104,18 @@ Optional getStrategy() { return Optional.ofNullable((String) getInputParameter(STRATEGY)); } + Optional getRuntimeType() { + String runtimeType = (String) getInputParameter(RUNTIME_TYPE); + if(runtimeType == null || runtimeType.isEmpty()) { + return Optional.empty(); + } + return Optional.ofNullable(runtimeType); + } + + Optional getSource() { + return Optional.ofNullable((String) getInputParameter(SOURCE)); + } + Optional> getInputArguments() { Object inputParameter = getInputParameter(INPUT_ARGS); if (inputParameter instanceof List) { @@ -146,11 +160,14 @@ List startJobs(String token, Release release, List robotIds) .setSource(Source.MANUAL.toString()) .setReleaseKey(release.getKey()); + getRuntimeType().ifPresent(startInfo::setRuntimeType); getStrategy().ifPresent(startInfo::setStrategy); + if (Objects.equals(startInfo.getStrategy(), Strategy.SPECIFIC.toString())) { startInfo.setRobotIds(robotIds); } - if (Objects.equals(startInfo.getStrategy(), Strategy.JOBS_COUNT.toString())) { + if (Objects.equals(startInfo.getStrategy(), Strategy.JOBS_COUNT.toString()) + || Objects.equals(startInfo.getStrategy(), Strategy.MODERN_JOBS_COUNT.toString())) { getJobsCount().ifPresent(startInfo::setJobsCount); } try { @@ -170,7 +187,8 @@ List startJobs(String token, Release release, List robotIds) if (LOGGER.isErrorEnabled()) { LOGGER.error(response.toString()); } - throw new ConnectorException("Failed to start job: " + response.message()); + throw new ConnectorException( + String.format("Failed to start job: %s - %s", response.code(), getErrorMessage(response))); } return response.body(); } @@ -222,7 +240,9 @@ List releases(String token) throws ConnectorException { throw new ConnectorException("Failed to retrieve releases.", e); } if (!response.isSuccessful()) { - throw new ConnectorException("Failed to retrieve releases: " + response.message()); + throw new ConnectorException(String.format("Failed to retrieve releases: %s - %s", + response.code(), + getErrorMessage(response))); } return response.body(); } @@ -235,7 +255,9 @@ List robots(String token) throws ConnectorException { throw new ConnectorException("Failed to retrieve robots.", e); } if (!response.isSuccessful()) { - throw new ConnectorException("Failed to retrieve robots: " + response.message()); + throw new ConnectorException(String.format("Failed to retrieve robots: %s - %s", + response.code(), + getErrorMessage(response))); } return response.body(); } diff --git a/src/main/java/org/bonitasoft/engine/connector/uipath/model/StartInfo.java b/src/main/java/org/bonitasoft/engine/connector/uipath/model/StartInfo.java index b6aff79..d349938 100644 --- a/src/main/java/org/bonitasoft/engine/connector/uipath/model/StartInfo.java +++ b/src/main/java/org/bonitasoft/engine/connector/uipath/model/StartInfo.java @@ -44,5 +44,9 @@ public class StartInfo { @JsonProperty("InputArguments") @JsonInclude(Include.NON_EMPTY) private String args; + @JsonProperty("RuntimeType") + @JsonInclude(Include.NON_NULL) + private String runtimeType; + } diff --git a/src/main/java/org/bonitasoft/engine/connector/uipath/model/Strategy.java b/src/main/java/org/bonitasoft/engine/connector/uipath/model/Strategy.java index e15a270..883f114 100644 --- a/src/main/java/org/bonitasoft/engine/connector/uipath/model/Strategy.java +++ b/src/main/java/org/bonitasoft/engine/connector/uipath/model/Strategy.java @@ -17,7 +17,7 @@ public enum Strategy { - ALL("All"), SPECIFIC("Specific"), JOBS_COUNT("JobsCount"); + ALL("All"), SPECIFIC("Specific"), JOBS_COUNT("JobsCount"), MODERN_JOBS_COUNT("ModernJobsCount"); private final String value; diff --git a/src/main/resources-filtered/uipath-add-queueItem.def b/src/main/resources-filtered/uipath-add-queueItem.def index 2e54077..1b6900e 100644 --- a/src/main/resources-filtered/uipath-add-queueItem.def +++ b/src/main/resources-filtered/uipath-add-queueItem.def @@ -13,6 +13,7 @@ + @@ -31,6 +32,7 @@ + diff --git a/src/main/resources-filtered/uipath-getjob.def b/src/main/resources-filtered/uipath-getjob.def index 7c18c93..5bd5d53 100644 --- a/src/main/resources-filtered/uipath-getjob.def +++ b/src/main/resources-filtered/uipath-getjob.def @@ -14,6 +14,7 @@ + @@ -25,6 +26,13 @@ - + + + + + + + + \ No newline at end of file diff --git a/src/main/resources-filtered/uipath-startjob.def b/src/main/resources-filtered/uipath-startjob.def index d9c5361..ba1125b 100644 --- a/src/main/resources-filtered/uipath-startjob.def +++ b/src/main/resources-filtered/uipath-startjob.def @@ -13,12 +13,15 @@ + - + + + @@ -28,6 +31,11 @@ + + + + + @@ -35,7 +43,15 @@ All Specific + RobotCount JobsCount + ModernJobsCount + + + + Manual + Schedule + Queue diff --git a/src/main/resources/uipath-add-queueItem.properties b/src/main/resources/uipath-add-queueItem.properties index 6499d55..e140c38 100644 --- a/src/main/resources/uipath-add-queueItem.properties +++ b/src/main/resources/uipath-add-queueItem.properties @@ -33,4 +33,6 @@ tenantLogicalNameWidget.description=The selected service's logical name userKeyWidget.label=User key userKeyWidget.description=Unique key to generate login tokens clientIdWidget.label=Client ID -clientIdWidget.description=Specific to the Orchestrator application itself \ No newline at end of file +clientIdWidget.description=Specific to the Orchestrator application itself +organizationUnitIdWidget.label=Organization Unit Id +organizationUnitIdWidget.description=Required when using modern folder feature in cloud env. \ No newline at end of file diff --git a/src/main/resources/uipath-getjob.properties b/src/main/resources/uipath-getjob.properties index bed6114..f94b81b 100644 --- a/src/main/resources/uipath-getjob.properties +++ b/src/main/resources/uipath-getjob.properties @@ -4,6 +4,8 @@ connectorDefinitionDescription=Get a UiPath Job status connectorDefinitionLabel=UiPath - Get job authenticationPage.pageTitle=Connector settings authenticationPage.pageDescription=Set connection and authentication connector parameters. +configurationPage.pageTitle=Job configuration +configurationPage.pageDescription=Configure job parameters. urlWidget.label=URL urlWidget.description=UiPath Orchestrator endpoint url urlWidget.example=https://demo.uipath.com/ @@ -22,4 +24,6 @@ tenantLogicalNameWidget.description=The selected service's logical name userKeyWidget.label=User key userKeyWidget.description=Unique key to generate login tokens clientIdWidget.label=Client ID -clientIdWidget.description=Specific to the Orchestrator application itself \ No newline at end of file +clientIdWidget.description=Specific to the Orchestrator application itself +organizationUnitIdWidget.label=Organization Unit Id +organizationUnitIdWidget.description=Required when using modern folder feature in cloud env. \ No newline at end of file diff --git a/src/main/resources/uipath-startjob.properties b/src/main/resources/uipath-startjob.properties index e71b40e..0c56a0a 100644 --- a/src/main/resources/uipath-startjob.properties +++ b/src/main/resources/uipath-startjob.properties @@ -23,6 +23,9 @@ strategyCombo.description=States which robots from the environment are being run sourceCombo.label=Source sourceCombo.description=The Source of the job starting the current process. robotsWidget.label=Robot names (if Specific strategy) +robotsWidget.description=The collection of names of specific robots selected to be run by the current process. This collection must be empty only if the start strategy is not Specific. +machineSessionIdsWidget.label=Machine sessions ids +machineSessionIdsWidget.description=The machines used for running the job. If empty, the job will start on the first available machine. jobsCountWidget.label=Jobs count (if JobsCount strategy) jobsCountWidget.description=Number of pending jobs to be created in the environment, for the current process. This number must be greater than 0 only if the start strategy is JobsCount. inputArgsPage.pageTitle=Input parameters @@ -36,4 +39,10 @@ tenantLogicalNameWidget.description=The selected service's logical name userKeyWidget.label=User key userKeyWidget.description=Unique key to generate login tokens clientIdWidget.label=Client ID -clientIdWidget.description=Specific to the Orchestrator application itself \ No newline at end of file +clientIdWidget.description=Specific to the Orchestrator application itself +organizationUnitIdWidget.label=Organization Unit Id +organizationUnitIdWidget.description=Required when using modern folder feature in cloud env. +runtimeTypeWidget.label=Runtime Type +runtimeTypeWidget.description=Required when using modern folder feature in cloud env. +sourceWidget.label=Source +sourceWidget.description=Manual is a good default value \ No newline at end of file diff --git a/src/test/java/org/bonitasoft/engine/connector/uipath/UIPathConnectorTest.java b/src/test/java/org/bonitasoft/engine/connector/uipath/UIPathConnectorTest.java index 5539373..982e7a4 100644 --- a/src/test/java/org/bonitasoft/engine/connector/uipath/UIPathConnectorTest.java +++ b/src/test/java/org/bonitasoft/engine/connector/uipath/UIPathConnectorTest.java @@ -79,7 +79,7 @@ void should_build_cloud_url() throws Exception { parameters.put(UIPathConnector.ACCOUNT_LOGICAL_NAME, "bonita"); parameters.put(UIPathConnector.TENANT_LOGICAL_NAME, "bonita"); connector.setInputParameters(parameters); - assertThat(connector.getUrl()).isEqualTo("https://platform.uipath.com/bonita/bonita/"); + assertThat(connector.getUrl()).isEqualTo("https://cloud.uipath.com/bonita/bonita/"); } @Test @@ -118,6 +118,7 @@ void should_authenticate_in_the_cloud() throws Exception { parameters.put(UIPathConnector.CLOUD, true); parameters.put(UIPathConnector.ACCOUNT_LOGICAL_NAME, "bonitasoft"); parameters.put(UIPathConnector.TENANT_LOGICAL_NAME, "a_tenant"); + parameters.put(UIPathConnector.ORGANIZATION_UNIT_ID, "myUnitId"); parameters.put(UIPathConnector.USER_KEY, "someToken"); parameters.put(UIPathConnector.CLIENT_ID, "1234"); parameters.put(UIPathGetJobConnector.JOB_ID, "268348846");