How to merge cells with Google Sheets API? - google-sheets-api

I am using the Python client for the Google Sheets API to build a spreadsheet. I am able to create a new sheet and update values in it, but I have been unable to merge cells for the header row that I want.
top_header_format = [
{'mergeCells': {
'mergeType': 'MERGE_COLUMNS',
'range': {
'endColumnIndex': 3,
'endRowIndex': 1,
'sheetId': '112501875',
'startColumnIndex': 0,
'startRowIndex': 0
}
}},
{'mergeCells': {
'mergeType': 'MERGE_COLUMNS',
'range': {
'endColumnIndex': 5,
'endRowIndex': 1,
'sheetId': '112501875',
'startColumnIndex': 3,
'startRowIndex': 0
}
}}
]
service.spreadsheets().batchUpdate(
spreadsheetId=spreadsheet_id,
body={'requests': top_header_format}
).execute()
This is the code I am running. There are no errors. The response's replies are empty and the spreadsheet doesn't have merged cells. The documentation is here.

Start indexes are inclusive and end indexes are exclusive, so by asking to merge row [0,1) you're saying "i want to merge row 0 into row 0", which is a no-op. You probably want [0,2), e.g:
top_header_format = [
{'mergeCells': {
'mergeType': 'MERGE_COLUMNS',
'range': {
'endColumnIndex': 4,
'endRowIndex': 2,
'sheetId': '112501875',
'startColumnIndex': 0,
'startRowIndex': 0
}
}},
{'mergeCells': {
'mergeType': 'MERGE_COLUMNS',
'range': {
'endColumnIndex': 6,
'endRowIndex': 2,
'sheetId': '112501875',
'startColumnIndex': 3,
'startRowIndex': 0
}
}}
]

