@iotready/nextjs-components-library 1.0.0-preview12 → 1.0.0-preview14

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,14 @@ 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
+ yAxisID: measure.source === 'measures1' ? 'y1' : 'y2'
292
318
  };
293
319
  });
294
- // Risolvi tutte le promesse per popolare i dataset
295
320
  const datasets = await Promise.all(datasetsPromises);
296
- let min = null;
297
- let max = null;
321
+ let min1 = null;
322
+ let max1 = null;
323
+ let min2 = null;
324
+ let max2 = null;
298
325
  let time;
299
326
  let minTime = null;
300
327
  datasets.forEach(dataset => {
@@ -304,37 +331,59 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
304
331
  }
305
332
  const datasetMin = Math.min(...values);
306
333
  const datasetMax = Math.max(...values);
307
- if (min === null || datasetMin < min)
308
- min = datasetMin;
309
- if (max === null || datasetMax > max)
310
- max = datasetMax;
334
+ if (dataset.yAxisID === 'y1') {
335
+ if (min1 === null || datasetMin < min1)
336
+ min1 = datasetMin;
337
+ if (max1 === null || datasetMax > max1)
338
+ max1 = datasetMax;
339
+ }
340
+ else {
341
+ if (min2 === null || datasetMin < min2)
342
+ min2 = datasetMin;
343
+ if (max2 === null || datasetMax > max2)
344
+ max2 = datasetMax;
345
+ }
311
346
  if (time && (minTime === null || time.unix() < minTime))
312
347
  minTime = time.unix() - 86400;
313
348
  });
314
- let paddedMin = null;
315
- let paddedMax = null;
316
- if (min !== null && max !== null) {
349
+ const getPaddedMinMax = (min, max) => {
350
+ if (min === null || max === null)
351
+ return { paddedMin: null, paddedMax: null };
317
352
  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]);
353
+ return {
354
+ paddedMin: Math.floor((min - diff) * 10) / 10,
355
+ paddedMax: Math.ceil((max + diff) * 10) / 10
356
+ };
357
+ };
358
+ const { paddedMin: paddedMin1, paddedMax: paddedMax1 } = getPaddedMinMax(min1, max1);
359
+ const { paddedMin: paddedMin2, paddedMax: paddedMax2 } = getPaddedMinMax(min2, max2);
360
+ setDataMeasures(datasets);
322
361
  setTimeStartPicker(minTime || timeStart);
362
+ const scalesOptions = {
363
+ ...options.scales,
364
+ y1: {
365
+ ...options.scales.y1,
366
+ min: paddedMin1,
367
+ max: paddedMax1,
368
+ position: "left",
369
+ display: "auto"
370
+ },
371
+ y2: {
372
+ ...options.scales.y2,
373
+ min: paddedMin2,
374
+ max: paddedMax2,
375
+ position: "right",
376
+ display: "auto"
377
+ },
378
+ x: {
379
+ ...options.scales.x,
380
+ min: moment.unix(minTime || timeStart).toString(),
381
+ max: moment.unix(timeEnd).toString()
382
+ }
383
+ };
323
384
  setOptions({
324
385
  ...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
- }
386
+ scales: scalesOptions
338
387
  });
339
388
  };
