metamaps--metamaps/frontend/src/Metamaps/Import.js

371 lines
11 KiB
JavaScript
Raw Normal View History

2016-03-27 11:43:30 +00:00
/* global Metamaps, $ */
import parse from 'csv-parse'
import _ from 'lodash'
2016-09-22 09:36:47 +00:00
import Active from './Active'
2016-10-01 04:43:02 +00:00
import AutoLayout from './AutoLayout'
2016-09-22 10:31:56 +00:00
import GlobalUI from './GlobalUI'
2016-09-22 09:36:47 +00:00
import Map from './Map'
2016-09-22 10:31:56 +00:00
import Synapse from './Synapse'
import Topic from './Topic'
2016-09-22 09:36:47 +00:00
/*
2016-03-27 11:43:30 +00:00
* Metamaps.Import.js.erb
*
2016-03-27 11:43:30 +00:00
* Dependencies:
* - Metamaps.Backbone
* - Metamaps.Mappings
* - Metamaps.Metacodes
* - Metamaps.Synapses
* - Metamaps.Topics
*/
const Import = {
// note that user is not imported
topicWhitelist: [
'id', 'name', 'metacode', 'x', 'y', 'description', 'link', 'permission'
],
synapseWhitelist: [
'topic1', 'topic2', 'category', 'desc', 'description', 'permission'
],
2016-03-27 11:43:30 +00:00
cidMappings: {}, // to be filled by import_id => cid mappings
handleTSV: function (text) {
2016-09-30 14:31:24 +00:00
const results = Import.parseTabbedString(text)
Import.handle(results)
},
handleCSV: function (text, parserOpts = {}) {
2016-09-30 14:31:24 +00:00
const self = Import
const topicsRegex = /("?Topics"?)([\s\S]*)/mi
const synapsesRegex = /("?Synapses"?)([\s\S]*)/mi
2016-10-01 04:43:02 +00:00
let topicsText = text.match(topicsRegex) || ''
if (topicsText) topicsText = topicsText[2].replace(synapsesRegex, '')
2016-10-01 04:43:02 +00:00
let synapsesText = text.match(synapsesRegex) || ''
if (synapsesText) synapsesText = synapsesText[2].replace(topicsRegex, '')
// merge default options and extra options passed in parserOpts argument
const csv_parser_options = Object.assign({
columns: true, // get headers
relax_column_count: true,
skip_empty_lines: true
}, parserOpts)
const topicsPromise = $.Deferred()
parse(topicsText, csv_parser_options, (err, data) => {
if (err) {
console.warn(err)
return topicsPromise.resolve([])
}
topicsPromise.resolve(data.map(row => self.lowercaseKeys(row)))
})
const synapsesPromise = $.Deferred()
parse(synapsesText, csv_parser_options, (err, data) => {
if (err) {
console.warn(err)
return synapsesPromise.resolve([])
}
synapsesPromise.resolve(data.map(row => self.lowercaseKeys(row)))
})
$.when(topicsPromise, synapsesPromise).done((topics, synapses) => {
self.handle({ topics, synapses})
})
},
handleJSON: function (text) {
2016-09-30 14:31:24 +00:00
const results = JSON.parse(text)
Import.handle(results)
},
handle: function(results) {
var self = Import
var topics = results.topics
var synapses = results.synapses
if (topics.length > 0 || synapses.length > 0) {
if (window.confirm('Are you sure you want to create ' + topics.length +
' new topics and ' + synapses.length + ' new synapses?')) {
self.importTopics(topics)
self.importSynapses(synapses)
2016-03-27 11:43:30 +00:00
} // if
} // if
},
2016-03-27 11:43:30 +00:00
parseTabbedString: function (text) {
var self = Import
// determine line ending and split lines
2016-03-27 11:43:30 +00:00
var delim = '\n'
if (text.indexOf('\r\n') !== -1) {
delim = '\r\n'
} else if (text.indexOf('\r') !== -1) {
delim = '\r'
} // if
var STATES = {
ABORT: -1,
UNKNOWN: 0,
TOPICS_NEED_HEADERS: 1,
SYNAPSES_NEED_HEADERS: 2,
TOPICS: 3,
2016-03-27 11:43:30 +00:00
SYNAPSES: 4
}
// state & lines determine parser behaviour
2016-03-27 11:43:30 +00:00
var state = STATES.UNKNOWN
var lines = text.split(delim)
var results = { topics: [], synapses: [] }
2016-03-27 11:43:30 +00:00
var topicHeaders = []
var synapseHeaders = []
lines.forEach(function (line_raw, index) {
var line = line_raw.split('\t')
var noblanks = line.filter(function (elt) {
return elt !== ''
})
switch (state) {
case STATES.UNKNOWN:
if (noblanks.length === 0) {
2016-03-27 11:43:30 +00:00
state = STATES.UNKNOWN
break
} else if (noblanks.length === 1 && self.simplify(line[0]) === 'topics') {
2016-03-27 11:43:30 +00:00
state = STATES.TOPICS_NEED_HEADERS
break
} else if (noblanks.length === 1 && self.simplify(line[0]) === 'synapses') {
2016-03-27 11:43:30 +00:00
state = STATES.SYNAPSES_NEED_HEADERS
break
}
2016-03-27 11:43:30 +00:00
state = STATES.TOPICS_NEED_HEADERS
// FALL THROUGH - if we're not sure what to do, pretend
// we're on the TOPICS_NEED_HEADERS state and parse some headers
2016-03-27 11:43:30 +00:00
case STATES.TOPICS_NEED_HEADERS: // eslint-disable-line
if (noblanks.length < 2) {
2016-03-27 11:43:30 +00:00
self.abort('Not enough topic headers on line ' + index)
state = STATES.ABORT
}
2016-03-27 11:43:30 +00:00
topicHeaders = line.map(function (header, index) {
return header.toLowerCase().replace('description', 'desc')
})
state = STATES.TOPICS
break
case STATES.SYNAPSES_NEED_HEADERS:
if (noblanks.length < 2) {
2016-03-27 11:43:30 +00:00
self.abort('Not enough synapse headers on line ' + index)
state = STATES.ABORT
}
2016-03-27 11:43:30 +00:00
synapseHeaders = line.map(function (header, index) {
return header.toLowerCase().replace('description', 'desc')
})
state = STATES.SYNAPSES
break
case STATES.TOPICS:
if (noblanks.length === 0) {
2016-03-27 11:43:30 +00:00
state = STATES.UNKNOWN
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'topics') {
2016-03-27 11:43:30 +00:00
state = STATES.TOPICS_NEED_HEADERS
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'synapses') {
2016-03-27 11:43:30 +00:00
state = STATES.SYNAPSES_NEED_HEADERS
} else {
2016-03-27 11:43:30 +00:00
var topic = {}
line.forEach(function (field, index) {
var header = topicHeaders[index]
if (self.topicWhitelist.indexOf(header) === -1) return
topic[header] = field
if (['id', 'x', 'y'].indexOf(header) !== -1) {
2016-03-27 11:43:30 +00:00
topic[header] = parseInt(topic[header])
} // if
})
results.topics.push(topic)
}
2016-03-27 11:43:30 +00:00
break
case STATES.SYNAPSES:
if (noblanks.length === 0) {
2016-03-27 11:43:30 +00:00
state = STATES.UNKNOWN
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'topics') {
2016-03-27 11:43:30 +00:00
state = STATES.TOPICS_NEED_HEADERS
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'synapses') {
2016-03-27 11:43:30 +00:00
state = STATES.SYNAPSES_NEED_HEADERS
} else {
2016-03-27 11:43:30 +00:00
var synapse = {}
line.forEach(function (field, index) {
var header = synapseHeaders[index]
if (self.synapseWhitelist.indexOf(header) === -1) return
synapse[header] = field
if (['id', 'topic1', 'topic2'].indexOf(header) !== -1) {
2016-03-27 11:43:30 +00:00
synapse[header] = parseInt(synapse[header])
} // if
})
results.synapses.push(synapse)
}
2016-03-27 11:43:30 +00:00
break
case STATES.ABORT:
2016-03-27 11:43:30 +00:00
default:
2016-03-27 11:43:30 +00:00
self.abort('Invalid state while parsing import data. Check code.')
state = STATES.ABORT
}
2016-03-27 11:43:30 +00:00
})
if (state === STATES.ABORT) {
2016-03-27 11:43:30 +00:00
return false
} else {
2016-03-27 11:43:30 +00:00
return results
}
},
2016-03-27 11:43:30 +00:00
importTopics: function (parsedTopics) {
var self = Import
2016-03-27 11:43:30 +00:00
parsedTopics.forEach(function (topic) {
var x, y
if (topic.x && topic.y) {
2016-03-27 11:43:30 +00:00
x = topic.x
y = topic.y
} else {
2016-10-01 04:43:02 +00:00
const coords = AutoLayout.getNextCoord()
x = coords.x
y = coords.y
}
if (topic.name && topic.link && !topic.metacode) {
2016-10-01 04:43:02 +00:00
topic.metacode = 'Reference'
}
self.createTopicWithParameters(
topic.name, topic.metacode, topic.permission,
topic.desc, topic.link, x, y, topic.id
2016-03-27 11:43:30 +00:00
)
})
},
2016-03-27 11:43:30 +00:00
importSynapses: function (parsedSynapses) {
var self = Import
2016-04-14 06:16:16 +00:00
parsedSynapses.forEach(function (synapse) {
2016-03-27 11:43:30 +00:00
// only createSynapseWithParameters once both topics are persisted
var topic1 = Metamaps.Topics.get(self.cidMappings[synapse.topic1])
var topic2 = Metamaps.Topics.get(self.cidMappings[synapse.topic2])
2016-03-31 01:25:14 +00:00
if (!topic1 || !topic2) {
console.error("One of the two topics doesn't exist!")
console.error(synapse)
2016-03-27 11:43:30 +00:00
return true
2016-03-31 01:25:14 +00:00
}
// ensure imported topics have a chance to get a real id attr before creating synapses
const topic1Promise = $.Deferred()
topic1.once('sync', () => topic1Promise.resolve())
const topic2Promise = $.Deferred()
topic2.once('sync', () => topic2Promise.resolve())
$.when(topic1Promise, topic2Promise).done(() => {
self.createSynapseWithParameters(
synapse.desc, synapse.category, synapse.permission,
topic1, topic2
)
2016-03-27 11:43:30 +00:00
})
})
},
2016-03-27 11:43:30 +00:00
createTopicWithParameters: function (name, metacode_name, permission, desc,
link, xloc, yloc, import_id, opts = {}) {
var self = Import
2016-09-22 09:36:47 +00:00
$(document).trigger(Map.events.editedByActiveMapper)
2016-03-27 11:43:30 +00:00
var metacode = Metamaps.Metacodes.where({name: metacode_name})[0] || null
2016-07-03 05:31:04 +00:00
if (metacode === null) {
metacode = Metamaps.Metacodes.where({ name: 'Wildcard' })[0]
console.warn("Couldn't find metacode " + metacode_name + ' so used Wildcard instead.')
}
2016-09-22 09:36:47 +00:00
var topic_permission = permission || Active.Map.get('permission')
var defer_to_map_id = permission === topic_permission ? Active.Map.get('id') : null
var topic = new Metamaps.Backbone.Topic({
name: name,
metacode_id: metacode.id,
permission: topic_permission,
defer_to_map_id: defer_to_map_id,
2016-09-21 02:24:57 +00:00
desc: desc || "",
link: link || "",
2016-09-22 09:36:47 +00:00
calculated_permission: Active.Map.get('permission')
2016-03-27 11:43:30 +00:00
})
Metamaps.Topics.add(topic)
if (import_id !== null && import_id !== undefined) {
self.cidMappings[import_id] = topic.cid
}
var mapping = new Metamaps.Backbone.Mapping({
2016-03-27 11:43:30 +00:00
xloc: xloc,
yloc: yloc,
mappable_id: topic.cid,
mappable_type: 'Topic'
})
Metamaps.Mappings.add(mapping)
2016-02-07 09:51:01 +00:00
// this function also includes the creation of the topic in the database
2016-09-22 10:31:56 +00:00
Topic.renderTopic(mapping, topic, true, true, {
success: opts.success
})
2016-09-22 10:31:56 +00:00
GlobalUI.hideDiv('#instructions')
},
createSynapseWithParameters: function (desc, category, permission,
2016-03-27 11:43:30 +00:00
topic1, topic2) {
var node1 = topic1.get('node')
var node2 = topic2.get('node')
if (!topic1.id || !topic2.id) {
2016-03-27 11:43:30 +00:00
console.error('missing topic id when creating synapse')
return
} // if
var synapse = new Metamaps.Backbone.Synapse({
desc: desc || "",
category: category,
permission: permission,
topic1_id: topic1.id,
topic2_id: topic2.id
2016-03-27 11:43:30 +00:00
})
Metamaps.Synapses.add(synapse)
var mapping = new Metamaps.Backbone.Mapping({
2016-03-27 11:43:30 +00:00
mappable_type: 'Synapse',
mappable_id: synapse.cid
})
Metamaps.Mappings.add(mapping)
2016-09-22 10:31:56 +00:00
Synapse.renderSynapse(mapping, synapse, node1, node2, true)
},
/*
* helper functions
*/
abort: function (message) {
console.error(message)
},
simplify: function (string) {
return string
.replace(/(^\s*|\s*$)/g, '')
.toLowerCase()
},
// thanks to http://stackoverflow.com/a/25290114/5332286
lowercaseKeys: function(obj) {
return _.transform(obj, (result, val, key) => {
result[key.toLowerCase()] = val
})
2016-03-27 11:43:30 +00:00
}
}
export default Import