-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Usage report functionality from main branch #485
base: main
Are you sure you want to change the base?
Conversation
@@ -52,6 +52,7 @@ function FormSectionField({ checkErrors, field, formMethods, formMode, disableUn | |||
<div className={classes} data-testid="form-section-field"> | |||
{renderField} | |||
</div> | |||
<div className={`${classes} ${css[commonInputProps.id]}`}>{renderField}</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this for? We are already rendering renderField above
|
||
.To{ | ||
margin-left: 307px; | ||
margin-top: -103px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct indents, and rename this css class to something a little more verbose if still used. If not used delete it.
@@ -0,0 +1,74 @@ | |||
// Copyright (c) 2014 - 2023 UNICEF. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
npm run test
and npm run lint
and fix test and lint issues from output
import PropTypes from "prop-types"; | ||
import DateFnsUtils from "@date-io/date-fns"; | ||
import { Controller, useWatch } from "react-hook-form"; | ||
import { MuiPickersUtilsProvider, KeyboardDatePicker } from "@material-ui/pickers"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this file and use the DateInput component in this directory
@@ -232,6 +234,8 @@ export default (field, { checkErrors, errors, formMode, disableUnderline }) => { | |||
return HiddenInput; | |||
case LINK_FIELD: | |||
return LinkField; | |||
case DATE_PICKER: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this
close={dialogClose} | ||
setPending={setDialogPending} | ||
/> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove space
@@ -0,0 +1,41 @@ | |||
// Copyright (c) 2014 - 2023 UNICEF. All rights reserved. | |||
|
|||
import { SwapVert } from "@material-ui/icons"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change package @mui/icons-material
import { saveExport } from "../../../../../components/record-actions/exports/action-creators"; | ||
import { formatFileName } from "../../../../../components/record-actions/exports/utils"; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove one empty line
// Form submission | ||
const onSubmit = getData => { | ||
// Check if any of the required fields are null | ||
if (!getData.From) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use yup for validation instead of the following. You can pass a schema into like
import { object, string } from "yup";
export const validationSchema = object().shape({
message: string().required()
});
<Form validations={validationSchema} />
into the form component
@@ -35,7 +35,8 @@ function Component({ | |||
formClassName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes in this file aren't needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure all unit tests and linters pass for both JS and Ruby
# API endpoint for generating exports, in bulk or for individual records | ||
class Api::V2::UsageReportsController < ApplicationApiController | ||
def create | ||
@export = ExportService.build(export_params, current_user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This endpoint needs an authorization check like the others. Otherwise stats can be pulled by anyone who hasn't logged in.
@export_params ||= params.require(:data).permit( | ||
:record_type, :export_format, | ||
:order, :query, :file_name, :password, :selected_from_date, :selected_to_date, | ||
{ custom_export_params: {} }, { filters: {} }, :match_criteria |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we know exactly what the filters and custom export params are let's explicitly specify them in this structure rather than just using the permissive {}
@@ -5,6 +5,7 @@ | |||
# Users CRUD API | |||
class Api::V2::UsersController < ApplicationApiController | |||
include Api::V2::Concerns::Pagination | |||
include Api::V2::Concerns::Export |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this code actually get used?
def export(password) | ||
process_records_in_batches(500) { |records_batch| exporter.export(records_batch) } | ||
def export(password, kpi_parameters) | ||
if check_usage_report |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I m not convinced you need to use the BulkExport here at all. The class is designed for exporting 1000's of records. A Primero instance should have significantly less users.
However, if you do need to leverage the bulk/batch logic of a bulk export (I don't see that being done in the proposed code) this would be a good situation to use class inheritance. Instead of inserting if/else logic in the BulkExport you should create a new UserKPIBulkExport that inherits from it. But again, I don't think you need to touch BulkExports at all.
|
||
# Export forms to an Excel file (.xlsx) | ||
# rubocop:disable Metrics/ClassLength | ||
class Exporters::UsageReportExporter < Exporters::BaseExporter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class has a lot in common with Exporters::ExcelExporter
. You should inherit from that class instead of Exporters::BaseExporter
|
||
# class for Usage Report | ||
class UsageReport | ||
include ActiveModel::Model |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason that this is a Rails ActiveModel object rather than a plain Ruby object? Please move this business logic to a service, something like UsageReportService
end | ||
|
||
def get_common_keys(module_id, start_date, end_date) | ||
['', UsageReport.total_records(module_id, Child).count, UsageReport.open_cases(module_id).count, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the wrong place to do the .count
query. It needs to take place on the UsageReport
class UsageReport | ||
include ActiveModel::Model | ||
|
||
class << self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the point of this is to run the .count
queries, move the counts to this class
|
||
def new_records_quarter(module_id, start_date, end_date, recordtype) | ||
recordtype.where( | ||
"data->>'module_id' = ? AND CAST(data->>'created_at' AS DATE) BETWEEN ? AND ?", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CAST(...) AS DATE
may lead to performance issues in large databases. This query can be improved in v2.13 where certain JSON data fields are normalized into tables.
) | ||
end | ||
|
||
def total_services(module_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the two methods below you are loading the entire set of cases into memory. This will crash Primero if it has more than 500-1000 cases. You need to do a more nuanced count query directly on the database that counts the services/followup subform entries. To do that you will probably need to do a CROSS JOIN. See how this was done for ReportableService on the Report. If you are writing complex SQL, don't forget to sanitize code to avoid SQL injections
@@ -124,6 +125,7 @@ | |||
end | |||
end | |||
resources :bulk_exports, as: :exports, path: :exports, only: %i[index show create destroy] | |||
resources :usage_reports |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restrict to only: %i[create]
No description provided.