Skip to content

Commit

Permalink
Fix #114 - when downloading, return empty csv file (with headers) whe…
Browse files Browse the repository at this point in the history
…n no results
  • Loading branch information
timhunt committed Dec 13, 2023
1 parent a291508 commit ce26f60
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 3 deletions.
2 changes: 1 addition & 1 deletion lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
22 changes: 21 additions & 1 deletion locallib.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,29 @@ 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);

$sql = report_customsql_prepare_sql($report, $timenow);

$queryparams = !empty($report->queryparams) ? unserialize($report->queryparams) : [];
$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);

Expand All @@ -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) {
Expand Down
25 changes: 24 additions & 1 deletion tests/behat/behat_report_customsql.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

use Behat\Gherkin\Node\PyStringNode as PyStringNode;
use Behat\Gherkin\Node\TableNode;

use Behat\Mink\Exception\ExpectationException;

/**
* Behat steps for the the custom SQL report.
Expand Down Expand Up @@ -248,6 +248,29 @@ 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<REPORT_NAME>[^"]*)" returns a file with headers "([^"]*)"$/
* @param string $reportname the name of the report to go to.
* @param string $headers the headers that shuold be returned.
*/
public function downloading_custom_sql_report_x_returns_a_file_with_headers(string $reportname, string $headers) {
$report = $this->get_report_by_name($reportname);
$url = new \moodle_url('/pluginfile.php/1/report_customsql/download/' . $report->id, ['dataformat' => 'csv']);

$session = $this->getSession()->getCookie('MoodleSession');
$filecontent = trim(download_file_content($url, ['Cookie' => 'MoodleSession=' . $session]));
$filecontent = core_text::trim_utf8_bom($filecontent);
if ($filecontent != $headers) {
throw new ExpectationException(
"File headers: $filecontent did not match expected: $headers", $this->getSession());
}
}

/**
* Find a report by name and get all the details.
*
Expand Down
7 changes: 7 additions & 0 deletions tests/behat/report_customsql.feature
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ Feature: Ad-hoc database queries report
And I view the "Test query" custom sql report
Then I should see "This query did not return any data."

Scenario: Download an Ad-hoc database query that returns no data but includes headers
Given the following custom sql report exists:
| name | Test query |
| querysql | SELECT * FROM {config} WHERE name = '-1' |
When I log in as "admin"
Then downloading custom sql report "Test query" returns a file with headers "id,name,value"

Scenario: Create an Ad-hoc database queries category
When I log in as "admin"
And I navigate to "Reports > Ad-hoc database queries" in site administration
Expand Down

0 comments on commit ce26f60

Please sign in to comment.