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

PDE-3732 feat(cli): Implement individual field flags for register command #618

Merged
merged 21 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f9151f1
Add optional required option and validation to prompt in `ZapierBaseC…
gregilo Jan 24, 2023
3a4aed6
Move nock from devDependencies to dependencies in CLI package.json
gregilo Feb 6, 2023
3aad044
Add fixture for register field choices API response
gregilo Feb 6, 2023
af927cd
Mock API call for register field choices when testing
gregilo Feb 6, 2023
eebe168
Add error handling for API call for register field choices
gregilo Feb 6, 2023
5fc33aa
Implement interactive prompts and individual field flags for register…
gregilo Feb 6, 2023
3e8ad70
Update packages/cli/src/oclif/commands/register.js
gregilo Feb 8, 2023
fae6431
Update audience flag description in packages/cli/src/oclif/commands/r…
gregilo Feb 10, 2023
4fa06df
Update homepage URL flag description in packages/cli/src/oclif/comman…
gregilo Feb 10, 2023
cb985fe
Include caught error object in error message in packages/cli/src/ocli…
gregilo Feb 10, 2023
abcadd3
Set flag options for register command dynamically
gregilo Feb 10, 2023
5825c47
Move register command tests out of smoke-tests and into separate unit
gregilo Feb 13, 2023
4610f36
Enforce character limit on description field/flag
gregilo Feb 14, 2023
3b57747
Use constant for max description length for register command
gregilo Feb 14, 2023
c536cb8
Add ?formId=create to API call for zapier register command
gregilo Feb 14, 2023
08e8ca4
Simplify logic for determining if provided flag has a field mapping w…
gregilo Feb 14, 2023
286afb7
Add more examples for zapier register command
gregilo Feb 14, 2023
093aef9
Utilize object.entries in register.js
gregilo Feb 14, 2023
c9ebfe4
Add test to ensure flags work properly for zapier register command
gregilo Feb 14, 2023
8b34c45
Suppress stdout and stderr when running tests
eliangcs Feb 15, 2023
568c066
Replace fs.statSync with fs.existsSync
eliangcs Feb 15, 2023
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
9 changes: 9 additions & 0 deletions docs/cli.html
Original file line number Diff line number Diff line change
Expand Up @@ -893,10 +893,19 @@ <h2 id="register">register</h2>
</blockquote><p><strong>Usage</strong>: <code>zapier register [TITLE]</code></p><p>After running this, you can run <code>zapier push</code> to build and upload your integration for use in the Zapier editor.</p><p>This will change the <code>./.zapierapprc</code> (which identifies this directory as holding code for a specific integration).</p><p><strong>Arguments</strong></p><ul>
<li><code>title</code> | Your integrations&apos;s public title. Asked interactively if not present.</li>
</ul><p><strong>Flags</strong></p><ul>
<li><code>-D, --desc</code> | A sentence describing your app in 140 characters or less, e.g. &quot;Trello is a team collaboration tool to organize tasks and keep projects on track.&quot;</li>
<li><code>-u, --url</code> | The homepage URL of your app, e.g., <a href="https://example.com">https://example.com</a>.</li>
<li><code>-a, --audience</code> | Are you building a public or private integration?</li>
<li><code>-r, --role</code> | What is your relationship with the app you&apos;re integrating with Zapier?</li>
<li><code>-c, --category</code> | How would you categorize your app? Choose the most appropriate option for your app&apos;s core features.</li>
<li><code>-s, --subscribe</code> | Get tips and recommendations about this integration along with our monthly newsletter that details the performance of your integration and the latest Zapier news.</li>
<li><code>-d, --debug</code> | Show extra debugging output.</li>
</ul><p><strong>Examples</strong></p><ul>
<li><code>zapier register</code></li>
<li><code>zapier register &quot;My Cool Integration&quot;</code></li>
<li><code>zapier register &quot;My Cool Integration&quot; --desc &quot;My Cool Integration helps you integrate your apps with the apps that you need.&quot; --no-subscribe</code></li>
<li><code>zapier register &quot;My Cool Integration&quot; --url &quot;https://www.zapier.com&quot; --audience private --role employee --category marketing-automation</code></li>
<li><code>zapier register --subscribe</code></li>
</ul>
</div>
<div class="col-md-7 col-sm-12 col-height is-empty docs-code">
Expand Down
9 changes: 9 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,11 +445,20 @@ This will change the `./.zapierapprc` (which identifies this directory as holdi
* `title` | Your integrations's public title. Asked interactively if not present.

**Flags**
* `-D, --desc` | A sentence describing your app in 140 characters or less, e.g. "Trello is a team collaboration tool to organize tasks and keep projects on track."
* `-u, --url` | The homepage URL of your app, e.g., https://example.com.
* `-a, --audience` | Are you building a public or private integration?
* `-r, --role` | What is your relationship with the app you're integrating with Zapier?
* `-c, --category` | How would you categorize your app? Choose the most appropriate option for your app's core features.
* `-s, --subscribe` | Get tips and recommendations about this integration along with our monthly newsletter that details the performance of your integration and the latest Zapier news.
* `-d, --debug` | Show extra debugging output.

**Examples**
* `zapier register`
* `zapier register "My Cool Integration"`
* `zapier register "My Cool Integration" --desc "My Cool Integration helps you integrate your apps with the apps that you need." --no-subscribe`
* `zapier register "My Cool Integration" --url "https://www.zapier.com" --audience private --role employee --category marketing-automation`
* `zapier register --subscribe`


## scaffold
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/docs/cli.html
Original file line number Diff line number Diff line change
Expand Up @@ -893,10 +893,19 @@ <h2 id="register">register</h2>
</blockquote><p><strong>Usage</strong>: <code>zapier register [TITLE]</code></p><p>After running this, you can run <code>zapier push</code> to build and upload your integration for use in the Zapier editor.</p><p>This will change the <code>./.zapierapprc</code> (which identifies this directory as holding code for a specific integration).</p><p><strong>Arguments</strong></p><ul>
<li><code>title</code> | Your integrations&apos;s public title. Asked interactively if not present.</li>
</ul><p><strong>Flags</strong></p><ul>
<li><code>-D, --desc</code> | A sentence describing your app in 140 characters or less, e.g. &quot;Trello is a team collaboration tool to organize tasks and keep projects on track.&quot;</li>
<li><code>-u, --url</code> | The homepage URL of your app, e.g., <a href="https://example.com">https://example.com</a>.</li>
<li><code>-a, --audience</code> | Are you building a public or private integration?</li>
<li><code>-r, --role</code> | What is your relationship with the app you&apos;re integrating with Zapier?</li>
<li><code>-c, --category</code> | How would you categorize your app? Choose the most appropriate option for your app&apos;s core features.</li>
<li><code>-s, --subscribe</code> | Get tips and recommendations about this integration along with our monthly newsletter that details the performance of your integration and the latest Zapier news.</li>
<li><code>-d, --debug</code> | Show extra debugging output.</li>
</ul><p><strong>Examples</strong></p><ul>
<li><code>zapier register</code></li>
<li><code>zapier register &quot;My Cool Integration&quot;</code></li>
<li><code>zapier register &quot;My Cool Integration&quot; --desc &quot;My Cool Integration helps you integrate your apps with the apps that you need.&quot; --no-subscribe</code></li>
<li><code>zapier register &quot;My Cool Integration&quot; --url &quot;https://www.zapier.com&quot; --audience private --role employee --category marketing-automation</code></li>
<li><code>zapier register --subscribe</code></li>
</ul>
</div>
<div class="col-md-7 col-sm-12 col-height is-empty docs-code">
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,11 +445,20 @@ This will change the `./.zapierapprc` (which identifies this directory as holdi
* `title` | Your integrations's public title. Asked interactively if not present.

**Flags**
* `-D, --desc` | A sentence describing your app in 140 characters or less, e.g. "Trello is a team collaboration tool to organize tasks and keep projects on track."
* `-u, --url` | The homepage URL of your app, e.g., https://example.com.
* `-a, --audience` | Are you building a public or private integration?
* `-r, --role` | What is your relationship with the app you're integrating with Zapier?
* `-c, --category` | How would you categorize your app? Choose the most appropriate option for your app's core features.
* `-s, --subscribe` | Get tips and recommendations about this integration along with our monthly newsletter that details the performance of your integration and the latest Zapier news.
* `-d, --debug` | Show extra debugging output.

**Examples**
* `zapier register`
* `zapier register "My Cool Integration"`
* `zapier register "My Cool Integration" --desc "My Cool Integration helps you integrate your apps with the apps that you need." --no-subscribe`
* `zapier register "My Cool Integration" --url "https://www.zapier.com" --audience private --role employee --category marketing-automation`
* `zapier register --subscribe`


## scaffold
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
},
"devDependencies": {
"@oclif/dev-cli": "^1.26.10",
"@oclif/test": "^1.2.9",
"chai": "^4.3.7",
"decompress": "4.2.1",
"litdoc": "1.5.6",
"markdown-toc": "^1",
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const IS_TESTING =
argvStr.includes('jest') ||
(process.env.NODE_ENV || '').toLowerCase().startsWith('test');

const MAX_DESCRIPTION_LENGTH = 140;

module.exports = {
ANALYTICS_KEY,
ANALYTICS_MODES,
Expand All @@ -76,6 +78,7 @@ module.exports = {
ISSUES_URL,
LAMBDA_VERSION,
LEGACY_RUNNER_PACKAGE,
MAX_DESCRIPTION_LENGTH,
NODE_VERSION,
NODE_VERSION_CLI_REQUIRES,
PACKAGE_NAME,
Expand Down
42 changes: 42 additions & 0 deletions packages/cli/src/oclif/ZapierBaseCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,54 @@ class ZapierBaseCommand extends Command {
this.log(renderList(items, { spacer: '\n', maxWidth: stdtermwidth }));
}

/**
*
* @param {Object} opts options object (as expected for this.prompt())
* @returns {string|boolean} Boolean if validation passes, string w/ error message if it doesn't
*/
_getCustomValidatation(opts) {
return (input) => {
const validators = {
required: (input) =>
input.trim() === '' ? 'This field is required.' : true,
charLimit: (input, charLimit) =>
input.length > charLimit
? `Please provide a value ${charLimit} characters or less.`
: true,
};
let aggregateResult = true;

for (const key in opts) {
if (typeof validators[key] === 'undefined') {
continue;
}

let individualResult;
if (validators[key].length > 1) {
individualResult = validators[key](input, opts[key]);
} else {
individualResult = validators[key](input);
}

if (individualResult !== true) {
aggregateResult = individualResult;
break;
}
}

return aggregateResult;
};
}

/**
* get user input
* @param {string} question the question to ask the user
* @param {object} opts `inquierer.js` opts ([read more](https://github.com/SBoudrias/Inquirer.js/#question))
*/
async prompt(question, opts = {}) {
if (Object.keys(opts).length) {
opts.validate = this._getCustomValidatation(opts);
}
const { ans } = await inquirer.prompt({
type: 'string',
...opts,
Expand Down
164 changes: 157 additions & 7 deletions packages/cli/src/oclif/commands/register.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
const ZapierBaseCommand = require('../ZapierBaseCommand');
const { CURRENT_APP_FILE } = require('../../constants');
const { CURRENT_APP_FILE, MAX_DESCRIPTION_LENGTH } = require('../../constants');
const { buildFlags } = require('../buildFlags');
const { callAPI, writeLinkedAppConfig } = require('../../utils/api');

const { flags } = require('@oclif/command');

class RegisterCommand extends ZapierBaseCommand {
async perform() {
let title = this.args.title;
if (!title) {
title = await this.prompt('What is the title of your integration?');
// Flag validation
this._validateEnumFlags();
if (
'desc' in this.flags &&
this.flags.desc.length >= MAX_DESCRIPTION_LENGTH
) {
throw new Error(
`Please provide a description that is ${MAX_DESCRIPTION_LENGTH} characters or less.`
);
}

this.startSpinner(`Registering your new integration "${title}"`);
const app = await callAPI('/apps', { method: 'POST', body: { title } });
const appMeta = await this._promptForAppMeta();

this.startSpinner(`Registering your new integration "${appMeta.title}"`);
const app = await callAPI('/apps?formId=create', {
method: 'POST',
body: appMeta,
});
this.stopSpinner();
this.startSpinner(
`Linking app to current directory with \`${CURRENT_APP_FILE}\``
Expand All @@ -22,6 +35,108 @@ class RegisterCommand extends ZapierBaseCommand {
'\nFinished! Now that your integration is registered with Zapier, you can `zapier push`!'
);
}

