Google sheets api append multiple values with formatting - javascript

I have an array of values that i want to insert to a google sheet via API, i need to give color formatting to certain cells depending on the content of the cell like the image below:
I get to insert the values using append but i have no found any example on formatting append requests.
let values = [["Department", "Product", "Cost", "Status"],
["Clothes", "Socks", "12" "On Stock"],
["Clothes", "Hat", "15.99", "Out of stock"],
["Fresh","Apple", "18", "Pending"],
["Fresh", "Bannana", "17" "Out of stock"],
["Kitchen", "Spoon", "0.99", "Out of stock"]]
await googleSheets.spreadsheets.values.append({
auth,
spreadsheetId,
range: "Products",
valueInputOption: "USER_ENTERED",
resource: {
values: values,
},
});
What is the best approach to achieve this? batchUpdate? how would it be implemented with batchUpdate?

In your situation, how about using ConditionalFormatRule? When ConditionalFormatRule is used, when a script is run one time, the colors are automatically reflected. In order to achieve this, a sample script is as follows.
Sample script:
const googleSheets = google.sheets({ version: "v4", auth }); // Please use your authorization script.
const spreadsheetId = "###"; // Please set Spreadsheet ID.
const sheetId = "###"; // Please set Sheet ID.
// These values and colors are from your showing image.
const colorObj = [
{ value: "On Stock", rgb: [182, 215, 168] }, // #b6d7a8
{ value: "Out of stock", rgb: [244, 204, 204] }, // #f4cccc
{ value: "Pending", rgb: [252, 229, 205] }, // #fce5cd
];
const requests = colorObj.map(({ value, rgb }, i) => ({
addConditionalFormatRule: {
index: 0,
rule: {
booleanRule: {
condition: {
values: [
{
userEnteredValue: value,
},
],
type: "TEXT_EQ",
},
format: {
backgroundColor: {
red: rgb[0] / 255,
green: rgb[1] / 255,
blue: rgb[2] / 255,
},
},
},
ranges: [
{
sheetId,
startColumnIndex: 3,
endColumnIndex: 4,
startRowIndex: 1,
},
],
},
},
}));
const res = await googleSheets.spreadsheets.batchUpdate({
spreadsheetId,
resource: { requests },
});
In this modification, this ConditionalFormatRule is reflected in the range of "D2:D".
Note:
I guessed that from your showing script, you might be using googleapis for Node.js. So, this sample scirpt is for googleapis for Node.js.
References:
Method: spreadsheets.batchUpdate
AddConditionalFormatRuleRequest

Related

How to create incremental range aggregation in mongoose from a query parameter. i.e. create an incremental range from a field value

