Skip to content

Commit 014f710

Browse files
authored
Move filters and functions to separate modules (#200)
Currently the index file is rather large and contains a large number of functions, and this PR instead moves filters and functions to separate modules, allowing for less noise in the main file and ease of adding new functions. Functions exported from functions.js, or filters exported from filters.js are automatically made available in the templates using the name of the function (e.g. exporting `my_func` from functions.js will add a function to the templates called `my_func`.)
1 parent 211cf1e commit 014f710

3 files changed

Lines changed: 234 additions & 230 deletions

File tree

lib/importer/src/filters.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
3+
const currency = (content) => {
4+
const num = parseInt(content)
5+
if (num === undefined) {
6+
return content
7+
}
8+
9+
return "£" + num.toLocaleString()
10+
}
11+
12+
13+
module.exports = { currency }

lib/importer/src/functions.js

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
2+
const sheets_lib = require("./sheets.js");
3+
4+
const IMPORTER_SESSION_KEY = "importer.session";
5+
const IMPORTER_ERROR_KEY = "importer.error";
6+
7+
//--------------------------------------------------------------------
8+
// Allows the templates to get a list of viable sheet names, and for
9+
// each one also obtain a preview of the data for display when selecting
10+
// a sheet.
11+
//--------------------------------------------------------------------
12+
const importSheetPreview = (data) => {
13+
let session = data[IMPORTER_SESSION_KEY];
14+
return session.sheets.map((sheetName) => {
15+
return {
16+
name: sheetName,
17+
data: { rows: sheets_lib.GetPreview(session, sheetName) },
18+
};
19+
});
20+
}
21+
22+
//--------------------------------------------------------------------
23+
// Adds a function which can be called to display an error message if
24+
// any have been raised by the most recent post request. This allows
25+
// users to show errors raised at each step (where the macro supports
26+
// displaying it).
27+
//--------------------------------------------------------------------
28+
const importerError = (data) => {
29+
if (data[IMPORTER_ERROR_KEY]) {
30+
return { text: data[IMPORTER_ERROR_KEY] };
31+
}
32+
33+
return false;
34+
}
35+
//--------------------------------------------------------------------
36+
// Allows a template to obtain `count` rows from the start of the data
37+
// range.
38+
//--------------------------------------------------------------------
39+
const importerGetRows = (data, start, count) => {
40+
const session = data[IMPORTER_SESSION_KEY];
41+
return sheets_lib.GetRows(session, start, count);
42+
}
43+
44+
//--------------------------------------------------------------------
45+
// Allows a template to obtain `count` rows from the end of the data
46+
// range.
47+
//--------------------------------------------------------------------
48+
const importerGetTrailingRows = (data, count) => {
49+
const session = data[IMPORTER_SESSION_KEY];
50+
return sheets_lib.GetTrailingRows(session, count);
51+
}
52+
53+
//--------------------------------------------------------------------
54+
// This function generates a caption for a table given the current
55+
// session, the number of rows requested, and the number of rows found.
56+
// This is either of the form
57+
// "First/Last {n} rows of {sheet}"
58+
// or
59+
// "All {n} rows of {sheet}"
60+
//--------------------------------------------------------------------
61+
const importerGetTableCaption =
62+
(data, prefix, rowsAskedFor, sheetName = null) => {
63+
const session = data[IMPORTER_SESSION_KEY];
64+
65+
const sheet = (sheetName ??= session.sheet);
66+
const rowCount = sheets_lib.GetTotalRows(session, sheet);
67+
68+
if (rowCount > rowsAskedFor) {
69+
return `${prefix} ${rowsAskedFor} rows of '${sheet}'`;
70+
}
71+
72+
return `All rows of '${sheet}'`;
73+
}
74+
75+
//--------------------------------------------------------------------
76+
// In the absence of a step to choose headers, we will instead provide
77+
// a function that will allow the system to get the headers for the
78+
// spreadsheet. This is used to retrieve the first row where the
79+
// system does not provide the 'choose headers' functionality.
80+
//--------------------------------------------------------------------
81+
const importerGetHeaders = (data) => {
82+
const session = data[IMPORTER_SESSION_KEY];
83+
let header_names = sheets_lib.GetHeader(session);
84+
85+
const response = {
86+
data: [],
87+
error: false,
88+
};
89+
90+
// Check that there is even data to map. If we have a headerRange
91+
// and footerRange with the same row index, then that means there
92+
// is no data and so add an error and go back.
93+
if (session.footerRange) {
94+
if (session.headerRange.end.row >= session.footerRange.start.row) {
95+
// TODO: Showing an error may not be the correct thing here, can we catch
96+
// the error earlier?
97+
response.error = { text: "There is no data in this sheet" };
98+
return response;
99+
}
100+
}
101+
102+
if (!header_names || header_names.length == 0) {
103+
response.error = { text: "Unable to detect header rows" };
104+
} else {
105+
response.data = header_names.map((elem, index) => {
106+
let examples = sheets_lib.GetColumnValues(
107+
session,
108+
index,
109+
/* cellWidth */ 20,
110+
/* count */ 5,
111+
).inputValues;
112+
113+
let exampleString = examples.join(", ").trim();
114+
115+
return {
116+
index: index,
117+
name: elem,
118+
examples: exampleString,
119+
};
120+
});
121+
}
122+
return response;
123+
}
124+
125+
//--------------------------------------------------------------------
126+
// Extracts the data from the spreadsheet and makes it available for
127+
// display in a macro. This is primarily used by the review step which
128+
// shows the output data in a table.
129+
//--------------------------------------------------------------------
130+
const importerMappedData = (data) => {
131+
const session = data[IMPORTER_SESSION_KEY];
132+
133+
const mapResults = sheets_lib.MapData(session);
134+
const headers = session.fields;
135+
136+
return {
137+
rows: mapResults.resultRecords,
138+
headers: headers,
139+
totalCount: mapResults.totalCount,
140+
extraRecordCount: mapResults.extraRecordCount,
141+
errorCount: mapResults.errorCount,
142+
warningCount: mapResults.warningCount,
143+
};
144+
}
145+
146+
//--------------------------------------------------------------------
147+
// Helper functions that can be used on the review page to show
148+
// information about the data that has been mapped.
149+
//--------------------------------------------------------------------
150+
const data_sum = (data, column) => {
151+
const session = data[IMPORTER_SESSION_KEY];
152+
const mapResults = sheets_lib.MapData(session);
153+
const headers = session.fields;
154+
155+
const idx = headers.findIndex((x) => x.name == column)
156+
if (idx == -1) {
157+
return "No data found"
158+
}
159+
160+
let numbers = mapResults.resultRecords
161+
.filter((x) => x !== undefined && x[idx] !== undefined)
162+
.map((x) => parseNumberFromString(x[idx]))
163+
164+
165+
return numbers.reduce((acc, i) => acc + i, 0)
166+
}
167+
168+
const data_avg = (data, column) => {
169+
const session = data[IMPORTER_SESSION_KEY];
170+
const mapResults = sheets_lib.MapData(session);
171+
const headers = session.fields;
172+
173+
const idx = headers.findIndex((x) => x.name == column)
174+
if (idx == -1) {
175+
return "No data found"
176+
}
177+
178+
const numbers = mapResults.resultRecords
179+
.filter((x) => x !== undefined && x[idx] !== undefined)
180+
.map((x) => parseNumberFromString(x[idx]))
181+
182+
const avg = numbers.reduce((acc, i) => acc + i, 0) / numbers.length
183+
if (Number.isNaN(avg)) {
184+
return "0"
185+
}
186+
return avg
187+
}
188+
189+
const parseNumberFromString = (s) => {
190+
const parsed = s.match(/([0-9]*\.[0-9]+|[0-9]+)/)
191+
if (parsed == null) {
192+
return 0
193+
}
194+
195+
if (parsed[0].indexOf(".") >= 0) {
196+
return parseFloat(parsed[0])
197+
}
198+
199+
return parseInt(parsed[0])
200+
}
201+
202+
203+
204+
module.exports = {
205+
importerError,
206+
importSheetPreview,
207+
importerGetRows,
208+
importerGetHeaders,
209+
importerGetTrailingRows,
210+
importerGetTableCaption,
211+
importerMappedData,
212+
data_sum,
213+
data_avg
214+
}

0 commit comments

Comments
 (0)