Skip to content

Commit

Permalink
Merge pull request #887 from codaco/feature/better-external-data
Browse files Browse the repository at this point in the history
Feature/better external data
  • Loading branch information
jthrilly authored Apr 17, 2019
2 parents 3f1d183 + 3e8beed commit 6aff60b
Show file tree
Hide file tree
Showing 14 changed files with 577 additions and 69 deletions.
18 changes: 18 additions & 0 deletions config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,24 @@ module.exports = {
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
{
test: /\.worker\.js$/,
use: [
{
loader: 'worker-loader',
options: { inline: true },
},
{
loader: require.resolve('babel-loader'),
options: {
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
},
},
],
},
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
Expand Down
18 changes: 18 additions & 0 deletions config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,24 @@ module.exports = {
name: 'static/media/[name].[hash:8].[ext]',
},
},
{
test: /\.worker\.js$/,
use: [
{
loader: 'worker-loader',
options: { inline: true },
},
{
loader: require.resolve('babel-loader'),
options: {
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
},
},
],
},
// Process JS with Babel.
{
test: /\.(js|jsx)$/,
Expand Down
384 changes: 356 additions & 28 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"connect-history-api-fallback": "1.3.0",
"cross-env": "^5.2.0",
"css-loader": "0.28.4",
"csvtojson": "^2.0.8",
"detect-port": "1.1.0",
"dotenv": "4.0.0",
"electron": "^2.0.11",
Expand Down Expand Up @@ -152,6 +153,7 @@
"webpack-dev-server": "2.7.1",
"webpack-manifest-plugin": "^1.3.1",
"whatwg-fetch": "2.0.3",
"worker-loader": "^2.0.0",
"xss": "^0.3.4"
},
"dependencies": {
Expand Down
1 change: 0 additions & 1 deletion public/components/windowManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ function createWindow() {
center: true,
fullscreen: true,
title: 'Network Canvas',

}, titlebarParameters);