So the data set looks like this:
screenshot of the data structure
{
"YearWeekISO": "2020-W53",
"FirstDose": 0,
"FirstDoseRefused": "",
"SecondDose": 0,
"DoseAdditional1": 0,
"DoseAdditional2": 0,
"UnknownDose": 0,
"NumberDosesReceived": 0,
"NumberDosesExported": 0,
"Region": "AT",
"Population": "8901064",
"ReportingCountry": "AT",
"TargetGroup": "ALL",
"Vaccine": "JANSS",
"Denominator": 7388778
}, {
"YearWeekISO": "2020-W53",
"FirstDose": 0,
"FirstDoseRefused": "",
"SecondDose": 0,
"DoseAdditional1": 0,
"DoseAdditional2": 0,
"UnknownDose": 8,
"NumberDosesReceived": 0,
"NumberDosesExported": 0,
"Region": "AT",
"Population": "8901064",
"ReportingCountry": "AT",
"TargetGroup": "ALL",
"Vaccine": "UNK",
"Denominator": 7388778
},
link to the data set
The query parameters will look like :
GET /vaccine-summary?c=AT&dateFrom=2020-W10&dateTo=2020-W53&range=5
where
c, country code to get report for
dateFrom, yyyy-Www, eg. 2020-W10 (Including)
dateTo, yyyy-Www, eg, 2020-W20 (Excluding)
rangeSize, number, eg, the period for which to calculate metrics
After applying the aggregation, you should have a transformed data set that looks like :
{
"summary": [{
"weekStart": "2020-W10",
"weekEnd": "2020-W15",
"NumberDosesReceived": 1000
},
{
"weekStart": "2020-W15",
"weekEnd": "2020-W20"
"NumberDosesReceived": 2000
}, …
till end of range(dateTo)
]
}
}
Notice how the weekStart incremental from 2020-W10 to 2020-W15, similar with weekEnd.
NumberDosesReceived is the sum of NumberDosesReceived fileld within that range
So was able to come up with a working solution using a mongo aggregate method called bucket, but one of the problem is that if you want an aggregation of like week 1 - week 20 in chunks of 5, i.e, 1-5 (1 included, 5 excluded), 5- 10,10-15 and 15-20, you will have to give it an array like; boundaries: [1,5,10,15,20] as part of the argument and from the question, i have to create a JS function to return an array of numbers between start week and end week with the range given also. Written in typescript, the return array from this question would look like : [2020-W01,2020-W05,2020-W10,2020-W15,2020-W20], Also there are certain edge cases you have to account for since all the parameters are dynamic, like if the week spans more than one year, also, the fact that mongo to the best of my knowledge don't have date format like "2020-W10" makes it a bit more complex
export function customLoop(
startWeek: number,
endWeek: number,
rangeNum: number,
year: number
): returnData {
const boundaryRange: string[] = [];
let skip = 0;
for (let i = startWeek; i <= endWeek; i += rangeNum) {
const currentNum: string = i < 10 ? `0${i}` : `${i}`;
const result = `${year}-W${currentNum}`;
boundaryRange.push(result);
//if all the weeks in a year, Check where the last loop stops to determine skip
if (endWeek === 53 && i + rangeNum > 53) {
skip = i + rangeNum - 53 - 1;
}
}
return {
skip,
theRange: boundaryRange,
};
}
After this i opened my mongo compass on local to construct and chain aggregate methods and function to satisfy the task given:
const result = await VaccinationModel.aggregate([
{
$match: {
ReportingCountry: c,
},
},
{
$bucket: {
groupBy: "$YearWeekISO",
boundaries: [...boundaryRange],
default: "others",
output: {
NumberDosesReceived: {
$sum: "$NumberDosesReceived",
},
},
},
},
{
$addFields: {
range: rangeNum,
},
},
{
$addFields: {
weekStart: "$_id",
weekEnd: {
$function: {
body: "function(id,range) {\n const arr = id.split('-')\n const year = arr[0];\n let week;\n let result=''\n if(arr[1]){\n week = arr[1].slice(1);\n result=`${year}-W${Number(week) + range}`\n\n }\n\n return result\n }",
args: ["$_id", "$range"],
lang: "js",
},
},
},
},
{
$unset: ["_id", "range"],
},
{
$match: {
weekEnd: {
$ne: "",
},
},
},
{
$sort: {
weekStart: 1,
},
},
]);
In that aggregation:
Match the country code.
I basically called the bucket aggregation with the array of boundaries, then summing the results of each chunk/range using its NumberDosesReceived field while naming it NumberDosesReceived.
since i needed extra two fields to complete the number of fields to return, namely weekStart and weekEnd that isnt in the dataset, the weekStart is the _id field from the bucket aggregation, to get the weekEnd, i added the range as a field.
If for instance the current mongo iteration is 2020-W5, which would be the in the _id, that means the weekend would be 5 + range = 10, so i used the mongo function method to extract that passing _id and range as argument.
Used the unset method to remove the _id and range field as it wouldn't be part of the return data.
Get this new weekEnd field excluding empty ones.
sort using it.
here is the link to the repo: link
It should work
const YearWeekISO = { $toDate: "$YearWeekISO" };
{
$project: {
fiveWeekperiod: {
$subtract: [
{ $week: YearWeekISO },
{ $mod: [{ $week: YearWeekISO }, 5] },
],
},
date: YearWeekISO,
NumberDosesReceived: 1,
},
},
{
$group: {
_id: {
year: { $year: "$date" },
fiveWeek: "$fiveWeekperiod",
},
weekStart: { $min: "$date" },
weekEnd: { $max: "$date" },
NumberDosesReceived: { $sum: "$NumberDosesReceived" },
},
},
{
$project: {
_id: 0,
weekStart: {
$dateToString: {
date: "$weekStart",
format: "%G-W%V",
},
},
weekEnd: {
$dateToString: {
date: {
$dateAdd: {
startDate: "$weekEnd",
unit: "week",
amount: 1,
},
},
format: "%G-W%V",
},
},
NumberDosesReceived: 1,
},
}

