diff --git a/camunda-formio-tasklist-vue/src/components/TaskList.vue b/camunda-formio-tasklist-vue/src/components/TaskList.vue index 79ba1d6a..13b3efae 100644 --- a/camunda-formio-tasklist-vue/src/components/TaskList.vue +++ b/camunda-formio-tasklist-vue/src/components/TaskList.vue @@ -39,10 +39,15 @@ :filterList="filterList" /> + +
+ + +
Loading... -
-
-

Please select a task from the list

+ + + + + + + + +
+ +

Please select a task from the list

+
+ + + + +
@@ -530,18 +151,14 @@ import * as bootstrap from 'bootstrap'; import { ALL_FILTER, CamundaRest, - SEARCH_USERS_BY, SocketIOService, authenticateFormio, findFilterIdForDefaultFilterName, getFormDetails, - getFormattedDateAndTime, - getISODateTime, getTaskFromList, getUserName, getformHistoryApi, isAllowedUser, - reviewerGroup, sortByPriorityList, } from "../services"; import { @@ -552,53 +169,42 @@ import { FilterPayload, FormRequestActionPayload, FormRequestPayload, - GroupListPayload, Payload, - SEARCH_OPTION_TYPE, TaskHistoryListPayload, TaskListSortType, TaskPayload, - UserListObject, - UserListPayload, - UserPayload, - UserSearchListLabelPayload, } from "../models"; import { Toast, Tooltip } from 'bootstrap'; import BpmnViewer from "bpmn-js/dist/bpmn-navigated-viewer.production.min.js"; -import DatePicker from "v-calendar/lib/components/date-picker.umd"; import ExpandContract from "./addons/ExpandContract.vue"; -import FormEdit from "./form/Edit.vue"; -import FormView from "./form/View.vue"; + import { Formio } from 'vue-formio'; import Header from "./layout/Header.vue"; import LeftSider from "./layout/LeftSider.vue"; -import TaskHistory from "../components/addons/TaskHistory.vue"; +import RightSider from "../components/layout/RightSider.vue"; import TaskListMixin from "../mixins/TaskListMixin.vue"; -import moment from "moment"; + import { namespace } from "vuex-class"; import serviceFlowModule from "../store/modules/serviceFlow-module"; -import vSelect from "vue-select"; const StoreServiceFlowModule = namespace("serviceFlowModule"); @Component({ components: { - TaskHistory, Header, LeftSider, - vSelect, ExpandContract, BpmnViewer, - FormEdit, - FormView, - VDatePicker: DatePicker, + RightSider, + + }, }) export default class Tasklist extends Mixins(TaskListMixin) { @@ -642,20 +248,10 @@ export default class Tasklist extends Mixins(TaskListMixin) { private formioUrl: string = ""; private task: TaskPayload = { }; - private setGroup = null; - private userSelected: UserListPayload = { - }; + public perPage: number = 10; private filterList: FilterPayload[] = []; - private selectedFilterTaskVariable={ - }; - private editAssignee: boolean = false; - private loadingEditAssignee: boolean= false; - private loadingClaimAndUnclaim: boolean = false; - private groupList: GroupListPayload[] = []; - private groupListNames?: string[] = []; - private groupListItems: string[] = []; private userEmail: string = "external"; private selectedfilterId: string = ""; private xmlData!: string; @@ -665,12 +261,11 @@ export default class Tasklist extends Mixins(TaskListMixin) { maxResults: this.perPage, }; private taskHistoryList: TaskHistoryListPayload[] = []; - private reviewerUsersList: UserListPayload[] = []; - private selectSearchType: string = "lastName"; + private taskIdValue: string = ""; private taskId2: string = ""; private activeUserSearchindex = 1; - private UserSearchListLabel: UserSearchListLabelPayload[] = SEARCH_USERS_BY; + private isUserAllowed: boolean = false private containerHeight: number = 0; private taskScrollableHeight: string = '100px'; @@ -700,33 +295,7 @@ export default class Tasklist extends Mixins(TaskListMixin) { this.userName = getUserName(); } - timedifference(date: Date) { - return moment(date).fromNow(); - } - - async toggleassignee() { - this.loadingEditAssignee=true; - const reviewerList = await CamundaRest.getUsersByMemberGroups( - this.token, - this.bpmApiUrl, - reviewerGroup - ); - if (reviewerList) { - this.reviewerUsersList = []; - reviewerList.data.forEach((user: UserPayload) => { - this.reviewerUsersList.push(UserListObject(user)); - }); - const userList = JSON.parse(JSON.stringify(this.reviewerUsersList)); - this.userSelected = userList.find(((user: any) => user.code?.includes(this.task.assignee))); - this.loadingEditAssignee=false; - this.editAssignee = !this.editAssignee; - } - } - setSelectedUserSearchBy(searchby: string, index: number) { - this.selectSearchType = searchby; - this.activeUserSearchindex = index; - } async onFormSubmitCallback(actionType = "") { if (this.task.id !== null) { @@ -735,54 +304,6 @@ export default class Tasklist extends Mixins(TaskListMixin) { } } - addGroup() { - CamundaRest.createTaskGroupByID( - this.token, - this.task.id!, - this.bpmApiUrl, - { - userId: null, - groupId: this.setGroup, - type: "candidate", - } - ).then(() => { - this.getGroupDetails(); - this.reloadCurrentTask(); - }); - this.setGroup = null; - } - - async getGroupDetails() { - const grouplist = await CamundaRest.getTaskGroupByID( - this.token, - this.task.id!, - this.bpmApiUrl - ); - this.groupList = grouplist.data; - this.groupListItems = []; - this.groupListNames = undefined; - for (const group of grouplist.data) { - this.groupListItems.push(group.groupId); - } - if (this.groupListItems.length) { - this.groupListNames = this.groupListItems; - } - } - - async deleteGroup(groupid: string) { - await CamundaRest.deleteTaskGroupByID( - this.token, - this.task.id!, - this.bpmApiUrl, - { - groupId: groupid, - type: "candidate", - } - ).then(async () => { - await this.getGroupDetails(); - await this.reloadCurrentTask(); - }); - } async onBPMTaskFormSubmit(taskId: string, actionType: string) { let formRequestFormat: FormRequestPayload = { @@ -832,7 +353,7 @@ export default class Tasklist extends Mixins(TaskListMixin) { this.task = taskResult.data; this.task.taskProcess = processResult.data.name; this.task.applicationId = applicationIdResult.data.applicationId.value; - await this.getGroupDetails(); + // await this.getGroupDetails(); } async getTaskFormIODetails(taskId: string) { @@ -959,51 +480,17 @@ export default class Tasklist extends Mixins(TaskListMixin) { ); } - async onClaim() { - this.loadingClaimAndUnclaim= true; - await CamundaRest.claim( - this.token, - this.task.id!, - { - userId: this.userName, - }, - this.bpmApiUrl - ); - - if (!SocketIOService.isConnected()) { - await this.getBPMTaskDetail(this.getFormsFlowTaskId); - await this.reloadLHSTaskList(); - } - this.loadingClaimAndUnclaim= false; - } - async onUnClaim() { - this.loadingClaimAndUnclaim= true; - await CamundaRest.unclaim(this.token, this.task.id!, this.bpmApiUrl); - if (!SocketIOService.isConnected()) { - await this.getBPMTaskDetail(this.getFormsFlowTaskId); - await this.reloadLHSTaskList(); - } - this.loadingClaimAndUnclaim= false; - } + async getBPMTaskandReload(){ + await this.getBPMTaskDetail(this.getFormsFlowTaskId); + await this.reloadLHSTaskList(); - async onSetassignee() { - await CamundaRest.setassignee( - this.token, - this.task.id!, - { - userId: this.userSelected?.code, - }, - this.bpmApiUrl - ); - await this.toggleassignee(); - if (!SocketIOService.isConnected()) { - await this.getBPMTaskDetail(this.getFormsFlowTaskId); - await this.reloadLHSTaskList(); - } } + + + async fetchFullTaskList(filterId: string, requestData: Payload) { const taskList = await CamundaRest.filterTaskList( @@ -1048,106 +535,6 @@ export default class Tasklist extends Mixins(TaskListMixin) { } } - async onUserSearch(search: string, loading: any) { - if (search.length) { - loading(true); - this.reviewerUsersList = []; - } - - if (this.selectSearchType === SEARCH_OPTION_TYPE.FIRST_NAME) { - const firstNameUserList = await CamundaRest.getUsersByFirstNameGroups( - this.token, - this.bpmApiUrl, - search, - reviewerGroup - ); - this.reviewerUsersList = []; - firstNameUserList.data.map((user: UserPayload) => { - this.reviewerUsersList.push(UserListObject(user)); - }); - loading(false); - } - - if (this.selectSearchType === SEARCH_OPTION_TYPE.LAST_NAME) { - const lastNameUserList = await CamundaRest.getUsersByLastNameGroups( - this.token, - this.bpmApiUrl, - search, - reviewerGroup - ); - this.reviewerUsersList = []; - lastNameUserList.data.map((user: UserPayload) => { - this.reviewerUsersList.push(UserListObject(user)); - }); - loading(false); - } - - if (this.selectSearchType === SEARCH_OPTION_TYPE.EMAIL) { - const emailUserList = await CamundaRest.getUsersByEmailGroups( - this.token, - this.bpmApiUrl, - search, - reviewerGroup - ); - this.reviewerUsersList = []; - emailUserList.data.map((user: UserPayload) => { - this.reviewerUsersList.push(UserListObject(user)); - }); - loading(false); - } - loading(false); - } - - async updateTaskDatedetails(taskId: string, task: TaskPayload) { - await CamundaRest.updateTasksByID(this.token, taskId, this.bpmApiUrl, task); - if (!SocketIOService.isConnected()) { - await this.reloadCurrentTask(); - } - } - - async updateFollowUpDate() { - const referenceobject = this.task; - try { - if (this.task?.followUp !== null) { - referenceobject.followUp = getISODateTime(this.task?.followUp); - await this.updateTaskDatedetails(this.task.id!, referenceobject); - } - } catch { - console.warn("Follow date error"); // eslint-disable-line no-console - } - } - - async updateDueDate() { - const referenceobject = this.task; - try { - if (this.task?.due !== null) { - referenceobject.due = getISODateTime(this.task.due); - await this.updateTaskDatedetails(this.task.id!, referenceobject); - } - } catch { - console.warn("Due date error"); // eslint-disable-line no-console - } - } - - async removeDueDate() { - const referenceobject = this.task; - try { - referenceobject["due"] = null; - await this.updateTaskDatedetails(this.task.id!, referenceobject); - } catch { - console.warn("Due date error"); // eslint-disable-line no-console - } - } - - async removeFollowupDate() { - const referenceobject = this.task; - try { - referenceobject["followUp"] = null; - await this.updateTaskDatedetails(this.task.id!, referenceobject); - } catch { - console.warn("Follow up date error"); // eslint-disable-line no-console - } - } async fetchTaskDetails(taskId: string) { await Promise.all([ @@ -1208,7 +595,7 @@ export default class Tasklist extends Mixins(TaskListMixin) { this.setFormsFlowactiveIndex(NaN); this.taskLoading = true; this.$root.$on("call-fetchTaskDetails", async (para: any) => { - this.editAssignee = false; + this.singleTaskLoading = true; this.setFormsFlowTaskId(para.selectedTaskId); await this.fetchTaskDetails(this.getFormsFlowTaskId); @@ -1341,9 +728,7 @@ export default class Tasklist extends Mixins(TaskListMixin) { } - getExactDate(date: Date) { - return getFormattedDateAndTime(date); - } + beforeDestroy() { SocketIOService.disconnect(); @@ -1366,126 +751,12 @@ export default class Tasklist extends Mixins(TaskListMixin) { } - + + diff --git a/camunda-formio-tasklist-vue/src/components/layout/LeftSider.vue b/camunda-formio-tasklist-vue/src/components/layout/LeftSider.vue index cf737c59..2fc2656e 100644 --- a/camunda-formio-tasklist-vue/src/components/layout/LeftSider.vue +++ b/camunda-formio-tasklist-vue/src/components/layout/LeftSider.vue @@ -88,7 +88,12 @@ v-if="task.followUp" >Follow up in {{ timedifference(task.followUp) }}
- + + + +
= []; + private getProcessDefinitions: Array = []; private processDefinitionId = ""; private currentPage = 1; diff --git a/camunda-formio-tasklist-vue/src/components/layout/RightSider.vue b/camunda-formio-tasklist-vue/src/components/layout/RightSider.vue new file mode 100644 index 00000000..4e845aae --- /dev/null +++ b/camunda-formio-tasklist-vue/src/components/layout/RightSider.vue @@ -0,0 +1,900 @@ + + + + + + diff --git a/camunda-formio-tasklist-vue/src/components/search/TaskSearchItem.vue b/camunda-formio-tasklist-vue/src/components/search/TaskSearchItem.vue index 178c05ca..247b07f3 100644 --- a/camunda-formio-tasklist-vue/src/components/search/TaskSearchItem.vue +++ b/camunda-formio-tasklist-vue/src/components/search/TaskSearchItem.vue @@ -90,7 +90,10 @@ v-if="showVariableValueState[index] === 's'" @click="updateVariableInput(index)" > - {{ selectedSearchQueries[index].variable }} + + 0 + {{ selectedSearchQueries[index].variable }} +
@@ -138,33 +141,51 @@ {{ formatDate(selectedSearchQueries[index].value) }}
+ +
+ 0 +
+
{{ selectedSearchQueries[index].value }}
+
+ @@ -223,6 +244,23 @@ export default class TaskSearchItem extends Vue { private taskVariableArray: any = []; private filteredVariable =[]; private searchListElements: SearchOptionPayload[] = taskSearchFilters; + private showCalender: boolean =false + + @Watch("selectedfilterId") + settingTaskVariable (){ + if(this.filterList.length&&this.selectedfilterId){ + this.filterList.forEach(filterListItem=>{ + if(filterListItem.id===this.selectedfilterId){ + this.filteredVariable = filterListItem?.properties?.variables ||[]; + this.taskVariableArray= filterListItem?.properties?.variables ||[]; + } + }); + } + } + mounted(){ + this.settingTaskVariable(); + + } @Watch("selectedfilterId") settingTaskVariable (){ @@ -291,6 +329,7 @@ export default class TaskSearchItem extends Vue { } setSearchQueryValue(item: SearchOptionPayload[], index: number) { + this.showCalender=false; this.$root.$emit("call-setSearchQueryValue", { item: item, index: index, @@ -353,3 +392,12 @@ export default class TaskSearchItem extends Vue { } + + + diff --git a/camunda-formio-tasklist-vue/src/components/taskVariable/TaskVariable.vue b/camunda-formio-tasklist-vue/src/components/taskVariable/TaskVariable.vue index 8350221e..964afd5b 100644 --- a/camunda-formio-tasklist-vue/src/components/taskVariable/TaskVariable.vue +++ b/camunda-formio-tasklist-vue/src/components/taskVariable/TaskVariable.vue @@ -12,8 +12,10 @@
- - {{ checkVlaueIsDateOrNOt(item.value)}} + + + {{ item.value.toString()}} +
@@ -38,9 +40,7 @@ import { Vue, Watch, } from "vue-property-decorator"; -import { - getFormattedDateAndTime -} from "../../services"; + @Component export default class TaskVariable extends Vue { @Prop() private variables!: any[]; @@ -87,14 +87,7 @@ export default class TaskVariable extends Vue { } } - checkVlaueIsDateOrNOt=(value: any)=>{ - const isValueNumber = isNaN(value); - if(isValueNumber){ - return getFormattedDateAndTime(value)!=='Invalid Date'?getFormattedDateAndTime(value):value; - }else{ - return value; - } - } + } diff --git a/camunda-formio-tasklist-vue/src/services/SearchOptions.ts b/camunda-formio-tasklist-vue/src/services/SearchOptions.ts index f8afc133..41dc3d49 100644 --- a/camunda-formio-tasklist-vue/src/services/SearchOptions.ts +++ b/camunda-formio-tasklist-vue/src/services/SearchOptions.ts @@ -160,19 +160,26 @@ export const FILTER_OPERATOR_TYPES = { AFTER: "after", }; -const convertingValueNumber =(value: any)=>{ - let newValue: any ; - if(value==='false'){ - newValue=false; - }else if(value==='true'){ - newValue= true; - }else if(isNaN(value)){ - newValue=value; + +const convertingValueNumber =(searchOption: any)=>{ + // if it is not like operator + if(searchOption.operator===FILTER_OPERATOR_TYPES.LIKE){ + return `%${searchOption.value}%`; }else{ - newValue= Number(value); - } - return newValue; -}; + const value = searchOption.value; + let newValue: any ; + if(value==='false'){ + newValue=false; + }else if(value==='true'){ + newValue= true; + }else if(isNaN(value)){ + newValue=value; + }else{ + newValue= Number(value); + } + return newValue; + }; + const getProcessedParamObject = (searchOption: SearchOptionPayload) => @@ -234,7 +241,8 @@ export const getFormattedQueryListParams = ( paramList[searchOption.key].push({ name: searchOption.variable, operator: getVariableOperator(searchOption.operator), - value: convertingValueNumber(searchOption.value), + value: convertingValueNumber(searchOption), + }); } } diff --git a/rpa-robocorp-extention/README.md b/rpa-robocorp-extention/README.md new file mode 100644 index 00000000..aabbafe4 --- /dev/null +++ b/rpa-robocorp-extention/README.md @@ -0,0 +1,97 @@ + +# formsflow.ai Camunda RPA Client + +![Java](https://img.shields.io/badge/Java-JDK_1.8-blue) ![Windows/Linux](https://img.shields.io/badge/Windows/Linux-OS-blue) ![Robocorp](https://img.shields.io/badge/Robocorp-rcc-blue) ![postgres](https://img.shields.io/badge/postgres-latest-blue) + +Run your automation using the camunda rpa client + + + +## Table of Content +1. [Introduction](#introduction) +2. [About the Project](#about-the-project) + * [Features](#features) +3. [Download and Installation](#download-and-installation) +4. [System Architecture](#system-architecture) +5. [Quick Start](#quick-start) +6. [Usage](#usage) +7. [License](#license) +8. [Links](#links) + +## Introduction + +This is an extension provided by [formsflow.ai](https://formsflow.ai/) to enable RPA feature to automate complex tasks. +Therefore, this application can be extended with camunda by enabling the external task handler from the service task and +further collaborated with a Robot. + +## About the Project + +The project was initiated by [AOT Technologies](https://www.aot-technologies.com/) as a means of addressing the complex +robotic use-cases which need to be automated outside [formsflow.ai](https://formsflow.ai/). + +Typical use cases are: + +* Browser task eg: web scraping. +* Tedious excel task eg: copying data from other sources to a given Excel file. +* PDF task eg: associating html pages / images to a PDF file. + +### Features + +* Builtin association with OpenSource RPA framework Robocorp. +* Configurable robot with audit and history. +* Support both cloud and command line robots. +* Input / Output operation supported. +* Supports multiple OS eg: Windows 10, Linux. +* Supports Docker build. + +## Download and Installation + +For download and setup, follow the [installation guide](./external-client-extention/INSTALLATION.md), you will find step-by-step instructions to download and install. + +## System Architecture + +![Architecture](https://user-images.githubusercontent.com/85665463/150078528-f05637b5-ae37-48c1-b4e4-501fb11c08d9.png) + +## Quick Start + +Following instructions need to be followed to run this project. + +1. Make sure to have Camunda running. +2. Create a simple process model with an External Service Task and define the topic as 'topicName' +3. Deploy the process to the Camunda Platform engine. +4. Create a robot and associate it with the topic like in BackgroundCheckRobotHandler by creating a new handler. +5. Start the camunda-rpa-client app. +6. Log in to formsflow.ai web component and associate the workflow with a form and submit the form. +7. The robot will be triggered + +## Usage + +For usage instructions, Please refer [here](./external-client-extention/USAGE.md) + +## About External Client + +This project is an extension to camunda's own [External task client](https://github.com/camunda/camunda-bpm-platform/tree/master/clients/java) to run robots + +This works based on an external task subscription, it triggers the robots using [rcc](https://robocorp.com/docs/rcc/installation). + +On completion of the robot, the external task client will send the completion request with data if any to camunda. + + +## License + +Copyright 2020 AppsOnTime-Technologies 2020 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +## Links diff --git a/rpa-robocorp-extention/external-client-extention/.gitignore b/rpa-robocorp-extention/external-client-extention/.gitignore new file mode 100644 index 00000000..10114eb2 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/.gitignore @@ -0,0 +1,43 @@ +HELP.md +target/ +src/main/resources/robots/web-scraper/output/ +src/main/resources/robots/background-check/output/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +*-db.mv +*-db.trace +*.iml +.mvn +mvnw* + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Docker ### +.env \ No newline at end of file diff --git a/rpa-robocorp-extention/external-client-extention/Dockerfile b/rpa-robocorp-extention/external-client-extention/Dockerfile new file mode 100644 index 00000000..25f92600 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/Dockerfile @@ -0,0 +1,39 @@ +FROM maven:3.6.1-jdk-11-slim AS MAVEN_TOOL_CHAIN +COPY pom.xml /tmp/ +COPY settings-docker.xml /usr/share/maven/ref/ +WORKDIR /tmp/ + +RUN mvn -s /usr/share/maven/ref/settings-docker.xml dependency:resolve-plugins dependency:resolve dependency:go-offline -B +COPY src /tmp/src/ +RUN mvn -s /usr/share/maven/ref/settings-docker.xml package + + +# Final custom slim java image (for apk command see jdk-11.0.3_7-alpine-slim) +FROM adoptopenjdk/openjdk11:jdk-11.0.3_7-alpine + +ENV JAVA_VERSION jdk-11.0.3+7 +ENV JAVA_HOME=/opt/java/openjdk \ + PATH="/opt/java/openjdk/bin:$PATH" + +EXPOSE 8090 + +ARG JAR_FILE=camunda-rpa-client.jar + +WORKDIR /opt/app + +RUN mkdir -p /opt/robots +COPY --from=MAVEN_TOOL_CHAIN /tmp/target/${JAR_FILE} /opt/app/ +COPY src/main/resources/robots/ /opt/robots/ +RUN chmod a+rwx -R /opt/app + +ENV ROBOT_BASE_DIR=/opt/robots + +RUN apk add bash +RUN apk add --no-cache curl +RUN curl -o rcc https://downloads.robocorp.com/rcc/releases/latest/linux64/rcc +RUN chmod a+x rcc +RUN mv rcc /usr/local/bin/ +RUN apk add chromium +RUN apk add chromium-chromedriver + +ENTRYPOINT ["java","-jar","/opt/app/camunda-rpa-client.jar"] \ No newline at end of file diff --git a/rpa-robocorp-extention/external-client-extention/INSTALLATION.md b/rpa-robocorp-extention/external-client-extention/INSTALLATION.md new file mode 100644 index 00000000..84cc96a2 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/INSTALLATION.md @@ -0,0 +1,197 @@ +# Download and Installation + +In the following document, we’ll describe about the different project dependencies, and the installation options being supported. + +## Table of Contents + +1. [Prerequisites](#prerequisites) +2. [Download the Camunda RPA Client Extension](#download-the-camunda-rpa-client-extension) +3. [Installation](#installation) + * [Standalone Installation](#standalone-installation) + * [Docker Based Installation](#docker-based-installation) + +## Prerequisites + +* Admin access to a local or remote server (can be local Windows PC or Mac) +* For docker based installation [Docker](https://docker.com) needs to be installed. + +## Download the Camunda RPA Client Extension + +* Clone this github repo: https://github.com/shibin-aot/formsflow-ai-extensions.git + +## Installation + +There are multiple options for installing Camunda RPA Client Extension. They are given below + +- [Standalone Installation](#standalone-installation) +- [Docker Based Installation](#docker-based-installation) + +## Standalone Installation + +------------------ +### Project Dependencies + +- [>= JDK 1.8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) +- [Maven ](https://maven.apache.org) +- [Robocorp RCC](https://robocorp.com/docs/rcc/installation) / [Robocorp Cloud](cloud.robocorp.com/) account + +### Configuration Setup + + * Modify the variables in application.yaml file. + + ##### Keycloak Integration +-------------------------- + + Variable name | Meaning | Possible values | Default value | + --- | --- | --- | --- + `keycloak.url`:triangular_flag_on_post:| URL to your Keycloak server || `http://localhost:8080` + `keycloak.url.realm`| The Keycloak realm to use|eg. forms-flow-ai | `forms-flow-ai` + `keycloak.clientId`|Your Keycloak Client ID within the realm| eg. forms-flow-bpm | `forms-flow-bpm` + `keycloak.clientSecret`:triangular_flag_on_post:|The secret for your Keycloak Client Id|eg. 22ce6557-6b86-4cf4-ac3b-42338c7b1ac12|`must be set to your Keycloak client secret` + + +##### RPA_JDBC : Dedicated rpa-client database. +----------------------------------------------------------------------- + + Variable name | Meaning | Possible values | Default value | + --- | --- | --- | --- + `spring.datasource.url`|H2 JDBC DB Connection URL|Used on installation to create the database. Choose your own|`jdbc:h2:./camunda-rpa-client-db` + `spring.datasource.driverClassName`|H2 JDBC Database Driver||`org.h2.Driver` + `spring.datasource.username`|H2 Database Username|Used on installation to create the database. Choose your own|`sa` + `spring.datasource.password`|H2 Database Password|Used on installation to create the database. Choose your own|`sa` + `spring.datasource.connectionTimeout`|Hikari Connection optimization setting||`30000` + `spring.datasource.idleTimeout`|Hikari Connection optimization setting||`600000` + `spring.datasource.maximumPoolSize`|Hikari Connection optimization setting||`10` + `spring.datasource.validationTimeout`|Hikari Connection optimization setting||`5000` + + ##### Formsflow-ai Settings +----------------------------------------------------------------------- + + Variable name | Meaning | Possible values | Default value | + --- | --- | --- | --- + `formsflow.formio.enabled`|||`true` + `formsflow.formio.url`|Formio URL||`https://{your ip address}:3001` + `formsflow.security.username`|Formio Username||`admin@example.com` + `formsflow.security.password`|Formio Password||`changeme` + + ##### General Settings + +----------------------------------------------------------------------- + + Variable name | Meaning | Possible values | Default value | + --- | --- | --- | --- + `robot.base-dir`|Base directory of robot||`/opt/robots` + `robot.robot-type`|Type of robot||`ROBOCORP_RCC` + `robot.cloud.api-url`|Robocorp Cloud base url|| + `robot.cloud.api-key`|Robocorp Cloud api key|| + `client.base-url`|Camunda Base URL||`https://localhost:8000` + `client.max-tasks`|Specifies the maximum amount of tasks that can be fetched within one request.||`10` + `disable-backoff-strategy`|||`true` + `async-response-timeout`|Specifies the response timeout.||`10000` ms + + + +#### Running the application + +We can run the application by below command. + +``` +mvn spring-boot:run +``` +## Health Check + +The application should be up and available for use at port defaulted to 8090 in http://localhost:8090/api/robot/healthcheck. By this step standalone based installation is completed. + + +## Docker Based Installation + +------------------ + +### Project Dependencies + +- [Docker](https://docker.com) + +### Solution Setup + +#### Installation + + * Make sure you have a Docker machine up and running. + * Rename the file [sample.env](./sample.env) to **.env**. + * Modify the environment variables in the newly created **.env** file if needed. Environment variables are given in the table below, + * **NOTE : {your-ip-address} given inside the .env file should be changed to your host system IP address. Please take special care to identify the correct IP address if your system has multiple network cards** + +> :information_source: Variables with trailing :triangular_flag_on_post: in below table should be updated in the .env file + +##### Keycloak Integration +-------------------------- + + Variable name | Meaning | Possible values | Default value | + --- | --- | --- | --- + `KEYCLOAK_URL`:triangular_flag_on_post:| URL to your Keycloak server || `http://{your-ip-address}:8080` + `KEYCLOAK_URL_REALM`| The Keycloak realm to use|eg. forms-flow-ai | `forms-flow-ai` + `KEYCLOAK_BPM_CLIENT_ID`|Your Keycloak Client ID within the realm| eg. forms-flow-bpm | `forms-flow-bpm` + `KEYCLOAK_BPM_CLIENT_SECRET`:triangular_flag_on_post:|The secret for your Keycloak Client Id|eg. 22ce6557-6b86-4cf4-ac3b-42338c7b1ac12|`must be set to your Keycloak client secret` +##### RPA_JDBC : Dedicated rpa-client database. +----------------------------------------------------------------------- + + Variable name | Meaning | Possible values | Default value | + --- | --- | --- | --- + `RPA_JDBC_URL`|Postgres JDBC DB Connection URL|Used on installation to create the database. Choose your own|`jdbc:postgresql://camunda-rpa-client-db:5432/rpa-client` + `RPA_JDBC_DRIVER`|Postgres JDBC Database Driver||`org.postgresql.Driver` + `RPA_POSTGRES_USER`|Postgres Database Username|Used on installation to create the database. Choose your own|`postgres` + `RPA_POSTGRES_PASSWORD`|Postgres Database Password|Used on installation to create the database. Choose your own|`password` + `RPA_JDBC_DB_NAME`|Postgres Database Name|Used on installation to create the database. Choose your own|`rpa-client` + `RPA_HIKARI_CONN_TIMEOUT`|Hikari Connection optimization setting||`30000` + `RPA_HIKARI_IDLE_TIMEOUT`|Hikari Connection optimization setting||`600000` + `RPA_HIKARI_MAX_POOLSIZE`|Hikari Connection optimization setting||`10` + `RPA_HIKARI_VALID_TIMEOUT`|Hikari Connection optimization setting||`5000` + + ##### Formsflow-ai Settings +----------------------------------------------------------------------- + + Variable name | Meaning | Possible values | Default value | + --- | --- | --- | --- + `FORMIO_ENABLED`|||`true` + `FORMIO_URL`|Formio URL||`https://{your ip address}:3001` + `FORMIO_USERNAME`|Formio Username||`admin@example.com` + `FORMIO_PASSWORD`|Formio Password||`changeme` + + ##### General Settings +----------------------------------------------------------------------- + + Variable name | Meaning | Possible values | Default value | + --- | --- | --- | --- + `ROBOT_BASE_DIR`|Base directory of robot||`/opt/robots` + `ROBOT_TYPE`|Type of robot||`ROBOCORP_RCC` + `ROBOCORP_CLOUD_BASE_URL`|Robocorp Cloud base url|| + `ROBOCORP_CLOUD_APIKEY`|Robocorp Cloud api key|| + `CAMUNDA_BPM_URL`|Camunda Base URL||`https://{your ip address}:8000` + `MAX_TASKS`|Specifies the maximum amount of tasks that can be fetched within one request.||`10` + `LOCK_DURATION`|Specifies the duration for the next task pull.||`3000` ms + + + + + #### Running the application + +* camunda-rpa-client service uses port 8090, make sure the port is available. + +Run +``` +docker-compose up -d +``` + +*NOTE: Use --build command with the start command to reflect any future **.env** changes eg : `docker-compose -f docker-compose.yml up --build -d`* + +#### To stop the application + +Run +``` +docker-compose stop +``` + +## Health Check + +The application should be up and available for use at port defaulted to 8090 in http://localhost:8090/api/robot/healthcheck. By this step docker based installation is completed. + + diff --git a/rpa-robocorp-extention/external-client-extention/USAGE.md b/rpa-robocorp-extention/external-client-extention/USAGE.md new file mode 100644 index 00000000..fea02fe1 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/USAGE.md @@ -0,0 +1,128 @@ +# Usage Instructions +In the following document, we’ll describe the step by step instructions to set up a working robot in the formsflow.ai solution. + +## Table of Contents +1. [Usage Instructions](#usage-instructions) + * [STEP 1 : Designer Steps](#designer-steps) + * [STEP 2 : Client Steps](#client-steps) + * [STEP 3 : Reviewer Steps](#reviewer-steps) +2. [Usage Instructions with examples](#usage-instructions-with-examples) + * [Create Background Check Robot](#create-background-check-robot) + * [STEP 1 : Designer Steps](#designer--steps) + * [STEP 2 : Client Steps](#client--steps) + * [STEP 3 : Clerk Steps](#clerk-steps) + * [STEP 4 : Approver Steps](#approver-steps) +3. [Database Configuration](#database-configuration) +4. [Links](#links) + +## Usage Instructions + +The following instructions can be followed to use camunda rpa client solution after the successful healthcheck. + +### Designer Steps + + * Build a new robot using robot framework [Robocorp](https://robocorp.com/). + * Robot can be deployed to cloud / place it under resources/robots to run it. + * Create a new Java handler to manage the robot and associate the robot with the handler using database configuration. + * Build a new process model using [Camunda Modeler](https://camunda.com/download/modeler/) with an External Service Task and deploy the process to formsflow.ai. + * After the successful deployment, login to [forms-flow-web](http://localhost:3000) using designer credentials and associate the form with workflow. To learn more about how to associate the form with workflow, go to [link](https://github.com/AOT-Technologies/forms-flow-ai/blob/master/USAGE.md) + * Now **Log out** and go to [Client Steps](#client-steps) + +### Client Steps + + * Login to [forms-flow-web](http://localhost:3000) with client credentials. + * Fill and submit the form. To learn more about how to submit the form, Please refer [here](https://github.com/AOT-Technologies/forms-flow-ai/blob/master/USAGE.md) + * Robot will be triggered. + * Now **Log out** and go to [Reviewer Steps](#reviewer-steps) + +### Reviewer Steps + + * Login to [forms-flow-web](http://localhost:3000) with reviewer credentials. + * Review the task and reviewer can take an action based on robot response + * Reviewer action is completed. + +> Clerk & Approver are part of the main group Reviewer, so the steps are similar to Reviewer Steps. + +## Usage Instructions with examples + +## Create Background Check Robot + +> The following instructions are using an example form from the [formsflow.ai](https://formsflow.ai/) - `Create New Business License Application`. + +### Designer Steps + + * Build the robot using OpenSource RPA framework [Robocorp](https://robocorp.com/). Refer this [example](https://github.com/robocorp/example-google-image-search) robot. + * Add the robot to the path `{base-dir}/src/main/resources/robots` + * Create a new handler to manage the robot under the package `org.camunda.rpa.client.handlers` `eg:BackgroundCheckRobotHandler`. Refer [How to Configure A Java Handler](./starter-examples/handlers/background-check-robot-handler-readme.md) to learn more about the detailed configuration. + * Associate the robot with handler by using a simple database configuration. Refer [Database Configuration](#database-configuration) to learn the detailed configuration. + * Create a new process model using [Camunda Modeler](https://camunda.com/download/modeler/) with an External Service Task - `Two Step Approval With RPA` + +![image](https://user-images.githubusercontent.com/85665463/150779537-376a0394-74d4-4897-85a5-34b954048197.png) + + * Add `FormBPMDataPipelineListener` if you need to accept any parameters from form.io to Camunda RPA Client.To know more about `FormBPMDataPipelineListener` configuration, Please refer [here](https://github.com/AOT-Technologies/forms-flow-ai/blob/master/forms-flow-bpm/starter-examples/listeners/formbpmdatapipeline-readme.md) + * Add topic name `background-check` to the property panel of `Complete Background Verification` Service Task. + +![image](https://user-images.githubusercontent.com/85665463/150779740-ed1699f2-c325-441f-84a0-c4b7acdd6b8f.png) + + * Now deploy the process to Camunda Platform Engine. + * Login to [forms-flow-web](http://localhost:3000) using designer credentials and associate the `New Business License Application` form with `Two Step Approval With RPA` workflow. To learn more about how to associate the form with workflow, go to [link](https://github.com/AOT-Technologies/forms-flow-ai/blob/master/USAGE.md) + * Now **Log out** and go to [Client Steps](#client--steps) + +### Client Steps + + + * Login to [forms-flow-web](http://localhost:3000) with client credentials. + * Submit the `New Business License Application` in formsflow.ai. To learn more about how to submit the form, Please refer [here](https://github.com/AOT-Technologies/forms-flow-ai/blob/master/USAGE.md) + * Fill in the details and submit the form, `background-check` robot will be triggered and perform its task. + * Now **Log out** and go to [Clerk Steps](#clerk-steps) + +### Clerk Steps + + * Login to [forms-flow-web](http://localhost:3000) with clerk credentials. + * The associated task can be found for the Clerk role in the **Task Tab** section + * Click on the **Task tab** to claim and review the task. + * You can view and download the robot response from the form. + * Clerk can take an action based on robot response. + * Log out and go to [Approver Steps](#approver-steps). + +### Approver Steps + + * Login to [forms-flow-web](http://localhost:3000) with reviewer credentials. + * Click on the **Task tab** to claim and review the task. + * Submit his/her review with feedback. Then the status change to approved. + * Reviewer action is completed. + +## Database Configuration + +BackgroundCheckRobotHandler make use of Robot Handler Configuration to associate with the robot. Robot Handler configuration Table includes below fields. + +* **`handlerId`** - Robot is identified by a unique `HANDLER_ID` in the configuration table. This `HANDLER_ID` is referred from the robot handler class. + +* **`robotName`** - Name of the Robot. Robot name should be similar to the robot folder name added to `{base-dir}/src/main/resources/robots` path. `eg: background-check` + +* **`topicName`** - Topic name of the External Service Task. This should be similar to the robot folder name. Topic name is referred to `@ExternalTaskSubscription` annotation in handler class and `Complete Background Verification` service task in the workflow. `eg:background-check` + +* **`variableNames`** - Robot Input variables will be in comma seperated format. + +* **`taskFileName`** - Represents Task File Name. `eg: task.robot, task.py` + +* **`processDefinitionKey`** - Filter for External Tasks with this process definition key + +* **`responseType`** - This is referred to the response type of robot. `eg: FILE, FILE_MULTI, KEY_VALUE` + +* **`workspace_id`** - If the robot is deployed in robocorp cloud provide the workspace id. + +* **`processId`** - If the robot is deployed in robocorp cloud provide the robot process id. + +* **`description`** - Defines the description of robot. + +* **`createdDate`** - Date and time of robot. + + +## Links + +* [Web site](https://formsflow.ai/) +* [Youtube Link](https://youtu.be/_H-P3Av3gqg) +* [Camunda External Task Client](https://camunda.com/blog/2021/03/external-task-client-spring-bootified/) +* [Robocorp RPA](https://robocorp.com/) +* [RCC toolchain](https://robocorp.com/docs/rcc/overview) diff --git a/rpa-robocorp-extention/external-client-extention/docker-compose.yml b/rpa-robocorp-extention/external-client-extention/docker-compose.yml new file mode 100644 index 00000000..e1ab5671 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/docker-compose.yml @@ -0,0 +1,65 @@ + +version: "3.6" +services: + camunda-rpa-client-db: + container_name: camunda-rpa-client-db + image: 'postgres' + environment: + POSTGRES_USER: ${RPA_JDBC_USER:-postgres} + POSTGRES_PASSWORD: ${RPA_JDBC_PASSWORD:-password} + POSTGRES_DB: ${RPA_JDBC_DB_NAME:-rpa-client} + volumes: + - ./postgres/rpa:/data/postgres + ports: + - '7432:5432' + networks: + - camunda-rpa-client-network + + camunda-rpa-client: + container_name: camunda-rpa-client + restart: always + depends_on: + - camunda-rpa-client-db + environment: + - KEYCLOAK_URL=${KEYCLOAK_URL} + - KEYCLOAK_URL_REALM=${KEYCLOAK_URL_REALM:-forms-flow-ai} + - KEYCLOAK_CLIENTID=${KEYCLOAK_BPM_CLIENT_ID:-forms-flow-bpm} + - KEYCLOAK_CLIENTSECRET=${KEYCLOAK_BPM_CLIENT_SECRET} + - RPA_JDBC_URL=${RPA_JDBC_URL:-jdbc:postgresql://camunda-rpa-client-db:5432/rpa-client} + - RPA_JDBC_USER=${RPA_JDBC_USER:-postgres} + - RPA_JDBC_PASSWORD=${RPA_JDBC_PASSWORD:-password} + - RPA_JDBC_DRIVER=${RPA_JDBC_DRIVER:-org.postgresql.Driver} + - ROBOT_BASE_DIR=${ROBOT_BASE_DIR:-/opt/robots} + - ROBOT_TYPE=${ROBOT_TYPE:-ROBOCORP_CLOUD} + - RPA_APP_LOG_LEVEL=${RPA_APP_LOG_LEVEL:-error} + - CAMUNDA_BPM_URL=${CAMUNDA_BPM_URL} + - MAX_TASKS=${MAX_TASKS:-10} + - LOCK_DURATION=${LOCK_DURATION:-3000} + - FORMIO_USERNAME=${FORMIO_USERNAME} + - FORMIO_PASSWORD=${FORMIO_PASSWORD} + - FORMIO_ENABLED=${FORMIO_ENABLED} + - FORMIO_URL=${FORMIO_URL} + - ROBOCORP_CLOUD_APIKEY=${ROBOCORP_CLOUD_APIKEY} + - ROBOCORP_CLOUD_BASE_URL=${ROBOCORP_CLOUD_BASE_URL} + build: + context: ./ + dockerfile: Dockerfile + links: + - camunda-rpa-client-db + ports: + - '8090:8090' + networks: + - camunda-rpa-client-network + + +networks: + camunda-rpa-client-network: + driver: "bridge" + +volumes: + postgres: + + + + + \ No newline at end of file diff --git a/rpa-robocorp-extention/external-client-extention/images/camunda-rpa-client-architecture.png b/rpa-robocorp-extention/external-client-extention/images/camunda-rpa-client-architecture.png new file mode 100644 index 00000000..ccb8f8e9 Binary files /dev/null and b/rpa-robocorp-extention/external-client-extention/images/camunda-rpa-client-architecture.png differ diff --git a/rpa-robocorp-extention/external-client-extention/pom.xml b/rpa-robocorp-extention/external-client-extention/pom.xml new file mode 100644 index 00000000..63f7e24c --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/pom.xml @@ -0,0 +1,245 @@ + + + 4.0.0 + + org.camunda.rpa.client + camunda-rpa-client + 0.0.1-SNAPSHOT + Camunda RPA Client + Camunda RPA Client + + + 11 + 11 + 11 + 7.16.0 + 2.5.4 + 2.8.9 + 2.0.206 + 3.0.0 + 2.11.0 + 2.13.1 + 3.12.0 + 2.0.6 + 2.0.2 + + + + + + org.camunda.bpm.springboot.project + camunda-bpm-spring-boot-starter-root + ${version.camunda} + pom + import + + + + + + + org.camunda.bpm + camunda-external-task-client-spring + + + + org.springframework.boot + spring-boot-autoconfigure + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-configuration-processor + 2.0.3.RELEASE + + + + org.projectlombok + lombok + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + junit + junit + test + + + + org.junit.vintage + junit-vintage-engine + test + + + + org.mockito + mockito-inline + test + + + + org.powermock + powermock-module-junit4 + + + + org.powermock + powermock-api-mockito2 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.postgresql + postgresql + 42.2.18 + + + + org.liquibase + liquibase-core + + + + com.google.code.gson + gson + ${version.gson} + + + + com.h2database + h2 + runtime + ${version.h2} + + + + io.springfox + springfox-boot-starter + ${version.swagger} + + + + commons-io + commons-io + ${version.commons.io} + + + + com.fasterxml.jackson.core + jackson-core + ${version.jackson} + + + + org.apache.commons + commons-lang3 + ${version.apache.lang3} + + + + org.jdom + jdom2 + ${version.jdom2} + + + + org.jdom + jdom + ${version.jdom} + + + + + + + camunda-rpa-client + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + org.springframework.boot + spring-boot-maven-plugin + 2.5.2 + + ZIP + + + + + repackage + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.0.0 + + org.camunda.rpa.client.CamundaRpaClientApplication + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.jacoco + jacoco-maven-plugin + 0.8.7 + + target/jacoco-ut + + org/camunda/rpa/client/*.class + + + + + + prepare-agent + report + + + + + + + + + + diff --git a/rpa-robocorp-extention/external-client-extention/sample.env b/rpa-robocorp-extention/external-client-extention/sample.env new file mode 100644 index 00000000..fa620de4 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/sample.env @@ -0,0 +1,36 @@ +############################################################################### +# This file is a sample file, for Docker compose to work with the settings # # +# Uncomment the variables if any changes from the default values # +############################################################################### + +##Environment Variables for Keycloak +KEYCLOAK_URL=http://{your-ip-address}:8080 +#KEYCLOAK_URL_REALM=forms-flow-ai +#KEYCLOAK_BPM_CLIENT_ID=forms-flow-bpm +KEYCLOAK_BPM_CLIENT_SECRET=3ddc269b-3174-435f-8f69-359b82bf8c7d + +#Primary Datasource connection and settings +RPA_JDBC_URL=jdbc:postgresql://camunda-rpa-client-db:5432/rpa-client +RPA_JDBC_USER=postgres +RPA_JDBC_PASSWORD=password +RPA_JDBC_DRIVER=org.postgresql.Driver +#RPA_HIKARI_CONN_TIMEOUT=30000 +#RPA_HIKARI_IDLE_TIMEOUT=600000 +#RPA_HIKARI_MAX_POOLSIZE=10 +#RPA_HIKARI_VALID_TIMEOUT=5000 + +#ROBOT_TYPE=ROBOCORP_RCC + +#RPA_APP_LOG_LEVEL=error + +CAMUNDA_BPM_URL=http://{your-ip-address}:8000 +#MAX_TASKS=10 +#LOCK_DURATION=3000 + +#FORMIO_ENABLED=false +#FORMIO_URL=http://192.168.0.102:3001 +#FORMIO_USERNAME=admin@example.com +#FORMIO_PASSWORD=changeme + +ROBOCORP_CLOUD_APIKEY=RC-WSKEY 7lJjKCgDoBAnRJ5ErYeShxJuO257yHHOemNOtmRoMTcwpT0dnZcwJ5dmWtpPjinO8TbqQP2EtPAN4zKAm5kCORylQgMqW +ROBOCORP_CLOUD_BASE_URL=https://api.eu1.robocorp.com \ No newline at end of file diff --git a/rpa-robocorp-extention/external-client-extention/settings-docker.xml b/rpa-robocorp-extention/external-client-extention/settings-docker.xml new file mode 100644 index 00000000..bef0f21b --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/settings-docker.xml @@ -0,0 +1,27 @@ + + + + + + + + + defaultProfile + + true + + + + + diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/CamundaRpaClientApplication.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/CamundaRpaClientApplication.java new file mode 100644 index 00000000..70e17f33 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/CamundaRpaClientApplication.java @@ -0,0 +1,22 @@ +package org.camunda.rpa.client; + +import org.camunda.rpa.client.config.ClientProperties; +import org.camunda.rpa.client.config.formsflow.FormsflowProperties; +import org.camunda.rpa.client.config.RobotHandlerProperties; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.scheduling.annotation.EnableAsync; + +/** + * Camunda RPA Client Spring Boot application. + */ +@EnableAsync +@SpringBootApplication +@EnableConfigurationProperties({ClientProperties.class, RobotHandlerProperties.class, FormsflowProperties.class}) +public class CamundaRpaClientApplication { + + public static void main(String[] args) { + SpringApplication.run(CamundaRpaClientApplication.class, args); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/AppConfig.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/AppConfig.java new file mode 100644 index 00000000..236fda42 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/AppConfig.java @@ -0,0 +1,39 @@ +package org.camunda.rpa.client.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.camunda.rpa.client.data.repository.RepoFinder; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.ExchangeStrategies; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +public class AppConfig { + + @Value("${spring.webclient.max-buffer-size}") + private Integer maxBufferSize; + + @Bean + public WebClient getWebClient(){ + return WebClient.builder() + .exchangeStrategies(ExchangeStrategies.builder() + .codecs(configurer -> configurer + .defaultCodecs() + .maxInMemorySize(maxBufferSize * 1024 * 1024)) + .build()) + .build(); + } + + @Bean + public RepoFinder getRepoFinder(){ + return new RepoFinder(); + } + + @Bean + @Qualifier("bpmObjectMapper") + public ObjectMapper bpmObjectMapper(){ + return new ObjectMapper(); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/ClientProperties.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/ClientProperties.java new file mode 100644 index 00000000..e534b8e6 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/ClientProperties.java @@ -0,0 +1,56 @@ +package org.camunda.rpa.client.config; + +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; +import org.camunda.rpa.client.data.repository.RepoFinder; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import org.camunda.bpm.client.spring.impl.client.ClientConfiguration; +import org.camunda.bpm.client.spring.impl.subscription.SubscriptionConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import javax.annotation.PostConstruct; +import java.util.*; + +/** + * Client Properties for Camunda RPA Client Spring Boot application. + * author:Shibin Thomas + */ +@Data +@ConfigurationProperties(prefix = "client") +public class ClientProperties extends ClientConfiguration { + + @Autowired + @JsonIgnore + private RepoFinder repoFinder; + + @PostConstruct + protected void init(){ + List configList = (List) repoFinder.getConfigRepository().findAll(); + if(configList.size() > 0){ + configList.forEach(config -> { + SubscriptionConfiguration subscriptionConfiguration = new SubscriptionConfiguration(); + subscriptionConfiguration.setTopicName(config.getTopicName()); + String variableNamesStr = config.getVariableNames(); + if(variableNamesStr != null && variableNamesStr.length() > 0) { + List variableNames = Arrays.asList(variableNamesStr.replace(" ","").split(",")); + subscriptionConfiguration.setVariableNames(variableNames); + } + subscriptionConfiguration.setProcessDefinitionKey(config.getProcessDefinitionKey()); + subscriptions.put(config.getTopicName(), subscriptionConfiguration); + }); + } + } + + private Map subscriptions = new HashMap<>(); + + private KeycloakConfiguration auth; + + public SubscriptionConfiguration findSubscriptionPropsByTopicName(String topic) { + return subscriptions.get(topic); + } + + public KeycloakConfiguration getAuthenticationProperties() { + return auth; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/KeycloakConfiguration.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/KeycloakConfiguration.java new file mode 100644 index 00000000..00d3ea45 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/KeycloakConfiguration.java @@ -0,0 +1,15 @@ +package org.camunda.rpa.client.config; + +import lombok.Data; + +/** + * Keycloak Configuration + */ +@Data +public class KeycloakConfiguration { + + private String clientId; + private String clientSecret; + private String grantType; + private String tokenUri; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/RobotHandlerProperties.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/RobotHandlerProperties.java new file mode 100644 index 00000000..980e7c97 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/RobotHandlerProperties.java @@ -0,0 +1,28 @@ +package org.camunda.rpa.client.config; + +import org.camunda.rpa.client.data.constants.RobotType; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * robot handler properties + * author:Shibin Thomas + */ +@Data +@ConfigurationProperties(prefix = "robot") +public class RobotHandlerProperties { + + /** + * Base directory of the robot. + * this can be left empty if working with normal deployment, default location will be then + * resources/robots folder + * In case of docker, this directory will be /opt/robots + */ + private String baseDir; + /** + * Robot type will be based on the way how the robots run + * available options are rcc / cloud + */ + private RobotType robotType; + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/SwaggerConfig.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/SwaggerConfig.java new file mode 100644 index 00000000..cac760f1 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/SwaggerConfig.java @@ -0,0 +1,35 @@ +package org.camunda.rpa.client.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + + +@Configuration +@EnableSwagger2 +public class SwaggerConfig extends WebMvcConfigurerAdapter { + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build(); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("swagger-ui.html") + .addResourceLocations("classpath:/META-INF/resources/"); + + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/"); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/AuthResponse.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/AuthResponse.java new file mode 100644 index 00000000..3560b666 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/AuthResponse.java @@ -0,0 +1,10 @@ +package org.camunda.rpa.client.config.auth; + +/** + * @author Shibin Thomas + */ +public class AuthResponse { + + private String access_token; + private String scope; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/KeycloakContext.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/KeycloakContext.java new file mode 100644 index 00000000..e96e08ea --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/KeycloakContext.java @@ -0,0 +1,35 @@ +package org.camunda.rpa.client.config.auth; + +/** + * Keycloak context holding access token. + * + * @author Shibin Thomas + */ +public class KeycloakContext { + + String accessToken; + + private long expiresAt; + + String refreshToken; + + public KeycloakContext(String accessToken, long expiresInMillis, String refreshToken) { + this.accessToken = accessToken; + expiresAt = System.currentTimeMillis() + expiresInMillis - 2000; + this.refreshToken = refreshToken; + } + + /** + * Checks if the token needs a refresh or not + * @return + */ + public boolean needsRefresh() { + return System.currentTimeMillis() >= expiresAt; + } + + public String getRefreshToken() { + return refreshToken; + } + + public String getAccessToken() { return accessToken; } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/KeycloakContextProvider.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/KeycloakContextProvider.java new file mode 100644 index 00000000..04d58fc9 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/KeycloakContextProvider.java @@ -0,0 +1,141 @@ +package org.camunda.rpa.client.config.auth; + +import org.camunda.rpa.client.config.KeycloakConfiguration; +import org.camunda.rpa.client.exception.IdentityProviderException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.*; +import org.springframework.util.MultiValueMap; +import org.springframework.util.MultiValueMapAdapter; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; +import java.nio.charset.Charset; +import java.util.*; + +import static org.camunda.rpa.client.util.ObjectUtil.*; +import static org.apache.http.HttpHeaders.AUTHORIZATION; + +/** + * Keycloak context provider. + *

+ * Manages access tokens for then Keycloak REST API. + */ +public class KeycloakContextProvider { + + private static final Logger LOG = LoggerFactory.getLogger(KeycloakContextProvider.class); + + private KeycloakContext context; + + WebClient webClient; + + KeycloakConfiguration keycloakConfiguration; + + /** + * Creates a new Keycloak context provider + * @param keycloakConfiguration the Keycloak configuration + * @param webClient REST template + */ + public KeycloakContextProvider(KeycloakConfiguration keycloakConfiguration, WebClient webClient) { + this.keycloakConfiguration = keycloakConfiguration; + this.webClient = webClient; + } + + /** + * Requests an access token for the configured Keycloak client. + * @return new Keycloak context holding the access token + */ + private KeycloakContext openAuthorizationContext() { + + String authToken = keycloakConfiguration.getClientId() + ":" + keycloakConfiguration.getClientSecret(); + String encodedAuthToken = encodeToBase64(authToken); + + try { + Map response = webClient.post().uri(keycloakConfiguration.getTokenUri()) + .header(AUTHORIZATION, "Basic " + encodedAuthToken) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .body(BodyInserters.fromFormData("grant_type", keycloakConfiguration.getGrantType())) + .retrieve() + .bodyToMono(Map.class) + .block(); + + String accessToken = convertKeyToString(response, "access_token"); + String refreshToken = convertKeyToString(response, "refresh_token"); + long expiresInMillis = convertKeyToLong(response, "expires_in") * 1000; + return new KeycloakContext(accessToken, expiresInMillis, refreshToken); + + } catch (Exception rce) { + LOG.error(rce.getMessage()); + throw new IdentityProviderException("Unable to get access token from Keycloak server", rce); + } + } + + + /** + * Refreshs an access token for the configured Keycloak client. + * @return the refreshed Keycloak context holding the access token + */ + private KeycloakContext refreshToken() { + + String authToken = keycloakConfiguration.getClientId() + ":" + keycloakConfiguration.getClientSecret(); + String encodedAuthToken = encodeToBase64(authToken); + + Map> targetMap = new HashMap<>(); + MultiValueMap formData = new MultiValueMapAdapter(targetMap); + formData.put("grant_type", Collections.singletonList("refresh_token")); + formData.put("refresh_token", Collections.singletonList(context.getRefreshToken())); + + try { + Map response = webClient.post().uri(keycloakConfiguration.getTokenUri()) + .header(AUTHORIZATION, "Basic " + encodedAuthToken) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .body(BodyInserters.fromFormData(formData)) + .retrieve() + .bodyToMono(Map.class) + .block(); + + String accessToken = convertKeyToString(response, "access_token"); + String refreshToken = convertKeyToString(response, "refresh_token"); + long expiresInMillis = convertKeyToLong(response, "expires_in") * 1000; + return new KeycloakContext(accessToken, expiresInMillis, refreshToken); + } catch (Exception rce) { + LOG.error(rce.getMessage()); + throw new IdentityProviderException("Unable to refresh access token from Keycloak server", rce); + } + } + + + /** + * Creates a valid request entity for the Keycloak management API. + * @return request entity with access token set + */ + public String createApiRequestAccessToken() { + if (context == null) { + context = openAuthorizationContext(); + } else if (context.needsRefresh()) { + if (context.getRefreshToken() == null) { + LOG.error("Error : missingRefreshToken"); + context = openAuthorizationContext(); + } else { + try { + context = refreshToken(); + } catch (IdentityProviderException ipe) { + context = openAuthorizationContext(); + } + } + } + return context.getAccessToken(); + } + + + /** + * Invalidates the current authorization context forcing to request a new token. + */ + public void invalidateToken() { context = null; + } + + private String encodeToBase64(String decodedString) { + byte[] stringAsBytes = decodedString.getBytes(Charset.defaultCharset()); + return Base64.getEncoder() + .encodeToString(stringAsBytes); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/KeycloakRequestProvider.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/KeycloakRequestProvider.java new file mode 100644 index 00000000..8d6b4c3c --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/auth/KeycloakRequestProvider.java @@ -0,0 +1,32 @@ +package org.camunda.rpa.client.config.auth; + +import org.camunda.rpa.client.config.KeycloakConfiguration; +import org.camunda.bpm.client.interceptor.ClientRequestContext; +import org.camunda.bpm.client.interceptor.ClientRequestInterceptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.apache.http.HttpHeaders.AUTHORIZATION; + +/** + * Keycloak Request Provider + */ +@Component +public class KeycloakRequestProvider implements ClientRequestInterceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakRequestProvider.class); + + private static KeycloakContextProvider keycloakContextProvider = null; + + public void init(KeycloakConfiguration configuration, WebClient webClient){ + LOGGER.debug("Initialized KeycloakRequestProvider"); + keycloakContextProvider = new KeycloakContextProvider(configuration, webClient); + } + + public void intercept(ClientRequestContext requestContext) { + requestContext.addHeader(AUTHORIZATION, "Bearer " + keycloakContextProvider.createApiRequestAccessToken()); + } + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/FormioProperties.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/FormioProperties.java new file mode 100644 index 00000000..822245e9 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/FormioProperties.java @@ -0,0 +1,11 @@ +package org.camunda.rpa.client.config.formsflow; + +import lombok.Data; + +@Data +public class FormioProperties { + + private boolean enabled; + private String url; + private FormioSecurity security; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/FormioSecurity.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/FormioSecurity.java new file mode 100644 index 00000000..fc9fff78 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/FormioSecurity.java @@ -0,0 +1,11 @@ +package org.camunda.rpa.client.config.formsflow; + +import lombok.Data; + +@Data +public class FormioSecurity { + + private String username; + private String password; + private String accessTokenUri; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/FormsflowProperties.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/FormsflowProperties.java new file mode 100644 index 00000000..79d6f154 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/FormsflowProperties.java @@ -0,0 +1,12 @@ +package org.camunda.rpa.client.config.formsflow; + +import lombok.Data; +import org.camunda.rpa.client.config.formsflow.FormioProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Data +@ConfigurationProperties(prefix = "formsflow") +public class FormsflowProperties { + + private FormioProperties formio; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/auth/FormioContext.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/auth/FormioContext.java new file mode 100644 index 00000000..2e837b28 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/auth/FormioContext.java @@ -0,0 +1,24 @@ +package org.camunda.rpa.client.config.formsflow.auth; + + +public class FormioContext { + + String accessToken; + + private long expiresAt; + + public FormioContext(String accessToken, long expiresAt) { + this.accessToken = accessToken; + this.expiresAt = expiresAt + System.currentTimeMillis(); + } + + /** + * Checks if the token needs a refresh or not + * @return + */ + public boolean needsRefresh() { + return System.currentTimeMillis() >= expiresAt; + } + + public String getAccessToken() { return accessToken; } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/auth/FormioContextProvider.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/auth/FormioContextProvider.java new file mode 100644 index 00000000..2f04c162 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/auth/FormioContextProvider.java @@ -0,0 +1,118 @@ +package org.camunda.rpa.client.config.formsflow.auth; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.camunda.rpa.client.config.formsflow.FormioProperties; +import org.camunda.rpa.client.exception.FormioIdentityException; +import org.camunda.rpa.client.exception.FormioServiceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +public class FormioContextProvider { + + private static final Logger LOG = LoggerFactory.getLogger(FormioContextProvider.class); + + private ObjectMapper bpmObjectMapper; + + private FormioContext context; + + private final WebClient webClient; + + private final FormioProperties formioConfiguration; + + /** + * Creates a new Formio context provider + * @param formioConfiguration the Formio configuration + * @param webClient REST template + */ + public FormioContextProvider( WebClient webClient, FormioProperties formioConfiguration,ObjectMapper objectMapper) { + this.formioConfiguration = formioConfiguration; + this.webClient = webClient; + this.bpmObjectMapper = objectMapper; + } + + /** + * Requests an access token for the configured Keycloak client. + * @return new Keycloak context holding the access token + */ + private synchronized FormioContext openAuthorizationContext() { + + if(context != null && !context.needsRefresh()) return context; + + Map paramMap = new HashMap<>(); + paramMap.put("email", formioConfiguration.getSecurity().getUsername()); + paramMap.put("password", formioConfiguration.getSecurity().getPassword()); + Map> dataMap = new HashMap<>(); + dataMap.put("data", paramMap); + + try{ + String token = webClient.post().uri(formioConfiguration.getSecurity().getAccessTokenUri()) + .bodyValue(dataMap) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .exchangeToMono(data -> { + if(data.statusCode().is2xxSuccessful()){ + return Mono.just(data.headers().header("x-jwt-token").get(0)); + } else{ + return Mono.error(new FormioServiceException("Exception occurred in getting x-jwt-token"+ ". Message Body: " + + data)); + } + }) + .block(); + + if(token == null) throw new FormioIdentityException("Access token is null"); + long expiresAt = decodeExpiresAt(token); + return new FormioContext(token, expiresAt); + } catch (Exception rce) { + LOG.error(rce.getMessage()); + throw new FormioIdentityException("Unable to get access token from formio server", rce); + } + } + + /** + * Creates a valid request entity for the Keycloak management API. + * @return request entity with access token set + */ + public String createFormioRequestAccessToken() { + if (context == null) { + LOG.info("context is null, creating new"); + context = openAuthorizationContext(); + } else if (context.needsRefresh()) { + try { + LOG.info("Token Need refresh"); + context = openAuthorizationContext(); + } catch (FormioIdentityException ipe) { + LOG.info("Token refresh failed"); + context = openAuthorizationContext(); + } + } + return context.getAccessToken(); + } + + private void clearAuthenticationContext(){ + this.context = null; + } + + private long decodeExpiresAt(String token) throws FormioIdentityException{ + try { + String[] chunks = token.split("\\."); + Base64.Decoder decoder = Base64.getUrlDecoder(); + String data = new String(decoder.decode(chunks[1])); + JsonNode dataNode = bpmObjectMapper.readTree(data); + long exp = dataNode.get("exp").asLong(); + long iat = dataNode.get("iat").asLong(); + return (exp - iat) * 1000; + } catch (JsonProcessingException | NullPointerException exc){ + throw new FormioIdentityException("Unable to parse access token from formio server", exc); + } + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/auth/FormioRequestProvider.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/auth/FormioRequestProvider.java new file mode 100644 index 00000000..75b852e7 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/formsflow/auth/FormioRequestProvider.java @@ -0,0 +1,30 @@ +package org.camunda.rpa.client.config.formsflow.auth; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.camunda.bpm.client.interceptor.ClientRequestContext; +import org.camunda.rpa.client.config.auth.KeycloakContextProvider; +import org.camunda.rpa.client.config.auth.KeycloakRequestProvider; +import org.camunda.rpa.client.config.formsflow.FormioProperties; +import org.camunda.rpa.client.config.formsflow.FormsflowProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.apache.http.HttpHeaders.AUTHORIZATION; + +@Component +public class FormioRequestProvider { + + private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakRequestProvider.class); + + private static FormioContextProvider formioContextProvider = null; + + private FormioRequestProvider(WebClient webClient, FormsflowProperties formsflowProperties, ObjectMapper objectMapper){ + formioContextProvider = new FormioContextProvider(webClient, formsflowProperties.getFormio(), objectMapper); + } + + public void intercept(WebClient.RequestHeadersSpec requestHeadersSpec) { + requestHeadersSpec.header("x-jwt-token", formioContextProvider.createFormioRequestAccessToken()); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/impl/ClientAutoConfiguration.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/impl/ClientAutoConfiguration.java new file mode 100644 index 00000000..1434c425 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/impl/ClientAutoConfiguration.java @@ -0,0 +1,24 @@ +package org.camunda.rpa.client.config.impl; + +import org.camunda.bpm.client.spring.impl.client.ClientPostProcessor; +import org.camunda.bpm.client.spring.impl.subscription.SubscriptionPostProcessor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Shibin Thomas + */ +@Configuration +public class ClientAutoConfiguration { + + @Bean + public static SubscriptionPostProcessor subscriptionPostprocessor() { + return new SubscriptionPostProcessor(PropertiesAwareSpringTopicSubscription.class); + } + + @Bean + public static ClientPostProcessor clientPostProcessor() { + return new ClientPostProcessor(PropertiesAwareClientFactory.class); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/impl/PropertiesAwareClientFactory.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/impl/PropertiesAwareClientFactory.java new file mode 100644 index 00000000..7370aaa8 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/impl/PropertiesAwareClientFactory.java @@ -0,0 +1,80 @@ +package org.camunda.rpa.client.config.impl; + +import org.camunda.rpa.client.config.KeycloakConfiguration; +import org.camunda.rpa.client.config.ClientProperties; +import org.camunda.rpa.client.config.auth.KeycloakRequestProvider; +import org.camunda.bpm.client.spring.impl.client.ClientConfiguration; +import org.camunda.bpm.client.spring.impl.client.ClientFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * @author Shibin Thomas + */ +public class PropertiesAwareClientFactory extends ClientFactory { + + @Autowired + private WebClient webClient; + + @Autowired + protected ClientProperties clientProperties; + + @Autowired + private KeycloakRequestProvider keycloakRequestProvider; + + @Override + public void afterPropertiesSet() throws Exception { + applyPropertiesFrom(clientProperties); + addKeycloakAuthInterceptor(); + super.afterPropertiesSet(); + } + + protected void addKeycloakAuthInterceptor() { + KeycloakConfiguration keycloakConfiguration = clientProperties.getAuthenticationProperties(); + if (keycloakConfiguration != null) { + + keycloakRequestProvider.init(keycloakConfiguration, webClient); + + getRequestInterceptors().add(keycloakRequestProvider); + } + } + + public void applyPropertiesFrom(ClientProperties clientConfigurationProps) { + ClientConfiguration clientConfiguration = new ClientConfiguration(); + if (clientConfigurationProps.getBaseUrl() != null) { + clientConfiguration.setBaseUrl(clientConfigurationProps.getBaseUrl()); + } + if (clientConfigurationProps.getWorkerId() != null) { + clientConfiguration.setWorkerId(clientConfigurationProps.getWorkerId()); + } + if (clientConfigurationProps.getMaxTasks() != null) { + clientConfiguration.setMaxTasks(clientConfigurationProps.getMaxTasks()); + } + if (clientConfigurationProps.getUsePriority() != null && !clientConfigurationProps.getUsePriority()) { + clientConfiguration.setUsePriority(false); + } + if (clientConfigurationProps.getDefaultSerializationFormat() != null) { + clientConfiguration.setDefaultSerializationFormat(clientConfigurationProps.getDefaultSerializationFormat()); + } + if (clientConfigurationProps.getDateFormat() != null) { + clientConfiguration.setDateFormat(clientConfigurationProps.getDateFormat()); + } + if (clientConfigurationProps.getLockDuration() != null) { + clientConfiguration.setLockDuration(clientConfigurationProps.getLockDuration()); + } + if (clientConfigurationProps.getAsyncResponseTimeout() != null) { + clientConfiguration.setAsyncResponseTimeout(clientConfigurationProps.getAsyncResponseTimeout()); + } + if (clientConfigurationProps.getDisableAutoFetching() != null && + clientConfigurationProps.getDisableAutoFetching()) { + clientConfiguration.setDisableAutoFetching(true); + } + if (clientConfigurationProps.getDisableBackoffStrategy() != null && + clientConfigurationProps.getDisableBackoffStrategy()) { + clientConfiguration.setDisableBackoffStrategy(true); + } + setClientConfiguration(clientConfiguration); + } + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/impl/PropertiesAwareSpringTopicSubscription.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/impl/PropertiesAwareSpringTopicSubscription.java new file mode 100644 index 00000000..03a2606c --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/config/impl/PropertiesAwareSpringTopicSubscription.java @@ -0,0 +1,84 @@ +package org.camunda.rpa.client.config.impl; + +import org.camunda.rpa.client.config.ClientProperties; +import org.camunda.bpm.client.spring.impl.subscription.SpringTopicSubscriptionImpl; +import org.camunda.bpm.client.spring.impl.subscription.SubscriptionConfiguration; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ApplicationEvent; + +import java.util.function.Predicate; + +/** + * @author Shibin Thomas + */ +public class PropertiesAwareSpringTopicSubscription extends SpringTopicSubscriptionImpl { + + @Autowired + protected ClientProperties clientProperties; + + @Override + public void afterPropertiesSet() throws Exception { + mergeSubscriptionWithProperties(); + super.afterPropertiesSet(); + } + + @Override + protected Predicate isEventThatCanStartSubscription() { + return event -> event instanceof ApplicationStartedEvent; + } + + protected void mergeSubscriptionWithProperties() { + SubscriptionConfiguration merge = getSubscriptionConfiguration(); + + String topicName = merge.getTopicName(); + SubscriptionConfiguration subscriptionProperties = + clientProperties.findSubscriptionPropsByTopicName(topicName); + + if (subscriptionProperties != null) { + if (subscriptionProperties.getAutoOpen() != null) { + merge.setAutoOpen(subscriptionProperties.getAutoOpen()); + } + if (subscriptionProperties.getLockDuration() != null) { + merge.setLockDuration(subscriptionProperties.getLockDuration()); + } + if (subscriptionProperties.getVariableNames() != null) { + merge.setVariableNames(subscriptionProperties.getVariableNames()); + } + if (subscriptionProperties.getBusinessKey() != null) { + merge.setBusinessKey(subscriptionProperties.getBusinessKey()); + } + if (subscriptionProperties.getProcessDefinitionId() != null) { + merge.setProcessDefinitionId(subscriptionProperties.getProcessDefinitionId()); + } + if (subscriptionProperties.getProcessDefinitionIdIn() != null) { + merge.setProcessDefinitionIdIn(subscriptionProperties.getProcessDefinitionIdIn()); + } + if (subscriptionProperties.getProcessDefinitionKey() != null) { + merge.setProcessDefinitionKey(subscriptionProperties.getProcessDefinitionKey()); + } + if (subscriptionProperties.getProcessDefinitionKeyIn() != null) { + merge.setProcessDefinitionKeyIn(subscriptionProperties.getProcessDefinitionKeyIn()); + } + if (subscriptionProperties.getProcessDefinitionVersionTag() != null) { + merge.setProcessDefinitionVersionTag(subscriptionProperties.getProcessDefinitionVersionTag()); + } + if (subscriptionProperties.getProcessVariables() != null) { + merge.setProcessVariables(subscriptionProperties.getProcessVariables()); + } + if (subscriptionProperties.getWithoutTenantId() != null) { + merge.setWithoutTenantId(subscriptionProperties.getWithoutTenantId()); + } + if (subscriptionProperties.getTenantIdIn() != null) { + merge.setTenantIdIn(subscriptionProperties.getTenantIdIn()); + } + if (subscriptionProperties.getIncludeExtensionProperties() != null) { + merge.setIncludeExtensionProperties(subscriptionProperties.getIncludeExtensionProperties()); + } + + setSubscriptionConfiguration(merge); + } + } + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/controller/CamundaRPAClientController.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/controller/CamundaRPAClientController.java new file mode 100644 index 00000000..a15c2d0e --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/controller/CamundaRPAClientController.java @@ -0,0 +1,82 @@ +package org.camunda.rpa.client.controller; + +import org.camunda.rpa.client.core.RobotDataManager; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; +import org.camunda.rpa.client.exception.RobotClientDataException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * This class make use of robot handler configuration table to do some + * operations + * + * @author Shibin Thomas + */ +@RestController +@RequestMapping("/api/robot/") +public class CamundaRPAClientController { + + @Autowired + private RobotDataManager robotDataManager; + + @GetMapping(value = "healthcheck") + public String healthCheck() { + return "Welcome to Camunda RPA Client"; + } + + /** + * Get list of robot configuration data + * + * @return + */ + @GetMapping(value = "config/list", produces = "application/json") + public ResponseEntity> listConfigData() { + + List response = null; + try { + response = robotDataManager.listRobotConfigData(); + return ResponseEntity.ok(response); + } catch (RobotClientDataException ex) { + return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Update robot configuration data + * + * @return + */ + @PutMapping(value = "config/update", produces = "application/json", consumes = "application/json") + public ResponseEntity updateConfigData(@RequestBody RobotHandlerConfig config) { + RobotHandlerConfig response = null; + try { + response = robotDataManager.updateRobotHandlerConfigData(config); + return ResponseEntity.ok(response); + } catch (RobotClientDataException ex) { + return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Save robot configuration data + * + * @return + */ + @PostMapping(value = "config/save", produces = "application/json", consumes = "application/json") + public ResponseEntity saveConfigData(@RequestBody RobotHandlerConfig config) { + RobotHandlerConfig response = null; + try { + response = robotDataManager.saveRobotHandlerConfigData(config); + return ResponseEntity.ok(response); + } catch (RobotClientDataException ex) { + return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); + } catch (Exception ex) { + ex.printStackTrace(); + return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/IManager.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/IManager.java new file mode 100644 index 00000000..52c6447d --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/IManager.java @@ -0,0 +1,9 @@ +package org.camunda.rpa.client.core; + +/** + * author : Shibin Thomas + */ +public interface IManager { + + void manage(); +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotDataManager.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotDataManager.java new file mode 100644 index 00000000..1b47ec48 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotDataManager.java @@ -0,0 +1,107 @@ +package org.camunda.rpa.client.core; + +import org.camunda.rpa.client.core.data.RobotHandlerAuditService; +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; +import org.camunda.rpa.client.data.repository.RepoFinder; +import org.camunda.bpm.client.task.ExternalTask; +import org.camunda.rpa.client.exception.RobotClientDataException; +import org.camunda.rpa.client.exception.RobotClientRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * RobotDataManager is the gateway / manager for com.camunda.rpa.client.core.data.* + * This class includes methods for managing the audit, handler configuration etc.. + * author : Shibin Thomas + */ +@Component +public class RobotDataManager { + + private static final Logger LOG = LoggerFactory.getLogger(RobotDataManager.class); + + @Autowired + private RepoFinder repoFinder; + @Autowired + private RobotHandlerAuditService auditService; + + /** + * initAudit can make a new entry on Audit table + * This method use Handler configuration to map robot with the audit. + * + * @param externalTask + * @param handlerId + * @return + */ + public RobotHandlerAudit initAudit(ExternalTask externalTask, Integer handlerId) { + LOG.debug("Initializing audit for handler = " + handlerId); + Optional optional = repoFinder.getConfigRepository().findById(handlerId); + RobotHandlerConfig config = optional.orElse(null); + return auditService.createAudit(externalTask, config); + } + + /** + * finalizeAudit will complete the audit process by invoking com.camunda.rpa.client.core.data.RobotHandlerAuditService + * updateAuditSuccess / updateAuditFailure by a flag. + * status flag will be true if the robot was ran successfully + * status flag will be true in case of a failure. On failure error message will be saved in audit. + * + * @param audit + * @param status + * @param errorDetails + */ + public void finalizeAudit(RobotHandlerAudit audit, boolean status, String errorDetails) { + + if (status) { + auditService.updateAuditSuccess(audit); + } else { + auditService.updateAuditFailure(audit, errorDetails); + } + } + + /** + * Get All the robot configuration data + * @return + */ + public List listRobotConfigData() { + return (ArrayList) repoFinder.getConfigRepository().findAll(); + } + + /** + * Update the robot configuration data + * @param robotHandlerConfig + * @return + */ + public RobotHandlerConfig updateRobotHandlerConfigData(RobotHandlerConfig robotHandlerConfig) throws RobotClientDataException { + + Integer handlerId = robotHandlerConfig != null ? robotHandlerConfig.getHandlerId() : null; + if (handlerId != null) { + if (repoFinder.getConfigRepository().findById(robotHandlerConfig.getHandlerId()).isPresent()) { + return repoFinder.getConfigRepository().save(robotHandlerConfig); + } else { + throw new RobotClientDataException("No suitable data found for handler id = " + handlerId); + } + } else { + throw new RobotClientDataException("No handler id present in the data"); + } + } + + /** + * Add a new robot configuration entry + * @param robotHandlerConfig + * @return + */ + public RobotHandlerConfig saveRobotHandlerConfigData(RobotHandlerConfig robotHandlerConfig) throws RobotClientDataException { + + if (robotHandlerConfig == null) { + throw new RobotClientDataException("No config data provided"); + } + return repoFinder.getConfigRepository().save(robotHandlerConfig); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotIOManager.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotIOManager.java new file mode 100644 index 00000000..bc483094 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotIOManager.java @@ -0,0 +1,114 @@ +package org.camunda.rpa.client.core; + +import org.camunda.rpa.client.config.ClientProperties; +import org.camunda.rpa.client.config.RobotHandlerProperties; +import org.camunda.rpa.client.data.RobotInput; +import org.camunda.rpa.client.data.TaskDataInput; +import org.camunda.rpa.client.core.io.RobotInputService; +import org.camunda.rpa.client.core.io.RobotResponseService; +import org.camunda.rpa.client.data.constants.RobotResponseType; +import org.camunda.rpa.client.data.constants.RobotType; +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; +import org.camunda.bpm.client.spring.impl.subscription.SubscriptionConfiguration; +import org.camunda.bpm.client.task.ExternalTask; +import org.camunda.bpm.engine.variable.VariableMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; + +/** + * Manager class for Input / Response service classes. author : Shibin Thomas + */ +@Component +public class RobotIOManager { + + private static final Logger LOG = LoggerFactory.getLogger(RobotIOManager.class); + + @Autowired + private RobotInputService robotInputService; + + @Autowired + private RobotResponseService robotResponseService; + + @Autowired + private ClientProperties clientProperties; + + @Autowired + private RobotHandlerProperties handlerProperties; + + public List buildInput(ExternalTask externalTask, RobotHandlerConfig config, + Map additionalVariables) { + + SubscriptionConfiguration subscriptionConfiguration = clientProperties.getSubscriptions() + .get(config.getTopicName()); + TaskDataInput input = new TaskDataInput(externalTask, subscriptionConfiguration.getVariableNames(), + additionalVariables); + + return robotInputService.buildInputVariables(input); + } + + /** + * This function will generate a random number and assign it as the file name + * The output directory name will derive from the random number + * + * @param robotName + * @return workingDirName + */ + public synchronized String getOutputDirName(String robotName) { + String workingDirName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")); + boolean status = robotResponseService.createOutputDirectory(robotName, workingDirName); + + return status ? workingDirName : "-1"; + } + + /** + * This function will generate response for camunda Response type is + * configurable and based on the configuration the response will be generated. + * + * @param audit + * @return + */ + public VariableMap getOutputData(RobotHandlerAudit audit, String formUrl) { + + RobotHandlerConfig config = audit.getHandlerConfig(); + VariableMap variableMap = null; + RobotType robotType = handlerProperties.getRobotType(); + + if (RobotResponseType.FILE.equals(config.getResponseType()) && RobotType.ROBOCORP_RCC.equals(robotType)) { + variableMap = robotResponseService.readRCCRobotFileResponse(config.getRobotName(), + config.getWorkingDirName(), formUrl); + } + if (RobotResponseType.FILE.equals(config.getResponseType()) && RobotType.ROBOCORP_CLOUD.equals(robotType)) { + try { + variableMap = robotResponseService.readRobocorpCloudResponseFile(audit, formUrl); + } catch (Exception e) { + e.printStackTrace(); + } + } + return variableMap; + } + + /** + * This function will clear the robot current output directory and it's data + * + * @param audit + * @return + */ + public boolean clearAndRemoveOutputDirectory(RobotHandlerAudit audit) { + + RobotHandlerConfig config = audit.getHandlerConfig(); + if (RobotResponseType.FILE.equals(config.getResponseType())) { + return robotResponseService.clearAndRemoveOutputDirectory(config.getRobotName(), + config.getWorkingDirName()); + } else { + return false; + } + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotManager.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotManager.java new file mode 100644 index 00000000..127da935 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotManager.java @@ -0,0 +1,62 @@ +package org.camunda.rpa.client.core; + +import org.camunda.rpa.client.config.RobotHandlerProperties; +import org.camunda.rpa.client.core.robot.IRobotService; +import org.camunda.rpa.client.data.RobotInput; +import org.camunda.rpa.client.core.robot.RCCService; +import org.camunda.rpa.client.core.robot.RobocorpCloudService; +import org.camunda.rpa.client.data.constants.RobotType; +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; +import org.camunda.rpa.client.exception.RobotClientRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * RobotManager manages robot services from com.camunda.rpa.client.core.robot.* + * This class will identify the service to run robots and manages them. + * author : Shibin Thomas + */ +@Component +public class RobotManager { + + private static final Logger LOG = LoggerFactory.getLogger(RobotManager.class); + + @Autowired + private RobotHandlerProperties handlerProperties; + @Autowired + private RCCService rccService; + @Autowired + private RobocorpCloudService robocorpCloudService; + + /** + * runRobot run the robot service by using a user defined property to identify. + * @param robotInputs + * @param audit + */ + public boolean runRobot(List robotInputs, RobotHandlerAudit audit){ + IRobotService robotService = getRobotService(); + return robotService.runRobot(robotInputs, audit); + } + + // get robot services by using user defined {robotType} property - refer RobotType enum class + private IRobotService getRobotService(){ + + IRobotService robotService; + RobotType robotType = handlerProperties.getRobotType(); + + if (robotType == RobotType.ROBOCORP_CLOUD) { + robotService = robocorpCloudService; + } else if(RobotType.ROBOCORP_RCC.equals(robotType)){ + robotService = rccService; + } else { + throw new RobotClientRuntimeException("No suitable service identified for robot type ="+robotType, HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + return robotService; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotPipelineManager.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotPipelineManager.java new file mode 100644 index 00000000..1d4ccdd6 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/RobotPipelineManager.java @@ -0,0 +1,53 @@ +package org.camunda.rpa.client.core; + +import org.camunda.rpa.client.core.pipe.CommandRunnerService; +import org.camunda.rpa.client.core.pipe.HoloTreeDictionary; +import org.camunda.rpa.client.core.pipe.RobotDirectoryScanner; +import org.camunda.rpa.client.exception.RobotClientRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.File; +/** + * rcc run --interactive --task scripting -- --variable answer:https://www.google.com tasks.robot + * author : Shibin Thomas + */ +@Component +public class RobotPipelineManager { + + private static final Logger LOG = LoggerFactory.getLogger(RobotPipelineManager.class); + + @Autowired + private CommandRunnerService commandRunnerService; + @Autowired + private RobotDirectoryScanner robotDirectoryScanner; + + + public String[] buildRobocorpRCCScript(String input, String robotName, String workingDirName, String taskFileName){ + + String[] scripts = new String[2]; + + File baseDir = robotDirectoryScanner.getRobotLocationMap().get(robotName); + if(baseDir == null){ + throw new RobotClientRuntimeException("No appropriate robot directories identified", 500); + } + scripts[0] = "cd "+baseDir.getAbsolutePath(); + + scripts[1] = "rcc run "+ + " --space "+HoloTreeDictionary.getHoloTreeInfo().get(robotName)+ + " --interactive -- "+ + (workingDirName != null ? " --outputdir output/"+workingDirName+" " : " ")+ + (input == null?"":input)+ + (taskFileName != null? " "+taskFileName:" tasks.robot"); + + return scripts; + } + + public void runRobocorpRccScript(String[] scripts){ + LOG.debug("Running robot scripts using rcc"); + commandRunnerService.run(scripts); + } + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/data/IDataService.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/data/IDataService.java new file mode 100644 index 00000000..2ffece8e --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/data/IDataService.java @@ -0,0 +1,4 @@ +package org.camunda.rpa.client.core.data; + +public interface IDataService { +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/data/RobotHandlerAuditService.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/data/RobotHandlerAuditService.java new file mode 100644 index 00000000..52b547bf --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/data/RobotHandlerAuditService.java @@ -0,0 +1,73 @@ +package org.camunda.rpa.client.core.data; + +import org.camunda.rpa.client.core.RobotIOManager; +import org.camunda.rpa.client.data.constants.RobotResponseType; +import org.camunda.rpa.client.data.repository.RepoFinder; +import org.camunda.rpa.client.exception.RobotClientRuntimeException; +import org.camunda.bpm.client.task.ExternalTask; +import org.camunda.rpa.client.data.constants.RobotStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; + +/** + * Service class aimed to create / update an audit entry. + * + * @author Sneha Suresh + * + */ +@Service +public class RobotHandlerAuditService implements IDataService { + + private static final Logger LOG = LoggerFactory.getLogger(RobotHandlerAuditService.class); + + @Autowired + private RobotIOManager robotIOManager; + + @Autowired + private RepoFinder repoFinder; + + /** + * This method is intended to make a new entry on Audit table + * @param externalTask + * @param config + * @return + */ + public RobotHandlerAudit createAudit(ExternalTask externalTask, RobotHandlerConfig config) { + if (externalTask == null) + throw new RobotClientRuntimeException("ExternalTask data cannot be null", 500); + RobotHandlerAudit robotHandlerAudit = new RobotHandlerAudit(); + robotHandlerAudit.setHandlerConfig(config); + robotHandlerAudit.setTaskId(externalTask.getId()); + robotHandlerAudit.setStatus(RobotStatus.IN_PROGRESS); + if (RobotResponseType.FILE.equals(config.getResponseType()) || RobotResponseType.FILE_MULTI.equals(config.getResponseType())) { + config.setWorkingDirName(robotIOManager.getOutputDirName(config.getRobotName())); + } + return repoFinder.getAuditRepository().save(robotHandlerAudit); + } + + /** + * The method updateAudit can make an update on Audit table + * @param robotHandlerAudit + */ + public void updateAuditSuccess(RobotHandlerAudit robotHandlerAudit) { + robotHandlerAudit.setStatus(RobotStatus.SUCCESS); + repoFinder.getAuditRepository().save(robotHandlerAudit); + } + + /** + * The method updateAuditFailure can make an update on Audit table + * @param robotHandlerAudit + * @param errorDetails + */ + public void updateAuditFailure(RobotHandlerAudit robotHandlerAudit, String errorDetails) { + robotHandlerAudit.setDetails(errorDetails); + robotHandlerAudit.setStatus(RobotStatus.FAILED); + repoFinder.getAuditRepository().save(robotHandlerAudit); + } + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/RobotInputService.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/RobotInputService.java new file mode 100644 index 00000000..e9e6373f --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/RobotInputService.java @@ -0,0 +1,65 @@ +package org.camunda.rpa.client.core.io; + +import org.camunda.rpa.client.config.RobotHandlerProperties; +import org.camunda.rpa.client.data.RobotInput; +import org.camunda.rpa.client.data.TaskDataInput; +import org.camunda.rpa.client.data.constants.RobotType; +import org.camunda.bpm.client.task.ExternalTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * This class is responsible for building input data for the robots + * This receives data from handlers, which then extracted and send back as appropriate + * robot input data. + * author: Shibin Thomas + */ +@Service +public class RobotInputService { + + private static final Logger LOG = LoggerFactory.getLogger(RobotInputService.class); + @Autowired + private RobotHandlerProperties handlerProperties; + + /** + * This function will build input variables for the robot. + * Handlers are invoking this service via the manager class + * @param input + * @return + */ + public List buildInputVariables(TaskDataInput input){ + + ExternalTask externalTask = input.getExternalTask(); + List variableNames = input.getVariableNames(); + + List robotInputs = new ArrayList<>(); + + for (String field : variableNames) { + Object value = externalTask.getVariable(field); + addRobotInputData(robotInputs, field, value); + } + if(input.getAdditionalVariables() != null){ + for(Map.Entry entry : input.getAdditionalVariables().entrySet()){ + addRobotInputData(robotInputs,entry.getKey(), entry.getValue()); + } + } + return robotInputs; + } + + private void addRobotInputData(List robotInputs, String field, Object value){ + RobotType robotType = handlerProperties.getRobotType(); + if(value instanceof String && robotType != RobotType.ROBOCORP_CLOUD){ + String strValue = (String) value; + strValue = "\""+strValue+"\""; + robotInputs.add(new RobotInput(field, strValue)); + } else { + robotInputs.add(new RobotInput(field, value)); + } + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/RobotResponseService.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/RobotResponseService.java new file mode 100644 index 00000000..0760b924 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/RobotResponseService.java @@ -0,0 +1,210 @@ +package org.camunda.rpa.client.core.io; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.camunda.bpm.engine.variable.value.FileValue; +import org.camunda.rpa.client.config.formsflow.FormsflowProperties; +import org.camunda.rpa.client.config.formsflow.auth.FormioRequestProvider; +import org.camunda.rpa.client.core.io.ro.Base64FileResponse; +import org.camunda.rpa.client.core.io.ro.FormElement; +import org.camunda.rpa.client.core.pipe.RobotDirectoryScanner; +import java.io.InputStream; +import org.camunda.rpa.client.data.constants.ExternalClientConstants; +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.camunda.bpm.engine.variable.VariableMap; +import org.camunda.bpm.engine.variable.Variables; +import org.camunda.rpa.client.exception.RobotClientRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.configurationprocessor.json.JSONException; +import org.springframework.boot.configurationprocessor.json.JSONObject; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * This class is responsible for delivering output data from robots The response + * can be a file / multi files / string / array. author: Shibin Thomas + */ +@Service +public class RobotResponseService { + + private static final Logger LOG = LoggerFactory.getLogger(RobotResponseService.class); + + @Autowired + private RobotDirectoryScanner robotDirectoryScanner; + @Autowired + private FormioRequestProvider formioRequestProvider; + + private final WebClient webClient; + + private final FormsflowProperties formsflowProperties; + + @Value("${robot.cloud.api-key}") + private String apiKey; + + private RobotResponseService(WebClient webClient, FormsflowProperties formsflowProperties) { + this.webClient = webClient; + this.formsflowProperties = formsflowProperties; + } + + /** + * Read a single file from output directory and return it as response. + * + * @param robotName + * @param outputDirName + * @return + */ + public VariableMap readRCCRobotFileResponse(String robotName, String outputDirName, String formUrl) { + + LOG.debug("Reading response from output directory for robot = " + robotName); + + VariableMap variables = Variables.createVariables(); + + File outputDir = robotDirectoryScanner.getRobotFinalDirectory(robotName, outputDirName); + File[] outputFileArray = outputDir.listFiles(); + + if ((outputFileArray != null ? outputFileArray.length : 0) > 0) { + File output = outputFileArray[0]; + + try { + // Need to check response type is base64 or not + if (true) { + String mimeType = URLConnection.guessContentTypeFromName(output.getName()); + LOG.debug("mimetype = " + mimeType); + byte[] encoded = Base64.encodeBase64(FileUtils.readFileToByteArray(output)); + String encodedData = new String(encoded, StandardCharsets.US_ASCII); + + if (formsflowProperties.getFormio().isEnabled()) { + patchResponse(formUrl, output.getName(), encodedData, output.length()); + } + } + FileValue typedFileValue = Variables.fileValue(output.getName()).file(output) + .mimeType("application/pdf").encoding("UTF-8").create(); + variables.put(ExternalClientConstants.FILE_RESPONSE_VAR, typedFileValue); + } catch (Exception ex) { + LOG.error("Error : Reading response file"); + ex.printStackTrace(); + throw new RobotClientRuntimeException("Response file is not found", + HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + } + return variables; + } + + private ResponseEntity patchResponse(String formUrl, String fileName, String encodedData, long length) + throws JsonProcessingException { + Base64FileResponse[] base64FileResponses = new Base64FileResponse[] { + new Base64FileResponse(fileName, encodedData, length) }; + + List elements = new ArrayList<>(); + + elements.add(new FormElement(ExternalClientConstants.FILE_RESPONSE_VAR, base64FileResponses)); + + WebClient.RequestHeadersSpec requestHeadersSpec = webClient.patch() + .uri(Objects.requireNonNull(getDecoratedServerUrl(formUrl))) + .bodyValue(new ObjectMapper().writeValueAsString(elements)); + + formioRequestProvider.intercept(requestHeadersSpec); + + Mono> entityMono = requestHeadersSpec.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).retrieve() + .onStatus(HttpStatus::is4xxClientError, + response -> Mono.error(new RobotClientRuntimeException(response.toString(), 404))) + .toEntity(String.class); + + return entityMono.block(); + } + + /** + * This function will create temporary output file generated by the robot + * + * @param robotName + * @param outputDirName + * @return + */ + public boolean createOutputDirectory(String robotName, String outputDirName) { + File outputDir = robotDirectoryScanner.getRobotLocationMap().get(robotName); + return robotDirectoryScanner.createOutputDirectory(outputDir, outputDirName); + } + + /** + * This function will clear temporary output file generated by the robot + * + * @param robotName + * @param outputDirName + * @return + */ + public boolean clearAndRemoveOutputDirectory(String robotName, String outputDirName) { + File outputDir = robotDirectoryScanner.getRobotWorkingDirectory(robotName, outputDirName); + return robotDirectoryScanner.deleteDirectory(outputDir); + } + + private String getDecoratedServerUrl(String url) { + if (StringUtils.contains(url, "/form/")) { + return formsflowProperties.getFormio().getUrl() + "/form/" + StringUtils.substringAfter(url, "/form/"); + } + return null; + } + + /** + * Read a file from outputUrl and return it as response. + * + * @param audit + * @param formUrl + * @return + */ + public VariableMap readRobocorpCloudResponseFile(RobotHandlerAudit audit, String formUrl) + throws JSONException, MalformedURLException, IOException { + VariableMap variables = Variables.createVariables(); + String fileName = "data.pdf"; + try { + ResponseEntity finalResponse = webClient.method(HttpMethod.GET).uri(audit.getResponseUri()) + .header("Authorization", getApiKey()).accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).body(null).retrieve() + .toEntity(String.class).block(); + + LOG.error(finalResponse.getBody()); + JSONObject jsonObject = new JSONObject(finalResponse.getBody()); + InputStream in = new URL(jsonObject.getString("url")).openStream(); + byte[] encoded = Base64.encodeBase64(in.readAllBytes()); + String encodedData = new String(encoded, StandardCharsets.US_ASCII); + + patchResponse(formUrl, fileName, encodedData, encodedData.length()); + + FileValue typedFileValue = Variables.fileValue(fileName).file(encoded).mimeType("application/pdf") + .encoding("UTF-8").create(); + variables.put(ExternalClientConstants.FILE_RESPONSE_VAR, typedFileValue); + } catch (Exception ex) { + LOG.error("Error : Reading response file"); + ex.printStackTrace(); + throw new RobotClientRuntimeException("Response file is not found", + HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + + return variables; + } + + private String getApiKey() { + return this.apiKey; + } + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/parser/ParserService.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/parser/ParserService.java new file mode 100644 index 00000000..4d4e50e7 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/parser/ParserService.java @@ -0,0 +1,12 @@ +package org.camunda.rpa.client.core.io.parser; + +import org.camunda.rpa.client.core.io.ro.RobotOutput; +import org.jdom2.JDOMException; + +import java.io.File; +import java.io.IOException; + +public interface ParserService { + + RobotOutput parse(File file) throws IOException, JDOMException; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/parser/RobotOutputXMLParser.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/parser/RobotOutputXMLParser.java new file mode 100644 index 00000000..7fbe154b --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/parser/RobotOutputXMLParser.java @@ -0,0 +1,97 @@ +package org.camunda.rpa.client.core.io.parser; + +import org.camunda.rpa.client.core.io.ro.RobotOutput; +import org.camunda.rpa.client.core.io.ro.RobotOutputMessage; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.input.SAXBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * XML parser to parse robot output.xml file + * output.xml will be produced once the robot completes it's execution + * author : Shibin Thomas + */ +@Component +public class RobotOutputXMLParser implements ParserService{ + + private static final Logger LOG = LoggerFactory.getLogger(RobotOutputXMLParser.class); + + /** + * To parse output.xml file produced by the robot + * @param file + * @return + */ + @Override + public RobotOutput parse(File file) { + + LOG.debug("START : Parsing output file to find the status"); + + RobotOutput robotOutput = null; + try { + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(file); + + LOG.debug("Root element :" + document.getRootElement().getName()); + + Element robotElement = document.getRootElement(); + + Element statistics = robotElement.getChild("statistics"); + Element total = statistics.getChild("total"); + Element stat = total.getChild("stat"); + String pass = stat.getAttributeValue("pass"); + String fail = stat.getAttributeValue("fail"); + String skip = stat.getAttributeValue("skip"); + + Element errors = robotElement.getChild("errors"); + List messages = errors.getChildren("msg"); + List messageList = new ArrayList<>(); + if(messages != null){ + for(Element msg : messages){ + String level = msg.getAttributeValue("level"); + String message = msg.getText(); + RobotOutputMessage robotOutputMessage = new RobotOutputMessage(level, message); + messageList.add(robotOutputMessage); + } + } + robotOutput = new RobotOutput(Integer.valueOf(pass), Integer.valueOf(fail), Integer.valueOf(skip), messageList); + if(robotOutput.getFail() > 0){ + List suites = robotElement.getChildren("suite"); + List errorMessages = findErrorDetails(suites); + if(errorMessages.size() > 0) { + for(String message : errorMessages){ + RobotOutputMessage robotOutputMessage = new RobotOutputMessage("FAIL", message); + messageList.add(robotOutputMessage); + } + } + } + } catch (JDOMException | IOException ex){ + ex.printStackTrace(); + } + LOG.debug("END : Parsing output file to find the status"); + return robotOutput; + } + + private List findErrorDetails(List suites){ + + List errorMessages = new ArrayList<>(); + for(Element suite : suites) { + List tests = suite.getChildren("test"); + for (Element test : tests) { + Element status = test.getChild("status"); + if (status.getAttributeValue("status").equals("FAIL")) { + errorMessages.add(status.getText()); + } + } + } + return errorMessages; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/Base64FileResponse.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/Base64FileResponse.java new file mode 100644 index 00000000..cabba054 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/Base64FileResponse.java @@ -0,0 +1,35 @@ +package org.camunda.rpa.client.core.io.ro; +import java.io.Serializable; + + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class Base64FileResponse implements IRO{ + + private static final long serialVersionUID = 1L; + + public Base64FileResponse(String name, String url, Long size) { + this.name = name; + this.url = "data:application/pdf;base64,"+url; + this.size = size; + } + + private String storage = "base64"; + private String name; + private String url; + + private Long size; + + @Override + public String toString() { + return "Base64FileResponse{" + + "storage='" + storage + '\'' + + ", name='" + name + '\'' + + ", url='" + url + '\'' + + ", size=" + size + + '}'; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/FormElement.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/FormElement.java new file mode 100644 index 00000000..d03d4a68 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/FormElement.java @@ -0,0 +1,22 @@ +package org.camunda.rpa.client.core.io.ro; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Sumathi Thirumani + * @author Shibin Thomas + */ +@Data +@NoArgsConstructor +public class FormElement { + private String op; + private String path; + private Object value; + + public FormElement(String elementId, Object value) { + this.op = "replace"; + this.path = "/data/" + elementId; + this.value = value; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/IRO.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/IRO.java new file mode 100644 index 00000000..b52085b3 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/IRO.java @@ -0,0 +1,8 @@ +package org.camunda.rpa.client.core.io.ro; + +import java.io.Serializable; + +public interface IRO extends Serializable { + + long serialVersionUID = 1L; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/RobotOutput.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/RobotOutput.java new file mode 100644 index 00000000..3b9eecb4 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/RobotOutput.java @@ -0,0 +1,29 @@ +package org.camunda.rpa.client.core.io.ro; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RobotOutput implements IRO{ + + private Integer pass; + private Integer fail; + private Integer skip; + + private List messages; + + @Override + public String toString() { + return "RobotOutput{" + + "pass=" + pass + + ", fail=" + fail + + ", skip=" + skip + + ", messages=" + messages + + '}'; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/RobotOutputMessage.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/RobotOutputMessage.java new file mode 100644 index 00000000..e5d0ae80 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/io/ro/RobotOutputMessage.java @@ -0,0 +1,22 @@ +package org.camunda.rpa.client.core.io.ro; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RobotOutputMessage { + + private String level; + private String message; + + @Override + public String toString() { + return "{" + + "level='" + level + '\'' + + ", message='" + message + '\'' + + '}'; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/CommandRunnerService.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/CommandRunnerService.java new file mode 100644 index 00000000..9b2b0660 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/CommandRunnerService.java @@ -0,0 +1,56 @@ +package org.camunda.rpa.client.core.pipe; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.PrintWriter; + +/** + * This class is responsible for managing service layer of command runner. + * + * @author Shibin Thomas + */ +@Service +public class CommandRunnerService { + + private static final Logger LOG = LoggerFactory.getLogger(CommandRunnerService.class); + + public void run(String[] commands) { + + LOG.debug("running commands using command runner service"); + + String os = System.getProperty("os.name"); + String[] command = null; + + if("Linux".equals(os)){ + command = new String[]{ "/bin/bash", }; + } else { + command = new String[]{ "cmd", }; + } + Process p = null; + PrintWriter stdin = null; + try { + p = Runtime.getRuntime().exec(command); + new Thread(new SyncPipe(p.getErrorStream(), System.err)).start(); + new Thread(new SyncPipe(p.getInputStream(), System.out)).start(); + + stdin = new PrintWriter(p.getOutputStream()); + + for(String cmd : commands){ + stdin.println(cmd); + } + + stdin.close(); + p.waitFor(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if(stdin != null){ + stdin.close(); + } + if(p != null) + p.destroy(); + } + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/HoloTreeDictionary.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/HoloTreeDictionary.java new file mode 100644 index 00000000..a657365c --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/HoloTreeDictionary.java @@ -0,0 +1,21 @@ +package org.camunda.rpa.client.core.pipe; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HoloTreeDictionary { + + private static Map holoTreeInfo = new HashMap<>(); + + public static Map getHoloTreeInfo() { + return holoTreeInfo; + } + + public static void setHoloTreeInfo(List robotList) { + String space = "space-"; + for(int i = 0; i < robotList.size(); i++) { + HoloTreeDictionary.holoTreeInfo.put( robotList.get(i), (space+i) ); + } + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/RobotDirectoryScanner.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/RobotDirectoryScanner.java new file mode 100644 index 00000000..4c08a446 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/RobotDirectoryScanner.java @@ -0,0 +1,225 @@ +package org.camunda.rpa.client.core.pipe; + +import org.apache.commons.io.FileUtils; +import org.camunda.rpa.client.config.RobotHandlerProperties; +import org.camunda.rpa.client.data.constants.ExternalClientConstants; +import org.camunda.rpa.client.exception.RobotClientRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.util.ResourceUtils; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * This class will manage the creation / deletion of the robot directory and it's discovery + * author : Shibin Thomas + */ +@Component +public class RobotDirectoryScanner { + + private static final Logger LOG = LoggerFactory.getLogger(RobotDirectoryScanner.class); + + @Autowired + private RobotHandlerProperties handlerProperties; + + private Map robotLocationMap; + + /** + * + * @throws FileNotFoundException + */ + @PostConstruct + public void scanRobotDirectory() throws FileNotFoundException { + robotLocationMap = new HashMap<>(); + String baseDir = handlerProperties.getBaseDir(); + File robotDir = null; + if(baseDir == null || baseDir.length() == 0) { + File file = ResourceUtils.getFile("classpath:robots"); + if (file.isDirectory()) { + robotDir = new File(file.getAbsolutePath()); + } + } else { + robotDir = new File(baseDir); + if(!robotDir.exists()){ + throw new FileNotFoundException("No directory exists for the robot base directory"); + } + } + if(robotDir != null) { + searchRobotConfigFileAndMark(robotDir.listFiles()); + } + Collection coll = robotLocationMap.keySet(); + List list; + if (coll instanceof List) + list = (List)coll; + else + list = new ArrayList<>(coll); + HoloTreeDictionary.setHoloTreeInfo(list); + } + + /** + * Search the list of directories and identify if it has a robot configuration file + * @param dirs + */ + private void searchRobotConfigFileAndMark(File[] dirs){ + + if(dirs == null) return; + + for(File dir : dirs){ + if(dir.isDirectory()) { + List allFiles = Arrays.asList(Objects.requireNonNull(dir.list())); + if (allFiles.size() > 0 && allFiles.contains(ExternalClientConstants.ROBOT_CONFIG_FILE)) { + String robotName = dir.getName(); + robotLocationMap.put(robotName, dir); + } else { + searchRobotConfigFileAndMark(dir.listFiles()); + } + } + } + } + + /** + * Fetches the final directory to get the response data + * @param robotName + * @param workingDirName + * @return + */ + public File getRobotFinalDirectory(String robotName, String workingDirName) throws RobotClientRuntimeException { + + File robotDir = getRobotLocationMap().get(robotName); + if(robotDir == null){ + throw new RobotClientRuntimeException("No appropriate robot directories identified", 500); + } + return getRobotFinalDirectory(robotDir, workingDirName); + } + + /** + * Get the robot output directory which has the robot response + * @param robotName + * @param outputDirName + * @return + * @throws RobotClientRuntimeException + */ + public File getRobotWorkingDirectory(String robotName, String outputDirName) throws RobotClientRuntimeException{ + + File robotDir = getRobotLocationMap().get(robotName); + if(robotDir == null){ + throw new RobotClientRuntimeException("No appropriate robot directories identified", 500); + } + return getRobotWorkingDirectory(robotDir, outputDirName); + } + + + /** + * Get the robot output directory which has the robot response + * Eg structure : *** 178278234 ******* + * ***** final ******************** runtime ******* + * @param robotDir + * @param workingDirName + * @return + */ + private File getRobotWorkingDirectory(File robotDir, String workingDirName){ + + File outputDir = null; + + for(File file : Objects.requireNonNull(robotDir.listFiles())){ + if(file.isDirectory() && file.getName().equals(ExternalClientConstants.OUTPUT_DIR)){ + List outputFileList = Arrays.asList(Objects.requireNonNull(file.listFiles())); + List matchingFileList = outputFileList.stream().filter(f -> (f.isDirectory() && f.getName().equals(workingDirName))).collect(Collectors.toList()); + if(matchingFileList.size() > 0) { + outputDir = matchingFileList.get(0); + } + break; + } + } + return outputDir; + } + + /** + * Fetches the final directory to get the response data + * @param robotDir + * @param workingDirName + * @return + */ + private File getRobotFinalDirectory(File robotDir, String workingDirName){ + + File finalDir = null; + + for(File file : Objects.requireNonNull(robotDir.listFiles())){ + if(file.isDirectory() && file.getName().equals(ExternalClientConstants.OUTPUT_DIR)){ + List outputFileList = Arrays.asList(Objects.requireNonNull(file.listFiles())); + List matchingFileList = outputFileList.stream().filter(f -> (f.isDirectory() && f.getName().equals(workingDirName))).collect(Collectors.toList()); + if(matchingFileList.size() > 0) { + for (File finalFile : Objects.requireNonNull(matchingFileList.get(0).listFiles())) { + if (finalFile.getName().equals(ExternalClientConstants.RESPONSE_DIR)) { + finalDir = finalFile; + break; + } + } + } + break; + } + } + return finalDir; + } + + /** + * Create the output directory for robots. + * @param outputDir + * @param outputDirName + * @return + */ + public boolean createOutputDirectory(File outputDir, String outputDirName){ + + LOG.debug("Building output directories for the robot response"); + + boolean creationStatus = false; + + try { + + File rootDir = new File(outputDir.getAbsolutePath() + + ExternalClientConstants.BACK_SLASH + + ExternalClientConstants.OUTPUT_DIR + + ExternalClientConstants.BACK_SLASH + + outputDirName); + if (!rootDir.exists()) creationStatus = rootDir.mkdirs(); + + File runtimeFile = new File(rootDir.getAbsolutePath() + + ExternalClientConstants.BACK_SLASH + + ExternalClientConstants.RUNTIME_DIR); + File finalFile = new File(rootDir.getAbsolutePath() + + ExternalClientConstants.BACK_SLASH + + ExternalClientConstants.RESPONSE_DIR); + if (!runtimeFile.exists()) creationStatus = creationStatus && runtimeFile.mkdirs(); + if (!finalFile.exists()) creationStatus = creationStatus && finalFile.mkdirs(); + } catch (Exception ex){ + ex.printStackTrace(); + throw new RobotClientRuntimeException("Exception during output directory creation", HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + + return creationStatus; + } + + /** + * Deletes a directory + * @param outputDir + * @return + */ + public boolean deleteDirectory(File outputDir){ + if(outputDir == null) return false; + return FileUtils.deleteQuietly(outputDir); + } + + /** + * @return the map of robots and it's location + */ + public Map getRobotLocationMap() { + return robotLocationMap; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/SyncPipe.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/SyncPipe.java new file mode 100644 index 00000000..7e9fd3f2 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/pipe/SyncPipe.java @@ -0,0 +1,28 @@ +package org.camunda.rpa.client.core.pipe; + +import java.io.InputStream; +import java.io.OutputStream; + +class SyncPipe implements Runnable +{ + public SyncPipe(InputStream istrm, OutputStream ostrm) { + istrm_ = istrm; + ostrm_ = ostrm; + } + public void run() { + try + { + final byte[] buffer = new byte[1024]; + for (int length = 0; (length = istrm_.read(buffer)) != -1; ) + { + ostrm_.write(buffer, 0, length); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + private final OutputStream ostrm_; + private final InputStream istrm_; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/robot/IRobotService.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/robot/IRobotService.java new file mode 100644 index 00000000..7248aea1 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/robot/IRobotService.java @@ -0,0 +1,15 @@ +package org.camunda.rpa.client.core.robot; + +import org.camunda.rpa.client.data.RobotInput; +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; + +import java.util.List; + +/** + * author : Shibin Thomas + */ +public interface IRobotService { + + boolean runRobot(List robotInputs, RobotHandlerAudit audit); +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/robot/RCCService.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/robot/RCCService.java new file mode 100644 index 00000000..3315e114 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/robot/RCCService.java @@ -0,0 +1,158 @@ +package org.camunda.rpa.client.core.robot; + +import org.camunda.rpa.client.core.io.parser.RobotOutputXMLParser; +import org.camunda.rpa.client.core.io.ro.RobotOutput; +import org.camunda.rpa.client.core.pipe.RobotDirectoryScanner; +import org.camunda.rpa.client.data.RobotInput; +import org.camunda.rpa.client.core.RobotPipelineManager; +import org.camunda.rpa.client.data.constants.ExternalClientConstants; +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; +import org.camunda.rpa.client.exception.RobotClientRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.List; + +/** + * This class is implemented to run the robot by making a script with formatted input + * + * author : Shibin Thomas + */ +@Service +public class RCCService implements IRobotService { + + private static final Logger LOG = LoggerFactory.getLogger(RCCService.class); + + private static final String format = " --variable "; + + @Autowired + private RobotPipelineManager robotPipelineManager; + + @Autowired + private RobotDirectoryScanner robotDirectoryScanner; + + @Autowired + private RobotOutputXMLParser xmlParser; + + @Override + public boolean runRobot(List robotInputs, RobotHandlerAudit audit) throws RobotClientRuntimeException{ + + RobotHandlerConfig config = audit.getHandlerConfig(); + String[] scripts; + String workingDirName = config.getWorkingDirName(); + if(robotInputs != null) { + String[] formattedInput = buildInput(robotInputs); + scripts = makeScriptWithInput(formattedInput, config.getRobotName(), workingDirName, config.getTaskFileName()); + } else { + scripts = makeScriptWithoutInput(config.getRobotName(), workingDirName, config.getTaskFileName()); + } + if(scripts == null || scripts.length == 0){ + throw new RobotClientRuntimeException("No script to run robots ", 500); + } + runScript(scripts); + + return checkRobotOutputStatus(config.getRobotName(), workingDirName); + } + + /** + * Get List of robot Inputs and return it as response. + * @param robotInputs + * @return + */ + private String[] buildInput(List robotInputs){ + + String[] formattedInput = new String[robotInputs.size()]; + + for(int i = 0; i < robotInputs.size(); i++){ + formattedInput[i] = robotInputs.get(i).getField()+":"+robotInputs.get(i).getValue(); + } + return formattedInput; + } + + /** + * + * @param robotName + * @param workingDirName + * @param taskFileName + * @return + */ + private String[] makeScriptWithoutInput(String robotName, String workingDirName, String taskFileName){ + + return buildScript(null, robotName, workingDirName, taskFileName); + } + + /** + * This method is responsible to make script with available inputs and robot name + * @param inputs + * param robotName + * param workingDirName + * param taskFileName + * @return + */ + private String[] makeScriptWithInput(String[] inputs, String robotName, String workingDirName, String taskFileName){ + + StringBuilder sb = new StringBuilder(); + for(String input : inputs){ + sb.append(format).append(input); + } + return buildScript(sb.toString(), robotName, workingDirName, taskFileName); + } + + /** + * This method is responsible to build the script + * @param variables + * param robotName + * param taskFileName + * @return + */ + private String[] buildScript(String variables, String robotName, String workingDirName, String taskFileName){ + return robotPipelineManager.buildRobocorpRCCScript(variables, robotName, workingDirName, taskFileName); + } + + /** + * This method is responsible to run the script + * @param scripts + */ + private void runScript(String[] scripts){ + robotPipelineManager.runRobocorpRccScript(scripts); + } + + /** + * Robot output status will be validated based on the fail count + * fail count > 0 if any of the task failed to run + * fail count = 0 and pass > 0 and skip = 0 if all task completed successfully + * @param robotName + * @param workingDirName + * @return + * @throws RobotClientRuntimeException + */ + private boolean checkRobotOutputStatus(String robotName, String workingDirName) throws RobotClientRuntimeException{ + File outputDir = robotDirectoryScanner.getRobotWorkingDirectory(robotName, workingDirName); + File[] outputFiles = outputDir.listFiles(); + LOG.debug("Working directory = "+workingDirName); + if(outputFiles != null) { + for (File file : outputFiles) { + if(file.getName().equals(ExternalClientConstants.ROBOT_OUTPUT_XML_FILE)){ + RobotOutput robotOutput = xmlParser.parse(file); + if(robotOutput.getFail() > 0){ + LOG.error("Robot Failed completing All the tasks - "+robotOutput.getMessages().toString()); + throw new RobotClientRuntimeException("Robot Failed completing All the tasks - "+ + robotOutput.getMessages().toString(), + HttpStatus.INTERNAL_SERVER_ERROR.value()); + } else if(robotOutput.getSkip() > 0){ + LOG.warn(robotOutput.getSkip()+" Tasks didn't complete"); + return true; + } else { + return true; + } + } + } + } + return false; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/robot/RobocorpCloudService.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/robot/RobocorpCloudService.java new file mode 100644 index 00000000..81e3b44d --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/core/robot/RobocorpCloudService.java @@ -0,0 +1,138 @@ +package org.camunda.rpa.client.core.robot; + +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.springframework.web.reactive.function.client.WebClient; + +import org.springframework.beans.factory.annotation.Value; +import org.camunda.rpa.client.core.pipe.RobotDirectoryScanner; +import org.camunda.rpa.client.data.RobotInput; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.configurationprocessor.json.JSONArray; +import org.springframework.boot.configurationprocessor.json.JSONException; +import org.springframework.boot.configurationprocessor.json.JSONObject; +import org.springframework.stereotype.Service; +import java.io.IOException; +import java.util.List; +import reactor.core.publisher.Mono; +import org.springframework.http.*; + +/** + * This class will enable running robots in Robocorp cloud author : Shibin + * Thomas + */ +@Service +public class RobocorpCloudService implements IRobotService { + + private static final Logger LOG = LoggerFactory.getLogger(RobocorpCloudService.class); + + @Autowired + private WebClient webClient; + + @Autowired + private RobotDirectoryScanner robotDirectoryScanner; + + @Value("${robot.cloud.api-key}") + private String apiKey; + + @Value("${robot.cloud.api-url}") + private String processApi; + + @Override + public boolean runRobot(List robotInputs, RobotHandlerAudit audit) { + try { + return invokeRobot(robotInputs, audit); + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + private boolean invokeRobot(List robotInputs, RobotHandlerAudit audit) throws JSONException { + RobotHandlerConfig config = audit.getHandlerConfig(); + String response = startRobotProcess(robotInputs, config); + JSONObject jsonObject = new JSONObject(response); + String robotRunId = jsonObject.getString("id"); + String workItemId = new JSONArray(jsonObject.getString("workItemIds")).getString(0); + + boolean success = false; + + String output; + for (; true;) { + + try { + output = getWorkItems(robotRunId, config); + Thread.sleep(5000); + if (output.contains("FAILED")) { + break; + } + success = isRobotCompleted(output); + if (success) { + setResponseUri(output, workItemId, config, robotInputs, audit); + break; + } + } catch (Exception e) { + break; + } + } + return success; + } + + private String startRobotProcess(List robotInputs, RobotHandlerConfig config) throws JSONException { + String formattedInput = buildInput(robotInputs).toString(); + String uri = getRobotProcessUrl(config) + "/runs"; + ResponseEntity response = webClient.method(HttpMethod.POST).uri(uri) + .header("Authorization", getApiKey()).accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(Mono.just(formattedInput), String.class).retrieve().toEntity(String.class).block(); + + LOG.error(response.getBody()); + return response.getBody(); + } + + private JSONObject buildInput(List robotInputs) throws JSONException { + JSONObject formattedInput = new JSONObject(); + for (int i = 0; i < robotInputs.size(); i++) { + formattedInput.put(robotInputs.get(i).getField(), robotInputs.get(i).getValue()); + } + return formattedInput; + } + + private String getWorkItems(String robotRunId, RobotHandlerConfig config) { + String uri = getRobotProcessUrl(config) + "/runs/" + robotRunId + "/work-items"; + ResponseEntity response = webClient.method(HttpMethod.GET).uri(uri).header("Authorization", getApiKey()) + .accept(MediaType.APPLICATION_JSON).header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(Mono.empty(), String.class).retrieve().toEntity(String.class).block(); + + return response.getBody(); + } + + private void setResponseUri(String output, String workItemId, RobotHandlerConfig config, + List robotInputs, RobotHandlerAudit audit) throws JSONException, IOException { + String jsonData = new JSONObject(output).getString("data"); + String data = jsonData != null ? new JSONArray(jsonData).getString(0) : null; + String files = data != null ? new JSONObject(data).getString("files") : null; + String fileIds = files != null ? new JSONArray(files).getString(0) : null; + if (fileIds != null) { + JSONObject jsonObject = new JSONObject(fileIds); + String fileId = jsonObject.getString("id"); + String fileName = jsonObject.getString("name"); + String uri = getRobotProcessUrl(config) + "/work-items/" + workItemId + "/files/" + fileId + "/download"; + audit.setResponseUri(uri); + } + } + + private boolean isRobotCompleted(String result) { + return result.contains("COMPLETED"); + } + + private String getApiKey() { + return this.apiKey; + } + + private String getRobotProcessUrl(RobotHandlerConfig config) { + return this.processApi + "/" + config.getWorkspaceId() + "/processes/" + config.getProcessId(); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/RobocorpCloudData.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/RobocorpCloudData.java new file mode 100644 index 00000000..b7b937ff --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/RobocorpCloudData.java @@ -0,0 +1,16 @@ +package org.camunda.rpa.client.data; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RobocorpCloudData { + + private String processRunID; + private String robotRunId; + private String artifactId; + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/RobotInput.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/RobotInput.java new file mode 100644 index 00000000..628a7809 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/RobotInput.java @@ -0,0 +1,14 @@ +package org.camunda.rpa.client.data; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RobotInput { + + private String field; + private Object value; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/ScriptData.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/ScriptData.java new file mode 100644 index 00000000..44103d0a --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/ScriptData.java @@ -0,0 +1,11 @@ +package org.camunda.rpa.client.data; + +import lombok.Data; + +@Data +public class ScriptData { + + private String cmd; + private String action; + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/TaskDataInput.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/TaskDataInput.java new file mode 100644 index 00000000..7360117c --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/TaskDataInput.java @@ -0,0 +1,21 @@ +package org.camunda.rpa.client.data; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.camunda.bpm.client.task.ExternalTask; + +import java.util.List; +import java.util.Map; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TaskDataInput { + + private ExternalTask externalTask; + + private List variableNames; + + private Map additionalVariables; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/ExternalClientConstants.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/ExternalClientConstants.java new file mode 100644 index 00000000..9e62bc2e --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/ExternalClientConstants.java @@ -0,0 +1,21 @@ +package org.camunda.rpa.client.data.constants; + +/** + * File to store the constants + */ +public class ExternalClientConstants { + + public static final String FILE_RESPONSE_VAR = "rpaFileResponse"; + + public static final String RESPONSE_DIR = "final"; + + public static final String OUTPUT_DIR = "output"; + + public static final String RUNTIME_DIR = "runtime"; + + public static final String ROBOT_CONFIG_FILE = "robot.yaml"; + + public static final String ROBOT_OUTPUT_XML_FILE = "output.xml"; + + public static final String BACK_SLASH = "/"; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/RobotResponseType.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/RobotResponseType.java new file mode 100644 index 00000000..28be5ad5 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/RobotResponseType.java @@ -0,0 +1,12 @@ +package org.camunda.rpa.client.data.constants; + +public enum RobotResponseType { + + NONE, + FILE, + FILE_MULTI, + FILE_BASE64_SINGLE, + FILE_BASE64_MULTI, + KEY_VALUE, + SINGLE_VALUE; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/RobotStatus.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/RobotStatus.java new file mode 100644 index 00000000..899f609e --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/RobotStatus.java @@ -0,0 +1,8 @@ +package org.camunda.rpa.client.data.constants; + +public enum RobotStatus { + + IN_PROGRESS, + SUCCESS, + FAILED; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/RobotType.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/RobotType.java new file mode 100644 index 00000000..952bae31 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/constants/RobotType.java @@ -0,0 +1,10 @@ +package org.camunda.rpa.client.data.constants; + +/** + * This class is to keep the different types of robots available in the system. + */ +public enum RobotType { + + ROBOCORP_RCC, + ROBOCORP_CLOUD +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/entity/IEntity.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/entity/IEntity.java new file mode 100644 index 00000000..3a2fa3a4 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/entity/IEntity.java @@ -0,0 +1,4 @@ +package org.camunda.rpa.client.data.entity; + +public interface IEntity { +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/entity/RobotHandlerAudit.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/entity/RobotHandlerAudit.java new file mode 100644 index 00000000..572e229e --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/entity/RobotHandlerAudit.java @@ -0,0 +1,56 @@ +package org.camunda.rpa.client.data.entity; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import javax.persistence.*; + +import org.camunda.rpa.client.data.constants.RobotStatus; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Sneha Suresh + * + */ +@Entity +@Table(name = "rpa_client_audit_handler") +@NoArgsConstructor +@Data +public class RobotHandlerAudit implements IEntity, Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false, length = 20) + private RobotStatus status; + + @Column(name = "task_id", nullable = false, length = 50) + private String taskId; + + @Column(name = "details") + private String details; + + //In case of cloud & file response type - this link can be used to download remote response + @Column(name = "response_uri") + private String responseUri; + + @OneToOne(optional=false) + @JoinColumn(name = "handler_id", nullable=false, updatable=false) + private RobotHandlerConfig handlerConfig; + + @Column(name = "created_date") + @CreationTimestamp + private LocalDateTime createdDate; + + @Column(name = "modified_date") + @UpdateTimestamp + private LocalDateTime modifiedDate; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/entity/RobotHandlerConfig.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/entity/RobotHandlerConfig.java new file mode 100644 index 00000000..3b0da420 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/entity/RobotHandlerConfig.java @@ -0,0 +1,79 @@ +package org.camunda.rpa.client.data.entity; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Transient; + +import org.camunda.rpa.client.data.constants.RobotResponseType; +import org.hibernate.annotations.CreationTimestamp; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Sneha Suresh + * + */ +@Entity +@Table(name = "rpa_client_handler_config") +@NoArgsConstructor +@AllArgsConstructor +@Data +public class RobotHandlerConfig implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "handler_id") + private Integer handlerId; + + @Column(name = "robot_name", length = 250) + private String robotName; + + @Column(name = "is_active", nullable = false) + private Boolean isActive; + + // topic name of the External Service Task + @Column(name = "topic_name", nullable = false) + private String topicName; + + // Task file name eg: task.robot / task.py + @Column(name = "task_file_name") + private String taskFileName; + + // our business logic may need variables + @Column(name = "variable_names") + private String variableNames; + + // only filter for External Tasks with this process definition key + @Column(name = "process_definition_key", nullable = false) + private String processDefinitionKey; + + @Enumerated(EnumType.STRING) + @Column(name = "response_type", nullable = false, length = 20) + private RobotResponseType responseType; + + @Transient + private String workingDirName; + + @Column(name = "description", length = 500) + private String description; + + @Column(name = "created_date") + @CreationTimestamp + private LocalDateTime createdDate; + + @Column(name = "workspace_id") + private String workspaceId; + + @Column(name = "process_id") + private String processId; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/repository/RepoFinder.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/repository/RepoFinder.java new file mode 100644 index 00000000..4935d0f9 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/repository/RepoFinder.java @@ -0,0 +1,14 @@ +package org.camunda.rpa.client.data.repository; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Autowired; + +@Data +public class RepoFinder { + + @Autowired + private RobotHandlerAuditRepository auditRepository; + + @Autowired + private RobotHandlerConfigRepository configRepository; +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/repository/RobotHandlerAuditRepository.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/repository/RobotHandlerAuditRepository.java new file mode 100644 index 00000000..428e3b77 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/repository/RobotHandlerAuditRepository.java @@ -0,0 +1,14 @@ +package org.camunda.rpa.client.data.repository; + +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Sneha Suresh + * + */ +@Repository +public interface RobotHandlerAuditRepository extends CrudRepository { + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/repository/RobotHandlerConfigRepository.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/repository/RobotHandlerConfigRepository.java new file mode 100644 index 00000000..c5efc6b9 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/data/repository/RobotHandlerConfigRepository.java @@ -0,0 +1,14 @@ +package org.camunda.rpa.client.data.repository; + +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Sneha Suresh + * + */ +@Repository +public interface RobotHandlerConfigRepository extends CrudRepository { + +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/FormioIdentityException.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/FormioIdentityException.java new file mode 100644 index 00000000..3c41c3c6 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/FormioIdentityException.java @@ -0,0 +1,17 @@ +package org.camunda.rpa.client.exception; + +/** + * To throw during the identity provider setup / token refresh + */ +public class FormioIdentityException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + public FormioIdentityException(String message) { + super(message); + } + + public FormioIdentityException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/FormioServiceException.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/FormioServiceException.java new file mode 100644 index 00000000..afa05685 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/FormioServiceException.java @@ -0,0 +1,17 @@ +package org.camunda.rpa.client.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * Specialized exception class for formio calls. + * + * @author sumathi.thirumani@aot-technologies.com + */ +@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) +public class FormioServiceException extends RuntimeException { + + public FormioServiceException(String message) { + super(message); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/IdentityProviderException.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/IdentityProviderException.java new file mode 100644 index 00000000..2582e671 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/IdentityProviderException.java @@ -0,0 +1,17 @@ +package org.camunda.rpa.client.exception; + +/** + * To throw during the identity provider setup / token refresh + */ +public class IdentityProviderException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + public IdentityProviderException(String message) { + super(message); + } + + public IdentityProviderException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/RobotClientDataException.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/RobotClientDataException.java new file mode 100644 index 00000000..aebf6c81 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/RobotClientDataException.java @@ -0,0 +1,11 @@ +package org.camunda.rpa.client.exception; + +/** + * Data exception for robot client + */ +public class RobotClientDataException extends RuntimeException{ + + public RobotClientDataException(String message){ + super(message); + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/RobotClientRuntimeException.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/RobotClientRuntimeException.java new file mode 100644 index 00000000..f008afd4 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/exception/RobotClientRuntimeException.java @@ -0,0 +1,16 @@ +package org.camunda.rpa.client.exception; + +import lombok.Data; + +/** + * To throw during the robot runtime + */ +@Data +public class RobotClientRuntimeException extends RuntimeException { + private Integer status; + + public RobotClientRuntimeException(String message, Integer status) { + super(message); + this.status = status; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/IRobotHandler.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/IRobotHandler.java new file mode 100644 index 00000000..5b1739ba --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/IRobotHandler.java @@ -0,0 +1,32 @@ +package org.camunda.rpa.client.handlers; + +import org.camunda.bpm.client.task.ExternalTaskService; +import org.camunda.rpa.client.data.RobotInput; +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; +import org.camunda.bpm.client.task.ExternalTask; +import org.camunda.bpm.engine.variable.VariableMap; + +import java.util.List; +import java.util.Map; + +/** + * + * @author Shibin Thomas + * + */ +public interface IRobotHandler extends TaskHandler{ + + RobotHandlerAudit startAudit(ExternalTask externalTask); + List buildInputWithAdditionalVariables(ExternalTask externalTask, RobotHandlerConfig config, Map additionalVariables); + List buildInput(ExternalTask externalTask, RobotHandlerConfig config); + boolean runRobot(List robotInputs, RobotHandlerAudit audit); + VariableMap collectResponse(RobotHandlerAudit robotHandlerAudit, String formUrl); + void completeAudit(RobotHandlerAudit audit, boolean status, String errorDetails); + void handleFailure(ExternalTask externalTask, ExternalTaskService externalTaskService, RobotHandlerAudit robotHandlerAudit, String message); + void doCleanup(RobotHandlerAudit robotHandlerAudit); + + default Integer getHandlerId(){ + return -1; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/TaskHandler.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/TaskHandler.java new file mode 100644 index 00000000..3fb1a7af --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/TaskHandler.java @@ -0,0 +1,8 @@ +package org.camunda.rpa.client.handlers; + +import org.camunda.bpm.client.task.ExternalTask; +import org.camunda.bpm.client.task.ExternalTaskService; + +public interface TaskHandler { + void subscribe(ExternalTask externalTask, ExternalTaskService externalTaskService); +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/TaskHandlerManager.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/TaskHandlerManager.java new file mode 100644 index 00000000..58e636bd --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/TaskHandlerManager.java @@ -0,0 +1,93 @@ +package org.camunda.rpa.client.handlers; + +import org.camunda.bpm.client.task.ExternalTask; +import org.camunda.bpm.client.task.ExternalTaskHandler; +import org.camunda.bpm.client.task.ExternalTaskService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public abstract class TaskHandlerManager implements ExternalTaskHandler,TaskHandler { + + private static final Logger LOG = LoggerFactory.getLogger(TaskHandlerManager.class); + + private static final Map activeTaskIdMap = new HashMap<>(); + + /** + * Not a function to override + * @param externalTask + * @param externalTaskService + */ + @Override + public void execute(ExternalTask externalTask, ExternalTaskService externalTaskService) { + String taskId = externalTask.getId(); + String topicName = externalTask.getTopicName(); + + // Allowing multiple robots to run in parallel in the starting will create installation chaos + if(activeTaskIdMap.containsKey(topicName)){ + LOG.info("Active tasks running for topicName :"+topicName); + return; + } else { + LOG.debug("Locking topicName = "+topicName+" for installation"); + activeTaskIdMap.put(topicName, taskId); + } + + Thread taskThread = new Thread(() -> { + try { + LOG.debug("START: Handler will be invoked to run robots"); + this.subscribe(externalTask, externalTaskService); + LOG.debug("END: Handler will be invoked to run robots"); + } finally { + activeTaskIdMap.remove(topicName); + LOG.info("Removed the taskId = "+taskId+", topicName :"+topicName); + } + }); + taskThread.start(); + } + + /** + * Not a function to override + * @param externalTask + * @param externalTaskService + */ + /*@Override + public void execute(ExternalTask externalTask, ExternalTaskService externalTaskService) { + String taskId = externalTask.getId(); + String topicName = externalTask.getTopicName(); + + // Allowing multiple robots to run in parallel in the starting will create installation chaos + int limit = (installLock.containsKey(topicName) && !installLock.get(topicName)) ? 2 : 0; + if(activeTaskIdMap.containsKey(topicName)){ + List taskIds = activeTaskIdMap.get(topicName); + LOG.info("Active tasks running for topicName :"+topicName+" is "+taskIds.size()); + if (taskIds.contains(taskId) || taskIds.size() > limit){ + return; + } + } else { + LOG.debug("Locking topicName = "+topicName+" for installation"); + installLock.put(topicName, true); + activeTaskIdMap.put(topicName, new ArrayList<>()); + } + + List taskIds = activeTaskIdMap.get(topicName); + if (!taskIds.contains(taskId) && taskIds.size() <= limit) { + LOG.info("New tak id added for processing " + taskId+" for topic - "+topicName); + taskIds.add(taskId); + Thread taskThread = new Thread(() -> { + try { + LOG.debug("START: Handler will be invoked to run robots"); + this.subscribe(externalTask, externalTaskService); + LOG.debug("END: Handler will be invoked to run robots"); + } finally { + taskIds.remove(taskId); + if(installLock.get(topicName)) { + installLock.put(topicName, false); + } + LOG.info("Removed the taskId = "+taskId+", active tasks running for topicName :"+topicName+" is "+taskIds.size()); + } + }); + taskThread.start(); + } + }*/ +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/impl/BackgroundCheckRobotHandler.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/impl/BackgroundCheckRobotHandler.java new file mode 100644 index 00000000..1e3bfb00 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/impl/BackgroundCheckRobotHandler.java @@ -0,0 +1,178 @@ +package org.camunda.rpa.client.handlers.impl; + +import org.camunda.rpa.client.core.RobotDataManager; +import org.camunda.rpa.client.data.RobotInput; +import org.camunda.rpa.client.core.RobotIOManager; +import org.camunda.rpa.client.core.RobotManager; +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; + +import org.camunda.bpm.client.spring.annotation.ExternalTaskSubscription; +import org.camunda.bpm.client.task.ExternalTask; +import org.camunda.bpm.client.task.ExternalTaskService; +import org.camunda.bpm.engine.variable.VariableMap; +import org.camunda.rpa.client.handlers.IRobotHandler; +import org.camunda.rpa.client.handlers.TaskHandlerManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class is responsible to handle the background-check robot + * + * @author Shibin Thomas + * + */ +@Component +@ExternalTaskSubscription("background-check") +public class BackgroundCheckRobotHandler extends TaskHandlerManager implements IRobotHandler { + + private static final Logger LOG = LoggerFactory.getLogger(BackgroundCheckRobotHandler.class); + + private static final Integer HANDLER_ID = 1; + + @Autowired + private RobotIOManager robotIOManager; + + @Autowired + private RobotDataManager robotDataManager; + + @Autowired + private RobotManager robotManager; + + /** + * This method is used to manage and run the robot + * + * @param externalTask + * @param externalTaskService + * + */ + @Override + public void subscribe(ExternalTask externalTask, ExternalTaskService externalTaskService) { + + LOG.debug("START - executing background check service"); + + RobotHandlerAudit robotHandlerAudit = startAudit(externalTask); + + try { + Map additionalVariables = new HashMap<>(); + additionalVariables.put("working-dir", robotHandlerAudit.getHandlerConfig().getWorkingDirName()); + + List robotInputs = buildInputWithAdditionalVariables(externalTask, + robotHandlerAudit.getHandlerConfig(), additionalVariables); + + boolean status = runRobot(robotInputs, robotHandlerAudit); + + VariableMap variableMap = null; + if(status) { + variableMap = collectResponse(robotHandlerAudit, externalTask.getVariable("formUrl")); + } + externalTaskService.complete(externalTask, variableMap); + + completeAudit(robotHandlerAudit, true, null); + + } catch (Exception ex) { + ex.printStackTrace(); + handleFailure(externalTask, externalTaskService, robotHandlerAudit, ex.getMessage()); + } finally { + doCleanup(robotHandlerAudit); + } + + LOG.debug("END - executing background check service"); + } + + /** + * Create a new entry in the Audit table to acknowledge that the robot started successfully. + * This method invokes initAudit() to update the robot current status + * @param externalTask + * + */ + @Override + public RobotHandlerAudit startAudit(ExternalTask externalTask) { + return robotDataManager.initAudit(externalTask, getHandlerId()); + } + + /** + * This method will build input with additional variables for robot by invoking buildInput() method. + * @param externalTask + * @param config + * @param additionalVariables + * + */ + @Override + public List buildInputWithAdditionalVariables(ExternalTask externalTask, RobotHandlerConfig config, + Map additionalVariables) { + return robotIOManager.buildInput(externalTask, config, additionalVariables); + } + + /** + * This is an overriden method used to build the Input. + * @param externalTask + * @param config + * + */ + @Override + public List buildInput(ExternalTask externalTask, RobotHandlerConfig config) { + + return null; + } + + /** + * This method invokes runRobot() to find out the robot type, build a script with input parameters and run using script manager + * @param robotInputs + * @param audit + * + */ + @Override + public boolean runRobot(List robotInputs, RobotHandlerAudit audit) { + return robotManager.runRobot(robotInputs, audit); + } + + /** + * Once robot completed its task successfully, this method invokes and collect the response. + * @param robotHandlerAudit + * @return + * + */ + @Override + public VariableMap collectResponse(RobotHandlerAudit robotHandlerAudit, String formUrl) { + return robotIOManager.getOutputData(robotHandlerAudit, formUrl); + } + + /** + * Complete the audit process and update the status in audit table + * @param audit + * @param status + * @param errorDetails + * + */ + @Override + public void completeAudit(RobotHandlerAudit audit, boolean status, String errorDetails) { + robotDataManager.finalizeAudit(audit, status, errorDetails); + } + + @Override + public void handleFailure(ExternalTask externalTask, ExternalTaskService externalTaskService, RobotHandlerAudit robotHandlerAudit, String message) { + completeAudit(robotHandlerAudit, false, message); + externalTaskService.handleFailure(externalTask, message, message, 0, 0); + } + + @Override + public void doCleanup(RobotHandlerAudit robotHandlerAudit) { + robotIOManager.clearAndRemoveOutputDirectory(robotHandlerAudit); + } + + /** + * @return + * + */ + @Override + public Integer getHandlerId() { + return HANDLER_ID; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/impl/WebScraperRobotHandler.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/impl/WebScraperRobotHandler.java new file mode 100644 index 00000000..e2e880b1 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/handlers/impl/WebScraperRobotHandler.java @@ -0,0 +1,172 @@ +package org.camunda.rpa.client.handlers.impl; + +import org.camunda.bpm.client.spring.annotation.ExternalTaskSubscription; +import org.camunda.bpm.client.task.ExternalTask; +import org.camunda.bpm.client.task.ExternalTaskService; +import org.camunda.bpm.engine.variable.VariableMap; +import org.camunda.rpa.client.core.RobotDataManager; +import org.camunda.rpa.client.core.RobotIOManager; +import org.camunda.rpa.client.core.RobotManager; +import org.camunda.rpa.client.data.RobotInput; +import org.camunda.rpa.client.data.entity.RobotHandlerAudit; +import org.camunda.rpa.client.data.entity.RobotHandlerConfig; +import org.camunda.rpa.client.handlers.IRobotHandler; +import org.camunda.rpa.client.handlers.TaskHandlerManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class is responsible to handle the web-scraper robot + * + * @author Shibin Thomas + * + */ +@Component +@ExternalTaskSubscription("web-scraper") +public class WebScraperRobotHandler extends TaskHandlerManager implements IRobotHandler { + + private static final Logger LOG = LoggerFactory.getLogger(WebScraperRobotHandler.class); + + private static final Integer HANDLER_ID = 2; + + @Autowired + private RobotIOManager robotIOManager; + + @Autowired + private RobotDataManager robotDataManager; + + @Autowired + private RobotManager robotManager; + + /** + * This method is used to manage and run the robot + * + * @param externalTask + * @param externalTaskService + * + */ + @Override + public void subscribe(ExternalTask externalTask, ExternalTaskService externalTaskService) { + LOG.debug("START - executing web-scraper service"); + + RobotHandlerAudit robotHandlerAudit = startAudit(externalTask); + + try { + Map additionalVariables = new HashMap<>(); + additionalVariables.put("working-dir", robotHandlerAudit.getHandlerConfig().getWorkingDirName()); + + List robotInputs = buildInputWithAdditionalVariables(externalTask, + robotHandlerAudit.getHandlerConfig(), additionalVariables); + + boolean status = runRobot(robotInputs, robotHandlerAudit); + + VariableMap variableMap = null; + if(status) { + variableMap = collectResponse(robotHandlerAudit, externalTask.getVariable("formUrl")); + } + externalTaskService.complete(externalTask, variableMap); + + completeAudit(robotHandlerAudit, true, null); + + } catch (Exception ex) { + ex.printStackTrace(); + handleFailure(externalTask, externalTaskService, robotHandlerAudit, ex.getMessage()); + } finally { + doCleanup(robotHandlerAudit); + } + + LOG.debug("END - executing web scraper service"); + } + + /** + * Create a new entry in the Audit table to acknowledge that the robot started successfully. + * This method invokes initAudit() to update the robot current status + * @param externalTask + * + */ + @Override + public RobotHandlerAudit startAudit(ExternalTask externalTask) { + return robotDataManager.initAudit(externalTask, getHandlerId()); + } + + /** + * This method will build input with additional variables for robot by invoking buildInput() method. + * @param externalTask + * @param config + * @param additionalVariables + * + */ + @Override + public List buildInputWithAdditionalVariables(ExternalTask externalTask, RobotHandlerConfig config, + Map additionalVariables) { + return robotIOManager.buildInput(externalTask, config, additionalVariables); + } + + /** + * This is an overriden method used to build the Input. + * @param externalTask + * @param config + * + */ + @Override + public List buildInput(ExternalTask externalTask, RobotHandlerConfig config) { + + return null; + } + + /** + * This method invokes runRobot() to find out the robot type, build a script with input parameters and run using script manager + * @param robotInputs + * @param audit + * + */ + @Override + public boolean runRobot(List robotInputs, RobotHandlerAudit audit) { + return robotManager.runRobot(robotInputs, audit); + } + + /** + * Once robot completed its task successfully, this method invokes and collect the response. + * @param robotHandlerAudit + * @return + * + */ + @Override + public VariableMap collectResponse(RobotHandlerAudit robotHandlerAudit, String formUrl) { + return robotIOManager.getOutputData(robotHandlerAudit, formUrl); + } + + /** + * Complete the audit process and update the status in audit table + * @param audit + * @param status + * @param errorDetails + * + */ + @Override + public void completeAudit(RobotHandlerAudit audit, boolean status, String errorDetails) { + robotDataManager.finalizeAudit(audit, status, errorDetails); + } + + @Override + public void handleFailure(ExternalTask externalTask, ExternalTaskService externalTaskService, RobotHandlerAudit robotHandlerAudit, String message) { + completeAudit(robotHandlerAudit, false, message); + externalTaskService.handleFailure(externalTask, message, message, 0, 0); + } + + @Override + public void doCleanup(RobotHandlerAudit robotHandlerAudit) { + robotIOManager.clearAndRemoveOutputDirectory(robotHandlerAudit); + } + + @Override + public Integer getHandlerId() { + return HANDLER_ID; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/util/ObjectUtil.java b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/util/ObjectUtil.java new file mode 100644 index 00000000..0ef5f00e --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/java/org/camunda/rpa/client/util/ObjectUtil.java @@ -0,0 +1,31 @@ +package org.camunda.rpa.client.util; + +import java.util.Map; + +/** + * author : Shibin Thomas + */ +public class ObjectUtil { + + /** + * + * @param entry + * @param key + * @return + */ + public static String convertKeyToString(Map entry, String key){ + if(entry == null) return null; + return entry.containsKey(key)?entry.get(key).toString():null; + } + + /** + * + * @param entry + * @param key + * @return + */ + public static Long convertKeyToLong(Map entry, String key){ + if(entry == null) return 0L; + return entry.containsKey(key)?Long.parseLong(String.valueOf(entry.get(key))):0L; + } +} diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/application.yaml b/rpa-robocorp-extention/external-client-extention/src/main/resources/application.yaml new file mode 100644 index 00000000..79006435 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/application.yaml @@ -0,0 +1,62 @@ +# Set variables + +keycloak.url: ${KEYCLOAK_URL} +keycloak.url.realm: ${KEYCLOAK_URL_REALM} +keycloak.clientId: ${KEYCLOAK_CLIENTID} +keycloak.clientSecret: ${KEYCLOAK_CLIENTSECRET} +apiKey: ${ROBOCORP_CLOUD_APIKEY} + +spring: + datasource: + url: ${RPA_JDBC_URL:jdbc:h2:./camunda-rpa-client-db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE} + username: ${RPA_JDBC_USER:sa} + password: ${RPA_JDBC_PASSWORD:sa} + driverClassName: ${RPA_JDBC_DRIVER:org.h2.Driver} + type: com.zaxxer.hikari.HikariDataSource + connectionTimeout: ${RPA_HIKARI_CONN_TIMEOUT:30000} + idleTimeout: ${RPA_HIKARI_IDLE_TIMEOUT:600000} + maximumPoolSize: ${RPA_HIKARI_MAX_POOLSIZE:10} + validationTimeout: ${RPA_HIKARI_VALID_TIMEOUT:5000} + liquibase: + change-log: classpath:/db/changelog/db-changelog-master.xml + webclient: + max-buffer-size: 20 + +formsflow: + formio: + enabled: ${FORMIO_ENABLED:true} + url: ${FORMIO_URL} + security: + access-token-uri: ${FORMIO_URL}/user/login + username: ${FORMIO_USERNAME} + password: ${FORMIO_PASSWORD} + +robot: + base-dir: ${ROBOT_BASE_DIR:} + robot-type: ${ROBOT_TYPE:ROBOCORP_RCC} + cloud: + api-url: ${ROBOCORP_CLOUD_BASE_URL}/process-v1/workspaces + api-key: ${apiKey} + + +client: + base-url: ${CAMUNDA_BPM_URL}/camunda/engine-rest # the URL pointing to the Camunda Platform Runtime REST API + lock-duration: ${LOCK_DURATION:3000} # defines how many milliseconds the External Tasks are locked until they can be fetched again + max-tasks: ${MAX_TASKS:10} + disable-backoff-strategy: true + async-response-timeout: 10000 + auth: + client-id: ${keycloak.clientId} + client-secret: ${keycloak.clientSecret} + token-uri: ${keycloak.url}/auth/realms/${keycloak.url.realm}/protocol/openid-connect/token + grant_type: client_credentials + +server: + port: 8090 + +logging: + level: + org.springframework.security: ${RPA_APP_LOG_LEVEL:error} + org.springframework.jdbc: ${RPA_APP_LOG_LEVEL:error} + org.camunda.bpm.client: ${RPA_APP_LOG_LEVEL:error} + org.camunda.rpa.client: ${RPA_APP_LOG_LEVEL:error} \ No newline at end of file diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/db/changelog/db-changelog-master.xml b/rpa-robocorp-extention/external-client-extention/src/main/resources/db/changelog/db-changelog-master.xml new file mode 100644 index 00000000..cef6aab3 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/db/changelog/db-changelog-master.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into rpa_client_handler_config values(1, + 'background-check', true, 'FILE', 'background-check', 'tasks.robot', + 'businessOperatingName,businessWebsite,formUrl', + 'two-step-approval-with-rpa', 'New Business Background checker + robot', CURRENT_DATE, '92ac4b6f-a891-4bab-9b8c-c3022d88d690', + 'a52dd66a-fe47-4db7-aa3e-769ad09a83b4') + + insert into rpa_client_handler_config values(2, 'web-scraper', + true, 'FILE', 'web-scraper', 'tasks.robot', + 'organizationName,formUrl', 'onestepapprovalwithrpa', 'Freedom Of + Information Background checker robot', CURRENT_DATE, + '92ac4b6f-a891-4bab-9b8c-c3022d88d690', + 'a52dd66a-fe47-4db7-aa3e-769ad09a83b4') + + + diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/chromedriver.log b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/chromedriver.log new file mode 100644 index 00000000..4c71a3e9 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/chromedriver.log @@ -0,0 +1,1212 @@ +[1642768233.604][INFO]: Starting ChromeDriver 97.0.4692.36 (747e0a0f19c13ca6ee136200f5b097448ae4892f-refs/branch-heads/4692@{#607}) on port 63985 +[1642768233.604][INFO]: Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe. +[1642768234.129][INFO]: [7877b0b61dac0daae69a230280b72e8a] COMMAND InitSession { + "capabilities": { + "alwaysMatch": { + "browserName": "chrome", + "goog:chromeOptions": { + "args": [ "--disable-dev-shm-usage", "--disable-web-security", "--allow-running-insecure-content", "--no-sandbox" ], + "excludeSwitches": [ "enable-logging", "enable-automation" ], + "extensions": [ ], + "prefs": { + "credentials_enable_service": false, + "profile.password_manager_enabled": false, + "safebrowsing.enabled": true + } + }, + "platformName": "any" + }, + "firstMatch": [ { + + } ] + }, + "desiredCapabilities": { + "browserName": "chrome", + "goog:chromeOptions": { + "args": [ "--disable-dev-shm-usage", "--disable-web-security", "--allow-running-insecure-content", "--no-sandbox" ], + "excludeSwitches": [ "enable-logging", "enable-automation" ], + "extensions": [ ], + "prefs": { + "credentials_enable_service": false, + "profile.password_manager_enabled": false, + "safebrowsing.enabled": true + } + }, + "platform": "ANY", + "version": "" + } +} +[1642768234.135][INFO]: Populating Preferences file: { + "alternate_error_pages": { + "enabled": false + }, + "autofill": { + "enabled": false + }, + "browser": { + "check_default_browser": false + }, + "credentials_enable_service": false, + "distribution": { + "import_bookmarks": false, + "import_history": false, + "import_search_engine": false, + "make_chrome_default_for_user": false, + "skip_first_run_ui": true + }, + "dns_prefetching": { + "enabled": false + }, + "profile": { + "content_settings": { + "pattern_pairs": { + "https://*,*": { + "media-stream": { + "audio": "Default", + "video": "Default" + } + } + } + }, + "default_content_setting_values": { + "geolocation": 1 + }, + "default_content_settings": { + "geolocation": 1, + "mouselock": 1, + "notifications": 1, + "popups": 1, + "ppapi-broker": 1 + }, + "password_manager_enabled": false + }, + "safebrowsing": { + "enabled": true + }, + "search": { + "suggest_enabled": false + }, + "translate": { + "enabled": false + } +} +[1642768234.136][INFO]: Populating Local State file: { + "background_mode": { + "enabled": false + }, + "ssl": { + "rev_checking": { + "enabled": false + } + } +} +[1642768234.139][INFO]: Launching chrome: "C:\Program Files\Google\Chrome\Application\chrome.exe" --allow-pre-commit-input --allow-running-insecure-content --disable-background-networking --disable-backgrounding-occluded-windows --disable-client-side-phishing-detection --disable-default-apps --disable-dev-shm-usage --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --disable-web-security --enable-blink-features=ShadowDOMV0 --log-level=0 --no-first-run --no-sandbox --no-service-autorun --password-store=basic --remote-debugging-port=0 --test-type=webdriver --use-mock-keychain --user-data-dir="C:\Users\DELL\AppData\Local\robocorp\temp\a68bc07d8def94b9\scoped_dir3980_150512762" data:, +[1642768234.462][DEBUG]: DevTools HTTP Request: http://localhost:63991/json/version +[1642768234.786][DEBUG]: DevTools HTTP Response: { + "Browser": "Chrome/97.0.4692.71", + "Protocol-Version": "1.3", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36", + "V8-Version": "9.7.106.18", + "WebKit-Version": "537.36 (@adefa7837d02a07a604c1e6eff0b3a09422ab88d)", + "webSocketDebuggerUrl": "ws://localhost:63991/devtools/browser/d67df48b-dec4-470a-86c2-03495a66c009" +} + +[1642768234.787][DEBUG]: DevTools HTTP Request: http://localhost:63991/json/list +[1642768234.789][DEBUG]: DevTools HTTP Response: [ { + "description": "", + "devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:63991/devtools/page/EBDCB326EE7911F3266EA857DCFF7A23", + "id": "EBDCB326EE7911F3266EA857DCFF7A23", + "title": "data:,", + "type": "page", + "url": "data:,", + "webSocketDebuggerUrl": "ws://localhost:63991/devtools/page/EBDCB326EE7911F3266EA857DCFF7A23" +} ] + +[1642768234.789][DEBUG]: DevTools HTTP Request: http://localhost:63991/json/list +[1642768234.792][DEBUG]: DevTools HTTP Response: [ { + "description": "", + "devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:63991/devtools/page/EBDCB326EE7911F3266EA857DCFF7A23", + "id": "EBDCB326EE7911F3266EA857DCFF7A23", + "title": "data:,", + "type": "page", + "url": "data:,", + "webSocketDebuggerUrl": "ws://localhost:63991/devtools/page/EBDCB326EE7911F3266EA857DCFF7A23" +} ] + +[1642768234.801][INFO]: resolved localhost to ["::1","127.0.0.1"] +[1642768234.804][DEBUG]: DevTools WebSocket Command: Page.addScriptToEvaluateOnNewDocument (id=1) EBDCB326EE7911F3266EA857DCFF7A23 { + "source": "(function () {window.cdc_adoQpoasnfa76pfcZLmcfl_Array = window.Array;window.cdc_adoQpoasnfa76pfcZLmcfl_Promise = window.Promise;window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol = window.Symbol;}) ();" +} +[1642768234.804][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=2) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "(function () {window.cdc_adoQpoasnfa76pfcZLmcfl_Array = window.Array;window.cdc_adoQpoasnfa76pfcZLmcfl_Promise = window.Promise;window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol = window.Symbol;}) ();" +} +[1642768234.804][DEBUG]: DevTools WebSocket Command: Log.enable (id=3) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.804][DEBUG]: DevTools WebSocket Command: DOM.getDocument (id=4) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.804][DEBUG]: DevTools WebSocket Command: Target.setAutoAttach (id=5) EBDCB326EE7911F3266EA857DCFF7A23 { + "autoAttach": true, + "flatten": true, + "waitForDebuggerOnStart": false +} +[1642768234.804][DEBUG]: DevTools WebSocket Command: Page.enable (id=6) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.804][DEBUG]: DevTools WebSocket Command: Page.enable (id=7) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.805][DEBUG]: DevTools WebSocket Response: Page.addScriptToEvaluateOnNewDocument (id=1) EBDCB326EE7911F3266EA857DCFF7A23 { + "identifier": "1" +} +[1642768234.805][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=2) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "type": "undefined" + } +} +[1642768234.805][DEBUG]: DevTools WebSocket Response: Log.enable (id=3) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.805][DEBUG]: DevTools WebSocket Response: DOM.getDocument (id=4) EBDCB326EE7911F3266EA857DCFF7A23 { + "root": { + "backendNodeId": 1, + "baseURL": "data:,", + "childNodeCount": 1, + "children": [ { + "attributes": [ ], + "backendNodeId": 2, + "childNodeCount": 2, + "children": [ { + "attributes": [ ], + "backendNodeId": 3, + "childNodeCount": 0, + "localName": "head", + "nodeId": 3, + "nodeName": "HEAD", + "nodeType": 1, + "nodeValue": "", + "parentId": 2 + }, { + "attributes": [ ], + "backendNodeId": 4, + "childNodeCount": 0, + "localName": "body", + "nodeId": 4, + "nodeName": "BODY", + "nodeType": 1, + "nodeValue": "", + "parentId": 2 + } ], + "frameId": "EBDCB326EE7911F3266EA857DCFF7A23", + "localName": "html", + "nodeId": 2, + "nodeName": "HTML", + "nodeType": 1, + "nodeValue": "", + "parentId": 1 + } ], + "compatibilityMode": "NoQuirksMode", + "documentURL": "data:,", + "localName": "", + "nodeId": 1, + "nodeName": "#document", + "nodeType": 9, + "nodeValue": "", + "xmlVersion": "" + } +} +[1642768234.805][DEBUG]: DevTools WebSocket Response: Target.setAutoAttach (id=5) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.805][DEBUG]: DevTools WebSocket Response: Page.enable (id=6) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.805][DEBUG]: DevTools WebSocket Response: Page.enable (id=7) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.805][DEBUG]: DevTools WebSocket Command: Runtime.enable (id=8) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.805][DEBUG]: DevTools WebSocket Event: Runtime.executionContextCreated EBDCB326EE7911F3266EA857DCFF7A23 { + "context": { + "auxData": { + "frameId": "EBDCB326EE7911F3266EA857DCFF7A23", + "isDefault": true, + "type": "default" + }, + "id": 1, + "name": "", + "origin": "://", + "uniqueId": "-5811666141198061506.-5623120982200588376" + } +} +[1642768234.805][DEBUG]: DevTools WebSocket Response: Runtime.enable (id=8) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.805][DEBUG]: DevTools WebSocket Command: Page.enable (id=9) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.806][DEBUG]: DevTools WebSocket Response: Page.enable (id=9) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.806][DEBUG]: DevTools WebSocket Command: Runtime.enable (id=10) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.809][DEBUG]: DevTools WebSocket Response: Runtime.enable (id=10) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.809][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=11) EBDCB326EE7911F3266EA857DCFF7A23 { + "awaitPromise": true, + "expression": "(function() { // Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n/**\n * Enum f...", + "returnByValue": true +} +[1642768234.814][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=11) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "type": "object", + "value": { + "status": 0, + "value": 1 + } + } +} +[1642768234.814][INFO]: [7877b0b61dac0daae69a230280b72e8a] RESPONSE InitSession { + "capabilities": { + "acceptInsecureCerts": false, + "browserName": "chrome", + "browserVersion": "97.0.4692.71", + "chrome": { + "chromedriverVersion": "97.0.4692.36 (747e0a0f19c13ca6ee136200f5b097448ae4892f-refs/branch-heads/4692@{#607})", + "userDataDir": "C:\\Users\\DELL\\AppData\\Local\\robocorp\\temp\\a68bc07d8def94b9\\scoped_dir3980_150512762" + }, + "goog:chromeOptions": { + "debuggerAddress": "localhost:63991" + }, + "networkConnectionEnabled": false, + "pageLoadStrategy": "normal", + "platformName": "windows", + "proxy": { + + }, + "setWindowRect": true, + "strictFileInteractability": false, + "timeouts": { + "implicit": 0, + "pageLoad": 300000, + "script": 30000 + }, + "unhandledPromptBehavior": "dismiss and notify", + "webauthn:extension:credBlob": true, + "webauthn:extension:largeBlob": true, + "webauthn:virtualAuthenticators": true + }, + "sessionId": "7877b0b61dac0daae69a230280b72e8a" +} +[1642768234.818][INFO]: [7877b0b61dac0daae69a230280b72e8a] COMMAND Navigate { + "url": "http://www.gs.com" +} +[1642768234.818][INFO]: Waiting for pending navigations... +[1642768234.818][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=12) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768234.819][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=12) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768234.819][DEBUG]: DevTools WebSocket Command: DOM.getDocument (id=13) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768234.820][DEBUG]: DevTools WebSocket Response: DOM.getDocument (id=13) EBDCB326EE7911F3266EA857DCFF7A23 { + "root": { + "backendNodeId": 1, + "baseURL": "data:,", + "childNodeCount": 1, + "children": [ { + "attributes": [ ], + "backendNodeId": 2, + "childNodeCount": 2, + "children": [ { + "attributes": [ ], + "backendNodeId": 3, + "childNodeCount": 0, + "localName": "head", + "nodeId": 7, + "nodeName": "HEAD", + "nodeType": 1, + "nodeValue": "", + "parentId": 6 + }, { + "attributes": [ ], + "backendNodeId": 4, + "childNodeCount": 0, + "localName": "body", + "nodeId": 8, + "nodeName": "BODY", + "nodeType": 1, + "nodeValue": "", + "parentId": 6 + } ], + "frameId": "EBDCB326EE7911F3266EA857DCFF7A23", + "localName": "html", + "nodeId": 6, + "nodeName": "HTML", + "nodeType": 1, + "nodeValue": "", + "parentId": 5 + } ], + "compatibilityMode": "NoQuirksMode", + "documentURL": "data:,", + "localName": "", + "nodeId": 5, + "nodeName": "#document", + "nodeType": 9, + "nodeValue": "", + "xmlVersion": "" + } +} +[1642768234.820][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=14) EBDCB326EE7911F3266EA857DCFF7A23 { + "awaitPromise": false, + "expression": "document.readyState", + "returnByValue": true +} +[1642768234.821][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=14) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "type": "string", + "value": "complete" + } +} +[1642768234.821][INFO]: Done waiting for pending navigations. Status: ok +[1642768234.821][DEBUG]: DevTools WebSocket Command: Page.navigate (id=15) EBDCB326EE7911F3266EA857DCFF7A23 { + "url": "http://www.gs.com" +} +[1642768235.761][DEBUG]: DevTools WebSocket Response: Page.navigate (id=15) EBDCB326EE7911F3266EA857DCFF7A23 { + "frameId": "EBDCB326EE7911F3266EA857DCFF7A23", + "loaderId": "DE9FABA8ED796A857BB2218290162DC8" +} +[1642768235.762][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=16) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "document.URL" +} +[1642768235.767][DEBUG]: DevTools WebSocket Event: Runtime.executionContextsCleared EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768235.768][DEBUG]: DevTools WebSocket Event: Page.frameStartedLoading EBDCB326EE7911F3266EA857DCFF7A23 { + "frameId": "EBDCB326EE7911F3266EA857DCFF7A23" +} +[1642768235.790][DEBUG]: DevTools WebSocket Event: Runtime.executionContextsCleared EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768235.790][DEBUG]: DevTools WebSocket Event: Page.frameNavigated EBDCB326EE7911F3266EA857DCFF7A23 { + "frame": { + "adFrameStatus": { + "adFrameType": "none" + }, + "crossOriginIsolatedContextType": "NotIsolated", + "domainAndRegistry": "goldmansachs.com", + "gatedAPIFeatures": [ ], + "id": "EBDCB326EE7911F3266EA857DCFF7A23", + "loaderId": "DE9FABA8ED796A857BB2218290162DC8", + "mimeType": "text/html", + "secureContextType": "Secure", + "securityOrigin": "https://www.goldmansachs.com", + "url": "https://www.goldmansachs.com/" + }, + "type": "Navigation" +} +[1642768235.790][DEBUG]: DevTools WebSocket Event: Runtime.executionContextCreated EBDCB326EE7911F3266EA857DCFF7A23 { + "context": { + "auxData": { + "frameId": "EBDCB326EE7911F3266EA857DCFF7A23", + "isDefault": true, + "type": "default" + }, + "id": 1, + "name": "", + "origin": "https://www.goldmansachs.com", + "uniqueId": "-302677859584493654.2588381363366894121" + } +} +[1642768235.790][DEBUG]: DevTools WebSocket Event: DOM.documentUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768235.790][DEBUG]: DevTools WebSocket Command: DOM.getDocument (id=17) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768235.800][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=16) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "type": "string", + "value": "https://www.goldmansachs.com/" + } +} +[1642768235.800][INFO]: Waiting for pending navigations... +[1642768235.800][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=18) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768235.802][DEBUG]: DevTools WebSocket Response: DOM.getDocument (id=17) EBDCB326EE7911F3266EA857DCFF7A23 { + "root": { + "backendNodeId": 1, + "baseURL": "https://www.goldmansachs.com/", + "childNodeCount": 0, + "children": [ ], + "compatibilityMode": "NoQuirksMode", + "documentURL": "https://www.goldmansachs.com/", + "localName": "", + "nodeId": 1, + "nodeName": "#document", + "nodeType": 9, + "nodeValue": "", + "xmlVersion": "" + } +} +[1642768235.802][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=18) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768235.817][DEBUG]: DevTools WebSocket Event: DOM.childNodeInserted EBDCB326EE7911F3266EA857DCFF7A23 { + "node": { + "backendNodeId": 2, + "localName": "", + "nodeId": 2, + "nodeName": "html", + "nodeType": 10, + "nodeValue": "", + "publicId": "", + "systemId": "" + }, + "parentNodeId": 1, + "previousNodeId": 0 +} +[1642768235.817][DEBUG]: DevTools WebSocket Event: DOM.childNodeInserted EBDCB326EE7911F3266EA857DCFF7A23 { + "node": { + "attributes": [ "lang", "en-US", "itemscope", "", "itemtype", "http://schema.org/Article" ], + "backendNodeId": 3, + "childNodeCount": 0, + "frameId": "EBDCB326EE7911F3266EA857DCFF7A23", + "localName": "html", + "nodeId": 3, + "nodeName": "HTML", + "nodeType": 1, + "nodeValue": "" + }, + "parentNodeId": 1, + "previousNodeId": 2 +} +[1642768235.817][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 1, + "nodeId": 3 +} +[1642768235.817][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=19) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768235.823][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=19) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768236.169][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 2, + "nodeId": 3 +} +[1642768236.169][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 1, + "nodeId": 3 +} +[1642768236.169][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 2, + "nodeId": 3 +} +[1642768236.169][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 1, + "nodeId": 3 +} +[1642768236.169][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": " js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video" +} +[1642768236.169][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": " js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder" +} +[1642768236.169][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 2, + "nodeId": 3 +} +[1642768236.169][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 1, + "nodeId": 3 +} +[1642768236.169][DEBUG]: DevTools WebSocket Event: Log.entryAdded EBDCB326EE7911F3266EA857DCFF7A23 { + "entry": { + "level": "warning", + "lineNumber": 362, + "source": "security", + "stackTrace": { + "callFrames": [ { + "columnNumber": 90, + "functionName": "Pb", + "lineNumber": 362, + "scriptId": "12", + "url": "https://www.goldmansachs.com/a/pgs/js/prod/gsAll.js" + }, { + "columnNumber": 380, + "functionName": "GS.Sitewide", + "lineNumber": 362, + "scriptId": "12", + "url": "https://www.goldmansachs.com/a/pgs/js/prod/gsAll.js" + }, { + "columnNumber": 468, + "functionName": "", + "lineNumber": 463, + "scriptId": "12", + "url": "https://www.goldmansachs.com/a/pgs/js/prod/gsAll.js" + }, { + "columnNumber": 484, + "functionName": "", + "lineNumber": 463, + "scriptId": "12", + "url": "https://www.goldmansachs.com/a/pgs/js/prod/gsAll.js" + } ] + }, + "text": "Mixed Content: The page at 'https://www.goldmansachs.com/' was loaded over HTTPS, but requested an insecure script 'http://home.web.gs.com/inettag.js'. This content should also be served over HTTPS.", + "timestamp": 1.642768236168736e+12, + "url": "https://www.goldmansachs.com/a/pgs/js/prod/gsAll.js" + } +} +[1642768236.169][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=20) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768236.175][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading" +} +[1642768236.180][DEBUG]: DevTools WebSocket Event: DOM.inlineStyleInvalidated EBDCB326EE7911F3266EA857DCFF7A23 { + "nodeIds": [ 3 ] +} +[1642768236.180][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=20) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0147lightcn-n4-loading" +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 2, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 1, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 2, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 1, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 2, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 3, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0147lightcn-n4-loading wf-universltw0155oblique-n4-loading" +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 4, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 3, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 4, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 3, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 4, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 5, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0147lightcn-n4-loading wf-universltw0155oblique-n4-loading wf-universlt..." +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 6, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 5, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 6, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 5, + "nodeId": 3 +} +[1642768236.213][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 6, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 7, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0147lightcn-n4-loading wf-universltw0155oblique-n4-loading wf-universlt..." +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 8, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 7, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 8, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 7, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 8, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 9, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0147lightcn-n4-loading wf-universltw0155oblique-n4-loading wf-universlt..." +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 10, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 9, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 10, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 9, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 10, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 11, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0147lightcn-n4-loading wf-universltw0155oblique-n4-loading wf-universlt..." +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 12, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 11, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 12, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 11, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 12, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 13, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0147lightcn-n4-loading wf-universltw0155oblique-n4-loading wf-universlt..." +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 14, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 13, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 14, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 13, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 14, + "nodeId": 3 +} +[1642768236.214][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 15, + "nodeId": 3 +} +[1642768236.215][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=21) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768236.225][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=21) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768236.239][DEBUG]: DevTools WebSocket Event: Page.frameAttached EBDCB326EE7911F3266EA857DCFF7A23 { + "frameId": "D7A230BB5A0E6775E6A1F34CF20E8CA9", + "parentFrameId": "EBDCB326EE7911F3266EA857DCFF7A23", + "stack": { + "callFrames": [ { + "columnNumber": 847, + "functionName": "", + "lineNumber": 137, + "scriptId": "23", + "url": "https://www.goldmansachs.com/" + }, { + "columnNumber": 1269, + "functionName": "", + "lineNumber": 137, + "scriptId": "23", + "url": "https://www.goldmansachs.com/" + }, { + "columnNumber": 3041, + "functionName": "", + "lineNumber": 137, + "scriptId": "23", + "url": "https://www.goldmansachs.com/" + } ] + } +} +[1642768236.239][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=22) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768236.239][DEBUG]: DevTools WebSocket Event: Page.frameStartedLoading EBDCB326EE7911F3266EA857DCFF7A23 { + "frameId": "D7A230BB5A0E6775E6A1F34CF20E8CA9" +} +[1642768236.250][DEBUG]: DevTools WebSocket Event: Runtime.executionContextCreated EBDCB326EE7911F3266EA857DCFF7A23 { + "context": { + "auxData": { + "frameId": "D7A230BB5A0E6775E6A1F34CF20E8CA9", + "isDefault": true, + "type": "default" + }, + "id": 2, + "name": "", + "origin": "https://www.goldmansachs.com", + "uniqueId": "-9092347270014312505.941165414401362757" + } +} +[1642768236.251][DEBUG]: DevTools WebSocket Event: Page.documentOpened EBDCB326EE7911F3266EA857DCFF7A23 { + "frame": { + "adFrameStatus": { + "adFrameType": "none", + "explanations": [ ] + }, + "crossOriginIsolatedContextType": "NotIsolated", + "domainAndRegistry": "goldmansachs.com", + "gatedAPIFeatures": [ ], + "id": "D7A230BB5A0E6775E6A1F34CF20E8CA9", + "loaderId": "C953959AF84B36D9830A5DA78BC9641C", + "mimeType": "text/html", + "name": "", + "parentId": "EBDCB326EE7911F3266EA857DCFF7A23", + "secureContextType": "Secure", + "securityOrigin": "https://www.goldmansachs.com", + "url": "https://www.goldmansachs.com/" + } +} +[1642768236.251][DEBUG]: DevTools WebSocket Event: Page.frameStoppedLoading EBDCB326EE7911F3266EA857DCFF7A23 { + "frameId": "D7A230BB5A0E6775E6A1F34CF20E8CA9" +} +[1642768236.333][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 16, + "nodeId": 3 +} +[1642768236.434][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=22) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768236.461][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 15, + "nodeId": 3 +} +[1642768236.461][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 14, + "nodeId": 3 +} +[1642768236.461][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0155oblique-n4-loading wf-universltw0155roman-n4-loading wf-universltw0..." +} +[1642768236.461][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=23) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768236.462][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 13, + "nodeId": 3 +} +[1642768236.462][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 12, + "nodeId": 3 +} +[1642768236.462][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0155oblique-n4-loading wf-universltw0157condense723821-n4-loading wf-un..." +} +[1642768236.462][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 11, + "nodeId": 3 +} +[1642768236.462][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 10, + "nodeId": 3 +} +[1642768236.462][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0155oblique-n4-loading wf-universltw0159ultracn-n4-loading wf-universlt..." +} +[1642768236.463][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 9, + "nodeId": 3 +} +[1642768236.463][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 8, + "nodeId": 3 +} +[1642768236.463][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0155oblique-n4-loading wf-universltw0165bold-n4-loading wf-universltw01..." +} +[1642768236.463][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 7, + "nodeId": 3 +} +[1642768236.463][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 6, + "nodeId": 3 +} +[1642768236.463][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0155oblique-n4-loading wf-universltw0167boldcn-n4-loading" +} +[1642768236.464][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 5, + "nodeId": 3 +} +[1642768236.464][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 4, + "nodeId": 3 +} +[1642768236.464][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading wf-universltw0155oblique-n4-loading" +} +[1642768236.483][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=23) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768236.484][DEBUG]: DevTools WebSocket Event: Log.entryAdded EBDCB326EE7911F3266EA857DCFF7A23 { + "entry": { + "level": "error", + "networkRequestId": "26420.195", + "source": "network", + "text": "Failed to load resource: net::ERR_NAME_NOT_RESOLVED", + "timestamp": 1.642768236484736e+12, + "url": "http://home.web.gs.com/inettag.js" + } +} +[1642768236.484][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=24) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768236.495][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=24) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768236.585][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 3, + "nodeId": 3 +} +[1642768236.585][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 2, + "nodeId": 3 +} +[1642768236.585][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder wf-loading" +} +[1642768236.585][DEBUG]: DevTools WebSocket Event: DOM.attributeModified EBDCB326EE7911F3266EA857DCFF7A23 { + "name": "class", + "nodeId": 3, + "value": "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder" +} +[1642768236.585][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=25) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768236.645][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=25) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768236.957][DEBUG]: DevTools WebSocket Event: DOM.documentUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768236.957][DEBUG]: DevTools WebSocket Command: DOM.getDocument (id=26) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768236.957][DEBUG]: DevTools WebSocket Event: Page.domContentEventFired EBDCB326EE7911F3266EA857DCFF7A23 { + "timestamp": 124223.065315 +} +[1642768236.957][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=27) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768236.963][DEBUG]: DevTools WebSocket Response: DOM.getDocument (id=26) EBDCB326EE7911F3266EA857DCFF7A23 { + "root": { + "backendNodeId": 1, + "baseURL": "https://www.goldmansachs.com/", + "childNodeCount": 2, + "children": [ { + "backendNodeId": 2, + "localName": "", + "nodeId": 5, + "nodeName": "html", + "nodeType": 10, + "nodeValue": "", + "parentId": 4, + "publicId": "", + "systemId": "" + }, { + "attributes": [ "lang", "en-US", "itemscope", "", "itemtype", "http://schema.org/Article", "style", "", "class", "js hashchange history rgba hsla borderimage borderradius boxshadow opacity fontface video placeholder" ], + "backendNodeId": 3, + "childNodeCount": 2, + "children": [ { + "attributes": [ ], + "backendNodeId": 401, + "childNodeCount": 83, + "localName": "head", + "nodeId": 7, + "nodeName": "HEAD", + "nodeType": 1, + "nodeValue": "", + "parentId": 6 + }, { + "attributes": [ "id", "homepage", "class", "interim-footer gs-v2 top-level full-width supress-series gs-sans max-width-1920 no-touch vt-body" ], + "backendNodeId": 21, + "childNodeCount": 10, + "localName": "body", + "nodeId": 8, + "nodeName": "BODY", + "nodeType": 1, + "nodeValue": "", + "parentId": 6 + } ], + "frameId": "EBDCB326EE7911F3266EA857DCFF7A23", + "localName": "html", + "nodeId": 6, + "nodeName": "HTML", + "nodeType": 1, + "nodeValue": "", + "parentId": 4 + } ], + "compatibilityMode": "NoQuirksMode", + "documentURL": "https://www.goldmansachs.com/", + "localName": "", + "nodeId": 4, + "nodeName": "#document", + "nodeType": 9, + "nodeValue": "", + "xmlVersion": "" + } +} +[1642768236.963][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=27) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768237.468][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=28) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768237.504][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=28) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768237.810][DEBUG]: DevTools WebSocket Event: DOM.inlineStyleInvalidated EBDCB326EE7911F3266EA857DCFF7A23 { + "nodeIds": [ 6 ] +} +[1642768237.810][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=29) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768237.811][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=29) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768238.319][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=30) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768238.319][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=30) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768238.443][DEBUG]: DevTools WebSocket Event: DOM.childNodeCountUpdated EBDCB326EE7911F3266EA857DCFF7A23 { + "childNodeCount": 11, + "nodeId": 8 +} +[1642768238.443][DEBUG]: DevTools WebSocket Event: Page.loadEventFired EBDCB326EE7911F3266EA857DCFF7A23 { + "timestamp": 124224.509529 +} +[1642768238.443][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=31) EBDCB326EE7911F3266EA857DCFF7A23 { + "awaitPromise": false, + "expression": "document.readyState", + "returnByValue": true +} +[1642768238.443][DEBUG]: DevTools WebSocket Event: Page.frameStoppedLoading EBDCB326EE7911F3266EA857DCFF7A23 { + "frameId": "EBDCB326EE7911F3266EA857DCFF7A23" +} +[1642768238.453][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=31) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "type": "string", + "value": "complete" + } +} +[1642768238.453][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=32) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768238.467][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=32) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768238.467][INFO]: Done waiting for pending navigations. Status: ok +[1642768238.467][INFO]: [7877b0b61dac0daae69a230280b72e8a] RESPONSE Navigate +[1642768238.470][INFO]: [7877b0b61dac0daae69a230280b72e8a] COMMAND Screenshot { + +} +[1642768238.470][INFO]: Waiting for pending navigations... +[1642768238.470][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=33) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768238.470][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=33) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768238.470][INFO]: Done waiting for pending navigations. Status: ok +[1642768238.470][DEBUG]: DevTools HTTP Request: http://localhost:63991/json/activate/EBDCB326EE7911F3266EA857DCFF7A23 +[1642768238.471][DEBUG]: DevTools HTTP Response: Target activated +[1642768238.471][DEBUG]: DevTools WebSocket Command: Page.captureScreenshot (id=34) EBDCB326EE7911F3266EA857DCFF7A23 { + +} +[1642768239.234][DEBUG]: DevTools WebSocket Response: Page.captureScreenshot (id=34) EBDCB326EE7911F3266EA857DCFF7A23 { + "data": "iVBORw0KGgoAAAANSUhEUgAABQ8AAAN4CAYAAABtTh7sAAAAAXNSR0IArs4c6QAAIABJREFUeJzs3X2wXGdh5/nv83LO6e77IuvKspFsg4yM7GDJNn6BQsTEEMYzUGOMh8zYs2zxMjPLhqpAQhKobCZkdyczO6mQCTVhZ0yxNQOkll2TCcEvFCyuBAxe24stO4Blg..." +} +[1642768239.238][INFO]: Waiting for pending navigations... +[1642768239.238][DEBUG]: DevTools WebSocket Command: Runtime.evaluate (id=35) EBDCB326EE7911F3266EA857DCFF7A23 { + "expression": "1" +} +[1642768239.240][DEBUG]: DevTools WebSocket Response: Runtime.evaluate (id=35) EBDCB326EE7911F3266EA857DCFF7A23 { + "result": { + "description": "1", + "type": "number", + "value": 1 + } +} +[1642768239.240][INFO]: Done waiting for pending navigations. Status: ok +[1642768239.242][INFO]: [7877b0b61dac0daae69a230280b72e8a] RESPONSE Screenshot "iVBORw0KGgoAAAANSUhEUgAABQ8AAAN4CAYAAABtTh7sAAAAAXNSR0IArs4c6QAAIABJREFUeJzs3X2wXGdh5/nv83LO6e77IuvKspFsg4yM7GDJNn6BQsTEEMYzUGOMh8zYs2zxMjPLhqpAQhKobCZkdyczO6mQCTVhZ0yxNQOkll2TCcEvFCyuBAxe24stO4Blg..." +[1642768239.596][INFO]: [7877b0b61dac0daae69a230280b72e8a] COMMAND Quit { + +} +[1642768239.637][INFO]: [7877b0b61dac0daae69a230280b72e8a] RESPONSE Quit +[1642768239.637][DEBUG]: Log type 'driver' lost 0 entries on destruction +[1642768239.637][DEBUG]: Log type 'browser' lost 2 entries on destruction diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/conda.yaml b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/conda.yaml new file mode 100644 index 00000000..8de76047 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/conda.yaml @@ -0,0 +1,16 @@ +channels: + # Define conda channels here. + - conda-forge + +dependencies: + # Define conda packages here. + # If available, always prefer the conda version of a package, installation will be faster and more efficient. + # https://anaconda.org/search + - python=3.7.5 + - FPDF + + - pip=20.1 + - pip: + # Define pip packages here. + # https://pypi.org/ + - rpaframework==11.4.0 # https://rpaframework.org/releasenotes.html diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/devdata/.ipynb_checkpoints/env-checkpoint.json b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/devdata/.ipynb_checkpoints/env-checkpoint.json new file mode 100644 index 00000000..5d274c6b --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/devdata/.ipynb_checkpoints/env-checkpoint.json @@ -0,0 +1,4 @@ +{ + "RPA_SECRET_MANAGER": "RPA.Robocorp.Vault.FileSecrets", + "RPA_SECRET_FILE": "C:\\Users\\DELL\\vault.json" +} \ No newline at end of file diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/devdata/env.json b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/devdata/env.json new file mode 100644 index 00000000..d53714e8 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/devdata/env.json @@ -0,0 +1,4 @@ +{ + "RPA_SECRET_MANAGER": "RPA.Robocorp.Vault.FileSecrets", + "RPA_SECRET_FILE": "vault.json" +} \ No newline at end of file diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/geckodriver-1.log b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/geckodriver-1.log new file mode 100644 index 00000000..e69de29b diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/pdfGen.py b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/pdfGen.py new file mode 100644 index 00000000..35c7fe92 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/pdfGen.py @@ -0,0 +1,86 @@ +# + +import os +from fpdf import FPDF +from PIL import Image + +pdf = FPDF(orientation = 'P', unit = 'mm', format='A4') + +def new_pdf(): + pdf.add_page() + + # Set Margin + pdf.l_margin = pdf.l_margin*1.0 + pdf.r_margin = pdf.r_margin*1.0 + pdf.t_margin = pdf.t_margin*1.0 + pdf.b_margin = pdf.b_margin*1.0 + # Effective page width and height + epw = pdf.w - pdf.l_margin - pdf.r_margin + eph = pdf.h - pdf.t_margin - pdf.b_margin + # Draw new margins. + pdf.rect(pdf.l_margin, pdf.t_margin, w=epw, h=eph) + + # set style and size of font + # that you want in the pdf + pdf.set_font("Arial", 'BU', 18.0) + pdf.ln(10) + # create a cell + pdf.cell(200, 10, txt = "New Business License Application", + ln = 2, align = 'C') + pdf.ln(2) + # add another cell + pdf.cell(200, 10, txt = "Background Verification", + ln = 2, align = 'C') + pdf.ln(10) + +def add_new_heading(headerText): + # set style and size of font + pdf.set_font("Arial", 'U', size = 12) + pdf.set_text_color(0, 0, 255) + # add a cell + pdf.cell(200, 0, txt = headerText, + ln = 2, align = 'C') + +def add_new_page(headerText): + pdf.add_page() + + # Set Margin + pdf.l_margin = pdf.l_margin*1.0 + pdf.r_margin = pdf.r_margin*1.0 + pdf.t_margin = pdf.t_margin*1.0 + pdf.b_margin = pdf.b_margin*1.0 + # Effective page width and height + epw = pdf.w - pdf.l_margin - pdf.r_margin + eph = pdf.h - pdf.t_margin - pdf.b_margin + # Draw new margins. + pdf.rect(pdf.l_margin, pdf.t_margin, w=epw, h=eph) + pdf.ln(20) + # set style and size of font + pdf.set_font("Arial", 'U', size = 12) + pdf.set_text_color(0, 0, 255) + # add a cell + pdf.cell(200, 10, txt = headerText, + ln = 2, align = 'C') + +def add_image_file(image): + pdf.image(image, 30, 80, 135) + +def footer(self): + # Go to 1.5 cm from bottom + self.set_y(-15) + # Select Arial italic 8 + self.set_font('Arial', 'I', 8) + # Print centered page number + self.cell(0, 10, 'Page %s' % self.page_no(), 0, 0, 'C') + +def header(): + # Arial bold 15 + pdf.set_font('Arial','B',15); + # Move to the right + pdf.cell(80); + # Title + pdf.cell(30,10,'AOT TECHNOLOGIES',1,0,'C'); + # Line break + pdf.ln(20); + +def save_pdf_file(pdfFile): + pdf.output(pdfFile) diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/robot.yaml b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/robot.yaml new file mode 100644 index 00000000..c141220a --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/robot.yaml @@ -0,0 +1,12 @@ +tasks: + Default: + shell: python -m robot --report NONE --outputdir output --logtitle "Task log" + +condaConfigFile: conda.yaml +artifactsDir: output +PATH: + - . +PYTHONPATH: + - . +ignoreFiles: + - .gitignore diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/tasks.robot b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/tasks.robot new file mode 100644 index 00000000..1a35fcc1 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/tasks.robot @@ -0,0 +1,135 @@ +# + +*** Settings *** +Documentation Background check for New Business License Application +... Capture screenshots of the below categories and append it to a PDF file. +... 1. Social media company profile like Linkedin, Twitter, Facebook and Instagram. +... 2. Checking if Business have a working website. +... 3. Google search results about company. +... 4. Validating if company is blacklisted or not. (Assuming the business is going to start in Canada) + + +#Imported Libraries + +Library RPA.Robocloud.Secrets +Library RPA.Browser.Selenium +Library RPA.FileSystem +Library Collections +Library RPA.Archive +Library pdfGen.py +Library RPA.HTTP +Library RPA.PDF +Library DateTime +# - + + +*** Variables *** +${GLOBAL_RETRY_AMOUNT}= 3x +${GLOBAL_RETRY_INTERVAL}= 1s +${MAX_SEARCH_RESULT_CLICKABLE}= 3 +${RUNTIME_DIR}= ${CURDIR}${/}output${/}${working-dir}${/}runtime +${FINAL_DIR}= ${CURDIR}${/}output${/}${working-dir}${/}final +${search-engine-url}= https://google.com +${verify-bc-org-url}= https://www.orgbook.gov.bc.ca/en/home + +*** Keywords *** + +Get Data From Vault + Log To Console START : Getting data from the vault + ${secret}= Get Secret my_vault + Log To Console START : Getting data from the vault + [Return] ${secret} + +Gather The Social Media Profiles + + New Pdf + + Add New Heading Validate If Company Was Blacklisted + + Validate If Company Was Blacklisted ${businessOperatingName} + + Add New Page Search In Intranet About The Company + + Search In Intranet About The Company ${businessOperatingName} + + ${profiles} = Create List facebook linkedin twitter instagram + + Search And Append Social Media Search Results ${businessOperatingName} ${profiles} + + Add New Page Validate Company Website + + Open And Validate Company Website ${businessWebsite} + + Log To Console Checks completed + + Save Pdf File ${FINAL_DIR}${/}${working-dir}.pdf + + +Search And Append Social Media Search Results + [Arguments] ${businessOperatingName} ${profiles} + Log To Console Started : Searching in facebook for company profiles + Open Available Browser ${search-engine-url} + FOR ${profile} IN @{profiles} + Input Text //input[@title="Search"] ${businessOperatingName} ${profile} + Submit Form + Wait Until Element Is Visible //div[@id="center_col"] + Take a screenshot of the page //div[@id="center_col"] ${profile} + Add New Page ${profile} + Add Image File ${RUNTIME_DIR}${/}${profile}.png + Go Back + END + Log To Console Completed : Searching in facebook for company profiles + [Teardown] Close Intranet Browser + +Search In Intranet About The Company + [Arguments] ${businessOperatingName} + Log To Console Started : Searching in intranet about the company + Open Available Browser ${search-engine-url} + Input Text //input[@title="Search"] ${businessOperatingName} + Submit Form + Wait Until Element Is Visible //div[@id="rcnt"] + Take a screenshot of the page //div[@id="rcnt"] searchResults + Add Image File ${RUNTIME_DIR}${/}searchResults.png + Log To Console Completed : Searching in intranet about the company + [Teardown] Close Intranet Browser + +Open And Validate Company Website + [Arguments] ${businessWebsite} + Log To Console Started : Open And Validate Company Website + ${length}= Get Length ${businessWebsite} + ${fileName}= Set Variable ${RUNTIME_DIR}${/}companyWebsite.png + IF ${length} > 0 + Open Available Browser ${businessWebsite} + Capture Page Screenshot ${fileName} + Add Image File ${fileName} + END + Log To Console End : Open And Validate Company Website + [Teardown] Close Intranet Browser + +Validate If Company Was Blacklisted + [Arguments] ${businessOperatingName} + Log To Console Started : Validate If Company Was Blacklisted + Open Available Browser ${verify-bc-org-url} + Wait Until Element Is Visible //input[@id="searchBar"] + Input Text //input[@id="searchBar"] ${businessOperatingName} + Click Button //button[@id="searchBarButton"] + Sleep 5s + Take a screenshot of the page //div[@class="v-main__wrap"] blacklistdata + Add Image File ${RUNTIME_DIR}${/}blacklistdata.png + Log To Console End : Validate If Company Was Blacklisted + [Teardown] Close Intranet Browser + +Take a screenshot of the page + [Arguments] ${webEl} ${name} + Sleep 1s + Wait Until Page Contains Element ${webEl} timeout=20 + Screenshot ${webEl} ${RUNTIME_DIR}${/}${name}.png + + + +Close Intranet Browser + Close Browser + +*** Tasks *** +Do Background check for the New Business Application + Gather The Social Media Profiles + [Teardown] Close Intranet Browser diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/vault.json b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/vault.json new file mode 100644 index 00000000..c28f72ad --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/background-check/vault.json @@ -0,0 +1,6 @@ +{ + "my_vault": { + "search-engine-url" : "https://google.com", + "verify-bc-org-url" : "https://www.orgbook.gov.bc.ca/en/home" + } +} \ No newline at end of file diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Browser.Library/.ipynb_checkpoints/browser-checkpoint.robot b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Browser.Library/.ipynb_checkpoints/browser-checkpoint.robot new file mode 100644 index 00000000..d18f6e74 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Browser.Library/.ipynb_checkpoints/browser-checkpoint.robot @@ -0,0 +1,39 @@ +*** Settings *** +Documentation Browser Keywords for web scrapper +Library RPA.Browser.Selenium +Library RPA.Tables +Library RPA.Excel.Files +Library String + +*** Variables *** +${inputEl} //input[@id="twotabsearchtextbox"] +${dataEl} //div[@class="s-result-item s-asin sg-col-0-of-12 sg-col-16-of-20 sg-col sg-col-12-of-16"] +${itemNameEl} xpath://span[@class="a-size-medium a-color-base a-text-normal"] +${itemPriceEl} xpath://span[@class="a-price-whole"] + +*** Keywords *** +Open Browser And Do Web Scrapping And Generate Excel + [Arguments] ${URL} ${SEARCH_KEY} ${OUTPUT_FILE} + Open Available Browser ${URL} + Input Text When Element Is Visible ${inputEl} ${SEARCH_KEY} + Sleep 1 + Submit Form + Sleep 1 + ${itemNames} = Get WebElements ${itemNameEl} + ${itemPrices} = Get WebElements ${itemPriceEl} + Create Workbook ${OUTPUT_FILE} + FOR ${itemName} ${itemPrice} IN ZIP ${itemNames} ${itemPrices} + ${productName} = Get Text ${itemName} + ${productPrice} = Get Text ${itemPrice} + ${productPrice} = Remove String Using Regexp + ... ${productPrice} , + ${productPrice} = Convert To Number ${productPrice} + &{row} = Create Dictionary + ... Product Name ${productName} + ... Price (In INR) ${productPrice} + ... Price (In USD) ${productPrice / 74.13} + Append Rows To Worksheet ${row} header=${TRUE} + END + Save Workbook + Close Workbook + Close Browser diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Browser.Library/browser.robot b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Browser.Library/browser.robot new file mode 100644 index 00000000..d18f6e74 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Browser.Library/browser.robot @@ -0,0 +1,39 @@ +*** Settings *** +Documentation Browser Keywords for web scrapper +Library RPA.Browser.Selenium +Library RPA.Tables +Library RPA.Excel.Files +Library String + +*** Variables *** +${inputEl} //input[@id="twotabsearchtextbox"] +${dataEl} //div[@class="s-result-item s-asin sg-col-0-of-12 sg-col-16-of-20 sg-col sg-col-12-of-16"] +${itemNameEl} xpath://span[@class="a-size-medium a-color-base a-text-normal"] +${itemPriceEl} xpath://span[@class="a-price-whole"] + +*** Keywords *** +Open Browser And Do Web Scrapping And Generate Excel + [Arguments] ${URL} ${SEARCH_KEY} ${OUTPUT_FILE} + Open Available Browser ${URL} + Input Text When Element Is Visible ${inputEl} ${SEARCH_KEY} + Sleep 1 + Submit Form + Sleep 1 + ${itemNames} = Get WebElements ${itemNameEl} + ${itemPrices} = Get WebElements ${itemPriceEl} + Create Workbook ${OUTPUT_FILE} + FOR ${itemName} ${itemPrice} IN ZIP ${itemNames} ${itemPrices} + ${productName} = Get Text ${itemName} + ${productPrice} = Get Text ${itemPrice} + ${productPrice} = Remove String Using Regexp + ... ${productPrice} , + ${productPrice} = Convert To Number ${productPrice} + &{row} = Create Dictionary + ... Product Name ${productName} + ... Price (In INR) ${productPrice} + ... Price (In USD) ${productPrice / 74.13} + Append Rows To Worksheet ${row} header=${TRUE} + END + Save Workbook + Close Workbook + Close Browser diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Excel.Library/.ipynb_checkpoints/excel-checkpoint.robot b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Excel.Library/.ipynb_checkpoints/excel-checkpoint.robot new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Excel.Library/.ipynb_checkpoints/excel-checkpoint.robot @@ -0,0 +1 @@ + diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Excel.Library/excel.robot b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Excel.Library/excel.robot new file mode 100644 index 00000000..ac8ff6d1 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/Keywords.Library/Excel.Library/excel.robot @@ -0,0 +1,6 @@ +*** Settings *** +Library RPA.Excel.Files + +*** Keywords *** +entering value in bbb + Set Cell Value 10 1 23 data diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/conda.yaml b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/conda.yaml new file mode 100644 index 00000000..a2e9e121 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/conda.yaml @@ -0,0 +1,15 @@ +channels: + # Define conda channels here. + - conda-forge + +dependencies: + # Define conda packages here. + # If available, always prefer the conda version of a package, installation will be faster and more efficient. + # https://anaconda.org/search + - python=3.7.5 + + - pip=20.1 + - pip: + # Define pip packages here. + # https://pypi.org/ + - rpaframework==10.3.0 # https://rpaframework.org/releasenotes.html diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/devdata/env.json b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/devdata/env.json new file mode 100644 index 00000000..d53714e8 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/devdata/env.json @@ -0,0 +1,4 @@ +{ + "RPA_SECRET_MANAGER": "RPA.Robocorp.Vault.FileSecrets", + "RPA_SECRET_FILE": "vault.json" +} \ No newline at end of file diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/pdfGen.py b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/pdfGen.py new file mode 100644 index 00000000..723f3526 --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/pdfGen.py @@ -0,0 +1,86 @@ +# + +import os +from fpdf import FPDF +from PIL import Image + +pdf = FPDF(orientation = 'P', unit = 'mm', format='A4') + +def new_pdf(): + pdf.add_page() + + # Set Margin + pdf.l_margin = pdf.l_margin*1.0 + pdf.r_margin = pdf.r_margin*1.0 + pdf.t_margin = pdf.t_margin*1.0 + pdf.b_margin = pdf.b_margin*1.0 + # Effective page width and height + epw = pdf.w - pdf.l_margin - pdf.r_margin + eph = pdf.h - pdf.t_margin - pdf.b_margin + # Draw new margins. + pdf.rect(pdf.l_margin, pdf.t_margin, w=epw, h=eph) + + # set style and size of font + # that you want in the pdf + pdf.set_font("Arial", 'BU', 18.0) + pdf.ln(10) + # create a cell + pdf.cell(200, 10, txt = "Freedom Of Information and Protection of Privacy", + ln = 2, align = 'C') + pdf.ln(2) + # add another cell + pdf.cell(200, 10, txt = "Background Verification", + ln = 2, align = 'C') + pdf.ln(10) + +def add_new_heading(headerText): + # set style and size of font + pdf.set_font("Arial", 'U', size = 12) + pdf.set_text_color(0, 0, 255) + # add a cell + pdf.cell(200, 0, txt = headerText, + ln = 2, align = 'C') + +def add_new_page(headerText): + pdf.add_page() + + # Set Margin + pdf.l_margin = pdf.l_margin*1.0 + pdf.r_margin = pdf.r_margin*1.0 + pdf.t_margin = pdf.t_margin*1.0 + pdf.b_margin = pdf.b_margin*1.0 + # Effective page width and height + epw = pdf.w - pdf.l_margin - pdf.r_margin + eph = pdf.h - pdf.t_margin - pdf.b_margin + # Draw new margins. + pdf.rect(pdf.l_margin, pdf.t_margin, w=epw, h=eph) + pdf.ln(20) + # set style and size of font + pdf.set_font("Arial", 'U', size = 12) + pdf.set_text_color(0, 0, 255) + # add a cell + pdf.cell(200, 10, txt = headerText, + ln = 2, align = 'C') + +def add_image_file(image): + pdf.image(image, 30, 80, 135) + +def footer(self): + # Go to 1.5 cm from bottom + self.set_y(-15) + # Select Arial italic 8 + self.set_font('Arial', 'I', 8) + # Print centered page number + self.cell(0, 10, 'Page %s' % self.page_no(), 0, 0, 'C') + +def header(): + # Arial bold 15 + pdf.set_font('Arial','B',15); + # Move to the right + pdf.cell(80); + # Title + pdf.cell(30,10,'AOT TECHNOLOGIES',1,0,'C'); + # Line break + pdf.ln(20); + +def save_pdf_file(pdfFile): + pdf.output(pdfFile) diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/robot.yaml b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/robot.yaml new file mode 100644 index 00000000..c141220a --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/robot.yaml @@ -0,0 +1,12 @@ +tasks: + Default: + shell: python -m robot --report NONE --outputdir output --logtitle "Task log" + +condaConfigFile: conda.yaml +artifactsDir: output +PATH: + - . +PYTHONPATH: + - . +ignoreFiles: + - .gitignore diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/tasks.robot b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/tasks.robot new file mode 100644 index 00000000..48c26b1b --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/tasks.robot @@ -0,0 +1,67 @@ +*** Settings *** +Documentation Web Scrapper with excel output +Resource ./Keywords.Library/Browser.Library/browser.robot + +#Imported Libraries + +Library RPA.Robocloud.Secrets +Library RPA.Browser.Selenium +Library RPA.FileSystem +Library Collections +Library RPA.Archive +Library pdfGen.py +Library RPA.HTTP +Library RPA.PDF +Library DateTime +# - + + +*** Variables *** +${GLOBAL_RETRY_AMOUNT}= 3x +${GLOBAL_RETRY_INTERVAL}= 1s +${MAX_SEARCH_RESULT_CLICKABLE}= 3 +${RUNTIME_DIR}= ${CURDIR}${/}output${/}${working-dir}${/}runtime +${FINAL_DIR}= ${CURDIR}${/}output${/}${working-dir}${/}final +${search-engine-url}= https://google.com + +*** Keywords *** + +Gather Organization Details + + New Pdf + + Search Organization Details ${organizationName} + + Log To Console Checks completed + + Save Pdf File ${FINAL_DIR}${/}${working-dir}.pdf + +Search Organization Details + [Arguments] ${organizationName} + Log To Console Started : Searching in intranet about the company + Open Available Browser ${search-engine-url} + Input Text //input[@title="Search"] ${organizationName} + Submit Form + Wait Until Element Is Visible //div[@id="rcnt"] + Take a screenshot of the page //div[@id="rcnt"] searchResults + Add Image File ${RUNTIME_DIR}${/}searchResults.png + Log To Console Completed : Searching in intranet about the company + [Teardown] Close Intranet Browser + + + +Take a screenshot of the page + [Arguments] ${webEl} ${name} + Sleep 1s + Wait Until Page Contains Element ${webEl} timeout=20 + Screenshot ${webEl} ${RUNTIME_DIR}${/}${name}.png + + + +Close Intranet Browser + Close Browser + +*** Tasks *** +Do Background check for the Freedom of Information Application + Gather Organization Details + [Teardown] Close Intranet Browser diff --git a/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/vault.json b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/vault.json new file mode 100644 index 00000000..c28f72ad --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/src/main/resources/robots/web-scraper/vault.json @@ -0,0 +1,6 @@ +{ + "my_vault": { + "search-engine-url" : "https://google.com", + "verify-bc-org-url" : "https://www.orgbook.gov.bc.ca/en/home" + } +} \ No newline at end of file diff --git a/rpa-robocorp-extention/external-client-extention/starter-examples/handlers/background-check-robot-handler-readme.md b/rpa-robocorp-extention/external-client-extention/starter-examples/handlers/background-check-robot-handler-readme.md new file mode 100644 index 00000000..4b70ce6f --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/starter-examples/handlers/background-check-robot-handler-readme.md @@ -0,0 +1,48 @@ +# Background Check Robot Handler + +**org.camunda.rpa.client.handlers.impl.BackgroundCheckRobotHandler** + + This component can be used to handle and manage the robot. + +## Table of Content +* [Type](#type) +* [How it Works](#how-it-works) +* [How to Configure](#how-to-configure) + +### Type + +Java Handler + +### How it Works + +This component will manage the robot to build a script with input parameters and run the robot. + +### How to Configure + +Below instructions can be followed to configure a robot handler to manage your robot. + +* Subscribe the Handler to a topic with `@ExternalTaskSubscription` annotation and `topic name`. `eg:@ExternalTaskSubscription("background-check")` + +* The robot Handler implements `IRobotHandler` interface and `ExternalTaskHandler` interface. + +* Override the below methods and provide business logics. These methods are used to manage and run the robot successfully. + + * **`startAudit`** - Create a new entry in the Audit table to acknowledge that the robot started successfully. This method invokes `org.camunda.rpa.client.core.RobotDataManager#initAudit()` to update the robot current status `eg:IN_PROGRESS` + + * **`buildInputWithAdditionalVariables`** - This method will build input with additional variables for robot by invoking the `org.camunda.rpa.client.core.RobotIOManager#buildInput()`. + + * **`buildInput`** - This is an overriden method expecting the parameter `ExternalTask` and `RobotHandlerConfig` to build the Input. + + * **`runRobot`** - This method invokes `org.camunda.rpa.client.core.RobotDataManager#runRobot()` to find out the robot type, build a script with input parameters and run using script manager. + + * **`collectResponse`** - Once robot completed its task successfully, this method invokes `org.camunda.rpa.client.core.RobotIOManager#getOutputData` to collect the response. + + * **`completeAudit`** - Complete the audit process by invoking `org.camunda.rpa.client.core.RobotDataManager#finalizeAudit` and update the status in audit table. `eg:SUCCESS/FAILURE` + + * **`handleFailure`** - Reports a failure to execute a task. A number of retries and a timeout untilthe task can be specified. If the retries are set to 0, an incident for thistask is created. + + * **`doCleanup`** - This function will clear the robot current output directory and it's data + + * **`getHandlerId`** - Each Robot handler is identified by a unique `HANDLER_ID`. + +* Checkout the configurations for [BackgroundCheckRobotHandler](../../src/main/java/org/camunda/rpa/client/handlers/impl/BackgroundCheckRobotHandler.java). diff --git a/rpa-robocorp-extention/external-client-extention/starter-examples/robots/background-check-robot-readme.md b/rpa-robocorp-extention/external-client-extention/starter-examples/robots/background-check-robot-readme.md new file mode 100644 index 00000000..713f72ea --- /dev/null +++ b/rpa-robocorp-extention/external-client-extention/starter-examples/robots/background-check-robot-readme.md @@ -0,0 +1,36 @@ +# Background Check Robot + + This robot will perform a background check for New Business License Application. + +## Table of Content +* [Type](#type) +* [How it Works](#how-it-works) +* [How to Use](#how-to-use) + +### Type + +Robot + +### How it Works + +Robot will be triggered when clerk review the task. This Background Check Robot will perform below tasks: + +* Search in social media like Linkedin, Twitter, Facebook and Instagram for company profiles. +* Validate the business website. +* Fetch google search results of the company. +* Validate if company is blacklisted or not. + +Finally capture the screenshot of all the task and append it to a PDF file. + +### How to Use + +Below instructions can be followed to run the robot. + + * Build the robot using [Robocorp](https://robocorp.com/). Refer this [background-check](https://github.com/shibin-aot/formsflow-ai-extensions/tree/main/formsflow-robocorp-sample/camunda-springboot-external-client/external-client-extention/src/main/resources/robots/background-check) robot. + * Add the robot to the path `{base-dir}/src/main/resources/robots`. + * Make sure your current working directory is `{base-dir}/src/main/resources/robots/background-check`. + * To Test if robot is working, run the below command: + + ``` +rcc run +``` diff --git a/rpa-robocorp-extention/robocorp-examples/background-check-robot/.gitignore b/rpa-robocorp-extention/robocorp-examples/background-check-robot/.gitignore new file mode 100644 index 00000000..8d658dde --- /dev/null +++ b/rpa-robocorp-extention/robocorp-examples/background-check-robot/.gitignore @@ -0,0 +1 @@ +./output/* \ No newline at end of file diff --git a/rpa-robocorp-extention/robocorp-examples/background-check-robot/conda.yaml b/rpa-robocorp-extention/robocorp-examples/background-check-robot/conda.yaml new file mode 100644 index 00000000..65c7d32e --- /dev/null +++ b/rpa-robocorp-extention/robocorp-examples/background-check-robot/conda.yaml @@ -0,0 +1,16 @@ +channels: + # Define conda channels here. + - conda-forge + +dependencies: + # Define conda packages here. + # If available, always prefer the conda version of a package, installation will be faster and more efficient. + # https://anaconda.org/search + - python=3.7.5 + - FPDF + + - pip=20.1 + - pip: + # Define pip packages here. + # https://pypi.org/ + - rpaframework==12.6.0 # https://rpaframework.org/releasenotes.html diff --git a/rpa-robocorp-extention/robocorp-examples/background-check-robot/mine_activate.bat b/rpa-robocorp-extention/robocorp-examples/background-check-robot/mine_activate.bat new file mode 100644 index 00000000..12fa639b --- /dev/null +++ b/rpa-robocorp-extention/robocorp-examples/background-check-robot/mine_activate.bat @@ -0,0 +1,23 @@ +SET PYTHON_EXE=C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\python.exe +SET CONDA_DEFAULT_ENV=rcc +SET CONDA_PREFIX=C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96 +SET CONDA_PROMPT_MODIFIER=(rcc) +SET CONDA_SHLVL=1 +SET PYTHONHOME= +SET PYTHONSTARTUP= +SET PYTHONEXECUTABLE= +SET PYTHONNOUSERSITE=1 +SET PYTHONDONTWRITEBYTECODE=x +SET PYTHONPYCACHEPREFIX=C:\Users\DELL\AppData\Local\robocorp\temp\810acbd444357612 +SET ROBOCORP_HOME=C:\Users\DELL\AppData\Local\robocorp +SET RCC_ENVIRONMENT_HASH= +SET RCC_INSTALLATION_ID=0d82799a-a5cf-12fa-782a-9e3ee2e77c02 +SET RCC_TRACKING_ALLOWED=true +SET TEMP=C:\Users\DELL\AppData\Local\robocorp\temp\810acbd444357612 +SET TMP=C:\Users\DELL\AppData\Local\robocorp\temp\810acbd444357612 +SET PATH=D:\AOT\Project\Source\formsflow-ai-extensions\robocorp-examples\background-check-robot;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\Library\mingw-w64\bin;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\Library\usr\bin;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\Library\bin;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\Scripts;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\bin;C:\Program Files\Common Files\Oracle\Java\javapath;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Windows\System32\OpenSSH;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\Program Files\Java\jdk-11.0.12\bin;C:\apache-maven-3.6.0\bin;C:\Program Files\PuTTY;C:\Program Files\Git\cmd;C:\Program Files\MySQL\MySQL Server 5.6\bin;C:\Program Files\nodejs;D:\AOT\Project\Deployment\apache-tomcat-9.0.45-windows-x64\apache-tomcat-9.0.45\bin;D:\AOT\Project\Software\grails-2.3.4\bin;C:\Program Files\TortoiseSVN\bin;C:\Users\DELL;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0;C:\WINDOWS\System32\OpenSSH;D:\AOT\Project\Software\openshift-client-windows;D:\Software\apache-ant\bin;C:\Program Files\k6;C:\Program Files\dotnet;C:\Program Files\Docker\Docker\resources\bin;C:\ProgramData\DockerDesktop\version-bin;C:\Users\DELL\AppData\Local\Microsoft\WindowsApps;C:\Users\DELL\AppData\Local\GitHubDesktop\bin;C:\Users\DELL\AppData\Roaming\npm;C:\Users\DELL\AppData\Local\Programs\Fiddler;D:\AOT\Project\Source\formsflow-ai-extensions\robocorp-examples\background-check-robot\%USERPROFILE%\AppData\Local\Microsoft\WindowsApps;D:\AOT\Project\Source\formsflow-ai-extensions\robocorp-examples\background-check-robot;C:\Program Files\liquibase +SET PYTHONPATH=D:\AOT\Project\Source\formsflow-ai-extensions\robocorp-examples\background-check-robot +SET ROBOT_ROOT=D:\AOT\Project\Source\formsflow-ai-extensions\robocorp-examples\background-check-robot +SET ROBOT_ARTIFACTS=D:\AOT\Project\Source\formsflow-ai-extensions\robocorp-examples\background-check-robot\output +SET MAMBA_ROOT_PREFIX=C:\Users\DELL\AppData\Local\robocorp +SET PATH=C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\Library\mingw-w64\bin;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\Library\usr\bin;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\Library\bin;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\Scripts;C:\Users\DELL\AppData\Local\robocorp\holotree\5a1fac3c5_0bacdd96\bin;C:\Program Files\Common Files\Oracle\Java\javapath;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Windows\System32\OpenSSH;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\Program Files\Java\jdk-11.0.12\bin;C:\apache-maven-3.6.0\bin;C:\Program Files\PuTTY;C:\Program Files\Git\cmd;C:\Program Files\MySQL\MySQL Server 5.6\bin;C:\Program Files\nodejs;D:\AOT\Project\Deployment\apache-tomcat-9.0.45-windows-x64\apache-tomcat-9.0.45\bin;D:\AOT\Project\Software\grails-2.3.4\bin;C:\Program Files\TortoiseSVN\bin;C:\Users\DELL;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0;C:\WINDOWS\System32\OpenSSH;D:\AOT\Project\Software\openshift-client-windows;D:\Software\apache-ant\bin;C:\Program Files\k6;C:\Program Files\dotnet;C:\Program Files\Docker\Docker\resources\bin;C:\ProgramData\DockerDesktop\version-bin;C:\Users\DELL\AppData\Local\Microsoft\WindowsApps;C:\Users\DELL\AppData\Local\GitHubDesktop\bin;C:\Users\DELL\AppData\Roaming\npm;C:\Users\DELL\AppData\Local\Programs\Fiddler;D:\AOT\Project\Source\formsflow-ai-extensions\robocorp-examples\background-check-robot\C:\Users\DELL\AppData\Local\Microsoft\WindowsApps;D:\AOT\Project\Source\formsflow-ai-extensions\robocorp-examples\background-check-robot;C:\Program Files\liquibase diff --git a/rpa-robocorp-extention/robocorp-examples/background-check-robot/pdfGen.py b/rpa-robocorp-extention/robocorp-examples/background-check-robot/pdfGen.py new file mode 100644 index 00000000..35c7fe92 --- /dev/null +++ b/rpa-robocorp-extention/robocorp-examples/background-check-robot/pdfGen.py @@ -0,0 +1,86 @@ +# + +import os +from fpdf import FPDF +from PIL import Image + +pdf = FPDF(orientation = 'P', unit = 'mm', format='A4') + +def new_pdf(): + pdf.add_page() + + # Set Margin + pdf.l_margin = pdf.l_margin*1.0 + pdf.r_margin = pdf.r_margin*1.0 + pdf.t_margin = pdf.t_margin*1.0 + pdf.b_margin = pdf.b_margin*1.0 + # Effective page width and height + epw = pdf.w - pdf.l_margin - pdf.r_margin + eph = pdf.h - pdf.t_margin - pdf.b_margin + # Draw new margins. + pdf.rect(pdf.l_margin, pdf.t_margin, w=epw, h=eph) + + # set style and size of font + # that you want in the pdf + pdf.set_font("Arial", 'BU', 18.0) + pdf.ln(10) + # create a cell + pdf.cell(200, 10, txt = "New Business License Application", + ln = 2, align = 'C') + pdf.ln(2) + # add another cell + pdf.cell(200, 10, txt = "Background Verification", + ln = 2, align = 'C') + pdf.ln(10) + +def add_new_heading(headerText): + # set style and size of font + pdf.set_font("Arial", 'U', size = 12) + pdf.set_text_color(0, 0, 255) + # add a cell + pdf.cell(200, 0, txt = headerText, + ln = 2, align = 'C') + +def add_new_page(headerText): + pdf.add_page() + + # Set Margin + pdf.l_margin = pdf.l_margin*1.0 + pdf.r_margin = pdf.r_margin*1.0 + pdf.t_margin = pdf.t_margin*1.0 + pdf.b_margin = pdf.b_margin*1.0 + # Effective page width and height + epw = pdf.w - pdf.l_margin - pdf.r_margin + eph = pdf.h - pdf.t_margin - pdf.b_margin + # Draw new margins. + pdf.rect(pdf.l_margin, pdf.t_margin, w=epw, h=eph) + pdf.ln(20) + # set style and size of font + pdf.set_font("Arial", 'U', size = 12) + pdf.set_text_color(0, 0, 255) + # add a cell + pdf.cell(200, 10, txt = headerText, + ln = 2, align = 'C') + +def add_image_file(image): + pdf.image(image, 30, 80, 135) + +def footer(self): + # Go to 1.5 cm from bottom + self.set_y(-15) + # Select Arial italic 8 + self.set_font('Arial', 'I', 8) + # Print centered page number + self.cell(0, 10, 'Page %s' % self.page_no(), 0, 0, 'C') + +def header(): + # Arial bold 15 + pdf.set_font('Arial','B',15); + # Move to the right + pdf.cell(80); + # Title + pdf.cell(30,10,'AOT TECHNOLOGIES',1,0,'C'); + # Line break + pdf.ln(20); + +def save_pdf_file(pdfFile): + pdf.output(pdfFile) diff --git a/rpa-robocorp-extention/robocorp-examples/background-check-robot/robot.yaml b/rpa-robocorp-extention/robocorp-examples/background-check-robot/robot.yaml new file mode 100644 index 00000000..2aa1dc23 --- /dev/null +++ b/rpa-robocorp-extention/robocorp-examples/background-check-robot/robot.yaml @@ -0,0 +1,12 @@ +tasks: + Default: + shell: python -m robot --report NONE --outputdir output --logtitle "Task log" tasks.robot + +condaConfigFile: conda.yaml +artifactsDir: output +PATH: + - . +PYTHONPATH: + - . +ignoreFiles: + - .gitignore diff --git a/rpa-robocorp-extention/robocorp-examples/background-check-robot/tasks.robot b/rpa-robocorp-extention/robocorp-examples/background-check-robot/tasks.robot new file mode 100644 index 00000000..12e40262 --- /dev/null +++ b/rpa-robocorp-extention/robocorp-examples/background-check-robot/tasks.robot @@ -0,0 +1,152 @@ +# + + # + +*** Settings *** +Documentation Background check for New Business License Application +... Capture screenshots of the below categories and append it to a PDF file. +... 1. Social media company profile like Linkedin, Twitter, Facebook and Instagram. +... 2. Checking if Business have a working website. +... 3. Google search results about company. +... 4. Validating if company is blacklisted or not. (Assuming the business is going to start in Canada) + +Library RPA.Robocloud.Secrets +Library RPA.Browser.Selenium +Library RPA.FileSystem +Library Collections +Library RPA.Archive +Library pdfGen.py +Library RPA.HTTP +Library RPA.PDF +Library DateTime +# - + + +# Imported Libraries + +*** Variables *** +${GLOBAL_RETRY_AMOUNT}= 3x +${GLOBAL_RETRY_INTERVAL}= 1s +${MAX_SEARCH_RESULT_CLICKABLE}= 3 +${RUNTIME_DIR}= ${CURDIR}${/}output${/}runtime +${FINAL_DIR}= ${CURDIR}${/}output${/}result +${SEARCH_ENGINE}= http://www.google.com +${businessOperatingName}= AOT Technologies +${businessWebsite}= http://www.aot-technologies.com +${search-engine-url}= https://google.com +${verify-bc-org-url}= https://www.orgbook.gov.bc.ca/en/home + + +# + +*** Keywords *** +Gather The Social Media Profiles + + New Pdf + + Add New Heading Validate If Company Was Blacklisted + + Validate If Company Was Blacklisted ${businessOperatingName} + + Add New Page Search In Intranet About The Company + + Search In Intranet About The Company ${businessOperatingName} + + ${profiles} = Create List facebook linkedin twitter instagram + + Search And Append Social Media Search Results ${businessOperatingName} ${profiles} + + Add New Page Validate Company Website + + Open And Validate Company Website ${businessWebsite} + + Log To Console Checks completed + + ${timestamp} = Get Current Date result_format=%Y%m%d%H%M%S + + Save Pdf File ${FINAL_DIR}${/}data.pdf + +Search And Append Social Media Search Results + [Arguments] ${businessOperatingName} ${profiles} + Log To Console Started : Searching in facebook for company profiles + Open Available Browser ${search-engine-url} + Agree To Google Terms + FOR ${profile} IN @{profiles} + Input Text //input[@title="Search"] ${businessOperatingName} ${profile} + Submit Form + Wait Until Element Is Visible //div[@id="center_col"] + Take a screenshot of the page //div[@id="center_col"] ${profile} + Add New Page ${profile} + Add Image File ${RUNTIME_DIR}${/}${profile}.png + Go Back + END + Log To Console Completed : Searching in facebook for company profiles + [Teardown] Close Intranet Browser + +Search In Intranet About The Company + [Arguments] ${businessOperatingName} + Log To Console Started : Searching in intranet about the company + Open Available Browser ${search-engine-url} + Agree To Google Terms + Input Text //input[@title="Search"] ${businessOperatingName} + Submit Form + Wait Until Element Is Visible //div[@id="rcnt"] + Take a screenshot of the page //div[@id="rcnt"] searchResults + Add Image File ${RUNTIME_DIR}${/}searchResults.png + Log To Console Completed : Searching in intranet about the company + [Teardown] Close Intranet Browser + +Open And Validate Company Website + [Arguments] ${businessWebsite} + Log To Console Started : Open And Validate Company Website + ${length}= Get Length ${businessWebsite} + ${fileName}= Set Variable ${RUNTIME_DIR}${/}companyWebsite.png + IF ${length} > 0 + Open Available Browser ${businessWebsite} + Capture Page Screenshot ${fileName} + Add Image File ${fileName} + END + Log To Console End : Open And Validate Company Website + [Teardown] Close Intranet Browser + +Validate If Company Was Blacklisted + [Arguments] ${businessOperatingName} + Log To Console Started : Validate If Company Was Blacklisted + Open Available Browser ${search-engine-url} + Agree To Google Terms + Input Text //input[@title="Search"] ${businessOperatingName} + Submit Form + Take a screenshot of the page //div[@class="GyAeWb"] blacklistdata + Add Image File ${RUNTIME_DIR}${/}blacklistdata.png + Log To Console End : Validate If Company Was Blacklisted + [Teardown] Close Intranet Browser + +Take a screenshot of the page + [Arguments] ${webEl} ${name} + Wait Until Page Contains Element ${webEl} timeout=20 + Screenshot ${webEl} ${RUNTIME_DIR}${/}${name}.png + +Agree To Google Terms + ${res}= Does Page Contain Button I agree + IF ${res} == True + Click Button I agree + END + +Remove and create directory + [Arguments] ${DIR} + Remove Directory ${DIR} True + Create Directory ${DIR} + +Cleanup directory + ${RUNTIME}= Does Directory Exist ${RUNTIME_DIR} + ${FINAL}= Does Directory Exist ${FINAL_DIR} + Run Keyword If '${RUNTIME}'==True Remove and create directory ${RUNTIME} ELSE Create Directory ${RUNTIME_DIR} + Run Keyword If '${FINAL}'==True Remove and create directory ${FINAL} ELSE Create Directory ${FINAL_DIR} + +Close Intranet Browser + Close Browser +# - + + +*** Tasks *** +Do Background check for the New Business Application + Cleanup directory + Gather The Social Media Profiles + [Teardown] Close Intranet Browser diff --git a/rpa-robocorp-extention/robocorp-examples/background-check-robot/vault.json b/rpa-robocorp-extention/robocorp-examples/background-check-robot/vault.json new file mode 100644 index 00000000..c28f72ad --- /dev/null +++ b/rpa-robocorp-extention/robocorp-examples/background-check-robot/vault.json @@ -0,0 +1,6 @@ +{ + "my_vault": { + "search-engine-url" : "https://google.com", + "verify-bc-org-url" : "https://www.orgbook.gov.bc.ca/en/home" + } +} \ No newline at end of file