Skip to content

Conversation

@leandroradusky
Copy link
Contributor

@leandroradusky leandroradusky commented Jan 11, 2023

Closes #1820, #1821, #1822 & #1555.

Reporting functioning for all the box purposes, also exporting to PDF and SVG working. I know that we talked on the contrary. Still, I'm submitting one big PR because:

  • it was started from an old version of main and the rebasing plus applying the changes dividing this PR is much more error-prone;
  • the intermediate PRs had code that changes at the end and revising that code will double the work without the necessity
  • the first of the PRs to be submitted before was not much smaller than this one.

Said this... There are three plots & one visualization, which have some differences from the mockups that will be remarked.

  • Confusion matrix, which is displayed on all the visualizations

image

  • Measured signal, looks like the mockup for Variants & LOD and in the case of Challenge have two differences: distractor samples are grouped in blue bars and virus ones are kept in red, and there is a line to indicate the threshold.

For LOD:

image

For Challenge:

image

  • Limit of detection which is only displayed in LOD purpose and looks different attending client request: each sample has a concentration and a measured signal and represents a point in the plot, the regression line computed for all these points is represented with a dashed line.

image

  • ROC curve which is only displayed for Challenge purpose. The curve itself is displayed in red, and the TPR and FPR of the selected threshold are updated to accommodate the dashed lines.

image

The Challenge report allows the user to update the threshold via a slider:

image

This threshold will update the confusion matrix, dashed lines mentioned above, and the information card on the left:

chrome-capture-2023-0-11

When the threshold bar is moved update_threshold is GET from the controller to update variables. For Variants and LOD threshold is always zero: all the samples contain virus, then they are only negative those containing zero concentration (no virus).

Also, there are enabled buttons to download SVG and PDF files are functioning:

  • For the PDF exporting, a new view was generated, avoiding layout and generating a view that will be "printed" to a file. This file is generated using this library, which helps with margins/page-orientation/page-breaks, etc. The library wasn't installed with NPM in the project, is dynamically loaded in javascript. Should it be installed?
  • For the SVG exporting: after intensive stack overflowing the issue, the only solution working properly was to:
  1. generate an element that downloads a generated Blob; along with
  2. a function that recursively takes the CSS styles of the elements and writes them into the styles property. This function is called applyInLine. The natural option for this when looking to solve this issue was to use getComputedStyle, but it didn't work properly for the react-generated SVG, while this one, more "sophisticated" did.

Screenshots of the PDF (for Challenge purpose, illustrative of the others):

1/3
image

2/3
image

3/3
image

Important: this PR rely in the existence of a distractor attribute in samples, which was faked during the development and not committed, and it should be merged into main after the PRs containing the addition of this attribute are merged

leandroradusky and others added 30 commits November 21, 2022 12:45
* Remove Institution and Purpose labels to improve display
* Truncate institution name
* Add samples count
* Custom confirm modals for upload results form

* Custom confirm modals for upload results form

* Custom confirm modals for upload results form
* Migrate to mini_racer gem to replace the deprecated therubyracer gem
* Fix: use MiniRacer::Context in ManifestFieldMapping
* Fix: don't expose raw Ruby/Nokogiri objects in ManifestFieldMapping#run_script

WARNING: THIS IS A BREAKING CHANGE.

All parsers for manifest fields return a trivial data structure, made of
hash, array, string and number that are easy to serialize to another
language (in this case JS).

The XML parse, however, returns a Nokogiri::XML::Element object that
therubyracer exposed entirely to JavaScript (all methods were
accessible). Even worse, it would expose all objects returned by calls
to the object (e.g. Ruby arrays, Nokogiri::XML::NodeSet, ...).

This behavior had numerous issues:

1. it's exposing an unlimited Ruby interface to user controlled code
   (device manifests), which is a security issue;
