├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── user-story.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── backendTesting.yml │ ├── frontendTesting.yml │ ├── herokuDeploy.yml │ ├── linterFrontend.yml │ ├── linterServer.yml │ ├── publishDocumentation.yml │ └── wikiPageSync.yml ├── .gitignore ├── .prettierrc.json ├── LICENSE.md ├── README.md ├── documentationConfig.json ├── frontend ├── .eslintrc.json ├── craco.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon-16.ico │ ├── favicon-192.png │ ├── favicon-512.png │ ├── index.html │ ├── logo │ │ ├── logoBlack.png │ │ ├── logoCta.png │ │ ├── logoInverted.png │ │ └── logoPrim.png │ └── manifest.json └── src │ ├── api │ ├── routes │ │ ├── functionalities │ │ │ └── functionalities.js │ │ ├── robots │ │ │ ├── robots.js │ │ │ ├── rpaAttributes.js │ │ │ └── rpaParameter.js │ │ └── users │ │ │ └── users.js │ └── socketHandler │ │ ├── socketEmitter.js │ │ └── socketListeners.js │ ├── components │ ├── App.jsx │ ├── multiPageComponents │ │ └── HeaderNavbar │ │ │ ├── HeaderNavbar.jsx │ │ │ └── HeaderNavbar.module.css │ └── pages │ │ ├── Error │ │ └── Error.jsx │ │ ├── Home │ │ ├── Home.jsx │ │ └── Home.module.css │ │ ├── RobotCodeEditor │ │ ├── RobotCodeEditor.jsx │ │ ├── RobotCodeEditor.module.css │ │ └── RobotCodeEditorSyntaxPopup │ │ │ └── RobotCodeEditorSyntaxPopup.jsx │ │ ├── RobotInteractionCockpit │ │ ├── RobotInteractionCockpit.jsx │ │ ├── RobotInteractionCockpit.module.css │ │ ├── RobotInteractionSections │ │ │ ├── RobotInteractionExecutionSection │ │ │ │ ├── RobotInteractionExecutionSection.jsx │ │ │ │ └── subComponents │ │ │ │ │ └── RobotLogEntryCard.jsx │ │ │ └── RobotInteractionInputSection │ │ │ │ ├── RobotInteractionInputSection.jsx │ │ │ │ └── subComponents │ │ │ │ └── RobotInteractionParameterInput.jsx │ │ └── robotInteractionCockpitFunctionality │ │ │ ├── robotExecution.test.js │ │ │ ├── robotExecutionTestData.js │ │ │ └── robotInteractionCockpitFunctionality.js │ │ ├── RobotModeler │ │ ├── BpmnModeler │ │ │ ├── BpmnModeler.jsx │ │ │ ├── BpmnModeler.module.css │ │ │ └── removeUnsupportedBpmnFunctions.js │ │ ├── ModelerSidebar │ │ │ ├── ModelerSidebar.jsx │ │ │ ├── ModelerSidebar.module.css │ │ │ ├── PropertiesPanel │ │ │ │ ├── PropertiesPanel.jsx │ │ │ │ └── PropertiesPanelSections │ │ │ │ │ ├── PPIdSection │ │ │ │ │ └── PPIdSection.jsx │ │ │ │ │ ├── PPNameSection │ │ │ │ │ └── PPNameSection.jsx │ │ │ │ │ ├── PPOutputValueSection │ │ │ │ │ └── PPOutputValueSection.jsx │ │ │ │ │ ├── PPParameterSection │ │ │ │ │ ├── PPParameterSection.jsx │ │ │ │ │ └── subComponents │ │ │ │ │ │ └── PPParameterInput.jsx │ │ │ │ │ ├── PPRpaSection │ │ │ │ │ ├── PPRpaSection.jsx │ │ │ │ │ └── subComponents │ │ │ │ │ │ ├── PPApplicationDropdown.jsx │ │ │ │ │ │ └── PPTaskDropdown.jsx │ │ │ │ │ └── PPTitleSection │ │ │ │ │ └── PPTitleSection.jsx │ │ │ └── modelerSidebarFunctionality │ │ │ │ ├── downloadStringAsFile.js │ │ │ │ ├── modelerSidebarFunctionality.js │ │ │ │ ├── modelerSidebarFunctionality.test.js │ │ │ │ └── modelerSidebarFunctionalityTestingUtils.js │ │ └── RobotModeler.jsx │ │ └── RobotOverview │ │ ├── RobotContainer │ │ ├── CreateRobotContainer.jsx │ │ ├── RobotContainer.jsx │ │ └── RobotContainer.module.css │ │ ├── RobotOverview.jsx │ │ └── RobotOverview.test.js │ ├── index.css │ ├── index.jsx │ ├── layout │ ├── corporateDesign.css │ ├── corporateDesign.js │ └── customizedTheme.js │ ├── resources │ └── modeler │ │ └── emptyBpmn.js │ └── utils │ ├── componentsFunctionality │ └── notificationUtils.jsx │ ├── parser │ ├── bpmnToSsotParsing │ │ ├── bpmnToSsotParsing.js │ │ └── bpmnToSsotParsing.test.js │ ├── robotCodeToSsotParsing │ │ ├── robotCodeTestData.js │ │ ├── robotCodeToSsotParsing.js │ │ └── robotCodeToSsotParsing.test.js │ ├── ssotBaseObjects.js │ └── ssotToBpmnParsing │ │ └── ssotToBpmnParsing.js │ ├── sessionStorage │ ├── localFunctionalitiesController │ │ └── functionalities.js │ ├── localSsotController │ │ ├── attributes.js │ │ ├── parameters.js │ │ └── ssot.js │ └── sessionStorageUtils.js │ └── socket │ └── socketConnections.js ├── server ├── .eslintrc.json ├── api │ ├── controllers │ │ ├── rpaFrameworkCommandsController.js │ │ ├── ssotParameterController.js │ │ ├── ssotParsingController.js │ │ ├── ssotRetrievalController.js │ │ └── ssotRpaAttributes.js │ ├── models │ │ ├── robotJobModel.js │ │ ├── robotJobModel.test.js │ │ ├── rpaTaskModel.js │ │ ├── rpaTaskModel.test.js │ │ ├── singleSourceOfTruthModel.js │ │ ├── singleSourceOfTruthModel.test.js │ │ ├── userAccessObjectModel.js │ │ └── userAccessObjectModel.test.js │ └── routes │ │ ├── functionalities │ │ ├── functionalities.js │ │ └── functionalities.test.js │ │ ├── robots │ │ ├── robots.js │ │ ├── robots.test.js │ │ ├── rpaAttributes │ │ │ ├── rpaAttributes.js │ │ │ └── rpaAttributes.test.js │ │ └── rpaParameters │ │ │ ├── rpaParameters.js │ │ │ └── rpaParameters.test.js │ │ └── users │ │ ├── users.js │ │ └── users.test.js ├── package-lock.json ├── package.json ├── server.js ├── socket │ ├── socketHelper.test.js │ ├── socketHelperFunctions.js │ └── socketManager.js └── utils │ ├── openApiDocumentation │ ├── docuGenerationHelper.js │ └── openApiComponents.js │ ├── ssotToRobotParsing │ ├── __tests__ │ │ ├── SsotForTesting.json │ │ └── SsotToRobotParser.test.js │ ├── generateCodeBase.js │ ├── generateCodeForRpaTasks.js │ ├── retrieveParameters.js │ ├── robotCodeConstants.js │ └── ssotToRobotParser.js │ └── testing │ ├── databaseLoader.js │ ├── testData.js │ ├── testDatabaseHandler.js │ └── testRobotFile.txt └── wiki ├── Coding-Standards.md ├── Database-and-Communication.md ├── Documentation-Communication-Local-Client.md ├── Documentation-Corporate-Identity.md ├── Documentation-Folder-Structure.md ├── Documentation-Single-Source-of-Truth.md ├── Github-Workflows.md ├── Home.md ├── How-To-Use-CSS.md ├── How-To-Write-Code-Documentation.md ├── Testing-Conventions.md ├── Tutorial.md ├── Vision-for-Ark-Automate.md ├── Why-RPA-Framework.md ├── _Sidebar.md └── images ├── Tutorial_Bot-Cockpit-Completed.png ├── Tutorial_Bot-Cockpit-Configuration.png ├── Tutorial_Bot-Modeler.png └── Tutorial_Bot-Overview.png /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Demonstrating empathy and kindness toward other people 18 | * Being respectful of differing opinions, viewpoints, and experiences 19 | * Giving and gracefully accepting constructive feedback 20 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 21 | * Focusing on what is best not just for us as individuals, but for the overall community 22 | 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or 42 | reject comments, commits, code, wiki edits, issues, and other contributions 43 | that are not aligned to this Code of Conduct, or to ban temporarily or 44 | permanently any contributor for other behaviors that they deem inappropriate, 45 | threatening, offensive, or harmful. 46 | 47 | ## Enforcement 48 | 49 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 50 | reported by contacting the project team at bpmw2020@gmail.com. All 51 | complaints will be reviewed and investigated and will result in a response that 52 | is deemed necessary and appropriate to the circumstances. The project team is 53 | obligated to maintain confidentiality with regard to the reporter of an incident. 54 | Further details of specific enforcement policies may be posted separately. 55 | 56 | Project maintainers who do not follow or enforce the Code of Conduct in good 57 | faith may face temporary or permanent repercussions as determined by other 58 | members of the project's leadership. 59 | 60 | ## Attribution 61 | 62 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 63 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 64 | 65 | [homepage]: https://www.contributor-covenant.org 66 | 67 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## reporting bugs 4 | * Use the [bug template](.github/ISSUE_TEMPLATE/bug_report.md) 5 | * Be descriptive when creating an issue (what, where, when and how does a problem pop up)? 6 | * Attach steps to reproduce (if applicable) 7 | * Attach code samples, configuration options or stack traces that may indicate a problem 8 | * Be helpful and respect others when commenting 9 | 10 | ## feature requests 11 | * Use the [user-story template](.github/ISSUE_TEMPLATE/user-story.md) or just put your thoughts on a note 12 | * Please formulate your thoughts and wishes behind the user story 13 | * If available, define open questions that we should discuss before starting the work 14 | * Define your acceptance criteria that fulfil your feature request 15 | 16 | ## pull requests 17 | Some things that make it easier for us to accept your pull requests. We also provide a [pull request template](.github/PULL_REQUEST_TEMPLATE.md) 18 | 19 | * The code is tested 20 | * The code is well documented 21 | * The linter has nothing to note 22 | * The `npm start` build passes 23 | * The pull request matches our requests from the template (screenshots, problem description, description of the solution) 24 | 25 | We'd be glad to assist you if you do not get these things right in the first place. 26 | 27 | ## further reading 28 | 29 | Maybe a look at the [documentation](https://github.com/bptlab/ark_automate/wiki) is a good start to the work on our project. 30 | We also recommend taking a look at our [Code of Conduct](.github/CODE_OF_CONDUCT.md). 31 | 32 | If you have any questions, don't be afraid to contact us on our [mailing list](mailto:bpmw2020@gmail.com). 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] Bug-Title" 5 | labels: Bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | ### Screenshots 14 | If applicable, add screenshots to help explain your problem. 15 | 16 | ### To Reproduce 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | ### Expected behavior 24 | A clear and concise description of what you expected to happen. 25 | 26 | ### Desktop (please complete the following information): 27 | - **OS:** [e.g. iOS] 28 | - **Browser:** [e.g. chrome, safari] 29 | - **JS IDE:** [e.g. VS Code, Atom] 30 | - (**Version:** [e.g. 22]) 31 | 32 | ### Additional context 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/user-story.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: User Story 3 | about: A normal user story as feature request. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | As a ‹role›, I'd like to ‹feature short description› [ , in order to ‹value it adds›. ] 11 | 12 | --- 13 | 14 | ### :thought_balloon:   Hints & Thoughts 15 | 16 | ### :question:   Additional Questions to Answer / Consider 17 | 18 | --- 19 | 20 | ## :white_check_mark:   Conditions of satisfaction 21 | 22 | - [ ] Should ‹testable condition that should be satisfied› 23 | - [ ] Should ‹testable condition that should be satisfied› 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | !Please link the completed Issue to this Pull Request! 2 | 3 | ## Changes 4 | **What does this PR change? What was the problem? What is the solution?** 5 | 6 | *describe problem here* 7 | - *enter change here* 8 | 9 | ## Screenshots 10 | *If the frontend has been changed, please add a screenshot of before and after the changes have been made* 11 | 12 | 13 | ## Checklist before merge 14 | 15 | **Developer's responsibilities** 16 | * [ ] **Assign** one or two developers 17 | * [ ] **Change code** if reviewer(s) has/have requested it 18 | * [ ] **Pull request build** has passed 19 | * [ ] **tested locally** (in at least chrome & firefox if frontend) 20 | * [ ] updated the **documentation** 21 | * [ ] added **tests** where necessary 22 | -------------------------------------------------------------------------------- /.github/workflows/backendTesting.yml: -------------------------------------------------------------------------------- 1 | name: Backend testing 2 | 3 | on: 4 | push: 5 | branches: [DEV] 6 | pull_request: 7 | branches: [DEV] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{matrix.os}} 13 | 14 | strategy: 15 | max-parallel: 24 16 | matrix: 17 | node-version: [12.x, 14.x] 18 | os: [ubuntu-latest, macos-latest, windows-latest] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm ci 27 | working-directory: server 28 | - run: npm test 29 | working-directory: server 30 | -------------------------------------------------------------------------------- /.github/workflows/frontendTesting.yml: -------------------------------------------------------------------------------- 1 | name: Frontend testing 2 | 3 | on: 4 | push: 5 | branches: [DEV] 6 | pull_request: 7 | branches: [DEV] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{matrix.os}} 13 | 14 | strategy: 15 | max-parallel: 24 16 | matrix: 17 | node-version: [12.x, 14.x] 18 | os: [ubuntu-latest, macos-latest, windows-latest] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm ci 27 | working-directory: frontend 28 | - run: npm test 29 | working-directory: frontend 30 | -------------------------------------------------------------------------------- /.github/workflows/herokuDeploy.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | workflow_dispatch: 6 | name: Heroku Deployment 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | - name: build frontend; do dir magic 15 | run: | 16 | cd frontend/ && npm install && CI=false npm run build 17 | cd .. 18 | mv ./frontend/build ./ 19 | rm -r frontend 20 | mv ./server/* ./ 21 | - uses: akhileshns/heroku-deploy@v3.11.10 22 | with: 23 | heroku_api_key: ${{secrets.HEROKU_API_KEY}} 24 | heroku_app_name: 'ark-automate' 25 | heroku_email: ${{secrets.HEROKU_EMAIL}} 26 | justlogin: true 27 | - run: heroku git:remote -a ark-automate 28 | - run: git config user.email "heroku.deployer@ark-automate.com" && git config user.name "Heroku Deployer" && git add . && git commit -m "heroku deployment commit" 29 | - run: git push heroku HEAD:master --force 30 | -------------------------------------------------------------------------------- /.github/workflows/linterFrontend.yml: -------------------------------------------------------------------------------- 1 | ########################### 2 | ########################### 3 | ## Linter GitHub Actions ## 4 | ########################### 5 | ########################### 6 | name: Lint Frontend Code Base 7 | 8 | # 9 | # Documentation: 10 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions 11 | # 12 | 13 | ############################# 14 | # Start the job on all push # 15 | ############################# 16 | on: 17 | push: 18 | branches-ignore: [master] 19 | # Remove the line above to run when pushing to master 20 | pull_request: 21 | branches: [master] 22 | 23 | ############### 24 | # Set the Job # 25 | ############### 26 | jobs: 27 | eslint: 28 | name: runner / eslint 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v2 32 | - uses: actions/setup-node@v2-beta 33 | with: 34 | node-version: '14' 35 | - run: npm ci 36 | working-directory: frontend 37 | - run: npm run lint 38 | working-directory: frontend 39 | -------------------------------------------------------------------------------- /.github/workflows/linterServer.yml: -------------------------------------------------------------------------------- 1 | ########################### 2 | ########################### 3 | ## Linter GitHub Actions ## 4 | ########################### 5 | ########################### 6 | name: Lint Server Code Base 7 | 8 | # 9 | # Documentation: 10 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions 11 | # 12 | 13 | ############################# 14 | # Start the job on all push # 15 | ############################# 16 | on: 17 | push: 18 | branches-ignore: [master] 19 | # Remove the line above to run when pushing to master 20 | pull_request: 21 | branches: [master] 22 | 23 | ############### 24 | # Set the Job # 25 | ############### 26 | jobs: 27 | eslint: 28 | name: runner / eslint 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v2 32 | - uses: actions/setup-node@v2-beta 33 | with: 34 | node-version: "14" 35 | - run: npm ci 36 | working-directory: server 37 | - run: npm run lint 38 | working-directory: server 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/publishDocumentation.yml: -------------------------------------------------------------------------------- 1 | name: Publish Documentation to GitHub pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | 15 | - name: navigate to frontend and build 16 | run: cd ./frontend/ && npm install --save-dev better-docs jsdoc && npm run docs 17 | 18 | - name: Deploy 19 | uses: peaceiris/actions-gh-pages@v3 20 | with: 21 | github_token: ${{ secrets.GITHUB_TOKEN }} 22 | publish_dir: ./documentationWebsite 23 | -------------------------------------------------------------------------------- /.github/workflows/wikiPageSync.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - DEV 5 | name: Wiki Update 6 | jobs: 7 | update-wiki: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Sync Wiki 12 | uses: joeizzard/action-wiki-sync@master 13 | with: 14 | username: example 15 | access_token: ${{ secrets.GITHUB_TOKEN }} 16 | wiki_folder: wiki 17 | commit_username: "Wiki Warden" 18 | commit_email: "wiki.warden@users.noreply.github.com" 19 | commit_message: "action: wiki sync" 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | frontend/node_modules/ 2 | frontend/coverage 3 | server/node_modules/ 4 | .idea/ 5 | .env 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "bracketSpacing": true, 4 | "endOfLine": "lf", 5 | "jsxSingleQuote": true, 6 | "semi": true, 7 | "singleQuote": true, 8 | "trailingComma": "es5", 9 | "printWidth": 80, 10 | "useTabs": false 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 [Sandro Speh](https://github.com/SanJSp), [Lukas Hüller](https://github.com/lukashueller), [Kay Erik Jenß](https://github.com/kej-jay), [Daniel Woelki](https://github.com/WolfgangDaniel) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /documentationConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "template": "../frontend/node_modules/better-docs", 4 | "destination": "./../documentationWebsite" 5 | }, 6 | "tags": { 7 | "allowUnknownTags": ["category", "component"] 8 | }, 9 | "plugins": ["node_modules/better-docs/category"], 10 | "source": { 11 | "include": [ 12 | "./../frontend/src", 13 | "./../server/utils/ssotToRobotParsing", 14 | "./../server/socket/socketHelperFunctions.js", 15 | "./../server/utils/testing/testDatabaseHandler.js" 16 | ], 17 | "exclude": ["./../frontend/node_modules", "./../server/node_modules"], 18 | "includePattern": ".+\\.js(doc|x)?$", 19 | "excludePattern": "(^|\\/|\\\\)_" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "plugin:react/recommended", 9 | "airbnb", 10 | "prettier", 11 | "prettier/react" 12 | ], 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "jsx": true 16 | }, 17 | "ecmaVersion": 12, 18 | "sourceType": "module" 19 | }, 20 | "plugins": ["react", "only-warn"], 21 | "rules": { 22 | "no-console": ["error", { "allow": ["warn", "error"] }] 23 | }, 24 | "root": true 25 | } 26 | -------------------------------------------------------------------------------- /frontend/craco.config.js: -------------------------------------------------------------------------------- 1 | const CracoAntDesignPlugin = require('craco-antd'); 2 | const customizedTheme = require('./src/layout/customizedTheme'); 3 | 4 | module.exports = { 5 | plugins: [ 6 | { 7 | plugin: CracoAntDesignPlugin, 8 | options: { 9 | customizeTheme: customizedTheme, 10 | }, 11 | }, 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ark-automate", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": ".", 6 | "proxy": "http://localhost:5000/", 7 | "dependencies": { 8 | "@ant-design/icons": "^4.4.0", 9 | "@craco/craco": "^6.1.1", 10 | "antd": "^4.8.2", 11 | "body-parser": "^1.18.3", 12 | "bpmn-font": "^0.10.0", 13 | "bpmn-js": "^7.4.0", 14 | "bpmn-js-cli": "^2.0.2", 15 | "bpmn-js-properties-panel": "^0.37.5", 16 | "camunda-bpmn-moddle": "^4.4.1", 17 | "craco-antd": "^1.19.0", 18 | "craco-less": "^1.17.1", 19 | "prismjs": "^1.23.0", 20 | "prop-types": "^15.7.2", 21 | "react": "^17.0.1", 22 | "react-dom": "^17.0.1", 23 | "react-router-dom": "^5.2.0", 24 | "react-scripts": "4.0.0", 25 | "react-simple-code-editor": "^0.11.0", 26 | "socket.io-client": "^4.0.0", 27 | "web-vitals": "^0.2.4", 28 | "xmljs2": "^1.0.0" 29 | }, 30 | "scripts": { 31 | "start": "craco start", 32 | "build": "craco build", 33 | "test": "craco test", 34 | "coverage": "craco test --collectCoverage", 35 | "eject": "react-scripts eject", 36 | "lint": "eslint --ignore-path ../.gitignore --ext .js,.jsx .", 37 | "docs": "jsdoc -c ../documentationConfig.json -r -R ../README.md" 38 | }, 39 | "eslintConfig": { 40 | "extends": [ 41 | "react-app", 42 | "react-app/jest" 43 | ] 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.2%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | }, 57 | "devDependencies": { 58 | "@testing-library/jest-dom": "^5.11.10", 59 | "@testing-library/react": "^11.2.6", 60 | "@testing-library/user-event": "^12.8.3", 61 | "better-docs": "^2.3.2", 62 | "eslint": "^7.17.0", 63 | "eslint-config-airbnb": "^18.2.1", 64 | "eslint-config-prettier": "^7.1.0", 65 | "eslint-plugin-import": "^2.22.1", 66 | "eslint-plugin-jsx-a11y": "^6.4.1", 67 | "eslint-plugin-only-warn": "^1.0.2", 68 | "eslint-plugin-react": "^7.22.0", 69 | "eslint-plugin-react-hooks": "^4.2.0", 70 | "jsdoc": "^3.6.6" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /frontend/public/favicon-16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/frontend/public/favicon-16.ico -------------------------------------------------------------------------------- /frontend/public/favicon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/frontend/public/favicon-192.png -------------------------------------------------------------------------------- /frontend/public/favicon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/frontend/public/favicon-512.png -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/public/logo/logoBlack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/frontend/public/logo/logoBlack.png -------------------------------------------------------------------------------- /frontend/public/logo/logoCta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/frontend/public/logo/logoCta.png -------------------------------------------------------------------------------- /frontend/public/logo/logoInverted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/frontend/public/logo/logoInverted.png -------------------------------------------------------------------------------- /frontend/public/logo/logoPrim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/frontend/public/logo/logoPrim.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Ark-Automate", 3 | "name": "Ark-Automate Robotic Process Automation App", 4 | "icons": [ 5 | { 6 | "src": "favicon-16.ico", 7 | "sizes": "16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "favicon-192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "favicon-512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } -------------------------------------------------------------------------------- /frontend/src/api/routes/functionalities/functionalities.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Frontend 3 | * @module 4 | */ 5 | 6 | /** 7 | * @description Fetch tasklist from Mongo-DB 8 | * @param {String} application - String with currently selected application from ApplicationDropdown 9 | * @returns {Array} Array of all task for the given application 10 | */ 11 | const fetchTasksFromDB = async (application) => { 12 | const response = await fetch(`/functionalities/${application}/tasks`); 13 | return response; 14 | }; 15 | 16 | /** 17 | * @description Fetch all applications from MongoDB 18 | * @returns {Array} Array of all available applications 19 | */ 20 | const getAvailableApplications = async () => { 21 | const response = await fetch('/functionalities/applications'); 22 | return response; 23 | }; 24 | 25 | /** 26 | * @description Will send a backend call to retrieve all rpa-task objects for the purpose of retrieving the related parameter and possible output value 27 | * @returns {Array} Array of all rpa-task objects 28 | */ 29 | const getAllRpaFunctionalities = async () => { 30 | const response = await fetch(`/functionalities`); 31 | return response; 32 | }; 33 | 34 | export { getAvailableApplications, fetchTasksFromDB, getAllRpaFunctionalities }; 35 | -------------------------------------------------------------------------------- /frontend/src/api/routes/robots/robots.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Frontend 3 | * @module 4 | */ 5 | 6 | /** 7 | * @description Triggers parsing of the ssot to .robot file 8 | * @returns {String} .robot file code 9 | */ 10 | const getParsedRobotFile = async (robotId) => 11 | fetch(`/robots/${robotId}/robotCode`); 12 | 13 | /** 14 | * @description Fetch the ssot correlating to the specified Id 15 | * @param {String} robotId Id of the robot that will be retrieved 16 | * @returns {Object} Found ssot 17 | */ 18 | const getSsot = async (robotId) => { 19 | const requestString = `/robots/${robotId}`; 20 | const response = await fetch(requestString); 21 | return response; 22 | }; 23 | 24 | /** 25 | * @description Rename the robot in the ssot 26 | * @param {String} robotId RobotId of the robot that will be renamed 27 | * @param {String} newRobotName String with the new RobotName 28 | * @returns {Object} Object containing robotName and starterId 29 | */ 30 | const changeSsotName = async (robotId, newRobotName) => { 31 | const payload = { 32 | newRobotName, 33 | }; 34 | const requestString = `/robots/${robotId}/robotName`; 35 | const requestParams = { 36 | body: JSON.stringify(payload), 37 | method: 'PATCH', 38 | headers: { 39 | 'Content-Type': 'application/json;charset=utf-8', 40 | }, 41 | }; 42 | const response = await fetch(requestString, requestParams); 43 | return response; 44 | }; 45 | 46 | /** 47 | * @description Delete a robot by sending a call to the backend 48 | * @param {String} robotId Id of the robot that will be deleted 49 | * @returns {Object} Mongoose query describing execution of call 50 | */ 51 | const deleteRobotFromDB = async (robotId) => { 52 | const requestStringParameters = `/robots/${robotId}`; 53 | await fetch(requestStringParameters, { method: 'DELETE' }).catch((err) => { 54 | console.error(err); 55 | }); 56 | }; 57 | 58 | /** 59 | * @description Overwrites an existing sssot in the backend with a new one 60 | * @param {String} robotId Id of the robot that will be overwritten 61 | * @param {String} ssot New ssot that will be written to the database 62 | * @returns {Object} Updated ssot object 63 | */ 64 | const updateRobot = async (robotId, ssot) => { 65 | const requestStringSsot = `/robots/${robotId}`; 66 | const response = await fetch(requestStringSsot, { 67 | body: ssot, 68 | method: 'PUT', 69 | headers: { 70 | 'Content-Type': 'application/json;charset=utf-8', 71 | }, 72 | }); 73 | return response; 74 | }; 75 | 76 | export { 77 | getParsedRobotFile, 78 | getSsot, 79 | changeSsotName, 80 | deleteRobotFromDB, 81 | updateRobot, 82 | }; 83 | -------------------------------------------------------------------------------- /frontend/src/api/routes/robots/rpaAttributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Frontend 3 | * @module 4 | */ 5 | 6 | /** 7 | * @description Will send a backend call to retrieve all attribute objects related to the provided robotId 8 | * @param {String} robotId Id of the robot for which to retrieve the values 9 | * @returns {Array} Array of attribute objects related to the robot 10 | */ 11 | const getAllAttributes = async (robotId) => { 12 | const response = await fetch(`/robots/rpaattributes/${robotId}`); 13 | return response; 14 | }; 15 | 16 | /** 17 | * @description Will send a backend call to update all given attribute objects with the new ones 18 | * @param {Array} attributeObjectList All updated attribute objects to overwrite the old attribute objects with 19 | * @returns {Array} Array of all updated attribute objects 20 | */ 21 | const updateManyAttributes = async (attributeObjectList) => { 22 | const requestStringAttributes = `/robots/rpaattributes`; 23 | const response = await fetch(requestStringAttributes, { 24 | body: JSON.stringify({ attributeObjectList }), 25 | method: 'PUT', 26 | headers: { 27 | 'Content-Type': 'application/json;charset=utf-8', 28 | }, 29 | }); 30 | return response; 31 | }; 32 | 33 | /** 34 | * @description Delete attributes for the given activities by sending a call to the backend 35 | * @param {String} robotId Id of the robot that is being used 36 | * @param {String} unusedActivityListString Stringified List of activityIds 37 | * @returns {Object} Mongoose query describing execution of call 38 | */ 39 | const deleteAttributesForActivities = (robotId, activityIdList) => { 40 | const requestStringParameters = `/robots/rpaattributes/${robotId}`; 41 | fetch(requestStringParameters, { 42 | method: 'DELETE', 43 | body: JSON.stringify({ activityIdList }), 44 | headers: { 45 | 'Content-Type': 'application/json;charset=utf-8', 46 | }, 47 | }).catch((err) => { 48 | console.error(err); 49 | }); 50 | }; 51 | 52 | export { 53 | getAllAttributes, 54 | updateManyAttributes, 55 | deleteAttributesForActivities, 56 | }; 57 | -------------------------------------------------------------------------------- /frontend/src/api/routes/robots/rpaParameter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Frontend 3 | * @module 4 | */ 5 | 6 | /** 7 | * @description Fetch all parameter objects for a specifc robot 8 | * @param { String } robotId Id of the robot to get all the parameters for 9 | * @returns {Array} All objects that have been found for the robot 10 | */ 11 | const getAllParametersForRobot = async (robotId) => { 12 | const requestString = `/robots/parameters/${robotId}`; 13 | const response = await fetch(requestString); 14 | return response; 15 | }; 16 | 17 | /** 18 | * @description Will send a backend call to update all given parameter objects with the new ones 19 | * @param {Array} parameterObjectsList All updated parameters objects to overwrite the old attribute objects with 20 | * @returns {Array} Array of all updated parameter objects 21 | */ 22 | const updateManyParameters = async (parameterObjectsList) => { 23 | const requestStringParameters = `/robots/parameters`; 24 | const response = await fetch(requestStringParameters, { 25 | body: JSON.stringify({ parameterObjectsList }), 26 | method: 'PUT', 27 | headers: { 28 | 'Content-Type': 'application/json;charset=utf-8', 29 | }, 30 | }); 31 | return response; 32 | }; 33 | 34 | /** 35 | * @description Delete parameters for the given activities by sending a call to the backend 36 | * @param {String} robotId Id of the robot that will be used 37 | * @param {String} unusedActivityListString Stringified List of activityIds 38 | * @returns {Object} Mongoose query describing execution of call 39 | */ 40 | const deleteParametersForActivities = (robotId, activityIdList) => { 41 | const requestStringParameters = `/robots/parameters/${robotId}`; 42 | fetch(requestStringParameters, { 43 | body: JSON.stringify({ activityIdList }), 44 | method: 'DELETE', 45 | headers: { 46 | 'Content-Type': 'application/json;charset=utf-8', 47 | }, 48 | }).catch((err) => { 49 | console.error(err); 50 | }); 51 | }; 52 | 53 | export { 54 | getAllParametersForRobot, 55 | updateManyParameters, 56 | deleteParametersForActivities, 57 | }; 58 | -------------------------------------------------------------------------------- /frontend/src/api/routes/users/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Frontend 3 | * @module 4 | */ 5 | 6 | /** 7 | * @description Fetch all those ssot names and ids, which are available for the current user 8 | * @param { String } userId UserId for which the ssots will be fetched 9 | * @returns {Array} Array of objects containing ssotId, robotName and starterId of each found robot 10 | */ 11 | const fetchSsotsForUser = async (userId) => { 12 | const requestString = `/users/${userId}/robots`; 13 | const response = await fetch(requestString); 14 | return response; 15 | }; 16 | 17 | /** 18 | * @description Create a new robot with the specified name for the specified user 19 | * @param {String} userId User for which the robot will be created 20 | * @param {String} robotName Name of the new robot 21 | * @returns {Object} Object containing robotId and robotName 22 | */ 23 | const createNewRobot = async (userId, robotName) => { 24 | const body = { 25 | userId, 26 | robotName, 27 | }; 28 | const requestString = `/users/${userId}/robots`; 29 | const response = await fetch(requestString, { 30 | body: JSON.stringify(body), 31 | method: 'POST', 32 | headers: { 33 | 'Content-Type': 'application/json;charset=utf-8', 34 | }, 35 | }); 36 | return response; 37 | }; 38 | 39 | export { fetchSsotsForUser, createNewRobot }; 40 | -------------------------------------------------------------------------------- /frontend/src/api/socketHandler/socketEmitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Frontend 3 | * @module 4 | */ 5 | import socket from '../../utils/socket/socketConnections'; 6 | 7 | /** 8 | * @description Emits a new event that a user joined its room 9 | * @param {String} userId Id of the user for which the room will be joined 10 | */ 11 | const joinRoomForUser = (userId) => { 12 | socket.emit('joinUserRoom', userId, 'webApplication'); 13 | }; 14 | 15 | /** 16 | * @description Emits a new event that a user wants to enqueue a robot instace for execution 17 | * @param {String} userId Id of the user for which the robot will be started 18 | * @param {String} robotId Id of the robot which will be started 19 | */ 20 | const startRobotForUser = (userId, robotId, parameters) => { 21 | socket.emit('robotExecutionJobs', { robotId, userId, parameters }); 22 | }; 23 | 24 | export { joinRoomForUser, startRobotForUser }; 25 | -------------------------------------------------------------------------------- /frontend/src/api/socketHandler/socketListeners.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Frontend 3 | * @module 4 | */ 5 | import socket from '../../utils/socket/socketConnections'; 6 | 7 | /** 8 | * @description Registers listener on successful connection to a user room 9 | */ 10 | const successRoomConnection = () => { 11 | socket.on('successUserRoomConnection', (message) => message); 12 | }; 13 | 14 | /** 15 | * @description Registers listener on faulty connection to a user room 16 | */ 17 | const errorRoomConnection = () => { 18 | socket.on('errorUserRoomConnection', (message) => message); 19 | }; 20 | 21 | /** 22 | * @description Registers listener when a new client joins the room 23 | */ 24 | const newClientJoined = () => { 25 | socket.on('newClientJoinedUserRoom', (message) => message); 26 | }; 27 | 28 | /** 29 | * @description Register listener when a new robot log update has been send 30 | * @param {function} logSetterMethod Method reference to update the state of the log in the robotInteractionCockpit 31 | */ 32 | const newRobotMonitorUpdate = (logSetterMethod) => { 33 | socket.on('changedRobotRunLogs', (robotLogs) => { 34 | logSetterMethod(robotLogs); 35 | }); 36 | }; 37 | 38 | /** 39 | * @description Register listener on when a new robot status has been set 40 | * @param {function} statusSetterMethod Method reference to update the status of the log in the robotInteractionCockpit 41 | */ 42 | const newRobotStatusUpdate = (statusSetterMethod) => { 43 | socket.on('changedRobotStatus', (status) => statusSetterMethod(status)); 44 | }; 45 | 46 | export { 47 | successRoomConnection, 48 | errorRoomConnection, 49 | newClientJoined, 50 | newRobotMonitorUpdate, 51 | newRobotStatusUpdate, 52 | }; 53 | -------------------------------------------------------------------------------- /frontend/src/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter, Route, Switch } from 'react-router-dom'; 3 | import RobotModeler from './pages/RobotModeler/RobotModeler'; 4 | import Home from './pages/Home/Home'; 5 | import Error from './pages/Error/Error'; 6 | import RobotFile from './pages/RobotCodeEditor/RobotCodeEditor'; 7 | import RobotOverview from './pages/RobotOverview/RobotOverview'; 8 | import RobotInteractionCockpit from './pages/RobotInteractionCockpit/RobotInteractionCockpit'; 9 | 10 | /** 11 | * @description Route component of the application 12 | * @category Frontend 13 | * @component 14 | */ 15 | const App = () => ( 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 |
30 |
31 | ); 32 | 33 | export default App; 34 | -------------------------------------------------------------------------------- /frontend/src/components/multiPageComponents/HeaderNavbar/HeaderNavbar.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import { Layout, Menu } from 'antd'; 4 | import { Link } from 'react-router-dom'; 5 | import { getRobotId } from '../../../utils/sessionStorage/localSsotController/ssot'; 6 | import styles from './HeaderNavbar.module.css'; 7 | 8 | const { Header } = Layout; 9 | 10 | /** 11 | * @component 12 | * @description Renders the header navbar for all pages and initially selects the passed key element. 13 | * @category Frontend 14 | * @example return 15 | */ 16 | const HeaderNavbar = (props) => { 17 | const { selectedKey } = props; 18 | const ICON_KEY = 0; 19 | const ROBOT_OVERVIEW_PAGE_KEY = 1; 20 | const BPMN_MODELER_PAGE_KEY = 2; 21 | const ROBOT_CODE_EDITOR_PAGE_KEY = 3; 22 | const ROBOT_INTERACTION_PAGE_KEY = 4; 23 | 24 | let onOverview = false; 25 | if (selectedKey === ROBOT_OVERVIEW_PAGE_KEY) { 26 | onOverview = true; 27 | } 28 | 29 | let onRobotInteraction = false; 30 | if (selectedKey === ROBOT_INTERACTION_PAGE_KEY) { 31 | onRobotInteraction = true; 32 | } 33 | 34 | let bpmnModelerLink = '/modeler'; 35 | if ( 36 | selectedKey === BPMN_MODELER_PAGE_KEY || 37 | selectedKey === ROBOT_CODE_EDITOR_PAGE_KEY 38 | ) { 39 | bpmnModelerLink += `/${getRobotId()}`; 40 | } 41 | 42 | return ( 43 |
44 | 49 | 50 | 51 | Ark Automate Icon 56 | 57 | 58 | 59 | 60 | Overview 61 | 62 | 63 | {!onRobotInteraction && ( 64 | <> 65 | {!onOverview && ( 66 | 67 | Modeler 68 | 69 | 70 | )} 71 | {!onOverview && ( 72 | 73 | Robot Code 74 | 75 | 76 | )} 77 | 78 | )} 79 | {onRobotInteraction && ( 80 | 81 | Robot Interaction Cockpit 82 | 83 | )} 84 | 85 |
86 | ); 87 | }; 88 | 89 | HeaderNavbar.propTypes = { 90 | selectedKey: PropTypes.number.isRequired, 91 | }; 92 | 93 | export default HeaderNavbar; 94 | -------------------------------------------------------------------------------- /frontend/src/components/multiPageComponents/HeaderNavbar/HeaderNavbar.module.css: -------------------------------------------------------------------------------- 1 | .modifiedMenuItem:hover { 2 | background-color: var(--colorPrimaryInverted) !important; 3 | } 4 | .header { 5 | padding: 0; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/components/pages/Error/Error.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Layout, Typography } from 'antd'; 3 | import HeaderNavbar from '../../multiPageComponents/HeaderNavbar/HeaderNavbar'; 4 | 5 | const { Title } = Typography; 6 | 7 | /** 8 | * @description Error page of the application 9 | * @category Frontend 10 | * @component 11 | */ 12 | const Error = () => ( 13 |
14 | 15 | 16 |
17 | Error: Page does not exist! 18 |
19 |
20 | ); 21 | 22 | export default Error; 23 | -------------------------------------------------------------------------------- /frontend/src/components/pages/Home/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Button, Typography, Space } from 'antd'; 4 | import corporateDesign from '../../../layout/corporateDesign'; 5 | import styles from './Home.module.css'; 6 | 7 | const { Title } = Typography; 8 | 9 | /** 10 | * @description Homepage of the application 11 | * @category Frontend 12 | * @component 13 | */ 14 | const Home = () => { 15 | const [message] = useState(null); 16 | 17 | return ( 18 |
19 | 24 | 27 | Ark 28 | 29 | 36 | fast, easy, automation 37 | 38 |

{message}

39 | 40 | 43 | 44 |
45 |
46 | ); 47 | }; 48 | 49 | export default Home; 50 | -------------------------------------------------------------------------------- /frontend/src/components/pages/Home/Home.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | height: 100vh; 3 | width: 100vw; 4 | background-color: var(--colorPrimaryInverted); 5 | display: flex; 6 | align-items: center; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotCodeEditor/RobotCodeEditor.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Layout, Button, Space, Row, Col } from 'antd'; 3 | import { highlight, languages } from 'prismjs/components/prism-core'; 4 | import Editor from 'react-simple-code-editor'; 5 | import HeaderNavbar from '../../multiPageComponents/HeaderNavbar/HeaderNavbar'; 6 | import { getParsedRobotFile } from '../../../api/routes/robots/robots'; 7 | import { parseRobotCodeToSsot } from '../../../utils/parser/robotCodeToSsotParsing/robotCodeToSsotParsing'; 8 | import { 9 | getRobotId, 10 | upsert, 11 | } from '../../../utils/sessionStorage/localSsotController/ssot'; 12 | import 'prismjs/components/prism-robotframework'; 13 | import 'prismjs/themes/prism.css'; 14 | import styles from './RobotCodeEditor.module.css'; 15 | import customNotification from '../../../utils/componentsFunctionality/notificationUtils'; 16 | import RobotFileSyntaxModal from './RobotCodeEditorSyntaxPopup/RobotCodeEditorSyntaxPopup'; 17 | 18 | /** 19 | * @description View of the robot file 20 | * @category Frontend 21 | * @component 22 | */ 23 | const RobotFile = () => { 24 | const [code, setCode] = useState( 25 | 'Please wait, your robot file is being loaded.' 26 | ); 27 | 28 | const [isModalVisible, setIsModalVisible] = useState(false); 29 | 30 | const showModal = () => { 31 | setIsModalVisible(true); 32 | }; 33 | 34 | const handleModalClose = () => { 35 | setIsModalVisible(false); 36 | }; 37 | 38 | /** 39 | * @description Equivalent to ComponentDidMount in class based components 40 | */ 41 | useEffect(() => { 42 | const robotId = getRobotId(); 43 | getParsedRobotFile(robotId) 44 | .then((response) => response.text()) 45 | .then((robotCode) => { 46 | setCode(robotCode); 47 | }); 48 | }, []); 49 | 50 | /** 51 | * @description Will retrieve the code from the editor, parse it to a ssot and write the 52 | * resulting ssot into the sessionStorage. Gets called when the the button is pressed to save to the cloud. 53 | */ 54 | const onSaveToCloud = () => { 55 | const ssot = parseRobotCodeToSsot(code); 56 | if (typeof ssot === 'undefined') { 57 | customNotification( 58 | 'Warning', 59 | 'Because a parsing error occurred, the robot was not saved to cloud.' 60 | ); 61 | } else { 62 | sessionStorage.setItem('ssotLocal', JSON.stringify(ssot)); 63 | 64 | upsert(); 65 | } 66 | }; 67 | 68 | return ( 69 | 70 | 71 | 72 | 73 | 78 |
79 | 86 | 93 |
94 | 98 | setCode(newCode)} 101 | highlight={(highlightCode) => 102 | highlight(highlightCode, languages.robotframework) 103 | } 104 | padding='1rem' 105 | className={styles.editor} 106 | tabsize={4} 107 | /> 108 |
109 | 110 |
111 |
112 | ); 113 | }; 114 | 115 | export default RobotFile; 116 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotCodeEditor/RobotCodeEditor.module.css: -------------------------------------------------------------------------------- 1 | .editor { 2 | font-family: Consolas, monospace; 3 | font-size: 1.25rem; 4 | background-color: var(--colorBackground2); 5 | } 6 | 7 | .syntaxModalButton { 8 | background-color: var(--colorPrimaryInverted2); 9 | border-color: var(--colorPrimaryInverted2); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotCodeEditor/RobotCodeEditorSyntaxPopup/RobotCodeEditorSyntaxPopup.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Table } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** 6 | * @description View of the robot file 7 | * @category Frontend 8 | * @component 9 | */ 10 | const RobotFileSyntaxModal = (props) => { 11 | const { visible, handleClose } = props; 12 | const dataSource = [ 13 | { 14 | key: '1', 15 | name: 'Regular parameters', 16 | syntax: 'parameterValue', 17 | description: 'Hard code the specific value for this parameter', 18 | }, 19 | { 20 | key: '2', 21 | name: 'Empty Parameter Field', 22 | syntax: '%%parameterName%%', 23 | description: 24 | 'Highlights a field which has not been configured yet in the ssot', 25 | }, 26 | { 27 | key: '3', 28 | name: 'Requires User Input', 29 | syntax: '!!parameterName!!', 30 | description: 31 | 'Highlights a parameter which should be specified by the user when starting the robot', 32 | }, 33 | { 34 | key: '4', 35 | name: 'Parameter', 36 | syntax: '$(parameterName)', 37 | description: 38 | 'Used to pass in a parameter, which could be the output of a previous activity', 39 | }, 40 | ]; 41 | 42 | const columns = [ 43 | { 44 | title: 'Name', 45 | dataIndex: 'name', 46 | key: 'name', 47 | }, 48 | { 49 | title: 'Syntax specification', 50 | dataIndex: 'syntax', 51 | key: 'syntax', 52 | }, 53 | { 54 | title: 'Description', 55 | dataIndex: 'description', 56 | key: 'description', 57 | }, 58 | ]; 59 | 60 | return ( 61 | 68 | 69 | 70 | ); 71 | }; 72 | 73 | RobotFileSyntaxModal.propTypes = { 74 | visible: PropTypes.bool.isRequired, 75 | handleClose: PropTypes.func.isRequired, 76 | }; 77 | 78 | export default RobotFileSyntaxModal; 79 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotInteractionCockpit/RobotInteractionCockpit.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | background-color: var(--colorPrimaryInverted); 3 | border-color: var(--colorPrimaryInverted); 4 | } 5 | 6 | .cardStyle { 7 | margin: '24px' !important; 8 | border-radius: '5px' !important; 9 | } 10 | 11 | .statusIconStyle { 12 | position: relative; 13 | top: 40%; 14 | left: 50%; 15 | transform: translate(-50%, -50%); 16 | font-size: 3rem; 17 | margin: 10px; 18 | } 19 | 20 | .loadingStatusIconStyle { 21 | top: 40%; 22 | left: 50%; 23 | font-size: 3rem; 24 | color: grey; 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotInteractionCockpit/RobotInteractionSections/RobotInteractionExecutionSection/RobotInteractionExecutionSection.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Col, Row, Typography, Card } from 'antd'; 3 | import { 4 | CheckCircleOutlined, 5 | CloseCircleOutlined, 6 | Loading3QuartersOutlined, 7 | PauseCircleOutlined, 8 | } from '@ant-design/icons'; 9 | import PropTypes from 'prop-types'; 10 | import RobotLogEntryCard from './subComponents/RobotLogEntryCard'; 11 | import styles from '../../RobotInteractionCockpit.module.css'; 12 | 13 | const { Title } = Typography; 14 | /** 15 | * @description Renders the robot logs and robot status while executing 16 | * @category Frontend 17 | * @component 18 | */ 19 | const RobotInteractionExecutionSection = (props) => { 20 | const { executionLogs } = props; 21 | const { robotState } = props; 22 | 23 | /** 24 | * @description Displays the suitable status icon for robot execution 25 | * @param {String} status Current Status of the robot execution 26 | * @returns {React.ReactElement} Returns an icon component or undefined 27 | */ 28 | const displayStatusIcon = (status) => { 29 | if (status === 'PASS' || status === 'successful') { 30 | return ( 31 | 35 | ); 36 | } 37 | if (status === 'FAIL' || status === 'failed') { 38 | return ( 39 | 43 | ); 44 | } 45 | if (status === 'running') { 46 | return ( 47 | 51 | ); 52 | } 53 | if (status === 'waiting') { 54 | return ( 55 | 59 | ); 60 | } 61 | return undefined; 62 | }; 63 | 64 | return ( 65 | <> 66 | 67 | 68 | 75 | Robot Status 76 | 77 | 78 | 79 | 80 | 87 | {robotState.toLocaleUpperCase()} 88 | 89 | 90 | {displayStatusIcon(robotState)} 91 | 92 | 93 | 94 | 95 | 96 | Robot Run Logs 97 | 98 | {executionLogs.robotRun && 99 | executionLogs.robotRun.activities.map((log, index) => ( 100 | 106 | ))} 107 | 108 | 109 | 110 | ); 111 | }; 112 | 113 | RobotInteractionExecutionSection.propTypes = { 114 | executionLogs: PropTypes.objectOf(PropTypes.shape).isRequired, 115 | robotState: PropTypes.string.isRequired, 116 | }; 117 | 118 | export default RobotInteractionExecutionSection; 119 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotInteractionCockpit/RobotInteractionSections/RobotInteractionExecutionSection/subComponents/RobotLogEntryCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card, Row, Col } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** 6 | * @description Renders the status of an individual robot log 7 | * @category Frontend 8 | * @component 9 | */ 10 | const RobotLogCard = (props) => { 11 | const { log } = props; 12 | const { displayStatusIcon } = props; 13 | 14 | return ( 15 | 23 | 24 | 25 | {log.message &&

Error Message: {log.message}

} 26 | {log.tasks && 27 | log.tasks.map((task, index) => ( 28 | // eslint-disable-next-line react/no-array-index-key 29 | 30 | 31 | 32 |

Task: {task.taskName}

33 | 34 | 35 |

Status: {task.status}

36 | 37 | 38 | 39 | ))} 40 | 41 | 42 | <>{displayStatusIcon(log.status)} 43 | 44 | 45 | 46 | ); 47 | }; 48 | export default RobotLogCard; 49 | 50 | RobotLogCard.propTypes = { 51 | displayStatusIcon: PropTypes.func.isRequired, 52 | log: PropTypes.oneOfType([PropTypes.arrayOf(Object), PropTypes.any]) 53 | .isRequired, 54 | }; 55 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotInteractionCockpit/RobotInteractionSections/RobotInteractionInputSection/RobotInteractionInputSection.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-array-index-key */ 2 | /* eslint-disable no-underscore-dangle */ 3 | import React from 'react'; 4 | import PropTypes from 'prop-types'; 5 | import { Card, Typography, Space } from 'antd'; 6 | import RobotInteractionInputParameter from './subComponents/RobotInteractionParameterInput'; 7 | 8 | const { Title } = Typography; 9 | 10 | /** 11 | * @description Renders all necessary input fields for inserting the missing parameters before executing a robot 12 | * @category Frontend 13 | * @component 14 | */ 15 | const RobotInteractionInputSection = ({ 16 | parameterList, 17 | updateParameterValue, 18 | }) => ( 19 | 20 | {parameterList.map((activityInformation, index) => { 21 | if (activityInformation.activityParameter.length > 0) { 22 | return ( 23 | 24 | 25 | Activity: {activityInformation.activityName} 26 | 27 | {activityInformation.activityParameter.map((params) => ( 28 | 37 | ))} 38 | 39 | ); 40 | } 41 | return
; 42 | })} 43 | 44 | ); 45 | 46 | RobotInteractionInputSection.propTypes = { 47 | parameterList: PropTypes.arrayOf(PropTypes.shape).isRequired, 48 | updateParameterValue: PropTypes.func.isRequired, 49 | }; 50 | 51 | export default RobotInteractionInputSection; 52 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotInteractionCockpit/RobotInteractionSections/RobotInteractionInputSection/subComponents/RobotInteractionParameterInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Typography, Input, Tooltip } from 'antd'; 3 | import { InfoCircleOutlined } from '@ant-design/icons'; 4 | import PropTypes from 'prop-types'; 5 | import corporateDesign from '../../../../../../layout/corporateDesign'; 6 | 7 | const { Text } = Typography; 8 | /** 9 | * @description Renders a parameter input field for a given parameter 10 | * @category Frontend 11 | * @component 12 | */ 13 | const RobotInteractionInputParameter = ({ 14 | parameterName, 15 | // eslint-disable-next-line no-unused-vars 16 | dataType, 17 | infoText, 18 | updateParameterValue, 19 | parameterId, 20 | }) => ( 21 | <> 22 | {parameterName} 23 | 27 | updateParameterValue(parameterId, event.target.value) 28 | } 29 | suffix={ 30 | 31 | 34 | 35 | } 36 | /> 37 | 38 | ); 39 | 40 | RobotInteractionInputParameter.propTypes = { 41 | parameterName: PropTypes.string.isRequired, 42 | dataType: PropTypes.string.isRequired, 43 | infoText: PropTypes.string, 44 | updateParameterValue: PropTypes.func.isRequired, 45 | parameterId: PropTypes.string.isRequired, 46 | }; 47 | 48 | RobotInteractionInputParameter.defaultProps = { 49 | infoText: undefined, 50 | }; 51 | 52 | export default RobotInteractionInputParameter; 53 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotInteractionCockpit/robotInteractionCockpitFunctionality/robotExecution.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | configuredRobotParamsCorrectly, 3 | configuredRobotActivitesCorrectly, 4 | } from './robotInteractionCockpitFunctionality'; 5 | import { 6 | paramObject1, 7 | paramObject2, 8 | paramObjectWithLaterSpecifiedUserInput, 9 | paramObjectWithoutRequiredValue, 10 | attributeObject1, 11 | attributeObject2, 12 | attributeObjectWithEmptyApplication, 13 | attributeObjectWithEmptyTask, 14 | } from './robotExecutionTestData'; 15 | 16 | describe('Checking robot parameters for Executability', () => { 17 | it('correctly assigns robot as executable', async () => { 18 | const correctListOfParameters = [paramObject1, paramObject2]; 19 | const response = configuredRobotParamsCorrectly(correctListOfParameters); 20 | expect(response).toBe(true); 21 | }); 22 | 23 | it('correctly assigns robot as not executable when required parameter is empty', () => { 24 | const incorrectListOfParameters = [ 25 | paramObject1, 26 | paramObjectWithoutRequiredValue, 27 | ]; 28 | const response = configuredRobotParamsCorrectly(incorrectListOfParameters); 29 | expect(response).toBe(false); 30 | }); 31 | 32 | it('correctly recognizes a robot as executable if required parameter is empty but is specified later by user input', () => { 33 | const listOfParameters = [ 34 | paramObject1, 35 | paramObjectWithLaterSpecifiedUserInput, 36 | ]; 37 | const response = configuredRobotParamsCorrectly(listOfParameters); 38 | expect(response).toBe(true); 39 | }); 40 | }); 41 | 42 | describe('Checking robot attributes for Executability', () => { 43 | it('correctly assigns robot as executable', () => { 44 | const correctListOfParameters = [attributeObject1, attributeObject2]; 45 | const response = configuredRobotActivitesCorrectly(correctListOfParameters); 46 | expect(response).toBe(true); 47 | }); 48 | 49 | it('correctly assigns robot as not executable when application is empty', () => { 50 | const incorrectListOfParameters = [ 51 | attributeObject1, 52 | attributeObjectWithEmptyApplication, 53 | ]; 54 | const response = configuredRobotActivitesCorrectly( 55 | incorrectListOfParameters 56 | ); 57 | expect(response).toBe(false); 58 | }); 59 | 60 | it('correctly assigns robot as not executable when task is empty', () => { 61 | const incorrectListOfParameters = [ 62 | attributeObject1, 63 | attributeObjectWithEmptyTask, 64 | ]; 65 | const response = configuredRobotActivitesCorrectly( 66 | incorrectListOfParameters 67 | ); 68 | expect(response).toBe(false); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotInteractionCockpit/robotInteractionCockpitFunctionality/robotExecutionTestData.js: -------------------------------------------------------------------------------- 1 | const paramObject1 = { 2 | activityId: 'Activity_0ay88v7', 3 | robotId: '60618ee17e82bbf2ca91cbb6', 4 | rpaParameters: [ 5 | { 6 | name: 'uri', 7 | type: 'String', 8 | isRequired: true, 9 | infoText: 'To send the GET request to', 10 | index: 1, 11 | value: 'https://localhost:3000/', 12 | }, 13 | { 14 | name: 'headers', 15 | type: 'String', 16 | isRequired: false, 17 | infoText: 'A dictionary of headers to use with the request', 18 | index: 2, 19 | value: '', 20 | }, 21 | { 22 | name: 'alias', 23 | type: 'String', 24 | isRequired: true, 25 | infoText: 26 | ' That will be used to identify the Session object in the cache', 27 | index: 0, 28 | value: 'aliasString', 29 | }, 30 | ], 31 | }; 32 | 33 | const paramObject2 = { 34 | activityId: 'Activity_0mgbrvu', 35 | robotId: '60619b7b5d691786a44a6093', 36 | rpaParameters: [ 37 | { 38 | name: 'path', 39 | type: 'String', 40 | isRequired: false, 41 | infoText: 42 | 'Path to save to. If not given, uses path given when opened or created.', 43 | index: 0, 44 | value: 'C:/Users/daniel/Desktop/demo.xlsx', 45 | }, 46 | ], 47 | }; 48 | 49 | const paramObjectWithoutRequiredValue = { 50 | activityId: 'Activity_0mgbrvu', 51 | robotId: '60619b7b5d691786a44a6093', 52 | rpaParameters: [ 53 | { 54 | name: 'path', 55 | type: 'String', 56 | isRequired: true, 57 | infoText: 58 | 'Path to save to. If not given, uses path given when opened or created.', 59 | index: 0, 60 | value: '', 61 | }, 62 | ], 63 | }; 64 | 65 | const paramObjectWithLaterSpecifiedUserInput = { 66 | activityId: 'Activity_0mgbrvu', 67 | robotId: '60619b7b5d691786a44a6093', 68 | rpaParameters: [ 69 | { 70 | name: 'path', 71 | type: 'String', 72 | requireUserInput: true, 73 | isRequired: true, 74 | infoText: 75 | 'Path to save to. If not given, uses path given when opened or created.', 76 | index: 0, 77 | value: '', 78 | }, 79 | ], 80 | }; 81 | 82 | const attributeObject1 = { 83 | activityId: 'Activity_1kwfds7', 84 | robotId: '60618ee17e82bbf2ca91cbb6', 85 | rpaApplication: 'Excel.Application', 86 | rpaTask: 'Quit Excel', 87 | }; 88 | 89 | const attributeObject2 = { 90 | activityId: 'Activity_0ay88v7', 91 | robotId: '60618ee17e82bbf2ca91cbb6', 92 | rpaApplication: 'HTTP', 93 | rpaTask: 'Get Request', 94 | }; 95 | 96 | const attributeObjectWithEmptyTask = { 97 | activityId: 'Activity_0ay88v7', 98 | robotId: '60618ee17e82bbf2ca91cbb6', 99 | rpaApplication: 'HTTP', 100 | rpaTask: '', 101 | }; 102 | 103 | const attributeObjectWithEmptyApplication = { 104 | activityId: 'Activity_0ay88v7', 105 | robotId: '60618ee17e82bbf2ca91cbb6', 106 | rpaApplication: '', 107 | rpaTask: 'Get Request', 108 | }; 109 | 110 | export { 111 | paramObject1, 112 | paramObject2, 113 | paramObjectWithLaterSpecifiedUserInput, 114 | paramObjectWithoutRequiredValue, 115 | attributeObject1, 116 | attributeObject2, 117 | attributeObjectWithEmptyApplication, 118 | attributeObjectWithEmptyTask, 119 | }; 120 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/BpmnModeler/BpmnModeler.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import CamundaBpmnModeler from 'bpmn-js/lib/Modeler'; 3 | import { Layout } from 'antd'; 4 | import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'; 5 | import PropTypes from 'prop-types'; 6 | import CliModule from 'bpmn-js-cli'; 7 | import removeUnsupportedBpmnFunctions from './removeUnsupportedBpmnFunctions'; 8 | import { emptyBpmn } from '../../../../resources/modeler/emptyBpmn'; 9 | import styles from './BpmnModeler.module.css'; 10 | import 'bpmn-js/dist/assets/diagram-js.css'; 11 | import 'bpmn-font/dist/css/bpmn-embedded.css'; 12 | 13 | const { Content } = Layout; 14 | 15 | /** 16 | * @description This component renders the BPMN modeling interface as well as the sidebar. 17 | * @category Frontend 18 | * @component 19 | */ 20 | const BpmnModeler = (props) => { 21 | let newModeler; 22 | 23 | /** 24 | * @description While the components were mounted, the BPMN-Modeler is initialized 25 | */ 26 | useEffect(() => { 27 | newModeler = new CamundaBpmnModeler({ 28 | container: '#bpmnview', 29 | keyboard: { 30 | bindTo: window, 31 | }, 32 | additionalModules: [ 33 | { 34 | __init__: ['customContextPadProvider'], 35 | customContextPadProvider: ['type', removeUnsupportedBpmnFunctions()], 36 | }, 37 | propertiesProviderModule, 38 | CliModule, 39 | ], 40 | cli: { 41 | bindTo: 'cli', 42 | }, 43 | }); 44 | 45 | props.onModelerUpdate(newModeler); 46 | 47 | const openBpmnDiagram = (xml) => { 48 | newModeler 49 | .importXML(xml) 50 | .then(() => { 51 | const canvas = newModeler.get('canvas'); 52 | canvas.zoom('fit-viewport'); 53 | }) 54 | .catch((error) => { 55 | console.error('failed to import xml: ', error); 56 | }); 57 | }; 58 | openBpmnDiagram(emptyBpmn); 59 | }, []); 60 | 61 | return ( 62 | 63 |
64 | 65 | ); 66 | }; 67 | 68 | BpmnModeler.propTypes = { 69 | onModelerUpdate: PropTypes.func.isRequired, 70 | }; 71 | 72 | export default BpmnModeler; 73 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/BpmnModeler/BpmnModeler.module.css: -------------------------------------------------------------------------------- 1 | .bpmn-modeler-container { 2 | width: 100%; 3 | height: calc(100vh - 4rem); 4 | float: left; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/BpmnModeler/removeUnsupportedBpmnFunctions.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-names */ 2 | import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider'; 3 | 4 | /** 5 | * @description Removes unsopported BPMN artifacts from the modeler 6 | * @category Frontend 7 | */ 8 | const removeUnsupportedBpmnFunctions = () => { 9 | const { getPaletteEntries } = PaletteProvider.prototype; 10 | PaletteProvider.prototype.getPaletteEntries = function () { 11 | const entries = getPaletteEntries.apply(this); 12 | delete entries['create.intermediate-event']; 13 | delete entries['create.subprocess-expanded']; 14 | delete entries['create.participant-expanded']; 15 | delete entries['create.group']; 16 | delete entries['create.exclusive-gateway']; 17 | delete entries['create.data-store']; 18 | delete entries['create.data-object']; 19 | return entries; 20 | }; 21 | class CustomContextPadProvider { 22 | constructor(contextPad) { 23 | contextPad.registerProvider(this); 24 | } 25 | 26 | // eslint-disable-next-line class-methods-use-this 27 | getContextPadEntries() { 28 | return function (entries) { 29 | const customizesEntries = entries; 30 | delete customizesEntries['append.text-annotation']; 31 | delete customizesEntries['append.gateway']; 32 | delete customizesEntries['append.intermediate-event']; 33 | delete customizesEntries['lane-insert-above']; 34 | delete customizesEntries['lane-divide-two']; 35 | delete customizesEntries['lane-divide-three']; 36 | delete customizesEntries['lane-insert-below']; 37 | delete customizesEntries.replace; 38 | return customizesEntries; 39 | }; 40 | } 41 | } 42 | 43 | CustomContextPadProvider.$inject = ['contextPad']; 44 | return CustomContextPadProvider; 45 | }; 46 | 47 | export default removeUnsupportedBpmnFunctions; 48 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/ModelerSidebar.module.css: -------------------------------------------------------------------------------- 1 | .label-on-dark-background { 2 | color: var(--colorPrimaryInvertedText) !important; 3 | } 4 | 5 | .requiredStar { 6 | color: var(--colorBackgroundCta) !important; 7 | } 8 | 9 | .title { 10 | margin-bottom: 0rem !important; 11 | color: var(--colorPrimaryInvertedText) !important; 12 | } 13 | 14 | .properties-panel-dropdown { 15 | width: 100%; 16 | } 17 | 18 | .sider { 19 | background: var(--colorPrimaryInverted2); 20 | padding: 1rem; 21 | min-width: 20rem !important; 22 | } 23 | 24 | .button { 25 | width: 100%; 26 | } 27 | 28 | .parameterButton { 29 | width: 100%; 30 | background-color: var(--color-Background); 31 | border-color: var(--colorBackgroundCta); 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/PropertiesPanel/PropertiesPanel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { is } from 'bpmn-js/lib/util/ModelUtil'; 3 | import { Space } from 'antd'; 4 | import PropTypes from 'prop-types'; 5 | 6 | import PPIdSection from './PropertiesPanelSections/PPIdSection/PPIdSection'; 7 | import PPTitle from './PropertiesPanelSections/PPTitleSection/PPTitleSection'; 8 | import PPNameSection from './PropertiesPanelSections/PPNameSection/PPNameSection'; 9 | import PPParameterSection from './PropertiesPanelSections/PPParameterSection/PPParameterSection'; 10 | import PPRpaSection from './PropertiesPanelSections/PPRpaSection/PPRpaSection'; 11 | import PPOutputValueSection from './PropertiesPanelSections/PPOutputValueSection/PPOutputValueSection'; 12 | 13 | /** 14 | * @description Displays the PropertiesPanel for one selected BPMN-Element. 15 | * @category Frontend 16 | * @component 17 | */ 18 | const PropertiesPanel = ({ 19 | element, 20 | nameChanged, 21 | applicationSelectionUpdated, 22 | tasksForSelectedApplication, 23 | selectedActivity, 24 | taskSelectionUpdated, 25 | disableTaskSelection, 26 | robotId, 27 | parameterList, 28 | parameterSelectionUpdated, 29 | outputValueName, 30 | outputNameUpdated, 31 | }) => ( 32 |
33 | 34 | 35 | 36 | 37 | 38 | {is(element, 'bpmn:Task') && ( 39 | <> 40 | 48 | {parameterList.length > 0 && ( 49 | 55 | )} 56 | {outputValueName && ( 57 | 61 | )} 62 | 63 | )} 64 | 65 |
66 | ); 67 | 68 | PropertiesPanel.defaultProps = { 69 | outputValueName: undefined, 70 | }; 71 | 72 | PropertiesPanel.propTypes = { 73 | element: PropTypes.objectOf(PropTypes.shape).isRequired, 74 | nameChanged: PropTypes.func.isRequired, 75 | applicationSelectionUpdated: PropTypes.func.isRequired, 76 | tasksForSelectedApplication: PropTypes.arrayOf(PropTypes.shape).isRequired, 77 | taskSelectionUpdated: PropTypes.func.isRequired, 78 | selectedActivity: PropTypes.string.isRequired, 79 | disableTaskSelection: PropTypes.bool.isRequired, 80 | parameterList: PropTypes.arrayOf(PropTypes.shape).isRequired, 81 | parameterSelectionUpdated: PropTypes.func.isRequired, 82 | outputValueName: PropTypes.string, 83 | outputNameUpdated: PropTypes.func.isRequired, 84 | robotId: PropTypes.string.isRequired, 85 | }; 86 | 87 | export default PropertiesPanel; 88 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/PropertiesPanel/PropertiesPanelSections/PPIdSection/PPIdSection.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Space, Typography } from 'antd'; 3 | 4 | const { Text } = Typography; 5 | 6 | /** 7 | * @description Renders the task-dropdown based on the passed list of tasks. 8 | * @category Frontend 9 | * @component 10 | */ 11 | const PPIdSection = (element) => { 12 | const { element: selectedElement } = element; 13 | const { id } = selectedElement; 14 | return ( 15 | 16 | ID: 17 | {id} 18 | 19 | ); 20 | }; 21 | 22 | export default PPIdSection; 23 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/PropertiesPanel/PropertiesPanelSections/PPNameSection/PPNameSection.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Input, Typography, Tooltip } from 'antd'; 4 | import { InfoCircleOutlined } from '@ant-design/icons'; 5 | import corporateDesign from '../../../../../../../layout/corporateDesign'; 6 | 7 | const { Text } = Typography; 8 | 9 | /** 10 | * @description Renders the name input field with the corresponding label. 11 | * @category Frontend 12 | * @component 13 | */ 14 | const PPNameSection = ({ element, nameChanged }) => ( 15 | <> 16 | Name: 17 | 21 | 24 | 25 | } 26 | value={element.businessObject.name || ''} 27 | onChange={nameChanged} 28 | /> 29 | 30 | ); 31 | 32 | PPNameSection.propTypes = { 33 | element: PropTypes.objectOf(PropTypes.shape).isRequired, 34 | nameChanged: PropTypes.func.isRequired, 35 | }; 36 | 37 | export default PPNameSection; 38 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/PropertiesPanel/PropertiesPanelSections/PPOutputValueSection/PPOutputValueSection.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Space, Typography, Tooltip, Input } from 'antd'; 3 | import { InfoCircleOutlined } from '@ant-design/icons'; 4 | import PropTypes from 'prop-types'; 5 | import corporateDesign from '../../../../../../../layout/corporateDesign'; 6 | 7 | const { Text } = Typography; 8 | 9 | /** 10 | * @description Renders the OutputValue-Section for activities. 11 | * @category Frontend 12 | * @component 13 | */ 14 | const PPOutputValueSection = ({ outputValueText, onNameChange }) => { 15 | const handleOutputValueChange = (event) => { 16 | const outputValueName = event.target.value.replace(/\$/g, ''); 17 | onNameChange(outputValueName); 18 | }; 19 | 20 | return ( 21 | <> 22 | 23 | OutputValue: 24 | 28 | 31 | 32 | 33 | 34 | 39 | 42 | 43 | } 44 | onPressEnter={handleOutputValueChange} 45 | /> 46 | 47 | ); 48 | }; 49 | 50 | PPOutputValueSection.propTypes = { 51 | outputValueText: PropTypes.string.isRequired, 52 | onNameChange: PropTypes.func.isRequired, 53 | }; 54 | 55 | export default PPOutputValueSection; 56 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/PropertiesPanel/PropertiesPanelSections/PPParameterSection/PPParameterSection.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Typography, Space } from 'antd'; 5 | import PPParameterInput from './subComponents/PPParameterInput'; 6 | 7 | const { Text } = Typography; 8 | 9 | /** 10 | * @description Renders the name input field with the corresponding label. 11 | * @category Frontend 12 | * @component 13 | */ 14 | const PPParameterSection = ({ 15 | selectedActivity, 16 | parameterList, 17 | onValueChange, 18 | robotId, 19 | }) => ( 20 | <> 21 | Parameter: 22 | 23 | 24 | {parameterList.map((singleInput, index) => ( 25 | 37 | ))} 38 | 39 | 40 | ); 41 | 42 | PPParameterSection.propTypes = { 43 | selectedActivity: PropTypes.string.isRequired, 44 | onValueChange: PropTypes.func.isRequired, 45 | parameterList: PropTypes.arrayOf(PropTypes.shape).isRequired, 46 | robotId: PropTypes.string.isRequired, 47 | }; 48 | 49 | export default PPParameterSection; 50 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/PropertiesPanel/PropertiesPanelSections/PPParameterSection/subComponents/PPParameterInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Input, Tooltip, Typography } from 'antd'; 3 | import { 4 | InfoCircleOutlined, 5 | LockOutlined, 6 | UnlockOutlined, 7 | } from '@ant-design/icons'; 8 | import PropTypes from 'prop-types'; 9 | import styles from '../../../../ModelerSidebar.module.css'; 10 | import { 11 | setPropertyForParameter, 12 | parameterPropertyStatus, 13 | } from '../../../../../../../../utils/sessionStorage/localSsotController/parameters'; 14 | 15 | const { Text } = Typography; 16 | 17 | /** 18 | * @description Renders a parameter input field for a given parameter 19 | * @category Frontend 20 | * @component 21 | */ 22 | const PPParameterInput = ({ 23 | parameterName, 24 | isRequired, 25 | // eslint-disable-next-line no-unused-vars 26 | dataType, 27 | infoText, 28 | value, 29 | robotId, 30 | selectedActivity, 31 | }) => { 32 | const [userInputRequired, setUserInputRequired] = useState( 33 | parameterPropertyStatus( 34 | robotId, 35 | selectedActivity, 36 | parameterName, 37 | 'requireUserInput' 38 | ) 39 | ); 40 | const [parameterValue, setParameterValue] = useState( 41 | parameterPropertyStatus(robotId, selectedActivity, parameterName, 'value') 42 | ); 43 | 44 | /** 45 | * @description changes the state for "userInputRequired" and also the parameter value 46 | * @param {String} currentParameterName Name of the currently handled parameter 47 | */ 48 | const changeUserInputRequirement = (currentParameterName) => { 49 | setParameterValue(''); 50 | setPropertyForParameter( 51 | selectedActivity, 52 | parameterName, 53 | 'requireUserInput', 54 | !userInputRequired 55 | ); 56 | setPropertyForParameter( 57 | selectedActivity, 58 | currentParameterName, 59 | 'value', 60 | '' 61 | ); 62 | setUserInputRequired(!userInputRequired); 63 | }; 64 | 65 | /** 66 | * @description changes the parameter value 67 | * @param {Object} event from the input field 68 | * @param {String} currentParameterName Name of the currently handled parameter 69 | */ 70 | const changeParameterValue = (event, currentParameterName) => { 71 | setPropertyForParameter( 72 | selectedActivity, 73 | currentParameterName, 74 | 'value', 75 | event.target.value 76 | ); 77 | setParameterValue(event.target.value); 78 | }; 79 | 80 | /** 81 | * @description returns the Unlock-Icon 82 | * @returns {React.ReactElement} the corresponding icon 83 | */ 84 | const returnLockIcon = (inputParameterName) => ( 85 | changeUserInputRequirement(inputParameterName)} 87 | /> 88 | ); 89 | 90 | return ( 91 | <> 92 | 93 | {parameterName} 94 | 95 | {isRequired && ( 96 | 97 |  * 98 | 99 | )} 100 | 101 | {!userInputRequired && ( 102 | changeParameterValue(event, parameterName)} 107 | suffix={ 108 | 109 | 110 | 111 | } 112 | addonAfter={returnLockIcon(parameterName)} 113 | disabled={userInputRequired} 114 | /> 115 | )} 116 | 117 | {userInputRequired && ( 118 | 126 | )} 127 | 128 | ); 129 | }; 130 | 131 | PPParameterInput.propTypes = { 132 | parameterName: PropTypes.string.isRequired, 133 | isRequired: PropTypes.bool.isRequired, 134 | dataType: PropTypes.string.isRequired, 135 | value: PropTypes.string.isRequired, 136 | robotId: PropTypes.string.isRequired, 137 | selectedActivity: PropTypes.string.isRequired, 138 | infoText: PropTypes.string, 139 | }; 140 | 141 | PPParameterInput.defaultProps = { 142 | infoText: undefined, 143 | }; 144 | 145 | export default PPParameterInput; 146 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/PropertiesPanel/PropertiesPanelSections/PPRpaSection/PPRpaSection.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Typography, Space } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | import PropertiesPanelApplicationDropdown from './subComponents/PPApplicationDropdown'; 5 | import PropertiesPanelTaskDropdown from './subComponents/PPTaskDropdown'; 6 | 7 | const { Text } = Typography; 8 | 9 | /** 10 | * @description Renders the RPA-Input fields if BPMN element is an activity 11 | * @category Frontend 12 | * @component 13 | */ 14 | const PPRpaSection = ({ 15 | applicationSelectionUpdated, 16 | tasksForSelectedApplication, 17 | taskSelectionUpdated, 18 | disableTaskSelection, 19 | selectedActivity, 20 | }) => ( 21 | <> 22 | Actions: 23 | 24 | 31 | 37 | 38 | 39 | ); 40 | 41 | PPRpaSection.propTypes = { 42 | applicationSelectionUpdated: PropTypes.func.isRequired, 43 | selectedActivity: PropTypes.string.isRequired, 44 | tasksForSelectedApplication: PropTypes.arrayOf(PropTypes.shape).isRequired, 45 | taskSelectionUpdated: PropTypes.func.isRequired, 46 | disableTaskSelection: PropTypes.bool.isRequired, 47 | }; 48 | 49 | export default PPRpaSection; 50 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/PropertiesPanel/PropertiesPanelSections/PPRpaSection/subComponents/PPApplicationDropdown.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Select } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | import styles from '../../../../ModelerSidebar.module.css'; 5 | import { getRpaApplication } from '../../../../../../../../utils/sessionStorage/localSsotController/attributes'; 6 | 7 | const { Option } = Select; 8 | 9 | /** 10 | * @description Renders the application-dropdown based on passed list of applications. 11 | * @category Frontend 12 | * @component 13 | */ 14 | const PPApplicationDropdown = ({ 15 | selectedActivity, 16 | onApplicationSelection, 17 | applications, 18 | }) => ( 19 | <> 20 | 33 | 34 | ); 35 | 36 | PPApplicationDropdown.propTypes = { 37 | applications: PropTypes.arrayOf(PropTypes.shape).isRequired, 38 | onApplicationSelection: PropTypes.func.isRequired, 39 | selectedActivity: PropTypes.string.isRequired, 40 | }; 41 | 42 | export default PPApplicationDropdown; 43 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/PropertiesPanel/PropertiesPanelSections/PPRpaSection/subComponents/PPTaskDropdown.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Select } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | import styles from '../../../../ModelerSidebar.module.css'; 5 | import { getRpaTask } from '../../../../../../../../utils/sessionStorage/localSsotController/attributes'; 6 | 7 | const { Option } = Select; 8 | 9 | /** 10 | * @description Renders the task-dropdown based on passed list of task. 11 | * @category Frontend 12 | * @component 13 | */ 14 | const PPTaskDropdown = ({ 15 | onTaskSelection, 16 | disabled, 17 | listOfTasks, 18 | selectedActivity, 19 | }) => ( 20 | <> 21 | 35 | 36 | ); 37 | 38 | PPTaskDropdown.propTypes = { 39 | listOfTasks: PropTypes.arrayOf(PropTypes.shape).isRequired, 40 | onTaskSelection: PropTypes.func.isRequired, 41 | disabled: PropTypes.bool.isRequired, 42 | selectedActivity: PropTypes.string.isRequired, 43 | }; 44 | 45 | export default PPTaskDropdown; 46 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/PropertiesPanel/PropertiesPanelSections/PPTitleSection/PPTitleSection.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Typography } from 'antd'; 3 | 4 | const { Text } = Typography; 5 | 6 | /** 7 | * @description Renders the corresponding title for selected BPMN element 8 | * @category Frontend 9 | * @component 10 | */ 11 | const PPTitleSection = (element) => { 12 | const { element: selectedElement } = element; 13 | let { type: title } = selectedElement; 14 | title = title.replace('bpmn:', ''); 15 | 16 | return ( 17 | 18 | {title} 19 | 20 | ); 21 | }; 22 | 23 | export default PPTitleSection; 24 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/modelerSidebarFunctionality/downloadStringAsFile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Frontend 3 | * @module 4 | */ 5 | 6 | /** 7 | * @description Will download a given string as a file 8 | * @param {string} text String that will be the content of the downloaded file 9 | * @param {string} fileType String that sets the file type of the downloaded file; Use form text/filetype 10 | * @param {string} fileName String that sets the name of the downloaded file; Use the same filetype as given in the fileType parameter e.g. name.filetype 11 | */ 12 | const downloadString = (text, fileType, fileName) => { 13 | const blob = new Blob([text], { type: fileType }); 14 | const element = document.createElement('a'); 15 | element.download = fileName; 16 | element.href = URL.createObjectURL(blob); 17 | element.dataset.downloadurl = [fileType, element.download, element.href].join( 18 | ':' 19 | ); 20 | element.style.display = 'none'; 21 | document.body.appendChild(element); 22 | element.click(); 23 | document.body.removeChild(element); 24 | setTimeout(() => { 25 | URL.revokeObjectURL(element.href); 26 | }, 1500); 27 | }; 28 | 29 | export default downloadString; 30 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/ModelerSidebar/modelerSidebarFunctionality/modelerSidebarFunctionalityTestingUtils.js: -------------------------------------------------------------------------------- 1 | const MOCK_ROBOT_ID = '0123456789abc0815'; 2 | const MOCK_CURRENT_ELEMENT_ID = '123450815'; 3 | const MOCK_ROBOT_NAME = 'testRobotFromTest'; 4 | const MOCK_ROBOT_CONTENT = 'This is a test robots content'; 5 | const MOCK_XML = 'testtesttesttest'; 6 | const MOCK_PARSER_RESULT = 'parserResult123'; 7 | const MOCK_ACTIVITY_ID = '0123456789abc4711'; 8 | const MOCK_NEW_VALUE = 'I am a new value'; 9 | const MOCK_VALUE = 'cookbookApplication'; 10 | const MOCK_APPLICATION = 'cookbookApplication'; 11 | const MOCK_SELECTED_APPLICATION = 'CookbookApplication'; 12 | const MOCK_PARAMETER_OBJECT = { 13 | rpaParameters: [ 14 | { 15 | index: 1, 16 | name: 'testParam1', 17 | type: 'Boolean', 18 | value: 'false', 19 | }, 20 | { 21 | index: 0, 22 | name: 'testParam1', 23 | type: 'Boolean', 24 | value: 'true', 25 | }, 26 | ], 27 | outputValue: 'OutputValueName', 28 | }; 29 | const MOCK_INPUTS_RIGHT_ORDER = [ 30 | { 31 | index: 0, 32 | name: 'testParam1', 33 | type: 'Boolean', 34 | value: 'true', 35 | }, 36 | { 37 | index: 1, 38 | name: 'testParam1', 39 | type: 'Boolean', 40 | value: 'false', 41 | }, 42 | ]; 43 | const MOCK_CURRENT_ELEMENT = { 44 | id: MOCK_CURRENT_ELEMENT_ID, 45 | businessObject: { name: 'oldTestName' }, 46 | type: 'bpmn:Task', 47 | }; 48 | const MOCK_SELECTED_ELEMENTS = [MOCK_CURRENT_ELEMENT]; 49 | const MOCK_ELEMENT_STATE = { 50 | selectedElements: MOCK_SELECTED_ELEMENTS, 51 | currentElement: MOCK_CURRENT_ELEMENT, 52 | }; 53 | const MOCK_EVENT = { 54 | target: { value: 'newTestName' }, 55 | newSelection: MOCK_SELECTED_ELEMENTS, 56 | element: MOCK_CURRENT_ELEMENT, 57 | }; 58 | const MOCK_MODELER = { 59 | get: (itemToGet) => { 60 | expect(itemToGet).toEqual('modeling'); 61 | return { 62 | updateLabel: (element, newName) => { 63 | expect(element).toEqual({ 64 | id: '123450815', 65 | businessObject: { name: 'newTestName' }, 66 | type: 'bpmn:Task', 67 | }); 68 | expect(newName).toEqual('newTestName'); 69 | }, 70 | }; 71 | }, 72 | saveXML: async (formatObject) => { 73 | expect(formatObject).toEqual({ format: true }); 74 | return MOCK_XML; 75 | }, 76 | }; 77 | 78 | const CONSTANTS = { 79 | MOCK_ROBOT_ID, 80 | MOCK_CURRENT_ELEMENT_ID, 81 | MOCK_ROBOT_NAME, 82 | MOCK_ROBOT_CONTENT, 83 | MOCK_XML, 84 | MOCK_PARSER_RESULT, 85 | MOCK_ACTIVITY_ID, 86 | MOCK_NEW_VALUE, 87 | MOCK_VALUE, 88 | MOCK_APPLICATION, 89 | MOCK_SELECTED_APPLICATION, 90 | MOCK_PARAMETER_OBJECT, 91 | MOCK_INPUTS_RIGHT_ORDER, 92 | MOCK_CURRENT_ELEMENT, 93 | MOCK_SELECTED_ELEMENTS, 94 | MOCK_ELEMENT_STATE, 95 | MOCK_EVENT, 96 | MOCK_MODELER, 97 | }; 98 | 99 | export default CONSTANTS; 100 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotModeler/RobotModeler.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Layout } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | import BpmnModeler from './BpmnModeler/BpmnModeler'; 5 | import HeaderNavbar from '../../multiPageComponents/HeaderNavbar/HeaderNavbar'; 6 | import ModelerSidebar from './ModelerSidebar/ModelerSidebar'; 7 | import { getSsot } from '../../../api/routes/robots/robots'; 8 | import { getAllParametersForRobot } from '../../../api/routes/robots/rpaParameter'; 9 | import { getAllAttributes } from '../../../api/routes/robots/rpaAttributes'; 10 | import { initSessionStorage } from '../../../utils/sessionStorage/sessionStorageUtils'; 11 | 12 | import 'bpmn-js/dist/assets/diagram-js.css'; 13 | import 'bpmn-font/dist/css/bpmn-embedded.css'; 14 | 15 | /** 16 | * @description Modeler page that enables the user to build a robot 17 | * @category Frontend 18 | * @component 19 | */ 20 | const RobotModeler = (props) => { 21 | const { match } = props; 22 | const { params } = match; 23 | const { robotId } = params; 24 | const [modeler, setModeler] = useState(null); 25 | const [robotName, setRobotName] = useState(); 26 | 27 | const updateModeler = (updatedModeler) => { 28 | setModeler(updatedModeler); 29 | }; 30 | 31 | /** 32 | * @description Equivalent to ComponentDidMount in class based components 33 | */ 34 | useEffect(() => { 35 | initSessionStorage('idCounter', JSON.stringify('541')); 36 | getSsot(robotId) 37 | .then((response) => response.json()) 38 | .then((data) => { 39 | sessionStorage.setItem('ssotLocal', JSON.stringify(data)); 40 | setRobotName(data.robotName); 41 | }) 42 | .catch((error) => { 43 | console.error(error); 44 | }); 45 | 46 | getAllAttributes(robotId) 47 | .then((response) => response.json()) 48 | .then((data) => { 49 | initSessionStorage('attributeLocalStorage', JSON.stringify([])); 50 | sessionStorage.setItem('attributeLocalStorage', JSON.stringify(data)); 51 | }); 52 | 53 | getAllParametersForRobot(robotId) 54 | .then((response) => response.json()) 55 | .then((data) => { 56 | initSessionStorage('parameterLocalStorage', JSON.stringify([])); 57 | sessionStorage.setItem('parameterLocalStorage', JSON.stringify(data)); 58 | }); 59 | }, []); 60 | 61 | return ( 62 | <> 63 | 64 | 65 | 70 | {modeler && robotName && ( 71 | 76 | )} 77 | 78 | 79 | ); 80 | }; 81 | 82 | RobotModeler.propTypes = { 83 | match: PropTypes.objectOf( 84 | PropTypes.oneOfType([ 85 | PropTypes.string, 86 | PropTypes.bool, 87 | PropTypes.objectOf(PropTypes.string), 88 | ]) 89 | ).isRequired, 90 | }; 91 | 92 | export default RobotModeler; 93 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotOverview/RobotContainer/CreateRobotContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Col, Row, Typography } from 'antd'; 3 | import { PlusCircleOutlined } from '@ant-design/icons'; 4 | import PropTypes from 'prop-types'; 5 | import styles from './RobotContainer.module.css'; 6 | import corporateDesign from '../../../../layout/corporateDesign'; 7 | 8 | const { Title } = Typography; 9 | 10 | /** 11 | * @component 12 | * @description Provides the first Box of Robot Overview with the "Create new Robot"-Box 13 | * @category Frontend 14 | */ 15 | const CreateRobotContainer = ({ createNewRobot }) => ( 16 |
17 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | Create new Robot 35 | 36 | 37 | 38 | 39 | ); 40 | 41 | CreateRobotContainer.propTypes = { 42 | createNewRobot: PropTypes.func.isRequired, 43 | }; 44 | 45 | export default CreateRobotContainer; 46 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotOverview/RobotContainer/RobotContainer.module.css: -------------------------------------------------------------------------------- 1 | .box { 2 | text-align: center; 3 | padding: 1rem; 4 | height: 13rem; 5 | border-radius: 5px; 6 | } 7 | 8 | .robotBox { 9 | background: var(--colorPrimaryInverted2); 10 | } 11 | 12 | .selectedRobotBox { 13 | border-style: solid; 14 | border-color: var(--colorBackgroundCta); 15 | border-width: 0.25rem; 16 | } 17 | 18 | .createRobotBox { 19 | background: var(--colorBackgroundCta); 20 | } 21 | 22 | .clickableIcon { 23 | color: var(--colorBackgroundCta); 24 | font-size: 4rem; 25 | } 26 | 27 | .title { 28 | color: var(--colorPrimaryInvertedText) !important; 29 | text-align: center; 30 | } 31 | 32 | .title:hover { 33 | overflow: visible; 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/components/pages/RobotOverview/RobotOverview.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-filename-extension */ 2 | /* eslint-disable func-names */ 3 | /* eslint-disable object-shorthand */ 4 | import React from 'react'; 5 | import { act, render, screen } from '@testing-library/react'; 6 | import userEvent from '@testing-library/user-event'; 7 | import { BrowserRouter } from 'react-router-dom'; 8 | import RobotOverview from './RobotOverview'; 9 | import '@testing-library/jest-dom'; 10 | 11 | const USER_ID = '80625d115100a2ee8d8e695b'; 12 | 13 | window.matchMedia = 14 | window.matchMedia || 15 | function () { 16 | return { 17 | matches: false, 18 | addListener: function () {}, 19 | removeListener: function () {}, 20 | }; 21 | }; 22 | 23 | const MOCK_ROBOT_LIST = [ 24 | { 25 | _id: '12345678901234567890123a', 26 | robotName: 'Awesome Robot', 27 | }, 28 | { 29 | _id: '12345678901234567890123b', 30 | robotName: 'Another Robot', 31 | }, 32 | ]; 33 | 34 | async function mockFetch(url) { 35 | switch (url) { 36 | case `/users/${USER_ID}/robots`: { 37 | return { 38 | ok: true, 39 | status: 200, 40 | json: async () => MOCK_ROBOT_LIST, 41 | }; 42 | } 43 | default: { 44 | throw new Error(`Unhandled request: ${url}`); 45 | } 46 | } 47 | } 48 | 49 | beforeAll(() => jest.spyOn(window, 'fetch')); 50 | beforeEach(() => window.fetch.mockImplementation(mockFetch)); 51 | 52 | describe('Testing functionality behind button to trigger function call for new robot creation', () => { 53 | it('checks if attempt to fetch occured twice', async () => { 54 | act(() => { 55 | render( 56 | 57 | 58 | 59 | ); 60 | }); 61 | 62 | act(() => { 63 | userEvent.click(screen.getByText('Create new Robot')); 64 | }); 65 | expect(window.fetch).toHaveBeenCalledTimes(3); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('./layout/corporateDesign.css'); 2 | 3 | body { 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 6 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 7 | sans-serif; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | code { 13 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 14 | monospace; 15 | } 16 | 17 | .label-on-dark-background { 18 | color: var(--colorPrimaryInvertedText) !important; 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import 'antd/dist/antd.css'; 4 | import './index.css'; 5 | import App from './components/App'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | -------------------------------------------------------------------------------- /frontend/src/layout/corporateDesign.css: -------------------------------------------------------------------------------- 1 | /* changes to the variables here also have to be made in corporateDesign.js! */ 2 | 3 | :root { 4 | --colorPrimary: #00c2ff; 5 | --colorPrimaryInverted: #1c272b; 6 | --colorPrimaryInverted2: #2f3c41; 7 | --colorPrimaryInvertedText: #ffffff; 8 | --colorBackground: #efefef; 9 | --colorBackground2: #ffffff; 10 | --colorBackgroundText: #1d1d1f; 11 | --colorBackgroundCta: #ff6b00; 12 | --colorSuccessNotificationBackground: #ebffeb; 13 | --colorSuccessNotificationIcon: #08c908; 14 | --colorWarningNotificationBackground: #ffffd0; 15 | --colorWarningNotificationIcon: #ff6b00; 16 | --colorErrorNotificationBackground: #ffbbbb; 17 | --colorErrorNotificationIcon: #cc0000; 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/layout/corporateDesign.js: -------------------------------------------------------------------------------- 1 | // Changes to the variables here also have to be made in corporateDesign.css! 2 | 3 | const colorPrimary = '#00C2FF'; 4 | const colorPrimaryInverted = '#1C272B'; 5 | const colorPrimaryInverted2 = '#2F3C41'; 6 | const colorPrimaryInvertedText = '#FFFFFF'; 7 | const colorBackground = '#EFEFEF'; 8 | const colorBackground2 = '#FFFFFF'; 9 | const colorBackgroundText = '#1D1D1F'; 10 | const colorBackgroundCta = '#FF6B00'; 11 | const colorSuccessNotificationBackground = '#ebffeb'; 12 | const colorSuccessNotificationIcon = '#08c908'; 13 | const colorWarningNotificationBackground = '#ffffD0'; 14 | const colorWarningNotificationIcon = '#ff6b00'; 15 | const colorErrorNotificationBackground = '#ffbbbb'; 16 | const colorErrorNotificationIcon = '#cc0000'; 17 | 18 | module.exports = { 19 | colorPrimary, 20 | colorPrimaryInverted, 21 | colorPrimaryInverted2, 22 | colorPrimaryInvertedText, 23 | colorBackground, 24 | colorBackground2, 25 | colorBackgroundText, 26 | colorBackgroundCta, 27 | colorSuccessNotificationBackground, 28 | colorSuccessNotificationIcon, 29 | colorWarningNotificationBackground, 30 | colorWarningNotificationIcon, 31 | colorErrorNotificationBackground, 32 | colorErrorNotificationIcon, 33 | }; 34 | -------------------------------------------------------------------------------- /frontend/src/layout/customizedTheme.js: -------------------------------------------------------------------------------- 1 | const corporateDesign = require('./corporateDesign'); 2 | 3 | const customizedTheme = { 4 | '@primary-color': corporateDesign.colorPrimary, 5 | '@link-color': corporateDesign.colorBackgroundCta, 6 | '@success-color': corporateDesign.colorBackgroundCta, 7 | '@warning-color': corporateDesign.colorBackgroundCta, 8 | '@error-color': corporateDesign.colorBackgroundCta, 9 | '@body-background': corporateDesign.colorBackground, 10 | '@layout-body-background': corporateDesign.colorBackground, 11 | '@border-radius-base': '5px', 12 | '@label-color': corporateDesign.colorPrimaryInvertedText, 13 | '@heading-color': corporateDesign.colorBackgroundText, 14 | '@text-color': corporateDesign.colorBackgroundText, 15 | '@text-color-dark': corporateDesign.colorPrimaryInvertedText, 16 | '@btn-primary-bg': corporateDesign.colorBackgroundCta, 17 | '@input-color': corporateDesign.colorBackgroundText, 18 | '@layout-sider-background': corporateDesign.colorPrimaryInverted2, 19 | '@component-background': corporateDesign.colorBackground2, 20 | '@layout-footer-background': corporateDesign.colorPrimaryInverted, 21 | '@dropdown-menu-bg': corporateDesign.colorPrimaryInverted2, 22 | '@select-item-selected-color': corporateDesign.colorBackgroundText, 23 | '@layout-header-background': corporateDesign.colorPrimaryInverted, 24 | '@font-family': 'Lato, sans-serif', 25 | }; 26 | 27 | module.exports = customizedTheme; 28 | -------------------------------------------------------------------------------- /frontend/src/resources/modeler/emptyBpmn.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/prefer-default-export 2 | export const emptyBpmn = ` 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | `; 21 | -------------------------------------------------------------------------------- /frontend/src/utils/componentsFunctionality/notificationUtils.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { notification } from 'antd'; 3 | import { 4 | CheckCircleOutlined, 5 | CloseCircleOutlined, 6 | CloudUploadOutlined, 7 | WarningOutlined, 8 | } from '@ant-design/icons'; 9 | import corporateDesign from '../../layout/corporateDesign'; 10 | 11 | /** 12 | * @category Frontend 13 | * @module 14 | */ 15 | 16 | /** 17 | * @description Will first consider if a special icon is requested and otherwise return the corresponding icon for the notification type. 18 | * @param {String} type One from 'Success', 'Warning' or 'Alert' - defines type (and with it the color scheme) for the notification Box 19 | * @param {String} icon Icon that will be displayed in the notification. (Must be imported and handled in notificationUtils accordingly!) 20 | * @param {String} colorName Returns the css selector for the matching notification type 21 | * @returns Icon component of the notification 22 | */ 23 | // eslint-disable-next-line consistent-return 24 | const getIconForType = (type, icon, colorName) => { 25 | switch (icon) { 26 | case 'CloudUploadOutlined': 27 | return ( 28 | 31 | ); 32 | default: 33 | switch (type) { 34 | case 'Success': 35 | return ( 36 | 39 | ); 40 | case 'Warning': 41 | return ( 42 | 45 | ); 46 | case 'Error': 47 | return ( 48 | 51 | ); 52 | default: 53 | } 54 | } 55 | }; 56 | 57 | /** 58 | * @description Throws a notification at the upper right edge of the screen, which disappears automatically 59 | * @param {String} type One from 'Success', 'Warning' or 'Alert' - defines type (and with it the color scheme) for the notification Box 60 | * @param {String} message Message that is displayed in the notification 61 | * @param {String} icon Icon that will be displayed in the notification. (Must be imported and handled in notificationUtils accordingly!) 62 | */ 63 | const customNotification = (type, message, icon) => { 64 | const colorName = `color${type}Notification`; 65 | 66 | notification.open({ 67 | message, 68 | icon: getIconForType(type, icon, colorName), 69 | style: { 70 | backgroundColor: corporateDesign[`${colorName}Background`], 71 | }, 72 | }); 73 | }; 74 | 75 | export default customNotification; 76 | -------------------------------------------------------------------------------- /frontend/src/utils/parser/bpmnToSsotParsing/bpmnToSsotParsing.test.js: -------------------------------------------------------------------------------- 1 | import { setRobotMetadata } from '../../sessionStorage/localSsotController/ssot'; 2 | import { initSessionStorage } from '../../sessionStorage/sessionStorageUtils'; 3 | import { parseBpmnToSsot } from './bpmnToSsotParsing'; 4 | 5 | const BPMN_XML = { 6 | xml: 'Flow_0m7u6buFlow_0m7u6buFlow_08r9hfxFlow_08r9hfxFlow_1lycczuFlow_1lycczuFlow_19rmn01Flow_19rmn01', 7 | }; 8 | const ROBOT_ID = '54ab2d30eb3cc402041ac60f'; 9 | 10 | describe('Parsing Tests', () => { 11 | it('successfully parses the bpmn to ssot', async () => { 12 | initSessionStorage('robotMetadata', JSON.stringify({})); 13 | setRobotMetadata('AwesomeTestRobot', ROBOT_ID); 14 | const Ssot = await parseBpmnToSsot(BPMN_XML, ROBOT_ID); 15 | expect(Ssot).toHaveProperty('robotName', 'AwesomeTestRobot'); 16 | expect(Ssot).toHaveProperty('starterId', 'Event_1wm4a0f'); 17 | 18 | expect(Ssot.elements[0]).toHaveProperty('name', 'Start Event'); 19 | expect(Ssot.elements[0]).toHaveProperty('predecessorIds', []); 20 | 21 | expect(Ssot.elements[1]).toHaveProperty('name', 'activity One'); 22 | expect(Ssot.elements[1]).toHaveProperty('type', 'INSTRUCTION'); 23 | 24 | expect(Ssot.elements[2]).toHaveProperty('name', 'activity Two'); 25 | expect(Ssot.elements[2]).toHaveProperty('type', 'INSTRUCTION'); 26 | 27 | expect(Ssot.elements[4]).toHaveProperty('name', 'finished'); 28 | expect(Ssot.elements[4]).toHaveProperty('type', 'MARKER'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /frontend/src/utils/parser/ssotBaseObjects.js: -------------------------------------------------------------------------------- 1 | const baseElement = { 2 | type: '', 3 | name: '', 4 | id: '', 5 | predecessorIds: [], 6 | successorIds: [], 7 | }; 8 | 9 | module.exports = { baseElement }; 10 | -------------------------------------------------------------------------------- /frontend/src/utils/sessionStorage/localFunctionalitiesController/functionalities.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Frontend 3 | * @module 4 | */ 5 | 6 | const FUNCTIONALITIES_STORAGE_PATH = 'taskApplicationCombinations'; 7 | 8 | /** 9 | * @description Retrieves the rpa functionalities object for a specific rpa application and rpa task combination 10 | * @param {String} application Name of the rpa application 11 | * @param {String} task Name of the rpa task 12 | * @returns {Object} Rpa functionalities object 13 | */ 14 | const getRpaFunctionalitiesObject = (application, task) => { 15 | const rpaFunctionalities = JSON.parse( 16 | sessionStorage.getItem(FUNCTIONALITIES_STORAGE_PATH) 17 | ); 18 | return rpaFunctionalities.find( 19 | (element) => element.application === application && element.task === task 20 | ); 21 | }; 22 | 23 | export default getRpaFunctionalitiesObject; 24 | -------------------------------------------------------------------------------- /frontend/src/utils/sessionStorage/sessionStorageUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Frontend 3 | * @module 4 | */ 5 | 6 | /** 7 | * @description Checks if the passed item already exists in the session storage and initializes it with given value if not existing. 8 | * @param {String} itemToCheckFor Selected item in the session storage that will be checked 9 | * @param {String} valueToInitTo Value to initialize to if the item is not existing in session storage yet. 10 | */ 11 | const initSessionStorage = (itemToCheckFor, valueToInitTo) => { 12 | if (sessionStorage.getItem(itemToCheckFor) === null) 13 | sessionStorage.setItem(itemToCheckFor, valueToInitTo); 14 | }; 15 | 16 | const initAvailableApplicationsSessionStorage = () => { 17 | initSessionStorage('availableApplications', JSON.stringify([])); 18 | const taskAndApplicationCombinations = JSON.parse( 19 | sessionStorage.getItem('taskApplicationCombinations') 20 | ); 21 | const allApplications = taskAndApplicationCombinations.map( 22 | (singleCombination) => singleCombination.application 23 | ); 24 | const applicationsWithoutDuplicates = allApplications.filter( 25 | (singleApplication, index, self) => 26 | self.indexOf(singleApplication) === index 27 | ); 28 | 29 | sessionStorage.setItem( 30 | 'availableApplications', 31 | JSON.stringify(applicationsWithoutDuplicates) 32 | ); 33 | }; 34 | 35 | export { initAvailableApplicationsSessionStorage, initSessionStorage }; 36 | -------------------------------------------------------------------------------- /frontend/src/utils/socket/socketConnections.js: -------------------------------------------------------------------------------- 1 | import socketIoClient from 'socket.io-client'; 2 | 3 | const ENDPOINT = 'http://localhost:5001'; 4 | 5 | const socket = socketIoClient(ENDPOINT); 6 | 7 | export default socket; 8 | -------------------------------------------------------------------------------- /server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "plugin:react/recommended", 9 | "airbnb", 10 | "prettier", 11 | "prettier/react" 12 | ], 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "jsx": true 16 | }, 17 | "ecmaVersion": 12, 18 | "sourceType": "module" 19 | }, 20 | "plugins": ["react", "only-warn"], 21 | "rules": { 22 | "no-console": ["error", { "allow": ["warn", "error"] }] 23 | }, 24 | "root": true 25 | } 26 | -------------------------------------------------------------------------------- /server/api/controllers/rpaFrameworkCommandsController.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | // eslint-disable-next-line no-unused-vars 3 | const rpaModels = require('../models/rpaTaskModel'); 4 | 5 | /** 6 | * @swagger 7 | * /functionalities/applications: 8 | * get: 9 | * tags: 10 | * - RPA-Functionalities 11 | * summary: Get all applications that ark automate supports 12 | * operationId: getApplications 13 | * responses: 14 | * 200: 15 | * description: OK 16 | * content: 17 | * application/json: 18 | * schema: 19 | * type: array 20 | * items: 21 | * $ref: '#/components/schemas/Applications' 22 | */ 23 | exports.getAvailableApplications = async (req, res) => { 24 | try { 25 | res.set('Content-Type', 'application/json'); 26 | const tasks = await mongoose.model('rpa-task').distinct('application'); 27 | res.send(tasks); 28 | } catch (err) { 29 | console.error(err); 30 | } 31 | }; 32 | 33 | /** 34 | * @swagger 35 | * /functionalities/{application}/tasks: 36 | * parameters: 37 | * - name: application 38 | * in: path 39 | * description: Name of an application 40 | * required: true 41 | * schema: 42 | * $ref: '#/components/schemas/Applications' 43 | * get: 44 | * tags: 45 | * - RPA-Functionalities 46 | * summary: Gets all the tasks to execute for an application 47 | * operationId: getTasks 48 | * responses: 49 | * 200: 50 | * description: OK 51 | * content: 52 | * application/json: 53 | * schema: 54 | * type: array 55 | * items: 56 | * $ref: '#/components/schemas/Tasks' 57 | * 404: 58 | * description: Not Found 59 | */ 60 | exports.getAvailableTasksForApplications = async (req, res) => { 61 | try { 62 | const { application } = req.params; 63 | res.set('Content-Type', 'application/json'); 64 | if (application != null) { 65 | await mongoose 66 | .model('rpa-task') 67 | .distinct('task', { application }, (err, tasks) => { 68 | res.send(tasks); 69 | }); 70 | } else { 71 | res.send('Please set a valid application parameter.'); 72 | } 73 | } catch (err) { 74 | console.error(err); 75 | } 76 | }; 77 | 78 | /** 79 | * @swagger 80 | * /functionalities: 81 | * get: 82 | * tags: 83 | * - RPA-Functionalities 84 | * summary: Retrieve all available Task and Application combinations with the input parameters and possible output values 85 | * operationId: getFunctionalities 86 | * responses: 87 | * 200: 88 | * description: OK 89 | * content: 90 | * application/json: 91 | * schema: 92 | * type: array 93 | * items: 94 | * $ref: '#/components/schemas/Functionalities' 95 | */ 96 | exports.getAllRpaFunctionalities = async (req, res) => { 97 | const parameterObjects = await mongoose.model('rpa-task').find().exec(); 98 | 99 | res.send(parameterObjects); 100 | }; 101 | -------------------------------------------------------------------------------- /server/api/controllers/ssotParsingController.js: -------------------------------------------------------------------------------- 1 | const ssotToRobotparser = require('../../utils/ssotToRobotParsing/ssotToRobotParser.js'); 2 | 3 | /** 4 | * @swagger 5 | * /robots/{robotId}/robotCode: 6 | * parameters: 7 | * - name: robotId 8 | * in: path 9 | * description: Id of a robot 10 | * required: true 11 | * schema: 12 | * $ref: '#/components/schemas/RobotIds' 13 | * get: 14 | * tags: 15 | * - Robots 16 | * summary: Retrieve the robot framework code of a specific robot 17 | * operationId: getRobotCode 18 | * responses: 19 | * 200: 20 | * description: OK 21 | * content: 22 | * application/json: 23 | * schema: 24 | * $ref: '#/components/schemas/RobotCode' 25 | */ 26 | exports.getRobotCodeForId = async (req, res) => { 27 | try { 28 | const { robotId } = req.params; 29 | const robotCode = await ssotToRobotparser.parseSsotById(robotId); 30 | res.send(robotCode); 31 | } catch (err) { 32 | console.error(err); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /server/api/controllers/ssotRpaAttributes.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | // eslint-disable-next-line no-unused-vars 3 | const ssotModels = require('../models/singleSourceOfTruthModel.js'); 4 | 5 | /** 6 | * @swagger 7 | * /robots/rpaattributes: 8 | * put: 9 | * tags: 10 | * - Robots 11 | * summary: Overwrite existing rpa attribute objects with updated ones 12 | * operationId: overwriteAttributes 13 | * requestBody: 14 | * content: 15 | * application/json: 16 | * schema: 17 | * type: object 18 | * required: 19 | * - attributeObjectList 20 | * properties: 21 | * attributeObjectList: 22 | * type: array 23 | * items: 24 | * $ref: '#/components/schemas/RPAAttributes' 25 | * description: updated attributes object 26 | * required: true 27 | * responses: 28 | * 204: 29 | * description: No Content 30 | * 400: 31 | * description: Bad Request 32 | */ 33 | exports.updateMany = async (req, res) => { 34 | try { 35 | res.set('Content-Type', 'application/json'); 36 | const { attributeObjectList } = req.body; 37 | 38 | const updateList = []; 39 | attributeObjectList.forEach((element) => { 40 | const updateElement = { 41 | updateOne: { 42 | filter: { 43 | robotId: element.robotId, 44 | activityId: element.activityId, 45 | }, 46 | update: element, 47 | upsert: true, 48 | }, 49 | }; 50 | updateList.push(updateElement); 51 | }); 52 | 53 | const updatedObjects = await mongoose 54 | .model('rpaAttributes') 55 | .bulkWrite(updateList); 56 | 57 | res.send(updatedObjects); 58 | } catch (err) { 59 | console.error(err); 60 | } 61 | }; 62 | 63 | /** 64 | * @swagger 65 | * /robots/rpaattributes/{robotId}: 66 | * parameters: 67 | * - name: robotId 68 | * in: path 69 | * description: Id of a robot 70 | * required: true 71 | * schema: 72 | * $ref: '#/components/schemas/RobotIds' 73 | * get: 74 | * tags: 75 | * - Robots 76 | * summary: Retrieve all rpa attribute objects for a specific robot 77 | * operationId: getAttributesForRobot 78 | * responses: 79 | * 200: 80 | * description: OK 81 | * content: 82 | * application/json: 83 | * schema: 84 | * type: array 85 | * items: 86 | * $ref: '#/components/schemas/RPAAttributes' 87 | */ 88 | exports.retrieveAttributesForRobot = async (req, res) => { 89 | const { robotId } = req.params; 90 | 91 | const attributeObjects = await mongoose 92 | .model('rpaAttributes') 93 | .find({ robotId }) 94 | .exec(); 95 | 96 | res.send(attributeObjects); 97 | }; 98 | 99 | /** 100 | * @swagger 101 | * /robots/rpaattributes/{robotId}: 102 | * parameters: 103 | * - name: robotId 104 | * in: path 105 | * description: Id of a robot 106 | * required: true 107 | * schema: 108 | * $ref: '#/components/schemas/RobotIds' 109 | * delete: 110 | * tags: 111 | * - Robots 112 | * summary: Delete attributes related to the specified activities 113 | * operationId: deleteAttributes 114 | * requestBody: 115 | * content: 116 | * application/json: 117 | * schema: 118 | * type: object 119 | * required: 120 | * - activityIdListObject 121 | * properties: 122 | * activityIdList: 123 | * type: array 124 | * items: 125 | * $ref: '#/components/schemas/ActivityIds' 126 | * description: list of activities for which the attributes should be deleted 127 | * required: true 128 | * responses: 129 | * 204: 130 | * description: No Content 131 | * 400: 132 | * description: Bad Request 133 | */ 134 | exports.deleteForActivities = async (req, res) => { 135 | const { activityIdList } = req.body; 136 | const { robotId } = req.params; 137 | const usablerobotId = mongoose.Types.ObjectId(robotId); 138 | 139 | try { 140 | const deletionResult = await mongoose 141 | .model('rpaAttributes') 142 | .deleteMany({ 143 | activityId: { $in: activityIdList }, 144 | robotId: usablerobotId, 145 | }) 146 | .exec(); 147 | 148 | res.send(deletionResult); 149 | } catch (error) { 150 | res.status(400).send(error); 151 | } 152 | }; 153 | -------------------------------------------------------------------------------- /server/api/models/robotJobModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const { Schema } = mongoose; 4 | 5 | const inputParameterSchema = new Schema({ 6 | parameterId: mongoose.Types.ObjectId, 7 | value: Schema.Types.Mixed, 8 | }); 9 | 10 | const tasksStatusSchema = new Schema({ 11 | task_name: String, 12 | status: String, 13 | }); 14 | 15 | const activityErrorSchema = new Schema({ 16 | activityName: { type: String, required: [true, 'Activity name required'] }, 17 | tasks: { 18 | type: [tasksStatusSchema], 19 | required: [true, 'At least on task required'], 20 | }, 21 | message: { type: String, required: [true, 'Error messsage required'] }, 22 | }); 23 | 24 | const jobSchema = new Schema({ 25 | userId: { 26 | type: mongoose.Types.ObjectId, 27 | required: [true, 'UserId required'], 28 | }, 29 | robotId: { 30 | type: mongoose.Types.ObjectId, 31 | required: [true, 'RobotId required'], 32 | }, 33 | status: { type: String, required: [true, 'Status required'] }, 34 | parameters: [inputParameterSchema], 35 | loggedErrors: [activityErrorSchema], 36 | }); 37 | 38 | const Job = mongoose.model('job', jobSchema); 39 | module.exports = { Job }; 40 | -------------------------------------------------------------------------------- /server/api/models/robotJobModel.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | 3 | const mongoose = require('mongoose'); 4 | const { expect } = require('chai'); 5 | const dbHandler = require('../../utils/testing/testDatabaseHandler.js'); 6 | const { testJob } = require('../../utils/testing/testData.js'); 7 | // eslint-disable-next-line no-unused-vars 8 | const jobsModel = require('./robotJobModel.js'); 9 | 10 | const Job = mongoose.model('job'); 11 | 12 | /** 13 | * Connect to a new in-memory database before running any tests. 14 | */ 15 | beforeAll(async () => dbHandler.connect()); 16 | 17 | /** 18 | * Clear all test data after every test. 19 | */ 20 | afterEach(async () => dbHandler.clearDatabase()); 21 | 22 | /** 23 | * Remove and close the db and server. 24 | */ 25 | afterAll(async () => dbHandler.closeDatabase()); 26 | 27 | describe('jobs can be created', () => { 28 | const job = new Job(testJob); 29 | it('should throw no errors for correct job', async () => { 30 | job.save((err) => { 31 | expect(err).to.not.exist; 32 | }); 33 | }); 34 | }); 35 | 36 | describe('jobs have validation for missing parameters', () => { 37 | const job = new Job({ 38 | parameters: [], 39 | }); 40 | it('should be invalid if userId is empty', async () => { 41 | job.save((err) => { 42 | expect(err.errors.userId).to.exist; 43 | expect(err.errors.userId.message).equal('UserId required'); 44 | }); 45 | }); 46 | 47 | it('should be invalid if robotId is empty', async () => { 48 | job.save((err) => { 49 | expect(err.errors.robotId).to.exist; 50 | expect(err.errors.robotId.message).equal('RobotId required'); 51 | }); 52 | }); 53 | 54 | it('should be invalid if status is empty', async () => { 55 | job.save((err) => { 56 | expect(err.errors.status).to.exist; 57 | expect(err.errors.status.message).equal('Status required'); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /server/api/models/rpaTaskModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const { Schema } = mongoose; 4 | 5 | const rpaParameterSchema = new Schema({ 6 | name: String, 7 | type: String, 8 | required: Boolean, 9 | infoText: String, 10 | index: Number, 11 | }); 12 | 13 | const rpaTaskSchema = new Schema({ 14 | application: { type: String, required: [true, 'Application required'] }, 15 | task: { type: String, required: [true, 'Task required'] }, 16 | code: { type: String, required: [true, 'Code required'] }, 17 | outputValue: Boolean, 18 | inputVars: [rpaParameterSchema], 19 | output: rpaParameterSchema, 20 | }); 21 | 22 | mongoose.model('rpa-task', rpaTaskSchema); 23 | -------------------------------------------------------------------------------- /server/api/models/rpaTaskModel.test.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { expect } = require('chai'); 3 | const dbHandler = require('../../utils/testing/testDatabaseHandler.js'); 4 | const { testRpaTask1 } = require('../../utils/testing/testData.js'); 5 | // eslint-disable-next-line no-unused-vars 6 | const taskModel = require('./rpaTaskModel.js'); 7 | 8 | const RpaTask = mongoose.model('rpa-task'); 9 | 10 | /** 11 | * Connect to a new in-memory database before running any tests. 12 | */ 13 | beforeAll(async () => dbHandler.connect()); 14 | 15 | /** 16 | * Clear all test data after every test. 17 | */ 18 | afterEach(async () => dbHandler.clearDatabase()); 19 | 20 | /** 21 | * Remove and close the db and server. 22 | */ 23 | afterAll(async () => dbHandler.closeDatabase()); 24 | 25 | describe('tasks can be created', () => { 26 | const task = new RpaTask(testRpaTask1); 27 | it('should throw no errors for correct job', async () => { 28 | task.save((err) => expect(err).to.not.exist); 29 | }); 30 | }); 31 | 32 | describe('tasks have validation for missing parameters', () => { 33 | const task = new RpaTask({}); 34 | it('should be invalid if application is empty', async () => { 35 | task.save( 36 | (err) => 37 | expect(err.errors.application).to.exist && 38 | expect(err.errors.application.message).equal('Application required') 39 | ); 40 | }); 41 | 42 | it('should be invalid if task is empty', async () => { 43 | task.save( 44 | (err) => 45 | expect(err.errors.task).to.exist && 46 | expect(err.errors.task.message).equal('Task required') 47 | ); 48 | }); 49 | 50 | it('should be invalid if code is empty', async () => { 51 | task.save( 52 | (err) => 53 | expect(err.errors.code).to.exist && 54 | expect(err.errors.code.message).equal('Code required') 55 | ); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /server/api/models/singleSourceOfTruthModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const { Schema } = mongoose; 4 | 5 | const singleParameterSchema = new Schema({ 6 | name: String, 7 | value: String, 8 | requireUserInput: Boolean, 9 | type: String, 10 | isRequired: Boolean, 11 | infoText: String, 12 | index: Number, 13 | }); 14 | 15 | const parameterObjectSchema = new Schema({ 16 | robotId: mongoose.Types.ObjectId, 17 | activityId: String, 18 | outputValue: String, 19 | rpaParameters: [singleParameterSchema], 20 | }); 21 | 22 | const instructionSchema = new Schema({ 23 | type: String, 24 | name: String, 25 | predecessorIds: [String], 26 | successorIds: [String], 27 | id: String, 28 | }); 29 | 30 | const rpaAttributesObjectSchema = new Schema({ 31 | robotId: mongoose.Types.ObjectId, 32 | activityId: String, 33 | rpaApplication: String, 34 | rpaTask: String, 35 | }); 36 | 37 | const markerSchema = new Schema({ 38 | type: String, 39 | name: String, 40 | predecessorIds: [mongoose.Types.ObjectId], 41 | successorIds: [mongoose.Types.ObjectId], 42 | }); 43 | 44 | const ssotSchema = new Schema({ 45 | starterId: String, 46 | robotName: { type: String, required: [true, 'robotName required'] }, 47 | elements: { type: [instructionSchema, markerSchema] }, 48 | }); 49 | 50 | mongoose.model('parameter', parameterObjectSchema); 51 | mongoose.model('rpaAttributes', rpaAttributesObjectSchema); 52 | mongoose.model('SSoT', ssotSchema); 53 | -------------------------------------------------------------------------------- /server/api/models/singleSourceOfTruthModel.test.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { expect } = require('chai'); 3 | const dbHandler = require('../../utils/testing/testDatabaseHandler.js'); 4 | const { testSsot } = require('../../utils/testing/testData.js'); 5 | // eslint-disable-next-line no-unused-vars 6 | const ssotModel = require('./singleSourceOfTruthModel.js'); 7 | 8 | const Ssot = mongoose.model('SSoT'); 9 | 10 | /** 11 | * Connect to a new in-memory database before running any tests. 12 | */ 13 | beforeAll(async () => dbHandler.connect()); 14 | 15 | /** 16 | * Clear all test data after every test. 17 | */ 18 | afterEach(async () => dbHandler.clearDatabase()); 19 | 20 | /** 21 | * Remove and close the db and server. 22 | */ 23 | afterAll(async () => dbHandler.closeDatabase()); 24 | 25 | describe('Robots can be created', () => { 26 | const ssot = new Ssot(testSsot); 27 | it('should throw no errors for correct job', async () => { 28 | ssot.save((err) => expect(err).to.not.exist); 29 | }); 30 | }); 31 | 32 | describe('Robots have validation for missing parameters', () => { 33 | const job = new Ssot({}); 34 | it('should be invalid if robotName is empty', async () => { 35 | job.save( 36 | (err) => 37 | expect(err.errors.robotName).to.exist && 38 | expect(err.errors.robotName.message).equal('robotName required') 39 | ); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /server/api/models/userAccessObjectModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const { Schema } = mongoose; 4 | 5 | const userAccessSchema = new Schema({ 6 | accessLevel: String, 7 | robotId: { 8 | type: mongoose.Types.ObjectId, 9 | required: [true, 'RobotId required'], 10 | }, 11 | userId: { 12 | type: mongoose.Types.ObjectId, 13 | required: [true, 'UserId required'], 14 | }, 15 | }); 16 | 17 | mongoose.model('userAccessObject', userAccessSchema); 18 | -------------------------------------------------------------------------------- /server/api/models/userAccessObjectModel.test.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { expect } = require('chai'); 3 | const dbHandler = require('../../utils/testing/testDatabaseHandler.js'); 4 | const { testUserAccessObject } = require('../../utils/testing/testData.js'); 5 | // eslint-disable-next-line no-unused-vars 6 | const userAccessObjectModel = require('./userAccessObjectModel.js'); 7 | 8 | const UserAccesObject = mongoose.model('userAccessObject'); 9 | /** 10 | * Connect to a new in-memory database before running any tests. 11 | */ 12 | beforeAll(async () => dbHandler.connect()); 13 | 14 | /** 15 | * Clear all test data after every test. 16 | */ 17 | afterEach(async () => dbHandler.clearDatabase()); 18 | 19 | /** 20 | * Remove and close the db and server. 21 | */ 22 | afterAll(async () => dbHandler.closeDatabase()); 23 | 24 | describe('user access objects can be created', () => { 25 | const userAccessObject = new UserAccesObject(testUserAccessObject); 26 | it('should throw no errors for correct job', async () => { 27 | userAccessObject.save((err) => expect(err).to.not.exist); 28 | }); 29 | }); 30 | 31 | describe('user access objects have validation for missing parameters', () => { 32 | const job = new UserAccesObject({}); 33 | it('should be invalid if robotId is empty', async () => { 34 | job.save( 35 | (err) => 36 | expect(err.errors.robotId).to.exist && 37 | expect(err.errors.robotId.message).equal('RobotId required') 38 | ); 39 | }); 40 | 41 | it('should be invalid if userId is empty', async () => { 42 | job.save( 43 | (err) => 44 | expect(err.errors.userId).to.exist && 45 | expect(err.errors.userId.message).equal('UserId required') 46 | ); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /server/api/routes/functionalities/functionalities.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const commandsController = require('../../controllers/rpaFrameworkCommandsController'); 3 | 4 | const router = express.Router(); 5 | 6 | router.get('/applications', commandsController.getAvailableApplications); 7 | router.get( 8 | '/:application/tasks', 9 | commandsController.getAvailableTasksForApplications 10 | ); 11 | router.get('/', commandsController.getAllRpaFunctionalities); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /server/api/routes/functionalities/functionalities.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const httpMocks = require('node-mocks-http'); 3 | const dbHandler = require('../../../utils/testing/testDatabaseHandler'); 4 | const dbLoader = require('../../../utils/testing/databaseLoader'); 5 | const rpaController = require('../../controllers/rpaFrameworkCommandsController'); 6 | const testData = require('../../../utils/testing/testData'); 7 | 8 | /** 9 | * Connect to a new in-memory database before running any tests. 10 | */ 11 | beforeAll(async () => dbHandler.connect()); 12 | 13 | /** 14 | * Remove and close the db and server. 15 | */ 16 | afterAll(async () => dbHandler.closeDatabase()); 17 | 18 | describe('GET /functionalities/applications', () => { 19 | it('retrieves the list of all available apps correctly', async () => { 20 | await dbLoader.loadTasksInDb(); 21 | const response = httpMocks.createResponse(); 22 | await rpaController.getAvailableApplications({}, response); 23 | const data = await response._getData(); 24 | 25 | expect(response.statusCode).toBe(200); 26 | expect(data).toEqual([ 27 | testData.testRpaTask1.application, 28 | testData.testRpaTask2.application, 29 | testData.testRpaTask4.application, 30 | testData.testRpaTask5.application, 31 | ]); 32 | }); 33 | }); 34 | 35 | describe('GET /functionalities/{application}/tasks', () => { 36 | it('retrieves the list of all available tasks for an application correctly', async () => { 37 | const request = httpMocks.createRequest({ 38 | params: { 39 | application: testData.testRpaTask1.application, 40 | }, 41 | }); 42 | 43 | const response = httpMocks.createResponse(); 44 | await rpaController.getAvailableTasksForApplications(request, response); 45 | const data = await response._getData(); 46 | 47 | expect(response.statusCode).toBe(200); 48 | expect(data).toEqual([ 49 | testData.testRpaTask1.task, 50 | testData.testRpaTask3.task, 51 | ]); 52 | }); 53 | }); 54 | 55 | describe('GET /functionalities', () => { 56 | it('retrieves the list of all available parameter Objects correctly', async () => { 57 | const response = httpMocks.createResponse(); 58 | await rpaController.getAllRpaFunctionalities({}, response); 59 | const data = await response._getData(); 60 | 61 | expect(response.statusCode).toBe(200); 62 | expect(data.length).toBe(testData.numberOfTestTasks); 63 | expect(data[0].inputVars.length).not.toBe(0); 64 | expect(data[0].inputVars.length).not.toBe(0); 65 | 66 | const paramObjectOfTestRpaTask1 = data[0].inputVars[0]; 67 | const testTaskKey = testData.testRpaTask1.inputVars.keys[0]; 68 | expect(paramObjectOfTestRpaTask1).toHaveProperty( 69 | String(testTaskKey), 70 | testData.testRpaTask1.inputVars[testTaskKey] 71 | ); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /server/api/routes/robots/robots.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const rpaattributesRouter = require('./rpaAttributes/rpaAttributes'); 3 | const parametersRouter = require('./rpaParameters/rpaParameters'); 4 | const parsingController = require('../../controllers/ssotParsingController'); 5 | const retrievalController = require('../../controllers/ssotRetrievalController'); 6 | 7 | const router = express.Router(); 8 | 9 | router.use('/parameters', parametersRouter); 10 | router.use('/rpaattributes', rpaattributesRouter); 11 | 12 | router.get('/:robotId', retrievalController.getSingleSourceOfTruth); 13 | router.put('/:robotId', retrievalController.overwriteRobot); 14 | router.delete('/:robotId', retrievalController.deleteRobot); 15 | router.patch('/:robotId/robotName', retrievalController.renameRobot); 16 | router.get('/:robotId/robotCode', parsingController.getRobotCodeForId); 17 | 18 | module.exports = router; 19 | -------------------------------------------------------------------------------- /server/api/routes/robots/rpaAttributes/rpaAttributes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const rpaAttributesController = require('../../../controllers/ssotRpaAttributes'); 3 | 4 | const router = express.Router(); 5 | 6 | router.put('/', rpaAttributesController.updateMany); 7 | router.delete('/:robotId', rpaAttributesController.deleteForActivities); 8 | router.get('/:robotId', rpaAttributesController.retrieveAttributesForRobot); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /server/api/routes/robots/rpaAttributes/rpaAttributes.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /* eslint-disable no-underscore-dangle */ 3 | const mongoose = require('mongoose'); 4 | const httpMocks = require('node-mocks-http'); 5 | const dbHandler = require('../../../../utils/testing/testDatabaseHandler'); 6 | const dbLoader = require('../../../../utils/testing/databaseLoader'); 7 | const ssotAttributesController = require('../../../controllers/ssotRpaAttributes'); 8 | 9 | // eslint-disable-next-line no-unused-vars 10 | const rpaTaskModel = require('../../../models/rpaTaskModel'); 11 | 12 | const testData = require('../../../../utils/testing/testData'); 13 | const { testSsot, testRobotId } = require('../../../../utils/testing/testData'); 14 | 15 | /** 16 | * Connect to a new in-memory database before running any tests. 17 | */ 18 | beforeAll(async () => dbHandler.connect()); 19 | 20 | /** 21 | * Clear all test data after every test. 22 | */ 23 | afterEach(async () => dbHandler.clearDatabase()); 24 | 25 | /** 26 | * Remove and close the db and server. 27 | */ 28 | afterAll(async () => dbHandler.closeDatabase()); 29 | 30 | describe('PUT /robots/rpaattributes', () => { 31 | it('successfully updates all attributes for a robot', async () => { 32 | await dbLoader.loadAttributesInDb(); 33 | 34 | const NEW_APP_VALUE = 'NewTestApp'; 35 | const NEW_TASK_VALUE = 'NewTestTask'; 36 | 37 | const request = httpMocks.createRequest({ 38 | method: 'POST', 39 | body: { 40 | attributeObjectList: [ 41 | { 42 | activityId: 'Activity_175v5b5', 43 | robotId: '606199015d691786a44a608f', 44 | rpaApplication: NEW_APP_VALUE, 45 | rpaTask: NEW_TASK_VALUE, 46 | }, 47 | ], 48 | }, 49 | }); 50 | const response = httpMocks.createResponse(); 51 | 52 | await ssotAttributesController.updateMany(request, response); 53 | expect(response.statusCode).toBe(200); 54 | const data = await response._getData(); 55 | expect(data.modifiedCount).toBe(1); 56 | 57 | const newAttributesObject = await mongoose 58 | .model('rpaAttributes') 59 | .findOne({ 60 | robotId: testRobotId, 61 | activityId: testSsot.elements[2].id, 62 | }) 63 | .exec(); 64 | 65 | expect(newAttributesObject.rpaApplication).toEqual(NEW_APP_VALUE); 66 | expect(newAttributesObject.rpaTask).toEqual(NEW_TASK_VALUE); 67 | }); 68 | }); 69 | 70 | describe('GET /robots/rpaattributes/{robotId}', () => { 71 | it('successfully retreives all attributes for a robot', async () => { 72 | await dbLoader.loadAttributesInDb(); 73 | 74 | const request = httpMocks.createRequest({ 75 | params: { 76 | robotId: testRobotId, 77 | }, 78 | }); 79 | const response = httpMocks.createResponse(); 80 | 81 | await ssotAttributesController.retrieveAttributesForRobot( 82 | request, 83 | response 84 | ); 85 | expect(response.statusCode).toBe(200); 86 | const data = await response._getData(); 87 | 88 | expect(data.length).toBe(3); 89 | expect(JSON.stringify(data)).toEqual( 90 | JSON.stringify([ 91 | testData.testAttributes1, 92 | testData.testAttributes2, 93 | testData.testAttributes3, 94 | ]) 95 | ); 96 | }); 97 | }); 98 | 99 | describe('DELETE /robots/rpaattributes/{robotId}', () => { 100 | it('deletes removed activity related attributes', async () => { 101 | await dbLoader.loadSsotInDb(); 102 | await dbLoader.loadAttributesInDb(); 103 | 104 | const deletedActivityList = [ 105 | testSsot.elements[2].id, 106 | testSsot.elements[3].id, 107 | ]; 108 | const payload = { activityIdList: deletedActivityList }; 109 | 110 | const request = httpMocks.createRequest({ 111 | method: 'DELETE', 112 | body: payload, 113 | params: { 114 | robotId: testRobotId, 115 | }, 116 | }); 117 | const response = httpMocks.createResponse(); 118 | 119 | await ssotAttributesController.deleteForActivities(request, response); 120 | 121 | const foundAttributes = await mongoose.model('rpaAttributes').find().exec(); 122 | expect(foundAttributes.length).toBe(1); 123 | 124 | expect(response.statusCode).toBe(200); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /server/api/routes/robots/rpaParameters/rpaParameters.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const parameterController = require('../../../controllers/ssotParameterController'); 3 | 4 | const router = express.Router(); 5 | 6 | router.put('/', parameterController.updateMany); 7 | router.delete('/:robotId', parameterController.deleteForActivities); 8 | router.get('/:robotId', parameterController.retrieveParametersForRobot); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /server/api/routes/robots/rpaParameters/rpaParameters.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const mongoose = require('mongoose'); 3 | const httpMocks = require('node-mocks-http'); 4 | const dbHandler = require('../../../../utils/testing/testDatabaseHandler'); 5 | const dbLoader = require('../../../../utils/testing/databaseLoader'); 6 | const ssotParameterController = require('../../../controllers/ssotParameterController'); 7 | 8 | // eslint-disable-next-line no-unused-vars 9 | const rpaTaskModel = require('../../../models/rpaTaskModel'); 10 | 11 | const testData = require('../../../../utils/testing/testData'); 12 | const { testSsot, testRobotId } = require('../../../../utils/testing/testData'); 13 | 14 | beforeAll(async () => dbHandler.connect()); 15 | 16 | afterEach(async () => dbHandler.clearDatabase()); 17 | 18 | afterAll(async () => dbHandler.closeDatabase()); 19 | 20 | describe('PUT /robots/parameters', () => { 21 | it('successfully updates parameter for a task', async () => { 22 | await dbLoader.loadParametersInDb(); 23 | const updatedValue = 'StonksOnlyGoDown.xls'; 24 | 25 | const request = httpMocks.createRequest({ 26 | method: 'POST', 27 | body: { 28 | parameterObjectsList: [ 29 | { 30 | activityId: testSsot.elements[2].id, 31 | robotId: testRobotId, 32 | rpaParameters: [ 33 | { 34 | name: 'filename', 35 | type: 'String', 36 | isRequired: true, 37 | infoText: 'Path to filename', 38 | index: 0, 39 | value: updatedValue, 40 | }, 41 | ], 42 | }, 43 | ], 44 | }, 45 | }); 46 | const response = httpMocks.createResponse(); 47 | 48 | await ssotParameterController.updateMany(request, response); 49 | expect(response.statusCode).toBe(200); 50 | const data = await response._getData(); 51 | expect(data.modifiedCount).toBe(1); 52 | 53 | const newParamObject = await mongoose 54 | .model('parameter') 55 | .findOne({ 56 | robotId: testRobotId, 57 | activityId: testSsot.elements[2].id, 58 | }) 59 | .exec(); 60 | 61 | expect(newParamObject.rpaParameters[0].value).toEqual(updatedValue); 62 | }); 63 | }); 64 | 65 | describe('GET /robots/parameters/{robotId}', () => { 66 | it('successfully retreives all parameters for a robot', async () => { 67 | await dbLoader.loadParametersInDb(); 68 | 69 | const request = httpMocks.createRequest({ 70 | params: { 71 | robotId: testRobotId, 72 | }, 73 | }); 74 | const response = httpMocks.createResponse(); 75 | 76 | await ssotParameterController.retrieveParametersForRobot(request, response); 77 | expect(response.statusCode).toBe(200); 78 | const data = await response._getData(); 79 | expect(data.length).toBe(3); 80 | expect(JSON.stringify(data)).toEqual( 81 | JSON.stringify([ 82 | testData.testParameter1, 83 | testData.testParameter2, 84 | testData.testParameter3, 85 | ]) 86 | ); 87 | }); 88 | }); 89 | 90 | describe('DELETE /robots/parameters/{robotId}', () => { 91 | it('deletes removed activity related parameter', async () => { 92 | await dbLoader.loadSsotInDb(); 93 | await dbLoader.loadParametersInDb(); 94 | 95 | const deletedActivityList = [ 96 | testSsot.elements[2].id, 97 | testSsot.elements[3].id, 98 | ]; 99 | const payload = { activityIdList: deletedActivityList }; 100 | 101 | const request = httpMocks.createRequest({ 102 | method: 'DELETE', 103 | body: payload, 104 | params: { 105 | robotId: testRobotId, 106 | }, 107 | }); 108 | const response = httpMocks.createResponse(); 109 | 110 | await ssotParameterController.deleteForActivities(request, response); 111 | 112 | const foundParameters = await mongoose.model('parameter').find().exec(); 113 | expect(foundParameters.length).toBe(1); 114 | 115 | expect(response.statusCode).toBe(200); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /server/api/routes/users/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const retrievalController = require('../../controllers/ssotRetrievalController'); 3 | 4 | const router = express.Router(); 5 | 6 | router.get('/:userId/robots', retrievalController.getRobotList); 7 | router.post('/robotAccess', retrievalController.shareRobotWithUser); 8 | router.post('/:userId/robots', retrievalController.createNewRobot); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /server/api/routes/users/users.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const mongoose = require('mongoose'); 3 | const httpMocks = require('node-mocks-http'); 4 | const dbHandler = require('../../../utils/testing/testDatabaseHandler'); 5 | const dbLoader = require('../../../utils/testing/databaseLoader'); 6 | const ssotRetrievalController = require('../../controllers/ssotRetrievalController'); 7 | 8 | // eslint-disable-next-line no-unused-vars 9 | const rpaTaskModel = require('../../models/rpaTaskModel'); 10 | 11 | const { 12 | testSsot, 13 | testRobotId, 14 | testUserId, 15 | } = require('../../../utils/testing/testData'); 16 | 17 | /** 18 | * Connect to a new in-memory database before running any tests. 19 | */ 20 | beforeAll(async () => dbHandler.connect()); 21 | 22 | /** 23 | * Clear all test data after every test. 24 | */ 25 | afterEach(async () => dbHandler.clearDatabase()); 26 | 27 | /** 28 | * Remove and close the db and server. 29 | */ 30 | afterAll(async () => dbHandler.closeDatabase()); 31 | 32 | describe('GET /users/{userId}/robots', () => { 33 | it('retreives the list of robots for user correctly', async () => { 34 | await dbLoader.loadSsotInDb(); 35 | await dbLoader.loadUserAccessObjectsInDb(); 36 | 37 | const request = httpMocks.createRequest({ 38 | params: { 39 | userId: testUserId, 40 | }, 41 | }); 42 | const response = httpMocks.createResponse(); 43 | await ssotRetrievalController.getRobotList(request, response); 44 | const data = await response._getData(); 45 | expect(response.statusCode).toBe(200); 46 | // Catches error "Received: serializes to the same string" 47 | // Solution found here https://github.com/facebook/jest/issues/8475#issuecomment-537830532 48 | expect(JSON.stringify(data[0]._id)).toEqual(JSON.stringify(testRobotId)); 49 | }); 50 | }); 51 | 52 | describe('POST /users/robotAccess', () => { 53 | it('successfully creates a userAccessObject for robot and user', async () => { 54 | const request = httpMocks.createRequest({ 55 | body: { 56 | userId: testUserId, 57 | robotId: testRobotId, 58 | accessLevel: 'ReadWrite', 59 | }, 60 | }); 61 | const response = httpMocks.createResponse(); 62 | 63 | await ssotRetrievalController.shareRobotWithUser(request, response); 64 | const data = await response._getData(); 65 | 66 | expect(response.statusCode).toBe(200); 67 | expect(JSON.stringify(data.userId)).toEqual(JSON.stringify(testUserId)); 68 | expect(JSON.stringify(data.robotId)).toEqual(JSON.stringify(testRobotId)); 69 | 70 | const userAccessObject = await mongoose 71 | .model('userAccessObject') 72 | .find({ 73 | userId: testUserId, 74 | robotId: testRobotId, 75 | }) 76 | .exec(); 77 | 78 | expect(JSON.stringify(userAccessObject[0].robotId)).toBe( 79 | JSON.stringify(testRobotId) 80 | ); 81 | expect(JSON.stringify(userAccessObject[0].userId)).toEqual( 82 | JSON.stringify(testUserId) 83 | ); 84 | }); 85 | }); 86 | 87 | describe('POST /users/{userId}/robots', () => { 88 | it('successfully creates a new ssot', async () => { 89 | const request = httpMocks.createRequest({ 90 | body: { 91 | userId: testUserId, 92 | robotName: testSsot.robotName, 93 | }, 94 | }); 95 | const response = httpMocks.createResponse(); 96 | 97 | await ssotRetrievalController.createNewRobot(request, response); 98 | expect(response.statusCode).toBe(200); 99 | 100 | const data = await response._getData(); 101 | const newRobotId = data._id; 102 | 103 | const request2 = httpMocks.createRequest({ 104 | params: { 105 | userId: testUserId, 106 | }, 107 | }); 108 | const response2 = httpMocks.createResponse(); 109 | await ssotRetrievalController.getRobotList(request2, response2); 110 | 111 | const data2 = await response2._getData(); 112 | expect(response.statusCode).toBe(200); 113 | expect(JSON.stringify(data2[0]._id)).toEqual(JSON.stringify(newRobotId)); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ark-automate", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node server", 6 | "build": "", 7 | "test": "jest --runInBand", 8 | "lint": "eslint --ignore-path ../.gitignore --ext .js,.jsx .", 9 | "local": "nodemon --exec \"heroku local\" --signal SIGTERM", 10 | "mac_m1_test": "env MONGOMS_ARCH=x64 npm run test" 11 | }, 12 | "cacheDirectories": [ 13 | "node_modules", 14 | "client/node_modules" 15 | ], 16 | "dependencies": { 17 | "chai": "^4.3.4", 18 | "express": "^4.16.4", 19 | "mongodb": "^3.6.5", 20 | "mongodb-memory-server": "^6.9.6", 21 | "mongoose": "^5.12.1", 22 | "nodemon": "^2.0.7", 23 | "socket.io": "^4.0.0", 24 | "swagger-jsdoc": "5.0.1", 25 | "swagger-ui-express": "^4.1.6" 26 | }, 27 | "devDependencies": { 28 | "eslint": "^7.17.0", 29 | "eslint-config-airbnb": "^18.2.1", 30 | "eslint-config-prettier": "^7.1.0", 31 | "eslint-plugin-import": "^2.22.1", 32 | "eslint-plugin-jsx-a11y": "^6.4.1", 33 | "eslint-plugin-only-warn": "^1.0.2", 34 | "eslint-plugin-react": "^7.22.0", 35 | "eslint-plugin-react-hooks": "^4.2.0", 36 | "jest": "^26.6.3", 37 | "node-mocks-http": "^1.10.1", 38 | "supertest": "^6.1.1" 39 | }, 40 | "engines": { 41 | "node": "12.13.x" 42 | }, 43 | "jest": { 44 | "testEnvironment": "node" 45 | }, 46 | "nodemonConfig": { 47 | "ext": "js,json,yaml", 48 | "ignore": [ 49 | "*.test.js" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const express = require('express'); 3 | const path = require('path'); 4 | const cluster = require('cluster'); 5 | const numCPUs = require('os').cpus().length; 6 | const http = require('http'); 7 | const socketio = require('socket.io'); 8 | 9 | const isDev = process.env.NODE_ENV !== 'production'; 10 | const PORT = process.env.PORT || 5000; 11 | const swaggerUi = require('swagger-ui-express'); 12 | const rpaFrameworkRouter = require('./api/routes/functionalities/functionalities'); 13 | const ssotRouter = require('./api/routes/robots/robots'); 14 | const userRouter = require('./api/routes/users/users'); 15 | const { socketManager } = require('./socket/socketManager'); 16 | const { 17 | swaggerSpec, 18 | } = require('./utils/openApiDocumentation/docuGenerationHelper'); 19 | 20 | // Multi-process to utilize all CPU cores. 21 | if (!isDev && cluster.isMaster) { 22 | console.error(`Node cluster master ${process.pid} is running`); 23 | 24 | // Fork workers. 25 | for (let i = 0; i < numCPUs; i += 1) { 26 | cluster.fork(); 27 | } 28 | 29 | cluster.on('exit', (worker, code, signal) => { 30 | console.error( 31 | `Node cluster worker ${worker.process.pid} exited: code ${code}, signal ${signal}` 32 | ); 33 | }); 34 | } else { 35 | const app = express(); 36 | 37 | const { createServer } = http; 38 | const { Server } = socketio; 39 | const httpServer = createServer(app); 40 | const io = new Server(httpServer, { 41 | cors: { 42 | origin: 'http://localhost:3000', 43 | methods: ['GET', 'POST'], 44 | }, 45 | }); 46 | 47 | io.on('connection', (socket) => { 48 | socketManager(io, socket); 49 | }); 50 | 51 | httpServer.listen(Number(PORT) + 1, () => { 52 | console.error(`Socket server: listening on port ${Number(PORT) + 1}`); 53 | }); 54 | 55 | mongoose.connect(process.env.MONGODB_URI, { 56 | useNewUrlParser: true, 57 | useUnifiedTopology: true, 58 | }); 59 | 60 | app.use(express.static(path.resolve(__dirname, 'build'))); 61 | app.use(express.json()); 62 | 63 | app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); 64 | app.use('/functionalities', rpaFrameworkRouter); 65 | app.use('/robots', ssotRouter); 66 | app.use('/users', userRouter); 67 | 68 | // All remaining requests return the React app, so it can handle routing. 69 | app.get('*', (request, response) => { 70 | response.sendFile(path.resolve(__dirname, 'build', 'index.html')); 71 | }); 72 | 73 | app.listen(PORT, () => { 74 | console.error( 75 | `Node ${ 76 | isDev ? 'dev server' : `cluster worker ${process.pid}` 77 | }: listening on port ${PORT}` 78 | ); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /server/socket/socketHelper.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const mongoose = require('mongoose'); 3 | const dbHandler = require('../utils/testing/testDatabaseHandler'); 4 | const socketHelperFunctions = require('./socketHelperFunctions'); 5 | const testData = require('../utils/testing/testData'); 6 | 7 | const { 8 | testRobotId, 9 | testUserId, 10 | testJobId, 11 | testRobotCode, 12 | } = require('../utils/testing/testData'); 13 | 14 | const dbLoader = require('../utils/testing/databaseLoader'); 15 | 16 | /** 17 | * Connect to a new in-memory database before running any tests. 18 | */ 19 | beforeAll(async () => dbHandler.connect()); 20 | 21 | /** 22 | * Clear all test data after every test. 23 | */ 24 | afterEach(async () => dbHandler.clearDatabase()); 25 | 26 | /** 27 | * Remove and close the db and server. 28 | */ 29 | afterAll(async () => dbHandler.closeDatabase()); 30 | 31 | describe('robot code retrieval', () => { 32 | it('sucessfully retreives the robot code', async () => { 33 | await dbLoader.loadSsotInDb(); 34 | await dbLoader.loadAttributesInDb(); 35 | await dbLoader.loadParametersInDb(); 36 | await dbLoader.loadJobInDb(); 37 | await dbLoader.loadTasksInDb(); 38 | 39 | const robotCode = await socketHelperFunctions.getRobotCodeForJob( 40 | testRobotId, 41 | testJobId 42 | ); 43 | expect(robotCode).not.toBeUndefined(); 44 | expect(robotCode).not.toBeNull(); 45 | expect(String(robotCode).replace(/\s/g, '')).toEqual( 46 | String(testRobotCode).replace(/\s/g, '') 47 | ); 48 | }); 49 | }); 50 | 51 | describe('user id retrieval', () => { 52 | it('sucessfully retreives all the user ids from the existing user access objects', async () => { 53 | await dbLoader.loadUserAccessObjectsInDb(); 54 | 55 | const userIds = await socketHelperFunctions.getAllUserIds(); 56 | expect(userIds).not.toBeUndefined(); 57 | expect(userIds).not.toBeNull(); 58 | expect(userIds).not.toContain('undefined'); 59 | expect(userIds).toContain(testUserId); 60 | expect(userIds).toContain(testData.user2Id); 61 | expect(userIds.length).toEqual(2); 62 | }); 63 | }); 64 | 65 | describe('job creation', () => { 66 | it('sucessfully creates a job', async () => { 67 | const jobId = await socketHelperFunctions.createJob( 68 | testUserId, 69 | testRobotId, 70 | 'testStatus', 71 | [] 72 | ); 73 | expect(jobId).not.toBeUndefined(); 74 | expect(jobId).not.toBeNull(); 75 | 76 | const foundJob = await mongoose.model('job').findById(jobId); 77 | expect(JSON.stringify(foundJob.id)).toEqual(JSON.stringify(jobId)); 78 | }); 79 | }); 80 | 81 | describe('updating of job', () => { 82 | it('sucessfully updates a job status', async () => { 83 | await dbLoader.loadJobInDb(); 84 | 85 | await socketHelperFunctions.updateRobotJobStatus( 86 | testData.testJob._id, 87 | 'updatedStatus' 88 | ); 89 | 90 | const foundJob = await mongoose.model('job').findById(testData.testJob._id); 91 | expect(foundJob).not.toBeNull(); 92 | expect(foundJob).not.toBeUndefined(); 93 | expect(foundJob.status).toEqual('updatedStatus'); 94 | }); 95 | 96 | it('sucessfully updates a job error object', async () => { 97 | await dbLoader.loadJobInDb(); 98 | 99 | await socketHelperFunctions.updateRobotJobErrors( 100 | testData.testJob._id, 101 | testData.failingRobotRunLog 102 | ); 103 | 104 | const foundJob = await mongoose.model('job').findById(testData.testJob._id); 105 | expect(foundJob.loggedErrors.length).toEqual(2); 106 | expect(foundJob.loggedErrors[0].activityName).toBe('Browser3'); 107 | expect(foundJob.loggedErrors[0].tasks.length).toBe(2); 108 | expect(foundJob.loggedErrors[0].message).toBe( 109 | "No keyword with name 'Open Chro Browser' found. Did you mean:\n RPA.Browser.Selenium.Open Chrome Browser" 110 | ); 111 | expect(foundJob.loggedErrors[1].activityName).toBe('Save file'); 112 | expect(foundJob.loggedErrors[1].message).toBe('Test Failing Message'); 113 | }); 114 | }); 115 | 116 | describe('getting all jobs for user', () => { 117 | it('sucessfully gets all jobs for user', async () => { 118 | await dbLoader.loadJobInDb(); 119 | 120 | const jobList = await socketHelperFunctions.getAllWaitingJobsForUser( 121 | testUserId 122 | ); 123 | 124 | expect(jobList).not.toBeUndefined(); 125 | expect(jobList).not.toBeNull(); 126 | expect(jobList).not.toContain('undefined'); 127 | expect(JSON.stringify(jobList)).toEqual(JSON.stringify([testData.testJob])); 128 | expect(jobList.length).toEqual(1); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /server/socket/socketManager.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | const socketHelperFunctions = require('./socketHelperFunctions'); 3 | 4 | exports.socketManager = (io, socket) => { 5 | // eslint-disable-next-line no-console 6 | console.log('Client connected via socket: ', socket.id); 7 | 8 | /* When a client wants to join a room we check if the roomId (userId) matches any of the userIds in the database. 9 | Once connected we check for waiting jobs and if available send them to the client to execute */ 10 | socket.on('joinUserRoom', (userId, clientType) => { 11 | socketHelperFunctions.getAllUserIds().then((users) => { 12 | if (users.includes(userId)) { 13 | socket.join(userId); 14 | socket.emit( 15 | 'successUserRoomConnection', 16 | `You have connected to the user room ${userId}` 17 | ); 18 | io.to(userId).emit( 19 | 'newClientJoinedUserRoom', 20 | `New user has been connected to the room` 21 | ); 22 | if (clientType !== 'webApplication') { 23 | socketHelperFunctions 24 | .getAllWaitingJobsForUser(userId) 25 | .then((jobList) => { 26 | if (jobList.length > 0) { 27 | jobList.forEach((job) => { 28 | const { id, robotId } = job; 29 | socketHelperFunctions 30 | .getRobotCodeForJob(robotId, id) 31 | .then((robotCode) => { 32 | if (robotCode) { 33 | socketHelperFunctions.updateRobotJobStatus( 34 | id, 35 | 'executing' 36 | ); 37 | io.to(userId).emit('robotExecution', { 38 | robotCode, 39 | jobId: id, 40 | }); 41 | } else { 42 | socketHelperFunctions.updateRobotJobStatus( 43 | id, 44 | 'failed' 45 | ); 46 | } 47 | }); 48 | }); 49 | } 50 | }); 51 | } 52 | } else { 53 | socket.emit('errorUserRoomConnection', 'Invalid userId: ', userId); 54 | } 55 | }); 56 | }); 57 | 58 | /* Gets triggered when the web client wants to execute a robot. We check if a desktop client is available. We either execute 59 | the robot immediately and add a job to the database with status executing or we just add a job to the database with status waiting */ 60 | socket.on('robotExecutionJobs', ({ robotId, userId, parameters }) => { 61 | const clients = io.sockets.adapter.rooms.get(userId); 62 | const numClients = clients ? clients.size : 0; 63 | if (numClients > 1) { 64 | socketHelperFunctions 65 | .createJob(userId, robotId, 'executing', parameters) 66 | .then((jobId) => { 67 | socketHelperFunctions 68 | .getRobotCodeForJob(robotId, jobId) 69 | .then((robotCode) => { 70 | io.to(userId).emit('robotExecution', { robotCode, jobId }); 71 | }); 72 | }); 73 | } else { 74 | socketHelperFunctions.createJob(userId, robotId, 'waiting', parameters); 75 | } 76 | }); 77 | 78 | socket.on('updatedLiveRobotLog', ({ userId, jobId, robotLogs }) => { 79 | io.to(userId).emit('changedRobotStatus', 'running'); 80 | if (robotLogs.finalMessage === 'Execution completed') { 81 | socketHelperFunctions.updateRobotJobStatus( 82 | jobId, 83 | robotLogs.robotRun.status === 'FAIL' ? 'failed' : 'successful' 84 | ); 85 | io.to(userId).emit( 86 | 'changedRobotStatus', 87 | robotLogs.robotRun.status === 'FAIL' ? 'failed' : 'successful' 88 | ); 89 | if (robotLogs.robotRun.status === 'FAIL') { 90 | socketHelperFunctions.updateRobotJobErrors(jobId, robotLogs); 91 | } 92 | } 93 | io.to(userId).emit('changedRobotRunLogs', robotLogs); 94 | }); 95 | }; 96 | -------------------------------------------------------------------------------- /server/utils/openApiDocumentation/docuGenerationHelper.js: -------------------------------------------------------------------------------- 1 | const swaggerJSDoc = require('swagger-jsdoc'); 2 | 3 | const swaggerDefinition = { 4 | openapi: '3.0.0', 5 | info: { 6 | title: 'Ark-Automate API', 7 | version: '1.0.0', 8 | description: 9 | '_This document describes the REST API of Ark Automate._
Ark Automate is a platform that allows office users and software developers to automate business or everyday processes by simply sketching the steps of their process. By using simple flowcharts or powerful BPMN in their process outlines, users can create small software solutions using RPA that finish their tasks much faster and more reliably.', 10 | license: { 11 | name: 'LICENSE (MIT)', 12 | url: 'https://github.com/bptlab/ark_automate/blob/main/LICENSE.md', 13 | }, 14 | }, 15 | servers: [ 16 | { 17 | url: 'http://localhost:5000', 18 | description: 'Development server', 19 | }, 20 | ], 21 | tags: [ 22 | { 23 | name: 'RPA-Functionalities', 24 | description: 25 | 'Operations about rpa supported applications, tasks and parameters', 26 | }, 27 | { 28 | name: 'Robots', 29 | description: 'Operations about robots', 30 | }, 31 | { 32 | name: 'Users', 33 | description: 'Operations dealing with the users access to robots', 34 | }, 35 | ], 36 | }; 37 | 38 | const options = { 39 | swaggerDefinition, 40 | // Paths to files containing OpenAPI definitions 41 | apis: [ 42 | './api/controllers/*.js', 43 | './utils/openApiDocumentation/openApiComponents.js', 44 | ], 45 | }; 46 | 47 | const swaggerSpec = swaggerJSDoc(options); 48 | 49 | module.exports = { swaggerSpec }; 50 | -------------------------------------------------------------------------------- /server/utils/ssotToRobotParsing/__tests__/SsotForTesting.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "606199015d691786a44a608f", 3 | "starterId": "Event_1wm4a0f", 4 | "robotName": "Sandros Testbot", 5 | "elements": [ 6 | { 7 | "predecessorIds": [], 8 | "successorIds": ["Activity_1elomab"], 9 | "_id": "6062f0ad92ffd3044c6ee382", 10 | "type": "MARKER", 11 | "name": "Start Event", 12 | "id": "Event_1wm4a0f" 13 | }, 14 | { 15 | "predecessorIds": ["Event_1wm4a0f"], 16 | "successorIds": ["Activity_175v5b5"], 17 | "_id": "6062f0ad92ffd3044c6ee383", 18 | "type": "INSTRUCTION", 19 | "name": "FirstActivity", 20 | "id": "Activity_1elomab" 21 | }, 22 | { 23 | "predecessorIds": ["Activity_1elomab"], 24 | "successorIds": ["Activity_1x8wlwh"], 25 | "_id": "6062f0ad92ffd3044c6ee384", 26 | "type": "INSTRUCTION", 27 | "name": "SecondActivity", 28 | "id": "Activity_175v5b5" 29 | }, 30 | { 31 | "predecessorIds": ["Activity_175v5b5"], 32 | "successorIds": ["Event_1cuknwt"], 33 | "_id": "6062f0ad92ffd3044c6ee385", 34 | "type": "INSTRUCTION", 35 | "name": "ThirdActivity", 36 | "id": "Activity_1x8wlwh" 37 | }, 38 | { 39 | "predecessorIds": ["Activity_1x8wlwh"], 40 | "successorIds": [], 41 | "_id": "6062f0ad92ffd3044c6ee386", 42 | "type": "MARKER", 43 | "name": "finished", 44 | "id": "Event_1cuknwt" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /server/utils/ssotToRobotParsing/generateCodeBase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @category Server 3 | * @module 4 | */ 5 | const mongoose = require('mongoose'); 6 | const { 7 | ACTIVITY_IDENTIFIER, 8 | FOURSPACE, 9 | LINEBREAK, 10 | } = require('./robotCodeConstants'); 11 | 12 | // eslint-disable-next-line no-unused-vars 13 | const ssotModels = require('../../api/models/singleSourceOfTruthModel.js'); 14 | 15 | /** 16 | * @description Collects the applications used by the robot 17 | * @param {Array} elements All the elements from the SSoT 18 | * @returns {Array} All unique Applications that occur in the ssot 19 | */ 20 | const collectApplications = (elements) => { 21 | const applications = []; 22 | if (elements !== undefined && elements.length > 0) { 23 | elements.forEach((element) => { 24 | if ( 25 | element.rpaApplication !== undefined && 26 | !applications.includes(element.rpaApplication) 27 | ) { 28 | applications.push(element.rpaApplication); 29 | } 30 | }); 31 | } 32 | return applications; 33 | }; 34 | 35 | /** 36 | * @description Generates the Library Import Code of the .robot file 37 | * @param {Array} elements All the elements from the SSoT 38 | * @returns {string} Library Import Code that has to be put in .robot file 39 | */ 40 | const generateCodeForLibraryImports = (elements) => { 41 | let libraryImports = ''; 42 | const applications = collectApplications(elements); 43 | if (applications.length > 0) { 44 | Object.values(applications).forEach((application) => { 45 | libraryImports += `Library${FOURSPACE}RPA.${application}${LINEBREAK}`; 46 | }); 47 | } 48 | 49 | return libraryImports; 50 | }; 51 | 52 | /** 53 | * @description Retrieve the associated parameter objects for all activities in the ssot 54 | * @param {Object} ssot Ssot for which the parameters will be retrieved 55 | * @returns {Array} Array of attribute objects 56 | */ 57 | const retrieveAttributes = async (ssot) => { 58 | const { id } = ssot; 59 | const { elements } = ssot; 60 | const listOfActivityIds = []; 61 | 62 | elements.forEach((element) => { 63 | if (element.type === ACTIVITY_IDENTIFIER) { 64 | listOfActivityIds.push(element.id); 65 | } 66 | }); 67 | 68 | const attributeObjects = await mongoose 69 | .model('rpaAttributes') 70 | .find({ 71 | robotId: id, 72 | activityId: { $in: listOfActivityIds }, 73 | }) 74 | .exec(); 75 | 76 | return attributeObjects; 77 | }; 78 | 79 | /** 80 | * @description Generates that basic code that every robot has 81 | * @param {Object} ssot Ssot that will be handled 82 | * @returns {string} Basic code for the .robot file 83 | */ 84 | const generateCodeBase = async (ssot) => { 85 | let parsedCode = ''; 86 | parsedCode += `*** Settings ***${LINEBREAK}`; 87 | const attributeObjects = await retrieveAttributes(ssot); 88 | parsedCode += generateCodeForLibraryImports(attributeObjects); 89 | parsedCode += `${LINEBREAK}*** Tasks ***${LINEBREAK}`; 90 | return { parsedCode, attributeObjects }; 91 | }; 92 | 93 | module.exports = { 94 | generateCodeBase, 95 | }; 96 | -------------------------------------------------------------------------------- /server/utils/ssotToRobotParsing/retrieveParameters.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /** 3 | * @category Server 4 | * @module 5 | */ 6 | const mongoose = require('mongoose'); 7 | const { ACTIVITY_IDENTIFIER } = require('./robotCodeConstants'); 8 | const ssotModels = require('../../api/models/singleSourceOfTruthModel.js'); 9 | const jobsModel = require('../../api/models/robotJobModel.js'); 10 | 11 | /** 12 | * @description Will retrieve the associated parameter objects for all activities in the ssot 13 | * @param {Object} ssot Ssot for which the parameters will be retrieved 14 | * @returns {Array} Array of parameter objects 15 | */ 16 | const retrieveParameters = async (ssot) => { 17 | const { id } = ssot; 18 | const { elements } = ssot; 19 | const listOfActivityIds = []; 20 | 21 | elements.forEach((element) => { 22 | if (element.type === ACTIVITY_IDENTIFIER) { 23 | listOfActivityIds.push(element.id); 24 | } 25 | }); 26 | 27 | const parameterObjects = await mongoose 28 | .model('parameter') 29 | .find( 30 | { 31 | robotId: id, 32 | activityId: { $in: listOfActivityIds }, 33 | }, 34 | { 35 | activityId: 1, 36 | rpaParameters: 1, 37 | outputValue: 1, 38 | } 39 | ) 40 | .exec(); 41 | 42 | return parameterObjects; 43 | }; 44 | 45 | /** 46 | * @description Updates Parameter Objects with new parameters 47 | * @param {Array} parameterObjects Selection of parameter objects that will possibly be updated 48 | * @param {Array} newParameters New parameters in the form {id, value} that will be used to update the parameter objects 49 | * @returns {Array} Array of updated parameter objects 50 | */ 51 | const updateParameterObjects = (parameterObjects, newParameters) => { 52 | parameterObjects.map((parameterObject) => { 53 | if (parameterObject.rpaParameters.length !== 0) { 54 | parameterObject.rpaParameters.map((currentParameter) => { 55 | newParameters.forEach((newParameter) => { 56 | if ( 57 | // eslint-disable-next-line no-underscore-dangle 58 | String(newParameter.parameterId) === String(currentParameter._id) 59 | ) { 60 | // eslint-disable-next-line no-param-reassign 61 | currentParameter.value = newParameter.value; 62 | } 63 | }); 64 | return currentParameter; 65 | }); 66 | } 67 | return parameterObject; 68 | }); 69 | return parameterObjects; 70 | }; 71 | 72 | /** 73 | * @description Retrieves all parameters for a specific job 74 | * @param {String} jobId Id of the job 75 | * @returns {Array} Array of parameter objects 76 | */ 77 | const getAllParametersForJob = async (jobId) => { 78 | const jobParametersObject = await mongoose 79 | .model('job') 80 | .findById(jobId, { parameters: 1 }); 81 | return jobParametersObject.parameters; 82 | }; 83 | 84 | /** 85 | * @description Retrieves the associated parameter objects for all activities in the ssot 86 | * @param {Object} ssot Ssot for which the parameters will be retrieved 87 | * @param {String} jobId Job id identifiyng a job object from which the additional paramters will be fetched 88 | * @returns {Array} Array of parameter objects 89 | */ 90 | const retrieveParametersFromSsotAndJob = async (ssot, jobId) => { 91 | const parameterObjects = await retrieveParameters(ssot); 92 | const newParameters = await getAllParametersForJob(jobId); 93 | const parameterObjectsUpdated = await updateParameterObjects( 94 | parameterObjects, 95 | newParameters 96 | ); 97 | return parameterObjectsUpdated; 98 | }; 99 | 100 | module.exports = { 101 | retrieveParameters, 102 | retrieveParametersFromSsotAndJob, 103 | }; 104 | -------------------------------------------------------------------------------- /server/utils/ssotToRobotParsing/robotCodeConstants.js: -------------------------------------------------------------------------------- 1 | const ACTIVITY_IDENTIFIER = 'INSTRUCTION'; 2 | const LINEBREAK = '\n'; 3 | const COMMENT = '#'; 4 | const DOUBLESPACE = ' '; 5 | const FOURSPACE = ' '; 6 | 7 | module.exports = { 8 | ACTIVITY_IDENTIFIER, 9 | LINEBREAK, 10 | COMMENT, 11 | DOUBLESPACE, 12 | FOURSPACE, 13 | }; 14 | -------------------------------------------------------------------------------- /server/utils/ssotToRobotParsing/ssotToRobotParser.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable no-useless-escape */ 3 | /** 4 | * @category Server 5 | * @module 6 | */ 7 | const mongoose = require('mongoose'); 8 | const ssotModels = require('../../api/models/singleSourceOfTruthModel.js'); 9 | const jobsModel = require('../../api/models/robotJobModel.js'); 10 | const { generateCodeBase } = require('./generateCodeBase'); 11 | const { 12 | retrieveParameters, 13 | retrieveParametersFromSsotAndJob, 14 | } = require('./retrieveParameters'); 15 | const { generateCodeForRpaTasks } = require('./generateCodeForRpaTasks'); 16 | 17 | /** 18 | * @description Parses the given SSoT to an executable .robot file 19 | * @param {Object} ssot Ssot of the robot 20 | * @returns {string} Code that has to be put in .robot file 21 | */ 22 | const parseSsotToRobotCode = async (ssot) => { 23 | const result = await generateCodeBase(ssot); 24 | const parameters = await retrieveParameters(ssot); 25 | result.parsedCode += await generateCodeForRpaTasks( 26 | ssot.elements, 27 | parameters, 28 | result.attributeObjects, 29 | 'frontend' 30 | ); 31 | return result.parsedCode; 32 | }; 33 | 34 | /** 35 | * @description Parses the given ssot and parameters of the robot job to an executable .robot file 36 | * @param {Object} ssot Ssot of the robot 37 | * @param {Object} jobId Id of the job 38 | * @returns {string} Code that has to be put in .robot file 39 | */ 40 | const parseSsotAndJobToRobotCode = async (ssot, jobId) => { 41 | const result = await generateCodeBase(ssot); 42 | const parameters = await retrieveParametersFromSsotAndJob(ssot, jobId); 43 | result.parsedCode += await generateCodeForRpaTasks( 44 | ssot.elements, 45 | parameters, 46 | result.attributeObjects, 47 | 'local client' 48 | ); 49 | return result.parsedCode; 50 | }; 51 | 52 | /** 53 | * @description Parses the ssot provided by its id to an executable .robot file 54 | * @param {String} robotId Id of the ssot which will be parsed 55 | * @returns {string} Code that has to be put in .robot file 56 | */ 57 | const parseSsotById = async (robotId) => { 58 | const ssot = await mongoose.model('SSoT').findById(robotId).exec(); 59 | return parseSsotToRobotCode(ssot); 60 | }; 61 | 62 | /** 63 | * @description Parses the ssot provided by its id to an executable .robot file 64 | * @param {String} robotId Id of the ssot which will be parsed 65 | * @param {String} jobId Id of the current robotJob that will be executed 66 | * @returns {string} Code that has to be put in .robot file 67 | */ 68 | const parseCodeForJob = async (robotId, jobId) => { 69 | const ssot = await mongoose.model('SSoT').findById(robotId).exec(); 70 | return parseSsotAndJobToRobotCode(ssot, jobId); 71 | }; 72 | 73 | module.exports = { 74 | parseSsotToRobotCode, 75 | parseSsotById, 76 | parseCodeForJob, 77 | }; 78 | -------------------------------------------------------------------------------- /server/utils/testing/databaseLoader.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const testData = require('./testData'); 3 | 4 | const loadSsotInDb = async () => { 5 | const SsotModel = mongoose.model('SSoT'); 6 | const ssot = new SsotModel(testData.testSsot); 7 | await ssot.save(); 8 | }; 9 | 10 | const loadUserAccessObjectsInDb = async () => { 11 | const UserAccessObjectModel = mongoose.model('userAccessObject'); 12 | const userAccessObject = UserAccessObjectModel(testData.testUserAccessObject); 13 | await userAccessObject.save(); 14 | const userAccessObject2 = UserAccessObjectModel( 15 | testData.testUserAccessObject2 16 | ); 17 | await userAccessObject2.save(); 18 | }; 19 | 20 | const loadJobInDb = async () => { 21 | const JobModel = mongoose.model('job'); 22 | const job = new JobModel(testData.testJob); 23 | await job.save(); 24 | }; 25 | 26 | const loadAttributesInDb = async () => { 27 | const RpaAttribute = mongoose.model('rpaAttributes'); 28 | const rpaAttribute = new RpaAttribute(testData.testAttributes1); 29 | await rpaAttribute.save(); 30 | const rpaAttribute2 = new RpaAttribute(testData.testAttributes2); 31 | await rpaAttribute2.save(); 32 | const rpaAttribute3 = new RpaAttribute(testData.testAttributes3); 33 | await rpaAttribute3.save(); 34 | }; 35 | 36 | const loadParametersInDb = async () => { 37 | const RpaParam = mongoose.model('parameter'); 38 | const rpaParameter = new RpaParam(testData.testParameter1); 39 | await rpaParameter.save(); 40 | const rpaParameter2 = new RpaParam(testData.testParameter2); 41 | await rpaParameter2.save(); 42 | const rpaParameter3 = new RpaParam(testData.testParameter3); 43 | await rpaParameter3.save(); 44 | }; 45 | 46 | const loadTasksInDb = async () => { 47 | const RpaTask = mongoose.model('rpa-task'); 48 | const rpaTask = await new RpaTask(testData.testRpaTask1); 49 | await rpaTask.save(); 50 | const rpaTask2 = await new RpaTask(testData.testRpaTask2); 51 | await rpaTask2.save(); 52 | const rpaTask3 = await new RpaTask(testData.testRpaTask3); 53 | await rpaTask3.save(); 54 | const rpaTask4 = await new RpaTask(testData.testRpaTask4); 55 | await rpaTask4.save(); 56 | const rpaTask5 = await new RpaTask(testData.testRpaTask5); 57 | await rpaTask5.save(); 58 | }; 59 | 60 | module.exports = { 61 | loadJobInDb, 62 | loadUserAccessObjectsInDb, 63 | loadSsotInDb, 64 | loadAttributesInDb, 65 | loadParametersInDb, 66 | loadTasksInDb, 67 | }; 68 | -------------------------------------------------------------------------------- /server/utils/testing/testDatabaseHandler.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { MongoMemoryServer } = require('mongodb-memory-server'); 3 | 4 | /** 5 | * @category Server 6 | * @module 7 | */ 8 | const mongooseOpts = { 9 | useNewUrlParser: true, 10 | useUnifiedTopology: true, 11 | }; 12 | 13 | const mongod = new MongoMemoryServer({ 14 | instance: { 15 | dbName: 'ark-automate', 16 | port: 59051, 17 | }, 18 | }); 19 | 20 | /** 21 | * @description Connects to the in-memory database. 22 | */ 23 | exports.connect = async () => { 24 | const uri = await mongod.getUri(); 25 | await mongoose.createConnection(uri, mongooseOpts); 26 | await mongoose.connect(uri, mongooseOpts); 27 | }; 28 | 29 | /** 30 | * @description Drops the database, closes the connection and stops mongod. 31 | */ 32 | exports.closeDatabase = async () => { 33 | await mongoose.connection.dropDatabase(); 34 | await mongoose.connection.close(); 35 | await mongoose.disconnect(); 36 | await mongod.stop(); 37 | }; 38 | 39 | /** 40 | * @description Removes all the data for all db collections. 41 | */ 42 | exports.clearDatabase = async () => { 43 | const { collections } = mongoose.connection; 44 | const result = []; 45 | Object.keys(collections).forEach((key) => { 46 | const collection = collections[key]; 47 | result.push(collection.deleteMany()); 48 | }); 49 | return Promise.all(result); 50 | }; 51 | -------------------------------------------------------------------------------- /server/utils/testing/testRobotFile.txt: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library RPA.Excel.Application 3 | Library RPA.Browser 4 | *** Tasks *** 5 | Excel.Application 6 | #FirstActivity 7 | Open Workbook C://Users/Filepath 8 | #SecondActivity 9 | Find Empty Row StonksOnlyGoUp.xls 10 | Browser 11 | #ThirdActivity 12 | Open Browser TESTVALUE -------------------------------------------------------------------------------- /wiki/Coding-Standards.md: -------------------------------------------------------------------------------- 1 | # Coding Standards 2 | 3 | In this repository we enforce the following coding standards. 4 | 5 | ## Tools 6 | ### ESLint 7 | In this project we use [EsLint](https://eslint.org/) as Linter and use the following configuration: 8 | 9 | ```json 10 | { 11 | "env": { 12 | "browser": true, 13 | "es2021": true 14 | }, 15 | "extends": [ 16 | "plugin:react/recommended", 17 | "airbnb", 18 | "prettier", 19 | "prettier/react" 20 | ], 21 | "parserOptions": { 22 | "ecmaFeatures": { 23 | "jsx": true 24 | }, 25 | "ecmaVersion": 12, 26 | "sourceType": "module" 27 | }, 28 | "plugins": ["react", "only-warn"], 29 | "rules": { 30 | "no-console": ["error", { "allow": ["warn", "error"] }] 31 | }, 32 | "root": true 33 | } 34 | 35 | ``` 36 | 37 | [Here](https://eslint.org/docs/rules/) you can find the rules that eslint enforces. 38 | ### Prettier 39 | Another Code Formatting Tool being used is [Prettier](https://prettier.io/). Here we use the following configuration: 40 | 41 | ```json 42 | { 43 | "tabWidth": 2, 44 | "bracketSpacing": true, 45 | "endOfLine": "lf", 46 | "jsxSingleQuote": true, 47 | "semi": true, 48 | "singleQuote": true, 49 | "trailingComma": "es5", 50 | "printWidth": 80, 51 | "useTabs": false 52 | } 53 | 54 | ``` 55 | 56 | [Here](https://prettier.io/docs/en/options.html) you can find all the rules that prettier enforces by default. 57 | 58 | **These tools enforce a lot of formatting, code-quality and simplifications that give use a base layer of standards. Please install these in your IDE and configure them as stated above.** 59 | 60 | ## Coding Standards 61 | 62 | ### Naming 63 | - Variable Names in `lowerCamelCase` 64 | - use PascalCase for the naming of components (.jsx files) 65 | - use camelCase for the naming of all other files (.js files mainly) 66 | - use UPPERCASE for constants 67 | - "Single Source of Truth" is abbreviated with `Ssot` or `ssot` (don't use `SSoT` or `SSOT`) 68 | - use `Robot` in all cases instead of `Bot` 69 | - use `Application` in all cases instead of `App` (in context of supported RPA Applications) 70 | - use **hyphens** for CSS-classes and CSS-ids consistently 71 | - For example, don't call the class `buttonBackground` and instead call it `button-background`. 72 | 73 | ### General Code-Style 74 | - Do not use double empty lines 75 | - Space after openning bracket and before closing bracket (Goal: `import { Space } from AntD`) 76 | - Always use single-quotation marks 77 | - We use only arrow functions. Please do not use the `function` keyword. 78 | - Try to use only relative units (vw,vh,rem,%) to size elements with css and **not** absolut units (px) 79 | 80 | ### Documentation 81 | Please do not use inline comments to explain the idea behind a variable or a function. Only use those for sources where you found a special solution or workaround or for especially complex code snippets. Further comments regarding the documentation with JSDoc are also ok/appreciated. 82 | 83 | Please document every written function for our automated documentation [JSDoc](https://jsdoc.app/). See our Guide for that [here](https://github.com/bptlab/ark_automate/wiki/How-to-write-code-documentation). 84 | 85 | 86 | ### Export/Imports 87 | 88 | #### Frontend 89 | We use 90 | ```javascript 91 | import xyz from 'pathOrModule' // for imports 92 | default export yourModule // for exports 93 | ``` 94 | Here `export` statements are always the last statement of a component (If available, a proptype typechecking therefore has to be done before the export statement) 95 | #### Backend 96 | We use 97 | ```javascript 98 | require('pathToFile') // for imports 99 | exports.yourFunction() // for exports 100 | ``` 101 | 102 | 103 | ### Other 104 | - Try to avoid to fix Eslint warnings by adding the "fix-comment" 105 | -------------------------------------------------------------------------------- /wiki/Database-and-Communication.md: -------------------------------------------------------------------------------- 1 | # Database and Communication 2 | 3 | We are using a MongoDB database to which we connect through our backend using the Mongoose Library/Module. 4 | 5 | ## How to communicate with the database 6 | 7 | If you plan to create a CRED operation which should be available for the frontend, please consider to create a path in the backend, which can then be called from the frontend. 8 | In the backend please use the mongoose module to make a callout to the database in question. For most use cases our standard database should be used, which can be connected to through the MONGODB_URI environment variable in our code. 9 | 10 | ## Models 11 | 12 | If you are planning on creating a new document type (called model from now on), please create a javascript file in the modules directory on the server side. There you can specify the schema for objects of that type and also create the model and specify, which collection should be used if not that of the name of the model that you specify. 13 | This can be a bit confusing at first, but you could think of it like this: The schema defines what fields are of what type. The model then assigns this a name and registers it for use. Mongoose will always try to retrieve objects from the remote db from a collection which has the same name as the one specified when creating the model, but this can be overwritten as mentioned. 14 | 15 | ## Testing 16 | 17 | To simulate a local copy of the mongodb instance, we are using the mongodb-memory-server module. That way we can have a controlled environment for our tests. 18 | One such example can be observed on the server side in the MongoDbManager.js which creates a local database instance, populates it and then switches the environment variable, so that all requests will be made against this database mock. 19 | 20 | For the future we should consider to refactor the MongoDbManager.js file to allow for usage with multiple test cases for any objects on the backend side. 21 | Until then, please keep in mind to connect to a local instance the way it is done in that file. Other ways might cause the first test after setup of any testclass to fail. If you want to read up on this issue, please have a look [here](https://github.com/nodkz/mongodb-memory-server#several-mongoose-connections-simultaneously) under _Note: When you create mongoose connection manually_. 22 | 23 | ## Tutorial 24 | 25 | For an additional tutorial on how to use Mongoose, please have a look [here](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose). 26 | -------------------------------------------------------------------------------- /wiki/Documentation-Communication-Local-Client.md: -------------------------------------------------------------------------------- 1 | # How the approach with socket.io was implemented on our platform 2 | 3 | **Desktop app:** 4 | Here we implemented a CLI that reads the userId the user enters, saves the userId and uses the userId as an authentication for the communication with the web app server. Once started, the desktop app connects with the server by using a socket connection. A socket connection is this bidirectional connection that exist between every client and server. Moreover we use the userId the user entered and ask the server if this socket connection can join the room userId (we have one room for every userId). Once the socket connection was added to the userId room we wait for robots to be executed. Because we are in the userId room we receive robot execution jobs of web frontends that are connected to the same room. 5 | 6 | **Database/Server:** 7 | We implemented a jobs collection in MongoDB as well as a Mongoose jobs model. Every job has a robotId, a userId, a status (waiting/executing/success/failed) and an array of parameters that contains the arguments the user entered in the web frontend when starting the robot execution. 8 | 9 | **Server:** 10 | Sets up a server and socket instance, establishes socket connection with the web frontend and the desktop app, groups sockets by userIds (by using the room concept), reacts on robot execution commands and forwards this command to the desktop app and updates the jobs collection in MongoDB continiously. 11 | 12 | **Web Frontend:** 13 | Connects with the server using a socket connection. Also, like the dektop app, we join a userId specific room whenever the robot overview is rendered. Additionally, we send a robot execution job to the backend when the user clicks on the play button in the robot container. 14 | 15 | ## Why we Use Socket.io 16 | 17 | In the end, we decided for socket.io as it is open-cource, well-supported, 'easy' to use, robust and websocket based enabling a bidirectional communication. 18 | To get started it is recommended reading [this](https://socket.io/docs/v4/index.html) introduction to socket.io. Especially these two subpages [1](https://socket.io/docs/v4/server-socket-instance/) & [2](https://socket.io/docs/v4/client-socket-instance/) are relevant for this usecase. 19 | -------------------------------------------------------------------------------- /wiki/Documentation-Corporate-Identity.md: -------------------------------------------------------------------------------- 1 | # Corporate / Visual Identity 2 | 3 | ## Colors: 4 | 5 | ![](https://via.placeholder.com/15/00C2FF/000000?text=+) color-primary: #00C2FF 6 | 7 | ![](https://via.placeholder.com/15/1C272B/000000?text=+) color-primary-inverted: #1C272B 8 | 9 | ![](https://via.placeholder.com/15/2F3C41/000000?text=+) color-primary-inverted-2: #2F3C41 10 | 11 | ![](https://via.placeholder.com/15/FFFFFF/000000?text=+) color-primary-inverted-text: #FFFFFF 12 | 13 | ![](https://via.placeholder.com/15/EFEFEF/000000?text=+) color-background: #EFEFEF 14 | 15 | ![](https://via.placeholder.com/15/FFFFFF/000000?text=+) color-background-2: #FFFFFF 16 | 17 | ![](https://via.placeholder.com/15/1D1D1F/000000?text=+) color-background-text: #1D1D1F 18 | 19 | ![](https://via.placeholder.com/15/FF6B00/000000?text=+) color-background-cta: #FF6B00 20 | 21 | **How to use this color schema:** 22 | 23 | - color-primary: This is the primary color of our brand. It is used for important elements (e.g headlines) 24 | - color-primary-inverted: Is the main color complementing the primary color. For example, it can be used for the header/footer. 25 | - color-primary-inverted-2: This is another color complementing the primary color. It is useful in combination with color-primary-inverted. 26 | - color-primary-inverted-text: Is the color of all text written on color-primary-inverted or color-primary-inverted-2. 27 | - color-background: This is the main background color of the website and should therefore be used for the coloring of the background. 28 | - color-background-2: This is another background color that can be used on the main background color (e.g. for containers). 29 | - color-background-text: The color of all the text that is either written on color-background or color-background-2 30 | - color-cta: This is the "call-to-action" color and thus is used for elements like buttons. 31 | 32 | ## Font 33 | 34 | - Font-Settings: 35 | font-family: 'Lato', sans-serif; 36 | font-style: normal; 37 | font-display: swap; 38 | font-weight: 400; 39 | - Font-Size: 1rem 40 | 41 | ## Miscellaneous 42 | 43 | - Border-Radius: 5px 44 | -------------------------------------------------------------------------------- /wiki/Documentation-Folder-Structure.md: -------------------------------------------------------------------------------- 1 | # General 2 | 3 | There is a clear separation of the server side of the application and the client side (`frontend`). Therefore, next to the README and some config files, there are only four folders in the top structure: 4 | 5 | - A **frontend/** folder with its own package.json. There you can find everything regarding the React frontend. 6 | - A **server/** folder with its own package.json. There you can find everything regarding the Node backend and the communication with the database. 7 | - A **wiki/** folder which contains all wiki-documents as Markdown file. The pages can be edited in this folder and will be deployed to the wiki on merge to the `main` branch. 8 | - A **.github/** folder which contains all workflows, our issue & pull request templates as well as some notes for the open source project. 9 | 10 | The local client which is required to run the created robots is located in a [separate repository](https://github.com/bptlab/ark_automate_local). 11 | 12 | ## Server 13 | 14 | The basic structure is explained [here](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes). 15 | The central file here is the **server.js**. Here all the different routers are being used. 16 | 17 | - **api/**: Contains the routes, models and controllers of our API. 18 | - **socket/**: Contains the socket manager who provides the socket rooms for communication 19 | - **utils/**: Helper functions or files like the SsotToRobotParser, the openApiDocumentation or some testing files 20 | 21 | ## Frontend 22 | 23 | On the top level there are only two folders next to the package.json and package-lock.json as well as some more config files. 24 | The **public/** folder contains the known meta information of a web page, as well as the favicon. 25 | Let's focus on the **src/** folder. On the highest level the relevant file is the **index.js**. Here our single page react application gets rendered. Also, there are many folders on the highest level within the src folder: 26 | 27 | - **api/**: API call related functions. 28 | - **components/**: Stateful and stateless components. On the highest level the relevant file is the App.js. It is the one component that is being rendered in the end and that includes all the other components. In general the test and CSS file for a component are saved together with the component in one folder that contains just these files. 29 | Next to the App.js there is a folder in the `components/` folder that contains all the pages of the application. In addition, the `multiPageComponents/` folder contains all components that are used by several pages like the navigation bar that gets imported from each page. 30 | In the pages folder, a subfolder is created for each page. the following folder structure is done logically hierarchically according to the order of imports. Also some functionalities of React components are outsourced to keep the pure `.jsx` files as small as possible. 31 | - **layout/**: Contains our corporate design and customized theme. 32 | - **resources/**: Contains static files like our `empty bpmn`. 33 | - **utils/**: Contains the following subfolders: 34 | - **parser/**: Contains our three parsers, which are stored in the frontend. Each parser has its own subfolder that also contains its tests 35 | - **sessionStorage/**: Contains all the helper files we need to interact with the session storage 36 | - **socket/**: Contains the socket connection file 37 | 38 | # Naming conventions 39 | 40 | - All components are saved as .jsx files (including index.jsx and App.jsx) 41 | - All non-component JavaScript files are saved as .js 42 | - Component names are written in _PascalCase_ 43 | - Non-component JavaScript files are written in _camelCase_ 44 | - The folders that directly wrap the component, and it's test and CSS files are also written in _PascalCase_ and have the same name as the wrapped component. 45 | -------------------------------------------------------------------------------- /wiki/Github-Workflows.md: -------------------------------------------------------------------------------- 1 | # Github Workflows 2 | 3 | To ensure quality, this repo is equipped with a variety of workflows. 4 | Because this repository combines the frontend, as well as the backend, there are testing and linting workflows, which will be the same for both subdirectories. 5 | 6 | ## Testing 7 | 8 | These workflows will run a matrix test on the node versions 12.x and 14.x, as well as the latests versions of macOS, Windows and Ubuntu. 9 | The workflows will checkout the repository, and run `npm ci`, as well as `npm test` in the client/server directory. 10 | 11 | Files: 12 | 13 | - frontendTesting.yml (Frontend testing) 14 | - backendTesting.yml (Backend testing) 15 | 16 | Runs on: 17 | 18 | - pushes to the `DEV` branch 19 | - PRs to the `DEV` branch 20 | - on workflow dispatch event (manual trigger) 21 | 22 | ## Linting 23 | 24 | The workflows will checkout the repository, initialize node on version 14, and run `npm ci`, as well as `npm run lint` in the client/server directory. 25 | 26 | Files: 27 | 28 | - linterFrontend.yml (Lint Frontend Code Base) 29 | - linterServer.yml (Lint Server Code Base) 30 | 31 | Runs on: 32 | 33 | - all pushes except to the `main` branch 34 | - all PRs except to the `main` branch 35 | 36 | ## Code Documentation 37 | 38 | The workflows will checkout the repository, navigate to the client directory, which contains the required command for documentation generation and will generate this by calling `cd ./client/ && npm install --save-dev better-docs jsdoc && npm run docs`. 39 | The resulting directory of `/documentationWebsite` will be deployed to Github Pages by an external Github action. 40 | 41 | Files: 42 | 43 | - publishDocumentation.yml (Publish Documentation to GitHub pages) 44 | 45 | Runs on: 46 | 47 | - pushes to the `main` branch 48 | 49 | ## Repository Wiki Sync 50 | 51 | The workflows will checkout the repository, and will use an external Github Action to deploy the `/wiki` directory to the repositories wiki. For this fictional data (username: Wiki Warden) is used for the commit. 52 | 53 | Files: 54 | 55 | - wikiPageSync.yml (Wiki Update) 56 | 57 | Runs on: 58 | 59 | - pushes to the `DEV` branch 60 | 61 | ## Code Deployment 62 | 63 | The workflows will checkout the repository and navigate to the `client` directory. There it will build the frontend, which results in a directory called `build`, which will then be moved to the top-level of the repository. Additionally the `client` directory is deleted and all content from the `server` directory is moved to the toplevel of the repository. 64 | From there on an external Github Action is used to authenticate to the Heroku CLI with the `HEROKU_API_KEY` and `HEROKU_EMAIL` secrets set in the repository secrets. The Heroku application name is set to `ark-automate` by default in the workflow. 65 | Finally, the deployment is being completed by commiting the changes and pushing to the heroku remote. 66 | 67 | Files: 68 | 69 | - herokuDeploy.yml (Heroku Deployment) 70 | 71 | Runs on: 72 | 73 | - pushes to the `main` branch 74 | - on workflow dispatch event (manual trigger) 75 | -------------------------------------------------------------------------------- /wiki/Home.md: -------------------------------------------------------------------------------- 1 | # Welcome to the Ark Automate wiki! 2 | 3 | Ark Automate is an open-source RPA tool that uses several modeling languages to create bots. 4 | 5 | In our Wiki you can find the following: 6 | 7 | - our [vision](https://github.com/bptlab/ark_automate/wiki/Vision-for-Ark-automate) for this project including a pitch, the architecture and the user interaction 8 | - our [code documentation](https://bptlab.github.io/ark_automate/) hostet on GitHub Pages grouped by modules and classes 9 | - documentation regarding [the structure](https://github.com/bptlab/ark_automate/wiki/Documentation-Folder-Structure) of this project 10 | - documentation regarding [our single source of truth](https://github.com/bptlab/ark_automate/wiki/Documentation-Single-Source-of-Truth) 11 | - documentation the [corporate identity of Ark Automate](https://github.com/bptlab/ark_automate/wiki/Documentation-Corporate-Identity) 12 | 13 | ## API documentation 14 | 15 | The OpenApi documentation of the offered API can be found when fetching the `/docs` route from the running backend, which is up to date with everything in the Code, as it is generated at runtime. 16 | WHen running a local version of the software you can access the documentation using `localhost:5000/docs` 17 | 18 | ## How to contribute to the wiki 19 | 20 | To contribute a page to the wiki, please create a markdown file in the `/wiki` directory or edit an existing one. It will automatically be deployed as a wiki page once it is pushed onto the DEV branch. 21 | The page title will have the same name as the filename of your created file and will show hyphens as empty spaces. Therefore, we ask you to not use empty spaces in your filenames. 22 | 23 | > The deployment of wiki pages will overwrite the existing pages, so if you want to persist your article in the wiki, please go the route of creating a markdown file. 24 | -------------------------------------------------------------------------------- /wiki/How-To-Use-CSS.md: -------------------------------------------------------------------------------- 1 | # How to apply CSS in this Project 2 | 3 | ## Use of styles via CSS 4 | 5 | In principle, before each use of CSS should be considered whether the use in the planned place is absolutely necessary. Special caution with: 6 | 7 | - **Changes of colors, font sizes, and fonts:** Should be urgently avoided since we always refer to the properties defined in the theme. 8 | - **Add spacing (padding):** Should be urgently avoided, as AntD's Space component should be used for this. 9 | 10 | ### Basic rules for styling: 11 | 12 |
See the rules 13 |

14 | 15 | - we do **just use inline CSS with AntD components for 1-2 properties** -> all CSS code with more than two properties is outsourced to external files. 16 | - **global CSS properties** (which cannot be specified in the theme) are only written to `Index.css` to prevent several sources of global style 17 | - **local CSS properties** are written to a file next to the component where they occur and CSS modules are used for this purpose 18 | - if **multiple components** need the **same customization**, the CSS property should be set in a CSS modules file next to the common parent component 19 | 20 |

21 | 22 | ### CSS vs. CSS modules 23 | 24 |
When to use what? 25 |

26 | 27 | In React the style of "normal" CSS files like _Example.css_ are defined globally. Therefore you don't need to explicitly import the CSS file to use the style. Thus be very careful when using normal CSS files and keep in mind that the style you define can be used in any file of the repository. 28 | For example when you define the following style... 29 | 30 | ```css 31 | .button { 32 | background-color: red; 33 | } 34 | ``` 35 | 36 | ... this might lead to confusion because whenever someone uses the class button now this style is applied no matter if it was intended or not. 37 | 38 | If you want to apply style just to specific files and not globally react has a solution called CSS modules. Instead of creating a file _Example.css_ you have to create _Example.module.css_. This file you have to explicitly import in every file you want to use it in. For example like this: 39 | 40 | ```jsx 41 | import styles from './Example.module.css'; 42 | ``` 43 | 44 | Now let's continue with this example. Let's say in _Example.module.css_ we have defined the following because we just want the buttons of this file to be green: 45 | 46 | ```css 47 | .button { 48 | background-color: green; 49 | } 50 | ``` 51 | 52 | In the file we would include the style in the following way: 53 | 54 | ```jsx 55 | 56 | ``` 57 | 58 |

59 | 60 | ### Conventions 61 | 62 |
We agreed on 63 |

64 | 65 | - naming: 66 | For the naming of classes and ids please use **hyphens** consistently. 67 | For example, don't call the class `buttonBackground` and instead call it `button-background`. 68 | - sizing: 69 | Try to use only relative units (vw,vh,rem,%) to size elements and **not** absolut units (px) 70 | 71 |

72 | -------------------------------------------------------------------------------- /wiki/How-To-Write-Code-Documentation.md: -------------------------------------------------------------------------------- 1 | ## General 2 | 3 | We are using [JsDoc](https://jsdoc.app) with an [additional plugin](https://github.com/SoftwareBrothers/better-docs), which allows us to also tag components in React. 4 | 5 | Documentation will be generated as a website under which the individual parts of the software (server/frontend) are visible and listed with their respective classes and modules. 6 | It should be noted here, that although components are supported as a separate tag, they are in the current version still listed under the _Classes_ part of the documentation. 7 | 8 | ## How to write documentation 9 | 10 | ### React Components 11 | 12 | As mentioned above, React components are supported through a plugin and are currently then still listed under the Classes section. 13 | For components please try to use the following style (taken from the [plugins repo](https://github.com/SoftwareBrothers/better-docs#preview)): 14 | 15 | ``` 16 | /** 17 | * @description Some documented component 18 | * @category Frontend/Server 19 | * @component 20 | * @example 21 | * const text = 'some example text' 22 | * return ( ) 23 | */ 24 | ``` 25 | 26 | The attributes are: 27 | 28 | - description: Describe the component, its use case and/or where it could be used 29 | - category: Either _Frontend_ or _Server_, based on where it is used (for React components this should most likely always be _Frontend_) 30 | - component: Tag to specify this as a component 31 | - example: Code which describes a possible use scenario for this component 32 | 33 | ### Classes 34 | 35 | To document classes, please follow the following scheme in front of the constructor method: 36 | 37 | ``` 38 | /** 39 | * @description This is a description of the MyClass constructor function. 40 | * @category Frontend/Server 41 | * @class 42 | * @classdesc This is a description of the MyClass class. 43 | */ 44 | ``` 45 | 46 | The attributes are: 47 | 48 | - description: Describe the constructor function 49 | - category: Either _Frontend_ or _Server_, based on where it is used 50 | - class: Tag to specify this as a class constructor 51 | - classdesc: Describe the functionality and behavior of the class 52 | 53 | ### Functions/Methods 54 | 55 | When grouping related functions loosely in a file because of the same context, please use the following snippet at the very beginning of the file to group all functions to that same module. For classes and components, this is done automatically and therefore a specification of the module is not needed there 56 | 57 | ``` 58 | /** 59 | * @description This is the description of what the function does 60 | * @param {string} arg1 - A nice argument 61 | * @param {*} arg2 - A fancy second argument 62 | * @returns {number} number of interest which is returned 63 | */ 64 | ``` 65 | 66 | The attributes are: 67 | 68 | - description: Describe the functionality and/or behavior of the function/method 69 | - param {datatype}: Specify the different input parameters this function/method accepts by using multiple of these tags. Specify the datatype expected or specify that any input is allowed by using \*. Specify the name of the parameter and separated from that name specify what this parameter should represent. 70 | - returns {datatype}: Specify the datatype returned by the function and what that value represents 71 | 72 | #### Group as module 73 | 74 | When grouping related functions loosely in a file because of the same context, please use the following snippet at the very beginning of the file to group all functions to that same module. For classes and components, this is done automatically and therefore a specification of the module is not needed there 75 | 76 | ``` 77 | /** 78 | * @category Frontend/Server 79 | * @module optionalString 80 | */ 81 | ``` 82 | 83 | The attributes are: 84 | 85 | - category: Either _Frontend_ or _Server_, based on where it is used 86 | - module: Specify this file as a module. In the documentation, this module will receive the name of the relative filePath to the root or the specified (but optional) String passed in as a name. 87 | -------------------------------------------------------------------------------- /wiki/Testing-Conventions.md: -------------------------------------------------------------------------------- 1 | # Testing Conventions 2 | 3 | ## What to test? 4 | 5 | ### Do 6 | 7 | - Utility methods 8 | - Complex implementations (eg: algorithms) 9 | - Anything that has edge cases (excl. frontend-interaction) 10 | - Core business logic 11 | - High-risk Services 12 | - Common user interactions 13 | - Error cases that you expect to happen frequently (such as entering an incorrect password when logging in) 14 | - Interactions between units that were stubbed in the unit tests 15 | 16 | ### Don't 17 | 18 | - JavaScript and NodeJS core functions 19 | - Third-party libraries 20 | - External applications 21 | - API calls. Stub them unless you’re doing E2E testing 22 | - Database operations, unless you’re doing E2E testing 23 | - Trivial methods not involving logic (simple getters and setters) 24 | - Code that is throw-away or placeholder code 25 | -------------------------------------------------------------------------------- /wiki/Vision-for-Ark-Automate.md: -------------------------------------------------------------------------------- 1 | # Pitch 2 |
What can be achieved with our software at the end of the project? 3 |

4 | 5 | **Customer view** 6 | > Using Ark Automate users can automate business or everyday processes by simply sketching the steps of their process. By using simple flowcharts or powerful BPMN in their process outlines, users can create small software solutions using RPA that finish their tasks much faster and more reliably. 7 | 8 | 9 | **Technical view** 10 | > Using Ark Automate users can build their own digital coworkers by visualizing business or everyday processes and automating these using robotic process automation (RPA). The digital coworkers request files or help whilst working on their own tasks which have been taught to them through the multiple modeling notations available. 11 |

12 | 13 | If you are interested in our 5-year vision, please contact us to get access to the file. 14 | Our vision with architecture and limits is stored on [HackMD](https://hackmd.io/@toUukITjSM6oWi52UMDSkA/Bk4kOnoqw). 15 | 16 | # Current Architecture 17 | 18 | ![Current Architecture](https://user-images.githubusercontent.com/36270527/120189142-4144db00-c217-11eb-921e-1f9954666944.png) 19 | 20 | 21 | The **main architectural benefit** will be the modularity and interchangeability of the single components within the system. 22 | As the main platform will be created as a single web application, all system components are accessible through a **single browser**. 23 | 24 | ### Describing the interaction and the components 25 |
More detailed description

26 | 27 | 28 | That way a **Low-Code RPA-Developer** can build new robots in the `Web-Browser` using the `Modelling Interface` with the mentioned multiple modeling tools. 29 | 30 | About our **Database-Structure:** 31 | - The `Robot Repository` is where all created robots are stored and are available for the users to retrieve and make changes. 32 | - The `RPA Activity Storage` stores all activities that can be automated with our software. 33 | - The `User Data Storage` stores all the user's data, such as login details, personal settings etc., so that the user can work with the same status of the software on any device. 34 | - The `Robot Job Storage` stores the execution information for each bot like status, user and error messages. 35 | 36 | **Customers** can start the robots via the `Control Interface` in their `Web Browser`. There they can also view basic statistics about the individual robots. (Currently not implemented) This interface also allows the **RPA developers** to execute the robots, since they also have all the permissions of the end users. 37 | 38 | In addition, an `API` is provided to the robots. External companies can use this to start robots in our system. Also, in the future, our robots could be controlled via this `API` through control and the IoT, for example. 39 | 40 | To start robots or to get further information about executed robots, there is a communication with the `Robot Coordinator Backend`. 41 | 42 | The `Robot Coordinator Backend` interacts with the `Local Client` and launches the bots on the local machine. In addition, the backend gets all the information it needs from the database. 43 | 44 | 45 |

46 | 47 | # Using Ark Automate 48 | Here we state how Ark Automate is supposed to be used and which individual steps happen (on an abstract layer). 49 | 50 | ### User interaction with our software: Use Case Diagram 51 |
See our Use Case Diagram

52 | 53 | ![Use Case Diagram](https://i.imgur.com/FakYLAh.png) 54 | 55 |

56 | 57 | ### Which steps happen in the software? 58 |
See list

59 | 60 | - Displaying of modeling interfaces 61 | - Getting the available RPA tasks and apps from the server 62 | - Providing an interface to expand activities with RPA apps, tasks, and properties 63 | - Parsing BPMN to SSOT (and back) 64 | - Parsing other modeling notation (flowchart f.e.) to SSOT (and back) 65 | - Parsing SSOT to .robot (and back) 66 | 67 |

68 | -------------------------------------------------------------------------------- /wiki/_Sidebar.md: -------------------------------------------------------------------------------- 1 | Back to the [Wiki Home](https://github.com/bptlab/ark_automate/wiki) 2 | 3 | - **About Ark Automate** 4 | 5 | - [Pitch](https://github.com/bptlab/ark_automate/wiki/Vision-for-Ark-automate#Pitch) 6 | - [Architecture](https://github.com/bptlab/ark_automate/wiki/Vision-for-Ark-automate#current-architecture) 7 | - [Interaction with our Software](https://github.com/bptlab/ark_automate/wiki/Vision-for-Ark-automate#using-ark-automate) until the summer of 2021 8 | 9 | - **Documentation** 10 | - [Repository Structure](https://github.com/bptlab/ark_automate/wiki/Documentation-Folder-structure) 11 | - [Code Documentation](https://bptlab.github.io/ark_automate/) 12 | - [Github Workflows](https://github.com/bptlab/ark_automate/wiki/Github-Workflows) 13 | - [Coding Standards](https://github.com/bptlab/ark_automate/wiki/Coding-standards) 14 | - [How To Style Components](https://github.com/bptlab/ark_automate/wiki/How-To-Use-CSS) 15 | - [Corporate Identity](https://github.com/bptlab/ark_automate/wiki/Documentation-Corporate-Identity) 16 | - [Concept Behind the Single Source of Truth](https://github.com/bptlab/ark_automate/wiki/Documentation-single-source-of-truth) 17 | - [Database Communication](https://github.com/bptlab/ark_automate/wiki/Database-and-Communication) 18 | - [Communication with Local Client](https://github.com/bptlab/ark_automate/wiki/Documentation-Communication-Local-Client) 19 | - [Testing Conventions](https://github.com/bptlab/ark_automate/wiki/Testing-Conventions) 20 | -------------------------------------------------------------------------------- /wiki/images/Tutorial_Bot-Cockpit-Completed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/wiki/images/Tutorial_Bot-Cockpit-Completed.png -------------------------------------------------------------------------------- /wiki/images/Tutorial_Bot-Cockpit-Configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/wiki/images/Tutorial_Bot-Cockpit-Configuration.png -------------------------------------------------------------------------------- /wiki/images/Tutorial_Bot-Modeler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/wiki/images/Tutorial_Bot-Modeler.png -------------------------------------------------------------------------------- /wiki/images/Tutorial_Bot-Overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bptlab/ark_automate/5b9a2f38cf2aebe56811fdb53682ac275b3b6c7d/wiki/images/Tutorial_Bot-Overview.png --------------------------------------------------------------------------------