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 |  color-primary: #00C2FF
6 |
7 |  color-primary-inverted: #1C272B
8 |
9 |  color-primary-inverted-2: #2F3C41
10 |
11 |  color-primary-inverted-text: #FFFFFF
12 |
13 |  color-background: #EFEFEF
14 |
15 |  color-background-2: #FFFFFF
16 |
17 |  color-background-text: #1D1D1F
18 |
19 |  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 | 
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
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
--------------------------------------------------------------------------------