@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, measures, enableExportData, enableDatePicker, handleGetInfluxData, theme }: {
10
+ declare const TrendChart: ({ deviceId, measures1, measures2, enableDatePicker, handleGetInfluxData, handleExportDataCB, theme }: {
11
11
  deviceId: string;
12
- measures: Array<Measure>;
13
- enableExportData: boolean;
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, measures, enableExportData, enableDatePicker, handleGetInfluxData, theme }) => {
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
- y: {
224
- ...options.scales.y,
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
- const data = await Promise.all(measures.map(async (measure) => {
262
- return await handleGetInfluxData(measure.name, timeStart, timeEnd, deviceId, "0s", true);
263
- }));
264
- const csvData = getCsvData(data, measures);
265
- setCsvData(csvData);
266
- setLoadingButton(false);
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
- // Inizializza un array di promesse per ottenere i dati per ciascuna misura
277
- const datasetsPromises = measures.map(async (measure) => {
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 min = null;
297
- let max = null;
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 (min === null || datasetMin < min)
308
- min = datasetMin;
309
- if (max === null || datasetMax > max)
310
- max = datasetMax;
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
- let paddedMin = null;
315
- let paddedMax = null;
316
- if (min !== null && max !== null) {
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
- paddedMin = Math.floor((min - diff) * 10) / 10;
319
- paddedMax = Math.ceil((max + diff) * 10) / 10;
320
- }
321
- setDataMeasures([...datasets]);
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.length || (prevMeasuresValue[0] && prevMeasuresValue[0].name !== measures[0].name)) {
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
- }, [measures, timeEnd, timeStart]);
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
- const topY = chart.scales.y.top;
388
- const bottomY = chart.scales.y.bottom;
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
- // sx={{ boxShadow: 1 }}
450
- 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' ?
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-preview12",
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 getInfluxDataV1(influxConfig: InfluxConfig, field: string, timeStart: number, timeEnd: number, deviceID: string, timeGroup: string, raw: boolean): Promise<any>;
12
- export declare function getManyMeasuresV1(influxConfig: InfluxConfig, fields: string[], limit: number, offset: number, sort: any, deviceID: string, timeStart?: number, timeEnd?: number): Promise<any>;
13
- export declare function getFirstTimestamp(influxConfig: InfluxConfig, deviceID: string): Promise<any>;
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>;
@@ -1,44 +1,83 @@
1
1
  "use server";
2
+ import { parse } from "csv-parse";
2
3
  import moment from "moment";
3
- /* export async function getInfluxDataV2(influxConfig: InfluxConfig, field: string, timeStart: string, timeEnd: string, deviceID: string, timeGroup: string, raw: boolean): Promise<Point[]> {
4
- const query = `
5
- from(bucket: "${influxConfig.bucket}")
6
- |> range(start: ${timeStart}, stop: ${timeEnd})
7
- |> filter(fn: (r) => r._measurement == "${influxConfig.measurement}")
8
- |> filter(fn: (r) => r["_field"] == "${field}")
9
- |> filter(fn: (r) => r["deviceid"] == "${deviceID}")
10
- |> aggregateWindow(every: ${timeGroup}m, fn: last, createEmpty: false)
11
- |> yield(name: "last")
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
- const response = await fetch(`${influxConfig.url}/api/v2/query?org=${influxConfig.orgId}`, {
15
- method: 'POST',
16
- headers: {
17
- 'Content-Type': 'application/json',
18
- 'Authorization': `Token ${influxConfig.accessToken}`,
19
- 'Accept': 'application/csv'
20
- },
21
- body: JSON.stringify({
22
- query: query
23
- })
24
- });
25
- if (!response.ok) {
26
- throw new Error(`Failed to fetch data from InfluxDB: ${response.statusText}`);
27
- }
28
- const data = await response.text();
29
- return data.split('\n').map(line => {
30
- const row = line.split(',');
31
- const timestamp = row[5];
32
- const value = parseFloat(row[6]);
33
- console.log(timestamp, value);
34
- if (isNaN(value)) {
35
- return null;
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
- return { x: new Date(timestamp).getTime(), y: value };
38
- }).filter(point => point !== null);
39
- }*/ // NOT WORKING, NEED TO FIX
40
- export async function getInfluxDataV1(influxConfig, field, timeStart, timeEnd, deviceID, timeGroup, raw) {
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
- else {
76
- // Always override the name to be the field
77
- data.results[0].series.forEach((series) => {
78
- series.name = field; // Force the series name to be the field name
79
- });
80
- }
81
- return data;
82
- }
83
- export async function getManyMeasuresV1(influxConfig, fields, limit, offset, sort, deviceID, timeStart, timeEnd) {
84
- if (fields.length > 0) {
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 getFirstTimestamp(influxConfig, deviceID) {
128
- // Query per ottenere il primo timestamp e il valore ordinato per "time" in modo crescente
129
- const query = `SELECT "time", "value" FROM "${influxConfig.measurement}" WHERE "deviceid" = '${deviceID}' ORDER BY time ASC LIMIT 1`;
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
- // Verifica che ci siano risultati nella risposta
140
- if (data?.results[0]?.series && data?.results[0]?.series[0]?.values[0]?.[0]) {
141
- // Ritorna il primo timestamp
142
- return data.results[0].series[0].values[0][0];
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 null; // Se non ci sono record, ritorna null
181
+ return data;
145
182
  }