Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import googleSheets from "../../google_sheets.app.mjs";
import { ConfigurationError } from "@pipedream/platform";

export default {
key: "google_sheets-add-conditional-format-rule",
name: "Add Conditional Format Rule",
description: "Create conditional formatting with color scales or custom formulas. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddConditionalFormatRuleRequest)",
version: "0.0.1",
type: "action",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: false,
},
props: {
googleSheets,
drive: {
propDefinition: [
googleSheets,
"watchedDrive",
],
},
sheetId: {
propDefinition: [
googleSheets,
"sheetID",
(c) => ({
driveId: googleSheets.methods.getDriveId(c.drive),
}),
],
},
worksheetId: {
propDefinition: [
googleSheets,
"worksheetIDs",
(c) => ({
sheetId: c.sheetId,
}),
],
},
range: {
propDefinition: [
googleSheets,
"range",
],
description: "The range of cells to format (e.g., `A1:A10`)",
},
conditionType: {
type: "string",
label: "Validation Type",
description: "The type of data condition",
options: [
"ONE_OF_LIST",
"NUMBER_GREATER",
"NUMBER_LESS",
"DATE_BEFORE",
"DATE_AFTER",
"TEXT_CONTAINS",
"TEXT_IS_EMAIL",
"TEXT_IS_URL",
"BOOLEAN",
],
},
conditionValues: {
type: "string[]",
label: "Condition Values",
description: "Values for condition (e.g., color scales or custom formulas)",
},
formattingType: {
type: "string",
label: "Formatting Type",
description: "Choose between boolean condition or gradient color scale",
options: [
"BOOLEAN_RULE",
"GRADIENT_RULE",
],
default: "BOOLEAN_RULE",
},
rgbColor: {
type: "object",
label: "RGB Color",
description: "The RGB color value (e.g., {\"red\": 1.0, \"green\": 0.5, \"blue\": 0.2})",
optional: true,
},
textFormat: {
type: "object",
label: "Text Format",
description: "The text format options",
optional: true,
},
bold: {
type: "boolean",
label: "Bold",
description: "Whether the text is bold",
optional: true,
},
italic: {
type: "boolean",
label: "Italic",
description: "Whether the text is italic",
optional: true,
},
strikethrough: {
type: "boolean",
label: "Strikethrough",
description: "Whether the text is strikethrough",
optional: true,
},
interpolationPointType: {
type: "string",
label: "Interpolation Point Type",
description: "The interpolation point type",
options: [
"MIN",
"MAX",
"NUMBER",
"PERCENT",
"PERCENTILE",
],
optional: true,
},
index: {
type: "integer",
label: "Index",
description: "The zero-based index of the rule",
},
},
async run({ $ }) {
const {
startCol,
endCol,
startRow,
endRow,
} = this.googleSheets._parseRangeString(`${this.worksheetId}!${this.range}`);

const rule = {
ranges: [
{
sheetId: this.worksheetId,
startRowIndex: startRow,
endRowIndex: endRow,
startColumnIndex: startCol.charCodeAt(0) - 65,
endColumnIndex: endCol.charCodeAt(0) - 64,
},
],
};

const parseRgbColor = (rgbColor = {}) => {
if (typeof rgbColor === "string") {
try {
rgbColor = JSON.parse(rgbColor);
} catch {
throw new ConfigurationError("Could not parse RGB Color. Please provide a valid JSON object.");
}
}
return rgbColor;
};

this.formattingType === "GRADIENT_RULE" ?
rule.gradientRule = {
minpoint: {
colorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
type: this.interpolationPointType,
value: "MIN",
},
midpoint: {
colorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
type: this.interpolationPointType,
value: "MID",
},
maxpoint: {
colorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
type: this.interpolationPointType,
value: "MAX",
},
} :
rule.booleanRule = {
condition: {
type: this.conditionType,
values: this.conditionValues?.map((v) => ({
userEnteredValue: v,
})) || [],
},
format: {
backgroundColorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
textFormat: {
...this.textFormat,
foregroundColorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
bold: this.bold,
italic: this.italic,
strikethrough: this.strikethrough,
},
},
};
Comment on lines +159 to +204
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 | 🔴 Critical

Rebuild gradientRule to match Sheets API contract.
value: "MIN", "MID", "MAX" and reusing one interpolation type for every point violate the InterpolationPoint schema, causing 400 invalidArgument responses. Emit MIN/MAX points without values, and only include a midpoint when the caller supplies a valid type/value pair.

-    this.formattingType === "GRADIENT_RULE" ?
-      rule.gradientRule = {
-        minpoint: {
-          colorStyle: {
-            rgbColor: parseRgbColor(this.rgbColor),
-          },
-          type: this.interpolationPointType,
-          value: "MIN",
-        },
-        midpoint: {
-          colorStyle: {
-            rgbColor: parseRgbColor(this.rgbColor),
-          },
-          type: this.interpolationPointType,
-          value: "MID",
-        },
-        maxpoint: {
-          colorStyle: {
-            rgbColor: parseRgbColor(this.rgbColor),
-          },
-          type: this.interpolationPointType,
-          value: "MAX",
-        },
-      } :
+    this.formattingType === "GRADIENT_RULE" ?
+      rule.gradientRule = {
+        minpoint: {
+          colorStyle: { rgbColor: parseRgbColor(this.rgbColor) },
+          type: "MIN",
+        },
+        ...(this.interpolationPointType && this.conditionValues?.length ? {
+          midpoint: {
+            colorStyle: { rgbColor: parseRgbColor(this.rgbColor) },
+            type: this.interpolationPointType,
+            value: this.conditionValues[0],
+          },
+        } : {}),
+        maxpoint: {
+          colorStyle: { rgbColor: parseRgbColor(this.rgbColor) },
+          type: "MAX",
+        },
+      } :

(developers.google.com)

📝 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
this.formattingType === "GRADIENT_RULE" ?
rule.gradientRule = {
minpoint: {
colorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
type: this.interpolationPointType,
value: "MIN",
},
midpoint: {
colorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
type: this.interpolationPointType,
value: "MID",
},
maxpoint: {
colorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
type: this.interpolationPointType,
value: "MAX",
},
} :
rule.booleanRule = {
condition: {
type: this.conditionType,
values: this.conditionValues?.map((v) => ({
userEnteredValue: v,
})) || [],
},
format: {
backgroundColorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
textFormat: {
...this.textFormat,
foregroundColorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
bold: this.bold,
italic: this.italic,
strikethrough: this.strikethrough,
},
},
};
this.formattingType === "GRADIENT_RULE" ?
rule.gradientRule = {
minpoint: {
colorStyle: { rgbColor: parseRgbColor(this.rgbColor) },
type: "MIN",
},
...(this.interpolationPointType && this.conditionValues?.length ? {
midpoint: {
colorStyle: { rgbColor: parseRgbColor(this.rgbColor) },
type: this.interpolationPointType,
value: this.conditionValues[0],
},
} : {}),
maxpoint: {
colorStyle: { rgbColor: parseRgbColor(this.rgbColor) },
type: "MAX",
},
} :
rule.booleanRule = {
condition: {
type: this.conditionType,
values: this.conditionValues?.map((v) => ({
userEnteredValue: v,
})) || [],
},
format: {
backgroundColorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
textFormat: {
...this.textFormat,
foregroundColorStyle: {
rgbColor: parseRgbColor(this.rgbColor),
},
bold: this.bold,
italic: this.italic,
strikethrough: this.strikethrough,
},
},
};
🤖 Prompt for AI Agents
components/google_sheets/actions/add-conditional-format-rule/add-conditional-format-rule.mjs
lines 159-204: the gradientRule construction violates the Sheets
InterpolationPoint schema by setting value:"MIN"/"MID"/"MAX" and reusing one
interpolation type for every point; rebuild gradientRule to match the API by
emitting minPoint and maxPoint without value fields (include only colorStyle and
type), and include midpoint only when the caller provides both a valid
interpolation type and a midpoint value (use those for midpoint.type and
midpoint.value); ensure property names and casing match the Sheets API
(minPoint/midPoint/maxPoint) and remove any hard-coded "MIN"/"MID"/"MAX" value
strings so the resulting gradientRule conforms and avoids 400 invalidArgument
errors.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you test this?


const request = {
spreadsheetId: this.sheetId,
requestBody: {
requests: [
{
addConditionalFormatRule: {
rule,
index: this.index,
},
},
],
},
};
const response = await this.googleSheets.batchUpdate(request);
$.export("$summary", "Successfully added conditional format rule.");
return response;
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import googleSheets from "../../google_sheets.app.mjs";

export default {
key: "google_sheets-add-protected-range",
name: "Add Protected Range",
description: "Add edit protection to cell range with permissions. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddProtectedRangeRequest)",
version: "0.0.1",
type: "action",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: false,
},
props: {
googleSheets,
drive: {
propDefinition: [
googleSheets,
"watchedDrive",
],
},
sheetId: {
propDefinition: [
googleSheets,
"sheetID",
(c) => ({
driveId: googleSheets.methods.getDriveId(c.drive),
}),
],
},
worksheetId: {
propDefinition: [
googleSheets,
"worksheetIDs",
(c) => ({
sheetId: c.sheetId,
}),
],
},
protectedRangeId: {
type: "integer",
label: "Protected Range ID",
description: "The ID of the protected range (required for update and delete operations). This is a unique identifier assigned by Google Sheets",
optional: true,
},
range: {
propDefinition: [
googleSheets,
"range",
],
description: "The range of cells to protect (e.g., `A1:A10`). Required for add and update operations",
},
description: {
type: "string",
label: "Description",
description: "A description of the protected range",
optional: true,
},
requestingUserCanEdit: {
type: "boolean",
label: "Requesting User Can Edit",
description: "If true, the user making this request can edit the protected range",
optional: true,
default: false,
},
protectors: {
type: "string[]",
label: "Protectors",
description: "Email addresses of users/groups who can edit the protected range (e.g., user@example.com)",
optional: true,
},
},
async run({ $ }) {
const {
startCol,
endCol,
startRow,
endRow,
} = this.googleSheets._parseRangeString(`${this.worksheetId}!${this.range}`);

const request = {
spreadsheetId: this.sheetId,
requestBody: {
requests: [
{
addProtectedRange: {
protectedRange: {
protectedRangeId: this.protectedRangeId,
range: {
sheetId: this.worksheetId,
startRowIndex: startRow,
endRowIndex: endRow,
startColumnIndex: startCol.charCodeAt(0) - 65,
endColumnIndex: endCol.charCodeAt(0) - 64,
},
description: this.description,
requestingUserCanEdit: this.requestingUserCanEdit,
editors: {
users: this.protectors || [],
},
},
},
},
],
},
};
const response = await this.googleSheets.batchUpdate(request);
$.export("$summary", "Successfully added protected range.");
return response;
},
};
Loading