Skip to content
This repository was archived by the owner on May 31, 2021. It is now read-only.

Commit 17b0aa5

Browse files
authored
Swardley script (#192)
* Some initial code * sorting + preserving links * import * refactored server side * ensure proper response is send * reload workspace after uploading a map
1 parent 5b0ce3d commit 17b0aa5

12 files changed

+573
-4
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"react-router-bootstrap": "0.24.3",
5555
"react-router-dom": "4.2.2",
5656
"react-twitter-widgets": "^1.2.0",
57+
"sanitize-filename": "^1.6.1",
5758
"sendgrid": "5.2.3",
5859
"socket.io": "2.0.3",
5960
"socket.io-client": "2.0.3",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/* Copyright 2017 Krzysztof Daniel.
2+
Licensed under the Apache License, Version 2.0 (the "License");
3+
you may not use this file except in compliance with the License.
4+
You may obtain a copy of the License at
5+
http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software
7+
distributed under the License is distributed on an "AS IS" BASIS,
8+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
See the License for the specific language governing permissions and
10+
limitations under the License.*/
11+
/*jshint esversion: 6 */
12+
13+
14+
var mongoose = require('mongoose');
15+
var Schema = mongoose.Schema;
16+
var ObjectId = mongoose.Types.ObjectId;
17+
var modelLogger = require('./../../log').getLogger('MapSchema');
18+
var _ = require('underscore');
19+
var q = require('q');
20+
21+
22+
function makeDependencyTo(jsonLink, id, node){
23+
if (!node) {
24+
console.error('Could not establish', jsonLink, id);
25+
return null;
26+
}
27+
// this calls save
28+
return node.makeDependencyTo(id);
29+
}
30+
31+
module.exports.mapImport = function(Node, workspace, incomingMapJSON) {
32+
if (!workspace) {
33+
return null;
34+
}
35+
return workspace
36+
/* create an empty map with just the title */
37+
.createAMap({
38+
name: incomingMapJSON.title
39+
})
40+
/* create all the imported nodes */
41+
.then(function(emptyMap) {
42+
var promises = [];
43+
44+
for (let i = 0; i < incomingMapJSON.elements.length; i++) {
45+
let currentNode = incomingMapJSON.elements[i];
46+
promises.push(new Node({
47+
name: currentNode.name,
48+
x: 1 - currentNode.maturity,
49+
y: 1 - currentNode.visibility,
50+
type: "INTERNAL",
51+
workspace: emptyMap.workspace,
52+
parentMap: emptyMap._id,
53+
description: "",
54+
inertia: 0,
55+
responsiblePerson: "",
56+
constraint: 0,
57+
foreignKey: currentNode.id // this needs to be stored for export and external diffs
58+
}).save());
59+
}
60+
61+
/* once nodes are created, push them into nodes array */
62+
return q.allSettled(promises).then(function(results) {
63+
for (let i = 0; i < results.length; i++) {
64+
emptyMap.nodes.push(results[i].value);
65+
}
66+
return emptyMap.save();
67+
});
68+
})
69+
.then(function(mapWithNodes) {
70+
// at this point we have a map with nodes and foreign keys
71+
// we need to translate json connections (based on foreign keys)
72+
// to use real _id we have in db
73+
74+
75+
// iteration one, build a lookup table connecting foreignKey with _ids
76+
// created by db during save in previous chapter
77+
let foreignKeyMap = {};
78+
for (let i = 0; i < mapWithNodes.nodes.length; i++) {
79+
let foreignKey = mapWithNodes.nodes[i].foreignKey;
80+
// the node may or not be expanded. Try to use _id (expanded),
81+
// and if there is none, use the object (hopefully _id).
82+
foreignKeyMap[foreignKey] = mapWithNodes.nodes[i]._id || mapWithNodes.nodes[i];
83+
}
84+
85+
// iterate over JSON links and establish appropriate connections
86+
// use foreignKey to locate real dependency _id
87+
let promises = [];
88+
for (let i = 0; i < incomingMapJSON.links.length; i++) {
89+
promises.push(
90+
Node
91+
.findOne(foreignKeyMap[incomingMapJSON.links[i].start])
92+
.exec()
93+
.then(
94+
makeDependencyTo.bind(
95+
this,
96+
incomingMapJSON.links[i], //for debugging only
97+
foreignKeyMap[incomingMapJSON.links[i].end]) //the dep to be made
98+
)
99+
);
100+
}
101+
// and once all connections are saved, return the fully imported map
102+
return q.allSettled(promises).then(function(r) {
103+
return mapWithNodes.defaultPopulate();
104+
});
105+
});
106+
};
107+
108+
module.exports.mapExport = function(map) {
109+
let newMap = {
110+
title: '',
111+
elements: [],
112+
links: []
113+
};
114+
newMap.title = map.name;
115+
116+
let foreignKeyMap = {};
117+
for (let i = 0; i < map.nodes.length; i++) {
118+
foreignKeyMap['' + map.nodes[i]._id] = map.nodes[i].foreignKey;
119+
}
120+
121+
for (let i = 0; i < map.nodes.length; i++) {
122+
let node = map.nodes[i];
123+
//translate nodes
124+
newMap.elements.push({
125+
id: '' + (node.foreignKey || node._id),
126+
name: node.name,
127+
visibility: 1 - node.y, //atlas uses screen based positioning
128+
maturity: 1 - node.x
129+
});
130+
131+
//translate links
132+
for (let j = 0; j < node.outboundDependencies.length; j++) {
133+
let targetId = node.outboundDependencies[j];
134+
newMap.links.push({
135+
start: '' + (foreignKeyMap['' + node._id] || node._id),
136+
end: '' + (foreignKeyMap[targetId] || targetId)
137+
});
138+
}
139+
}
140+
141+
// sort nodes to avoid unnecessary diffs
142+
newMap.elements.sort(function(a, b) {
143+
let aName = a.id;
144+
let bName = b.id;
145+
if (aName < bName) {
146+
return -1;
147+
}
148+
if (aName > bName) {
149+
return 1;
150+
}
151+
return 0;
152+
});
153+
154+
newMap.links.sort(function(a, b) {
155+
if (a.start < b.start) {
156+
return -1;
157+
}
158+
if (a.start > b.start) {
159+
return 1;
160+
}
161+
if (a.end < b.end) {
162+
return -1;
163+
}
164+
if (a.end > b.end) {
165+
return 1;
166+
}
167+
return 0;
168+
});
169+
170+
return newMap;
171+
};

src-server/workspace/model/map-schema.js

+6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ var ObjectId = mongoose.Types.ObjectId;
1717
var modelLogger = require('./../../log').getLogger('MapSchema');
1818
var _ = require('underscore');
1919
var q = require('q');
20+
let mapExport = require('./map-import-export').mapExport;
2021

2122
var wardleyMap = {};
2223

@@ -704,6 +705,11 @@ module.exports = function(conn) {
704705
});
705706
};
706707

