Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/sc 124023/refactor-list #702

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 33 additions & 17 deletions src/cmd/logic-function.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const settings = require('../../settings');
const { normalizedApiError } = require('../lib/api-client');
const templateProcessor = require('../lib/template-processor');
const { slugify } = require('../lib/utilities');
const LogicFunction = require('../lib/logic-function');

const logicFunctionTemplatePath = path.join(__dirname, '/../../assets/logicFunction');
const CLICommandBase = require('./base');
Expand All @@ -25,25 +26,23 @@ module.exports = class LogicFunctionsCommand extends CLICommandBase {

async list({ org }) {
this._setOrg(org);

await this._getLogicFunctionList();

if (this.logicFuncList === null || this.logicFuncList.length === 0) {
const logicFunctions = await LogicFunction.listFromCloud({ org, api: this.api });
if (!logicFunctions.length) {
this._printListHelperOutput();
} else {
this._printListOutput({ logicFunctionsList: this.logicFuncList });
this._printListOutput({ logicFunctionsList: logicFunctions });
}
}

_printListHelperOutput() {
this.ui.stdout.write(`No Logic Functions deployed in your ${getOrgName(this.org)}.${os.EOL}`);
this.ui.stdout.write(`No Logic Functions deployed in ${getOrgName(this.org)}.${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
this.ui.stdout.write(`To create a Logic Function, see ${this.ui.chalk.yellow('particle logic-function create')}.${os.EOL}`);
this.ui.stdout.write(`To download an existing Logic Function, see ${this.ui.chalk.yellow('particle logic-function get')}.${os.EOL}`);
}

_printListOutput({ logicFunctionsList }) {
this.ui.stdout.write(`Logic Functions deployed in your ${getOrgName(this.org)}:${os.EOL}`);
this.ui.stdout.write(`Logic Functions deployed in ${getOrgName(this.org)}:${os.EOL}`);
logicFunctionsList.forEach((item) => {
// We assume at least one trigger
this.ui.stdout.write(`- ${item.name} (${item.enabled ? this.ui.chalk.cyanBright('enabled') : this.ui.chalk.cyan('disabled')})${os.EOL}`);
Expand Down Expand Up @@ -110,7 +109,9 @@ module.exports = class LogicFunctionsCommand extends CLICommandBase {
const slugName = slugify(name);
const destinationPath = path.join(logicFuncPath, slugName);

this.ui.stdout.write(`Creating Logic Function ${this.ui.chalk.bold(name)} for ${getOrgName(this.org)}...${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
this.ui.stdout.write(`Creating Logic Function ${this.ui.chalk.cyan(name)} for ${getOrgName(this.org)}...${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
const logicFuncNameDeployed = await this._validateLFName({ name });
if (logicFuncNameDeployed) {
throw new Error(`Logic Function ${name} already exists in ${getOrgName(this.org)}. Use a new name for your Logic Function.`);
Expand All @@ -123,15 +124,17 @@ module.exports = class LogicFunctionsCommand extends CLICommandBase {
templatePath: logicFunctionTemplatePath,
destinationPath: path.join(logicFuncPath, slugName)
});
this.ui.stdout.write(`Successfully created ${this.ui.chalk.bold(name)} in ${this.ui.chalk.bold(logicFuncPath)}${os.EOL}`);
this.ui.stdout.write(`Successfully created ${this.ui.chalk.cyan(name)} locally in ${this.ui.chalk.bold(logicFuncPath)}${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
this.ui.stdout.write(`Files created:${os.EOL}`);
createdFiles.forEach((file) => {
this.ui.stdout.write(`- ${file}${os.EOL}`);
});
this.ui.stdout.write(`${os.EOL}Guidelines for creating your Logic Function can be found <TBD>.${os.EOL}`);
this.ui.stdout.write(`${os.EOL}Guidelines for creating your Logic Function can be found here <TBD>.${os.EOL}`);
this.ui.stdout.write(`Once you have written your Logic Function, run${os.EOL}`);
this.ui.stdout.write('- ' + this.ui.chalk.yellow('\'particle logic-function execute\'') + ` to run your Function${os.EOL}`);
this.ui.stdout.write('- ' + this.ui.chalk.yellow('\'particle logic-function deploy\'') + ` to deploy your new changes${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
return createdFiles;
}

Expand Down Expand Up @@ -279,18 +282,27 @@ module.exports = class LogicFunctionsCommand extends CLICommandBase {
const api = createAPI();
try {
this.ui.stdout.write(`Executing Logic Function ${this.ui.chalk.bold(logicCodeFileName)} for ${orgName}...${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
const { result } = await api.executeLogicFunction({ org, logic, data });
const resultType = result.status === 'Success' ? this.ui.chalk.cyanBright(result.status) : this.ui.chalk.red(result.status);
this.ui.stdout.write(`Execution Status: ${resultType}${os.EOL}`);
this.ui.stdout.write(`Logs of the Execution:${os.EOL}`);
result.logs.forEach((log, index) => {
this.ui.stdout.write(` ${index + 1}.- ${JSON.stringify(log)}${os.EOL}`);
});
if (result.logs.length === 0) {
this.ui.stdout.write(`No logs obtained from Execution${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
} else {
this.ui.stdout.write(`Logs from Execution:${os.EOL}`);
result.logs.forEach((log, index) => {
this.ui.stdout.write(` ${index + 1}.- ${JSON.stringify(log)}${os.EOL}`);
});
this.ui.stdout.write(`${os.EOL}`);
}
if (result.err) {
this.ui.stdout.write(this.ui.chalk.red(`Error during Execution:${os.EOL}`));
this.ui.stdout.write(`${result.err}${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
} else {
this.ui.stdout.write(this.ui.chalk.cyanBright(`No errors during Execution.${os.EOL}`));
this.ui.stdout.write(`${os.EOL}`);
}
return { logicConfigContent, logicCodeContent };
} catch (error) {
Expand Down Expand Up @@ -369,7 +381,7 @@ module.exports = class LogicFunctionsCommand extends CLICommandBase {
const confirm = await this._prompt({
type: 'confirm',
name: 'proceed',
message: `A Logic Function with name ${name} is already available in the cloud ${getOrgName(this.org)}. Proceed and overwrite with the new content?`,
message: `A Logic Function with name ${name} is already available in the cloud ${getOrgName(this.org)}.${os.EOL}Proceed and overwrite with the new content?`,
choices: Boolean
});

Expand All @@ -396,16 +408,20 @@ module.exports = class LogicFunctionsCommand extends CLICommandBase {

async _printDeployOutput(name, id) {
this.ui.stdout.write(`${os.EOL}`);
this.ui.stdout.write(`Deploying Logic Function ${this.ui.chalk.bold(`${name} (${id})`)} to ${getOrgName(this.org)}...${os.EOL}`);
this.ui.stdout.write(`Deploying Logic Function ${this.ui.chalk.cyanBright(`${name} (${id})`)} to ${getOrgName(this.org)}...${os.EOL}`);
this.ui.stdout.write(`${this.ui.chalk.cyanBright('Success!')}${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
this.ui.stdout.write(`${this.ui.chalk.yellow('Visit \'console.particle.io\' to view results from your device(s)!')}${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
}

async _printDeployNewLFOutput(name, id) {
this.ui.stdout.write(`${os.EOL}`);
this.ui.stdout.write(`Deploying Logic Function ${this.ui.chalk.bold(`${name}`)} to ${getOrgName(this.org)}...${os.EOL}`);
this.ui.stdout.write(`${this.ui.chalk.cyanBright(`Success! Logic Function ${name} deployed with ${id}`)}${os.EOL}`);
this.ui.stdout.write(`${this.ui.chalk.cyanBright(`Success! Logic Function ${this.ui.chalk.cyanBright(name)} deployed with ${this.ui.chalk.cyanBright(id)}`)}${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
this.ui.stdout.write(`${this.ui.chalk.yellow('Visit \'console.particle.io\' to view results from your device(s)!')}${os.EOL}`);
this.ui.stdout.write(`${os.EOL}`);
}

async updateStatus({ org, name, id }, { enable }) {
Expand Down
50 changes: 45 additions & 5 deletions src/cmd/logic-function.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const LogicFunctionCommands = require('./logic-function');
const { PATH_FIXTURES_LOGIC_FUNCTIONS, PATH_TMP_DIR } = require('../../test/lib/env');
const templateProcessor = require('../lib/template-processor');
const { slugify } = require('../lib/utilities');
const LogicFunction = require('../lib/logic-function');

describe('LogicFunctionCommands', () => {
let logicFunctionCommands;
Expand All @@ -29,9 +30,10 @@ describe('LogicFunctionCommands', () => {
chalk: {
bold: sinon.stub(),
cyanBright: sinon.stub(),
cyan: sinon.stub(),
yellow: sinon.stub(),
grey: sinon.stub(),
red: sinon.stub()
red: sinon.stub(),
},
};
});
Expand All @@ -56,6 +58,43 @@ describe('LogicFunctionCommands', () => {
});
});

describe('list', () => {
it('lists logic functions in Sandbox account', async () => {
const logicListStub = sinon.stub(LogicFunction, 'listFromCloud').resolves(logicFunc1.logic_functions);
await logicFunctionCommands.list({});
expect(logicListStub.calledWith({ api: logicFunctionCommands.api, org: undefined })).to.be.true;
expect(logicListStub.calledOnce).to.be.true;
expect(logicFunctionCommands.ui.stdout.write.firstCall.args[0]).to.equal(`Logic Functions deployed in your Sandbox:${os.EOL}`);
expect(logicFunctionCommands.ui.stdout.write.secondCall.args[0]).to.equal(`- LF1 (undefined)${os.EOL}`);
});

it('lists logic functions in an org', async () => {
const logicListStub = sinon.stub(LogicFunction, 'listFromCloud').resolves(logicFunc1.logic_functions);
await logicFunctionCommands.list({ org: 'particle' });
expect(logicListStub.calledWith({ api: logicFunctionCommands.api, org: 'particle' })).to.be.true;
expect(logicListStub.calledOnce).to.be.true;
expect(logicFunctionCommands.ui.stdout.write.firstCall.args[0]).to.equal(`Logic Functions deployed in particle:${os.EOL}`);
expect(logicFunctionCommands.ui.stdout.write.secondCall.args[0]).to.equal(`- LF1 (undefined)${os.EOL}`);
});

it('shows help if no logic functions are found', async () => {
const logicListStub = sinon.stub(LogicFunction, 'listFromCloud').resolves([]);
await logicFunctionCommands.list({});
expect(logicListStub.calledWith({ api: logicFunctionCommands.api, org: undefined })).to.be.true;
expect(logicListStub.calledOnce).to.be.true;
expect(logicFunctionCommands.ui.stdout.write.firstCall.args[0]).to.equal(`No Logic Functions deployed in your Sandbox.${os.EOL}`);
});

it('shows help if no logic functions are found', async () => {
const logicListStub = sinon.stub(LogicFunction, 'listFromCloud').resolves([]);
await logicFunctionCommands.list({ api: logicFunctionCommands.api, org: 'particle' });
expect(logicListStub.calledWith({ api: logicFunctionCommands.api, org: 'particle' })).to.be.true;
expect(logicListStub.calledOnce).to.be.true;
expect(logicFunctionCommands.ui.stdout.write.firstCall.args[0]).to.equal(`No Logic Functions deployed in particle.${os.EOL}`);
});

});

describe('_getLogicFunctionList', () => {
it('lists logic functions in Sandbox', async () => {
nock('https://api.particle.io/v1', )
Expand Down Expand Up @@ -278,7 +317,7 @@ describe('LogicFunctionCommands', () => {
params: { filepath: path.join(PATH_FIXTURES_LOGIC_FUNCTIONS, 'lf1_proj') },
data: { foo: 'bar' }
});
expect(logicFunctionCommands.ui.stdout.write.callCount).to.equal(4);
expect(logicFunctionCommands.ui.stdout.write.callCount).to.equal(7);
expect(logicFunctionCommands.ui.chalk.bold.callCount).to.equal(1);
expect(logicFunctionCommands.ui.chalk.bold.firstCall.args[0]).to.equal('code.js'); // file name
expect(logicFunctionCommands.ui.chalk.cyanBright.callCount).to.equal(2);
Expand All @@ -293,13 +332,14 @@ describe('LogicFunctionCommands', () => {
params: { filepath: path.join(PATH_FIXTURES_LOGIC_FUNCTIONS, 'lf1_proj') },
dataPath: path.join(PATH_FIXTURES_LOGIC_FUNCTIONS, 'lf1_proj', 'sample', 'data.json')
});
expect(logicFunctionCommands.ui.stdout.write.callCount).to.equal(4);
expect(logicFunctionCommands.ui.stdout.write.callCount).to.equal(7);
expect(logicFunctionCommands.ui.chalk.bold.callCount).to.equal(1);
expect(logicFunctionCommands.ui.chalk.bold.firstCall.args[0]).to.equal('code.js'); // file name
expect(logicFunctionCommands.ui.chalk.cyanBright.callCount).to.equal(2);
expect(logicFunctionCommands.ui.chalk.cyanBright.firstCall.args[0]).to.equal('Success');
expect(logicFunctionCommands.ui.chalk.cyanBright.secondCall.args[0]).to.equal(`No errors during Execution.${os.EOL}`);
});

it('executes a logic function with user provided data from file and shows error', async () => {
nock('https://api.particle.io/v1', )
.intercept('/logic/execute', 'POST')
Expand All @@ -308,10 +348,10 @@ describe('LogicFunctionCommands', () => {
params: { filepath: path.join(PATH_FIXTURES_LOGIC_FUNCTIONS, 'lf1_proj') },
dataPath: path.join(PATH_FIXTURES_LOGIC_FUNCTIONS, 'lf1_proj', 'sample', 'data.json')
});
expect(logicFunctionCommands.ui.stdout.write.callCount).to.equal(5);
expect(logicFunctionCommands.ui.stdout.write.callCount).to.equal(8);
expect(logicFunctionCommands.ui.chalk.bold.firstCall.args[0]).to.equal('code.js'); // file name
expect(logicFunctionCommands.ui.stdout.write.lastCall.args[0]).to.equal(`Error message${os.EOL}`);
});

it('prompts if found multiple files', async () => {
nock('https://api.particle.io/v1', )
.intercept('/logic/execute', 'POST')
Expand Down
50 changes: 50 additions & 0 deletions src/lib/logic-function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const ParticleAPI = require('../cmd/api');
const settings = require('../../settings');

class LogicFunction {
constructor({ org, api = createAPI() }) {
// throw if api is not provided
this.org = org;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the final object. I'll refine it once I complete the whole refactor.

this.api = api;
this.configuration = {};
this.source = {
type: '',
code: ''
};
this.fileNames = {
source: '',
configuration: ''
};
}

static async listFromCloud({ org, api = createAPI() } = {}) {
const response = await api.getLogicFunctionList({ org: org });
const logicFunctions = response.logic_functions;
return logicFunctions;
}

static async listFromDisk({ path }) {
// TODO - implement
throw new Error(`Not implemented yet for ${path}`);
}

// should return an instance of LogicFunction
static async getByIdOrName({ id, name, list }) {
// TODO - implement
throw new Error(`Not implemented yet for ${id} or ${name} or ${list}`);
// throw new Error(`Not implemented yet for ${id} or ${name}`);
}

saveToDisk(path){
// TODO - implement
throw new Error(`Not implemented yet for ${path}`);
}
}

function createAPI() {
return new ParticleAPI(settings.apiUrl, {
accessToken: settings.access_token
});
}

module.exports = LogicFunction;
45 changes: 45 additions & 0 deletions src/lib/logic-function.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { expect } = require('chai');
const LogicFunction = require('./logic-function');
const nock = require('nock');


describe('LogicFunction', () => {
describe('list', () => {
it('returns an empty array if there are no logic functions', async () => {
nock('https://api.particle.io/v1/', )
.intercept('/logic/functions', 'GET')
.reply(200, { logic_functions : [] } );
const logicFunctions = await LogicFunction.listFromCloud();
expect(logicFunctions).to.have.lengthOf(0);
});
it('returns a list of logic functions', async () => {
nock('https://api.particle.io/v1/', )
.intercept('/logic/functions', 'GET')
.reply(200, { logic_functions : [{ name: 'my-logic-function' }] } );

const logicFunctions = await LogicFunction.listFromCloud();
expect(logicFunctions).to.have.lengthOf(1);
});

it('returns a list of logic functions from an specific org', async () => {
nock('https://api.particle.io/v1/orgs/', )
.intercept('/my-org/logic/functions', 'GET')
.reply(200, { logic_functions : [{ name: 'my-logic-function' }] } );
const logicFunctions = await LogicFunction.listFromCloud({ org: 'my-org' });
expect(logicFunctions).to.have.lengthOf(1);
});

it('propagates errors', async () => {
nock('https://api.particle.io/v1/orgs/', )
.intercept('/my-org/logic/functions', 'GET')
.reply(500, { error: 'Internal Server Error' } );

try {
await LogicFunction.listFromCloud({ org: 'my-org' });
} catch (error) {
expect(error.message).to.equal('HTTP error 500 from https://api.particle.io/v1/orgs/my-org/logic/functions');
}
});
});

});