_validateEnumFlags() {
const flagFieldMappings = {
audience: 'intention',
role: 'role',
category: 'app_category',
};

for (const [flag, flagValue] of Object.entries(this.flags)) {
// Only validate user input for enum flags (in flagFieldMappings)
if (!flagFieldMappings[flag]) {
continue;
}

// Check user input against this.config.enumFieldChoices (retrieved in getAppRegistrationFieldChoices hook)
const enumFieldChoices = this.config.enumFieldChoices[
flagFieldMappings[flag]
];
if (!enumFieldChoices.find((option) => option.value === flagValue)) {
throw new Error(
`${flagValue} is not a valid value for ${flag}. Must be one of the following: ${enumFieldChoices
.map((option) => option.value)
.join(', ')}`
);
}
}
}

async _promptForAppMeta() {
const appMeta = {};

appMeta.title = this.args.title?.trim();
if (!appMeta.title) {
appMeta.title = await this.prompt(
'What is the title of your integration?',
{ required: true }
);
}

appMeta.description = this.flags.desc?.trim();
if (!appMeta.description) {
appMeta.description = await this.prompt(
`Please provide a sentence describing your app in ${MAX_DESCRIPTION_LENGTH} characters or less.`,
{ required: true, charLimit: MAX_DESCRIPTION_LENGTH }
);
}

appMeta.homepage_url = this.flags.url;
if (!appMeta.homepage_url) {
appMeta.homepage_url = await this.prompt(
'What is the homepage URL of your app? (optional)'
);
}

appMeta.intention = this.flags.audience;
if (!appMeta.intention) {
appMeta.intention = await this.promptWithList(
'Are you building a public or private integration?',
this.config.enumFieldChoices.intention
);
}

appMeta.role = this.flags.role;
if (!appMeta.role) {
appMeta.role = await this.promptWithList(
"What is your relationship with the app you're integrating with Zapier?",
this._getRoleChoicesWithAppTitle(
appMeta.title,
this.config.enumFieldChoices.role
)
);
}

appMeta.app_category = this.flags.category;
if (!appMeta.app_category) {
appMeta.app_category = await this.promptWithList(
'How would you categorize your app?',
this.config.enumFieldChoices.app_category
);
}

appMeta.subscription = this.flags.subscribe;
// boolean field, so using `typeof` === `undefined`
if (typeof appMeta.subscription === 'undefined') {
appMeta.subscription = await this.promptWithList(
'Subscribe to Updates about your Integration',
[
{ name: 'Yes', value: true },
{ name: 'No', value: false },
]
);
}

return appMeta;
}

