├── src ├── index.ts └── botcity │ ├── index.ts │ └── maestro │ ├── interfaces │ ├── attachment.ts │ ├── index.ts │ ├── alert.ts │ ├── common.ts │ ├── artifact.ts │ ├── task.ts │ └── log.ts │ ├── index.ts │ ├── columns.ts │ ├── utils.ts │ └── sdk.ts ├── .gitignore ├── tests ├── screenshot.png └── integration │ ├── credential.test.ts │ ├── error.test.ts │ ├── artifact.test.ts │ ├── login.test.ts │ ├── alert.test.ts │ ├── log.test.ts │ └── task.test.ts ├── jest.config.js ├── .prettierrc ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── feature-request.md │ └── bug-report.md ├── workflows │ ├── pull_request.yaml │ └── publish.yaml ├── SUPPORT.md └── CONTRIBUTING.md ├── CHANGELOG.md ├── tsconfig.json ├── package.json ├── README.md └── LICENSE /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './botcity' 2 | -------------------------------------------------------------------------------- /src/botcity/index.ts: -------------------------------------------------------------------------------- 1 | export * from './maestro' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | .env -------------------------------------------------------------------------------- /src/botcity/maestro/interfaces/attachment.ts: -------------------------------------------------------------------------------- 1 | export interface Attachment { 2 | filepath: string 3 | } 4 | -------------------------------------------------------------------------------- /src/botcity/maestro/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sdk' 2 | export * from './utils' 3 | export * from './columns' 4 | -------------------------------------------------------------------------------- /tests/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botcity-dev/botcity-maestro-sdk-js/HEAD/tests/screenshot.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /src/botcity/maestro/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './task' 2 | export * from './log' 3 | export * from './alert' 4 | export * from './artifact' 5 | export * from './common' 6 | export * from './attachment' 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 2, 4 | "printWidth": 100, 5 | "singleQuote": true, 6 | "trailingComma": "all", 7 | "jsxSingleQuote": true, 8 | "bracketSpacing": true, 9 | "arrowParens": "always" 10 | } -------------------------------------------------------------------------------- /src/botcity/maestro/interfaces/alert.ts: -------------------------------------------------------------------------------- 1 | export interface Alert { 2 | id: string 3 | organizationId: string 4 | taskId: number 5 | activityName: string 6 | botId?: string 7 | title: string 8 | message: string 9 | type: string 10 | date: string 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": "standard-with-typescript", 7 | "overrides": [ 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": "latest", 11 | "sourceType": "module", 12 | "project": "tsconfig.json" 13 | }, 14 | "rules": { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/botcity/maestro/interfaces/common.ts: -------------------------------------------------------------------------------- 1 | export interface Sort { 2 | sorted: boolean 3 | unsorted: boolean 4 | empty: boolean 5 | } 6 | 7 | export interface Pageable { 8 | sort: Sort 9 | pageNumber: number 10 | pageSize: number 11 | offset: number 12 | paged: boolean 13 | unpaged: boolean 14 | } 15 | 16 | export interface IColumn { 17 | name: string 18 | label: string 19 | width: number 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature. 4 | 5 | --- 6 | 7 | **What's the problem this feature will solve?** 8 | 9 | 10 | **Describe the solution you'd like** 11 | 12 | 13 | **Additional context** 14 | -------------------------------------------------------------------------------- /src/botcity/maestro/columns.ts: -------------------------------------------------------------------------------- 1 | import { IColumn } from './interfaces' 2 | 3 | export class Column implements IColumn { 4 | name: string 5 | label: string 6 | width: number 7 | object: { 8 | name: string 9 | label: string 10 | width: number 11 | } 12 | 13 | constructor (name: string, label: string, width: number) { 14 | this.name = name 15 | this.label = label 16 | this.width = width 17 | this.object = { name, label, width } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | [//]: # (s-0.1.3) 3 | 4 | # [0.1.3] - (2022-09-05) 5 | 6 | ## Misc 7 | * Add news-fragments and release-it in project. 8 | * Update version in package json to 0.1.2. 9 | 10 | [//]: # (e-0.1.3) 11 | 12 | 13 | [//]: # (s-0.1.4) 14 | 15 | # [0.1.4] - (2022-09-05) 16 | 17 | [//]: # (e-0.1.4) 18 | 19 | 20 | [//]: # (s-0.1.3) 21 | 22 | # [0.1.3] - (2022-09-05) 23 | 24 | ## Misc 25 | * Add news-fragments and release-it in project. 26 | * Update version in package json to 0.1.2. 27 | 28 | [//]: # (e-0.1.3) 29 | 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "outDir": "./dist", 6 | "lib": [ "es2015", "dom" ], 7 | "declaration": true, 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "esModuleInterop": true, 14 | "experimentalDecorators": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules", "**/*.test.ts"] 18 | } -------------------------------------------------------------------------------- /.github/workflows/pull_request.yaml: -------------------------------------------------------------------------------- 1 | name: Pipeline in pull request 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | run: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: Setup Node 12 | uses: actions/setup-node@v2 13 | - name: Install dependencies 14 | run: npm install 15 | - name: Run eslint 16 | run: npm run lint 17 | - name: Run tests 18 | run: npm run test 19 | env: 20 | BOTCITY_SERVER: ${{ secrets.BOTCITY_SERVER }} 21 | BOTCITY_LOGIN: ${{ secrets.BOTCITY_LOGIN }} 22 | BOTCITY_KEY: ${{ secrets.BOTCITY_KEY }} 23 | - name: Execute build 🔧 24 | run: npm run build -------------------------------------------------------------------------------- /src/botcity/maestro/interfaces/artifact.ts: -------------------------------------------------------------------------------- 1 | import { Pageable, Sort } from './common' 2 | 3 | interface Content { 4 | id: number 5 | type: string 6 | taskId: number 7 | name: string 8 | fileName: string 9 | organizationId: number 10 | dateCreation: string 11 | taskName: string 12 | } 13 | 14 | export interface Artifact { 15 | id: number 16 | type: string 17 | taskId: number 18 | name: string 19 | filename?: string 20 | organizationId: number 21 | dateCreation?: string 22 | userId?: number 23 | taskName?: string 24 | } 25 | 26 | export interface Artifacts { 27 | content: Content[] 28 | pageable: Pageable 29 | last: boolean 30 | totalPages: number 31 | totalElements: number 32 | sort: Sort 33 | first: boolean 34 | numberOfElements: number 35 | size: number 36 | number: number 37 | empty: boolean 38 | } 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Let us know if something is broken. 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | 9 | 10 | **Expected behavior** 11 | 12 | 13 | **Steps to Reproduce** 14 | 15 | 16 | **Possible Solution** 17 | 21 | 22 | **My Platform** 23 | 29 | 30 | **Additional context** 31 | -------------------------------------------------------------------------------- /src/botcity/maestro/interfaces/task.ts: -------------------------------------------------------------------------------- 1 | export interface Task { 2 | id: number 3 | state: string 4 | parameters: object 5 | activityId: number 6 | userEmail: string | null 7 | agentId: string 8 | userCreationName: string 9 | organizationLabel: string | null 10 | organizationCreationId: number 11 | dateCreation: string // TODO: Transform in date 12 | dateLastModified: string // TODO: Transform in date 13 | finishStatus?: string 14 | finishMessage?: string 15 | test: boolean 16 | interrupted: boolean | null 17 | machineId: string | number | null 18 | activityLabel?: string 19 | minExecutionDate: string | Date 20 | killed: boolean | null 21 | dateStartRunning: string | Date 22 | priority: number | null 23 | repositoryLabel: string 24 | processedItems: number | null 25 | failedItems: number | null 26 | totalItems: number | null 27 | } 28 | 29 | // TODO: Implement endpoint getTasks 30 | -------------------------------------------------------------------------------- /src/botcity/maestro/interfaces/log.ts: -------------------------------------------------------------------------------- 1 | // TODO: Change in next version 2 | 3 | import { Column } from '../columns' 4 | 5 | // TODO: Verify in next version 6 | export interface Logs { 7 | id: number 8 | label: string // TODO 9 | name: string 10 | icon: string // TODO: Search type 11 | description: string 12 | agentId: string // TODO: Search type 13 | organizationLabel: string 14 | humanTimePerItem: number 15 | parameters: Object[] 16 | machines: string[] 17 | cron: string // TODO: Transform in date? 18 | scheduleStrategy: string 19 | technology: string 20 | notification: Object[] // TODO: Searh type 21 | status?: string 22 | user?: string[] 23 | notificationType: string[] 24 | } 25 | 26 | export interface Log { 27 | id: string 28 | organizationLabel: string 29 | activityLabel: string 30 | columns: Column[] 31 | } 32 | 33 | export interface DataLog { 34 | columns: Column[] 35 | dateCreation: string 36 | } 37 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | ### Getting Started 2 | 3 | ### Bug reports & Feature request 4 | 5 | If you spot a problem, please let us know by following the template in 6 | here: [Report a bug](https://github.com/botcity-dev/botcity-maestro-sdk-python/issues/new?template=bug-report.md). 7 | 8 | Ideas or suggestions for enhancements are more than welcome. Please use the following 9 | template in here: [Request feature](https://github.com/botcity-dev/botcity-maestro-sdk-python/issues/new?template=feature-request.md). 10 | 11 | ### Contact us 12 | 13 | If you have questions of comments in general about the framework core we want to know. 14 | 15 | You can choose between the channels open for communication the one that best fit you: 16 | 17 | - [BotCity Community]() (Public) 18 | 19 | or you can [file a bug](https://github.com/botcity-dev/botcity-maestro-sdk-python/issues/new?template=bug-report.md) and let us know where our documentation could be improved. -------------------------------------------------------------------------------- /tests/integration/credential.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv" 2 | import { BotMaestroSdk } from "../../src/botcity"; 3 | import { v4 as uuid4} from 'uuid'; 4 | 5 | dotenv.config() 6 | 7 | const SERVER = process.env.BOTCITY_SERVER || "" 8 | const LOGIN = process.env.BOTCITY_LOGIN || "" 9 | const KEY = process.env.BOTCITY_KEY || "" 10 | 11 | let sdk: BotMaestroSdk; 12 | let credentialLabel = `testing-${uuid4()}` 13 | let credentialKey = `testing-${uuid4()}` 14 | 15 | describe("Testing Credential cases", () => { 16 | beforeAll(async () => { 17 | sdk = new BotMaestroSdk(SERVER, LOGIN, KEY); 18 | await sdk.login() 19 | }, 10000); 20 | 21 | test("Create Credential", async () => { 22 | await sdk.createCredential(credentialLabel, credentialKey, "testing") 23 | }, 10000) 24 | 25 | test("Get Credential", async () => { 26 | const credential = await sdk.getCredential(credentialLabel, credentialKey) 27 | expect(credential).toEqual("testing") 28 | }, 10000) 29 | }) -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - name: Setup Node 13 | uses: actions/setup-node@v2 14 | with: 15 | node-version: '16.x' 16 | registry-url: 'https://registry.npmjs.org' 17 | - name: Install dependencies 18 | run: npm install 19 | - name: Run eslint 20 | run: npm run lint 21 | - name: Run tests 22 | run: npm run test 23 | env: 24 | BOTCITY_SERVER: ${{ secrets.BOTCITY_SERVER }} 25 | BOTCITY_LOGIN: ${{ secrets.BOTCITY_LOGIN }} 26 | BOTCITY_KEY: ${{ secrets.BOTCITY_KEY }} 27 | - name: Execute build 🔧 28 | run: npm run build 29 | - name: Publish package on NPM 📦 30 | run: npm publish 31 | env: 32 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | -------------------------------------------------------------------------------- /tests/integration/error.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv" 2 | import { BotMaestroSdk } from "../../src/botcity"; 3 | import path from "path"; 4 | import { Task } from "../../src/botcity/maestro/interfaces"; 5 | 6 | dotenv.config() 7 | 8 | const SERVER = process.env.BOTCITY_SERVER || "" 9 | const LOGIN = process.env.BOTCITY_LOGIN || "" 10 | const KEY = process.env.BOTCITY_KEY || "" 11 | 12 | const screenshotFilepath = path.resolve("tests/screenshot.png") 13 | 14 | let sdk: BotMaestroSdk; 15 | let task: Task; 16 | 17 | describe("Testing Error cases", () => { 18 | beforeAll(async () => { 19 | sdk = new BotMaestroSdk(SERVER, LOGIN, KEY); 20 | await sdk.login() 21 | const parameters = { 22 | "test_to_test": "testing", 23 | "integer_to_test": 123, 24 | "double_to_test": 1.0 25 | } 26 | task = await sdk.createTask("TestCI", parameters, false) 27 | }, 10000); 28 | 29 | test("Create Error", async () => { 30 | try { 31 | eval("hoo bar"); 32 | } catch (error: any) { 33 | const attachments: string[] = [screenshotFilepath] 34 | await sdk.createError(task.id, error, {}, screenshotFilepath, attachments) 35 | } 36 | }, 10000) 37 | 38 | }) -------------------------------------------------------------------------------- /tests/integration/artifact.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv" 2 | import { BotMaestroSdk } from "../../src/botcity"; 3 | import tmp from 'tmp' 4 | import { Task } from "../../src/botcity/maestro/interfaces"; 5 | 6 | dotenv.config() 7 | 8 | const SERVER = process.env.BOTCITY_SERVER || "" 9 | const LOGIN = process.env.BOTCITY_LOGIN || "" 10 | const KEY = process.env.BOTCITY_KEY || "" 11 | 12 | let sdk: BotMaestroSdk; 13 | let task: Task; 14 | 15 | describe("Testing Artifact cases", () => { 16 | beforeAll(async () => { 17 | sdk = new BotMaestroSdk(SERVER, LOGIN, KEY); 18 | await sdk.login() 19 | const parameters = { 20 | "test_to_test": "testing", 21 | "integer_to_test": 123, 22 | "double_to_test": 1.0 23 | } 24 | task = await sdk.createTask("TestCI", parameters, false) 25 | }, 10000); 26 | 27 | test("Post Artifact", async () => { 28 | const tmpobj = tmp.fileSync({ mode: 0o644, prefix: 'prefix-', postfix: '.txt' }); 29 | try { 30 | await sdk.uploadArtifact(task.id, "My Artifact", "My artificat", tmpobj.name) 31 | } finally { 32 | tmpobj.removeCallback(); 33 | } 34 | }, 10000) 35 | 36 | test("Get Artifacts", async () => { 37 | const listArtifacts = await sdk.getArtifacts("1", "1", [], "1") 38 | expect(listArtifacts) 39 | }, 10000) 40 | }) -------------------------------------------------------------------------------- /tests/integration/login.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv" 2 | import { BotMaestroSdk } from "../../src/botcity"; 3 | 4 | dotenv.config() 5 | 6 | const SERVER = process.env.BOTCITY_SERVER || "" 7 | const LOGIN = process.env.BOTCITY_LOGIN || "" 8 | const KEY = process.env.BOTCITY_KEY || "" 9 | 10 | let sdk: BotMaestroSdk; 11 | 12 | 13 | describe("Testing logins cases", () => { 14 | beforeAll(async () => { 15 | sdk = new BotMaestroSdk(SERVER, LOGIN, KEY); 16 | }, 10000); 17 | 18 | test("Error server not found", async () => { 19 | const expected = async () => { 20 | await new BotMaestroSdk("", LOGIN, KEY).login() 21 | } 22 | expect(expected()).rejects.toThrow("Server is required.") 23 | }, 10000) 24 | 25 | test("Error in login", async () => { 26 | const expected = async () => { 27 | await new BotMaestroSdk(SERVER, "", KEY).login() 28 | } 29 | expect(expected()).rejects.toThrow("Login is required.") 30 | }, 10000) 31 | 32 | test("Error in key", async () => { 33 | const expected = async () => { 34 | await new BotMaestroSdk(SERVER, LOGIN, "").login() 35 | } 36 | expect(expected()).rejects.toThrow("Key is required.") 37 | }, 10000) 38 | 39 | test("Login Success", async () => { 40 | await sdk.login() 41 | expect(sdk.accessToken).not.toBe("") 42 | expect(sdk.isOnline).toBe(true) 43 | }, 10000) 44 | }) -------------------------------------------------------------------------------- /tests/integration/alert.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv" 2 | import { BotMaestroSdk } from "../../src/botcity"; 3 | import { Task } from "../../src/botcity/maestro/interfaces"; 4 | 5 | dotenv.config() 6 | 7 | const SERVER = process.env.BOTCITY_SERVER || "" 8 | const LOGIN = process.env.BOTCITY_LOGIN || "" 9 | const KEY = process.env.BOTCITY_KEY || "" 10 | 11 | let sdk: BotMaestroSdk; 12 | let task: Task; 13 | 14 | describe("Testing alerts cases", () => { 15 | beforeAll(async () => { 16 | sdk = new BotMaestroSdk(SERVER, LOGIN, KEY); 17 | await sdk.login() 18 | const parameters = { 19 | "test_to_test": "testing", 20 | "integer_to_test": 123, 21 | "double_to_test": 1.0 22 | } 23 | task = await sdk.createTask("TestCI", parameters, false) 24 | }, 10000); 25 | 26 | test("Create Alert Info", async () => { 27 | const alert = await sdk.createAlert(task.id, "Info Warn", "This is an info alert", "INFO") 28 | expect(alert.type).toEqual("INFO") 29 | }, 10000) 30 | 31 | test("Create Alert Warn", async () => { 32 | const alert = await sdk.createAlert(task.id, "Info Warn", "This is an info warn", "WARN") 33 | expect(alert.type).toEqual("WARN") 34 | }, 10000) 35 | 36 | test("Create Alert Error", async () => { 37 | const alert = await sdk.createAlert(task.id, "Info Error", "This is an info error", "ERROR") 38 | expect(alert.type).toEqual("ERROR") 39 | }, 10000) 40 | }) -------------------------------------------------------------------------------- /tests/integration/log.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv" 2 | import { BotMaestroSdk, Column } from "../../src/botcity"; 3 | import { v4 as uuid4} from 'uuid'; 4 | 5 | dotenv.config() 6 | 7 | const SERVER = process.env.BOTCITY_SERVER || "" 8 | const LOGIN = process.env.BOTCITY_LOGIN || "" 9 | const KEY = process.env.BOTCITY_KEY || "" 10 | 11 | let sdk: BotMaestroSdk; 12 | 13 | const activityLabelToLog = `TestCI-${uuid4()}` 14 | 15 | const columns = [ 16 | new Column("Date/Time", "timestamp", 300), 17 | new Column("# Records", "records", 200), 18 | new Column("Status", "status", 100), 19 | ] 20 | 21 | describe("Testing log cases", () => { 22 | beforeAll(async () => { 23 | sdk = new BotMaestroSdk(SERVER, LOGIN, KEY); 24 | await sdk.login() 25 | }, 10000); 26 | 27 | test("Create log", async () => { 28 | const log = await sdk.createLog(activityLabelToLog, columns) 29 | expect(log.activityLabel).toEqual(activityLabelToLog) 30 | }, 10000) 31 | 32 | test("Create new log entry", async () => { 33 | await sdk.logEntry(activityLabelToLog, { 34 | "timestamp": new Date().toISOString(), 35 | "records": 10, 36 | "status": "SUCCESS" 37 | , }) 38 | }, 10000) 39 | 40 | test("Get log", async () => { 41 | const log = await sdk.getLog(activityLabelToLog) 42 | console.log({ log }) 43 | expect(log.id).toBe(`botcity-${activityLabelToLog}`) 44 | }, 10000) 45 | 46 | test("Delete log", async () => { 47 | await sdk.deleteLog(activityLabelToLog) 48 | }, 10000) 49 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@botcity/botcity-maestro-sdk", 3 | "version": "1.2.3", 4 | "description": "Botcity Maestro SDK", 5 | "author": "botcity", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "scripts": { 9 | "clean": "rimraf dist", 10 | "prebuild": "npm run clean", 11 | "build": "tsc", 12 | "release": "release-it", 13 | "lint": "eslint src/**/*.{js,ts,json}", 14 | "lint:fix": "eslint --fix src/**/*.{js,ts,json}", 15 | "format": "prettier --write src/**/*.{js,ts,md,json} --config ./.prettierrc", 16 | "commit": "git-cz", 17 | "prepare": "husky install", 18 | "test": "jest" 19 | }, 20 | "release-it": { 21 | "npm": { 22 | "publish": false 23 | }, 24 | "github": { 25 | "release": true 26 | }, 27 | "git": { 28 | "commitMessage": "chore: release ${version}" 29 | } 30 | }, 31 | "contributors": [ 32 | { 33 | "name": "Kayque Govetri", 34 | "email": "kayque.govetri@botcity.dev", 35 | "url": "https://github.com/kayqueGovetri" 36 | } 37 | ], 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/botcity-dev/botcity-maestro-sdk-js" 41 | }, 42 | "homepage": "https://github.com/botcity-dev/botcity-maestro-sdk-js#readme", 43 | "license": "Apache License 2.0", 44 | "devDependencies": { 45 | "@commitlint/cli": "17.1.2", 46 | "@commitlint/config-conventional": "17.1.0", 47 | "@rocketseat/eslint-config": "1.1.3", 48 | "@types/jest": "^29.5.12", 49 | "@types/node": "^14.18.33", 50 | "@types/tmp": "^0.2.6", 51 | "@types/uuid": "^9.0.8", 52 | "@typescript-eslint/eslint-plugin": "^5.36.2", 53 | "@typescript-eslint/parser": "5.36.2", 54 | "commitizen": "4.2.5", 55 | "cz-conventional-changelog": "3.3.0", 56 | "eslint": "^8.23.0", 57 | "eslint-config-prettier": "8.5.0", 58 | "eslint-config-standard-with-typescript": "^22.0.0", 59 | "eslint-import-resolver-typescript": "3.5.0", 60 | "eslint-plugin-import": "^2.26.0", 61 | "eslint-plugin-n": "^15.2.5", 62 | "eslint-plugin-prettier": "4.2.1", 63 | "eslint-plugin-promise": "^6.0.1", 64 | "husky": "8.0.1", 65 | "jest": "^29.7.0", 66 | "moment": "^2.30.1", 67 | "prettier": "2.7.1", 68 | "release-it": "^15.4.1", 69 | "rimraf": "^3.0.2", 70 | "tmp": "^0.2.3", 71 | "ts-jest": "^29.1.2", 72 | "typescript": "^4.8.2", 73 | "uuid": "^9.0.1" 74 | }, 75 | "dependencies": { 76 | "axios": "^1.6.0", 77 | "dotenv": "^16.4.5", 78 | "form-data": "^4.0.0" 79 | }, 80 | "engines": { 81 | "node": ">= 16.0.0", 82 | "npm": ">= 3.0.0" 83 | }, 84 | "keywords": [ 85 | "botcity", 86 | "automation", 87 | "automation desktop", 88 | "sdk" 89 | ], 90 | "config": { 91 | "commitizen": { 92 | "path": "./node_modules/cz-conventional-changelog" 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/botcity/maestro/utils.ts: -------------------------------------------------------------------------------- 1 | import { BotMaestroSdk } from './sdk' 2 | import os from 'os' 3 | import process from 'node:process' 4 | import util from 'util' 5 | import childProcess from 'child_process' 6 | import fs from 'fs/promises' 7 | import path from 'path' 8 | 9 | const exec = util.promisify(childProcess.exec) 10 | 11 | export const ensureAccessToken = ( 12 | _target: Object, 13 | _propertyKey: string, 14 | descriptor: PropertyDescriptor 15 | ): PropertyDescriptor => { 16 | const originalMethod = descriptor.value 17 | descriptor.value = function (this: BotMaestroSdk, ...args: any) { 18 | if (this.accessToken === '') { 19 | throw new Error('Access Token not available. Make sure to invoke login first') 20 | } 21 | const result = originalMethod.apply(this, args) 22 | return result 23 | } 24 | 25 | return descriptor 26 | } 27 | 28 | export const catchError = ( 29 | _target: Object, 30 | _propertyKey: string, 31 | descriptor: PropertyDescriptor 32 | ): any => { 33 | const method = descriptor.value 34 | descriptor.value = function (this: BotMaestroSdk, ...args: any) { 35 | try { 36 | return method.apply(this, args) 37 | } catch (error: any) { 38 | let message = 'unknown error' 39 | if (error instanceof Error) message = error.message 40 | throw new Error(`Error during message. Server returned: ${message}`) 41 | } 42 | } 43 | } 44 | 45 | export const getStackInError = (error: Error): string => { 46 | if (error.stack !== undefined) return error.stack 47 | return '' 48 | } 49 | 50 | export const getTypeInError = (error: Error): string => { 51 | if (error.constructor.name !== undefined) return error.constructor.name 52 | return '' 53 | } 54 | 55 | export const getMessageInError = (error: Error): string => { 56 | if (error.message !== undefined) return error.message 57 | return '' 58 | } 59 | 60 | export const getDefaultTags = async (tags: any): Promise => { 61 | const userInfo = os.userInfo() 62 | tags.user_name = userInfo.username 63 | tags.host_name = os.hostname() 64 | tags.os_name = os.platform() 65 | tags.os_version = os.release() 66 | tags.node_version = process.version 67 | return tags 68 | } 69 | 70 | const npmls = async (): Promise => { 71 | try { 72 | const { stdout } = await exec('npm ls --json') 73 | const dependencies = JSON.parse(stdout).dependencies 74 | const response = getVersionsToDependencies(dependencies) 75 | return response 76 | } catch { 77 | return {} 78 | } 79 | } 80 | 81 | const getVersionsToDependencies = (dependencies: any): any => { 82 | const response: any = {} 83 | for (const dependency in dependencies) { 84 | response[dependency] = dependencies[dependency].version 85 | } 86 | return response 87 | } 88 | 89 | export const createNpmList = async (): Promise => { 90 | const filepath = tmpFile('npmlist.txt') 91 | const npmList = await npmls() 92 | await fs.writeFile(filepath, `${JSON.stringify(npmList)}`) 93 | return filepath 94 | } 95 | 96 | const tmpFile = (filename: string): string => { 97 | return path.join(os.tmpdir(), filename).toString() 98 | } 99 | 100 | export const verifyUrlServer = (server: string): string => { 101 | if (server !== '' && server.slice(-1) === '/') { 102 | server = server.slice(0, -1) 103 | } 104 | return server 105 | } 106 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Getting Started 4 | 5 | * Make sure you have a [GitHub account](https://github.com/signup/free) 6 | * Submit a ticket for your issue, assuming one does not already exist. 7 | * Clearly describe the issue including steps to reproduce when it is a bug. 8 | * Make sure you fill in the earliest version that you know has the issue. 9 | * Fork the repository on GitHub 10 | 11 | 12 | ## Making Changes 13 | 14 | * Create a topic branch from where you want to base your work. 15 | * This is usually the master branch. 16 | * Only target release branches if you are certain your fix must be on that 17 | branch. 18 | * To quickly create a topic branch based on master; `git checkout -b 19 | fix/master/my_contribution master`. Please avoid working directly on the 20 | `master` branch. 21 | * Make commits of logical units. 22 | * Check for unnecessary whitespace with `git diff --check` before committing. 23 | * Make sure your commit messages are in the proper format (see below) 24 | * Make sure you have added the necessary tests for your changes. 25 | * Run _all_ the tests to assure nothing else was accidentally broken. 26 | 27 | ### Writing the commit message 28 | 29 | Commit messages should be clear and follow a few basic rules. Example: 30 | 31 | ``` 32 | ENH: add functionality X to framework.. 33 | 34 | The first line of the commit message starts with a capitalized acronym 35 | (options listed below) indicating what type of commit this is. Then a blank 36 | line, then more text if needed. Lines shouldn't be longer than 72 37 | characters. If the commit is related to a ticket, indicate that with 38 | "See #3456", "See ticket 3456", "Closes #3456" or similar. 39 | ``` 40 | 41 | Describing the motivation for a change, the nature of a bug for bug fixes 42 | or some details on what an enhancement does are also good to include in a 43 | commit message. Messages should be understandable without looking at the code 44 | changes. 45 | 46 | Standard acronyms to start the commit message with are: 47 | 48 | 49 | |Code| Description | 50 | |----|----------------------------------------------------| 51 | |API | an (incompatible) API change | 52 | |BLD | change related to building | 53 | |BUG | bug fix | 54 | |DEP | deprecate something, or remove a deprecated object | 55 | |DEV | development tool or utility | 56 | |DOC | documentation | 57 | |ENH | enhancement | 58 | |MNT | maintenance commit (refactoring, typos, etc.) | 59 | |REV | revert an earlier commit | 60 | |STY | style fix (whitespace, PEP8) | 61 | |TST | addition or modification of tests | 62 | |REL | related to releasing numpy | 63 | |WIP | Commit that is a work in progress | 64 | 65 | ## The Pull Request 66 | 67 | * Now push to your fork 68 | * Submit a [pull request](https://help.github.com/articles/using-pull-requests) to this branch. This is a start to the conversation. 69 | 70 | At this point you're waiting on us. We like to at least comment on pull requests within three business days 71 | (and, typically, one business day). We may suggest some changes or improvements or alternatives. 72 | 73 | Hints to make the integration of your changes easy (and happen faster): 74 | - Keep your pull requests small 75 | - Don't forget your unit tests 76 | - All algorithms need documentation, don't forget the .rst file 77 | - Don't take changes requests to change your code personally -------------------------------------------------------------------------------- /tests/integration/task.test.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv" 2 | import { BotMaestroSdk } from "../../src/botcity"; 3 | import { Task } from "../../src/botcity/maestro/interfaces"; 4 | 5 | dotenv.config() 6 | 7 | const SERVER = process.env.BOTCITY_SERVER || "" 8 | const LOGIN = process.env.BOTCITY_LOGIN || "" 9 | const KEY = process.env.BOTCITY_KEY || "" 10 | 11 | let sdk: BotMaestroSdk; 12 | let task: Task; 13 | 14 | 15 | describe("Testing tasks cases", () => { 16 | beforeAll(async () => { 17 | sdk = new BotMaestroSdk(SERVER, LOGIN, KEY); 18 | await sdk.login() 19 | const parameters = { 20 | "test_to_test": "testing", 21 | "integer_to_test": 123, 22 | "double_to_test": 1.0 23 | } 24 | const priority = Math.floor(Math.random() * (Math.floor(10) - Math.ceil(1) + 1)) + Math.ceil(1); 25 | const now = new Date() 26 | now.setHours(now.getHours() - 1) 27 | task = await sdk.createTask("TestCI", parameters, false, priority, now) 28 | }, 10000); 29 | 30 | test("Create Task", async () => { 31 | const parameters = { 32 | "test_to_test": "testing", 33 | "integer_to_test": 123, 34 | "double_to_test": 1.0 35 | } 36 | const priority = Math.floor(Math.random() * (Math.floor(10) - Math.ceil(1) + 1)) + Math.ceil(1); 37 | const now = new Date() 38 | now.setHours(now.getHours() - 1) 39 | const actualTask = await sdk.createTask("TestCI", parameters, false, priority, now) 40 | expect(actualTask) 41 | }, 10000) 42 | 43 | test("Get Task", async () => { 44 | const actualTask = await sdk.getTask(task.id) 45 | expect(actualTask.activityLabel).toBe("TestCI") 46 | }, 10000) 47 | 48 | test("Interrupting Task", async () => { 49 | const actualTask = await sdk.interruptTask(task.id) 50 | expect(actualTask.interrupted).toBeTruthy() 51 | }, 10000) 52 | 53 | test("Finish Task to Success", async () => { 54 | const actualTask = await sdk.finishTask(task.id, "SUCCESS", "Task Finished with Success.") 55 | expect(actualTask.finishStatus).toEqual("SUCCESS") 56 | }, 10000) 57 | 58 | test("Finish Task to Partially Completed", async () => { 59 | const actualTask = await sdk.finishTask(task.id, "PARTIALLY_COMPLETED", "Task Finished with partially completed.") 60 | expect(actualTask.finishStatus).toEqual("PARTIALLY_COMPLETED") 61 | }, 10000) 62 | 63 | test("Finish Task to Failed", async () => { 64 | const actualTask = await sdk.finishTask(task.id, "FAILED", "Task Finished with failed.") 65 | expect(actualTask.finishStatus).toEqual("FAILED") 66 | }, 10000) 67 | 68 | test("Finish Task no report items", async () => { 69 | const actualTask = await sdk.finishTask(task.id, "SUCCESS", "Task Finished with Success.") 70 | expect(actualTask.totalItems).toBeNull() 71 | expect(actualTask.processedItems).toBeNull() 72 | expect(actualTask.failedItems).toBeNull() 73 | }, 10000) 74 | 75 | test("Finish Task to report items", async () => { 76 | const createdTask = await sdk.createTask("TestCI", {}) 77 | const actualTask = await sdk.finishTask(createdTask.id, "SUCCESS", "Task Finished with Success.", 10, 5, 5) 78 | expect(actualTask.totalItems).toEqual(10) 79 | expect(actualTask.processedItems).toEqual(5) 80 | expect(actualTask.failedItems).toEqual(5) 81 | }, 10000) 82 | 83 | test("Finish Task to report processed and failed items", async () => { 84 | const createdTask = await sdk.createTask("TestCI", {}) 85 | const actualTask = await sdk.finishTask(createdTask.id, "SUCCESS", "Task Finished with Success.", null, 5, 5) 86 | expect(actualTask.totalItems).toEqual(10) 87 | expect(actualTask.processedItems).toEqual(5) 88 | expect(actualTask.failedItems).toEqual(5) 89 | }, 10000) 90 | 91 | test("Finish Task to report error invalid total items", async () => { 92 | const createdTask = await sdk.createTask("TestCI", {}) 93 | const expected = async () => { 94 | await sdk.finishTask(createdTask.id, "SUCCESS", "Task Finished with Success.", 10, 6, 5) 95 | } 96 | expect(expected()).rejects.toThrow("Total items is not equal to the sum of processed and failed items.") 97 | }, 10000) 98 | 99 | test("Finish Task to report error invalid total items", async () => { 100 | const createdTask = await sdk.createTask("TestCI", {}) 101 | const expected = async () => { 102 | await sdk.finishTask(createdTask.id, "SUCCESS", "Task Finished with Success.", 10) 103 | } 104 | expect(expected()).rejects.toThrow("You must inform at least two of the following parameters: totalItems, processedItems, failedItems.") 105 | }, 10000) 106 | }) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

BotCity Maestro SDK - Javascript

3 | 4 |

5 | « Explore Framework docs 6 |

7 |

8 | 9 |
10 | 11 | ## Summary 12 | 13 | - [Summary](#summary) 14 | - [🤖 Computer-vision based UI Automation](#-computer-vision-based-ui-automation) 15 | - [🚀 Getting Started](#-getting-started) 16 | - [📦 Prerequisites](#-prerequisites) 17 | - [💫 Installing](#-installing) 18 | - [📚 Documentation](#-documentation) 19 | - [💻 Developers Portal](#-developers-portal) 20 | - [💬 Forum](#-forum) 21 | - [🌎 BotCity Automation Platform](#-botcity-automation-platform) 22 | - [1️⃣ All in One Platform](#1️⃣--all-in-one-platform) 23 | - [2️⃣ Deploy with a Single Line](#2️⃣--deploy-with-a-single-line) 24 | - [3️⃣ Manage your Task Queue](#3️⃣--manage-your-task-queue) 25 | - [4️⃣ Manage your Runtime Environments](#4️⃣--manage-your-runtime-environments) 26 | - [5️⃣ Create Alerts and Reports from the Automation Execution](#5️⃣--create-alerts-and-reports-from-the-automation-execution) 27 | - [6️⃣ Create Google Data Studio Dashboards](#6️⃣--create-google-data-studio-dashboards) 28 | - [7️⃣ Create your Community Account](#7️⃣--create-your-community-account) 29 | - [🤝 Contributing to BotCity Framework](#-contributing-to-botcity-framework) 30 | - [⛑ Support](#-support) 31 | - [🐛 Bug reports and 💎 Feature requests](#-bug-reports-and--feature-requests) 32 | - [📢 Contact us](#-contact-us) 33 | 34 | 35 | ## 🤖 Computer-vision based UI Automation 36 | 37 | Recognize and interact with UI elements using state-of-art computer vision module. 38 | 39 | Operate any UI interface independent of the technology or platform (desktop, web, terminal). 40 | 41 | 42 | ## 🚀 Getting Started 43 | 44 | ### 📦 Prerequisites 45 | * Node 16+ 46 | 47 | ### 💫 Installing 48 | 49 | Install by Npm: 50 | ``` 51 | npm i @botcity/botcity-maestro-sdk 52 | ``` 53 | 54 | Install a local: 55 | ```bash 56 | npm install 57 | ``` 58 | 59 | 60 | ## 📚 Documentation 61 | 62 | Documentation is available at https://documentation.botcity.dev/maestro/maestro-sdk/ 63 | 64 | ## 💻 Developers Portal 65 | 66 | [![Developers Portal](https://botcity.dev/github/readme/portal.png)](https://documentation.botcity.dev) 67 | 68 | ## 💬 Forum 69 | 70 | [![Forum](https://botcity.dev/github/readme/forum.png)](https://community.botcity.dev/) 71 | 72 | ## 🌎 BotCity Automation Platform 73 | BotCity is a platform to develop, deploy, manage and maintain automations. Automations can be developed in Python or Java using open-source libraries that are market standard. 74 | 75 | ### 1️⃣ All in One Platform 76 | Develop, deploy, manage and scale your Automation Ops using All in One platform that provides task queue, runtime environment management, reports, alerts, logs and much more. 77 | 78 | [![BotCity Maestro](https://botcity.dev/github/readme/maestro/maestro.png)](https://botcity.atlassian.net/l/c/WWGswYRX) 79 | 80 | ### 2️⃣ Deploy with a Single Line 81 | 82 | Use BotCity command-line interface (CLI) to deploy your bot into a runtime environment with a single line: 83 | 84 | [![BotCity CLI](https://botcity.dev/github/readme/cli/botcli.gif?raw=true)](https://botcity.atlassian.net/l/c/hJHE1ZFv) 85 | 86 | ### 3️⃣ Manage your Task Queue 87 | 88 | [![BotCity Maestro Task Queue](https://botcity.dev/github/readme/maestro/tasks.png)](https://botcity.atlassian.net/l/c/gR3AAd2a) 89 | 90 | ### 4️⃣ Manage your Runtime Environments 91 | 92 | [![BotCity Maestro Machines](https://botcity.dev/github/readme/maestro/machines.png)](https://botcity.atlassian.net/l/c/uDB087nK) 93 | 94 | ### 5️⃣ Create Alerts and Reports from the Automation Execution 95 | 96 | [![BotCity Maestro Alerts](https://botcity.dev/github/readme/maestro/alerts.png)](https://botcity.atlassian.net/l/c/McH09qYw) 97 | 98 | ### 6️⃣ Create Google Data Studio Dashboards 99 | 100 | [![BotCity Maestro Dashboards](https://botcity.dev/github/readme/maestro/dashboard.png)](https://botcity.atlassian.net/l/c/Z1uMY1vX) 101 | 102 | ### 7️⃣ Create your Community Account 103 | 104 | We have a community account for hobbyists and students. Just signup and start automating. 105 | 106 | [![Sign Up](https://botcity.dev/github/readme/signup.png)](https://botcity.dev/app/signup) 107 | 108 | 109 | ## 🤝 Contributing to BotCity Framework 110 | 111 | - [Guidelines](https://github.com/botcity-dev/botcity-maestro-sdk-js/blob/main/.github/CONTRIBUTING.md) 112 | - [Documentation](https://documentation.botcity.dev/maestro/maestro-sdk/) 113 | 114 | ## ⛑ Support 115 | 116 | ### 🐛 Bug reports and 💎 Feature requests 117 | 118 | If you spot a problem, please let us know by following the template in 119 | here: [Report a bug](https://github.com/botcity-dev/botcity-maestro-sdk-js/issues/new?template=bug-report.md). 120 | 121 | Ideas or suggestions for enhancements are more than welcome. Please use the following 122 | template in here: [Request feature](https://github.com/botcity-dev/botcity-maestro-sdk-js/issues/new?template=feature-request.md&labels=request). 123 | 124 | ### 📢 Contact us 125 | 126 | If you have questions or comments in general about the framework, we want to know. 127 | 128 | You can choose between the channels the one that best fit you: 129 | 130 | - [BotCity Community]() (Public) 131 | 132 | or you can [file a bug](https://github.com/botcity-dev/botcity-maestro-sdk-js/issues/new?template=bug-report.md) and let us know where our documentation could be improved. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/botcity/maestro/sdk.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse } from 'axios' 2 | import { 3 | ensureAccessToken, 4 | catchError, 5 | getMessageInError, 6 | getStackInError, 7 | getTypeInError, 8 | getDefaultTags, 9 | createNpmList, 10 | verifyUrlServer 11 | } from './utils' 12 | import { Alert, DataLog, Log, Logs, Task, Artifact, Artifacts } from './interfaces' 13 | import fs from 'fs' 14 | import https from 'https' 15 | import FormData from 'form-data' 16 | import { Column } from './columns' 17 | import { basename } from 'path' 18 | 19 | export class BotMaestroSdk { 20 | private _server: string 21 | private _login: string 22 | private _key: string 23 | private _accessToken: string 24 | private _verifySSLCert: boolean 25 | 26 | constructor (server: string, login: string, key: string, verifySSLCert: boolean = true) { 27 | this._server = verifyUrlServer(server) 28 | this._login = login 29 | this._key = key 30 | this._accessToken = '' 31 | this._verifySSLCert = verifySSLCert 32 | } 33 | 34 | get server (): string { 35 | return this._server 36 | } 37 | 38 | set server (server: string) { 39 | this._server = verifyUrlServer(server) 40 | } 41 | 42 | private get headers (): Object { 43 | return { 44 | headers: { token: this.accessToken, organization: this._login }, 45 | httpsAgent: this._getHttpsAgent() 46 | } 47 | } 48 | 49 | get accessToken (): string { 50 | return this._accessToken 51 | } 52 | 53 | set accessToken (accessToken: string) { 54 | this._accessToken = accessToken 55 | } 56 | 57 | get verify (): string { 58 | return this._accessToken 59 | } 60 | 61 | set verifySSLCert (verifySSLCert: boolean) { 62 | this._verifySSLCert = verifySSLCert 63 | } 64 | 65 | get verifySSLCert (): boolean { 66 | return this._verifySSLCert 67 | } 68 | 69 | get isOnline (): boolean { 70 | return this.accessToken !== '' 71 | } 72 | 73 | private _getHttpsAgent (): https.Agent { 74 | return new https.Agent({ 75 | rejectUnauthorized: this.verifySSLCert 76 | }) 77 | } 78 | 79 | private _validateItems ( 80 | totalItems: number | null, 81 | processedItems: number | null, 82 | failedItems: number | null 83 | ): any[] { 84 | if (totalItems === null && processedItems === null && failedItems === null) { 85 | console.warn( 86 | `Attention: this task is not reporting items. Please inform the total, processed and failed items. 87 | Reporting items is a crucial step to calculate the ROI, success rate and other metrics for your automation 88 | via BotCity Insights.` 89 | ) 90 | return [null, null, null] 91 | } 92 | 93 | if (totalItems === null && processedItems !== null && failedItems !== null) { 94 | totalItems = processedItems + failedItems 95 | } 96 | 97 | if (totalItems !== null && processedItems !== null && failedItems === null) { 98 | failedItems = totalItems - processedItems 99 | } 100 | 101 | if (totalItems !== null && processedItems === null && failedItems !== null) { 102 | processedItems = totalItems - failedItems 103 | } 104 | 105 | if (totalItems === null || processedItems === null || failedItems === null) { 106 | throw new Error( 107 | 'You must inform at least two of the following parameters: totalItems, processedItems, failedItems.' 108 | ) 109 | } 110 | 111 | totalItems = Math.max(0, totalItems) 112 | processedItems = Math.max(0, processedItems) 113 | failedItems = Math.max(0, failedItems) 114 | 115 | if (totalItems !== null && processedItems !== null && failedItems !== null) { 116 | if (totalItems !== processedItems + failedItems) { 117 | throw new Error('Total items is not equal to the sum of processed and failed items.') 118 | } 119 | } 120 | return [totalItems, processedItems, failedItems] 121 | } 122 | 123 | async login (server: string = '', login: string = '', key: string = ''): Promise { 124 | try { 125 | if (server !== '') { 126 | this.server = verifyUrlServer(server) 127 | } 128 | 129 | this._login = login !== '' ? login : this._login 130 | this._key = key !== '' ? key : this._key 131 | 132 | if (this.server === '') { 133 | throw new Error('Server is required.') 134 | } 135 | 136 | if (this._login === '') { 137 | throw new Error('Login is required.') 138 | } 139 | 140 | if (this._key === '') { 141 | throw new Error('Key is required.') 142 | } 143 | 144 | const url = `${this.server}/api/v2/workspace/login` 145 | const data = { login: this._login, key: this._key } 146 | const response: AxiosResponse = await axios.post(url, data, { 147 | httpsAgent: this._getHttpsAgent() 148 | }) 149 | this.accessToken = response.data.accessToken 150 | } catch (error) { 151 | console.error(error) 152 | throw error 153 | } 154 | } 155 | 156 | async logoff (): Promise { 157 | this.accessToken = '' 158 | } 159 | 160 | @ensureAccessToken 161 | @catchError 162 | async createTask ( 163 | activityLabel: string, 164 | parameters: Object, 165 | test: boolean = false, 166 | priority: number = 0, 167 | minExecutionDate: Date | null = null 168 | ): Promise { 169 | const url = `${this.server}/api/v2/task` 170 | const data = { 171 | activityLabel, 172 | test, 173 | parameters, 174 | priority, 175 | minExecutionDate: minExecutionDate instanceof Date ? minExecutionDate.toISOString() : null 176 | } 177 | const response: AxiosResponse = await axios 178 | .post(url, data, this.headers) 179 | .catch((error: any) => { 180 | console.log(error) 181 | throw new Error(error.response.data.message) 182 | }) 183 | return response.data 184 | } 185 | 186 | @ensureAccessToken 187 | @catchError 188 | async finishTask ( 189 | taskId: string | number, 190 | finishStatus: Object, 191 | finishMessage: string = '', 192 | totalItems: number | null = null, 193 | processedItems: number | null = null, 194 | failedItems: number | null = null 195 | ): Promise { 196 | const url = `${this.server}/api/v2/task/${taskId}` 197 | const [validTotalItems, validProcessedItems, validFailedItems] = this._validateItems( 198 | totalItems, 199 | processedItems, 200 | failedItems 201 | ) 202 | const data = { 203 | state: 'FINISHED', 204 | finishStatus, 205 | finishMessage, 206 | totalItems: validTotalItems, 207 | processedItems: validProcessedItems, 208 | failedItems: validFailedItems 209 | } 210 | const response: AxiosResponse = await axios 211 | .post(url, data, this.headers) 212 | .catch((error: any) => { 213 | throw new Error(error.response.data.message) 214 | }) 215 | return response.data 216 | } 217 | 218 | @ensureAccessToken 219 | @catchError 220 | async getTask (taskId: string | number): Promise { 221 | const url = `${this.server}/api/v2/task/${taskId}` 222 | const response: AxiosResponse = await axios.get(url, this.headers).catch((error: any) => { 223 | throw new Error(error.response.data.message) 224 | }) 225 | return response.data 226 | } 227 | 228 | @ensureAccessToken 229 | @catchError 230 | async restartTask (taskId: string | number): Promise { 231 | const url = `${this.server}/api/v2/task/${taskId}` 232 | const data = { state: 'START' } 233 | const response: AxiosResponse = await axios 234 | .post(url, data, this.headers) 235 | .catch((error: any) => { 236 | throw new Error(error.response.data.message) 237 | }) 238 | return response.data 239 | } 240 | 241 | @ensureAccessToken 242 | @catchError 243 | async interruptTask (taskId: string | number): Promise { 244 | const url = `${this.server}/api/v2/task/${taskId}` 245 | const data = { interrupted: true } 246 | const response: AxiosResponse = await axios 247 | .post(url, data, this.headers) 248 | .catch((error: any) => { 249 | throw new Error(error.response.data.message) 250 | }) 251 | return response.data 252 | } 253 | 254 | @ensureAccessToken 255 | @catchError 256 | async createLog (activityLabel: string, columns: Column[]): Promise { 257 | const url = `${this.server}/api/v2/log` 258 | const data = { activityLabel, columns, organizationLabel: this._login } 259 | const response: AxiosResponse = await axios 260 | .post(url, data, this.headers) 261 | .catch((error: any) => { 262 | throw new Error(error.response.data.message) 263 | }) 264 | return response.data 265 | } 266 | 267 | @ensureAccessToken 268 | @catchError 269 | async getLogs (): Promise { 270 | const url = `${this.server}/api/v2/log` 271 | const response: AxiosResponse = await axios.get(url, this.headers).catch((error: any) => { 272 | throw new Error(error.response.data.message) 273 | }) 274 | return response.data 275 | } 276 | 277 | @ensureAccessToken 278 | @catchError 279 | async getLog (idLog: string): Promise { 280 | const url = `${this.server}/api/v2/log/${idLog}` 281 | const response: AxiosResponse = await axios.get(url, this.headers).catch((error: any) => { 282 | console.log(error) 283 | throw new Error(error.response.data.message) 284 | }) 285 | return response.data 286 | } 287 | 288 | // TODO: Implement others queries params 289 | @ensureAccessToken 290 | @catchError 291 | async fetchDataLog (idLog: string, days: number = 7): Promise { 292 | const url = `${this.server}/api/v2/log/${idLog}/entry-list?days=${days}` 293 | const response: AxiosResponse = await axios.get(url, this.headers).catch((error: any) => { 294 | throw new Error(error.response.data.message) 295 | }) 296 | return response.data 297 | } 298 | 299 | @ensureAccessToken 300 | @catchError 301 | async downloadCsvLog (idLog: string, filepath: string, days: number = 7): Promise { 302 | const url = `${this.server}/api/v2/log/${idLog}/export?days=${days}&format=csv` 303 | const response: AxiosResponse = await axios 304 | .get(url, { ...this.headers, responseType: 'arraybuffer' }) 305 | .catch((error: any) => { 306 | throw new Error(error.response.data.message) 307 | }) 308 | await fs.promises.writeFile(filepath, response.data) 309 | return response.data 310 | } 311 | 312 | @ensureAccessToken 313 | @catchError 314 | async deleteLog (idLog: string): Promise { 315 | const url = `${this.server}/api/v2/log/${idLog}` 316 | await axios.delete(url, this.headers).catch((error: any) => { 317 | throw new Error(error.response.data.message) 318 | }) 319 | } 320 | 321 | @ensureAccessToken 322 | @catchError 323 | async logEntry (idLog: string, content: Object): Promise { 324 | const url = `${this.server}/api/v2/log/${idLog}/entry` 325 | await axios.post(url, content, this.headers).catch((error: any) => { 326 | throw new Error(error.response.data.message) 327 | }) 328 | } 329 | 330 | @ensureAccessToken 331 | @catchError 332 | async createAlert ( 333 | taskId: string | number, 334 | title: string, 335 | message: string, 336 | type: string 337 | ): Promise { 338 | const url = `${this.server}/api/v2/alerts` 339 | const data = { taskId, title, message, type } 340 | const response: AxiosResponse = await axios 341 | .post(url, data, this.headers) 342 | .catch((error: any) => { 343 | throw new Error(error.response.data.message) 344 | }) 345 | return response.data 346 | } 347 | 348 | @ensureAccessToken 349 | @catchError 350 | async createMessage ( 351 | emails: String[], 352 | logins: String[] = [], 353 | subject: string, 354 | body: string, 355 | type: string 356 | ): Promise { 357 | const url = `${this.server}/api/v2/message` 358 | const data = { emails, logins, subject, body, type } 359 | await axios.post(url, data, this.headers).catch((error: any) => { 360 | throw new Error(error.response.data.message) 361 | }) 362 | } 363 | 364 | @ensureAccessToken 365 | @catchError 366 | async createArtifact (taskId: string | number, name: string, filename: string): Promise { 367 | const url = `${this.server}/api/v2/artifact` 368 | const data = { taskId, name, filename } 369 | const response: AxiosResponse = await axios 370 | .post(url, data, this.headers) 371 | .catch((error: any) => { 372 | throw new Error(error.response.data.message) 373 | }) 374 | return response.data 375 | } 376 | 377 | @ensureAccessToken 378 | @catchError 379 | async uploadFile (artifactId: string, filepath: string): Promise { 380 | const formData = new FormData() 381 | const file = fs.createReadStream(filepath) 382 | formData.append('file', file) 383 | const url = `${this.server}/api/v2/artifact/log/${artifactId}` 384 | const contentType = `multipart/form-data; boundary=${formData.getBoundary()}` 385 | const headers: object = { 386 | ...this.headers, 387 | 'Content-Type': contentType 388 | } 389 | await axios.post(url, formData, headers).catch((error: any) => { 390 | throw new Error(error.response.data.message) 391 | }) 392 | } 393 | 394 | @ensureAccessToken 395 | @catchError 396 | async uploadArtifact ( 397 | taskId: string | number, 398 | name: string, 399 | filename: string, 400 | filepath: string 401 | ): Promise { 402 | const artifact = await this.createArtifact(taskId, name, filename) 403 | await this.uploadFile(`${artifact.id}`, filepath) 404 | return artifact 405 | } 406 | 407 | // Todo: Ajust pageable 408 | @ensureAccessToken 409 | @catchError 410 | async getArtifacts ( 411 | size: string, 412 | page: string, 413 | sort: String[] = ['dateCreation'], 414 | days: string = '7' 415 | ): Promise { 416 | const url = `${this.server}/api/v2/artifact?size=${size}&page=${page}&sort=${sort.join( 417 | ',' 418 | )}&days=${days}` 419 | // TODO: Implement not pageable 420 | const response: AxiosResponse = await axios.get(url, this.headers).catch((error: any) => { 421 | throw new Error(error.response.data.message) 422 | }) 423 | return response.data 424 | } 425 | 426 | @ensureAccessToken 427 | @catchError 428 | async downloadArtifact (artifactId: string, filepath: string): Promise { 429 | const url = `${this.server}/api/v2/artifact/${artifactId}/file` 430 | const response: AxiosResponse = await axios 431 | .get(url, { ...this.headers, responseType: 'arraybuffer' }) 432 | .catch((error: any) => { 433 | throw new Error(error.response.data.message) 434 | }) 435 | await fs.promises.writeFile(filepath, response.data) 436 | return response.data 437 | } 438 | 439 | @ensureAccessToken 440 | @catchError 441 | async createError ( 442 | taskId: string | number, 443 | error: Error, 444 | tags: object = {}, 445 | screenshot: string = '', 446 | attachments: string[] = [] 447 | ): Promise { 448 | const message: string = getMessageInError(error) 449 | const type: string = getTypeInError(error) 450 | const stackTrace: string = getStackInError(error) 451 | const language: string = 'javascript'.toUpperCase() 452 | const url = `${this.server}/api/v2/error` 453 | tags = await getDefaultTags(tags) 454 | const data = { taskId, type, message, stackTrace, language, tags } 455 | const response: AxiosResponse = await axios 456 | .post(url, data, this.headers) 457 | .catch((error: any) => { 458 | throw new Error(error.response.data.message) 459 | }) 460 | if (screenshot !== '') { 461 | await this.createScreenshot(response.data.id, screenshot) 462 | } 463 | 464 | const npmListPath = await createNpmList() 465 | 466 | attachments.push(npmListPath) 467 | 468 | try { 469 | if (attachments.length > 0) { 470 | await this.createAttachments(response.data.id, attachments) 471 | } 472 | } finally { 473 | fs.unlinkSync(npmListPath) 474 | } 475 | } 476 | 477 | async getCredential (label: string, key: string): Promise { 478 | const url = `${this.server}/api/v2/credential/${label}/key/${key}` 479 | const response: AxiosResponse = await axios.get(url, this.headers).catch((error: any) => { 480 | throw new Error(error.response.data.message) 481 | }) 482 | return response.data 483 | } 484 | 485 | @ensureAccessToken 486 | @catchError 487 | private async createScreenshot (errorId: string, filepath: string): Promise { 488 | const formData = new FormData() 489 | const file = fs.createReadStream(filepath) 490 | try { 491 | formData.append('file', file) 492 | const url = `${this.server}/api/v2/error/${errorId}/screenshot` 493 | const contentType = `multipart/form-data; boundary=${formData.getBoundary()}` 494 | const headers: object = { 495 | ...this.headers, 496 | 'Content-Type': contentType 497 | } 498 | await axios.post(url, formData, headers).catch((error: any) => { 499 | throw new Error(error.response.data.message) 500 | }) 501 | } finally { 502 | file.destroy() 503 | } 504 | } 505 | 506 | async getCredentialByLabel (label: string): Promise { 507 | const url = `${this.server}/api/v2/credential/${label}` 508 | try { 509 | const response: AxiosResponse = await axios.get(url, this.headers) 510 | return response.data 511 | } catch { 512 | return null 513 | } 514 | } 515 | 516 | @ensureAccessToken 517 | @catchError 518 | private async createAttachments (errorId: string, attachments: string[]): Promise { 519 | const url = `${this.server}/api/v2/error/${errorId}/attachments` 520 | for (const attachment of attachments) { 521 | const formData = new FormData() 522 | const file = fs.createReadStream(attachment) 523 | try { 524 | formData.append('file', file, { filename: basename(attachment) }) 525 | const contentType = `multipart/form-data; boundary=${formData.getBoundary()}` 526 | const headers: object = { 527 | ...this.headers, 528 | 'Content-Type': contentType 529 | } 530 | await axios.post(url, formData, headers).catch((error: any) => { 531 | throw new Error(error.response.data.message) 532 | }) 533 | } finally { 534 | file.destroy() 535 | } 536 | } 537 | } 538 | 539 | async createCredentialByLabel (label: string, key: string, value: any): Promise { 540 | const data = { 541 | label, 542 | secrets: [{ key, value, valid: true }] 543 | } 544 | const url = `${this.server}/api/v2/credential` 545 | const credential = await axios.post(url, data, this.headers).catch((error: any) => { 546 | throw new Error(error.response.data.message) 547 | }) 548 | return credential 549 | } 550 | 551 | @ensureAccessToken 552 | @catchError 553 | async createCredential (label: string, key: string, value: any): Promise { 554 | let credential = await this.getCredentialByLabel(label) 555 | if (credential == null) { 556 | credential = await this.createCredentialByLabel(label, key, value) 557 | return credential.data 558 | } 559 | const url = `${this.server}/api/v2/credential/${label}/key` 560 | const data = { key, value } 561 | credential = await axios.post(url, data, this.headers).catch((error: any) => { 562 | throw new Error(error.response.data.message) 563 | }) 564 | return credential.data 565 | } 566 | } 567 | --------------------------------------------------------------------------------