Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/csv to string #64

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
save-prefix=~
arch=x64
5 changes: 1 addition & 4 deletions config/setupTestEnv.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
// Cert verification is disabled during tests for now to work around
// https://github.com/nodejs/node/issues/14736; fixed with Node 8.10.0.

const http = require('http');
const https = require('https');
const enzyme = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');
const url = require('url');
const Writable = require('stream').Writable;
const { Writable } = require('stream');

enzyme.configure({ adapter: new Adapter() });

Expand Down
38 changes: 24 additions & 14 deletions src/exportFile.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable global-require */
const { createWriteStream } = require('./utils/filesystem');
const { createWriteStream, writeFile } = require('./utils/filesystem');
const {
getFileExtension,
makeFilename,
Expand Down Expand Up @@ -50,31 +50,41 @@ const exportFile = (
const pathPromise = new Promise((resolve, reject) => {
promiseResolve = resolve;
promiseReject = reject;
let filePath;

const formatter = new Formatter(network, codebook, exportOptions);
const outputName = makeFilename(namePrefix, partitonedEntityName, exportFormat, extension);
if (isElectron()) {
const path = require('path');
filePath = path.join(outDir, outputName);
const filePath = path.join(outDir, outputName);

createWriteStream(filePath)
.then((ws) => {
writeStream = ws;
writeStream.on('finish', () => {
promiseResolve(filePath);
});
writeStream.on('error', (err) => {
promiseReject(err);
});

streamController = formatter.writeToStream(writeStream);
});
}

// We encountered a bug with Cordova where CSV files where sometimes empty.
// As a precaution, we switched to writing the file in one go.
if (isCordova()) {
filePath = `${outDir}${outputName}`;
}
const filePath = `${outDir}${outputName}`;
const string = formatter.writeToString();

createWriteStream(filePath)
.then((ws) => {
writeStream = ws;
writeStream.on('finish', () => {
writeFile(filePath, string)
.then(() => {
promiseResolve(filePath);
});
writeStream.on('error', (err) => {
})
.catch((err) => {
promiseReject(err);
});

streamController = formatter.writeToStream(writeStream);
});
}
});

// Decorate the promise with an abort method that also tears down the
Expand Down
17 changes: 17 additions & 0 deletions src/formatters/csv/__tests__/attribute-list-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,23 @@ describe('asAttributeList', () => {
});
});

describe('writeToString', () => {
it('writes a simple CSV', () => {
const formatter = new AttributeListFormatter(
{ nodes: [node] }, mockCodebook, mockExportOptions,
);
const csv = formatter.writeToString();
const result = [
...baseCSVAttributes,
'name\r\n',
123,
1,
'Jane\r\n',
].join(',');
expect(csv).toEqual(result);
});
});

describe('toCSVStream', () => {
let writable;
let testNode;
Expand Down
45 changes: 44 additions & 1 deletion src/formatters/csv/__tests__/edge-list-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import {
ncSourceUUID,
ncTargetUUID,
} from '../../../utils/reservedAttributes';
import { EdgeListFormatter, asEdgeList, toCSVStream } from '../edge-list';
import {
EdgeListFormatter,
asEdgeList,
toCSVStream,
toCSVString,
} from '../edge-list';

const nodes = [
{ [entityPrimaryKeyProperty]: 1 },
Expand Down Expand Up @@ -76,6 +81,44 @@ describe('asEdgeList', () => {
});
});

describe('toCSVString', () => {
it('Writes a csv with attributes', () => {
const list = listFromEdges([
{
[entityPrimaryKeyProperty]: 123,
[egoProperty]: 456,
[edgeExportIDProperty]: 1,
[ncSourceUUID]: 1,
[ncTargetUUID]: 2,
[edgeSourceProperty]: 1,
[edgeTargetProperty]: 2,
[entityAttributesProperty]: {
a: 1,
},
},
]);
const csv = toCSVString(list);
const result = [
edgeExportIDProperty,
edgeSourceProperty,
edgeTargetProperty,
egoProperty,
ncUUIDProperty,
ncSourceUUID,
ncTargetUUID,
'a\r\n1',
1,
2,
456,
123,
1,
2,
'1\r\n',
].join(',');
expect(csv).toEqual(result);
});
});

describe('toCSVStream', () => {
let writable;

Expand Down
25 changes: 24 additions & 1 deletion src/formatters/csv/__tests__/ego-list-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/* eslint-env jest */
import { makeWriteableStream } from '../../../../config/setupTestEnv';
import { mockCodebook, mockExportOptions } from '../../../../config/mockObjects';
import { EgoListFormatter, asEgoAndSessionVariablesList, toCSVStream } from '../ego-list';
import {
EgoListFormatter,
asEgoAndSessionVariablesList,
toCSVStream,
toCSVString,
} from '../ego-list';
import {
entityPrimaryKeyProperty,
entityAttributesProperty,
Expand Down Expand Up @@ -52,6 +57,24 @@ describe('asEgoAndSessionVariablesList', () => {
});
});

describe('toCSVString', () => {
it('writes a simple CSV', () => {
const csv = toCSVString([ego]);
const result = [
...baseCSVAttributes,
'name\r\n1',
'case id',
789,
'protocol name',
100,
200,
300,
'Jane\r\n',
].join(',');
expect(csv).toEqual(result);
});
});

describe('toCSVStream', () => {
let writable;
let testEgo;
Expand Down
27 changes: 27 additions & 0 deletions src/formatters/csv/attribute-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,29 @@ const toCSVStream = (nodes, outStream) => {
};
};

const toCSVString = (nodes) => {
const attrNames = attributeHeaders(nodes);
const headerValue = `${attrNames.map((attr) => sanitizedCellValue(getPrintableAttribute(attr))).join(',')}${csvEOL}`;
const rows = nodes.map((node) => {
const values = attrNames.map((attrName) => {
// The primary key and ego id exist at the top-level; all others inside `.attributes`
let value;
if (
attrName === entityPrimaryKeyProperty
|| attrName === egoProperty
|| attrName === nodeExportIDProperty
) {
value = node[attrName];
} else {
value = node[entityAttributesProperty][attrName];
}
return sanitizedCellValue(value);
});
return `${values.join(',')}${csvEOL}`;
});
return headerValue + rows.join('');
};

class AttributeListFormatter {
constructor(data, codebook, exportOptions) {
this.list = asAttributeList(data, codebook, exportOptions);
Expand All @@ -106,6 +129,10 @@ class AttributeListFormatter {
writeToStream(outStream) {
return toCSVStream(this.list, outStream);
}

writeToString() {
return toCSVString(this.list);
}
}

module.exports = {
Expand Down
32 changes: 32 additions & 0 deletions src/formatters/csv/edge-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,33 @@ const toCSVStream = (edges, outStream) => {
abort: () => { inStream.destroy(); },
};
};

const toCSVString = (edges) => {
const attrNames = attributeHeaders(edges);
const headerValue = `${attrNames.map((attr) => sanitizedCellValue(getPrintableAttribute(attr))).join(',')}${csvEOL}`;
const rows = edges.map((edge) => {
const values = attrNames.map((attrName) => {
// primary key/ego id/to/from exist at the top-level; all others inside `.attributes`
let value;
if (
attrName === entityPrimaryKeyProperty
|| attrName === edgeExportIDProperty
|| attrName === egoProperty
|| attrName === 'to'
|| attrName === 'from'
|| attrName === ncSourceUUID
|| attrName === ncTargetUUID
) {
value = edge[attrName];
} else {
value = edge[entityAttributesProperty][attrName];
}
return sanitizedCellValue(value);
});
return `${values.join(',')}${csvEOL}`;
});
return headerValue + rows.join('');
};
class EdgeListFormatter {
constructor(data, codebook, exportOptions) {
this.list = asEdgeList(data, codebook, exportOptions);
Expand All @@ -154,10 +181,15 @@ class EdgeListFormatter {
writeToStream(outStream) {
return toCSVStream(this.list, outStream);
}

writeToString() {
return toCSVString(this.list);
}
}

module.exports = {
EdgeListFormatter,
asEdgeList,
toCSVStream,
toCSVString,
};
34 changes: 34 additions & 0 deletions src/formatters/csv/ego-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,35 @@ const toCSVStream = (egos, outStream) => {
};
};

const toCSVString = (egos) => {
const attrNames = attributeHeaders(egos);
const headerValue = `${attrNames.map((attr) => sanitizedCellValue(getPrintableAttribute(attr))).join(',')}${csvEOL}`;

const rows = egos.map((ego) => {
const values = attrNames.map((attrName) => {
// Session variables exist at the top level - all others inside `attributes`
let value;
if (
attrName === entityPrimaryKeyProperty
|| attrName === caseProperty
|| attrName === sessionProperty
|| attrName === protocolName
|| attrName === sessionStartTimeProperty
|| attrName === sessionFinishTimeProperty
|| attrName === sessionExportTimeProperty
) {
value = ego[attrName];
} else {
value = ego[entityAttributesProperty][attrName];
}
return sanitizedCellValue(value);
});
return `${values.join(',')}${csvEOL}`;
});

return headerValue + rows.join('');
};

class EgoListFormatter {
constructor(network, codebook, exportOptions) {
this.list = asEgoAndSessionVariablesList(network, codebook, exportOptions) || [];
Expand All @@ -136,10 +165,15 @@ class EgoListFormatter {
writeToStream(outStream) {
return toCSVStream(this.list, outStream);
}

writeToString() {
return toCSVString(this.list);
}
}

module.exports = {
EgoListFormatter,
asEgoAndSessionVariablesList,
toCSVStream,
toCSVString,
};
31 changes: 8 additions & 23 deletions src/formatters/graphml/GraphMLFormatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,21 @@ class GraphMLFormatter {
this.exportOptions = exportOptions;
}

streamToString = (stream) => {
const chunks = [];
return new Promise((resolve, reject) => {
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('error', reject);
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
});
}

/**
* A method allowing writing the file to a string. Used for tests.
*/
writeToString() {
const generator = graphMLGenerator(
this.network,
this.codebook,
this.exportOptions,
);

const inStream = new Readable({
read(/* size */) {
const { done, value } = generator.next();
if (done) {
this.push(null);
} else {
this.push(value);
}
},
});
const chunks = [];

return this.streamToString(inStream);
// Call the generator until it is done
for (let { done, value } = generator.next(); !done; { done, value } = generator.next()) {
chunks.push(value);
}

return chunks.join('');
}

/**
Expand All @@ -58,6 +42,7 @@ class GraphMLFormatter {
this.codebook,
this.exportOptions,
);

const inStream = new Readable({
read(/* size */) {
const { done, value } = generator.next();
Expand Down
Loading
Loading