3
3
*/
4
4
5
5
import Airtable from 'airtable' ;
6
- import ContentSource , { SourceOptions } from './content-source.js' ;
7
- import ContentResult , { MediaDownload } from './content-result.js' ;
8
- import Credentials from '../credentials.js' ;
6
+ // eslint-disable-next-line no-unused-vars
9
7
import { Logger } from '@bluecadet/launchpad-utils' ;
8
+ import { ContentSource , SourceOptions } from './content-source' ;
9
+ import { ContentResult , MediaDownload } from './content-result' ;
10
+ import Credentials from '../credentials' ;
10
11
11
12
/**
12
13
* Options for AirtableSource
@@ -22,46 +23,47 @@ export class AirtableOptions extends SourceOptions {
22
23
...rest
23
24
} = { } ) {
24
25
super ( rest ) ;
25
-
26
+
26
27
/**
27
- * Airtable base ID. @see https://help.appsheet.com/en/articles/1785063-using-data-from-airtable#:~:text=To%20obtain%20the%20ID%20of,API%20page%20of%20the%20base.
28
+ * Airtable base ID. @see https://help.appsheet.com/en/articles/1785063-using-data-from-airtable#:~:text=To%20obtain%20the%20ID%20of,API%20page%20of%20the%20base.
28
29
* @type {string }
29
30
*/
30
31
this . baseId = baseId ;
31
-
32
+
32
33
/**
33
34
* @type {string }
34
35
* @default 'Grid view'
35
36
*/
36
37
this . defaultView = defaultView ;
37
-
38
+
38
39
/**
39
40
* The tables you want to fetch from
40
41
* @type {string }
41
42
* @default []
42
43
*/
43
44
this . tables = tables ;
44
-
45
+
45
46
/**
46
- * As a convenience feature, you can store tables listed here as key/value pairs. Field names should be `"key"` and `"value"`.
47
+ * As a convenience feature, you can store tables listed here as
48
+ * key/value pairs. Field names should be `"key"` and `"value"`.
47
49
* @type {string }
48
50
* @default []
49
51
*/
50
52
this . keyValueTables = keyValueTables ;
51
-
53
+
52
54
/**
53
55
* The API endpoint to use for Airtable
54
56
* @type {string }
55
57
* @default 'https://api.airtable.com'
56
58
*/
57
59
this . endpointUrl = endpointUrl ;
58
-
60
+
59
61
/**
60
- * The table view which to select for syncing by default
62
+ * The table view which to select for syncing by default
61
63
* @type {string }
62
- */
64
+ */
63
65
this . defaultView = defaultView ;
64
-
66
+
65
67
/**
66
68
* Appends the local path of attachments to the saved JSON
67
69
* @type {boolean }
@@ -73,20 +75,22 @@ export class AirtableOptions extends SourceOptions {
73
75
74
76
export class AirtableSource extends ContentSource {
75
77
/** @type {Airtable.Base } */
76
- _base ;
78
+ _base = null ;
79
+
77
80
_rawAirtableData = { } ;
81
+
78
82
_simplifiedData = { } ;
79
83
80
84
/**
81
- *
82
- * @param {* } config
83
- * @param {Logger } logger
85
+ *
86
+ * @param {* } config
87
+ * @param {Logger } logger
84
88
*/
85
89
constructor ( config , logger ) {
86
90
super ( new AirtableOptions ( config ) , logger ) ;
87
-
91
+
88
92
const credentials = Credentials . getCredentials ( this . config . id ) ;
89
-
93
+
90
94
if ( ! credentials . apiKey ) {
91
95
throw new Error ( `No Airtable API Key for '${ this . config . id } '` ) ;
92
96
}
@@ -97,12 +101,12 @@ export class AirtableSource extends ContentSource {
97
101
} ) ;
98
102
99
103
this . _base = Airtable . base ( this . config . baseId ) ;
100
- this . _base . makeRequest
104
+ this . _base . makeRequest ( ) ;
101
105
}
102
-
106
+
103
107
/**
104
- * @returns {Promise<ContentResult> }
105
- */
108
+ * @returns {Promise<ContentResult> }
109
+ */
106
110
async fetchContent ( ) {
107
111
const result = new ContentResult ( ) ;
108
112
const tablePromises = [ ] ;
@@ -127,19 +131,19 @@ export class AirtableSource extends ContentSource {
127
131
}
128
132
129
133
/**
130
- *
131
- * @param {string } tableId
132
- * @param {boolean } isKeyValueTable
133
- * @param {ContentResult } result
134
+ *
135
+ * @param {string } tableId
136
+ * @param {boolean } isKeyValueTable
137
+ * @param {ContentResult } result
134
138
* @returns {ContentResult }
135
139
*/
136
- async _processTable ( tableId , isKeyValueTable = false , result = new ContentResult ( ) ) {
140
+ async _processTable ( tableId , isKeyValueTable = false , result = new ContentResult ( ) ) {
137
141
// Write raw Data file.
138
-
139
- const rawDataPath = tableId + ' .raw.json' ;
142
+
143
+ const rawDataPath = ` ${ tableId } .raw.json` ;
140
144
result . addDataFile ( rawDataPath , this . _rawAirtableData [ tableId ] ) ;
141
-
142
- let simpData = isKeyValueTable ? { } : [ ] ;
145
+
146
+ const simpData = isKeyValueTable ? { } : [ ] ;
143
147
this . _simplifiedData [ tableId ] = [ ] ;
144
148
145
149
// Process simplified data
@@ -149,7 +153,7 @@ export class AirtableSource extends ContentSource {
149
153
if ( isKeyValueTable ) {
150
154
if ( Object . keys ( row ) . length < 2 ) {
151
155
this . logger . error (
152
- `Table ${ tableId } requires at least 2 columns to map it to a key-value pair.`
156
+ `Table ${ tableId } requires at least 2 columns to map it to a key-value pair.` ,
153
157
) ;
154
158
return result ;
155
159
}
@@ -161,7 +165,7 @@ export class AirtableSource extends ContentSource {
161
165
const key = fields [ keyField ] ;
162
166
const value = fields [ valueField ] ;
163
167
164
- let matches = key ? [ ...key . matchAll ( regex ) ] : [ ] ;
168
+ const matches = key ? [ ...key . matchAll ( regex ) ] : [ ] ;
165
169
if ( matches . length > 0 ) {
166
170
if ( ! simpData [ matches [ 0 ] [ 1 ] ] ) {
167
171
simpData [ matches [ 0 ] [ 1 ] ] = [ ] ;
@@ -184,19 +188,20 @@ export class AirtableSource extends ContentSource {
184
188
// Gather attachments
185
189
if ( ! isKeyValueTable ) {
186
190
result . addMediaDownloads (
187
- this . _getMediaUrls ( simpData ) . map ( url => new MediaDownload ( { url} ) )
191
+ this . _getMediaUrls ( simpData ) . map ( ( url ) => new MediaDownload ( { url } ) ) ,
188
192
) ;
189
193
}
190
194
191
- const simpDataPath = tableId + ' .json' ;
195
+ const simpDataPath = ` ${ tableId } .json` ;
192
196
result . addDataFile ( simpDataPath , this . _simplifiedData [ tableId ] ) ;
193
-
197
+
194
198
return result ;
195
199
}
196
200
197
201
_isBoolStr ( str ) {
198
202
return str === 'true' || str === 'false' ;
199
203
}
204
+
200
205
_isNumericStr ( str ) {
201
206
return ! isNaN ( str ) ;
202
207
}
@@ -211,28 +216,27 @@ export class AirtableSource extends ContentSource {
211
216
if ( this . _rawAirtableData [ table ] && this . _rawAirtableData [ table ] . length > 0 ) {
212
217
// Return cached data
213
218
return Promise . resolve ( this . _rawAirtableData [ table ] ) ;
214
- } else {
215
- // Fetch new data
216
- return this . _fetchData ( table ) . then ( ( tableData ) => {
217
- this . _rawAirtableData [ table ] = tableData ;
218
- return this . _rawAirtableData [ table ] ;
219
- } ) ;
220
219
}
220
+ // Fetch new data
221
+ return this . _fetchData ( table ) . then ( ( tableData ) => {
222
+ this . _rawAirtableData [ table ] = tableData ;
223
+ return this . _rawAirtableData [ table ] ;
224
+ } ) ;
221
225
}
222
226
223
227
// Fetch from Airtable.
224
228
async _fetchData ( table ) {
225
229
return new Promise ( ( resolve , reject ) => {
226
- let rows = [ ] ;
230
+ const rows = [ ] ;
227
231
228
232
this . _base ( table )
229
233
. select ( {
230
234
view : this . config . defaultView ,
231
235
} )
232
236
. eachPage (
233
- function page ( records , fetchNextPage ) {
237
+ ( records , fetchNextPage ) => {
234
238
// This function (`page`) will get called for each page of records.
235
- records . forEach ( function ( record ) {
239
+ records . forEach ( ( record ) => {
236
240
rows . push ( record ) ;
237
241
} ) ;
238
242
@@ -241,29 +245,29 @@ export class AirtableSource extends ContentSource {
241
245
// If there are no more records, `done` will get called.
242
246
fetchNextPage ( ) ;
243
247
} ,
244
- function done ( err ) {
248
+ ( err ) => {
245
249
if ( err ) {
246
250
reject ( err ) ;
247
251
} else {
248
252
resolve ( rows ) ;
249
253
}
250
- }
254
+ } ,
251
255
) ;
252
256
} ) ;
253
257
}
254
-
258
+
255
259
/**
256
- *
257
- * @param {* } tableData
260
+ *
261
+ * @param {* } tableData
258
262
* @returns {Array<string> } media urls
259
263
*/
260
- _getMediaUrls ( tableData ) {
264
+ _getMediaUrls ( tableData ) {
261
265
const urls = [ ] ;
262
266
// Loop through all records, for filename fields.
263
267
for ( const row of tableData ) {
264
268
for ( const colId of Object . keys ( row ) ) {
265
269
// We assume it is a file if value is an array and `filename` key exists.
266
- if ( Array . isArray ( row [ colId ] ) && row [ colId ] [ 0 ] [ ' filename' ] ) {
270
+ if ( Array . isArray ( row [ colId ] ) && row [ colId ] [ 0 ] . filename ) {
267
271
for ( const attachment of row [ colId ] ) {
268
272
const url = new URL ( attachment . url ) ;
269
273
if ( this . config . appendLocalAttachmentPaths ) {
0 commit comments