340
389
  useEffect(() => {
@@ -367,13 +416,13 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
367
416
  const prevMeasuresValue = prevMeasures.current || [];
368
417
  //@ts-ignore
369
418
  prevMeasures.current = measures;
370
- if (!prevMeasuresValue || prevMeasuresValue.length !== measures.length || (prevMeasuresValue[0] && prevMeasuresValue[0].name !== measures[0].name)) {
419
+ if (!prevMeasuresValue || prevMeasuresValue.length !== measures?.length || (prevMeasuresValue[0] && prevMeasuresValue[0].name !== measures[0].name)) {
371
420
  setChartLoading(true);
372
421
  }
373
422
  loadDatasets(chartPeriod).then(() => {
374
423
  setChartLoading(false);
375
424
  });
376
- }, [measures, timeEnd, timeStart]);
425
+ }, [measures1, timeEnd, timeStart, measures2]);
377
426
  useEffect(() => {
378
427
  const loadZoomPlugin = async () => {
379
428
  const zoomPlugin = (await import('chartjs-plugin-zoom')).default;
@@ -384,8 +433,24 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
384
433
  const activePoint = chart.tooltip._active[0];
385
434
  const ctx = chart.ctx;
386
435
  const x = activePoint.element.x;
387
- const topY = chart.scales.y.top;
388
- const bottomY = chart.scales.y.bottom;
436
+ let topY1 = 0;
437
+ let topY2 = 0;
438
+ let bottomY1 = 0;
439
+ let bottomY2 = 0;
440
+ if (chart.scales.y1?.top) {
441
+ topY1 = chart.scales.y1.top;
442
+ }
443
+ if (chart.scales.y2?.top) {
444
+ topY2 = chart.scales.y2.top;
445
+ }
446
+ if (chart.scales.y1?.bottom) {
447
+ bottomY1 = chart.scales.y1.bottom;
448
+ }
449
+ if (chart.scales.y2?.bottom) {
450
+ bottomY2 = chart.scales.y2.bottom;
451
+ }
452
+ const topY = Math.max(topY1, topY2);
453
+ const bottomY = Math.min(bottomY1, bottomY2);
389
454
  ctx.save();
390
455
  ctx.beginPath();
391
456
  ctx.moveTo(x, topY);
@@ -423,7 +488,7 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
423
488
  //@ts-ignore
424
489
  , {
425
490
  //@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: () => {
491
+ 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
492
  setChartLoading(true);
428
493
  setZoomed(false);
429
494
  if (chartPeriod === "ALL") {
@@ -445,9 +510,11 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
445
510
  : moment().subtract(1, 'day').unix());
446
511
  setTimeEnd(moment().unix());
447
512
  }
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' ?
513
+ }, 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: {
514
+ '& .MuiToggleButton-root': {
515
+ color: 'text.primary', fontSize: '0.95rem', fontWeight: 'normal', paddingTop: '6px', paddingBottom: '6px'
516
+ }
517
+ }, 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
518
  _jsx(_Fragment, { children: dataMeasures && (dataMeasures.length > 1 || (dataMeasures.length === 1 && dataMeasures[0].data?.length)) ?
452
519
  (_jsx(Line, { options: options, data: {
453
520
  // 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-preview14",
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)
@@ -49,7 +88,29 @@ export async function getInfluxDataV1(influxConfig, field, timeStart, timeEnd, d
49
88
  .unix(timeStart)
50
89
  .toISOString()}' AND time <= '${moment
51
90
  .unix(timeEnd)
52
- .toISOString()}' GROUP BY time(${timeGroup}) fill(null)`;
91
+ .toISOString()}' GROUP BY time(${timeGroup})`;
92
+ if (fill) {
93
+ query += ` fill(none)`;
94
+ }
95
+ else {
96
+ query += ` fill(null)`;
97
+ }
98
+ }
99
+ if (fill) {
100
+ // Query to get the last data point before timeStart
101
+ const preStartQuery = `SELECT last("value") FROM "${influxConfig.measurement}" WHERE "deviceid" = '${deviceID}' AND "valueName" = '${field}' AND time < '${moment
102
+ .unix(timeStart)
103
+ .toISOString()}'`;
104
+ const preStartResponse = await fetch(encodeURI(`${influxConfig.url}/query?db=${influxConfig.dbName}&epoch=s&q=${preStartQuery}`), {
105
+ headers: {
106
+ Authorization: `Basic ${btoa(`${influxConfig.username}:${influxConfig.password}`)}`
107
+ }
108
+ });
109
+ if (!preStartResponse.ok) {
110
+ throw new Error(`Failed to fetch pre-start data from InfluxDB: ${preStartResponse.statusText}`);
111
+ }
112
+ const preStartData = await preStartResponse.json();
113
+ preStartValue = preStartData.results[0].series?.[0]?.values?.[0]?.[1];
53
114
  }
54
115
  const response = await fetch(encodeURI(`${influxConfig.url}/query?db=${influxConfig.dbName}&epoch=s&q=${query}`), {
55
116
  headers: {
@@ -57,7 +118,6 @@ export async function getInfluxDataV1(influxConfig, field, timeStart, timeEnd, d
57
118
  }
58
119
  });
59
120
  if (!response.ok) {
60
- console.log(response);
61
121
  throw new Error(`Failed to fetch data from InfluxDB: ${response.statusText}`);
62
122
  }
63
123
  const data = await response.json();
@@ -72,61 +132,34 @@ export async function getInfluxDataV1(influxConfig, field, timeStart, timeEnd, d
72
132
  }
73
133
  ];
74
134
  }
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
- })
115
- ]);
116
- if (!responses[0].ok) {
117
- throw new Error(`Failed to fetch data from InfluxDB: ${responses[0].statusText}`);
135
+ // Always override the name to be the field
136
+ data.results[0].series.forEach((series) => {
137
+ series.name = field; // Force the series name to be the field name
138
+ });
139
+ // 1000000 REMOVED AND ADDED TO MOVE THE POINT AWAY IN THE CHART
140
+ if (fill) {
141
+ if (preStartValue !== null && preStartValue !== undefined) {
142
+ // Insert the pre-start value at the beginning of the dataset
143
+ data.results[0].series[0].values.unshift([
144
+ timeStart - 1000000,
145
+ preStartValue
146
+ ]);
118
147
  }
119
- const data = await responses[0].json();
120
- const count = await responses[1].json();
121
- return { data, count };
122
- }
123
- else {
124
- return null;
148
+ // Set the last point as the timeEnd and last value of the dataset
149
+ const lastSeries = data.results[0].series[0];
150
+ const lastValue = lastSeries?.values &&
151
+ lastSeries.values.length > 0 &&
152
+ lastSeries.values.slice(-1)[0].length > 1
153
+ ? lastSeries.values.slice(-1)[0][1]
154
+ : null;
155
+ data.results[0].series[0].values.push([timeEnd + 1000000, lastValue]);
125
156
  }
157
+ return data;
126
158
  }
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`;
159
+ export async function exportDataV1(influxConfig, field, timeStart, timeEnd, deviceID) {
160
+ const query = `SELECT ("value") FROM "${influxConfig.measurement}" WHERE "deviceid" = '${deviceID}' AND "valueName" = '${field}' AND time >= '${moment
161
+ .unix(timeStart)
162
+ .toISOString()}' AND time <= '${moment.unix(timeEnd).toISOString()}'`;
130
163
  const response = await fetch(encodeURI(`${influxConfig.url}/query?db=${influxConfig.dbName}&epoch=s&q=${query}`), {
131
164
  headers: {
132
165
  Authorization: `Basic ${btoa(`${influxConfig.username}:${influxConfig.password}`)}`
@@ -136,10 +169,22 @@ export async function getFirstTimestamp(influxConfig, deviceID) {
136
169
  throw new Error(`Failed to fetch data from InfluxDB: ${response.statusText}`);
137
170
  }
138
171
  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];
172
+ // Ensure the name is manually set to the field
173
+ if (!data.results[0].series) {
174
+ // Set default value with null time and null value
175
+ data.results[0].series = [
176
+ {
177
+ name: field, // Manually set the series name as the field
178
+ columns: ["time", "value"],
179
+ values: [] // Set null point for time and value
180
+ }
181
+ ];
182
+ }
183
+ else {
184
+ // Always override the name to be the field
185
+ data.results[0].series.forEach((series) => {
186
+ series.name = field; // Force the series name to be the field name
187
+ });
143
188
  }
144
- return null; // Se non ci sono record, ritorna null
189
+ return data;
145
190
  }