2. it's tying the Nokogiri interface to application uncontrolled code,
   leading to the impossibility to ever use anything else, or having to
   re-implement the whole interface (or at least what's used).
3. it's preventing us from moving away from the old abandonned
   `therubyracer` gem to something maintained (i.e. `mini_racer`) that
   don't expose non trivial objects.

After verifying on different production sites, we noticed that nobody
was using the XML parser, so I chose to change how we expose XML parsed
data to JavaScript.

We now serialize the Nokogiri::XML::Element as a trivial data structure
of the form:

```
var message = {
  "name": "node_name",
  "attributes": { "attr_name" => "attr_value" },
  "children": [
    // nested elements
  ]
}
```

In addition we still expose a `message.xpath(query)` function that
allows to query for nested data in a nicer way. It's only available on
the `message` variable and not available for nested children (the query
should directly return the node that you're interested in). The function
returns the same serialized data structure.
* Allow to download CSV template file and style fixes

* Fixes PR review
@leandroradusky leandroradusky self-assigned this Jan 11, 2023
Copy link
Contributor

@ysbaddaden ysbaddaden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leandroradusky This is only a partial review, so you can continue working! I'm still pondering about the PDF rendering.

def show
@samples_report = SamplesReport.find(params[:id])
@reports_data = measured_signal_data(@samples_report)
@samples_without_results_count = @samples_report.samples.where("core_fields NOT LIKE '%measured_signal%'").count
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition should be extracted to a model scope, where specific SQL should live.

Copy link
Contributor

@ysbaddaden ysbaddaden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's good as-is.

I still think we should generate the PDF on the backend, but we should work on that in a follow-up PR.

@leandroradusky leandroradusky merged commit 0fc7422 into feature/1734-box-reports Jan 17, 2023
@leandroradusky leandroradusky deleted the feature/1821-reporting-merged branch January 17, 2023 14:29
leandroradusky added a commit that referenced this pull request Jan 19, 2023
* Feature/1809 samples reports entity (#1823)

* New feature: Upload measured concentrations for samples

* Specs passing and small fixes

* Enablin upload button only when files are present, remove file button, specs

* Fixes PR review

* All jquery removed, more PR review suggestions solved

* Stashing changes

* Stashing changes

* Stashing changes

* Stashing changes

* Boxes reports entity working

* Small fixes

* Fixes PR review

* Fixes PR review

* Fixes PR review

* Fixes Julien's PR review

* SamplesReportsController specs (#1839)

* Boxes reports functioning (#1840)

* New feature: Upload measured concentrations for samples

* Specs passing and small fixes

* Enablin upload button only when files are present, remove file button, specs

* Fixes PR review

* All jquery removed, more PR review suggestions solved

* Stashing changes

* Stashing changes

* Stashing changes

* Stashing changes

* Boxes reports entity working

* Small fixes

* Rework Box Label (#1819)

* Remove Institution and Purpose labels to improve display
* Truncate institution name
* Add samples count

* Box reports for LOD samples working

* Small fix

* Enabled PDF and SVG export in box reports

* Visualization and print implmented for Challenge Boxes

* Added computed AUC, TPR and FPR

* automatic unblinding for samples with uploaded measurements (#1831)

* Fixing of box label in UI (#1836)

* Feature/1807 custom confirm modal (#1834)

* Custom confirm modals for upload results form

* Custom confirm modals for upload results form

* Custom confirm modals for upload results form

* Upgrade to `mini_racer` gem (#1791)

* Migrate to mini_racer gem to replace the deprecated therubyracer gem
* Fix: use MiniRacer::Context in ManifestFieldMapping
* Fix: don't expose raw Ruby/Nokogiri objects in ManifestFieldMapping#run_script

WARNING: THIS IS A BREAKING CHANGE.

All parsers for manifest fields return a trivial data structure, made of
hash, array, string and number that are easy to serialize to another
language (in this case JS).

The XML parse, however, returns a Nokogiri::XML::Element object that
therubyracer exposed entirely to JavaScript (all methods were
accessible). Even worse, it would expose all objects returned by calls
to the object (e.g. Ruby arrays, Nokogiri::XML::NodeSet, ...).

This behavior had numerous issues:

1. it's exposing an unlimited Ruby interface to user controlled code
   (device manifests), which is a security issue;
2. it's tying the Nokogiri interface to application uncontrolled code,
   leading to the impossibility to ever use anything else, or having to
   re-implement the whole interface (or at least what's used).
3. it's preventing us from moving away from the old abandonned
   `therubyracer` gem to something maintained (i.e. `mini_racer`) that
   don't expose non trivial objects.

After verifying on different production sites, we noticed that nobody
was using the XML parser, so I chose to change how we expose XML parsed
data to JavaScript.

We now serialize the Nokogiri::XML::Element as a trivial data structure
of the form:

```
var message = {
  "name": "node_name",
  "attributes": { "attr_name" => "attr_value" },
  "children": [
    // nested elements
  ]
}
```

In addition we still expose a `message.xpath(query)` function that
allows to query for nested data in a nicer way. It's only available on
the `message` variable and not available for nested children (the query
should directly return the node that you're interested in). The function
returns the same serialized data structure.

* GHA: fix deprecated nodejs version warning

* Styled slider, small fixes

* Allow to download CSV template file and style fixes (#1835)

* Allow to download CSV template file and style fixes

* Fixes PR review

* Basic functioning report for variants box

* Merging completition

* Merging completition

* Merging completition

* Merging competition

* Fixes PR review

Co-authored-by: Julien Portalier <julien@portalier.com>

* Merging reports into main & fixes

* more bugfixes

* Update app/helpers/samples_reports_helper.rb

Co-authored-by: Julien Portalier <julien@portalier.com>

* small fix

Co-authored-by: Julien Portalier <julien@portalier.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants