diff --git a/community/tools/firebase2graphql/package.json b/community/tools/firebase2graphql/package.json index 4870010f03a..fdecedbc137 100644 --- a/community/tools/firebase2graphql/package.json +++ b/community/tools/firebase2graphql/package.json @@ -1,7 +1,7 @@ { "name": "firebase2graphql", "description": "A CLI tool to get GraphQL over Firebase data dump", - "version": "0.0.1-alpha8", + "version": "0.0.2", "author": "Hasura", "bin": { "firebase2graphql": "./bin/run", diff --git a/community/tools/firebase2graphql/src/firebase/generateGenericJson.js b/community/tools/firebase2graphql/src/firebase/generateGenericJson.js index fe36bd7665d..4c308cae930 100644 --- a/community/tools/firebase2graphql/src/firebase/generateGenericJson.js +++ b/community/tools/firebase2graphql/src/firebase/generateGenericJson.js @@ -6,6 +6,8 @@ const { isRandomList, isList, isObjectList, + makeFirebaseListFromObj, + makeFirebaseListFromArr, } = require('./utils'); const throwError = require('../error'); @@ -13,16 +15,18 @@ const handleTableCandidate = (obj, tableName, tableDetectedCallback, isRootLevel const rowArray = []; const flattenObject = (object, row, parent) => { if (isObjectList(object)) { + const firebaseList = makeFirebaseListFromObj(object); const dummyRow = {...row}; - for (var objListKey in object) { + for (var objListKey in firebaseList) { row[getPrimaryKeyName(dummyRow)] = objListKey; - const newRow = {...flattenObject(object[objListKey], row)}; + const newRow = {...flattenObject(firebaseList[objListKey], row)}; if (newRow && Object.keys(newRow).length > 0) { rowArray.push(newRow); } } } else if (isList(object)) { - for (var listKey in object) { + const firebaseObject = makeFirebaseListFromArr(object); + for (var listKey in firebaseObject) { const dummyRow = {...row}; dummyRow[getPrimaryKeyName(dummyRow, null, 'self')] = uuid(); dummyRow._value = listKey; @@ -33,28 +37,30 @@ const handleTableCandidate = (obj, tableName, tableDetectedCallback, isRootLevel } else { for (var objectKey in object) { const value = object[objectKey]; - if (value === null || value.constructor.name !== 'Object') { + if (value === null || !['Object', 'Array'].includes(value.constructor.name)) { row[objectKey] = value; - } else if (value.constructor.name === 'Object') { + } else if (['Object', 'Array'].includes(value.constructor.name)) { const pkeyMap = getParentPrimaryKeyMap(row); if (isList(value)) { + const firebaseList = makeFirebaseListFromArr(value); tableDetectedCallback( null, { tableName: parent || tableName, name: objectKey, pkeys: pkeyMap, - data: Object.keys(value).map(item => ({_value: item})), + data: Object.keys(firebaseList).map(item => ({_value: item})), } ); } else if (isObjectList(value)) { + const firebaseList = makeFirebaseListFromObj(value); tableDetectedCallback( null, { tableName: parent || tableName, name: objectKey, pkeys: pkeyMap, - data: handleTableCandidate(value, `${parent || tableName}_${objectKey}`, tableDetectedCallback, false), + data: handleTableCandidate(firebaseList, `${parent || tableName}_${objectKey}`, tableDetectedCallback, false), } ); } else if (Object.keys(value).length !== 0) { @@ -75,7 +81,8 @@ const handleTableCandidate = (obj, tableName, tableDetectedCallback, isRootLevel }; if (!isObjectList(obj)) { if (isList(obj)) { - for (var listKey in obj) { + const firebaseObject = makeFirebaseListFromArr(obj); + for (var listKey in firebaseObject) { rowArray.push({ _value: listKey, _id: uuid(), diff --git a/community/tools/firebase2graphql/src/firebase/utils.js b/community/tools/firebase2graphql/src/firebase/utils.js index 17a7d40d86c..88e4c15b30c 100644 --- a/community/tools/firebase2graphql/src/firebase/utils.js +++ b/community/tools/firebase2graphql/src/firebase/utils.js @@ -42,44 +42,96 @@ const isList = obj => { if (Object.keys(obj).length === 0) { return false; } - for (var objKey in obj) { - if (obj[objKey] === null) { + if (obj.constructor.name === 'Array') { + let arrayElementDataType = null; + for (let _i = obj.length - 1; _i >= 0; _i--) { + if (arrayElementDataType === null) { + arrayElementDataType = typeof obj[_i]; + } else if (arrayElementDataType !== typeof obj[_i]) { + return false; + } + } + return true; + } + for (var objkey in obj) { + if (obj[objkey] === null) { return false; } - if (obj[objKey].constructor.name !== 'Boolean' || !obj[objKey]) { + if (obj[objkey].constructor.name !== 'Boolean' || !obj[objkey]) { return false; } } return true; }; +const makeFirebaseListFromObj = obj => { + if (obj.constructor.name === 'Array') { + const firebaseList = {}; + for (var i = obj.length - 1; i >= 0; i--) { + const element = obj[i]; + firebaseList[i.toString()] = element; + } + return firebaseList; + } + return obj; +}; + +const makeFirebaseListFromArr = obj => { + if (obj.constructor.name === 'Array') { + const firebaseList = {}; + for (var i = obj.length - 1; i >= 0; i--) { + const element = obj[i]; + firebaseList[element] = true; + } + return firebaseList; + } + return obj; +}; + const isObjectList = obj => { if (obj === null || obj === undefined) { return false; } const listChildStructure = {}; - for (var key in obj) { - if (obj[key] === null) { + const checkElementConsistency = element => { + if (element === null) { return false; } - if (typeof obj[key] !== 'object') { + if (typeof element !== 'object') { return false; } - if (Object.keys(obj[key]).length === 0) { + if (Object.keys(obj).length === 0) { return false; } - - for (var childKey in obj[key]) { + for (var childKey in element) { if (!listChildStructure[childKey]) { - if (obj[key][childKey] !== null && obj[key][childKey] !== undefined) { - listChildStructure[childKey] = typeof obj[key][childKey]; + if (element[childKey] !== null && element[childKey] !== undefined) { + listChildStructure[childKey] = typeof element[childKey]; } - } else if (obj[key][childKey] !== null && obj[key][childKey] !== undefined) { - if (typeof obj[key][childKey] !== listChildStructure[childKey]) { + } else if (element[childKey] !== null && element[childKey] !== undefined) { + if (typeof element[childKey] !== listChildStructure[childKey]) { return false; } } } + return true; + }; + if (obj.constructor.name === 'Array') { + for (let _i = obj.length - 1; _i >= 0; _i--) { + let element = obj[_i]; + let consistent = checkElementConsistency(element); + if (!consistent) { + return false; + } + } + return true; + } + for (var key in obj) { + const element = obj[key]; + let consistent = checkElementConsistency(element); + if (!consistent) { + return false; + } } return true; }; @@ -91,4 +143,6 @@ module.exports = { isRandomList, isList, isObjectList, + makeFirebaseListFromObj, + makeFirebaseListFromArr, }; diff --git a/community/tools/firebase2graphql/src/import/generateTables.js b/community/tools/firebase2graphql/src/import/generateTables.js index 279b987dd28..3fdcb356373 100644 --- a/community/tools/firebase2graphql/src/import/generateTables.js +++ b/community/tools/firebase2graphql/src/import/generateTables.js @@ -17,10 +17,10 @@ const getDataType = (data, column) => { if (data.constructor.name === 'Date') { return 'timestamptz'; } - if (data.constructor.name === 'Object') { + if (data.constructor.name === 'Object' || data.constructor.name === 'Array') { return 'json'; } - throwError(`Message: invalid data type given for column ${column}: ${typeof data}`); + throwError(`Message: invalid data type given for column ${column}: ${data.constructor.name}`); }; const isForeign = (name, db) => { diff --git a/community/tools/firebase2graphql/test/data-sets/array_example.js b/community/tools/firebase2graphql/test/data-sets/array_example.js new file mode 100644 index 00000000000..8794f619f77 --- /dev/null +++ b/community/tools/firebase2graphql/test/data-sets/array_example.js @@ -0,0 +1,14 @@ +module.exports = { + appointment: { + '-LMlfYiyfmR7RxODd2lF': { + 'appointment-notes': { + images: [ + 'https://firebasestorage.googleapis.comd2e34932caf', + ], + 'notes-content': 'testing........', + }, + 'created-time': 1537358052, + 'work-request-id': '-LMlf80ePhwjG4Rkh8tY', + }, + }, +};