If someone looking for doing merging with PHP, you can use this code below:
For merging cells first you will have to download the PHP library from composer as in their documentation ( https://developers.google.com/sheets/api/quickstart/php )
once installed follow their guide to set up your client to get authenticated.
//$client will be your Google_Client authentication, for more info check the documentation link above.
Use below code for doing merging of rows and columns
$service = new Google_Service_Sheets($client);
$spreadsheetId = '1jfUz2VMUUEt2s4BP2Ye'; //this is test spreadsheet it, use your spreadsheet id here
$rangeinst = new Google_Service_Sheets_GridRange();
$rangeinst->setSheetId(0); //your sheet id
$rangeinst->setStartRowIndex(1); // row index from where to start
$rangeinst->setEndRowIndex(11); //ending row upto which you want merging
$rangeinst->setStartColumnIndex(0); //start of column index, first column has 0 index like row
$rangeinst->setEndColumnIndex(4); //end of column upto which you want merging
$merge = new Google_Service_Sheets_MergeCellsRequest();
$merge->setMergeType('MERGE_COLUMNS'); //merge type request
$merge->setRange($rangeinst);
$requestBody = new Google_Service_Sheets_Request();
$requestBody->setMergeCells($merge);
$requestBatchBody = new Google_Service_Sheets_BatchUpdateSpreadsheetRequest();
$requestBatchBody->setRequests($requestBody);
$response = $service->spreadsheets->batchUpdate($spreadsheetId, $requestBatchBody);
echo "<pre>";
print_r($response);

Related

Mark a position between 2 characters

I often use markers to underline a part of code (e.g., to invite users to hover on it and see hints). Most of time, we want to underline at least one characters. But sometimes, we may want to underline the position between two characters. For instance, given a formula fg(23,), I want to underline the position between , and ), then hovering on it shows a non-empty argument is expected here.
It seems that the following code in the playground can achieve this more or less.
var ed = monaco.editor.create(document.getElementById("container"), {
value: "fg(23,)",
language: "javascript"
});
monaco.editor.setModelMarkers(ed.getModel(), 'test', [{
startLineNumber: 1,
startColumn: 7,
endLineNumber: 1,
endColumn: 7,
message: "a non-empty argument is expected here",
severity: 8
}])
However, setting startColumn same as endColumn does not always do the trick. For instance, I cannot underline the position between ( and 2; setting startColumn: 4, endColumn: 4 does not work.
So does anyone know how to mark a position between 2 characters? We are not limited to underlining, other ways like highlighting are welcomed as well.
(* Link on GitHub: https://github.com/microsoft/monaco-editor/issues/3039 *)
To your question: following this post, it seems that a solution may look something like this (please use the playground to test)
HTML
<div id="container" style="height: 100%"></div>
CSS
.formulaHighlight { /* this class name can be whatever you want */
color: #3456ff !important;
cursor: pointer;
text-decoration: underline;
font-weight: bold;
font-style: oblique;
}
JS
const ed = monaco.editor.create(document.getElementById("container"), {
value: "fg(23,)",
language: "javascript"
});
monaco.editor.setModelMarkers(ed.getModel(), 'test', [{
startLineNumber: 1,
startColumn: 7,
endLineNumber: 1,
endColumn: 7,
message: "a non-empty argument is expected here",
severity: 8
}])
const decorations = ed.deltaDecorations(
[],
[
{
range: new monaco.Range(1, 4, 1, 6),
options: { inlineClassName: 'formulaHighlight' }
}
]
);
The delta decoration method allow to customize styles in specific locations. The monaco.Range method uses the following parameters:
startLineColumn - from which line to start the highlight
startColumn - the numeric value of the first highlight character
endLineNumber - which line stops the highlight
endColumn - the numeric value of last highlighted character
In your case it seems that startLineColumn and endLineNumber will be the same line number. Also,startColumn and endColumn will represent the actual highlight range (within the same line)

API call from google sheets is too long

I am trying to write a script in google sheets to update my 3commas bots. The API requires a number of mandatory fields are passed even when there's only 1 item that needs to be updated.
The code I have is below and it uses the values already read from the platform updating only the base_order_volume value. This works perfectly except for when the pairs value is long (more the 2k chars) and then I get an error from the UrlFetchApp call because the URL is too long.
var sheet = SpreadsheetApp.getActiveSheet();
var key = sheet.getRange('F4').getValue();
var secret = sheet.getRange('F5').getValue();
var baseUrl = "https://3commas.io";
var editBots = "/ver1/bots/"+bots[botCounter].id+"/update";
var patchEndPoint = "/public/api"+editBots+"?";
.
.
[loop around values in sheet]
.
.
var BaseOrder=Number(sheet.getRange(rowCounter,12).getValue().toFixed(2));
var botParams = {
"name": bots[botCounter].name,
"pairs": bots[botCounter].pairs,
"max_active_deals": bots[botCounter].max_active_deals,
"base_order_volume": BaseOrder,
"take_profit": Number(bots[botCounter].take_profit),
"safety_order_volume": bots[botCounter].safety_order_volume,
"martingale_volume_coefficient": bots[botCounter].martingale_volume_coefficient,
"martingale_step_coefficient": Number(bots[botCounter].martingale_step_coefficient),
"max_safety_orders": bots[botCounter].max_safety_orders,
"active_safety_orders_count": Number(bots[botCounter].active_safety_orders_count),
"safety_order_step_percentage": Number(bots[botCounter].safety_order_step_percentage),
"take_profit_type": bots[botCounter].take_profit_type,
"strategy_list": bots[botCounter].strategy_list,
"bot_id": bots[botCounter].id
};
var keys = Object.keys(botParams);
var totalParams = keys.reduce(function(q, e, i) {
q += e + "=" + encodeURIComponent(JSON.stringify(botParams[e])) + (i != keys.length - 1 ? "&" : "");
return q;
},endPoint);
var signature = Utilities.computeHmacSha256Signature(totalParams, secret);
signature = signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
var headers = {
'APIKEY': key,
'Signature': signature,
};
try {
var params = {
'method': 'PATCH',
'headers': headers,
'muteHttpExceptions': true
};
var response = JSON.parse(UrlFetchApp.fetch(baseUrl + totalParams, params).getContentText());
I have tried to set the botParams as a payload in the params but when I do the signature is incorrect.
I anyone knows how to use sheets to make a call using extensive length of parameters I'd appreciate any help at all
Some sample data for the bots array would be
{
"name": "TestBot",
"base_order_volume": 0.001,
"take_profit": 1.5,
"safety_order_volume": 0.001,
"martingale_volume_coefficient": 2,
"martingale_step_coefficient": 1,
"max_safety_orders": 1,
"active_safety_orders_count": 1,
"safety_order_step_percentage": 2.5,
"take_profit_type": "total",
"stop_loss_percentage": 0,
"cooldown": 0,
"pairs": ["BTC_ADA","BTC_TRX"],
"trailing_enabled":"true",
"trailing_deviation":0.5,
"strategy_list": [{"strategy":"cqs_telegram"}]
}
Thanks in advance
I'd consider using a Cloud Function to either do the heavy lifting, or, if you're worried about costs, use it as a proxy. You can then call the cloud function from Google Sheets. Cloud Functions can be written in whatever language you're most comfortable with, including Node.
Check the GCP pricing calculator to see what the cost would be. For many cases it would be completely free.
This should give you a sense of how to use cloud functions for CSV creation:
https://codelabs.developers.google.com/codelabs/cloud-function2sheet#0
Here is a SO question with an answer that explains how to query cloud functions with authentication.

Google Sheets API addProtectedRange Error: No grid with id: 0

I am not sure if I am making a mistake or if possibly this is related to the same issue reported here:
Google Sheets API V4 - Autofill Error - No grid with id: 0
I am getting:
HttpError 400
"Invalid requests[0].addProtectedRange: No grid with id: 1"
Code is something like this (additional addProtectedRange objects removed)
def add_protected_ranges(spreadsheet_id):
service = get_sheets_service()
requests = [
{
"addProtectedRange": {
'protectedRange': {
"range": {
"sheetId": 1,
"startRowIndex": 0,
"endRowIndex": 0,
"startColumnIndex": 0
},
"description": "Headers must not be changed",
"warningOnly": True
}
}
}
]
body = {
'requests': requests
}
response = service.spreadsheets().batchUpdate(spreadsheetId=spreadsheet_id,
body=body).execute()
Had kind of the same issue. I was confusing the sheet id with sheet index.
Easiest way to find the sheet id is in the browser URL when you open the spreadsheet / sheet: https://docs.google.com/spreadsheets/d/{spreadsheetId}/edit#gid={sheetId}
If you're looking for a more programmatic way you find that property on the SheetProperties.
I know that's an old question but I was looking for it too. You're looking for:
res.data.sheets[].properties.sheetId
To get the sheetId (not the spreadsheetId) use:
sheets.spreadsheets.get({
spreadsheetId
}).then(res => {
console.log("All the sheets:");
for(i in res.data.sheets) {
let title = res.data.sheets[i].properties.title;
let id = res.data.sheets[i].properties.sheetId;
console.log(title + ': ' + id);
}
});
Make sure that there is a workbook with the id 1 in your spreadsheet. In google sheet, a spreadsheet can contain multiple worksheet(grid), there is a unique id for each of the worksheet(grid).

Mocha ignores dynamically-generated tests

Trying to run a bunch of dynamically-generated tests within a single describe(). I've copied in the example from the Mocha docs, and it works fine:
describe('add()', function() {
var tests = [
{args: [1, 2], expected: 3},
{args: [1, 2, 3], expected: 6},
{args: [1, 2, 3, 4], expected: 10}
];
tests.forEach(function(test) {
it('correctly adds ' + test.args.length + ' args', function() {
var res = add.apply(null, test.args);
assert.equal(res, test.expected);
});
});
});
BUT, when I try iterating over an array (or cursor) from a database call the entire describe() module is ignored - no errors, just doesn't run at all and doesn't appear in the reporter.
i.e. if I replace the tests array with
const tests = Tests.find().fetch();
the whole thing is just skipped.
What am I doing wrong? Is there a way around this?

Linqpad extension to plot graphs

I tried to plot some graphs in Linqpad with with "Util.RawHtml()" and "Dump()" but it is not working with this example from amcharts.com. I created a string variable including all the HTML source code but the result is not working.
string html = "";
using (System.Net.WebClient client = new System.Net.WebClient ())
{
html = client.DownloadString(#"http://pastebin.com/raw/pmMMwXhm");
}
Util.RawHtml(html).Dump();
Later versions of LinqPad 5 now support charting out of the box with Util.Chart. You can see the samples in the Samples Tab (next to My Queries) under
LINQPad Tutorial&Reference
Scratchpad Features
Charting with Chart
The following script is the Chart() - dual scale sample:
// Each y-series can have a different series type, and can be assigned to the secondary y-axis scale on the right.
var customers = new[]
{
new { Name = "John", TotalOrders = 1000, PendingOrders = 50, CanceledOrders = 20 },
new { Name = "Mary", TotalOrders = 1300, PendingOrders = 70, CanceledOrders = 25 },
new { Name = "Sara", TotalOrders = 1400, PendingOrders = 60, CanceledOrders = 17 },
};
customers.Chart (c => c.Name)
.AddYSeries (c => c.TotalOrders, Util.SeriesType.Spline, "Total")
.AddYSeries (c => c.PendingOrders, Util.SeriesType.Column, "Pending", useSecondaryYAxis:true)
.AddYSeries (c => c.CanceledOrders, Util.SeriesType.Column, "Cancelled", useSecondaryYAxis:true)
.Dump();
As I understand it, this will not work because the html contains scripts that will not be executed.
As an alternative, you can still use the old (and deprecated) google charts api, eg
var link = #"http://chart.apis.google.com/chart?chxt=y&chbh=a&chs=300x225&cht=bvg&chco=A2C180,3D7930&chd=t:10,20,30,40,50,60|30,35,40,45,55,60&chtt=Sample";
Util.Image (link).Dump();
or see
http://blog.divebomb.org/2012/11/dumping-charts-in-linqpad/
Not sure if it's the answer you're after but there may be value in looking at the DisplayWebPage method on the Util class in Linqpad. This correctly rendered your chart in the result window, (although there was a script error). Obviously, this may not solve your underlying issue.
I used version 5.10.00 to test this.