708+
709+
_MapSchema.methods.exportJSON = function(){
710+
return mapExport(this);
711+
};
712+
707713
/*
708714
* This method is to clean up the state after removing a map, that is:
709715
* - remove nodes belonging to the map

src-server/workspace/model/node-schema.js

+7
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ module.exports = function(conn){
123123
}
124124

125125
var NodeSchema = new Schema({
126+
/*
127+
EXPERIMENTAL - subject to change without notification.
128+
this is a special field that stores the ID of an imported map.
129+
Atlas2 nodes must have globally individual ids, and this will not be
130+
the case if the map is exported and then imported.
131+
*/
132+
foreignKey: Schema.Types.String,
126133
workspace: {
127134
type: Schema.Types.ObjectId,
128135
ref: 'Workspace'

src-server/workspace/model/workspace-schema.js

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ var Boolean = Schema.Types.Boolean;
1919
var Number = Schema.Types.Number;
2020
var deduplicationLogger = require('./../../log').getLogger('deduplication');
2121
var variantLogger = require('./../../log').getLogger('variants');
22+
let mapImport = require('./map-import-export').mapImport;
2223
/**
2324
* Workspace, referred also as an organization, is a group of maps that all
2425
* refer to the same subject, for example to the company. Many people can work
@@ -1328,6 +1329,11 @@ module.exports = function(conn) {
13281329
});
13291330
};
13301331

1332+
workspaceSchema.methods.importJSON = function(json){
1333+
let Node = require('./node-schema')(conn);
1334+
return mapImport(Node, this, json);
1335+
};
1336+
13311337

13321338
workspaceSchema.methods.modifyTimeslice = function(sourceTimeSliceId, name, description, current) {
13331339
var WardleyMap = require('./map-schema')(conn);

src-server/workspace/workspace-router.js

+48
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,26 @@ module.exports = function(authGuardian, mongooseConnection) {
246246
}, defaultErrorHandler.bind(this, res));
247247
});
248248

249+
module.router.get('/map/:mapID/json', authGuardian.authenticationRequired, function(req, res) {
250+
var owner = getUserIdFromReq(req);
251+
WardleyMap.findOne({
252+
_id: req.params.mapID,
253+
archived: false
254+
})
255+
.then(checkAccess.bind(this, req.params.mapID, owner))
256+
.then(function(result) {
257+
return result.defaultPopulate();
258+
})
259+
.done(function(map) {
260+
let json = map.exportJSON();
261+
res.type('json').json(json);
262+
track(owner, 'export_map', {
263+
'id': map._id,
264+
'name': map.name
265+
});
266+
}, defaultErrorHandler.bind(this, res));
267+
});
268+
249269
module.router.get('/map/:mapID/diff', authGuardian.authenticationRequired, function(req, res) {
250270
var owner = getUserIdFromReq(req);
251271
WardleyMap.findOne({
@@ -546,6 +566,34 @@ module.exports = function(authGuardian, mongooseConnection) {
546566
}, defaultErrorHandler.bind(this, res));
547567
});
548568

569+
module.router.post('/map/json', authGuardian.authenticationRequired, function(req, res) {
570+
var editor = getUserIdFromReq(req);
571+
let incomingMap = req.body.map;
572+
if(!incomingMap.elements || !incomingMap.links || !incomingMap.title){
573+
return res.status(400).send();
574+
}
575+
Workspace
576+
.findOne({
577+
_id: new ObjectId(req.body.workspaceID),
578+
owner: editor
579+
})
580+
.exec()
581+
.then(function(workspace){
582+
return workspace.importJSON(incomingMap);
583+
})
584+
.done(function(result) {
585+
res.json({
586+
map: result
587+
});
588+
if(result){
589+
track(editor,'import_map',{
590+
'id' : result._id,
591+
'name' : req.body.name
592+
}, req.body);
593+
}
594+
}, defaultErrorHandler.bind(this, res));
595+
});
596+
549597
module.router.post('/variant/:timesliceId/map', authGuardian.authenticationRequired, function(req, res) {
550598
var editor = getUserIdFromReq(req);
551599
Workspace

0 commit comments

Comments
 (0)