diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2634621..d73dd5d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,7 +23,7 @@ jobs:
services:
postgres:
- image: postgres:10
+ image: postgres:12
env:
POSTGRES_USER: 'postgres'
POSTGRES_HOST_AUTH_METHOD: 'trust'
diff --git a/db/install.xml b/db/install.xml
old mode 100644
new mode 100755
index de5c328..909c24a
--- a/db/install.xml
+++ b/db/install.xml
@@ -1,5 +1,5 @@
-
Are you really sure you want to delete this category?
'; $string['deletereportx'] = 'Delete query \'{$a}\''; $string['description'] = 'Description'; +$string['directorynotwritable'] = 'The directory you provided is not writable.'; $string['displayname'] = 'Query name'; $string['displaynamex'] = 'Query name: {$a}'; $string['displaynamerequired'] = 'You must enter a query name'; diff --git a/lib.php b/lib.php index 21013e6..8294dbf 100644 --- a/lib.php +++ b/lib.php @@ -89,7 +89,7 @@ function report_customsql_pluginfile($course, $cm, $context, $filearea, $args, $ if ($report->runable !== 'manual') { $runtime = $report->lastrun; } - $csvtimestamp = \report_customsql_generate_csv($report, $runtime); + $csvtimestamp = \report_customsql_generate_csv($report, $runtime, true); } list($csvfilename) = report_customsql_csv_filename($report, $csvtimestamp); diff --git a/locallib.php b/locallib.php index cd9fec4..423718b 100644 --- a/locallib.php +++ b/locallib.php @@ -96,7 +96,14 @@ function report_customsql_get_element_type($name) { return 'text'; } -function report_customsql_generate_csv($report, $timenow) { +/** + * Generate customsql csv file. + * + * @param stdclass $report report record from customsql table. + * @param int $timetimenow unix timestamp - usually "now()" + * @param bool $returnheaderwhenempty if true, a CSV file with headers will always be generated, even if there are no results. + */ +function report_customsql_generate_csv($report, $timenow, $returnheaderwhenempty = false) { global $DB; $starttime = microtime(true); @@ -104,6 +111,14 @@ function report_customsql_generate_csv($report, $timenow) { $queryparams = !empty($report->queryparams) ? unserialize($report->queryparams) : array(); $querylimit = $report->querylimit ?? get_config('report_customsql', 'querylimitdefault'); + if ($returnheaderwhenempty) { + // We want the export to always generate a CSV file so we modify the query slightly + // to generate an extra "null" values row, so we can get the column names, + // then we ignore rows that contain null records in every row when generating the csv. + $sql = "SELECT subq.* + FROM (SELECT 1) as ignoreme + LEFT JOIN ($sql) as subq on true"; + } // Query one extra row, so we can tell if we hit the limit. $rs = report_customsql_execute_query($sql, $queryparams, $querylimit + 1); @@ -124,6 +139,11 @@ function report_customsql_generate_csv($report, $timenow) { } $data = get_object_vars($row); + + if ($returnheaderwhenempty && array_unique(array_values($data)) === [null]) { + // This is a row with all null values - ignore it. + continue; + } foreach ($data as $name => $value) { if (report_customsql_get_element_type($name) == 'date_time_selector' && report_customsql_is_integer($value) && $value > 0) { @@ -146,6 +166,7 @@ function report_customsql_generate_csv($report, $timenow) { fclose($handle); } + // Update the execution time in the DB. $updaterecord = new stdClass(); $updaterecord->id = $report->id; diff --git a/tests/behat/behat_report_customsql.php b/tests/behat/behat_report_customsql.php index 70a09cc..e02c23e 100644 --- a/tests/behat/behat_report_customsql.php +++ b/tests/behat/behat_report_customsql.php @@ -238,6 +238,28 @@ public function adhoc_database_queries_thinks_the_time_is($time) { set_config('behat_fixed_time', $value, 'report_customsql'); } + /** + * Simulates downloading an empty report to ensure it shows table headers. + * + * For example: + * When downloading the empty custom sql report "Frog" it contains the headers "frogname,freddy" + * + * @Then /^downloading custom sql report "(?P