const mainWindow = new BrowserWindow(windowParameters);
Expand Down
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
style-src 'self' 'unsafe-inline';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
font-src 'self' data:;
worker-src blob:;
worker-src 'self' blob:;
"
/>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/MainMenu/SettingsMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class SettingsMenu extends PureComponent {
</p>
<Button
color="mustard"
onClick={() => importProtocolFromURI('https://github.com/codaco/development-protocol/releases/download/20190408150137-8c32f10/development-protocol.netcanvas')}
onClick={() => importProtocolFromURI('https://github.com/codaco/development-protocol/releases/download/20190410122751-7141c7a/development-protocol.netcanvas')}
>
Import development protocol
</Button>
Expand Down
24 changes: 22 additions & 2 deletions src/containers/__tests__/withExternalData.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,25 @@ import { mount } from 'enzyme';
import { last } from 'lodash';
import withExternalData from '../withExternalData';
import loadExternalData from '../../utils/loadExternalData';
import { entityAttributesProperty } from '../../ducks/modules/network';

jest.mock('../../utils/loadExternalData');

const mockReducer = () => ({
installedProtocols: {
mockProtocol: {},
mockProtocol: {
codebook: {
node: {},
edge: {},
},
assetManifest: {
bar: {
name: 'bar',
source: 'file.json',
type: 'network',
},
},
},
},
activeSessionId: 'foo',
sessions: {
Expand All @@ -21,7 +34,14 @@ const mockReducer = () => ({
});

const mockResult = {
bar: 'bazz',
nodes: [
{
type: 'person',
[entityAttributesProperty]: {
fun: true,
},
},
],
};

const mockSource = 'bar';
Expand Down
74 changes: 67 additions & 7 deletions src/containers/withExternalData.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,75 @@ import {
import {
get,
mapValues,
mapKeys,
findKey,
isEmpty,
} from 'lodash';
import loadExternalData from '../utils/loadExternalData';
import { entityAttributesProperty } from '../ducks/modules/network';

const mapStateToProps = (state) => {
const session = state.sessions[state.activeSessionId];
const protocolUID = session.protocolUID;
const protocolCodebook = state.installedProtocols[protocolUID].codebook;
const assetManifest = state.installedProtocols[protocolUID].assetManifest;
const assetFiles = mapValues(
state.installedProtocols[protocolUID].assetManifest,
assetManifest,
asset => asset.source,
);

return {
protocolUID,
assetManifest,
assetFiles,
protocolCodebook,
};
};

/**
* Utility function that can be used to help with translating external data
* variable labels to UUIDs, if a match is possible.
*
* Assuming that {object} contains other objects, keyed by a UUID, this function
* first checks if the string to find is a valid key in the object, and returns it
* if so (equivalent to codebook.node.uuid === toFind )
*
* if not, it iterates the keys of the object, and tests the keys of each child object
* to see if the 'name' property equals {toFind}. This is equivalent to
* codebook.node.uuid.name === toFind. Where this child object is found, its key within
* the parent object is returned.
*
* Finally, if neither approach finds a UUID, {toFind} is returned.
*/
const getObjectUUIDByValue = (object, toFind) => {
if (isEmpty(object) || object[toFind]) {
return toFind;
}

// Iterate object keys and return the key (itself )
const foundKey = findKey(object, objectItem => objectItem.name === toFind);

return foundKey || toFind;
};

const withUUIDReplacement = (nodeList, codebook) => nodeList.map(
(node) => {
const nodeTypeUUID = getObjectUUIDByValue(codebook.node, node.type);
const codebookDefinition = codebook.node[nodeTypeUUID] || {};

const attributes = mapKeys(node[entityAttributesProperty],
(attributeValue, attributeKey) =>
getObjectUUIDByValue(codebookDefinition.variables, attributeKey),
);

return {
...node,
type: nodeTypeUUID,
[entityAttributesProperty]: attributes,
};
},
);

/**
* Creates a higher order component which can be used to load data from network assets in
* the assetsManifest onto a component.
Expand All @@ -53,24 +105,32 @@ const mapStateToProps = (state) => {
const withExternalData = (sourceProperty, dataProperty) =>
compose(
connect(mapStateToProps),
withState(dataProperty, 'setExternalData', null),
withState(
dataProperty, // State name
'setExternalData', // State updater name
null, // initialState
),
withHandlers({
loadExternalData: ({
setExternalData,
protocolUID,
assetFiles,
assetManifest,
protocolCodebook,
}) =>
(sourceId) => {
if (!sourceId) { return; }

// This is where we could set the loading state for URL assets
setExternalData(null);

const sourceFile = assetFiles[sourceId];
const type = assetManifest[sourceId].type;

loadExternalData(protocolUID, sourceFile)
.then((externalData) => {
setExternalData(externalData);
});
loadExternalData(protocolUID, sourceFile, type)
.then(externalData =>
setExternalData({
nodes: withUUIDReplacement(externalData.nodes, protocolCodebook),
}));
},
}),
lifecycle({
Expand Down
2 changes: 1 addition & 1 deletion src/ui
Submodule ui updated from 67fb1e to c5f620
4 changes: 2 additions & 2 deletions src/utils/__tests__/loadExternalData.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import loadExternalData from '../loadExternalData';

const mockProtocolName = 'myMockProtocol';
const mockAssetName = 'myMockSource';
const mockProtocolType = null;
const mockAssetType = 'network';
const mockResult = {
nodes: [
{ foo: 'bar' },
Expand All @@ -19,7 +19,7 @@ global.fetch = jest.fn(() => Promise.resolve(mockFetchResponse));
describe('loadExternalData', () => {
it('returns a cancellable request');
it('request response is json with uids ', (done) => {
loadExternalData(mockProtocolName, mockAssetName, mockProtocolType)
loadExternalData(mockProtocolName, mockAssetName, mockAssetType)
.then((result) => {
expect(result.nodes.length).toBe(mockResult.nodes.length);
expect(result.nodes.every(
Expand Down
32 changes: 32 additions & 0 deletions src/utils/csvDecoder.worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { omit } from 'lodash';
import { entityAttributesProperty } from '../ducks/modules/network';

/**
* Converts a CSV file into a Network Canvas node list JSON
*
* @param {string} data - the contents of a CSV file
*
* See: https://github.com/Keyang/node-csvtojson We may want to introduce buffering
* to this function to increase performance particularly on cordova.
*
*/

const csv = require('../../node_modules/csvtojson/browser/browser.js');

const CSVToJSONNetworkFormat = (data) => {
const withTypeAndAttributes = node => ({
type: node.type,
[entityAttributesProperty]: {
...omit(node, 'type'),
},
});

csv().fromString(data)
.then((json) => {
const nodeList = json.map(entry => withTypeAndAttributes(entry));
self.postMessage({ nodes: nodeList });
});
};

// Respond to message from parent thread
self.addEventListener('message', event => CSVToJSONNetworkFormat(event.data));
Loading

0 comments on commit 6aff60b

Please sign in to comment.