Skip to content

Commit

Permalink
closes #20
Browse files Browse the repository at this point in the history
  • Loading branch information
Jong authored and Jong committed Jan 4, 2019
1 parent d5529a5 commit 7f670c8
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 88 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- The `xiblepm nodepack publish` command now informs the user of the published version, after completion. ([#44](https://github.com/SpectrumBroad/xible/issues/44))

- The UI now allows users to search and install flows from the registry. This feature can be found in the 'Flows' section of your XIBLE installation. ([#20](https://github.com/SpectrumBroad/xible/issues/20))

### Changed
- The `xible config list` command now returns a human readable key-value list. The key nesting is identified by dots, must like json paths and how `xible config set` and `xible config get` already worked. A `xible config json` command has been added which sticks to the previous behaviour and simply prints the actual JSON (formatted) config. ([#23](https://github.com/SpectrumBroad/xible/issues/23))

Expand Down
12 changes: 11 additions & 1 deletion app/CliQueue/index.js
Expand Up @@ -45,7 +45,7 @@ module.exports = (XIBLE) => {
// get the latest contents of the queue file
fs.readFile(`${XIBLE.configPath}.queue`, {
encoding: 'utf8'
}, (readQueueErr, data) => {
}, async (readQueueErr, data) => {
// handle errors reading the queue file
if (readQueueErr) {
debug(readQueueErr);
Expand Down Expand Up @@ -85,6 +85,16 @@ module.exports = (XIBLE) => {
case 'flow.delete':
flow.delete();
break;
case 'registry.flow.install': {
if (obj.registryFlowName) {
const registryFlow = await XIBLE.Registry.Flow.getByName(obj.registryFlowName);
if (!registryFlow) {
continue;
}
registryFlow.install(obj.altFlowName);
}
break;
}
case 'server.stop':
XIBLE.close();
break;
Expand Down
84 changes: 62 additions & 22 deletions app/Flow/index.js
Expand Up @@ -68,12 +68,12 @@ module.exports = (XIBLE, EXPRESS_APP) => {
* Init flows from a given path.
* This will parse all json files except for _status.json into flows.
* Note that a path cannot be initiated twice because it is used for saveStatuses()
* @param {String} path The path to the directory containing the flows.
* @param {String} flowPath The path to the directory containing the flows.
* @param {Boolean} cleanVault Indicates whether the json data from each flow
* needs vault sanitizing.
* @return {Object.<String, Flow>} List of flows by their _id.
* @return {Promise.<Object.<String, Flow>>} List of flows by their _id.
*/
static initFromPath(flowPath, cleanVault) {
static async initFromPath(flowPath, cleanVault) {
flowDebug(`init flows from "${flowPath}"`);
if (this.flowPath) {
throw new Error(`cannot init multiple flow paths. "${this.flowPath}" already init`);
Expand All @@ -98,37 +98,77 @@ module.exports = (XIBLE, EXPRESS_APP) => {
files = [];
}

// get the flows and load them
for (let i = 0; i < files.length; i += 1) {
const filepath = `${flowPath}/${files[i]}`;

// only fetch json files but ignore _status.json and hidden files
if (files[i].substring(0, 1) !== '_' && files[i].substring(0, 1) !== '.' && fs.statSync(filepath).isFile() && path.extname(filepath) === '.json') {
try {
const json = JSON.parse(fs.readFileSync(filepath));
if (json._id) {
const flow = new Flow(XIBLE);
flow.initJson(json, cleanVault);
flows[flow._id] = flow;
}
} catch (err) {
flowDebug(`could not init "${filepath}": ${err.stack}`);
await Promise.all(files.map(async (file) => {
try {
const flow = await this.initOneFromPath(flowPath, file, cleanVault);
if (flow) {
flows[flow._id] = flow;
}
} catch (err) {
flowDebug(`could not init "${file}": ${err.stack}`);
}
}
}));

return flows;
}

/**
* Init a single flow from a given path and filename.
* This will parse the json file into a flows.
* @param {String} flowPath The path to the directory containing the fileName.
* @param {String} fileName The name of the file to to parse.
* @param {Boolean} cleanVault Indicates whether the json data from each flow
* needs vault sanitizing.
* @returns {Promise.<Flow>} A single Flow object.
* @since 0.16.0
*/
static initOneFromPath(flowPath, fileName, cleanVault) {
return new Promise((resolve, reject) => {
if (flowPath !== this.flowPath) {
reject(new Error(`flowPath "${this.flowPath}" already initialized and differs from "${flowPath}"`));
return;
}

const filePath = `${flowPath}/${fileName}`;

if (
fileName.substring(0, 1) !== '_'
&& fileName.substring(0, 1) !== '.'
&& fs.statSync(filePath).isFile()
&& path.extname(filePath) === '.json'
) {
fs.readFile(filePath, { encoding: 'utf8' }, (err, data) => {
if (err) {
reject(err);
return;
}

try {
const json = JSON.parse(data);
if (json._id) {
const flow = new Flow();
flow.initJson(json, cleanVault);
resolve(flow);
}
} catch (flowParseErr) {
reject(flowParseErr);
}
});
} else {
resolve();
}
});
}

/**
* Initializes all flows from a given path, by running them through initFromPath().
* Processes the related flow statuses and starts/inits where necessary.
* @param {String} flowPath The path to the directory containing the flows.
* @return {Object.<String, Flow>} List of flows by their _id.
* @returns {Promise.<Object.<String, Flow>>} List of flows by their _id.
* @since 0.5.0
*/
static init(flowPath) {
const flows = this.initFromPath(flowPath);
static async init(flowPath) {
const flows = await this.initFromPath(flowPath);

// start all flows which had status running before
// also do some cleaning while we're at it
Expand Down
3 changes: 2 additions & 1 deletion app/Registry/index.js
Expand Up @@ -69,7 +69,8 @@ module.exports = (XIBLE, EXPRESS_APP) => {
reject(err);
return;
}
resolve();

resolve(XIBLE.Flow.initOneFromPath(flowPath, `${this._id}.json`, true));
});
});
};
Expand Down
36 changes: 36 additions & 0 deletions app/Registry/routes.js
Expand Up @@ -65,4 +65,40 @@ module.exports = (XIBLE_REGISTRY, XIBLE, EXPRESS_APP) => {
res.status(500).end();
});
});

// returns a list of online flows
EXPRESS_APP.get('/api/registry/flows', async (req, res) => {
const searchString = req.query.search;
let flows;
if (!searchString) {
flows = await XIBLE_REGISTRY.Flow.getAll()
} else {
flows = await XIBLE_REGISTRY.Flow.search(searchString);
}

res.json(flows);
});

// get a flow by a given name
EXPRESS_APP.param('regFlowName', async (req, res, next, flowName) => {
req.locals.flowName = flowName;
const flow = await XIBLE_REGISTRY.Flow.getByName(flowName);
if (!flow) {
res.status(404).end();
return;
}

req.locals.flow = flow;
next();
});

EXPRESS_APP.get('/api/registry/flows/:regFlowName', (req, res) => {
res.json(req.locals.flow);
});

// install a flow
EXPRESS_APP.patch('/api/registry/flows/:regFlowName/install', async (req, res) => {
await req.locals.flow.install();
res.end();
});
};
55 changes: 31 additions & 24 deletions bin/xiblepm.js
Expand Up @@ -160,31 +160,31 @@ if (userToken) {
// cli context and commands
const cli = {
flow: {
publish(flowName) {
async publish(flowName) {
if (!flowName) {
return Promise.reject('The flow name must be provided');
throw 'The flow name must be provided';
}

if (!xible.Config.getValue('registry.flows.allowpublish')) {
return Promise.reject('Your config does not allow to publish flows to the registry');
throw 'Your config does not allow to publish flows to the registry';
}

let flowPath = xible.Config.getValue('flows.path');
if (!flowPath) {
return Promise.reject('no "flows.path" configured');
throw 'no "flows.path" configured';
}
flowPath = xible.resolvePath(flowPath);
xible.Flow.initFromPath(flowPath, true);
await xible.Flow.initFromPath(flowPath, true);
const flow = xible.getFlowById(flowName);
if (!flow) {
return Promise.reject(`No such flow "${flowName}"`);
throw `No such flow "${flowName}"`;
}

let flowJson = flow.json;
const altFlowId = opts.altname;
if (altFlowId) {
if (!xible.Flow.validateId(altFlowId)) {
return Promise.reject('flow _id/name cannot contain reserved/unsave characters');
throw 'flow _id/name cannot contain reserved/unsave characters';
}
flowJson = Object.assign({}, flowJson);
flowJson._id = altFlowId;
Expand All @@ -194,7 +194,7 @@ const cli = {
// verify that we have a token
const token = getUserToken();
if (!token) {
return Promise.reject('You are not logged in. Run "xiblepm user login" or "xiblepm user add" to create a new user.');
throw 'You are not logged in. Run "xiblepm user login" or "xiblepm user add" to create a new user.';
}

// verify that we're logged in
Expand Down Expand Up @@ -225,38 +225,45 @@ const cli = {
});
});
},
install(flowName) {
async install(flowName) {
if (!flowName) {
return Promise.reject('The flow name must be provided');
throw 'The flow name must be provided';
}

if (!xible.Config.getValue('registry.flows.allowinstall')) {
return Promise.reject('Your config does not allow to install flows from the registry');
throw 'Your config does not allow to install flows from the registry';
}

const altFlowName = opts.altname;
if (altFlowName && !xible.Flow.validateId(altFlowName)) {
throw 'flow _id/name cannot contain reserved/unsave characters';
}

const altFlowId = opts.altname;
let flowPath = xible.Config.getValue('flows.path');
if (!flowPath) {
return Promise.reject('no "flows.path" configured');
throw 'no "flows.path" configured';
}
flowPath = xible.resolvePath(flowPath);
xible.Flow.initFromPath(flowPath);
const flow = xible.getFlowById(altFlowId || flowName);
await xible.Flow.initFromPath(flowPath);
const flow = xible.getFlowById(altFlowName || flowName);

// check if the flow already exists
if (flow && !opts.force) {
return Promise.reject('A flow already exists by this name. Provide --force to overwrite.');
throw 'A flow already exists by this name. Provide --force to overwrite.';
}

return xible.Registry.Flow
.getByName(flowName)
.then((registryFlow) => {
if (!registryFlow) {
return Promise.reject(`Flow "${flowName}" does not exist`);
}
return registryFlow.install(altFlowId)
.then(() => log(`Installed flow "${altFlowId || registryFlow.name}"`));
const registryFlow = await xible.Registry.Flow.getByName(flowName);
if (!registryFlow) {
throw `Flow "${flowName}" does not exist`;
}

xible.CliQueue.add({
method: 'registry.flow.install',
registryFlowName: flowName,
altFlowName
});

log(`Installed flow "${altFlowName || registryFlow.name}"`);
},
search(str) {
if (!str) {
Expand Down

0 comments on commit 7f670c8

Please sign in to comment.