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-5321 feat(cli): zapier invoke command #856

Merged
merged 15 commits into from
Sep 25, 2024

Conversation

eliangcs
Copy link
Member

@eliangcs eliangcs commented Sep 10, 2024

Introducing a new command zapier invoke, the following is mostly copied from the help text with zapier invoke --help.

invoke

Invoke an auth operation, a trigger, or a create/search action locally.

Usage: zapier invoke [ACTIONTYPE] [ACTIONKEY]

This command emulates how Zapier production environment would invoke your integration. It runs code locally, so you can use this command to quickly test your integration without deploying it to Zapier. This is especially useful for debugging and development.

This command loads authData from the .env file in the current directory. Create a .env file with the necessary auth data before running this command. Each line in .env should be in the format authData_FIELD_KEY=VALUE. For example, an OAuth2 integration might have a .env file like this:

authData_access_token=1234567890
authData_other_auth_field=abcdef

To test if the auth data is correct, run either one of these:

zapier invoke auth test   # invokes authentication.test method
zapier invoke auth label  # invokes authentication.test and renders connection label

Then you can test an trigger, a search, or a create action. For example, this is how you invoke a trigger with key new_recipe:

zapier invoke trigger new_recipe

To add input data, use the --inputData flag. The input data can come from the command directly, a file, or stdin. See EXAMPLES below.

The following are current limitations and may be supported in the future:

  • zapier invoke auth start to help you initialize the auth data in .env
  • zapier invoke auth refresh to refresh the auth data in .env
  • Hook triggers, including REST hook subscribe/unsubscribe
  • Line items
  • Output hydration
  • File upload
  • Dynamic dropdown pagination
  • Function-based connection label
  • Buffered create actions

Arguments

  • actionType | The action type you want to invoke.
  • actionKey | The trigger/action key you want to invoke. If ACTIONTYPE is "auth", this can be "test" or "label".

Flags

  • -i, --inputData | The input data to pass to the action. Must be a JSON-encoded object. The data can be passed from the command directly like '{"key": "value"}', read from a file like @file.json, or read from stdin like @-.
  • --isLoadingSample | Set bundle.meta.isLoadingSample to true. When true in production, this run is initiated by the user in the Zap editor trying to pull a sample.
  • --isFillingDynamicDropdown | Set bundle.meta.isFillingDynamicDropdown to true. Only makes sense for a polling trigger. When true in production, this poll is being used to populate a dynamic dropdown.
  • --isPopulatingDedupe | Set bundle.meta.isPopulatingDedupe to true. Only makes sense for a polling trigger. When true in production, the results of this poll will be used initialize the deduplication list rather than trigger a Zap. This happens when a user enables a Zap.
  • --limit | Set bundle.meta.limit. Only makes sense for a trigger. When used in production, this indicates the number of items you should fetch. -1 means no limit. Defaults to -1.
  • -p, --page | Set bundle.meta.page. Only makes sense for a trigger. When used in production, this indicates which page of items you should fetch. First page is 0.
  • -z, --timezone | Set the default timezone for datetime fields. If not set, defaults to America/Chicago, which matches Zapier production behavior. Defaults to America/Chicago.
  • -d, --debug | Show extra debugging output.

Examples

  • zapier invoke
  • zapier invoke auth test
  • zapier invoke trigger new_recipe
  • zapier invoke create add_recipe --inputData '{"title": "Pancakes"}'
  • zapier invoke search find_recipe -i @file.json
  • cat file.json | zapier invoke trigger new_recipe -i @-

@eliangcs eliangcs changed the title PDE-5321 feat(cli): zapier invoke command - WIP PDE-5321 feat(cli): zapier invoke command Sep 23, 2024
@eliangcs eliangcs marked this pull request as ready for review September 23, 2024 12:48
@eliangcs eliangcs requested a review from a team as a code owner September 23, 2024 12:48
Copy link
Contributor

@standielpls standielpls left a comment

Choose a reason for hiding this comment

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

Neat! I tested the (probably) main usage patterns:

  • zapier invoke fakeaction fish and got validation err (Error: Expected triggers to be one of: auth, trigger, search, create)
  • zapier invoke auth test and label with no .env
  • zapier invoke auth test and label with .env
  • zapier invoke triggers fish and got validation error (Error: Expected triggers to be one of: auth, trigger, search, create)
  • zapier invoke trigger fish
  • zapier invoke search recipe and it fetched required input fields
  • zapier invoke search recipe --inputData {"name": "eggs"} and got validation err ( parse error near `}
  • zapier invoke search recipe --inputData '{"name": "eggs"}'
  • zapier invoke create recipe --inputData '{"name": "eggs"}'
  • zapier invoke create recipe --inputData @test_input_data_file.json (with correct data)
  • zapier invoke create recipe --inputData @test_input_data_file.json (with bad data, and got prompt with required input fields)
  • zapier invoke create recipe (with dynamic dropdown from fish trigger)

I tested all the supported input field options (left some comments below)

Comment on lines +433 to +435
bundle: {
inputData,
authData,
Copy link
Contributor

Choose a reason for hiding this comment

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

I noticed in my testing - with --debug passed in
I kind of expected to see what the final API call might look like, even if we omit authData, perhaps inputData?

staging/caroline [ zl invoke create recipe --debug                                                                                            ] 11:00 am
  zapier:invoke argv is [ 'create', 'recipe', '--debug' ] +0ms
  zapier:invoke args are { actionType: 'create', actionKey: 'recipe' } +1ms
  zapier:invoke flags are {
  debug: true,
  isLoadingSample: false,
  isFillingDynamicDropdown: false,
  isPopulatingDedupe: false,
  limit: -1,
  page: 0,
  timezone: 'America/Chicago'
} +0ms
  zapier:invoke ------------ +0ms
  zapier:invoke Action type: create +0ms
  zapier:invoke Action key: recipe +0ms
  zapier:invoke Action label: Create Recipe +0ms
⠋ Invoking creates.recipe.operation.inputFields  zapier:analytics sending {
  command: 'invoke',
  isValidCommand: true,
  numArgs: 2,
  flags: {
    debug: true,
    isLoadingSample: false,
    isFillingDynamicDropdown: false,
    isPopulatingDedupe: false,
    limit: -1,
    page: 0,
    timezone: 'America/Chicago'
  },
  cliVersion: '15.15.0',
  os: 'darwin',
  sendUserId: true
} +0ms
✔ Invoking creates.recipe.operation.inputFields
  zapier:invoke inputFields: [
  { key: 'name', required: true },
  { key: 'directions', required: false },
  { key: 'style', required: false }
] +17ms
? Required input field "name" (string):   zapier:api >> POST undefined +0ms
  zapier:api >> {"command":"invoke","isValidCommand":true,"numArgs":2,"flags":{"debug":true,"isLoadingSample":false,"isFillingDynamicDropdown":false,"isPopulatingDedupe":false,"limit":-1,"page":0,"timezone":"America/Chicago"},"cliVersion":"15.15.0","os":"darwin"} +0ms
  zapier:api << 400 +0ms
  zapier:api << {"errors": ["Unknown CLI command"]} +0ms
  zapier:api ------------ +0ms
  zapier:analytics err: "https://zapier-staging.com/api/platform/cli/analytics" returned "400" saying "Unknown CLI command" +172ms
? Required input field "name" (string):
? Would you like to fill optional input fields? Select "DONE" when you are ready to invoke the action. DONE
✔ Invoking creates.recipe.operation.perform
{
  "name": "",
  "music": "pop",
  "id": "bOEe2~p"
}

Copy link
Member Author

@eliangcs eliangcs Sep 24, 2024

Choose a reason for hiding this comment

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

Good catch! Included the logs in 74da163. I haven't figured out how to make debug() play nicely with ora (the spinner thing), so the output is not pretty, but it's a start.

To test, you'd need to yarn link zapier-platform-core in your integration so it uses the development version of platform-core.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! I see it, it's helpful to debug. Thanks!

Comment on lines +88 to +94
const parseInteger = (s) => {
const n = parseInt(s);
if (!isNaN(n)) {
return n;
}
return Math.floor(parseDecimal(s));
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I tried putting a decimal in, but got a s is not iterable pointing to the for loop in parseDecimal

Should s be a string in this case?

Copy link
Member Author

@eliangcs eliangcs Sep 24, 2024

Choose a reason for hiding this comment

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

I expect inputData should not contain non-string primitives (but could have a nested structure) to emulate the Zap editor. Added a check in 72dcad6.

Comment on lines +96 to +105
const parseBoolean = (s) => {
s = s.toLowerCase();
if (TRUE_STRINGS.has(s)) {
return true;
}
if (FALSE_STRINGS.has(s)) {
return false;
}
return Boolean(s);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I found this a bit unintuitive - even though it looks like it should help.
For example,

$ zl invoke create recipe --inputData '{"name": "eggs", "style": "no" }'
✔ Invoking creates.recipe.operation.perform
{
  "name": "eggs",
  "music": "pop",
  "fish_type": "qui est esse",
  "style": false,
  "id": "RXU237w"
}

$ zl invoke create recipe --inputData '{"name": "eggs", "style": "noo" }'
✔ Invoking creates.recipe.operation.perform
{
  "name": "eggs",
  "music": "pop",
  "fish_type": "qui est esse",
  "style": false,
  "id": "sqAM~d4"
}

$ zl invoke create recipe --inputData '{"name": "eggs", "style": "nooo" }'
✔ Invoking creates.recipe.operation.perform
{
  "name": "eggs",
  "music": "pop",
  "fish_type": "qui est esse",
  "style": true,
  "id": "4dDws4N"
}

Does it make more sense to have strict true/false validation here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I wrote it this way to match this function in production.

timezone: flags.string({
char: 'z',
description:
'Set the default timezone for datetime fields. If not set, defaults to America/Chicago, which matches Zapier production behavior.',
Copy link
Contributor

Choose a reason for hiding this comment

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

I think people can infer from the timezone string American/Chicago but would it be helpful to provide a link for options?

i.e.: https://data.iana.org/time-zones/tzdb-2021a/zone1970.tab

Copy link
Member Author

Choose a reason for hiding this comment

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

Good call! Added the link in 10ff7f7. I use the wikipedia link because IANA.org has releases over time and the latest link can change.

Copy link
Contributor

@standielpls standielpls left a comment

Choose a reason for hiding this comment

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

This looks good, thanks for the changes

@eliangcs eliangcs merged commit 5d94509 into main Sep 25, 2024
14 checks passed
@eliangcs eliangcs deleted the PDE-5321-invoke-trigger-search-create branch September 25, 2024 02:40
@eliangcs eliangcs mentioned this pull request Oct 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants