[Perl] DAQ system for the FAPG.
feat select download probes by checkbox
Replace the probe dropdown with multi-select checkboxes. Export selected probe sets as timestamp-aligned columns.
Changed files
roles/dashboard/lib/FAPG/DAQ/Dashboard/Controller/Download.pm
@@ -21,7 +21,7 @@
21
21
22
22
my $from = $self->param('from') // '';
23
23
my $to = $self->param('to') // '';
24
Removed:
my $probe = $self->param('probe') // '';
24
Added:
my @probes = $self->every_param('probe')->@*;
25
25
my $from_epoch = date_epoch($from);
26
26
my $to_epoch = date_epoch($to);
27
27
my %known = map { $_->{key} => 1 } $self->probes->@*;
@@ -38,11 +38,10 @@
38
38
39
39
return $self->render(
40
40
status => 400,
41
Removed:
text => 'The probe must be blank or a known probe type.',
42
Removed:
) if $probe ne '' && !$known{$probe};
41
Added:
text => 'Every selected probe must be a known probe type.',
42
Added:
) if grep { !$known{$_} } @probes;
43
43
44
Removed:
my $probe_filter = $probe eq '' ? '' : ' AND probe = ?';
45
Removed:
my @probe_bind = $probe eq '' ? () : ($probe);
44
Added:
my $probe_filter = @probes ? ' AND probe IN (' . join( ',', ('?') x @probes ) . ')' : '';
46
45
my $rows = raw_readings(
47
46
$self,
48
47
'COALESCE(received_at, timestamp) >= ? '
@@ -51,14 +50,15 @@
51
50
'COALESCE(received_at, timestamp)',
52
51
utc_timestamp($from_epoch),
53
52
utc_timestamp( $to_epoch + ( 24 * 60 * 60 ) ),
54
Removed:
@probe_bind,
53
Added:
@probes,
55
54
);
55
Added:
my $probe_name = join '-', @probes;
56
56
my $filename
57
Removed:
= $probe eq ''
57
Added:
= !@probes
58
58
? "fapg-daq-readings-$from-to-$to.$format"
59
Removed:
: "fapg-daq-readings-$probe-$from-to-$to.$format";
59
Added:
: "fapg-daq-readings-$probe_name-$from-to-$to.$format";
60
60
61
Removed:
render_download( $self, $rows, $filename, $format, $probe eq '' );
61
Added:
render_download( $self, $rows, $filename, $format, \@probes );
62
62
}
63
63
64
64
sub timeframe_readings ( $self, $format ) {
@@ -98,7 +98,7 @@
98
98
);
99
99
my $filename = "fapg-daq-$probe-$timeframe-$range.$format";
100
100
101
Removed:
render_download( $self, $rows, $filename, $format, 0 );
101
Added:
render_download( $self, $rows, $filename, $format, [$probe] );
102
102
}
103
103
104
104
sub raw_readings ( $self, $where, $order_by, @bind ) {
@@ -121,10 +121,13 @@
121
121
)->hashes->to_array;
122
122
}
123
123
124
Removed:
sub render_download ( $self, $rows, $filename, $format, $all_probes ) {
124
Added:
sub render_download ( $self, $rows, $filename, $format, $selected_probes ) {
125
Added:
my $pivoted = $selected_probes->@* != 1;
125
126
my ( $headers, $table )
126
Removed:
= $all_probes ? pivoted_readings( $self, $rows ) : raw_reading_table($rows);
127
Removed:
my @timestamp_columns = $all_probes ? (0) : ( 0, 1 );
127
Added:
= $pivoted
128
Added:
? pivoted_readings( $self, $rows, $selected_probes )
129
Added:
: raw_reading_table($rows);
130
Added:
my @timestamp_columns = $pivoted ? (0) : ( 0, 1 );
128
131
129
132
return render_xlsx( $self, $headers, $table, $filename, \@timestamp_columns )
130
133
if $format eq 'xlsx';
@@ -148,8 +151,10 @@
148
151
return ( \@headers, \@table );
149
152
}
150
153
151
Removed:
sub pivoted_readings ( $self, $rows ) {
152
Removed:
my @probes = map { $_->{key} } $self->probes->@*;
154
Added:
sub pivoted_readings ( $self, $rows, $selected_probes ) {
155
Added:
my %selected = map { $_ => 1 } $selected_probes->@*;
156
Added:
my @probes = map { $_->{key} }
157
Added:
grep { !%selected || $selected{ $_->{key} } } $self->probes->@*;
153
158
my %values;
154
159
155
160
for my $row ( $rows->@* ) {
roles/dashboard/t/06-downloads.t
@@ -21,9 +21,7 @@
21
21
->element_exists('.navbar-nav a.nav-link.active[href="/downloads"][aria-current="page"]')
22
22
->element_exists('form.downloads-form[action="/downloads/readings.csv"]')
23
23
->element_exists('[data-downloads-error]')
24
Removed:
->text_is(
25
Removed:
'select#download-probe[name="probe"] option[value=""][selected]',
26
Removed:
'All probes' )
24
Added:
->element_exists('[data-download-probes]')
27
25
->element_exists('input[name="date_range"][value="week"][checked]')
28
26
->element_exists(
29
27
'[data-custom-date-fields][hidden] input[name="from"][type="date"]')
@@ -40,7 +38,9 @@
40
38
);
41
39
42
40
for my $probe (qw(ph do orp ec)) {
43
Removed:
$t->element_exists(qq{select#download-probe option[value="$probe"]});
41
Added:
$t->element_exists(
42
Added:
qq{input#download-probe-${probe}[type="checkbox"][name="probe"][value="$probe"]}
43
Added:
);
44
44
}
45
45
46
46
for my $range (qw(today week month all custom)) {
roles/dashboard/t/07-downloads-csv.t
@@ -44,6 +44,11 @@
44
44
->content_like(qr/2026-07-05 12:00:00/)
45
45
->content_unlike(qr/2026-07-05 12:00:15/);
46
46
47
Added:
$t->get_ok('/downloads/readings.csv?from=2026-07-05&to=2026-07-05&probe=ph&probe=do')
48
Added:
->status_is(200)
49
Added:
->content_like(qr/^timestamp,ph,do/m)
50
Added:
->content_unlike(qr/^timestamp,ph,do,orp,ec/m);
51
Added:
47
52
$t->get_ok('/downloads/readings.csv?probe=ph&timeframe=hour&range=1')
48
53
->status_is(200)
49
54
->header_like( 'Content-Disposition' => qr/fapg-daq-ph-hour-1\.csv/ )
roles/dashboard/templates/dashboard/downloads.html.ep
@@ -13,15 +13,18 @@
13
13
<div class="alert alert-danger d-none" role="alert" id="downloads-error" data-downloads-error></div>
14
14
<form action="/downloads/readings.csv" method="get" class="downloads-form d-flex flex-column gap-3">
15
15
16
Removed:
<div>
17
Removed:
<label class="form-label fw-bold" for="download-probe">Probe</label>
18
Removed:
<select class="form-select w-auto" id="download-probe" name="probe">
19
Removed:
<option value="" selected>All probes</option>
16
Added:
<fieldset>
17
Added:
<legend class="fw-bold col-form-label pt-0 mb-2">Probes</legend>
18
Added:
<p class="form-text mt-0">Leave all boxes clear to download every probe.</p>
19
Added:
<div class="d-flex flex-wrap gap-3" data-download-probes>
20
20
% for my $probe ($probes->@*) {
21
Removed:
<option value="<%= $probe->{key} %>"><%= $probe->{label} %></option>
21
Added:
<div class="form-check">
22
Added:
<input class="form-check-input" id="download-probe-<%= $probe->{key} %>" name="probe" type="checkbox" value="<%= $probe->{key} %>">
23
Added:
<label class="form-check-label" for="download-probe-<%= $probe->{key} %>"><%= $probe->{label} %></label>
24
Added:
</div>
22
25
% }
23
Removed:
</select>
24
Removed:
</div>
26
Added:
</div>
27
Added:
</fieldset>
25
28
26
29
<fieldset>
27
30
<legend class="fw-bold col-form-label pt-0 mb-2">Date range</legend>