@iotready/nextjs-components-library 1.0.0-preview12 → 1.0.0-preview13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,12 +7,13 @@ type Measure = {
|
|
7
7
|
unit: string;
|
8
8
|
stepped: boolean;
|
9
9
|
};
|
10
|
-
declare const TrendChart: ({ deviceId,
|
10
|
+
declare const TrendChart: ({ deviceId, measures1, measures2, enableDatePicker, handleGetInfluxData, handleExportDataCB, theme }: {
|
11
11
|
deviceId: string;
|
12
|
-
|
13
|
-
|
12
|
+
measures1?: Array<Measure>;
|
13
|
+
measures2?: Array<Measure>;
|
14
14
|
enableDatePicker: boolean;
|
15
|
-
handleGetInfluxData: (measure: string, timeStart: number, timeEnd: number, deviceId: string, timeGroup: string, raw: boolean) => Promise<any>;
|
15
|
+
handleGetInfluxData: (measure: string, timeStart: number, timeEnd: number, deviceId: string, timeGroup: string, raw: boolean, fill?: boolean) => Promise<any>;
|
16
|
+
handleExportDataCB?: (measure: string, timeStart: number, timeEnd: number, deviceId: string) => Promise<any>;
|
16
17
|
theme: Theme;
|
17
18
|
}) => import("react/jsx-runtime").JSX.Element;
|
18
19
|
export default TrendChart;
|
@@ -64,7 +64,19 @@ const lineOptions = {
|
|
64
64
|
day: 'DD MMM YY',
|
65
65
|
hour: 'DD MMM HH:mm',
|
66
66
|
minute: 'HH:mm'
|
67
|
-
}
|
67
|
+
},
|
68
|
+
y1: {
|
69
|
+
type: 'linear',
|
70
|
+
position: 'left',
|
71
|
+
title: { display: false, text: 'Primary Axis' },
|
72
|
+
display: false
|
73
|
+
},
|
74
|
+
y2: {
|
75
|
+
type: 'linear',
|
76
|
+
position: 'right',
|
77
|
+
title: { display: false, text: 'Secondary Axis' },
|
78
|
+
display: false
|
79
|
+
},
|
68
80
|
},
|
69
81
|
title: {
|
70
82
|
display: false,
|
@@ -76,7 +88,6 @@ const lineOptions = {
|
|
76
88
|
drawOnChartArea: false,
|
77
89
|
}
|
78
90
|
},
|
79
|
-
y: {},
|
80
91
|
},
|
81
92
|
};
|
82
93
|
const chartConfigByPeriod = {
|
@@ -175,7 +186,7 @@ function getCsvData(data, measures) {
|
|
175
186
|
return csvData.map(row => row.join(',')).join('\n');
|
176
187
|
}
|
177
188
|
// eslint-disable-next-line no-unused-vars
|
178
|
-
const TrendChart = ({ deviceId,
|
189
|
+
const TrendChart = ({ deviceId, measures1, measures2, enableDatePicker, handleGetInfluxData, handleExportDataCB, theme }) => {
|
179
190
|
const [chartJsLoaded, setChartJsLoaded] = useState(false);
|
180
191
|
const [dataMeasures, setDataMeasures] = useState(null);
|
181
192
|
const [chartPeriod, setChartPeriod] = useState('1D');
|
@@ -191,6 +202,10 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
|
|
191
202
|
const csvLinkRef = useRef(null);
|
192
203
|
const [csvData, setCsvData] = useState('');
|
193
204
|
const [spanGapsOption, setSpanGapsOption] = useState(true);
|
205
|
+
const enableExportData = handleExportDataCB ? true : false;
|
206
|
+
const measures = measures1 && measures2 ? [...measures1, ...measures2] :
|
207
|
+
measures1 ? [...measures1] :
|
208
|
+
measures2 ? [...measures2] : [];
|
194
209
|
const [options, setOptions] = useState({
|
195
210
|
...lineOptions,
|
196
211
|
plugins: {
|
@@ -220,8 +235,13 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
|
|
220
235
|
...options,
|
221
236
|
scales: {
|
222
237
|
...options.scales,
|
223
|
-
|
224
|
-
...options.scales.
|
238
|
+
y1: {
|
239
|
+
...options.scales.y1,
|
240
|
+
min: 0,
|
241
|
+
max: 1
|
242
|
+
},
|
243
|
+
y2: {
|
244
|
+
...options.scales.y2,
|
225
245
|
min: 0,
|
226
246
|
max: 1
|
227
247
|
}
|
@@ -258,12 +278,14 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
|
|
258
278
|
};
|
259
279
|
const handleExportData = async () => {
|
260
280
|
setLoadingButton(true);
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
281
|
+
if (handleExportDataCB) {
|
282
|
+
const data = await Promise.all(measures.map(async (measure) => {
|
283
|
+
return await handleExportDataCB(measure.name, timeStart, timeEnd, deviceId);
|
284
|
+
}));
|
285
|
+
const csvData = getCsvData(data, measures);
|
286
|
+
setCsvData(csvData);
|
287
|
+
setLoadingButton(false);
|
288
|
+
}
|
267
289
|
};
|
268
290
|
useEffect(() => {
|
269
291
|
if (csvData.length > 0) {
|
@@ -272,11 +294,15 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
|
|
272
294
|
}, [csvData]);
|
273
295
|
const loadDatasets = async (chartPeriod) => {
|
274
296
|
let intervalInSeconds = chartPeriod === "ALL" && !datePickerUsed && !zoomed ? 31536000 : timeEnd - timeStart;
|
275
|
-
const rawQuery = intervalInSeconds < 86400;
|
276
|
-
//
|
277
|
-
const
|
297
|
+
const rawQuery = intervalInSeconds < 86400;
|
298
|
+
// Combine measures and track their source
|
299
|
+
const allMeasures = [
|
300
|
+
...(measures1?.map(m => ({ ...m, source: 'measures1' })) || []),
|
301
|
+
...(measures2?.map(m => ({ ...m, source: 'measures2' })) || [])
|
302
|
+
];
|
303
|
+
const datasetsPromises = allMeasures.map(async (measure) => {
|
278
304
|
const polltime = getPollTime(intervalInSeconds, measure.polltime || 30);
|
279
|
-
const influxData = await handleGetInfluxData(measure.name, timeStart, timeEnd, deviceId, polltime, !measure.polltime && rawQuery);
|
305
|
+
const influxData = await handleGetInfluxData(measure.name, timeStart, timeEnd, deviceId, polltime, !measure.polltime && rawQuery, !measure.polltime);
|
280
306
|
const points = getChartPoints(influxData);
|
281
307
|
return {
|
282
308
|
label: measure.name,
|
@@ -288,13 +314,15 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
|
|
288
314
|
pointHoverBackgroundColor: 'rgba(52, 125, 236, 0.5)',
|
289
315
|
change: !measure.polltime,
|
290
316
|
stepped: measure.stepped,
|
291
|
-
fill: false
|
317
|
+
fill: false,
|
318
|
+
yAxisID: measure.source === 'measures1' ? 'y1' : 'y2'
|
292
319
|
};
|
293
320
|
});
|
294
|
-
// Risolvi tutte le promesse per popolare i dataset
|
295
321
|
const datasets = await Promise.all(datasetsPromises);
|
296
|
-
let
|
297
|
-
let
|
322
|
+
let min1 = null;
|
323
|
+
let max1 = null;
|
324
|
+
let min2 = null;
|
325
|
+
let max2 = null;
|
298
326
|
let time;
|
299
327
|
let minTime = null;
|
300
328
|
datasets.forEach(dataset => {
|
@@ -304,37 +332,59 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
|
|
304
332
|
}
|
305
333
|
const datasetMin = Math.min(...values);
|
306
334
|
const datasetMax = Math.max(...values);
|
307
|
-
if (
|
308
|
-
|
309
|
-
|
310
|
-
|
335
|
+
if (dataset.yAxisID === 'y1') {
|
336
|
+
if (min1 === null || datasetMin < min1)
|
337
|
+
min1 = datasetMin;
|
338
|
+
if (max1 === null || datasetMax > max1)
|
339
|
+
max1 = datasetMax;
|
340
|
+
}
|
341
|
+
else {
|
342
|
+
if (min2 === null || datasetMin < min2)
|
343
|
+
min2 = datasetMin;
|
344
|
+
if (max2 === null || datasetMax > max2)
|
345
|
+
max2 = datasetMax;
|
346
|
+
}
|
311
347
|
if (time && (minTime === null || time.unix() < minTime))
|
312
348
|
minTime = time.unix() - 86400;
|
313
349
|
});
|
314
|
-
|
315
|
-
|
316
|
-
|
350
|
+
const getPaddedMinMax = (min, max) => {
|
351
|
+
if (min === null || max === null)
|
352
|
+
return { paddedMin: null, paddedMax: null };
|
317
353
|
const diff = ((max - min) * 0.2) < 0.1 ? 0.1 : (max - min) * 0.2;
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
354
|
+
return {
|
355
|
+
paddedMin: Math.floor((min - diff) * 10) / 10,
|
356
|
+
paddedMax: Math.ceil((max + diff) * 10) / 10
|
357
|
+
};
|
358
|
+
};
|
359
|
+
const { paddedMin: paddedMin1, paddedMax: paddedMax1 } = getPaddedMinMax(min1, max1);
|
360
|
+
const { paddedMin: paddedMin2, paddedMax: paddedMax2 } = getPaddedMinMax(min2, max2);
|
361
|
+
setDataMeasures(datasets);
|
322
362
|
setTimeStartPicker(minTime || timeStart);
|
363
|
+
const scalesOptions = {
|
364
|
+
...options.scales,
|
365
|
+
y1: {
|
366
|
+
...options.scales.y1,
|
367
|
+
min: paddedMin1,
|
368
|
+
max: paddedMax1,
|
369
|
+
position: "left",
|
370
|
+
display: "auto"
|
371
|
+
},
|
372
|
+
y2: {
|
373
|
+
...options.scales.y2,
|
374
|
+
min: paddedMin2,
|
375
|
+
max: paddedMax2,
|
376
|
+
position: "right",
|
377
|
+
display: "auto"
|
378
|
+
},
|
379
|
+
x: {
|
380
|
+
...options.scales.x,
|
381
|
+
min: moment.unix(minTime || timeStart).toString(),
|
382
|
+
max: moment.unix(timeEnd).toString()
|
383
|
+
}
|
384
|
+
};
|
323
385
|
setOptions({
|
324
386
|
...options,
|
325
|
-
scales:
|
326
|
-
...options.scales,
|
327
|
-
y: {
|
328
|
-
...options.scales.y,
|
329
|
-
min: paddedMin,
|
330
|
-
max: paddedMax,
|
331
|
-
},
|
332
|
-
x: {
|
333
|
-
...options.scales.x,
|
334
|
-
min: moment.unix(minTime || timeStart).toString(),
|
335
|
-
max: moment.unix(timeEnd).toString()
|
336
|
-
}
|
337
|
-
}
|
387
|
+
scales: scalesOptions
|
338
388
|
});
|
339
389
|
};
|
340
390
|
useEffect(() => {
|
@@ -367,13 +417,13 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
|
|
367
417
|
const prevMeasuresValue = prevMeasures.current || [];
|
368
418
|
//@ts-ignore
|
369
419
|
prevMeasures.current = measures;
|
370
|
-
if (!prevMeasuresValue || prevMeasuresValue.length !== measures
|
420
|
+
if (!prevMeasuresValue || prevMeasuresValue.length !== measures?.length || (prevMeasuresValue[0] && prevMeasuresValue[0].name !== measures[0].name)) {
|
371
421
|
setChartLoading(true);
|
372
422
|
}
|
373
423
|
loadDatasets(chartPeriod).then(() => {
|
374
424
|
setChartLoading(false);
|
375
425
|
});
|
376
|
-
}, [
|
426
|
+
}, [measures1, timeEnd, timeStart, measures2]);
|
377
427
|
useEffect(() => {
|
378
428
|
const loadZoomPlugin = async () => {
|
379
429
|
const zoomPlugin = (await import('chartjs-plugin-zoom')).default;
|
@@ -384,8 +434,24 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
|
|
384
434
|
const activePoint = chart.tooltip._active[0];
|
385
435
|
const ctx = chart.ctx;
|
386
436
|
const x = activePoint.element.x;
|
387
|
-
|
388
|
-
|
437
|
+
let topY1 = 0;
|
438
|
+
let topY2 = 0;
|
439
|
+
let bottomY1 = 0;
|
440
|
+
let bottomY2 = 0;
|
441
|
+
if (chart.scales.y1?.top) {
|
442
|
+
topY1 = chart.scales.y1.top;
|
443
|
+
}
|
444
|
+
if (chart.scales.y2?.top) {
|
445
|
+
topY2 = chart.scales.y2.top;
|
446
|
+
}
|
447
|
+
if (chart.scales.y1?.bottom) {
|
448
|
+
bottomY1 = chart.scales.y1.bottom;
|
449
|
+
}
|
450
|
+
if (chart.scales.y2?.bottom) {
|
451
|
+
bottomY2 = chart.scales.y2.bottom;
|
452
|
+
}
|
453
|
+
const topY = Math.max(topY1, topY2);
|
454
|
+
const bottomY = Math.min(bottomY1, bottomY2);
|
389
455
|
ctx.save();
|
390
456
|
ctx.beginPath();
|
391
457
|
ctx.moveTo(x, topY);
|
@@ -423,7 +489,7 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
|
|
423
489
|
//@ts-ignore
|
424
490
|
, {
|
425
491
|
//@ts-ignore
|
426
|
-
ref: csvLinkRef, data: csvData, filename: `export_${moment().toISOString()}.csv`, separator: ';' })] })), zoomed && (_jsxs(Button, { sx: { minWidth: '40px !important', boxShadow: 1, px: 1 }, variant: "contained", color: "secondary", size: 'small', onClick: () => {
|
492
|
+
ref: csvLinkRef, data: csvData, filename: `export_${moment().toISOString()}.csv`, separator: ';' })] })), zoomed && measures.length > 0 && (_jsxs(Button, { sx: { minWidth: '40px !important', boxShadow: 1, px: 1 }, variant: "contained", color: "secondary", size: 'small', onClick: () => {
|
427
493
|
setChartLoading(true);
|
428
494
|
setZoomed(false);
|
429
495
|
if (chartPeriod === "ALL") {
|
@@ -445,9 +511,11 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
|
|
445
511
|
: moment().subtract(1, 'day').unix());
|
446
512
|
setTimeEnd(moment().unix());
|
447
513
|
}
|
448
|
-
}, children: [_jsx(ZoomOut, { fontSize: 'small' }), _jsx(Box, { sx: { display: { xs: 'none', lg: 'block' }, ml: { md: 1 } }, children: "Reset" })] }))] }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'flex-end' }, children: [enableDatePicker && _jsx(Box, { sx: { display: { xs: 'none', lg: 'flex' } }, children: datePicker }), _jsxs(ToggleButtonGroup, { color: "primary", value: !datePickerUsed && !zoomed ? chartPeriod : null, exclusive: true, onChange: handleChange, size: "small",
|
449
|
-
|
450
|
-
|
514
|
+
}, children: [_jsx(ZoomOut, { fontSize: 'small' }), _jsx(Box, { sx: { display: { xs: 'none', lg: 'block' }, ml: { md: 1 } }, children: "Reset" })] }))] }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'flex-end' }, children: [enableDatePicker && _jsx(Box, { sx: { display: { xs: 'none', lg: 'flex' } }, children: datePicker }), _jsxs(ToggleButtonGroup, { color: "primary", value: !datePickerUsed && !zoomed ? chartPeriod : null, exclusive: true, onChange: handleChange, size: "small", sx: {
|
515
|
+
'& .MuiToggleButton-root': {
|
516
|
+
color: 'text.primary', fontSize: '0.95rem', fontWeight: 'normal', paddingTop: '6px', paddingBottom: '6px'
|
517
|
+
}
|
518
|
+
}, disabled: chartLoading, children: [_jsx(ToggleButton, { value: "1D", sx: { px: 1 }, children: "1d" }), _jsx(ToggleButton, { value: "1W", sx: { px: 1 }, children: "1w" }), _jsx(ToggleButton, { value: "1M", sx: { px: 1 }, children: "1M" }), _jsx(ToggleButton, { value: "3M", sx: { px: 1 }, children: "3M" }), _jsx(ToggleButton, { value: "6M", sx: { px: 1 }, children: "6M" }), _jsx(ToggleButton, { value: "1Y", sx: { px: 1 }, children: "1Y" }), _jsx(ToggleButton, { value: "ALL", sx: { px: 1 }, children: "ALL" })] }), _jsx(MuiTooltip, { placement: "top", arrow: true, title: "Connect point values", children: _jsx(ToggleButton, { value: "check", color: "primary", size: "small", selected: spanGapsOption, disabled: chartLoading, onChange: () => handleSpanGaps(!spanGapsOption), sx: { ml: 2 }, children: _jsx(TimelineIcon, {}) }) })] })] }), _jsx(Box, { component: 'div', className: "chart-container", sx: { mt: 2, height: '100%' }, children: chartJsLoaded && !chartLoading && typeof window !== 'undefined' ?
|
451
519
|
_jsx(_Fragment, { children: dataMeasures && (dataMeasures.length > 1 || (dataMeasures.length === 1 && dataMeasures[0].data?.length)) ?
|
452
520
|
(_jsx(Line, { options: options, data: {
|
453
521
|
// datasets: dataMeasures || [{ data: [] }]
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@iotready/nextjs-components-library",
|
3
|
-
"version": "1.0.0-
|
3
|
+
"version": "1.0.0-preview13",
|
4
4
|
"main": "index.js",
|
5
5
|
"scripts": {
|
6
6
|
"build": "rm -rf dist && tsc --project tsconfig.build.json && cp package.json dist/",
|
@@ -19,6 +19,7 @@
|
|
19
19
|
"chartjs-adapter-moment": "^1.0.1",
|
20
20
|
"chartjs-plugin-annotation": "^3.1.0",
|
21
21
|
"chartjs-plugin-zoom": "^2.0.1",
|
22
|
+
"csv-parse": "^5.6.0",
|
22
23
|
"firebase": "^10.13.1",
|
23
24
|
"leaflet": "^1.9.4",
|
24
25
|
"leaflet-defaulticon-compatibility": "^0.1.2",
|
@@ -8,6 +8,6 @@ export type InfluxConfig = {
|
|
8
8
|
username: string;
|
9
9
|
password: string;
|
10
10
|
};
|
11
|
-
export declare function
|
12
|
-
export declare function
|
13
|
-
export declare function
|
11
|
+
export declare function getInfluxAlarms(influxConfig: InfluxConfig, fields: string[], limit: number, offset: number, sort: any, deviceID: string, timeStart: number, timeEnd: number): Promise<any>;
|
12
|
+
export declare function getInfluxDataV1(influxConfig: InfluxConfig, field: string, timeStart: number, timeEnd: number, deviceID: string, timeGroup: string, raw: boolean, fill?: boolean): Promise<any>;
|
13
|
+
export declare function exportDataV1(influxConfig: InfluxConfig, field: string, timeStart: number, timeEnd: number, deviceID: string): Promise<any>;
|
package/server-actions/influx.js
CHANGED
@@ -1,44 +1,83 @@
|
|
1
1
|
"use server";
|
2
|
+
import { parse } from "csv-parse";
|
2
3
|
import moment from "moment";
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
export async function getInfluxAlarms(influxConfig, fields, limit, offset, sort, deviceID, timeStart, timeEnd) {
|
5
|
+
const conditions = fields
|
6
|
+
.map((field) => `r["valueName"] == "${field}"`)
|
7
|
+
.join(" or ");
|
8
|
+
let query = `
|
9
|
+
import "contrib/tomhollingworth/events"
|
10
|
+
from(bucket: "${influxConfig.bucket}")`;
|
11
|
+
if (timeStart && timeEnd) {
|
12
|
+
query += ` |> range(start: ${timeStart}, stop: ${timeEnd})`;
|
13
|
+
}
|
14
|
+
query += `
|
15
|
+
|> filter(fn: (r) => r["_measurement"] == "${influxConfig.measurement}" and r["deviceid"] == "${deviceID}" and (${conditions}))
|
16
|
+
`;
|
17
|
+
const queryCount = `${query} |> group() |> count()`;
|
18
|
+
query += `
|
19
|
+
|> sort(columns: ["_time"]) // Ordina gli eventi cronologicamente
|
20
|
+
|> group(columns: ["valueName"]) // Raggruppa per il tag
|
21
|
+
|> events.duration(unit: 1s, stop: 2020-01-02T00:00:00Z)
|
22
|
+
|> keep(columns: ["_time", "valueName", "_value", "duration"])
|
23
|
+
|> group() // Raggruppa tutti i dati in un unico gruppo
|
12
24
|
`;
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
25
|
+
if (sort && sort.field === "time" && sort.sort === "desc") {
|
26
|
+
query += ` |> sort(columns: ["_time"], desc: true)`;
|
27
|
+
}
|
28
|
+
else {
|
29
|
+
query += ` |> sort(columns: ["_time"])`;
|
30
|
+
}
|
31
|
+
query += ` |> limit(n:${limit}, offset:${offset})`;
|
32
|
+
const responses = await Promise.all([
|
33
|
+
fetch(encodeURI(`${influxConfig.url}/api/v2/query?org=${influxConfig.orgId}`), {
|
34
|
+
method: "POST",
|
35
|
+
headers: {
|
36
|
+
Authorization: `Token ${influxConfig.accessToken}`,
|
37
|
+
"Content-Type": "application/json"
|
38
|
+
},
|
39
|
+
body: JSON.stringify({
|
40
|
+
query: query,
|
41
|
+
type: "flux"
|
42
|
+
})
|
43
|
+
}),
|
44
|
+
fetch(encodeURI(`${influxConfig.url}/api/v2/query?org=${influxConfig.orgId}`), {
|
45
|
+
method: "POST",
|
46
|
+
headers: {
|
47
|
+
Authorization: `Token ${influxConfig.accessToken}`,
|
48
|
+
"Content-Type": "application/json"
|
49
|
+
},
|
50
|
+
body: JSON.stringify({
|
51
|
+
query: queryCount,
|
52
|
+
type: "flux"
|
53
|
+
})
|
54
|
+
})
|
55
|
+
]);
|
56
|
+
if (!responses[0].ok) {
|
57
|
+
throw new Error(`Failed to fetch data from InfluxDB: ${responses[0].statusText}`);
|
36
58
|
}
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
59
|
+
const data = await responses[0].text();
|
60
|
+
const count = await responses[1].text();
|
61
|
+
const rows = [];
|
62
|
+
const parsedData = parse(data.trim(), { columns: true });
|
63
|
+
parsedData.forEach((row) => {
|
64
|
+
rows.push({
|
65
|
+
time: row["_time"],
|
66
|
+
duration: parseInt(row["duration"], 10),
|
67
|
+
value: parseInt(row["_value"], 10),
|
68
|
+
valueName: row["valueName"]
|
69
|
+
});
|
70
|
+
});
|
71
|
+
const parsedCount = count.split("\n")[1].split(",");
|
72
|
+
let countData = parsedCount[5];
|
73
|
+
return {
|
74
|
+
data: rows,
|
75
|
+
count: countData
|
76
|
+
};
|
77
|
+
}
|
78
|
+
export async function getInfluxDataV1(influxConfig, field, timeStart, timeEnd, deviceID, timeGroup, raw, fill) {
|
41
79
|
let query;
|
80
|
+
let preStartValue = null;
|
42
81
|
if (raw) {
|
43
82
|
query = `SELECT ("value") FROM "${influxConfig.measurement}" WHERE "deviceid" = '${deviceID}' AND "valueName" = '${field}' AND time >= '${moment
|
44
83
|
.unix(timeStart)
|
@@ -51,13 +90,28 @@ export async function getInfluxDataV1(influxConfig, field, timeStart, timeEnd, d
|
|
51
90
|
.unix(timeEnd)
|
52
91
|
.toISOString()}' GROUP BY time(${timeGroup}) fill(null)`;
|
53
92
|
}
|
93
|
+
if (fill) {
|
94
|
+
// Query to get the last data point before timeStart
|
95
|
+
const preStartQuery = `SELECT last("value") FROM "${influxConfig.measurement}" WHERE "deviceid" = '${deviceID}' AND "valueName" = '${field}' AND time < '${moment
|
96
|
+
.unix(timeStart)
|
97
|
+
.toISOString()}'`;
|
98
|
+
const preStartResponse = await fetch(encodeURI(`${influxConfig.url}/query?db=${influxConfig.dbName}&epoch=s&q=${preStartQuery}`), {
|
99
|
+
headers: {
|
100
|
+
Authorization: `Basic ${btoa(`${influxConfig.username}:${influxConfig.password}`)}`
|
101
|
+
}
|
102
|
+
});
|
103
|
+
if (!preStartResponse.ok) {
|
104
|
+
throw new Error(`Failed to fetch pre-start data from InfluxDB: ${preStartResponse.statusText}`);
|
105
|
+
}
|
106
|
+
const preStartData = await preStartResponse.json();
|
107
|
+
preStartValue = preStartData.results[0].series?.[0]?.values?.[0]?.[1];
|
108
|
+
}
|
54
109
|
const response = await fetch(encodeURI(`${influxConfig.url}/query?db=${influxConfig.dbName}&epoch=s&q=${query}`), {
|
55
110
|
headers: {
|
56
111
|
Authorization: `Basic ${btoa(`${influxConfig.username}:${influxConfig.password}`)}`
|
57
112
|
}
|
58
113
|
});
|
59
114
|
if (!response.ok) {
|
60
|
-
console.log(response);
|
61
115
|
throw new Error(`Failed to fetch data from InfluxDB: ${response.statusText}`);
|
62
116
|
}
|
63
117
|
const data = await response.json();
|
@@ -72,61 +126,32 @@ export async function getInfluxDataV1(influxConfig, field, timeStart, timeEnd, d
|
|
72
126
|
}
|
73
127
|
];
|
74
128
|
}
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
const conditions = fields
|
86
|
-
.map((field) => `"valueName" = '${field}'`)
|
87
|
-
.join(" OR ");
|
88
|
-
let queryCount = `SELECT count(*) FROM "${influxConfig.measurement}" WHERE "deviceid" = '${deviceID}' AND (${conditions})`;
|
89
|
-
let queryPagination = `SELECT "valueName", "value" FROM "${influxConfig.measurement}" WHERE "deviceid" = '${deviceID}' AND (${conditions})`;
|
90
|
-
if (timeStart) {
|
91
|
-
queryCount += ` AND time >= '${moment.unix(timeStart).toISOString()}'`;
|
92
|
-
queryPagination += ` AND time >= '${moment
|
93
|
-
.unix(timeStart)
|
94
|
-
.toISOString()}'`;
|
95
|
-
}
|
96
|
-
if (timeEnd) {
|
97
|
-
queryCount += ` AND time <= '${moment.unix(timeEnd).toISOString()}'`;
|
98
|
-
queryPagination += ` AND time <= '${moment.unix(timeEnd).toISOString()}'`;
|
99
|
-
}
|
100
|
-
if (sort && sort.field === "time") {
|
101
|
-
queryPagination = `${queryPagination} ORDER BY "${sort.field}" ${sort.sort}`;
|
102
|
-
}
|
103
|
-
queryPagination = `${queryPagination} LIMIT ${limit} OFFSET ${offset}`;
|
104
|
-
const responses = await Promise.all([
|
105
|
-
fetch(encodeURI(`${influxConfig.url}/query?db=${influxConfig.dbName}&epoch=s&q=${queryPagination}`), {
|
106
|
-
headers: {
|
107
|
-
Authorization: `Basic ${btoa(`${influxConfig.username}:${influxConfig.password}`)}`
|
108
|
-
}
|
109
|
-
}),
|
110
|
-
fetch(encodeURI(`${influxConfig.url}/query?db=${influxConfig.dbName}&epoch=s&q=${queryCount}`), {
|
111
|
-
headers: {
|
112
|
-
Authorization: `Basic ${btoa(`${influxConfig.username}:${influxConfig.password}`)}`
|
113
|
-
}
|
114
|
-
})
|
129
|
+
// Always override the name to be the field
|
130
|
+
data.results[0].series.forEach((series) => {
|
131
|
+
series.name = field; // Force the series name to be the field name
|
132
|
+
});
|
133
|
+
// 1000000 REMOVED AND ADDED TO MOVE THE POINT AWAY IN THE CHART
|
134
|
+
if (raw && preStartValue !== null && preStartValue !== undefined) {
|
135
|
+
// Insert the pre-start value at the beginning of the dataset
|
136
|
+
data.results[0].series[0].values.unshift([
|
137
|
+
timeStart - 1000000,
|
138
|
+
preStartValue
|
115
139
|
]);
|
116
|
-
if (!responses[0].ok) {
|
117
|
-
throw new Error(`Failed to fetch data from InfluxDB: ${responses[0].statusText}`);
|
118
|
-
}
|
119
|
-
const data = await responses[0].json();
|
120
|
-
const count = await responses[1].json();
|
121
|
-
return { data, count };
|
122
|
-
}
|
123
|
-
else {
|
124
|
-
return null;
|
125
140
|
}
|
141
|
+
// Set the last point as the timeEnd and last value of the dataset
|
142
|
+
const lastSeries = data.results[0].series[0];
|
143
|
+
const lastValue = lastSeries?.values &&
|
144
|
+
lastSeries.values.length > 0 &&
|
145
|
+
lastSeries.values.slice(-1)[0].length > 1
|
146
|
+
? lastSeries.values.slice(-1)[0][1]
|
147
|
+
: null;
|
148
|
+
data.results[0].series[0].values.push([timeEnd + 1000000, lastValue]);
|
149
|
+
return data;
|
126
150
|
}
|
127
|
-
export async function
|
128
|
-
|
129
|
-
|
151
|
+
export async function exportDataV1(influxConfig, field, timeStart, timeEnd, deviceID) {
|
152
|
+
const query = `SELECT ("value") FROM "${influxConfig.measurement}" WHERE "deviceid" = '${deviceID}' AND "valueName" = '${field}' AND time >= '${moment
|
153
|
+
.unix(timeStart)
|
154
|
+
.toISOString()}' AND time <= '${moment.unix(timeEnd).toISOString()}'`;
|
130
155
|
const response = await fetch(encodeURI(`${influxConfig.url}/query?db=${influxConfig.dbName}&epoch=s&q=${query}`), {
|
131
156
|
headers: {
|
132
157
|
Authorization: `Basic ${btoa(`${influxConfig.username}:${influxConfig.password}`)}`
|
@@ -136,10 +161,22 @@ export async function getFirstTimestamp(influxConfig, deviceID) {
|
|
136
161
|
throw new Error(`Failed to fetch data from InfluxDB: ${response.statusText}`);
|
137
162
|
}
|
138
163
|
const data = await response.json();
|
139
|
-
//
|
140
|
-
if (data
|
141
|
-
//
|
142
|
-
|
164
|
+
// Ensure the name is manually set to the field
|
165
|
+
if (!data.results[0].series) {
|
166
|
+
// Set default value with null time and null value
|
167
|
+
data.results[0].series = [
|
168
|
+
{
|
169
|
+
name: field, // Manually set the series name as the field
|
170
|
+
columns: ["time", "value"],
|
171
|
+
values: [] // Set null point for time and value
|
172
|
+
}
|
173
|
+
];
|
174
|
+
}
|
175
|
+
else {
|
176
|
+
// Always override the name to be the field
|
177
|
+
data.results[0].series.forEach((series) => {
|
178
|
+
series.name = field; // Force the series name to be the field name
|
179
|
+
});
|
143
180
|
}
|
144
|
-
return
|
181
|
+
return data;
|
145
182
|
}
|