Replace each ID in array of IDs with the data fetched from an API by iterating over the array and calling useQuery hook in each iteration in rtk-query - react-hooks

Layout data format:
{
"header": [
{
"cellId": "header1",
"height": "25vh",
"width": null,
"defaultWidgetHeight": "25vh",
"defaultWidgetWidth": null,
"widgetIds": [
3,
2
]
}
],
"footer": [
{
"cellId": "footer1",
"height": "25vh",
"width": null,
"defaultWidgetHeight": "25vh",
"defaultWidgetWidth": null,
"widgetIds": [
4
]
}
],
"leftSidebar": [
{
"cellId": "leftSidebar1",
"height": "70vh",
"width": {
"xs": 12,
"md": 3
},
"defaultWidgetHeight": "30vh",
"defaultWidgetWidth": null,
"widgetIds": [
5
]
}
],
"rightSidebar": [
{
"cellId": "rightSidebar1",
"height": "70vh",
"width": {
"xs": 12,
"md": 3
},
"defaultWidgetHeight": "30vh",
"defaultWidgetWidth": null,
"widgetIds": [
6
]
}
],
"innerHeader": [],
"innerFooter": [],
"innerLeftSidebar": [],
"innerRightSidebar": [],
"center": [
{
"cellId": "center1",
"height": "70vh",
"width": {
"xs": 12
},
"defaultWidgetHeight": "30vh",
"defaultWidgetWidth": null,
"widgetIds": [
7
]
}
]
}
Child component:
Inside the child component, I am trying to transform the layout data and storing a new tranformed layout data in newLayoutData state variable. I am trying to transform it by replacing each ID inside widgetIds array with the data fetched from an API that uses widgetId as a path parameter using useGetDataQuery hook provided by api slice of RTK-query. The problem is that I cannot call hook inside loops or callbacks.
import React, { useState, useEffect } from 'react';
import { useGetDataQuery } from './features/apiSlice';
const Child2 = ({ layoutData }) => {
const [newLayoutData, setNewLayoutData] = useState(null);
useEffect(() => {
const updatedLayoutData = {};
Object.keys(layoutData).forEach((masterContainerId) => {
updatedLayoutData[masterContainerId] = [];
layoutData[masterContainerId].forEach(async(childContainer) => {
updatedLayoutData[masterContainerId].push({
...childContainer,
widgetIds: childContainer.widgetIds.map(async(widgetId) => {
const widgetQuery = useGetDataQuery(`/app/v2/dashboard/widget/${widgetId}`)
return widgetQuery.data;
})
})
});
});
setNewLayoutData(updatedLayoutData);
}, []);
return (
<h1>Layout data transformed</h1>
);
};
export default Child2;
I tried solving this issue using axios but in that case I cannot have the priveledge of using cache that RTK query provides because if the same widgetId appears in some other widgetIds array then axios will again going to make an API call for that ID whereas rtk-query won't and will return the existing cache data for that widgetId.

Related

qrcode-vue3 CORS issue intermittently

