├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── codecov.yml
├── index.js
├── package.json
├── preview.png
├── template.ejs
└── test
└── unit-test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | .DS_Store
36 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: false
3 | env:
4 | - CODECOV_TOKEN="70be989e-dd91-4fd3-b1fd-7a52ec06188f"
5 | node_js:
6 | - "6"
7 | - "5"
8 | - "4"
9 |
10 | after_success:
11 | - npm run coverage
12 | - codecov
13 | - cat ./coverage/lcov.info | coveralls
14 | - bash <(curl -s https://codecov.io/bash)
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 yantrashala
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 | # loopback-component-visualizer
2 |
3 | [](https://travis-ci.org/yantrashala/loopback-component-visualizer) [](https://david-dm.org/yantrashala/loopback-component-visualizer.svg) [](https://codecov.io/gh/yantrashala/loopback-component-visualizer) [](https://github.com/yantrashala/loopback-component-visualizer/issues)
4 |
5 | ### Introduction
6 |
7 | Visualizing a model is sometimes a difficult task. When the data model gets larger, it becomes even more difficult to understand how models relate to each other.
8 |
9 | loopback-component-visualizer helps you in creating a model diagram with a representation of all the properties, methods and relationships of your models for your loopback application.
10 |
11 | ### Table of contents
12 | * Installation
13 | * Usage
14 |
15 | #### Installation
16 |
17 | Install the module in your loopback application folder.
18 |
19 | ```sh
20 | $ npm install loopback-component-visualizer --save
21 | ```
22 |
23 | #### Usage
24 |
25 | Inside your component-config.json, add the loopback-component-visualizer and a '/visualize' api will be mounted to your server.
26 |
27 | You can browse @ http://host:port/visualize
28 |
29 | ```sh
30 | "loopback-component-visualizer": {
31 | "mountPath": "/visualize"
32 | }
33 | ```
34 |
35 | You can also get the options passed to the visualizer as below
36 |
37 | ```sh
38 | loopbackApp.get('loopback-component-visualizer')
39 | ```
40 |
41 | #### Preview
42 |
43 | 
44 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | comment: false
2 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = visualize;
4 |
5 | var _defaults = require('lodash').defaults;
6 |
7 | function visualize(loopbackApp, options) {
8 | options = _defaults({}, options, {
9 | mountPath: '/visualize',
10 | });
11 | loopbackApp.use(options.mountPath, createDiagram(loopbackApp, options));
12 | loopbackApp.set('loopback-component-visualizer', options);
13 | }
14 |
15 | function createDiagram(app, options) {
16 |
17 | var finalObj = prepareJson(app);
18 |
19 | return respond(finalObj, app);
20 | }
21 |
22 | function prepareJson(app) {
23 |
24 | var models = (app && app.models && app.models()) || {};
25 | var remotes = (app && app.remoteObjects && app.remoteObjects()) || {};
26 | var level = 0;
27 | var x = 0;
28 | var propArr = [];
29 | var edgeArr = [];
30 | var idx = 0;
31 | var modIdxObj = {};
32 | var finalObj = {};
33 |
34 | for (var i in models) {
35 | modIdxObj[models[i].modelName] = i;
36 | }
37 |
38 | models.length && models.forEach(function(Model) {
39 | var remotes = [];
40 | Model.sharedClass.methods().forEach(function(method) {
41 | remotes.push(method.name);
42 | });
43 | var modelDefinition = app.models[Model.modelName].definition;
44 | if (modelDefinition && modelDefinition.properties) {
45 | var propString = Object.keys(modelDefinition.properties).toString().split(',').join('
');
46 |
47 | var propObj = {};
48 | propObj.id = idx;
49 | propObj.label = Model.modelName;
50 | propObj.props = '- ' + Object.keys(modelDefinition.properties).toString().split(',').join('
- \n').replace('"', '') + '
';
51 | propObj.level = level;
52 | propObj.remotes = remotes && '- ' + remotes.toString().split(',').join('
- \n').replace('"', '') + '
';
53 | propObj.title = '' + propObj.label + '
Props:
\n' + propObj.props + '\nRemotes:
' + propObj.remotes
54 | propArr.push(propObj);
55 | x++;
56 | if (x == 5) {
57 | x = 0;
58 | level++;
59 | }
60 | }
61 | //edges
62 | if (Model.settings.relations) {
63 | var relArr = Object.keys(Model.settings.relations);
64 | for (var rel in relArr) {
65 | if (Model.settings.relations[relArr[rel]].model) {
66 | var edgeObj = {};
67 | edgeObj.from = modIdxObj[Model.modelName];
68 | edgeObj.to = modIdxObj[Model.settings.relations[relArr[rel]].model];
69 | edgeObj.label = relArr[rel] || '';
70 | edgeObj.title = JSON.stringify(Model.settings.relations[relArr[rel]]);
71 | edgeObj.arrows = 'to';
72 | edgeArr.push(edgeObj);
73 | }
74 | }
75 | }
76 |
77 |
78 | idx++;
79 | });
80 |
81 |
82 | finalObj.nodes = propArr;
83 | finalObj.edges = edgeArr;
84 |
85 | return finalObj;
86 | }
87 |
88 | function respond(finalObj, app) {
89 |
90 | return function(req, res, next) {
91 | var data = finalObj || {};
92 | var render = app.loopback.template(__dirname + '/template.ejs');
93 | var html = render(data);
94 | res.status(200).send(html);
95 | };
96 |
97 | }
98 |
99 | module.exports.prepareJson = prepareJson;
100 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "loopback-component-visualizer",
3 | "version": "1.2.1",
4 | "description": "Loopback component to visualize models and their relationships",
5 | "main": "./index.js",
6 | "scripts": {
7 | "test": "mocha",
8 | "coverage": "istanbul cover _mocha"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/yantrashala/loopback-component-visualizer.git"
13 | },
14 | "contributors": ["prashantbaid", "pbhadauria2000", "mennu"],
15 | "author": "prashantbaid, pbhadauria2000",
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/yantrashala/loopback-component-visualizer/issues"
19 | },
20 | "homepage": "https://github.com/yantrashala/loopback-component-visualizer#readme",
21 | "dependencies": {
22 | "lodash": "^4.12.0"
23 | },
24 | "devDependencies": {
25 | "chai": "^3.5.0",
26 | "loopback": "^2.28.0",
27 | "loopback-datasource-juggler": "^2.46.0",
28 | "mocha": "^2.4.5",
29 | "supertest": "^1.2.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yantrashala/loopback-component-visualizer/09010b036a867962abd363af6f44aeb33f79be6f/preview.png
--------------------------------------------------------------------------------
/template.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Domain Model Visualizer
5 |
6 |
7 |
8 |
9 |
84 |
85 |
86 |
91 |
92 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/test/unit-test.js:
--------------------------------------------------------------------------------
1 | var loopback = require('loopback');
2 | var visualize = require('../');
3 | var request = require('supertest');
4 | var expect = require('chai').expect;
5 | var application;
6 |
7 | describe('checkFinalObject', function() {
8 | beforeEach(function() {
9 | application = loopback({localRegistry: true, loadBuiltinModels : true});
10 | var customerProperties = {
11 | name: {type: String, required: true}
12 | };
13 | var customerOptions = {
14 | "relations": {
15 | "address": {
16 | "type": "embedsOne",
17 | "model": "Address",
18 | "property": "billingAddress",
19 | "options": {
20 | "validate": true,
21 | "forceId": false
22 | }
23 | }
24 | }
25 | }
26 |
27 | var addressProperties = {
28 | street: {type: String, required: true},
29 | city: {type: String, required: true},
30 | state: {type: String, required: true}
31 | };
32 |
33 | var Address = loopback.Model.extend('Address', addressProperties);
34 |
35 | application.model(Address);
36 |
37 | var Customer = loopback.Model.extend('Customer', customerProperties, customerOptions);
38 |
39 | Customer.greet = function(msg, cb) {
40 | cb(null, 'Greetings... ' + msg);
41 | }
42 |
43 | Customer.remoteMethod(
44 | 'greet',
45 | {
46 | accepts: {arg: 'msg', type: 'string'},
47 | returns: {arg: 'greeting', type: 'string'}
48 | }
49 | );
50 |
51 | application.model(Customer);
52 |
53 | });
54 |
55 | it('should compare the finalObject for address and customer.', function(done) {
56 | var finalObj = visualize.prepareJson(application);
57 | expect(finalObj.nodes).to.have.lengthOf(2);
58 | expect(finalObj.nodes[0]).to.contain.all.keys({'label': 'Address'});
59 | expect(finalObj.nodes[0]).to.contain.all.keys({'label': 'Customer'});
60 | done();
61 | });
62 |
63 | it('should compare the finalObject by sending an empty object.', function(done) {
64 | var finalObj = visualize.prepareJson({});
65 | expect(finalObj.nodes).to.have.lengthOf(0);
66 | expect(finalObj.edges).to.have.lengthOf(0);
67 | done();
68 | });
69 |
70 | });
71 |
72 | describe('visualize', function() {
73 | describe('with default config', function() {
74 | beforeEach(createLoopbackApplication());
75 |
76 | it('should redirect to /visualize/', function(done) {
77 | request(this.app)
78 | .get('/visualize')
79 | .expect(200)
80 | .end(done);
81 | });
82 |
83 |
84 | });
85 | });
86 |
87 |
88 | function createLoopbackApplication(basePath) {
89 | return function(done) {
90 | var app = this.app = loopback({localRegistry: true, loadBuiltinModels : true});
91 |
92 | var customerProperties = {
93 | name: {type: String, required: true}
94 | };
95 | var customerOptions = {
96 | "relations": {
97 | "address": {
98 | "type": "embedsOne",
99 | "model": "Address",
100 | "property": "billingAddress",
101 | "options": {
102 | "validate": true,
103 | "forceId": false
104 | }
105 | }
106 | }
107 | }
108 |
109 |
110 |
111 | var addressProperties = {
112 | street: {type: String, required: true},
113 | city: {type: String, required: true},
114 | state: {type: String, required: true}
115 | };
116 |
117 | var Address = loopback.Model.extend('Address', addressProperties);
118 |
119 | app.model(Address);
120 |
121 | var BillingAddress = loopback.Model.extend('BillingAddress', addressProperties);
122 |
123 | app.model(BillingAddress);
124 |
125 | var OfficeAddress = loopback.Model.extend('OfficeAddress', addressProperties);
126 |
127 | app.model(OfficeAddress);
128 |
129 | var bookProperties = {
130 | name: {type: String, required: true}
131 | };
132 |
133 | var Book = loopback.Model.extend('Book', bookProperties);
134 |
135 | app.model(Book);
136 |
137 | var Address = loopback.Model.extend('Address', addressProperties);
138 |
139 | app.model(Address);
140 |
141 | var Address = loopback.Model.extend('Address', addressProperties);
142 |
143 | app.model(Address);
144 |
145 | var Customer = loopback.Model.extend('Customer', customerProperties, customerOptions);
146 |
147 | Customer.greet = function(msg, cb) {
148 | cb(null, 'Greetings... ' + msg);
149 | }
150 |
151 | Customer.remoteMethod(
152 | 'greet',
153 | {
154 | accepts: {arg: 'msg', type: 'string'},
155 | returns: {arg: 'greeting', type: 'string'}
156 | }
157 | );
158 |
159 | app.model(Customer);
160 |
161 | visualize(app);
162 | app.use(app.get('restApiRoot') || '/', loopback.rest());
163 | done();
164 | };
165 | }
166 |
--------------------------------------------------------------------------------