├── .babelrc ├── .eslintignore ├── .eslintrc ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── assets └── screenshots │ ├── destination-panel.png │ ├── mockups-panel.png │ └── source-panel.png ├── lintresults ├── package.json ├── public ├── icon.icns ├── icon_png.png ├── index.html └── main.js ├── src ├── assets │ └── no-data.svg ├── main │ ├── components │ │ ├── AssertionsForm.jsx │ │ ├── Button.jsx │ │ ├── DataTree.jsx │ │ ├── EmptyState.jsx │ │ ├── HeaderBar.jsx │ │ ├── InlineForm.jsx │ │ ├── InlineInput.jsx │ │ ├── InlineSelect.jsx │ │ ├── Mockup.jsx │ │ ├── MockupName.jsx │ │ ├── NameForm.jsx │ │ ├── PerformanceMetrics.jsx │ │ ├── RequestBar.jsx │ │ ├── ResponseComponent.jsx │ │ ├── ResponseWrapper.jsx │ │ └── TestComponent.jsx │ ├── containers │ │ ├── App.jsx │ │ ├── DataCanvas.jsx │ │ ├── DestinationPanel.jsx │ │ ├── MockupsPanel.jsx │ │ ├── Panels.jsx │ │ ├── SourcePanel.jsx │ │ └── StyledPanel.jsx │ ├── dummyData.js │ ├── index.js │ ├── lib │ │ └── treeView │ │ │ ├── icons.svg │ │ │ ├── jsonTree.css │ │ │ └── jsonTree.js │ └── testsContext.js └── renderer │ └── Nodatasvg.jsx ├── test └── spec.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "react-hot-loader/babel" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/** -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "parser": "babel-eslint", 4 | "rules": { 5 | "no-console": 0, 6 | "consistent-return": 0, 7 | "func-names": 0, 8 | "padded-blocks": 0, 9 | "import/no-extraneous-dependencies": 0, 10 | "no-unused-vars": 0 11 | }, 12 | "env": { 13 | "browser": true, 14 | "commonjs": true, 15 | "es6": true 16 | } 17 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | .DS_Store 64 | .vscode 65 | .idea/ 66 | package-lock.json 67 | dist 68 | release-builds/ 69 | 70 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | before_script: 5 | - npm install 6 | 7 | cache: 8 | yarn: true 9 | directories: 10 | - node_modules 11 | script: 12 | - npm run lint 13 | # - npm run test -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensourcelabs1@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

Interspect

4 |

5 | 6 | [![Build Status](https://travis-ci.com/oslabs-beta/Interspect.svg?branch=master)](https://travis-ci.com/github/oslabs-beta/interspect) 7 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/oslabs-beta/Interspect/pulls) 8 | ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg) 9 | 10 | Interspect is an API-mocking tool that helps you ensure data interoperability between microservices and arbitrary API endpoints. [Download the app](https://github.com/oslabs-beta/Interspect/releases/download/1.0.0/Interspect-macOS-x64.zip) for macOS to get started. 11 | 12 | ## Basic usage 13 | The app is split into three, distinct panels: 14 | 15 | ### Source 16 | This is where you can define a data source that will become the foundation for your data mockups. There are a couple ways to get data into the application. 17 | 18 | ![Screenshot of source panel](/assets/screenshots/source-panel.png) 19 | 20 | #### Send a GET request 21 | If your microservice supports `GET` requests, add your endpoint to the request bar and hit send. You’ll then be able to view the response data, along with basic performance metrics for the request. 22 | 23 | #### Send data to Interspect 24 | You can also send data to the source panel at `http://localhost:3001/posturl` with `POST`, `PUT`, `PATCH`, or `DELETE`. 25 | 26 | **Note:** The open socket may timeout after extended use. There’s an [open issue](https://github.com/oslabs-beta/Interspect/issues/94) to resolve (and we welcome contributions 😉) 27 | 28 | #### Accpeted data types 29 | Interspect works with `JSON` and `XML`-formatted data 30 | 31 | ### Mockups 32 | In the mockups panel, you can create new data scenarios from your source data. Each data tree is editable so you can delete, update, or add new data. 33 | 34 | ![Screenshot of mockups panel](/assets/screenshots/mockups-panel.png) 35 | 36 | If your data source is a collection, you can also create new mockups from a specific index or key. 37 | 38 | #### Creating test assertions 39 | You can add test assertions to any mockup as well. Simply click **Edit Test Assertions** and choose a specific status code or status code range that you expect to receive when sending your mockup to your destination microservice. 40 | 41 | While only status code assertions are supported at launch, we welcome contributions to add additional test domains. 42 | 43 | ### Destination 44 | In the third panel, you can enter a destination endpoint for your mock data. When you send your request, each mockup you created will be sent in sequence to the destination endpoint. 45 | 46 | ![Screenshot of destination panel](/assets/screenshots/destination-panel.png) 47 | 48 | Upon a successful request, you will be able to see returned status codes as well as the result of any test assertions and basic performance metrics. 49 | 50 | **Note:** HTTP Requests in the destination panel are sent 10 times to get an average of the total request time. At launch, this behavior cannot be turned off, but a [preference will be added in a future version](https://github.com/oslabs-beta/Interspect/issues/95). 51 | 52 | 53 | ---- 54 | ## Authors 55 | Interspect was created by: 56 | 57 | - Joe Pavlisko [@joepav](https://github.com/joepav) 58 | - Donald Blanc [@Donaldblanc](https://github.com/Donaldblanc) 59 | - Conor Sexton [@conorsexton](https://github.com/conorsexton) 60 | - Joel Rivera [@RealJoelRivera](https://github.com/RealJoelRivera) 61 | 62 | ## Contributing 63 | We welcome pull requests and new issues. There are also several open issues that are up for grabs. 64 | 65 | ## License 66 | MIT License 67 | 68 | © 2019 Interspect 69 | 70 | Permission is hereby granted, free of charge, to any person obtaining a copy 71 | of this software and associated documentation files (the "Software"), to deal 72 | in the Software without restriction, including without limitation the rights 73 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 74 | copies of the Software, and to permit persons to whom the Software is 75 | furnished to do so, subject to the following conditions: 76 | 77 | The above copyright notice and this permission notice shall be included in all 78 | copies or substantial portions of the Software. 79 | 80 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 81 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 82 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 83 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 84 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 85 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 86 | SOFTWARE. 87 | -------------------------------------------------------------------------------- /assets/screenshots/destination-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Interspect/d62f11c50cd30286e2812adb7683a14145a15315/assets/screenshots/destination-panel.png -------------------------------------------------------------------------------- /assets/screenshots/mockups-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Interspect/d62f11c50cd30286e2812adb7683a14145a15315/assets/screenshots/mockups-panel.png -------------------------------------------------------------------------------- /assets/screenshots/source-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Interspect/d62f11c50cd30286e2812adb7683a14145a15315/assets/screenshots/source-panel.png -------------------------------------------------------------------------------- /lintresults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Interspect/d62f11c50cd30286e2812adb7683a14145a15315/lintresults -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interspect", 3 | "productName": "Interspect", 4 | "version": "1.0.0", 5 | "description": "An tool to test communication between your microservices", 6 | "main": "public/main.js", 7 | "scripts": { 8 | "test": "mocha", 9 | "start": "ELECTRON_IS_DEV=0 electron .", 10 | "build": "webpack --mode production", 11 | "dev": "webpack-dev-server --watch --hot --inline --mode development", 12 | "lint": "npx eslint '**/*.{tsx,ts,js,jsx}' --fix --quiet", 13 | "create_electron": "electron-packager . --overwrite --platform=darwin --arch=x64 --icon=public/icon.icns --prune=true --out=release-builds" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/oslabs-beta/Interspect.git" 18 | }, 19 | "keywords": [ 20 | "microservices" 21 | ], 22 | "author": "Joe Pavlisko, Conor Sexton, Joel Rivera, Donald Blanc", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/oslabs-beta/Interspect/issues" 26 | }, 27 | "homepage": "https://interspect.io/", 28 | "dependencies": { 29 | "body-parser": "^1.19.0", 30 | "chart.js": "^1.1.1", 31 | "electron": "^9.1.0", 32 | "electron-is-dev": "^1.1.0", 33 | "express": "^4.16.4", 34 | "react": "^16.8.6", 35 | "react-chartjs": "^1.2.0", 36 | "react-diff-viewer": "^1.1.0", 37 | "react-dom": "^16.8.6", 38 | "react-feather": "^1.1.6", 39 | "react-json-view": "^1.19.1", 40 | "socket.io": "^2.2.0", 41 | "socket.io-client": "^2.2.0", 42 | "styled-components": "^4.3.1", 43 | "xml2js": "^0.4.19" 44 | }, 45 | "devDependencies": { 46 | "@babel/core": "^7.4.5", 47 | "@babel/preset-env": "^7.4.5", 48 | "@babel/preset-react": "^7.0.0", 49 | "@hot-loader/react-dom": "^16.8.6", 50 | "babel-core": "^6.26.3", 51 | "babel-eslint": "^10.0.1", 52 | "babel-loader": "^8.0.6", 53 | "babel-preset-env": "^1.7.0", 54 | "babel-preset-react": "^6.24.1", 55 | "chai": "^4.2.0", 56 | "chai-as-promised": "^7.1.1", 57 | "css-loader": "^2.1.1", 58 | "electron-packager": "^14.0.0", 59 | "eslint": "^5.16.0", 60 | "eslint-config-airbnb-base": "^13.1.0", 61 | "eslint-plugin-import": "^2.17.3", 62 | "file-loader": "^4.0.0", 63 | "html-webpack-plugin": "^3.2.0", 64 | "mocha": "^6.1.4", 65 | "mocha-loader": "^2.0.1", 66 | "react-hot-loader": "^4.9.0", 67 | "spectron": "^5.0.0", 68 | "style-loader": "^0.23.1", 69 | "webpack": "^4.33.0", 70 | "webpack-cli": "^3.3.2", 71 | "webpack-dev-server": "^3.6.0" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /public/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Interspect/d62f11c50cd30286e2812adb7683a14145a15315/public/icon.icns -------------------------------------------------------------------------------- /public/icon_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Interspect/d62f11c50cd30286e2812adb7683a14145a15315/public/icon_png.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Interspect 9 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /public/main.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require('electron'); 2 | const isDev = require('electron-is-dev'); 3 | const path = require('path'); 4 | const os = require('os'); 5 | const bodyParser = require('body-parser'); 6 | const expressApp = require('express')(); 7 | const server = require('http').Server(expressApp); 8 | const io = require('socket.io')(server); 9 | 10 | if (isDev) { 11 | console.log('Running in development'); 12 | } else { 13 | console.log('Running in production'); 14 | } 15 | 16 | let mainWindow; 17 | 18 | function createWindow() { 19 | expressApp.use(bodyParser.urlencoded({ extended: true })); 20 | expressApp.use(bodyParser.json()); 21 | 22 | server.listen(3001); 23 | 24 | mainWindow = new BrowserWindow({ 25 | width: 1200, 26 | height: 900, 27 | webPreferences: { 28 | nodeIntegration: true, 29 | webSecurity: false, 30 | }, 31 | icon: './icon_png.png' 32 | }); 33 | 34 | mainWindow.loadURL(isDev ? 'http://localhost:8080' : `file://${__dirname}/../dist/index.html`); 35 | 36 | if (isDev) { 37 | BrowserWindow.addDevToolsExtension( 38 | path.join(os.homedir(), './Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/3.6.0_0'), 39 | ); 40 | } 41 | mainWindow.on('closed', () => { 42 | mainWindow = null; 43 | }); 44 | 45 | const handleRequest = (request, response, socket) => { 46 | try { 47 | if (request.headers['content-type'].includes('json') 48 | || request.headers['content-type'].includes('xml')) { 49 | socket.emit('post_received', request.body); 50 | response.status(200); 51 | response.end(); 52 | } else { 53 | response.status(400); 54 | response.end(); 55 | } 56 | } catch { 57 | response.status(500); 58 | response.end(); 59 | } 60 | }; 61 | 62 | const poster = io.on('connection', (socket) => { 63 | console.log('a user connected'); 64 | 65 | expressApp.post('/posturl', (request, response) => { 66 | handleRequest(request, response, socket); 67 | }); 68 | 69 | expressApp.patch('/posturl', (request, response) => { 70 | handleRequest(request, response, socket); 71 | }); 72 | 73 | expressApp.put('/posturl', (request, response) => { 74 | handleRequest(request, response, socket); 75 | }); 76 | 77 | expressApp.delete('/posturl', (request, response) => { 78 | handleRequest(request, response, socket); 79 | }); 80 | }); 81 | 82 | io.on('disconnect', () => { 83 | console.log('a user disconnected'); 84 | }); 85 | } 86 | 87 | app.on('ready', createWindow); 88 | -------------------------------------------------------------------------------- /src/assets/no-data.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | No-Data/title> 4 | Created with Sketch. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/main/components/AssertionsForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import styled from 'styled-components'; 3 | import { TestsContext } from '../testsContext'; 4 | import Select from './InlineSelect.jsx'; 5 | import InlineInput from './InlineInput.jsx'; 6 | import Form from './InlineForm.jsx'; 7 | import Button from './Button.jsx'; 8 | 9 | // Styles 10 | const FormWrapper = styled.section` 11 | background: #F0F3F4; 12 | border-radius: 5px; 13 | box-shadow: inset 0 0 2px 0 rgba(41, 47, 50, 0.25); 14 | padding: 0.5em 1.5em; 15 | h3 { 16 | color: #555B5E; 17 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 18 | font-feature-settings: 'ss18'; 19 | font-size: 1em; 20 | letter-spacing: 0.075em; 21 | text-transform: uppercase; 22 | } 23 | `; 24 | 25 | const Input = styled(InlineInput)` 26 | background: none; 27 | margin: 0 1em; 28 | padding: 0 0 0.25em 0.25em; 29 | text-align: center; 30 | `; 31 | 32 | const Fieldset = styled.fieldset` 33 | border: none; 34 | `; 35 | 36 | const Label = styled.label` 37 | color: #292F32; 38 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 39 | `; 40 | 41 | const AssertionsForm = (props) => { 42 | const { index, setMode } = props; 43 | 44 | // State 45 | const [isRange, setIsRange] = useState(true); 46 | const [minCode, setMinCode] = useState(200); 47 | const [maxCode, setMaxCode] = useState(299); 48 | const [exactCode, setExactCode] = useState(200); 49 | const [tests, setTests] = useContext(TestsContext); 50 | 51 | // Event handlers 52 | const handleChange = (event) => { 53 | const { value, name } = event.target; 54 | switch (name) { 55 | case 'rangeSelector': 56 | if (value === 'range') setIsRange(true); 57 | else setIsRange(false); 58 | break; 59 | case 'min-code': 60 | setMinCode(Number.parseInt(value, 10)); 61 | break; 62 | case 'max-code': 63 | setMaxCode(Number.parseInt(value, 10)); 64 | break; 65 | case 'exact-code': 66 | setExactCode(Number.parseInt(value, 10)); 67 | break; 68 | default: 69 | break; 70 | } 71 | }; 72 | 73 | const handleSubmit = (event) => { 74 | event.preventDefault(); 75 | const testsCopy = [...tests]; 76 | const status = isRange ? [minCode, maxCode] : [exactCode]; 77 | testsCopy[index].expectedStatus = status; 78 | setTests(testsCopy); 79 | setMode('view'); 80 | }; 81 | 82 | return ( 83 | 84 |

Test Assertions

85 |
86 | 87 |
88 | 92 | 101 | 102 | {isRange && } 103 | {isRange && 112 | } 113 |
114 | 115 |
116 |
117 | ); 118 | }; 119 | 120 | export default AssertionsForm; 121 | -------------------------------------------------------------------------------- /src/main/components/Button.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | let backgroundColor; 4 | 5 | const Button = styled.button` 6 | background-color: ${(props) => { 7 | if (!props.enabled) return '#BCC1C2'; 8 | if (props.variation === 'positive') return '#49893E'; 9 | return '#1F4E7A'; 10 | }}; 11 | border: none; 12 | color: ${(props) => { 13 | if (!props.enabled) return '#555B5E'; 14 | if (props.variation === 'positive') return '#DEF0DB'; 15 | return '#D9EDF2'; 16 | }}; 17 | border-radius: 3px; 18 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; 19 | font-size: 1em; 20 | font-weight: 500; 21 | padding: 0.25em 1em; 22 | transition: all 0.15s ease-in-out; 23 | 24 | :hover { 25 | background-color: ${(props) => { 26 | if (!props.enabled) return '#BCC1C2'; 27 | if (props.variation === 'positive') return '#35612E'; 28 | return '#0D273F'; 29 | }}; 30 | cursor: ${(props) => { 31 | if (!props.enabled) return 'not-allowed'; 32 | return 'pointer'; 33 | }}; 34 | } 35 | 36 | :active { 37 | color: #D4E6F7; 38 | outline: 0; 39 | box-shadow: none; 40 | } 41 | 42 | :focus { 43 | outline: 0; 44 | box-shadow: ${(props) => { 45 | if (!props.enabled) return '0 0 1px 2px #949C9E'; 46 | if (props.variation === 'positive') return '0 0 1px 2px #DEF0DB'; 47 | return '0 0 1px 2px #96C1E9'; 48 | }} 49 | } 50 | `; 51 | 52 | export default Button; 53 | -------------------------------------------------------------------------------- /src/main/components/DataTree.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactJson from 'react-json-view'; 3 | 4 | const styles = { 5 | borderRadius: '5px', 6 | fontFamily: '\'IBM Plex Mono\', monospace', 7 | fontSize: '90%', 8 | maxHeight: '250px', 9 | overflow: 'auto', 10 | margin: '0.75em auto', 11 | padding: '1em', 12 | }; 13 | 14 | const DataTree = (props) => { 15 | const { 16 | treeId, data, options, saveUpdatedTree, 17 | } = props; 18 | const { 19 | onAdd, onEdit, onDelete, enableClipboard, 20 | } = options; 21 | 22 | const changeObject = (src) => { 23 | saveUpdatedTree(src.updated_src, treeId, src.new_value, src.name, src.namespace); 24 | }; 25 | 26 | return ( 27 |
28 | {/* Tree gets rendered here after component mounts */} 29 | 40 |
41 | ); 42 | }; 43 | 44 | export default DataTree; 45 | -------------------------------------------------------------------------------- /src/main/components/EmptyState.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const EmptyState = styled.section` 4 | align-items: center; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: middle; 8 | margin: 4em auto; 9 | h2 { 10 | color: #171A1C; 11 | font-family: 'P22 Mackinac Pro', 'Iowan Old Style', 'Palantino', Georgia, serif; 12 | font-weight: 400; 13 | text-align: center; 14 | } 15 | p { 16 | color: #949C9E; 17 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 18 | text-align: center; 19 | margin-bottom: 2em; 20 | } 21 | `; 22 | 23 | export default EmptyState; 24 | -------------------------------------------------------------------------------- /src/main/components/HeaderBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Form from './InlineForm.jsx'; 3 | import Select from './InlineSelect.jsx'; 4 | import Input from './InlineInput.jsx'; 5 | 6 | const HeaderBar = (props) => { 7 | const { 8 | header, authType, handleChange, 9 | } = props; 10 | 11 | return ( 12 |
13 |
14 | 24 | 33 | 34 |
35 |
36 | ); 37 | }; 38 | 39 | export default HeaderBar; 40 | -------------------------------------------------------------------------------- /src/main/components/InlineForm.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const InlineForm = styled.form` 4 | align-items: center; 5 | border: ${(props) => { 6 | if (props.bordered) return '1px solid #BCC1C2'; 7 | return 'none'; 8 | }} 9 | border-radius: 3px; 10 | display: flex; 11 | flex-shrink: 1; 12 | justify-content: flex-start; 13 | margin-top: ${(props) => { 14 | if (!props.bordered) return '1em'; 15 | return 'inherit'; 16 | }} 17 | transition: all 0.15s ease-in-out 18 | &:focus-within { 19 | ${(props) => { 20 | if (props.bordered) { 21 | return `border: 1px solid #949C9E; 22 | box-shadow: 0 2px 6px -2px rgba(41, 47, 50, 0.25);`; 23 | } 24 | return 'none'; 25 | }} 26 | } 27 | button { 28 | border-radius: 2px; 29 | margin-left: auto; 30 | margin-right: 0.5em; 31 | // min-width: 0; 32 | } 33 | input { 34 | min-width: 0; 35 | } 36 | `; 37 | 38 | export default InlineForm; 39 | -------------------------------------------------------------------------------- /src/main/components/InlineInput.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const InlineInput = styled.input` 4 | border: none; 5 | border-left: ${(props) => { 6 | if (props.bordered) return '1px solid #D8D8D8;'; 7 | return 'none;'; 8 | }}; 9 | border-bottom: ${(props) => { 10 | if (!props.bordered) return '1px solid #D8D8D8;'; 11 | return 'none;'; 12 | }}; 13 | color: #292F32; 14 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 15 | font-size: 1em; 16 | line-height: 1em; 17 | padding: 0.825em; 18 | width: ${(props) => { 19 | if (!props.bordered) return 'fit-content;'; 20 | return '100%;'; 21 | }}; 22 | &:focus { 23 | outline: none; 24 | } 25 | &::placeholder { 26 | color: #BCC1C2; 27 | font-size: 0.9em; 28 | } 29 | `; 30 | 31 | export default InlineInput; 32 | -------------------------------------------------------------------------------- /src/main/components/InlineSelect.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const InlineSelect = styled.select` 4 | background: none; 5 | border: none; 6 | color: #555B5E; 7 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 8 | font-size: 0.825em; 9 | margin: 0.925em 1em 0.725em 0.725em; 10 | &:focus { 11 | outline: 0; 12 | } 13 | `; 14 | 15 | export default InlineSelect; 16 | -------------------------------------------------------------------------------- /src/main/components/Mockup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import NameForm from './NameForm.jsx'; 3 | import MockupName from './MockupName.jsx'; 4 | import DataTree from './DataTree.jsx'; 5 | import Button from './Button.jsx'; 6 | import AssertionsForm from './AssertionsForm.jsx'; 7 | import { TestsContext } from '../testsContext'; 8 | 9 | 10 | const dataTreeOptions = { 11 | onAdd: true, 12 | onEdit: true, 13 | onDelete: true, 14 | enableClipboard: false, 15 | }; 16 | 17 | const Mockup = (props) => { 18 | const { test, index, saveUpdatedTree } = props; 19 | 20 | const [tests, setTests] = useContext(TestsContext); 21 | 22 | // Mode controls test assertions and name edit form—it’s set by child components 23 | const [mode, setMode] = useState('view'); 24 | 25 | function setName(name, testIndex) { 26 | const testsClone = [...tests]; 27 | // This if-statement just makes sure the test name wasn't just whitespace 28 | if (name.replace(/\s/g, '') !== '') testsClone[testIndex].name = name; 29 | else testsClone[testIndex].name = `Test #${testIndex + 1}`; 30 | setTests(testsClone); 31 | } 32 | 33 | function deleteTest(testIndex) { 34 | const testsClone = []; 35 | for (let i = 0; i < tests.length; i += 1) { 36 | if (i !== testIndex) testsClone.push(tests[i]); 37 | } 38 | setTests(testsClone); 39 | } 40 | 41 | return ( 42 |
43 | {mode === 'editName' 44 | ? 45 | : 46 | } 47 | 54 | {mode === 'editAssertions' 55 | ? 56 | : 57 | } 58 | 59 |
60 | ); 61 | }; 62 | 63 | export default Mockup; 64 | -------------------------------------------------------------------------------- /src/main/components/MockupName.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | // Styles 5 | const Header = styled.header` 6 | display: flex; 7 | align-items: flex-end; 8 | h3 { 9 | margin-bottom: 0; 10 | } 11 | `; 12 | 13 | const Name = styled.h3` 14 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 15 | color: #292F32; 16 | `; 17 | 18 | const EditButton = styled.button` 19 | background: none; 20 | border: none; 21 | color: #4299EA; 22 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 23 | letter-spacing: 0.05em; 24 | margin: 0 auto 0.25em 0.825em; 25 | text-transform: uppercase; 26 | opacity: ${props => (props.hovered ? 1 : 0)}; 27 | transition: all 0.2s ease-in-out; 28 | &:hover { cursor: pointer; } 29 | &:active, &:focus { 30 | color: #1F4E7A; 31 | outline: 0; 32 | } 33 | `; 34 | 35 | const MockupName = (props) => { 36 | const { name, setMode } = props; 37 | // Hovered state is passed to edit button to control visibility 38 | const [hovered, setHovered] = useState(false); 39 | 40 | 41 | return ( 42 |
setHovered(true)} onMouseLeave={() => setHovered(false)}> 43 | {name} 44 | { setMode('editName'); }} hovered={hovered}> 45 | Edit 46 | 47 |
48 | ); 49 | }; 50 | 51 | export default MockupName; 52 | -------------------------------------------------------------------------------- /src/main/components/NameForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import styled from 'styled-components'; 3 | import { TestsContext } from '../testsContext'; 4 | import InlineForm from './InlineForm.jsx'; 5 | import InlineInput from './InlineInput.jsx'; 6 | import Button from './Button.jsx'; 7 | 8 | // Form styles 9 | const Form = styled(InlineForm)` 10 | flex-direction: column; 11 | align-items: flex-start; 12 | margin: 1em auto; 13 | `; 14 | 15 | const Input = styled(InlineInput)` 16 | padding: 0; 17 | padding-bottom: 0.25em; 18 | `; 19 | 20 | const Label = styled.label` 21 | color: #BCC1C2; 22 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 23 | font-size: 0.75em; 24 | `; 25 | 26 | const NameForm = (props) => { 27 | const { 28 | name, index, setMode, setName, 29 | } = props; 30 | 31 | // State 32 | const [tests, setTests] = useContext(TestsContext); 33 | const [newName, setNewName] = useState(''); 34 | 35 | // Event handlers 36 | const handleSubmit = (event) => { 37 | event.preventDefault(); 38 | const testsCopy = [...tests]; 39 | testsCopy[index].name = newName; 40 | 41 | // Update name in test array, then set mockup “mode” to trigger re-render 42 | setName(newName, index); 43 | setTests(testsCopy); 44 | setMode('view'); 45 | }; 46 | 47 | const updateName = (event) => { 48 | const { value } = event.target; 49 | setNewName(value); 50 | }; 51 | 52 | return ( 53 |
54 | 60 | 61 | 62 |
63 | ); 64 | }; 65 | 66 | export default NameForm; 67 | -------------------------------------------------------------------------------- /src/main/components/PerformanceMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { Bar } from 'react-chartjs'; 4 | 5 | const PerformanceWrapper = styled.div` 6 | background-color: #F0F3F4; 7 | border-radius: 3px; 8 | box-shadow: inset; 9 | box-shadow: inset 0 0 1px 0 rgba(41,47,50,0.25); 10 | display: flex; 11 | justify-content: space-between; 12 | align-items: center; 13 | padding: 1em; 14 | margin-bottom: 1em; 15 | h3 { 16 | color: #555B5E; 17 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 18 | font-feature-settings: 'ss18'; 19 | font-size: 1em; 20 | font-weight: 400; 21 | } 22 | `; 23 | 24 | const BarChart = Bar; 25 | 26 | const PerformanceMetrics = (props) => { 27 | const { fetchTimes } = props; 28 | const chartData = { 29 | labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 30 | datasets: [ 31 | { 32 | label: 'My First dataset', 33 | fillColor: 'rgba(151,187,205,0.5)', 34 | strokeColor: 'rgba(151,187,205,0.8)', 35 | highlightFill: 'rgba(151,187,205,0.75)', 36 | highlightStroke: 'rgba(151,187,205,1)', 37 | data: fetchTimes, 38 | }, 39 | ], 40 | }; 41 | 42 | const average = fetchTimes.reduce((acc, val) => acc + (val / fetchTimes.length), 0); 43 | const max = Math.max(...fetchTimes); 44 | const min = Math.min(...fetchTimes); 45 | const variance = fetchTimes.reduce( 46 | (acc, val) => (acc + ((val - average) ** 2)), 0, 47 | ) / fetchTimes.length; 48 | 49 | return ( 50 | 51 |
52 |
53 |

API Request Times

54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 |
62 |

63 | Average: 64 | {' '} 65 | {Math.floor(average)} 66 | {' '} 67 | ms | 68 | Max: 69 | {' '} 70 | {max} 71 | {' '} 72 | ms | 73 | Min: 74 | {' '} 75 | {min} 76 | {' '} 77 | ms | 78 | Standard Deviation: 79 | {' '} 80 | {Math.floor(Math.sqrt(variance))} 81 | {' '} 82 | ms 83 |

84 |
85 |
86 |
87 | ); 88 | }; 89 | 90 | export default PerformanceMetrics; 91 | -------------------------------------------------------------------------------- /src/main/components/RequestBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import { parseString, Builder } from 'xml2js'; 3 | import HeaderBar from './HeaderBar.jsx'; 4 | import Form from './InlineForm.jsx'; 5 | import Select from './InlineSelect.jsx'; 6 | import Input from './InlineInput.jsx'; 7 | import Button from './Button.jsx'; 8 | import { TestsContext } from '../testsContext'; 9 | 10 | 11 | const RequestBar = (props) => { 12 | const { 13 | SourceOrDest, setData, setFetchTimes, setContentType, contentType, 14 | } = props; 15 | const [tests, setTests] = useContext(TestsContext); 16 | 17 | const method = (SourceOrDest === 'dest' ? 'POST' : 'GET'); 18 | const [selected, setSelected] = useState(method); 19 | const [uri, setUri] = useState(''); 20 | const [valid, setValid] = useState(false); 21 | 22 | // header info 23 | const [headerType, setHeaderType] = useState('Authorization'); 24 | const [authType, setType] = useState('Bearer Token'); 25 | const [headerKey, setHeaderKey] = useState(''); 26 | let now = new Date(); 27 | 28 | const handleChange = (e) => { 29 | const { name, value } = e.target; 30 | if (name === 'method') setSelected(value); 31 | else if (name === 'uri') setUri(value); 32 | 33 | // Header info 34 | if (name === 'Authentication') setHeaderType(value); 35 | if (name === 'headerKey') setHeaderKey(`Bearer ${value}`); 36 | 37 | setValid(e.target.parentElement.reportValidity()); 38 | }; 39 | 40 | const handleInvalid = (e) => { 41 | e.preventDefault(); 42 | }; 43 | 44 | // Extra fetches for performance metrics 45 | const fetchTimesList = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 46 | function getPerformanceMetricsData(getOrPost, sendingObj) { 47 | let successfulFetchesCounter = 0; 48 | 49 | function recordFetchTimes(i) { 50 | fetch(uri, sendingObj) 51 | .then(() => { 52 | fetchTimesList[i] = new Date() - now; 53 | successfulFetchesCounter += 1; 54 | now = new Date(); 55 | if (successfulFetchesCounter === 9) { 56 | setFetchTimes(fetchTimesList); 57 | } 58 | }); 59 | } 60 | 61 | for (let i = 1; i < 10; i += 1) recordFetchTimes(i); 62 | } 63 | 64 | const runTest = (link, sendingObj, testsClone, i) => { 65 | const test = testsClone; 66 | now = new Date(); 67 | fetch(link, sendingObj) 68 | .then((response) => { 69 | fetchTimesList[0] = new Date() - now; 70 | test[i].status = response.status; 71 | if (i === test.length - 1) setTests(test); 72 | now = new Date(); 73 | }) 74 | .catch(error => console.log(error)); 75 | 76 | getPerformanceMetricsData('post', sendingObj); 77 | }; 78 | 79 | const parseXmlToJson = (xml) => { 80 | let json; 81 | parseString(xml, (err, result) => { 82 | json = result; 83 | return result; 84 | }); 85 | return json; 86 | }; 87 | 88 | const sendFetch = (e) => { 89 | e.preventDefault(); 90 | if (!valid) return alert('Enter a valid URI'); 91 | 92 | if (SourceOrDest === 'source') { 93 | const sendingObj = { method: selected, mode: 'cors' }; 94 | if (headerType !== 'NONE') sendingObj.headers = { [headerType]: headerKey }; 95 | 96 | now = new Date(); 97 | fetch(uri, sendingObj) 98 | .then((res) => { 99 | fetchTimesList[0] = new Date() - now; 100 | now = new Date(); 101 | const val = res.headers.get('content-type'); 102 | setContentType(val); 103 | if (val.includes('xml')) { 104 | return res.text().then(xml => parseXmlToJson(xml)); 105 | } 106 | return res.json(); 107 | }) 108 | .then((res) => { 109 | setTests([{ 110 | payload: res, status: '', name: 'Test #1', 111 | }]); 112 | setData(res); 113 | }); 114 | 115 | getPerformanceMetricsData('get', sendingObj); 116 | 117 | } else if (SourceOrDest === 'dest') { 118 | const testsClone = [...tests]; 119 | const sendingObj = { method: selected, mode: 'cors' }; 120 | sendingObj.headers = { 'Content-Type': contentType }; 121 | if (headerType !== 'NONE') sendingObj.headers[headerType] = headerKey; 122 | 123 | for (let i = 0; i < testsClone.length; i += 1) { 124 | if (sendingObj.headers['Content-Type'].includes('xml')) { 125 | const jsonToXml = new Builder(); 126 | sendingObj.body = jsonToXml.buildObject(testsClone[i].payload); 127 | } else { 128 | sendingObj.body = JSON.stringify(testsClone[i].payload); 129 | } 130 | runTest(uri, sendingObj, testsClone, i); 131 | } 132 | } 133 | }; 134 | 135 | return ( 136 |
137 |
138 | { 139 | (SourceOrDest === 'source') 140 | && ( 141 | 150 | ) 151 | } 152 | { 153 | (SourceOrDest === 'dest') 154 | && 166 | } 167 | 168 | 169 |
170 | 171 |
172 | ); 173 | }; 174 | 175 | export default RequestBar; 176 | -------------------------------------------------------------------------------- /src/main/components/ResponseComponent.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import ReactDiffViewer from 'react-diff-viewer'; 3 | import styled from 'styled-components'; 4 | import { ArrowRightCircle, CheckCircle, XCircle } from 'react-feather'; 5 | import ResponseWrapper from './ResponseWrapper.jsx'; 6 | 7 | 8 | // Styles 9 | const CardButton = styled.button` 10 | background: #F0F3F4; 11 | border: none; 12 | border-bottom-left-radius: 3px; 13 | border-bottom-right-radius: 3px; 14 | border-top: 1px solid #BCC1C2; 15 | box-shadow: inset 0 0 1px 0 rgba(41, 47, 50, 0.35); 16 | color: #555B5E; 17 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 18 | font-size: 0.825em; 19 | padding: 1em; 20 | 21 | :hover { 22 | cursor: pointer; 23 | background: #555B5E; 24 | color: #F0F3F4; 25 | } 26 | 27 | :active { 28 | background: #555B5E; 29 | color: #F0F3F4; 30 | outline: 0; 31 | } 32 | 33 | :focus { 34 | outline: 0; 35 | box-shadow: inset 0 0 3px 0 rgba(41, 47, 50, 0.5); 36 | } 37 | `; 38 | 39 | const Status = styled.div` 40 | color: #292F32; 41 | display: flex; 42 | align-items: center; 43 | svg { 44 | margin-right: 0.625em; 45 | } 46 | `; 47 | 48 | const diffColors = { 49 | variables: { 50 | addedBackground: '#DEF0DB', 51 | addedColor: '#1A3715', 52 | removedBackground: '#FFE5E5', 53 | removedColor: '#5C0505', 54 | wordAddedBackground: '#B2D2A2', 55 | wordRemovedBackground: '#FBB6B6', 56 | addedGutterBackground: '#B2D2A2', 57 | removedGutterBackground: '#FBB6B6', 58 | gutterBackground: '#F0F3F4', 59 | gutterBackgroundDark: '#F0F3F4', 60 | highlightBackground: '#fffbdd', 61 | highlightGutterBackground: '#fff5b1', 62 | }, 63 | }; 64 | 65 | const Header = styled.header` 66 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 67 | padding: 1em; 68 | h3 { 69 | color: #292F32; 70 | margin: 0; 71 | padding-bottom: 0.5em; 72 | } 73 | `; 74 | 75 | const ResponseComponent = (props) => { 76 | const { 77 | status, expectedStatus, payload, data, name, diff, 78 | } = props; 79 | 80 | const [showDiff, setShowDiff] = useState(false); 81 | 82 | // Event handlers 83 | const didPass = () => { 84 | if (!status) return 'pending'; 85 | if (!expectedStatus) { 86 | return status >= 200 && status <= 299; 87 | } 88 | if (expectedStatus[1]) { 89 | const [lower, upper] = expectedStatus; 90 | return status >= lower && status <= upper; 91 | } 92 | return expectedStatus[0] === status; 93 | }; 94 | 95 | const renderCheckmark = () => { 96 | if (!status) return ; 97 | return didPass() 98 | ? 99 | : ; 100 | }; 101 | 102 | const renderExpectedStatus = () => { 103 | if (!expectedStatus) { 104 | return 'Expecting status code in the 200–299 range'; 105 | } 106 | if (expectedStatus.length === 2) { 107 | const [lower, upper] = expectedStatus; 108 | return `Expecting status code in the ${lower}–${upper} range`; 109 | } 110 | if (expectedStatus.length === 1) { 111 | return `Expecting status code to be ${expectedStatus[0]}`; 112 | } 113 | }; 114 | 115 | const renderTestResult = () => { 116 | if (!expectedStatus) { 117 | return 'Expecting status code in the 200–299 range'; 118 | } 119 | if (expectedStatus[1]) { 120 | const [lower, upper] = expectedStatus; 121 | return didPass() 122 | ? 'Passed' 123 | : `Expected status code to fall between ${lower}–${upper}`; 124 | } 125 | return didPass() 126 | ? 'Passed' 127 | : `Expected status code to be ${expectedStatus[0]}`; 128 | }; 129 | 130 | const originalValue = (diff === undefined) 131 | ? JSON.stringify(data, null, 2) 132 | : JSON.stringify(data[diff], null, 2); 133 | const newValue = JSON.stringify(payload, null, 2); 134 | 135 | const cardButton = (originalValue === newValue 136 | ? No Changes 137 | : setShowDiff(true)}> Show Changes ); 138 | 139 | return ( 140 | 141 |
142 |