I am using this package - qrcode-vue3 in my Laravel Vue project.
I am creating a QR code in various places of the app, each of which contains an logo image supplied via the component options:
<template>
<QRCodeVue3
v-bind="qrOptions"
/>
</template>
<script>
import QRCodeVue3 from "qrcode-vue3";
import {computed} from "vue";
import {useStore} from "vuex";
export default {
name: "QrGenerator",
components: {
QRCodeVue3,
},
props: {
qrData: {
type: String,
default: undefined,
},
withSquareLogo: {
type: Boolean,
default: true,
},
width: {
type: Number,
default: 300,
},
height: {
type: Number,
default: 300,
},
download: {
type: Boolean,
default: true,
},
downloadFileName: {
type: String,
default: undefined,
},
},
setup(props) {
const store = useStore();
const colors = computed(() => store.getters['whiteLabel/colors']);
const logos = computed(() => store.getters['whiteLabel/logos']);
const qrOptions = computed( () => ({
"backgroundOptions": {
"color": "#ffffff"
},
"backgroundOptionsHelper": {
"colorType": {
"gradient": false,
"single": true
},
"gradient": {
"color1": "#ffffff",
"color2": "#ffffff",
"linear": true,
"radial": false,
"rotation": "0"
}
},
"cornersDotOptions": {
"color": "#000000",
"type": ""
},
"cornersDotOptionsHelper": {
"colorType": {
"gradient": false,
"single": true
},
"gradient": {
"color1": "#000000",
"color2": "#000000",
"linear": true,
"radial": false,
"rotation": "0"
}
},
"cornersSquareOptions": {
"color": "#000000",
"type": "extra-rounded"
},
"cornersSquareOptionsHelper": {
"colorType": {
"gradient": false,
"single": true
},
"gradient": {
"color1": "#000000",
"color2": "#000000",
"linear": true,
"radial": false,
"rotation": "0"
}
},
"value": props.qrData,
"dotsOptions": {
"color": colors.value.primary_color,
"type": "extra-rounded"
},
"dotsOptionsHelper": {
"colorType": {
"gradient": false,
"single": true
},
"gradient": {
"color1": colors.value.primary_color,
"color2": colors.value.primary_color,
"linear": true,
"radial": false,
"rotation": "0"
}
},
"image": logos.value.square,
"imageOptions": {
"hideBackgroundDots": true,
"imageSize": 0.4,
"margin": 0,
"crossOrigin": "Anonymous"
},
"margin": 0,
"qrOptions": {
"errorCorrectionLevel": "Q",
"mode": "Byte",
"typeNumber": "0"
},
"download": false,
"height": props.height,
"width": props.width
}));
return {
logos,
colors,
qrOptions,
}
}
};
This works OK for some of the time, with logo loading into the QR code. The logo is in a static assets S3 bucket, that is public access and allow-origins of "*". The domain to the static assets bucket (https://static.myapp.com.au) is being proxied via CloudFlare.
But for some of the time I get a CORS error as the component is mounting and generating the QR:
Access to image at 'https://static.myapp.com.au/logo_square.png' from origin 'https://lins.myapp.com.au' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. index.js?f95a:1
GET https://static.myapp.com.au/logo_square.png net::ERR_FAILED 200
It has wasted a bunch of time now but without luck in finding the solution. It will work consecutively for a number of tries (say 20 times), then try again some minutes later and Bang! CORS error.
Thanks

Combining two observable sources filtering by one first observable property

Having an observable emitting a list of users with the next content:
[
{
"id": 1,
"name": "John",
"status": "Active"
},
{
"id": 2,
"name": "Mary",
"status": "Inactive"
},
{
"id": 3,
"name": "Peter",
"status": "Inactive"
},
{
"id": 4,
"name": "Susan",
"status": "Active"
}
]
And I have another observable returning the extended user data:
{
"id": 1,
"authorizations: 20
}
I use the detail of each user in an specific details page, but I would like to combine part of the detail in the users list and obtain the next result and only filter by the status Active:
[
{
"id": 1,
"name": "John",
"status": "Active",
"authorizations": 20
},
{
"id": 4,
"name": "Susan",
"status": "Active",
"authorizations": 10
}
]
It is possible to use some filtering operator and combine those results without use two subscriptions?
Tried the following code but, would be a better or simplified way to do it?
import { of, Observable, combineLatest } from 'rxjs';
import { filter, map, mergeAll, mergeMap } from 'rxjs/operators';
type State = 'Active' | 'Inactive';
type User = { id: number; name: string; status: State };
type UserDetail = { id: number; authorizations: number };
type UserWithAuthorizations = User & UserDetail
const users: User[] = [
{
"id": 1,
"name": "John",
"status": "Active"
},
{
"id": 2,
"name": "Mary",
"status": "Inactive"
},
{
"id": 3,
"name": "Peter",
"status": "Inactive"
},
{
"id": 4,
"name": "Susan",
"status": "Active"
}
]
const authorizations: UserDetail[] = [
{ id: 1, authorizations: 20 },
{ id: 2, authorizations: 5 },
{ id: 3, authorizations: 30 },
{ id: 4, authorizations: 10 },
];
const getAuthorizationsByUser= (userId: number): Observable<Partial<UserWithAuthorizations>> => {
const users$ = of(users)
const authorizations$ = of(authorizations)
return combineLatest([users$, authorizations$]).pipe(
map(res => {
const user = res[0].find(u => u.id === userId)
const { authorizations } = res[1].find(a => a.id === userId)
return {
...user,
authorizations
}
}))
};
const fetchUsersWithAuthorizations = () => of(users);
fetchUsersWithAuthorizations()
.pipe(
mergeAll<User>(),
filter((user) => user.status === "Active"),
mergeMap((user) => getAuthorizationsByUser(user.id))
)
.subscribe(console.log);
Why not do it all in a single combine latest?
const { of, map, combineLatest } = rxjs;
const users = [
{
"id": 1,
"name": "John",
"status": "Active"
},
{
"id": 2,
"name": "Mary",
"status": "Inactive"
},
{
"id": 3,
"name": "Peter",
"status": "Inactive"
},
{
"id": 4,
"name": "Susan",
"status": "Active"
}
]
const authorizations = [
{ id: 1, authorizations: 20 },
{ id: 2, authorizations: 5 },
{ id: 3, authorizations: 30 },
{ id: 4, authorizations: 10 },
];
const users$ = of(users)
const authorizations$ = of(authorizations)
const activeUsersWithAuthorizations$ = combineLatest([users$, authorizations$]).pipe(
map(([users, authorizations]) =>
users
.filter((user) => user.status === 'Active')
.map((user) => ({
...user,
authorizations: authorizations.find((a) => a.id === user.id)?.authorizations,
}))
)
);
activeUsersWithAuthorizations$.subscribe(activeUsersWithAuthorizations => {
console.log(activeUsersWithAuthorizations);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js" integrity="sha512-v0/YVjBcbjLN6scjmmJN+h86koeB7JhY4/2YeyA5l+rTdtKLv0VbDBNJ32rxJpsaW1QGMd1Z16lsLOSGI38Rbg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Improve chart update performance in GAS

I have a function that is updating every chart of a sheet and my main issue is that the updateChart() function is really slow (about 50 seconds) :
I already made a script that parallelize the function but due to the 20 triggers per script limitation I can only run my thread twice. So I wanted to know if there was anything that can speed up the update of my charts.
function ModifyVAxisChart()
{
var ss=SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DCN Dashboard Data");
var rangeMax=ss.getRange("O3:O231").getValues();//Range to modify if you add charts
var rangeMinId=ss.getRange("P3:P232").getValues();//Range to modify if you add charts
var i=0;
var nbChart=39;
//Logger.log("range Max ="+rangeMax + "autre="+rangeMinId);
var Vmin=0;
var Vmax=0;
var id=-1;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DCN Dashboard");
var chart = sheet.getCharts();
for(i=0;i<nbChart;i++)
{
Vmax=rangeMax[i*6];
Vmin=rangeMinId[i*6];
id=rangeMinId[(i*6)+1];
var delta=(Vmax-Vmin)*0.1;
Logger.log("Vmax="+Vmax+"Vmin="+Vmin+"id="+id+"i="+i);
var currChart = chart[id];
if(currChart.getType()!="COLUMN")
{
Vmin-=delta
Vmax=Number(Vmax)+(delta*1.5)//Number() function to avoid Vmax becoming a string for no reason
}
Logger.log("Vmax="+Vmax+"Vmin="+Vmin+"id="+id+"i="+i);
currChart = currChart.modify()
.setOption('vAxes', {0: {textStyle: {fontSize: 10}, titleTextStyle: {fontSize : 8}, viewWindow: {min: Vmin, max:Vmax}}})//adpative vaxis for AREA and COMBO
.build();
sheet.updateChart(currChart);
}
}
As the App script functions aren't asynchronous, they will wait until the request is complete to keep running the code (and thus to run the rest of the requests). What you could do is to make the requests using the Sheets API [1] in either JavaScript (you would need to serve and html) or using the UrlFetchApp class [2], this way you could initiate the requests without needing to wait for the response from the previous request.
I implemented the fetchAll() method [2] with one request to update a chart and worked successfully, here is the code (you need to put the sheetID):
function uploadChart() {
var data = SpreadsheetApp.getActiveSpreadsheet();
var sheet = data.getSheets()[1];
var chart = sheet.getCharts()[0];
var chartId = chart.getChartId();
var token = ScriptApp.getOAuthToken();
var url = "https://sheets.googleapis.com/v4/spreadsheets/SHEET_ID:batchUpdate";
var chartBody = {
"updateChartSpec": {
"chartId": chartId,
"spec": {
"title": "Model Q1 Sales",
"basicChart": {
"chartType": "BAR",
"legendPosition": "RIGHT_LEGEND",
"axis": [
{
"format": {
"bold": true,
"italic": true,
"fontSize": 24
},
"position": "BOTTOM_AXIS",
"title": "Sales"
},
{
"format": {
"bold": true,
"italic": true,
"fontSize": 24
},
"position": "LEFT_AXIS",
"title": "Model Numbers"
}
],
"domains": [
{
"domain": {
"sourceRange": {
"sources": [
{
"startRowIndex": 0,
"endRowIndex": 6,
"startColumnIndex": 0,
"endColumnIndex": 1
}
]
}
}
}
],
"series": [
{
"series": {
"sourceRange": {
"sources": [
{
"startRowIndex": 0,
"endRowIndex": 6,
"startColumnIndex": 1,
"endColumnIndex": 2
}
]
}
},
"targetAxis": "BOTTOM_AXIS"
},
{
"series": {
"sourceRange": {
"sources": [
{
"startRowIndex": 0,
"endRowIndex": 6,
"startColumnIndex": 2,
"endColumnIndex": 3
}
]
}
},
"targetAxis": "BOTTOM_AXIS"
},
{
"series": {
"sourceRange": {
"sources": [
{
"startRowIndex": 0,
"endRowIndex": 6,
"startColumnIndex": 3,
"endColumnIndex": 4
}
]
}
},
"targetAxis": "BOTTOM_AXIS"
}
],
"headerCount": 1
}
}
}
}
var requestBody = {
'requests': [chartBody]
}
var request1 = {
'url': url,
'headers': {
'Authorization': 'Bearer ' + token,
},
'method' : 'post',
'contentType': 'application/json',
'payload' : JSON.stringify(requestBody),
'muteHttpExceptions': true
};
var requests = [request1]
var response = UrlFetchApp.fetchAll(requests);
Logger.log(response)
}
I used the update chart json from the "edit a chart" example [1].
To add more requests, you can either add more request jsons in the fetchAll() array parameter or add more update chart jsons in the 'requests' array on requestBody.
[1] https://developers.google.com/sheets/api/samples/charts
[2] https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetchAll(Object)

Transforming object data to array for d3plus-react

I have an api that returns data in the following format:
{
"Information Technology": {
"Name": "Information Technology",
"Change": "0.82%"
},
"Consumer Staples": {
"Name": "Consumer Staples",
"Change": "0.19%"
}
}
I want to convert it to the following format inside my d3plus visualizations:
[
{
"Name": "Information Technology",
"Change": "0.82%"
},
{
"Name": "Consumer Staples",
"Change": "0.19%"
}
]
How do I do this. Here's my React component that uses d3plus:
function Chart() {
const methods = {
groupBy: 'Name',
data: 'https://example.com/api/sectors-performance',
size: d => d.Change
};
return <Treemap config={methods} />;
}
There was a small hint in the docs which helped me come up with this solution:
function Chart() {
const methods = {
groupBy: 'id',
data: 'https://example.com/api/sectors-performance',
size: d => d.value
};
const formatter = d =>
Object.keys(d).map(key => ({
id: d[key].Name,
value: numeral(d[key].Change).value()
}));
return <Treemap config={methods} dataFormat={formatter} />;
}
The trick is to send a formatter as a property!

Datatables with laravel how to render hasMany relationship

i have the following response from my controller
[
{
"id": 1,
"place_id": 1,
"name": "Test",
"type": 5,
"created_at": "2018-06-04 15:29:02",
"updated_at": "2018-06-04 15:29:02",
"time": [
{
"id": 1,
"stadium_id": 1,
"day": "Saturday",
"from_hour": 7,
"to_hour": 12,
"created_at": "2018-06-04 15:29:42",
"updated_at": "2018-06-04 15:29:42"
},
{
"id": 2,
"stadium_id": 1,
"day": "Sunday",
"from_hour": 7,
"to_hour": 12,
"created_at": "2018-06-04 15:54:03",
"updated_at": "2018-06-04 15:54:03"
}
]
}
]
i want to access the day attribute in each time object
i tried the code below but of course it just loads the first result
is there a solution
columns: [
{ data: 'name' },
{ data: 'type' },
{ data: 'time.0.day' },
{ data: 'time.0.from_hour' },
{ data: 'time.0.to_hour' },
],
You can actually access time array by using the render property:
columns: [{
data: 'name'
},
{
data: 'type'
},
{
data: 'time',
render: function(data, type, row) {
var txt = '';
data.forEach(function(item) {
if (txt.length > 0) {
txt += '</br> '
}
txt += item.day;
});
return txt;
}
},
...
This would just add a line break for every objects in time. Here's a Fiddle for you to examine.

Resources