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
14 changes: 14 additions & 0 deletions components/pipedream_utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Overview

The Pipedream Utils app is a set of pre-built functions that streamline common tasks in your workflows. It acts like a Swiss Army knife for developers, providing essential tools such as format conversion, date manipulation, and text processing. By leveraging these functions, you can reduce the boilerplate code needed for routine operations, speeding up the development of intricate automations. The Helper Functions API can be a game changer when it comes to tasks like parsing dates in user-friendly formats, encoding and decoding data, or generating UUIDs, making them more efficient and less error-prone.

# Example Use Cases

- **Format User Input for Database Storage**
In an app where users submit data through forms, the Helper Functions API can be used to sanitize and format user input before it is stored in a database, such as Airtable. This ensures that data is clean and uniform, simplifying retrieval and analysis.

- **Process Webhook Payloads**
When dealing with incoming webhooks from apps like GitHub, the Helper Functions API can parse and transform JSON payloads. This allows you to extract specific data points and reformat them for use in other apps like Slack for team notifications or JIRA for creating issues.

- **Automate Content Publication Workflow**
A content calendar on Google Sheets can trigger a workflow that uses Helper Functions to parse dates and format post titles. The workflow could then use this data to automatically schedule and publish content on platforms like WordPress or social media apps.
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { ConfigurationError } from "@pipedream/platform";
import pipedream_utils from "../../pipedream_utils.app.mjs";
import commonDateTime from "../../common/date-time/commonDateTime.mjs";
import {
DATE_FORMAT_PARSE_MAP, DEFAULT_FORMAT_VALUE,
} from "../../common/date-time/dateFormats.mjs";
import { DATE_TIME_UNITS } from "../../common/date-time/dateTimeUnits.mjs";
import sugar from "sugar";
const OPERATION_OPTIONS = {
ADD: "Add",
SUBTRACT: "Subtract",
};
export default {
...commonDateTime,
name: "Formatting - [Date/Time] Add/Subtract Time",
description: "Add or subtract time from a given input",
key: "pipedream_utils-add-subtract-time",
version: "0.0.5",
type: "action",
props: {
...commonDateTime.props,
operation: {
label: "Operation",
description: "Whether to add or subtract time.",
type: "string",
options: Object.values(OPERATION_OPTIONS),
},
duration: {
label: "Duration",
description: "The duration for the operation. You can use the shorthand duration, for example: `1s`, `1m`, `1h`, `1d`, `1w`, `1y` equal one second, minute, hour, day, week, and year respectively",
type: "string",
},
outputFormat: {
propDefinition: [
pipedream_utils,
"outputFormat",
],
},
},
methods: {
...commonDateTime.methods,
getOperationMilliseconds(str) {
let result = 0;
const {
second, minute, hour, day, week, year,
} = DATE_TIME_UNITS;
Object.entries({
s: second,
m: minute,
h: hour,
d: day,
w: week,
y: year,
}).forEach(([
identifier,
multiplier,
]) => {
const substr = str.match(new RegExp(`[0-9]+\\s*${identifier}`))?.[0];
if (substr) {
const value = Number(substr.match(/[0-9]+/));
result += value * multiplier;
}
});
return result;
},
},
async run({ $ }) {
const {
operation, duration, outputFormat,
} = this;
const dateObj = this.getDateFromInput();
const value = dateObj.valueOf();
let amount = this.getOperationMilliseconds(duration);
if (operation === OPERATION_OPTIONS.SUBTRACT)
amount *= -1;
const result = value + amount;
const format = outputFormat ?? this.inputFormat ?? DEFAULT_FORMAT_VALUE;
try {
const { outputFn } = DATE_FORMAT_PARSE_MAP.get(format);
const output = outputFn(sugar.Date.create(result));
$.export("$summary", `Successfully ${operation === OPERATION_OPTIONS.SUBTRACT
? "subtracted"
: "added"} time`);
return output;
}
catch (err) {
console.log("Error parsing date", err);
throw new ConfigurationError("**Parse error** - check your input and if the selected format is correct.");
}
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// legacy_hash_id: a_0Mio28
import pipedream_utils from "../../pipedream_utils.app.mjs";

export default {
key: "pipedream_utils-base64-decode-string",
name: "Helper Functions - Base64 Decode String",
description: "Accepts a base64-encoded string, returns a decoded UTF-8 string",
version: "0.0.1",
type: "action",
props: {
pipedream_utils,
data: {

Check warning on line 12 in components/pipedream_utils/actions/base64-decode-string/base64-decode-string.mjs

View workflow job for this annotation

GitHub Actions / Lint Code Base

Component prop data must have a label. See https://pipedream.com/docs/components/guidelines/#props

Check warning on line 12 in components/pipedream_utils/actions/base64-decode-string/base64-decode-string.mjs

View workflow job for this annotation

GitHub Actions / Lint Code Base

Component prop data must have a description. See https://pipedream.com/docs/components/guidelines/#props
type: "string",
},
Comment on lines +12 to +14
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 label and description to data prop

The data prop is missing a label and description as required by component guidelines.

data: {
  type: "string",
+  label: "Base64 String",
+  description: "The base64-encoded string that you want to decode to UTF-8",
},
📝 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
data: {
type: "string",
},
data: {
type: "string",
label: "Base64 String",
description: "The base64-encoded string that you want to decode to UTF-8",
},
🧰 Tools
🪛 GitHub Check: Lint Code Base

[warning] 12-12:
Component prop data must have a label. See https://pipedream.com/docs/components/guidelines/#props


[warning] 12-12:
Component prop data must have a description. See https://pipedream.com/docs/components/guidelines/#props

},
async run({ $ }) {
const buffer = Buffer.from(this.data, "base64");
$.export("data", buffer.toString("utf8"));
},
Comment on lines +16 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

Add input validation and return decoded data

The current implementation doesn't validate the input base64 string and doesn't return the decoded data - it only exports it using $.export(). Add validation and return the decoded data for consistency.

async run({ $ }) {
+  // Validate base64 input
+  if (!/^[A-Za-z0-9+/=]+$/.test(this.data)) {
+    throw new Error("Invalid base64 string. Base64 strings can only contain A-Z, a-z, 0-9, +, /, and =");
+  }
+
  const buffer = Buffer.from(this.data, "base64");
+  const decodedString = buffer.toString("utf8");
  $.export("data", buffer.toString("utf8"));
+  
+  // Return the decoded string for consistency
+  return { data: decodedString };
},
📝 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
async run({ $ }) {
const buffer = Buffer.from(this.data, "base64");
$.export("data", buffer.toString("utf8"));
},
async run({ $ }) {
// Validate base64 input
if (!/^[A-Za-z0-9+/=]+$/.test(this.data)) {
throw new Error("Invalid base64 string. Base64 strings can only contain A-Z, a-z, 0-9, +, /, and =");
}
const buffer = Buffer.from(this.data, "base64");
const decodedString = buffer.toString("utf8");
$.export("data", buffer.toString("utf8"));
// Return the decoded string for consistency
return { data: decodedString };
},

};
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import pipedream_utils from "../../pipedream_utils.app.mjs";

export default {
key: "pipedream_utils-compare-arrays",
name: "Helper Functions - Compare Arrays",
description: "Get the difference, intersection, union, or symetric difference of two arrays/sets.",
version: "0.0.1",
type: "action",
props: {
pipedream_utils,
array1: {
type: "string[]",
label: "Array 1",
description: "Array to compare to second array",
default: [],
},
array2: {
type: "string[]",
label: "Array 2",
description: "Array to be compared with first array",
default: [],
},
actionType: {
type: "string",
label: "Compare Action",
description: "Type of action to perform on the arrays",
options: [
"difference",
"union",
"intersection",
"symmetric difference",
],
},
},
methods: {
getDifference(set1, set2) {
return new Set([
...set1,
].filter((x) => !set2.has(x)));
},
getIntersection(set1, set2) {
return new Set([
...set1,
].filter((x) => set2.has(x)));
},
getUnion(set1, set2) {
for (const elem of set2) {
set1.add(elem);
}
return set1;
},
Comment on lines +46 to +51
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

Avoid modifying the original Set

getUnion modifies set1 directly. For consistency with immutable operations and to avoid unexpected side effects, consider creating a new Set instead.

  getUnion(set1, set2) {
-   for (const elem of set2) {
-     set1.add(elem);
-   }
-   return set1;
+   const result = new Set(set1);
+   for (const elem of set2) {
+     result.add(elem);
+   }
+   return result;
  },
📝 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
getUnion(set1, set2) {
for (const elem of set2) {
set1.add(elem);
}
return set1;
},
getUnion(set1, set2) {
const result = new Set(set1);
for (const elem of set2) {
result.add(elem);
}
return result;
},

getSymmetricDifference(set1, set2) {
for (const elem of set2) {
if (set1.has(elem)) {
set1.delete(elem);
} else {
set1.add(elem);
}
}
return set1;
},
Comment on lines +52 to +61
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

Avoid modifying the original Set

Similarly, getSymmetricDifference modifies set1 directly. Consider creating a new Set instead to maintain consistency and avoid unexpected side effects.

  getSymmetricDifference(set1, set2) {
-   for (const elem of set2) {
-     if (set1.has(elem)) {
-       set1.delete(elem);
-     } else {
-       set1.add(elem);
-     }
-   }
-   return set1;
+   const result = new Set(set1);
+   for (const elem of set2) {
+     if (result.has(elem)) {
+       result.delete(elem);
+     } else {
+       result.add(elem);
+     }
+   }
+   return result;
  },
📝 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
getSymmetricDifference(set1, set2) {
for (const elem of set2) {
if (set1.has(elem)) {
set1.delete(elem);
} else {
set1.add(elem);
}
}
return set1;
},
getSymmetricDifference(set1, set2) {
const result = new Set(set1);
for (const elem of set2) {
if (result.has(elem)) {
result.delete(elem);
} else {
result.add(elem);
}
}
return result;
},

},
run() {
const set1 = new Set(this.array1);
const set2 = new Set(this.array2);

let results;

switch (this.actionType) {
case "difference": {
results = this.getDifference(set1, set2);
break;
}
case "union": {
results = this.getUnion(set1, set2);
break;
}
case "intersection": {
results = this.getIntersection(set1, set2);
break;
}
case "symmetric difference": {
results = this.getSymmetricDifference(set1, set2);
break;
}
default:
throw new Error(`Unknown action type: ${this.actionType}`);
}

return Array.from(results);
},
};
63 changes: 63 additions & 0 deletions components/pipedream_utils/actions/compare-dates/compare-dates.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import commonDateTime from "../../common/date-time/commonDateTime.mjs";
import pipedream_utils from "../../pipedream_utils.app.mjs";
import { DATE_TIME_UNITS } from "../../common/date-time/dateTimeUnits.mjs";
export default {
...commonDateTime,
name: "Formatting - [Date/Time] Compare Dates",
description: "Get the duration between two dates in days, hours, minutes, and seconds along with checking if they are the same.",
key: "pipedream_utils-compare-dates",
version: "0.0.5",
type: "action",
props: {
...commonDateTime.props,
inputDate: {
propDefinition: [
pipedream_utils,
"inputDate",
],
label: "Start Date",
description: "Enter start date string, in the format defined in `Input Format`. If the start date is after the end date, these dates will be swapped and in the output `datesSwapped` will be set to `true`.",
},
endDate: {
propDefinition: [
pipedream_utils,
"inputDate",
],
label: "End Date",
description: "Enter end date string, in the format defined in `Input Format`. Timezone is assumed the same for both dates if not explicitly set.",
},
},
async run({ $ }) {
const startDateObj = this.getDateFromInput(this.inputDate);
const endDateObj = this.getDateFromInput(this.endDate);
const startValue = startDateObj.valueOf();
const endValue = endDateObj.valueOf();
const datesSwapped = startValue > endValue;
let result = "equal";
let remainingValue = Math.abs(endValue - startValue);
if (remainingValue) {
const arrResults = [];
const arrUnits = Object.entries(DATE_TIME_UNITS).sort((a, b) => b[1] - a[1]);
for (const [
word,
unit,
] of arrUnits) {
const amount = Math.floor(remainingValue / unit);
if (amount) {
arrResults.push(`${amount} ${amount === 1
? word
: `${word}s`}`);
remainingValue %= unit;
if (!remainingValue)
break;
}
}
result = arrResults.join(", ");
}
$.export("$summary", "Successfully compared dates");
return {
datesSwapped,
result,
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { axios } from "@pipedream/platform";
import pipedream_utils from "../../pipedream_utils.app.mjs";

export default {
key: "pipedream_utils-convert-currency",
name: "Helper Functions - Convert Currency",
description: "Convert an amount between currencies. [See the documentation](https://www.frankfurter.app/docs/)",
version: "0.0.1",
type: "action",
props: {
pipedream_utils,
fromCurrency: {
type: "string",
label: "From Currency",
description: "The currency to convert from",
async options() {
return this.getCurrencyOptions();
},
},
toCurrency: {
type: "string",
label: "To Currency",
description: "The currency to convert to",
async options() {
return this.getCurrencyOptions();
},
},
value: {
type: "string",
label: "Value",
description: "The value to convert",
},
Comment on lines +28 to +32
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

Validate currency value is a number.

The value property is defined as a string type, but should be a valid number for currency conversion. Consider adding validation or changing the type:

value: {
-  type: "string",
+  type: "integer",
  label: "Value",
  description: "The value to convert",
+  validate: (value) => {
+    const num = Number(value);
+    if (isNaN(num)) {
+      return "Value must be a valid number";
+    }
+    return true;
+  },
},
📝 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
value: {
type: "string",
label: "Value",
description: "The value to convert",
},
value: {
type: "integer",
label: "Value",
description: "The value to convert",
validate: (value) => {
const num = Number(value);
if (isNaN(num)) {
return "Value must be a valid number";
}
return true;
},
},

},
methods: {
async getCurrencyOptions() {
const currencies = await this.getCurrencies();
const options = [];
for (const [
key,
value,
] of Object.entries(currencies)) {
options.push({
value: key,
label: value,
});
}
return options;
},
getCurrencies($ = this) {
return axios($, {
url: "https://api.frankfurter.app/currencies",
});
},
Comment on lines +49 to +53
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

Add error handling for API requests.

The current implementation of getCurrencies doesn't handle potential API failures. Consider adding error handling:

getCurrencies($ = this) {
-  return axios($, {
-    url: "https://api.frankfurter.app/currencies",
-  });
+  return axios($, {
+    url: "https://api.frankfurter.app/currencies",
+  })
+    .catch(error => {
+      console.error("Error fetching currencies:", error);
+      throw new Error(`Failed to fetch currency list: ${error.message}`);
+    });
}

Similar error handling should be added to convertCurrency as well.

📝 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
getCurrencies($ = this) {
return axios($, {
url: "https://api.frankfurter.app/currencies",
});
},
getCurrencies($ = this) {
return axios($, {
url: "https://api.frankfurter.app/currencies",
})
.catch(error => {
console.error("Error fetching currencies:", error);
throw new Error(`Failed to fetch currency list: ${error.message}`);
});
},

convertCurrency($ = this) {
return axios($, {
url: "https://api.frankfurter.app/latest",
params: {
from: this.fromCurrency,
to: this.toCurrency,
amount: this.value,
},
});
},
},
async run({ $ }) {
const response = await this.convertCurrency($);
$.export("$summary", `${this.value} ${this.fromCurrency} = ${response.rates[this.toCurrency]} ${this.toCurrency}`);
return response;
Comment on lines +66 to +68
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

Add validation for API response.

The current implementation assumes the API response will contain the expected data structure. Consider adding validation to ensure the response contains the required properties:

async run({ $ }) {
  const response = await this.convertCurrency($);
+  
+  // Validate response contains expected data
+  if (!response || !response.rates || !response.rates[this.toCurrency]) {
+    throw new Error(`Invalid API response: Missing conversion rate for ${this.toCurrency}`);
+  }
  
  $.export("$summary", `${this.value} ${this.fromCurrency} = ${response.rates[this.toCurrency]} ${this.toCurrency}`);
  return response;
}
📝 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
const response = await this.convertCurrency($);
$.export("$summary", `${this.value} ${this.fromCurrency} = ${response.rates[this.toCurrency]} ${this.toCurrency}`);
return response;
async run({ $ }) {
const response = await this.convertCurrency($);
// Validate response contains expected data
if (!response || !response.rates || !response.rates[this.toCurrency]) {
throw new Error(`Invalid API response: Missing conversion rate for ${this.toCurrency}`);
}
$.export("$summary", `${this.value} ${this.fromCurrency} = ${response.rates[this.toCurrency]} ${this.toCurrency}`);
return response;
}

},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { parseHTML } from "linkedom";
import showdown from "showdown";
import pipedream_utils from "../../pipedream_utils.app.mjs";
export default {
name: "Formatting - [Text] Convert HTML to Markdown",
description: "Convert valid HTML to Markdown text",
key: "pipedream_utils-convert-html-to-markdown",
version: "0.0.6",
type: "action",
props: {
pipedream_utils,
input: {
label: "Input",
description: "HTML string to be converted to Markdown",
type: "string",
},
},
async run({ $ }) {
const { input } = this;
const converter = new showdown.Converter();
const dom = parseHTML("");
const result = converter.makeMarkdown(input, dom.window.document);
Comment on lines +21 to +22
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

Fix empty DOM initialization

The parseHTML function is called with an empty string, but then it's not used for parsing the actual HTML input. This appears to be an issue with how the showdown converter is being used.

-    const dom = parseHTML("");
-    const result = converter.makeMarkdown(input, dom.window.document);
+    const dom = parseHTML(input);
+    const result = converter.makeMarkdown(input, dom.window.document);

Alternatively, if the intention is to use showdown's makeMarkdown with the input directly:

-    const dom = parseHTML("");
-    const result = converter.makeMarkdown(input, dom.window.document);
+    const result = converter.makeMarkdown(input);
📝 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
const dom = parseHTML("");
const result = converter.makeMarkdown(input, dom.window.document);
const dom = parseHTML(input);
const result = converter.makeMarkdown(input, dom.window.document);

$.export("$summary", "Successfully converted to Markdown");
return result;
},
};
Loading
Loading