Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import app from "../../tremendous.app.mjs";
import { FUNDING_SOURCE_OPTIONS } from "../../common/constants.mjs";

export default {
name: "Create Order Email Reward",
version: "0.0.1",
key: "tremendous-create-order-email-reward",
description: "Create an order to send out a reward. [See the documentation](https://developers.tremendous.com/reference/create-order)",
type: "action",
props: {
app,
campaignId: {
propDefinition: [
app,
"campaignId",
],
optional: true,
},
products: {
propDefinition: [
app,
"products",
],
optional: true,
},
Comment on lines +12 to +25
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add runtime validation for campaign/products requirement.

While the info box alerts users that either Products or Campaign ID must be specified, there's no runtime validation enforcing this requirement. This could lead to invalid API calls.

Add validation in the run method:

 async run({ $ }) {
+  if (!this.campaignId && !this.products) {
+    throw new Error("Either Campaign ID or Products must be specified");
+  }
   const response = await this.app.createOrder({

Also applies to: 26-30

infoBox: {
type: "alert",
alertType: "info",
content: "Either `Products` or `Campaign ID` must be specified. [See the documentation](https://developers.tremendous.com/reference/create-order) for more information.",
},
externalId: {
type: "string",
label: "External ID",
description: "Reference for this order. If set, any subsequent requests with the same `External ID` will not create any further orders, and simply return the initially created order.",
optional: true,
},
fundingSourceId: {
type: "string",
label: "Funding Source",
description: "Tremendous ID of the funding source that will be used to pay for the order.",
options: FUNDING_SOURCE_OPTIONS,
optional: true,
},
},
async run({ $ }) {
const response = await this.app.createOrder({
$,
data: {
external_id: this.externalId,
payment: this.fundingSourceId && {
funding_source_id: this.fundingSourceId,
},
reward: {
campaign_id: this.campaignId,
products: this.products,
},
},
});

$.export("$summary", `Successfully created order (ID: ${response?.order?.id})`);

return response;
},
};
19 changes: 19 additions & 0 deletions components/tremendous/common/constants.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const FUNDING_SOURCE_OPTIONS = [
{
value: "balance",
label:
"Pre-funded balance in your Tremendous account to draw funds from to send rewards to recipients.",
},
{
value: "bank_account",
label: "Bank account to draw funds from to send rewards to recipients.",
},
{
value: "credit_card",
label: "Credit card to draw funds from to send rewards to recipients.",
},
{
value: "invoice",
label: "Send rewards to recipients and pay by invoice.",
},
];
18 changes: 18 additions & 0 deletions components/tremendous/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@pipedream/tremendous",
"version": "0.0.1",
"description": "Pipedream Tremendous Components",
"main": "tremendous.app.mjs",
"keywords": [
"pipedream",
"tremendous"
],
"homepage": "https://pipedream.com/apps/tremendous",
"author": "Pipedream <[email protected]> (https://pipedream.com/)",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@pipedream/platform": "^3.0.3"
}
}
66 changes: 62 additions & 4 deletions components/tremendous/tremendous.app.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,69 @@
import { axios } from "@pipedream/platform";

export default {
type: "app",
app: "tremendous",
propDefinitions: {},
propDefinitions: {
campaignId: {
type: "string",
label: "Campaign ID",
description: "ID of the campaign in your account, that defines the available products (different gift cards, charity, etc.) that the recipient can choose from.",
async options() {
const { campaigns } = await this.listCampaigns();
return campaigns?.map(({
id, name,
}) => ({
label: name,
value: id,
}));
},
Comment on lines +11 to +19
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor options methods to reduce duplication

The options methods for campaignId, products, and fundingSourceId have similar implementations. Consider refactoring them into a reusable helper function to improve maintainability.

Here's an example of how you might implement a helper method:

async fetchOptions(fetchMethod, dataKey, labelKey, valueKey) {
  const response = await fetchMethod();
  const data = response[dataKey];
  return data?.map((item) => ({
    label: item[labelKey],
    value: item[valueKey],
  }));
}

Update your options methods:

-async options() {
-  const { campaigns } = await this.listCampaigns();
-  return campaigns?.map(({ id, name }) => ({
-    label: name,
-    value: id,
-  }));
-},
+options() {
+  return this.fetchOptions(
+    this.listCampaigns.bind(this),
+    "campaigns",
+    "name",
+    "id"
+  );
+},

Apply similar changes to the products and fundingSourceId options methods.

Also applies to: 25-33, 39-47

},
products: {
type: "string[]",
label: "Products",
description: "IDs of products (different gift cards, charity, etc.) that will be available to the recipient to choose from. If this and `Campaign ID` are specified, this will override the products made available by the campaign. It will not override other campaign attributes, like the message and customization of the look and feel.",
async options() {
const { products } = await this.listProducts();
return products?.map(({
id, name,
}) => ({
label: name,
value: id,
}));
},
},
},
methods: {
// this.$auth contains connected account data
authKeys() {
console.log(Object.keys(this.$auth));
_baseRequest({
$, headers, ...args
}) {
return axios($, {
headers: {
...headers,
Authorization: `Bearer ${this.$auth.api_key}`,
},
baseURL: "https://testflight.tremendous.com/api/v2",
Comment on lines +51 to +59
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider making the baseURL configurable for environment flexibility

The baseURL in the _baseRequest method is hardcoded to the test environment "https://testflight.tremendous.com/api/v2". This may not be suitable for production environments. Consider making the baseURL configurable to switch between test and production environments.

You can modify the code to use a configurable baseURL:

return axios($, {
  headers: {
    ...headers,
    Authorization: `Bearer ${this.$auth.api_key}`,
  },
- baseURL: "https://testflight.tremendous.com/api/v2",
+ baseURL: this.$auth.baseURL || "https://testflight.tremendous.com/api/v2",
  ...args,
});

And update your authentication configuration to include baseURL as needed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_baseRequest({
$, headers, ...args
}) {
return axios($, {
headers: {
...headers,
Authorization: `Bearer ${this.$auth.api_key}`,
},
baseURL: "https://testflight.tremendous.com/api/v2",
_baseRequest({
$, headers, ...args
}) {
return axios($, {
headers: {
...headers,
Authorization: `Bearer ${this.$auth.api_key}`,
},
baseURL: this.$auth.baseURL || "https://testflight.tremendous.com/api/v2",

...args,
});
},
createOrder(args) {
return this._baseRequest({
method: "POST",
url: "/orders",
...args,
});
},
listCampaigns() {
return this._baseRequest({
method: "GET",
url: "/campaigns",
});
},
listProducts() {
return this._baseRequest({
method: "GET",
url: "/products",
});
},
},
};
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading