├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── lib ├── getBookmarks.js ├── getDataConnections.js ├── getDimensions.js ├── getFields.js ├── getList.js ├── getMeasures.js ├── getMediaList.js ├── getSnapshots.js ├── getVariables.js └── serializeapp.js ├── package-lock.json └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | 45 | # Logs 46 | logs 47 | *.log 48 | npm-debug.log* 49 | 50 | # Runtime data 51 | pids 52 | *.pid 53 | *.seed 54 | 55 | # Directory for instrumented libs generated by jscoverage/JSCover 56 | lib-cov 57 | 58 | # Coverage directory used by tools like istanbul 59 | coverage 60 | 61 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 62 | .grunt 63 | 64 | # node-waf configuration 65 | .lock-wscript 66 | 67 | # Compiled binary addons (http://nodejs.org/api/addons.html) 68 | build/Release 69 | 70 | # Dependency directory 71 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 72 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2015 Alexander Karlsson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 6 | (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 7 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 14 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## serializeapp 2 | 3 | serializeapp is a node utility module to serialize a Qlik Sense app into a JSON object. 4 | Pass it a [qsocks](https://github.com/mindspank/qsocks) or a [enigma.js](https://github.com/qlik-oss/enigma.js) app object and it returns a promise containing the JSON representation of that app. 5 | 6 | Verified to work in the browser using browserify or webpack. 7 | 8 | ## installing 9 | ``` 10 | npm install serializeapp 11 | ``` 12 | or 13 | ``` 14 | yarn add serializeapp 15 | ``` 16 | 17 | ## examples 18 | 19 | Connect to Qlik Sense Desktop, open a app and pass that into serializeapp. 20 | 21 | ```javascript 22 | var qsocks = require('qsocks') 23 | var serializeapp = require('serializeapp') 24 | 25 | qsocks.Connect() 26 | .then(global => global.openDoc('Executive Dashboard.qvf')) 27 | .then(app => serializeapp(app)) 28 | .then(result => console.log(result)) 29 | ``` 30 | ```javascript 31 | const serializeapp = require('serializeapp') 32 | const enigma = require('enigma.js') 33 | const WebSocket = require('ws') 34 | 35 | enigma.getService('qix', { 36 | schema: require(`./node_modules/enigma.js/schemas/qix/3.2/schema.json`), 37 | session: { 38 | host: 'localhost', 39 | port: 4848, 40 | secure: false 41 | }, 42 | createSocket: (url) => new WebSocket(url) 43 | }) 44 | .then(qix => qix.global.openDoc('Executive Dashboard.qvf')) 45 | .then(app => serializeapp(app)) 46 | .then(result => console.log(result)) 47 | ``` 48 | ## Returns 49 |
50 | {
51 | 	properties: {}, -> @Object [AppEntry](https://help.qlik.com/sense/2.0/en-us/developer/Subsystems/EngineAPI/Content/Structs/AppEntry.htm)
52 | 	loadscript: '', -> @String Loadscript
53 | 	sheets: [], -> @Array - Array of [GenericObjectEntry](https://help.qlik.com/sense/2.0/en-us/developer/Subsystems/EngineAPI/Content/Structs/GenericObjectEntry.htm) and its children
54 | 	stories: [], -> @Array - Array of [GenericObjectEntry](https://help.qlik.com/sense/2.0/en-us/developer/Subsystems/EngineAPI/Content/Structs/GenericObjectEntry.htm) and its children
55 | 	masterobjects: [], -> @Array - Array of [GenericObjectEntry](https://help.qlik.com/sense/2.0/en-us/developer/Subsystems/EngineAPI/Content/Structs/GenericObjectEntry.htm)
56 | 	dataconnections: [], -> @Array - Array of [Connection](https://help.qlik.com/sense/2.0/en-us/developer/Subsystems/EngineAPI/Content/Structs/Connection.htm)
57 | 	dimensions: [], -> @Array - Array of [GenericDimensionProperties](https://help.qlik.com/sense/2.0/en-us/developer/Subsystems/EngineAPI/Content/Structs/GenericDimensionProperties.htm)
58 | 	measures: [], -> @Array - Array of [GenericMeasureProperties](https://help.qlik.com/sense/2.0/en-us/developer/Subsystems/EngineAPI/Content/Structs/GenericMeasureProperties.htm)
59 | 	bookmarks: [], -> @Array - Array of[GenericBookmarkLayout](https://help.qlik.com/sense/2.0/en-us/developer/Subsystems/EngineAPI/Content/Structs/GenericDimensionLayout.htm)
60 | 	embeddedmedia: [], -> @Array of [MediaListItem](https://help.qlik.com/sense/2.0/en-us/developer/Subsystems/EngineAPI/Content/Structs/MediaListItem.htm)
61 | 	fields: [], -> @Array of [NxFieldDescription](https://help.qlik.com/en-US/sense-developer/2.0/Subsystems/EngineAPI/Content/Structs/NxFieldDescription.htm)
62 | 	snapshots: [] -> @Array of Array of [GenericBookmarkLayout](https://help.qlik.com/sense/2.0/en-us/developer/Subsystems/EngineAPI/Content/Structs/GenericDimensionLayout.htm)
63 | }
64 | 
65 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/serializeapp'); -------------------------------------------------------------------------------- /lib/getBookmarks.js: -------------------------------------------------------------------------------- 1 | var Promise = require('promise'); 2 | 3 | function getBookmarks(app) { 4 | 5 | return app.createSessionObject({ 6 | qBookmarkListDef: { 7 | qType: 'bookmark', 8 | qData: { 9 | info: '/qDimInfos' 10 | }, 11 | qMeta: {} 12 | }, 13 | qInfo: { qId: "BookmarkList", qType: "BookmarkList" } 14 | }).then(function (list) { 15 | return list.getLayout().then(function (layout) { 16 | return Promise.all(layout.qBookmarkList.qItems.map(function (d) { 17 | return app.getBookmark(d.qInfo.qId).then(function (bookmark) { 18 | return bookmark.getProperties().then(function(properties) { 19 | return bookmark.getLayout().then(function(layout) { 20 | properties.qData = properties.qData || {}; 21 | properties.qData.qBookMark = layout.qBookmark; 22 | return properties; 23 | }); 24 | 25 | }); 26 | }); 27 | })); 28 | }); 29 | }); 30 | 31 | }; 32 | 33 | module.exports = getBookmarks; -------------------------------------------------------------------------------- /lib/getDataConnections.js: -------------------------------------------------------------------------------- 1 | var Promise = require('promise'); 2 | 3 | function getDataConnections(app) { 4 | return app.getConnections() 5 | .then(function (connections) { 6 | return Promise.all(connections.map(function(d) { 7 | return app.getConnection(d.qId) 8 | })) 9 | }); 10 | }; 11 | 12 | module.exports = getDataConnections; -------------------------------------------------------------------------------- /lib/getDimensions.js: -------------------------------------------------------------------------------- 1 | var Promise = require('promise'); 2 | 3 | function getDimensions(app) { 4 | return app.createSessionObject({ 5 | qDimensionListDef: { 6 | qType: 'dimension', 7 | qData: { 8 | info: '/qDimInfos' 9 | }, 10 | qMeta: {} 11 | }, 12 | qInfo: { qId: "DimensionList", qType: "DimensionList" } 13 | }).then(function (list) { 14 | return list.getLayout().then(function (layout) { 15 | return Promise.all(layout.qDimensionList.qItems.map(function (d) { 16 | return app.getDimension(d.qInfo.qId).then(function (dimension) { 17 | return dimension.getProperties(); 18 | }); 19 | })); 20 | }); 21 | }); 22 | }; 23 | 24 | module.exports = getDimensions; -------------------------------------------------------------------------------- /lib/getFields.js: -------------------------------------------------------------------------------- 1 | var Promise = require('promise'); 2 | 3 | function getFields(app) { 4 | 5 | return app.createSessionObject({ 6 | qFieldListDef: { 7 | qShowSystem: true, 8 | qShowHidden: true, 9 | qShowSrcTables: true, 10 | qShowSemantic: true 11 | }, 12 | qInfo: { qId: 'FieldList', qType: 'FieldList' } 13 | }).then(function (fields) { 14 | return fields.getLayout().then(function (layout) { 15 | return layout.qFieldList.qItems; 16 | }); 17 | }); 18 | 19 | }; 20 | 21 | module.exports = getFields; -------------------------------------------------------------------------------- /lib/getList.js: -------------------------------------------------------------------------------- 1 | var Promise = require('promise'); 2 | 3 | function getList(app, objectType) { 4 | return app.createSessionObject({ 5 | qAppObjectListDef: { 6 | qType: objectType, 7 | qData: { 8 | id: "/qInfo/qId" 9 | } 10 | }, 11 | qInfo: { 12 | qId: objectType + 'List', 13 | qType: objectType + 'List' 14 | }, 15 | qMetaDef: {}, 16 | qExtendsId: '' 17 | }).then(function (list) { 18 | return list.getLayout().then(function (layout) { 19 | return Promise.all(layout.qAppObjectList.qItems.map(function(d) { 20 | return app.getObject(d.qInfo.qId).then(function (handle) { 21 | return handle.getFullPropertyTree(); 22 | }); 23 | })); 24 | }); 25 | }); 26 | 27 | }; 28 | 29 | module.exports = getList; -------------------------------------------------------------------------------- /lib/getMeasures.js: -------------------------------------------------------------------------------- 1 | var Promise = require('promise'); 2 | 3 | function getMeasures(app) { 4 | return app.createSessionObject({ 5 | qMeasureListDef: { 6 | qType: 'measure', 7 | qData: { 8 | info: '/qDimInfos' 9 | }, 10 | qMeta: {} 11 | }, 12 | qInfo: { qId: "MeasureList", qType: "MeasureList" } 13 | }).then(function (list) { 14 | return list.getLayout().then(function (layout) { 15 | return Promise.all(layout.qMeasureList.qItems.map(function (d) { 16 | return app.getMeasure(d.qInfo.qId).then(function (measure) { 17 | return measure.getProperties().then(function(properties) { return properties; }); 18 | }); 19 | })); 20 | }); 21 | }); 22 | }; 23 | module.exports = getMeasures; -------------------------------------------------------------------------------- /lib/getMediaList.js: -------------------------------------------------------------------------------- 1 | function getMediaList(app) { 2 | return app.createSessionObject({ 3 | qInfo: { 4 | qId: 'mediaList', 5 | qType: 'MediaList' 6 | }, 7 | qMediaListDef: {} 8 | }).then(function (list) { 9 | return list.getLayout().then(function (layout) { 10 | return layout.qMediaList.qItems.filter(function(d) { 11 | // This is dodgy... 12 | return d.qUrlDef.substring(0,7) === '/media/'; 13 | }); 14 | }); 15 | }); 16 | }; 17 | module.exports = getMediaList; -------------------------------------------------------------------------------- /lib/getSnapshots.js: -------------------------------------------------------------------------------- 1 | var Promise = require('promise'); 2 | 3 | function getSnapshots(app) { 4 | 5 | return app.createSessionObject({ 6 | qBookmarkListDef: { 7 | qType: 'snapshot', 8 | qData: { 9 | info: '/qDimInfos' 10 | }, 11 | qMeta: {} 12 | }, 13 | qInfo: { qId: "BookmarkList", qType: "BookmarkList" } 14 | }).then(function (list) { 15 | return list.getLayout().then(function (layout) { 16 | return Promise.all(layout.qBookmarkList.qItems.map(function (d) { 17 | return app.getBookmark(d.qInfo.qId).then(function (bookmark) { 18 | return bookmark.getProperties().then(function(properties) { return properties; }); 19 | }); 20 | })); 21 | }); 22 | }); 23 | 24 | }; 25 | 26 | module.exports = getSnapshots; -------------------------------------------------------------------------------- /lib/getVariables.js: -------------------------------------------------------------------------------- 1 | var Promise = require('promise'); 2 | 3 | function getVariables(app) { 4 | 5 | return app.createSessionObject({ 6 | qVariableListDef: { 7 | qType: 'variable', 8 | qShowReserved: true, 9 | qShowConfig: true, 10 | qData: { 11 | info: '/qDimInfos' 12 | }, 13 | qMeta: {} 14 | }, 15 | qInfo: { qId: "VariableList", qType: "VariableList" } 16 | }).then(function (list) { 17 | return list.getLayout().then(function (layout) { 18 | return Promise.all(layout.qVariableList.qItems.map(function (d) { 19 | return app.getVariableById(d.qInfo.qId).then(function (variable) { 20 | return variable.getProperties().then(function(properties) { 21 | if (d.qIsScriptCreated) properties.qIsScriptCreated = d.qIsScriptCreated; 22 | if (d.qIsReserved) properties.qIsReserved = d.qIsReserved; 23 | if (d.qIsConfig) properties.qIsConfig = d.qIsConfig; 24 | 25 | return properties; 26 | }); 27 | }); 28 | })); 29 | }); 30 | }); 31 | 32 | }; 33 | 34 | module.exports = getVariables; -------------------------------------------------------------------------------- /lib/serializeapp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Library modules 3 | */ 4 | var getList = require('./getList'); 5 | var getDimensions = require('./getDimensions'); 6 | var getMeasures = require('./getMeasures'); 7 | var getBookmarks = require('./getBookmarks'); 8 | var getMediaList = require('./getMediaList'); 9 | var getSnapshots = require('./getSnapshots'); 10 | var getFields = require('./getFields'); 11 | var getConnections = require('./getDataConnections'); 12 | var getVariables = require('./getVariables'); 13 | 14 | /** 15 | * serializeApp 16 | * 17 | * Accepts a qsocks app connection object and returns a promise. 18 | * Resolves a serialized app. 19 | */ 20 | 21 | function serializeApp(app, callback) { 22 | if(!app || typeof app.createSessionObject !== 'function') { 23 | throw new Error('Expects a valid qsocks app connection') 24 | }; 25 | 26 | var appObj = {}; 27 | 28 | // Generic Lists to be iterated over for qAppObjectListDef 29 | var LISTS = [{'sheets': 'sheet'}, {'stories': 'story'}, {'masterobjects': 'masterobject'}, {'appprops': 'appprops'}]; 30 | 31 | // Property name mapping against methods 32 | var METHODS = { 33 | dimensions: getDimensions, 34 | measures: getMeasures, 35 | bookmarks: getBookmarks, 36 | embeddedmedia: getMediaList, 37 | snapshots: getSnapshots, 38 | fields: getFields, 39 | dataconnections: getConnections, 40 | variables: getVariables 41 | }; 42 | 43 | return app.getAppProperties().then(function (properties) { 44 | return appObj.properties = properties; 45 | }) 46 | .then(function () { 47 | return app.getScript().then(function (script) { return appObj.loadScript = script; }) 48 | }) 49 | .then(function () { 50 | return Promise.all(LISTS.map(function (d, i) { 51 | return getList(app, d[Object.keys(d)[0]]) 52 | })).then(function (data) { return LISTS.forEach(function(d, y) { appObj[Object.keys(d)[0]] = data[y] }); }); 53 | }) 54 | .then(function () { 55 | return Promise.all(Object.keys(METHODS).map(function(key, i) { 56 | return METHODS[key](app).then(function(data) { return appObj[key] = data }); 57 | })); 58 | }) 59 | .then(function() { 60 | return appObj; 61 | }) 62 | 63 | }; 64 | module.exports = serializeApp; -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serializeapp", 3 | "version": "3.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "asap": { 8 | "version": "2.0.6", 9 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 10 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" 11 | }, 12 | "promise": { 13 | "version": "8.0.1", 14 | "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.1.tgz", 15 | "integrity": "sha1-5F1osAoXZHttpxG/he1u1HII9FA=", 16 | "requires": { 17 | "asap": "2.0.6" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serializeapp", 3 | "version": "3.0.0", 4 | "description": "Serializes a Qlik Sense App into JSON", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Alexander Karlsson ", 10 | "maintainers": [ 11 | { 12 | "name": "Alexander Karlsson", 13 | "email": "akl@qlik.com" 14 | } 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/mindspank/serializeapp.git" 19 | }, 20 | "keywords": [ 21 | "qlik", 22 | "qsocks" 23 | ], 24 | "bugs": { 25 | "url": "https://github.com/mindspank/serializeapp/issues" 26 | }, 27 | "homepage": "https://github.com/mindspank/serializeapp", 28 | "license": "MIT", 29 | "dependencies": { 30 | "promise": "^8.0.1" 31 | } 32 | } 33 | --------------------------------------------------------------------------------