jsreport, dynamic number of charts through jquery

I'm using JSreport 3.4.1. and Chart.js 3.8.0. From a server API I'm getting a series of data to create n. charts. The problem is that the number of charts are never the same (they depend on various parameters in a database).
I cannot create n. static charts for the reason above, so I was trying to dynamically create and inject them in the DOM through jQuery, but I'm having some difficulties:
It successfully creates the first chart, but with incorrect data (like it isn't waiting for the trigger input), and the second chart isn't shown at all.
Any idea on how to create a dynamic number of charts based on the number of objects (inside an array) that arrives through an API?
const datasets = {
"datasets": [{
"dynamic_id": 0,
"NomeAnomalia": "MIT Appoggi",
"GruppiAnomalie": 199,
"anomalyList": [{
"GruppiAnomalie": 199,
"Code": "Classe 1\nApp1",
"Name": "Piastra di base deformata",
"Class": "Classe 1",
"Severity": "0 - Lieve",
"Value": 100
}],
"pieChartData": [{
"severityName": "Lieve",
"severityValue": 100
},
{
"severityName": "Media",
"severityValue": 0
},
{
"severityName": "Forte",
"severityValue": 0
}
]
},
{
"dynamic_id": 1,
"NomeAnomalia": "MIT Impalcati,Travi,Traversi CA CAP",
"GruppiAnomalie": 199,
"anomalyList": [{
"GruppiAnomalie": 199,
"Code": "Classe 1\nApp1",
"Name": "Piastra di base deformata",
"Class": "Classe 1",
"Severity": "0 - Lieve",
"Value": 100
}],
"pieChartData": [{
"severityName": "Lieve",
"severityValue": 100
},
{
"severityName": "Media",
"severityValue": 0
},
{
"severityName": "Forte",
"severityValue": 0
}
]
}
]
}
var content = document.getElementById('content');
for (dataset of datasets.datasets) {
var divPieChart = `
<div class="row">
<div class="col-sm-12">
<div class="chart-container">
<canvas id="bar_chart_${dataset.dynamic_id}"></canvas>
</div>
</div>
</div>`;
content.innerHTML += divPieChart;
var bar_chart_ctx = document.getElementById(`bar_chart_${dataset.dynamic_id}`).getContext('2d');
var bar_chart = new Chart(bar_chart_ctx, {
type: 'bar',
data: {
labels: [1, 2, 3],
datasets: [{
"label": "2017",
"data": [5, 3, 7.5],
"backgroundColor": ["rgba(215, 221, 234)"]
}]
},
options: {
maintainAspectRatio: false,
devicePixelRatio: 1.5,
plugins: {
legend: {
display: true,
position: "top"
}
},
scales: {
y: {
beginAtZero: true
}
},
animation: {
onComplete: function() {
// set the PDF printing trigger when the animation is done
// to have this working, the chrome-pdf menu in the left must
// have the wait for printing trigger option selected
window.JSREPORT_READY_TO_START = true
}
}
}
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels#2.0.0"></script>
<div id="test"></div>
<div id="content"></div>
I've set a playground with mock data (not really needed cause I've put static data inside the charts) so you can see what I mean:
playground test
Thank you
EDIT
I figured out how to do it (JSReport specifically): in JSReport, window.JSREPORT_READY_TO_START = true tells the report that all the components in the page are done to print. Breaking down the "creation" of the html and the "creation" of the charts into two separates loop, using the length of the dataset as control, makes the work (only JSReport, I won't post a snippet cause it won't work the same as window.JSREPORT_READY_TO_START = true is not present).
Here's the playground if someone needs it: playground test

How can I format an excel file with empty rows to have nested arrays and match a JSON object that I need to render?

I am facing an issue with an excel file. I receive some data from the DB and the user should be able to replace that data with a spreadsheet that looks like this:
This is how the data comes from the DB and how the excel file should be finally formatted:
"employers": [{
"id": "4147199311345513",
"shifts": [{
"url": "https://zoom.com/983493unsdkd/",
"days": "Mon,Tue,Wed,Thu,Fri",
"name": "Morning",
"endTime": "12:00",
"timezone": "CST",
"startTime": "8:00"
}, {
"url": "https://zoom.com/983493unsdkd/",
"days": "Mon,Tue,Wed,Thu,Fri",
"name": "Afternoon",
"endTime": "12:00",
"timezone": "CST",
"startTime": "8:00"
}],
"employerUrl": "http://www.google.com",
"employerName": "AT&T",
"employerUrlText": "URL Text",
"employerLogoSmall": "assets/images/att-logo.png",
"employerDescription": "AT&T is a world premier employer with a bunch of stuff here and there."
}, {
"id": "3763171269270198",
"shifts": [{
"url": "https://zoom.com/983493unsdkd/",
"days": "Mon,Tue,Wed,Thu,Fri",
"name": "Morning",
"endTime": "12:00",
"timezone": "CST",
"startTime": "8:00"
}, {
"url": "https://zoom.com/983493unsdkd/",
"days": "Mon,Tue,Wed,Thu,Fri",
"name": "Afternoon",
"endTime": "12:00",
"timezone": "CST",
"startTime": "8:00"
}],
"employerUrl": "http://www.google.com",
"employerName": "AT&T",
"employerUrlText": "URL Text",
"employerLogoSmall": "assets/images/att-logo.png",
"employerDescription": "AT&T is a world premier employer with a bunch of stuff here and there."
}]
So I need to take that spreadsheet and format it to look like that JSON above. All of this with Javascript/React.
This is what I have so far to format my excel file and render it:
const [excelData, setExcelData] = useState({ rows: [], fileName: "" });
const fileHandler = (event) => {
let fileObj = event.target.files[0];
ExcelRenderer(fileObj, (err, resp) => {
if (err) {
console.log(err);
} else {
let newRows = [];
let shiftRows = [];
console.log(resp.rows);
resp.rows.slice(1).map((row, index) => {
if (row && row !== "undefined") {
return newRows.push({
key: index,
employer: {
name: row[0],
description: row[1],
employerUrl: row[2],
employerUrlText: row[3],
shifts: shiftRows.push({ shift: row[2] }),
},
});
}
return false;
});
setExcelData({ rows: newRows, fileName: fileObj.name });
}
});
};
That console.log above (console.log(resp.rows)) returns this:
Where the first row are the headers of the excel file.
And the code above ends up like this and it should be exactly as the JSON I mentioned:
rows: [
{
key: 0,
employer: {
name: 'AT&T',
description: 'AT&T is a world premier employer with a bunch of stuff here and there.',
shifts: 1
}
},
{
key: 1,
employer: {
shifts: 2
}
},
{
key: 2,
employer: {
shifts: 3
}
},
{
key: 3,
employer: {
shifts: 4
}
},
{
key: 4,
employer: {
name: 'Verizon',
description: 'Verizon is a world premier employer with a bunch of stuff here and there.',
shifts: 5
}
},
{
key: 5,
employer: {
shifts: 6
}
},
{
key: 6,
employer: {
shifts: 7
}
},
{
key: 7,
employer: {
shifts: 8
}
}
],
fileName: 'EmployerChats.xlsx',
false: {
rows: [
{
url: 'https://www.youtube.com/kdfjkdjfieht/',
title: 'This is a video',
thumbnail: '/assets/images/pages/5/links/0/link.png',
description: 'This is some text'
},
{
url: 'https://www.youtube.com/kdfjkdjfieht/',
title: 'This is a video',
thumbnail: '/assets/images/pages/5/links/1/link.png',
description: 'This is some text'
}
]
},
I am using this plugin to help me render the excel file: https://www.npmjs.com/package/react-excel-renderer
Any ideas on what can I do to make format the spreadsheet data as the JSON?
Please notice those empty rows.
For example every time there is a new employer name, that's a new row or item in the array, then all of the columns and rows below and after Shift Name is a new nested array of objects. Hence, this file contains an array with a length of 2 and then it contains another array of items when it hits the Shift Name column.
Is it clear?
1st of all - you don't need to follow 'original', class based setState. In FC you can just use two separate useState.
const [rows, setRows] = useState([]);
const [fileName, setFileName] = useState("");
Data conversion
I know that you need a bit different workflow, but this can be usefull (common point - data structure), too - as conversion guide, read on.
You don't need to use ExcelRenderer to operate on data from db and render it as sheet. Converted data can be exported to file later.
You can just create array of array (aoa) that follows expected view (rows = array of row cells array). To do this you need very easy algorithm:
let newData = []
map over emplyers, for each (emp):
set flag let first = true;
map over shifts, for each (shift):
if( first ) { newData.push( [emp.name, emp.descr, shift.name, shift.timezone...]); first = false;
} else newData.push( [null, null, shift.name, shift.timezone...]);
setRows( newData );
Rendering
<OutTable/> operates on data and colums props - structures similar to internal state. 'datais ourrows, we only needcolumns` prop, just another state value:
const [columns, setColumns] = useState([
{ name: "Employer name", key: 0 },
{ name: "Employer description", key: 1 },
{ name: "Shift name", key: 2 },
// ...
]);
and finally we can render it
return (
<OutTable data={rows] columns />
Later
User can operate on sheet view - f.e. insert rows using setRows() or download this as file (XLSX.writeFile()) after simple conversion:
var ws = XLSX.utils.aoa_to_sheet( columns.concat( rows ) );
There is a lot of utils you can use for conversions - see samples.
Back to your needs
We have data loaded from db, data in aoa form, rendered as sheet. I don't fully understand format you need, but for your db format conversion is simple (opposite to above) - you can follow it and adjust to your needs.
let newEmployers = [];
let empCounter = -1;
// itarate on rows, on each (`row`):
rows.map( (row) => {
// new employer
if( row[0] ) {
newEmployers.push( {
// id should be here
"employerName": row[0],
"employerDescription": row[1],
"shifts": [
{
"shiftName": row[3],
"shiftDescription": row[4],
// ...
}
]
} );
empCounter ++;
} else {
// new shift for current employer
newEmployers[empCounter].shifts.push(
{
"shiftName": row[3],
"shiftDescription": row[4],
// ...
}
);
}
});
// newEmployers can be sent to backend (as json) to update DB

How to add a custom last row within a table using jspdf-Autotable

I'm trying to build a small app with jspdf and jspdf-Autotable but I don't find how to create a custom last row with another variable value in an existing table.
What I just have done so far is this:
doc.autoTable({
body: concepts,
columns: [{ header: 'Concept', dataKey: 'name' }, { header: 'Amount', dataKey: 'amount' }],
didDrawPage: function (data) {
let rows = data.table.body;
rows.push({
content: 'Total = ' + total,
colSpan: 2
});
if (data.row.index === rows.length - 1) { doc.setFillColor(239, 154, 154); }
}
});
I'm pretty sure that I'm getting confused on how to face this problem. total comes with a number value but I want to print it in one custom last row alone but within the table, kind like an Excel rule would do.
Any suggestion would be helpful! Thank you.
You have to add your custom data before processing the elements. Use ... to destructure the data and merge with the new row.
function generate() {
var doc = new jsPDF();
var data = [
{name: "Bar", amount: 1200},
{name: "Zap", amount: 200},
{name: "Foo", amount: 320}];
var total = data.reduce((sum, el) => sum + el.amount, 0);
var body = [...data.map(el => [el.name, el.amount]),
[{content: `Total = ${total}`, colSpan: 2,
styles: { fillColor: [239, 154, 154] }
}]]
doc.autoTable({
head: [['Concept', 'Amount']],
body: body
});
doc.save("table.pdf");
}
<script src="https://unpkg.com/jspdf#1.5.3/dist/jspdf.min.js"></script>
<script src="https://unpkg.com/jspdf-autotable#3.4.1/dist/jspdf.plugin.autotable.js"></script>
<button onclick="generate()">Generate PDF</button>

Setting PageSize of Google Slides with Javascript API

I am creating google slides with the help of javascript API. I need to set up the page size But its not working. Below is my code .
var size = {
width:{
magnitude: 612,
unit: "PT"
},
height: {
magnitude: 792,
unit: 'PT'
}};
gapi.client.slides.presentations.create({
title: "Some title",
pageSize: size
}).then((response) => {
var presentationId = response.result.presentationId;
})
Use this Method: presentations.create
Creates a new presentation using the title given in the request. Other
fields in the request are ignored. Returns the created presentation.
Here is the request body:
{
"presentationId": string,
"pageSize": {
object(Size)
},
"slides": [
{
object(Page)
}
],
"title": string,
"masters": [
{
object(Page)
}
],
"layouts": [
{
object(Page)
}
],
"locale": string,
"revisionId": string,
"notesMaster": {
object(Page)
},
}
To check the description of pageSize, you can refer to the fields table. It was specified as the size of pages in the presentation.
Also, you may want to check this javascript Browser Quickstart to help you further.

Categories