{ name }

143 | {renderCheckmark()} 144 | { status || 'Ready to send' } 145 | 146 |

{(diff === undefined) ? '' : `Test created from subset from key: ${diff}`}

147 |

{ status ? renderTestResult() : renderExpectedStatus() }

148 |
149 | {showDiff ? setShowDiff(false)}>Collapse Changes : null} 150 | {showDiff 151 | ?
152 | 159 |
160 | : cardButton 161 | } 162 | 163 |
164 | ); 165 | }; 166 | 167 | export default ResponseComponent; 168 | -------------------------------------------------------------------------------- /src/main/components/ResponseWrapper.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const ResponseWrapper = styled.article` 4 | background-color: #F0F3F4; 5 | border-radius: 3px; 6 | box-shadow: inset; 7 | box-shadow: inset 0 0 2px 0 rgba(41, 47, 50, 0.25); 8 | display: flex; 9 | flex-direction: column; 10 | justify-content: space-between; 11 | margin-bottom: 1em; 12 | a { 13 | color: #555B5E; 14 | font-family: 'Halyard Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 15 | font-size: 0.825em; 16 | text-align: center; 17 | :hover { 18 | cursor: pointer; 19 | } 20 | } 21 | div.diff { 22 | border-top: 1px solid #E2E6E9; 23 | margin: 0.25em; 24 | max-height: 480px; 25 | overflow-y: scroll; 26 | } 27 | table { 28 | border-collapse: collapse; 29 | } 30 | pre { 31 | font-family: 'IBM Plex Mono', monospace; 32 | font-size: 90%; 33 | } 34 | `; 35 | 36 | export default ResponseWrapper; 37 | -------------------------------------------------------------------------------- /src/main/components/TestComponent.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | // import DataTree from '../components/DataTree.jsx'; 3 | 4 | const TestComponent = (props) => { 5 | // data is the data response from msA 6 | // key is the unique ID for this test component-- maps to tests[i] 7 | const { data, listPosition, saveTest } = props; 8 | 9 | // Look at that, CS. Looks like I might've been wrong :) 10 | // Setting this component's initial state to a prop 11 | // because it will be the same `data` every time one is 12 | // created but each one is unique and can be changed. 13 | const [json, setJson] = useState(JSON.stringify(data)); 14 | 15 | const handleChange = (e) => { 16 | setJson(e.target.value); 17 | }; 18 | 19 | return ( 20 |
21 |

Here's a test!

22 |