_getRoleChoicesWithAppTitle(title, choices) {
return choices.map((choice) => ({
value: choice.value,
name: choice.name.replace('[app_title]', title),
}));
}
}

RegisterCommand.skipValidInstallCheck = true;
Expand All @@ -32,10 +147,45 @@ RegisterCommand.args = [
"Your integrations's public title. Asked interactively if not present.",
},
];
RegisterCommand.flags = buildFlags();

RegisterCommand.flags = buildFlags({
commandFlags: {
desc: flags.string({
char: 'D',
description: `A sentence describing your app in ${MAX_DESCRIPTION_LENGTH} characters or less, e.g. "Trello is a team collaboration tool to organize tasks and keep projects on track."`,
}),
url: flags.string({
char: 'u',
description: 'The homepage URL of your app, e.g., https://example.com.',
}),
audience: flags.string({
char: 'a',
gregilo marked this conversation as resolved.
Show resolved Hide resolved
description: 'Are you building a public or private integration?',
}),
role: flags.string({
char: 'r',
description:
"What is your relationship with the app you're integrating with Zapier?",
}),
category: flags.string({
char: 'c',
description:
"How would you categorize your app? Choose the most appropriate option for your app's core features.",
}),
subscribe: flags.boolean({
char: 's',
description:
'Get tips and recommendations about this integration along with our monthly newsletter that details the performance of your integration and the latest Zapier news.',
allowNo: true,
}),
},
});
RegisterCommand.examples = [
'zapier register',
'zapier register "My Cool Integration"',
Copy link
Member

Choose a reason for hiding this comment

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

Can we add some more examples here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@eliangcs I added more here - 286afb7. Do you think these are thorough enough?

'zapier register "My Cool Integration" --desc "My Cool Integration helps you integrate your apps with the apps that you need." --no-subscribe',
'zapier register "My Cool Integration" --url "https://www.zapier.com" --audience private --role employee --category marketing-automation',
'zapier register --subscribe',
];
RegisterCommand.description = `Register a new integration in your account.

Expand Down
30 changes: 29 additions & 1 deletion packages/cli/src/oclif/hooks/getAppRegistrationFieldChoices.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,39 @@ module.exports = async function (options) {
}

const enumFieldChoices = {};
const formFields = await callAPI('/apps/fields-choices');
let formFields;

try {
formFields = await callAPI('/apps/fields-choices');
} catch (e) {
this.error(
`Unable to connect to Zapier API. Please check your connection and try again. ${e}`
);
}

for (const fieldName of ['intention', 'role', 'app_category']) {
enumFieldChoices[fieldName] = formFields[fieldName];
}

this.config.enumFieldChoices = enumFieldChoices;

// This enables us to see all available options when running `zapier register --help`
const cmd = options.config.findCommand('register');
if (cmd && cmd.flags) {
if (cmd.flags.audience) {
cmd.flags.audience.options = formFields.intention.map(
(audienceOption) => audienceOption.value
);
}
if (cmd.flags.role) {
cmd.flags.role.options = formFields.role.map(
(roleOption) => roleOption.value
);
}
if (cmd.flags.category) {
cmd.flags.category.options = formFields.app_category.map(
(categoryOption) => categoryOption.value
);
}
}
};
Loading