├── .gitignore ├── README.md ├── bin └── run.js ├── example ├── example.png └── example.ts ├── package-lock.json ├── package.json └── viewer ├── effects.json ├── index.html ├── main.js └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | viewer/force-horse -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redux-viewer 2 | A simple CLI tool to view the graph of events and actions 3 | 4 | ![example](example/example.png) 5 | 6 | ## Usage 7 | Install the package globally: 8 | ``` 9 | npm i -g redux-viewer 10 | ``` 11 | 12 | Document every effect you have with the following format: 13 | 14 | | Property | Status | Description | 15 | |---------------|-----------|-------------------------------------------------------------------------------------------------------| 16 | | @type | mandatory | Effect | 17 | | @name | mandatory | Effect's name | 18 | | @ofType | mandatory | What actions trigger the effect, comma seperated | 19 | | @dependencies | optional | What stores is this effect using, comma seperated | 20 | | @filter | optional | Human readable filter that guards the effect | 21 | | @action | optional | What actions does this effect trigger, comma separated. If an action is optional, use `?` in the end | 22 | 23 | A full example: 24 | ```ts 25 | /** 26 | * @type Effect 27 | * @name someEffectName$ 28 | * @ofType someAction, aDifferentAction 29 | * @dependencies feed, friends 30 | * @filter If something exists 31 | * @action callAction, anotherCallAction, maybeCallAction? 32 | */ 33 | ``` 34 | 35 | Run the following command on your project: 36 | ```bash 37 | rv -s src/ 38 | ``` 39 | 40 | ## Contributing: 41 | fork repository and then: 42 | 43 | ```bash 44 | npm link 45 | npm start 46 | ``` 47 | This will start viewer session pointing to example folder -------------------------------------------------------------------------------- /bin/run.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | // Parse arguments for the CLI 4 | const ArgumentParser = require('argparse').ArgumentParser; 5 | const parser = new ArgumentParser({ 6 | addHelp: true, 7 | description: 'The Redux Viewer CLI' 8 | }); 9 | // Source parameter 10 | parser.addArgument( 11 | ['-s', '--source'], 12 | {help: 'Where are your source files?'} 13 | ); 14 | const args = parser.parseArgs(); 15 | 16 | let directory; 17 | // Make sure there is a directory passed 18 | if (!args.source) { 19 | console.error('Must pass a folder!'); 20 | process.exit(); 21 | } else { 22 | directory = args.source; 23 | } 24 | 25 | 26 | const fs = require('fs'); // File system to write a JSON 27 | const path = require('path'); // Path resolving 28 | const findInFiles = require('find-in-files'); // File regex lookup 29 | const ncp = require('ncp').ncp; 30 | 31 | ncp.limit = 16; 32 | ncp(path.join(__dirname, '../node_modules/force-horse/dist'), path.join(__dirname, '../viewer/force-horse')); 33 | 34 | // Regex describing an effect 35 | const regexSearch = new RegExp(/\/\*\*([\s\S]*?)\@type Effect([\s\S]*?)\*\//); 36 | 37 | // Run file lookup 38 | findInFiles.find(regexSearch, directory, '.ts$').then(result => { 39 | let effects = []; 40 | 41 | Object.keys(result).forEach(file => { 42 | result[file].matches.forEach(match => { 43 | let matchArray = match.split('\n') 44 | .map(l => l.trim()) 45 | .map(l => l.substr(2, l.length - 2)) 46 | .map(l => l.split(' ')) 47 | .map(l => ({param: l.shift(), body: l.join(' ')})) 48 | .map(({param, body}) => ({param: param.substr(1, param.length - 1), body})); 49 | matchArray.pop(); 50 | matchArray.shift(); 51 | 52 | let effect = {}; 53 | matchArray.forEach(line => { 54 | switch (line.param) { 55 | case 'name': 56 | case 'filter': 57 | effect[line.param] = line.body; 58 | break; 59 | case 'ofType': 60 | case 'dependencies': 61 | case 'action': 62 | effect[line.param] = line.body.split(',').map(s => s.trim()); 63 | } 64 | }); 65 | 66 | effects.push(effect); 67 | }); 68 | }); 69 | 70 | const viewer = path.join(__dirname, '../viewer/'); 71 | 72 | const outputPath = path.join(viewer, 'effects.json'); 73 | fs.writeFileSync(outputPath, JSON.stringify(effects, null, 2)); 74 | 75 | const connect = require('connect'); // Server connection 76 | const serveStatic = require('serve-static'); // Serving strategy 77 | const PORT = 8090; 78 | connect().use(serveStatic(viewer)).listen(PORT, () => { 79 | console.warn('Note!', 'The RV server is not watching for effect changes'); 80 | console.log('Static server running on', PORT); 81 | 82 | const open = require('open'); // Open the server 83 | open('http://localhost:' + PORT); 84 | }); 85 | }); -------------------------------------------------------------------------------- /example/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmitMY/redux-viewer/35ba65f2e7b78681120335e9e846b81b793eec3c/example/example.png -------------------------------------------------------------------------------- /example/example.ts: -------------------------------------------------------------------------------- 1 | /* 2 | This example is just with the documentation, and not with actual effects and actions 3 | */ 4 | 5 | /** 6 | * @type Effect 7 | * @name fetchData$ 8 | * @ofType FetchDataAction 9 | * @action FetchDataSuccessAction?, FetchDataFailureAction? 10 | **/ 11 | 12 | /** 13 | * @type Effect 14 | * @name fetchDataClientError$ 15 | * @ofType FetchDataFailureAction 16 | * @filter If error is 4xx (Client) 17 | * @action ToastAction 18 | **/ 19 | 20 | /** 21 | * @type Effect 22 | * @name fetchDataServerError$ 23 | * @ofType FetchDataFailureAction 24 | * @filter If error is 5xx (Server) 25 | * @action ToastAction 26 | **/ 27 | 28 | /** 29 | * @type Effect 30 | * @name fetchDataSuccess$ 31 | * @ofType FetchDataSuccessAction 32 | * @action UpdateViewAction, ToastAction 33 | **/ 34 | 35 | /** 36 | * @type Effect 37 | * @name fetchDataSuccessSetPin$ 38 | * @ofType FetchDataSuccessAction 39 | * @filter If in pinning mode 40 | * @action SetPinAction 41 | **/ 42 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-viewer", 3 | "version": "0.0.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "argparse": { 8 | "version": "1.0.9", 9 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", 10 | "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", 11 | "requires": { 12 | "sprintf-js": "1.0.3" 13 | } 14 | }, 15 | "commander": { 16 | "version": "2.11.0", 17 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", 18 | "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" 19 | }, 20 | "connect": { 21 | "version": "3.6.5", 22 | "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.5.tgz", 23 | "integrity": "sha1-+43ee6B2OHfQ7J352sC0tA5yx9o=", 24 | "requires": { 25 | "debug": "2.6.9", 26 | "finalhandler": "1.0.6", 27 | "parseurl": "1.3.2", 28 | "utils-merge": "1.0.1" 29 | } 30 | }, 31 | "copy-dir": { 32 | "version": "0.3.0", 33 | "resolved": "https://registry.npmjs.org/copy-dir/-/copy-dir-0.3.0.tgz", 34 | "integrity": "sha1-3rLcL6nJKQ7UfIQVWpmabUX1o1g=", 35 | "requires": { 36 | "mkdir-p": "0.0.7" 37 | } 38 | }, 39 | "d3": { 40 | "version": "4.11.0", 41 | "resolved": "https://registry.npmjs.org/d3/-/d3-4.11.0.tgz", 42 | "integrity": "sha512-o048nfmydnbt0ciIvCUDTq9p62rZYOXzl8cKps0XVzk+5nHgeXmAS7jU4nh+3v82pUyH7t/GFm1bJRX4oIAlPw==", 43 | "requires": { 44 | "d3-array": "1.2.1", 45 | "d3-axis": "1.0.8", 46 | "d3-brush": "1.0.4", 47 | "d3-chord": "1.0.4", 48 | "d3-collection": "1.0.4", 49 | "d3-color": "1.0.3", 50 | "d3-dispatch": "1.0.3", 51 | "d3-drag": "1.2.1", 52 | "d3-dsv": "1.0.7", 53 | "d3-ease": "1.0.3", 54 | "d3-force": "1.1.0", 55 | "d3-format": "1.2.0", 56 | "d3-geo": "1.8.1", 57 | "d3-hierarchy": "1.1.5", 58 | "d3-interpolate": "1.1.5", 59 | "d3-path": "1.0.5", 60 | "d3-polygon": "1.0.3", 61 | "d3-quadtree": "1.0.3", 62 | "d3-queue": "3.0.7", 63 | "d3-random": "1.1.0", 64 | "d3-request": "1.0.6", 65 | "d3-scale": "1.0.6", 66 | "d3-selection": "1.1.0", 67 | "d3-shape": "1.2.0", 68 | "d3-time": "1.0.7", 69 | "d3-time-format": "2.0.5", 70 | "d3-timer": "1.0.7", 71 | "d3-transition": "1.1.0", 72 | "d3-voronoi": "1.1.2", 73 | "d3-zoom": "1.6.0" 74 | } 75 | }, 76 | "d3-array": { 77 | "version": "1.2.1", 78 | "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz", 79 | "integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw==" 80 | }, 81 | "d3-axis": { 82 | "version": "1.0.8", 83 | "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.8.tgz", 84 | "integrity": "sha1-MacFoLU15ldZ3hQXOjGTMTfxjvo=" 85 | }, 86 | "d3-brush": { 87 | "version": "1.0.4", 88 | "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.4.tgz", 89 | "integrity": "sha1-AMLyOAGfJPbAoZSibUGhUw/+e8Q=", 90 | "requires": { 91 | "d3-dispatch": "1.0.3", 92 | "d3-drag": "1.2.1", 93 | "d3-interpolate": "1.1.5", 94 | "d3-selection": "1.1.0", 95 | "d3-transition": "1.1.0" 96 | } 97 | }, 98 | "d3-chord": { 99 | "version": "1.0.4", 100 | "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.4.tgz", 101 | "integrity": "sha1-fexPC6iG9xP+ERxF92NBT290yiw=", 102 | "requires": { 103 | "d3-array": "1.2.1", 104 | "d3-path": "1.0.5" 105 | } 106 | }, 107 | "d3-collection": { 108 | "version": "1.0.4", 109 | "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.4.tgz", 110 | "integrity": "sha1-NC39EoN8kJdPM/HMCnha6lcNzcI=" 111 | }, 112 | "d3-color": { 113 | "version": "1.0.3", 114 | "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.0.3.tgz", 115 | "integrity": "sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs=" 116 | }, 117 | "d3-dispatch": { 118 | "version": "1.0.3", 119 | "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz", 120 | "integrity": "sha1-RuFJHqqbWMNY/OW+TovtYm54cfg=" 121 | }, 122 | "d3-drag": { 123 | "version": "1.2.1", 124 | "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.1.tgz", 125 | "integrity": "sha512-Cg8/K2rTtzxzrb0fmnYOUeZHvwa4PHzwXOLZZPwtEs2SKLLKLXeYwZKBB+DlOxUvFmarOnmt//cU4+3US2lyyQ==", 126 | "requires": { 127 | "d3-dispatch": "1.0.3", 128 | "d3-selection": "1.1.0" 129 | } 130 | }, 131 | "d3-dsv": { 132 | "version": "1.0.7", 133 | "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.7.tgz", 134 | "integrity": "sha512-12szKhDhM/tM5U/Ch3hyJ7sMdcwPqMRmrUWitLLdPBMKO9Wuox95ezKZvemy/fxFbefLF/HIPKUmJMBLLuFDaQ==", 135 | "requires": { 136 | "commander": "2.11.0", 137 | "iconv-lite": "0.4.19", 138 | "rw": "1.3.3" 139 | } 140 | }, 141 | "d3-ease": { 142 | "version": "1.0.3", 143 | "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz", 144 | "integrity": "sha1-aL+8NJM4o4DETYrMT7wzBKotjA4=" 145 | }, 146 | "d3-force": { 147 | "version": "1.1.0", 148 | "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.0.tgz", 149 | "integrity": "sha512-2HVQz3/VCQs0QeRNZTYb7GxoUCeb6bOzMp/cGcLa87awY9ZsPvXOGeZm0iaGBjXic6I1ysKwMn+g+5jSAdzwcg==", 150 | "requires": { 151 | "d3-collection": "1.0.4", 152 | "d3-dispatch": "1.0.3", 153 | "d3-quadtree": "1.0.3", 154 | "d3-timer": "1.0.7" 155 | } 156 | }, 157 | "d3-format": { 158 | "version": "1.2.0", 159 | "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.2.0.tgz", 160 | "integrity": "sha1-a0gLqohohdRlHcJIqPSsnaFtsHo=" 161 | }, 162 | "d3-geo": { 163 | "version": "1.8.1", 164 | "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.8.1.tgz", 165 | "integrity": "sha512-PuvmWl1A1UXuaxcH55EGNhvMNAUBS0RQN2PAnxrZbDvDX56Xwkd+Yp1t1+ECkaJO3my+dnhxAyqAKMyyK+IFPQ==", 166 | "requires": { 167 | "d3-array": "1.2.1" 168 | } 169 | }, 170 | "d3-hierarchy": { 171 | "version": "1.1.5", 172 | "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz", 173 | "integrity": "sha1-ochFxC+Eoga88cAcAQmOpN2qeiY=" 174 | }, 175 | "d3-interpolate": { 176 | "version": "1.1.5", 177 | "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.1.5.tgz", 178 | "integrity": "sha1-aeCZ/zkhRxblY8muw+qdHqS4p58=", 179 | "requires": { 180 | "d3-color": "1.0.3" 181 | } 182 | }, 183 | "d3-path": { 184 | "version": "1.0.5", 185 | "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.5.tgz", 186 | "integrity": "sha1-JB6xhJvZ6egCHA0KeZ+KDo5EF2Q=" 187 | }, 188 | "d3-polygon": { 189 | "version": "1.0.3", 190 | "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.3.tgz", 191 | "integrity": "sha1-FoiOkCZGCTPysXllKtN4Ik04LGI=" 192 | }, 193 | "d3-quadtree": { 194 | "version": "1.0.3", 195 | "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz", 196 | "integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg=" 197 | }, 198 | "d3-queue": { 199 | "version": "3.0.7", 200 | "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz", 201 | "integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg=" 202 | }, 203 | "d3-random": { 204 | "version": "1.1.0", 205 | "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.0.tgz", 206 | "integrity": "sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM=" 207 | }, 208 | "d3-request": { 209 | "version": "1.0.6", 210 | "resolved": "https://registry.npmjs.org/d3-request/-/d3-request-1.0.6.tgz", 211 | "integrity": "sha512-FJj8ySY6GYuAJHZMaCQ83xEYE4KbkPkmxZ3Hu6zA1xxG2GD+z6P+Lyp+zjdsHf0xEbp2xcluDI50rCS855EQ6w==", 212 | "requires": { 213 | "d3-collection": "1.0.4", 214 | "d3-dispatch": "1.0.3", 215 | "d3-dsv": "1.0.7", 216 | "xmlhttprequest": "1.8.0" 217 | } 218 | }, 219 | "d3-scale": { 220 | "version": "1.0.6", 221 | "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.6.tgz", 222 | "integrity": "sha1-vOGdqA06DPQiyVQ64zIghiILNO0=", 223 | "requires": { 224 | "d3-array": "1.2.1", 225 | "d3-collection": "1.0.4", 226 | "d3-color": "1.0.3", 227 | "d3-format": "1.2.0", 228 | "d3-interpolate": "1.1.5", 229 | "d3-time": "1.0.7", 230 | "d3-time-format": "2.0.5" 231 | } 232 | }, 233 | "d3-selection": { 234 | "version": "1.1.0", 235 | "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.1.0.tgz", 236 | "integrity": "sha1-GZhoSJZIj4OcoDchI9o08dMYgJw=" 237 | }, 238 | "d3-shape": { 239 | "version": "1.2.0", 240 | "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.0.tgz", 241 | "integrity": "sha1-RdAVOPBkuv0F6j1tLLdI/YxB93c=", 242 | "requires": { 243 | "d3-path": "1.0.5" 244 | } 245 | }, 246 | "d3-time": { 247 | "version": "1.0.7", 248 | "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.7.tgz", 249 | "integrity": "sha1-lMr27bt4ebuAnQ0fdXK8SEgvcnA=" 250 | }, 251 | "d3-time-format": { 252 | "version": "2.0.5", 253 | "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.0.5.tgz", 254 | "integrity": "sha1-nXeAIE98kRnJFwsaVttN6aivly4=", 255 | "requires": { 256 | "d3-time": "1.0.7" 257 | } 258 | }, 259 | "d3-timer": { 260 | "version": "1.0.7", 261 | "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.7.tgz", 262 | "integrity": "sha512-vMZXR88XujmG/L5oB96NNKH5lCWwiLM/S2HyyAQLcjWJCloK5shxta4CwOFYLZoY3AWX73v8Lgv4cCAdWtRmOA==" 263 | }, 264 | "d3-transition": { 265 | "version": "1.1.0", 266 | "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.0.tgz", 267 | "integrity": "sha1-z8hcdOUjkyQpBUZiNXKZBWDDlm8=", 268 | "requires": { 269 | "d3-color": "1.0.3", 270 | "d3-dispatch": "1.0.3", 271 | "d3-ease": "1.0.3", 272 | "d3-interpolate": "1.1.5", 273 | "d3-selection": "1.1.0", 274 | "d3-timer": "1.0.7" 275 | } 276 | }, 277 | "d3-voronoi": { 278 | "version": "1.1.2", 279 | "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", 280 | "integrity": "sha1-Fodmfo8TotFYyAwUgMWinLDYlzw=" 281 | }, 282 | "d3-zoom": { 283 | "version": "1.6.0", 284 | "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.6.0.tgz", 285 | "integrity": "sha512-viq+6rXA9JQY1wD+gpDEdlOtCeJ6IfcsNT2aVr31VTjIAIhYlO0YurQ80yDRZeMJw5P/e6nVPhGJF9D9Ade5Og==", 286 | "requires": { 287 | "d3-dispatch": "1.0.3", 288 | "d3-drag": "1.2.1", 289 | "d3-interpolate": "1.1.5", 290 | "d3-selection": "1.1.0", 291 | "d3-transition": "1.1.0" 292 | } 293 | }, 294 | "debug": { 295 | "version": "2.6.9", 296 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 297 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 298 | "requires": { 299 | "ms": "2.0.0" 300 | } 301 | }, 302 | "depd": { 303 | "version": "1.1.1", 304 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 305 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 306 | }, 307 | "destroy": { 308 | "version": "1.0.4", 309 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 310 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 311 | }, 312 | "ee-first": { 313 | "version": "1.1.1", 314 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 315 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 316 | }, 317 | "encodeurl": { 318 | "version": "1.0.1", 319 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 320 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 321 | }, 322 | "escape-html": { 323 | "version": "1.0.3", 324 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 325 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 326 | }, 327 | "etag": { 328 | "version": "1.8.1", 329 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 330 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 331 | }, 332 | "finalhandler": { 333 | "version": "1.0.6", 334 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", 335 | "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", 336 | "requires": { 337 | "debug": "2.6.9", 338 | "encodeurl": "1.0.1", 339 | "escape-html": "1.0.3", 340 | "on-finished": "2.3.0", 341 | "parseurl": "1.3.2", 342 | "statuses": "1.3.1", 343 | "unpipe": "1.0.0" 344 | } 345 | }, 346 | "find": { 347 | "version": "0.1.7", 348 | "resolved": "https://registry.npmjs.org/find/-/find-0.1.7.tgz", 349 | "integrity": "sha1-yGyHrxqxjyIrvjjeyGy8dg0Wpvs=", 350 | "requires": { 351 | "traverse-chain": "0.1.0" 352 | } 353 | }, 354 | "find-in-files": { 355 | "version": "0.4.0", 356 | "resolved": "https://registry.npmjs.org/find-in-files/-/find-in-files-0.4.0.tgz", 357 | "integrity": "sha1-nNS2ejRZxUOB9Wzi8daTFq17sBE=", 358 | "requires": { 359 | "find": "0.1.7", 360 | "q": "1.5.0" 361 | } 362 | }, 363 | "force-horse": { 364 | "version": "1.0.12", 365 | "resolved": "https://registry.npmjs.org/force-horse/-/force-horse-1.0.12.tgz", 366 | "integrity": "sha512-VY2J9+Rt7j4voo5tQdqkJOCHebVt2+brbtZdzkajFTbrAi4IHsnZVMeP2SH7AY0k2qDSxln/oWnk8F5R9FDxKQ==", 367 | "requires": { 368 | "d3": "4.11.0", 369 | "performance-now": "2.1.0" 370 | } 371 | }, 372 | "fresh": { 373 | "version": "0.5.2", 374 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 375 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 376 | }, 377 | "http-errors": { 378 | "version": "1.6.2", 379 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 380 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 381 | "requires": { 382 | "depd": "1.1.1", 383 | "inherits": "2.0.3", 384 | "setprototypeof": "1.0.3", 385 | "statuses": "1.3.1" 386 | } 387 | }, 388 | "iconv-lite": { 389 | "version": "0.4.19", 390 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 391 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 392 | }, 393 | "inherits": { 394 | "version": "2.0.3", 395 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 396 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 397 | }, 398 | "mime": { 399 | "version": "1.4.1", 400 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 401 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 402 | }, 403 | "mkdir-p": { 404 | "version": "0.0.7", 405 | "resolved": "https://registry.npmjs.org/mkdir-p/-/mkdir-p-0.0.7.tgz", 406 | "integrity": "sha1-JMXb4m2jqZ7xWKHu+aXC3Z3laDw=" 407 | }, 408 | "ms": { 409 | "version": "2.0.0", 410 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 411 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 412 | }, 413 | "ncp": { 414 | "version": "2.0.0", 415 | "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", 416 | "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=" 417 | }, 418 | "on-finished": { 419 | "version": "2.3.0", 420 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 421 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 422 | "requires": { 423 | "ee-first": "1.1.1" 424 | } 425 | }, 426 | "open": { 427 | "version": "0.0.5", 428 | "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", 429 | "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=" 430 | }, 431 | "parseurl": { 432 | "version": "1.3.2", 433 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 434 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 435 | }, 436 | "performance-now": { 437 | "version": "2.1.0", 438 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 439 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 440 | }, 441 | "q": { 442 | "version": "1.5.0", 443 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.0.tgz", 444 | "integrity": "sha1-3QG6ydBtMObyGa7LglPunr3DCPE=" 445 | }, 446 | "range-parser": { 447 | "version": "1.2.0", 448 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 449 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 450 | }, 451 | "rw": { 452 | "version": "1.3.3", 453 | "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", 454 | "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" 455 | }, 456 | "send": { 457 | "version": "0.16.1", 458 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", 459 | "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", 460 | "requires": { 461 | "debug": "2.6.9", 462 | "depd": "1.1.1", 463 | "destroy": "1.0.4", 464 | "encodeurl": "1.0.1", 465 | "escape-html": "1.0.3", 466 | "etag": "1.8.1", 467 | "fresh": "0.5.2", 468 | "http-errors": "1.6.2", 469 | "mime": "1.4.1", 470 | "ms": "2.0.0", 471 | "on-finished": "2.3.0", 472 | "range-parser": "1.2.0", 473 | "statuses": "1.3.1" 474 | } 475 | }, 476 | "serve-static": { 477 | "version": "1.13.1", 478 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", 479 | "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", 480 | "requires": { 481 | "encodeurl": "1.0.1", 482 | "escape-html": "1.0.3", 483 | "parseurl": "1.3.2", 484 | "send": "0.16.1" 485 | } 486 | }, 487 | "setprototypeof": { 488 | "version": "1.0.3", 489 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 490 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 491 | }, 492 | "sprintf-js": { 493 | "version": "1.0.3", 494 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 495 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" 496 | }, 497 | "statuses": { 498 | "version": "1.3.1", 499 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 500 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 501 | }, 502 | "traverse-chain": { 503 | "version": "0.1.0", 504 | "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", 505 | "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=" 506 | }, 507 | "unpipe": { 508 | "version": "1.0.0", 509 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 510 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 511 | }, 512 | "utils-merge": { 513 | "version": "1.0.1", 514 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 515 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 516 | }, 517 | "xmlhttprequest": { 518 | "version": "1.8.0", 519 | "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", 520 | "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" 521 | } 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-viewer", 3 | "description": "A simple CLI tool to view the graph of events and actions", 4 | "version": "0.0.2", 5 | "license": "MIT", 6 | "author": "Amit Moryossef ", 7 | "scripts": { 8 | "patch-release": "npm version patch && npm publish && git push --follow-tags", 9 | "start": "node ./bin/run.js -s example" 10 | }, 11 | "bin": { 12 | "rv": "./bin/run.js" 13 | }, 14 | "dependencies": { 15 | "argparse": "^1.0.9", 16 | "connect": "^3.6.5", 17 | "find-in-files": "^0.4.0", 18 | "force-horse": "^1.0.12", 19 | "ncp": "^2.0.0", 20 | "open": "0.0.5", 21 | "serve-static": "^1.13.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /viewer/effects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "fetchData$", 4 | "ofType": [ 5 | "FetchDataAction" 6 | ], 7 | "action": [ 8 | "FetchDataSuccessAction?", 9 | "FetchDataFailureAction?" 10 | ] 11 | }, 12 | { 13 | "name": "fetchDataClientError$", 14 | "ofType": [ 15 | "FetchDataFailureAction" 16 | ], 17 | "filter": "If error is 4xx (Client)", 18 | "action": [ 19 | "ToastAction" 20 | ] 21 | }, 22 | { 23 | "name": "fetchDataServerError$", 24 | "ofType": [ 25 | "FetchDataFailureAction" 26 | ], 27 | "filter": "If error is 5xx (Server)", 28 | "action": [ 29 | "ToastAction" 30 | ] 31 | }, 32 | { 33 | "name": "fetchDataSuccess$", 34 | "ofType": [ 35 | "FetchDataSuccessAction" 36 | ], 37 | "action": [ 38 | "UpdateViewAction", 39 | "ToastAction" 40 | ] 41 | }, 42 | { 43 | "name": "fetchDataSuccessSetPin$", 44 | "ofType": [ 45 | "FetchDataSuccessAction" 46 | ], 47 | "filter": "If in pinning mode", 48 | "action": [ 49 | "SetPinAction" 50 | ] 51 | } 52 | ] -------------------------------------------------------------------------------- /viewer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Effects map 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /viewer/main.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_COLORS = { 2 | filters: '#F5A45D', 3 | action: '#FF0000', 4 | effects: '#86B342', 5 | }; 6 | 7 | const forceHorse = document.querySelector('force-horse'); 8 | 9 | forceHorse.setConfig({ 10 | showLabels: true, 11 | showNodeWeight: true, 12 | showEdgeWeight: true, 13 | showFilterButton: true, 14 | showLabelsButton: true, 15 | showNodeWeightButton: true, 16 | showEdgeWeightButton: true, 17 | useEdgesWeights: true, 18 | forceParameters: { 19 | '#charge': -350, 20 | '#linkStrength': 1, 21 | '#gravity': 0.2, 22 | '#linkDistance': 10, 23 | '#friction': 0.5 24 | } 25 | }); 26 | 27 | class EffectsGraph { 28 | constructor() { 29 | this.data = fetch('effects.json') 30 | .then(res => res.json()); 31 | } 32 | 33 | isActionOptional(action) { 34 | return action[action.length - 1] === '?'; 35 | } 36 | 37 | getActions(effects) { 38 | let actions = []; 39 | effects.forEach(effect => { 40 | effect.ofType.forEach(a => actions = actions.concat(a)); 41 | if (effect.action) { 42 | effect.action.forEach(a => actions = actions.concat(a)); 43 | } 44 | }); 45 | 46 | actions = actions.map(a => { 47 | if (this.isActionOptional(a)) { 48 | return a.substr(0, a.length - 1); 49 | } 50 | return a; 51 | }); 52 | 53 | return Array.from(new Set(actions)); 54 | } 55 | 56 | getGraph(effects) { 57 | console.log(effects); 58 | 59 | const actions = this.getActions(effects); 60 | const filters = effects.filter(e => e.filter); 61 | 62 | 63 | let nodes = [], edges = []; 64 | actions.forEach(action => { 65 | nodes.push({ 66 | label: action, 67 | id: action, 68 | color: DEFAULT_COLORS.action, 69 | shape: 'circle' 70 | }); 71 | }); 72 | 73 | filters.forEach(effect => { 74 | nodes.push({ 75 | label: effect.filter, 76 | id: 'filter-' + effect.index, 77 | color: DEFAULT_COLORS.filters, 78 | shape: 'triangle' 79 | }); 80 | 81 | effect.ofType.forEach(action => { 82 | edges.push({ 83 | source: action, 84 | target: 'filter-' + effect.index, 85 | weight: 5 86 | }); 87 | }); 88 | 89 | edges.push({ 90 | source: 'filter-' + effect.index, 91 | target: effect.index, 92 | weight: 5, 93 | color: '#6FB1FC' 94 | }); 95 | }); 96 | 97 | effects.forEach(effect => { 98 | nodes.push({ 99 | label: effect.name, 100 | id: effect.index, 101 | color: DEFAULT_COLORS.effects, 102 | shape: 'square' 103 | }); 104 | 105 | if (effect.action) { 106 | effect.action.forEach(action => { 107 | if (this.isActionOptional(action)) { 108 | action = action.substr(0, action.length - 1); 109 | edges.push({source: effect.index, target: action, color: '#eef442'}); 110 | } else { 111 | edges.push({source: effect.index, target: action, color: '#6FB1FC'}); 112 | } 113 | }); 114 | } 115 | 116 | if (!effect.filter) { 117 | effect.ofType.forEach(action => { 118 | edges.push({ 119 | source: action, 120 | target: effect.index, 121 | color: '#EDA1ED' 122 | }); 123 | }); 124 | } 125 | }); 126 | 127 | return {nodes, links: edges}; 128 | } 129 | 130 | drawGraph() { 131 | this.data.then(effects => { 132 | effects = effects.map((effect, index) => ({...effect, index})); 133 | 134 | forceHorse.setData(this.getGraph(effects)); 135 | 136 | console.log(this.getGraph(effects)); 137 | console.log(forceHorse); 138 | }); 139 | } 140 | } 141 | 142 | let effectsGraph = new EffectsGraph(); 143 | document.addEventListener('DOMContentLoaded', () => effectsGraph.drawGraph(), false); 144 | -------------------------------------------------------------------------------- /viewer/style.css: -------------------------------------------------------------------------------- 1 | html, body, force-horse { 2 | height: 100%; 3 | width: 100%; 4 | } 5 | 6 | #search-text { 7 | color: black; 8 | font: 15px arial, sans-serif; 9 | font-weight: 900; 10 | padding: 0 10px; 11 | } --------------------------------------------------------------------------------