Skip to content
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

4791 add report versioning #5533

Merged
merged 38 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e6c170b
Add versioning sort to ReportRepo
fergie-nz Nov 21, 2024
841e2f3
Add code and is_custom filters
fergie-nz Nov 21, 2024
1d1548d
Add only upsert standard reports
fergie-nz Nov 22, 2024
50d3403
Also add check for don't upsert old report versions
fergie-nz Nov 22, 2024
7d3ed26
Fix loop logic
fergie-nz Nov 22, 2024
9cb8f7c
Add code to report node api
fergie-nz Nov 22, 2024
3bc4682
Add version to schema
fergie-nz Nov 22, 2024
9cf6c6b
Add front end mapping
fergie-nz Nov 22, 2024
5a8975c
Add version compare package
fergie-nz Nov 24, 2024
1e94997
Add version compare when comparing version strings
fergie-nz Nov 24, 2024
deeae60
Merge branch 'v2.4.0' into 4791-add-report-versioning
fergie-nz Nov 26, 2024
834f2a2
Remove front end report filtering
fergie-nz Nov 26, 2024
d9240a8
Add get latest report version to service layer
fergie-nz Nov 26, 2024
c7ba894
Remove unnecessary changes
fergie-nz Nov 27, 2024
8b3bed3
Simplify upsert logic
fergie-nz Nov 27, 2024
920bd33
Remove unused repo function
fergie-nz Nov 27, 2024
f494a95
Remove unused imports
fergie-nz Nov 27, 2024
aefe758
Reset formatting
fergie-nz Nov 27, 2024
df99155
Rename to count
fergie-nz Nov 27, 2024
2a11c29
Add mock reports
fergie-nz Nov 27, 2024
a5ccccc
Format reports
fergie-nz Nov 27, 2024
2ee5a38
Add basic filter method scaffold
fergie-nz Nov 27, 2024
89a2a20
Add check for correct version to show
fergie-nz Nov 27, 2024
2808db6
Add sub query for metadata only
fergie-nz Nov 27, 2024
fd484a0
minimise changes in cli
fergie-nz Nov 27, 2024
700e1a4
remove unwanted changes
fergie-nz Nov 27, 2024
916974f
remove unwanted changes
fergie-nz Nov 27, 2024
516f705
Remove unused pagination
fergie-nz Nov 27, 2024
0c66f6a
Rename fields
fergie-nz Nov 27, 2024
9503c67
Add clippy recommendations
fergie-nz Nov 27, 2024
14afadc
Rename variable for ease of understanding
fergie-nz Nov 27, 2024
b618482
Remove unused struct
fergie-nz Nov 28, 2024
1ce0ac4
Report filter method returns vec of ids only
fergie-nz Dec 16, 2024
06635a0
Add from domain object
fergie-nz Dec 17, 2024
914c673
Update readme
fergie-nz Dec 17, 2024
0556fae
Further readme updates
fergie-nz Dec 17, 2024
682b122
Merge branch 'develop' into 4791-add-report-versioning
fergie-nz Dec 19, 2024
751ac3b
Merge branch 'develop' into 4791-add-report-versioning
fergie-nz Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion server/cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,10 @@ async fn main() -> anyhow::Result<()> {
let version = manifest.version;
let id_version = str::replace(&version, ".", "_");

let id = format!("{code}_{id_version}");
let context = manifest.context;
let report_name = manifest.name;
let is_custom = manifest.is_custom;
let id = format!("{code}_{id_version}_{is_custom}");
let sub_context = manifest.sub_context;
let arguments_path = manifest
.arguments
Expand Down
31 changes: 25 additions & 6 deletions server/graphql/reports/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use async_graphql::*;
use graphql_core::{generic_inputs::PrintReportSortInput, pagination::PaginationInput};
use graphql_core::generic_inputs::PrintReportSortInput;
use print::{generate_report, generate_report_definition, PrintReportResponse};
use reports::{
report, reports, ReportFilterInput, ReportResponse, ReportSortInput, ReportsResponse,
Expand Down Expand Up @@ -37,11 +37,10 @@ impl ReportQueries {
ctx: &Context<'_>,
store_id: String,
user_language: String,
page: Option<PaginationInput>,
filter: Option<ReportFilterInput>,
sort: Option<Vec<ReportSortInput>>,
) -> Result<ReportsResponse> {
reports(ctx, store_id, user_language, page, filter, sort)
reports(ctx, store_id, user_language, filter, sort)
}

/// Creates a generated report.
Expand All @@ -64,7 +63,17 @@ impl ReportQueries {
sort: Option<PrintReportSortInput>,
current_language: Option<String>,
) -> Result<PrintReportResponse> {
generate_report(ctx, store_id, report_id, data_id, arguments, format, sort, current_language).await
generate_report(
ctx,
store_id,
report_id,
data_id,
arguments,
format,
sort,
current_language,
)
.await
}

/// Can be used when developing reports, e.g. to generate a report that is not already in the
Expand All @@ -77,10 +86,20 @@ impl ReportQueries {
#[graphql(desc = "The report definition to be generated")] report: serde_json::Value,
data_id: Option<String>,
arguments: Option<serde_json::Value>,
format: Option<PrintFormat>,
format: Option<PrintFormat>,
current_language: Option<String>,
) -> Result<PrintReportResponse> {
generate_report_definition(ctx, store_id, name, report, data_id, arguments, format, current_language).await
generate_report_definition(
ctx,
store_id,
name,
report,
data_id,
arguments,
format,
current_language,
)
.await
}
}

Expand Down
9 changes: 4 additions & 5 deletions server/graphql/reports/src/reports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ use graphql_core::simple_generic_errors::FailedTranslation;
use graphql_core::standard_graphql_error::{list_error_to_gql_err, StandardGraphqlError};
use graphql_core::{
generic_filters::{EqualFilterStringInput, StringFilterInput},
pagination::PaginationInput,
standard_graphql_error::validate_auth,
};
use graphql_core::{map_filter, ContextExt};
use graphql_types::types::FormSchemaNode;
use repository::{
ContextType as ReportContextDomain, EqualFilter, PaginationOption, Report, ReportFilter,
ReportSort, ReportSortField, StringFilter,
ContextType as ReportContextDomain, EqualFilter, Report, ReportFilter, ReportSort,
ReportSortField, StringFilter,
};
use service::auth::{Resource, ResourceAccessRequest};
use service::report::report_service::{GetReportError, GetReportsError};
Expand Down Expand Up @@ -173,7 +172,6 @@ pub fn reports(
ctx: &Context<'_>,
store_id: String,
user_language: String,
page: Option<PaginationInput>,
filter: Option<ReportFilterInput>,
sort: Option<Vec<ReportSortInput>>,
) -> Result<ReportsResponse> {
Expand All @@ -193,7 +191,6 @@ pub fn reports(
&service_context,
&translation_service,
user_language,
page.map(PaginationOption::from),
filter.map(|f| f.to_domain()),
sort.and_then(|mut sort_list| sort_list.pop())
.map(|sort| sort.to_domain()),
Expand All @@ -216,6 +213,8 @@ impl ReportFilterInput {
.context
.map(|t| map_filter!(t, ReportContext::to_domain)),
sub_context: self.sub_context.map(EqualFilter::from),
code: None,
is_custom: None,
}
}
}
Expand Down
37 changes: 28 additions & 9 deletions server/report_builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ Optional fields in the manifest json are marked as '// optional'
"is_custom": false,
// open mSupply needs to know the report version
"version": "2.3.0",
// The code is a new field which is the unique identifier of a report group inclusive of all versions of that report.
// The code is the unique identifier of a report group inclusive of all versions of that report.
// Each different version of a standard report will have the same code.
// The code is required to identify reports of the same version where they will have different ids
"code": "item-usage",
Expand Down Expand Up @@ -306,10 +306,16 @@ Standard reports in the reports dir can then be built into the generated json by
./target/debug/remote_server_cli build-standard-reports
```

Reports can be upserted using

```bash
./target/debug/remote_server_cli upsert-reports-json
```

Because this command utilises the built cli, you will need to first run

```
cargo run
cargo build
```

from the open-msupply/server dir.
Expand All @@ -334,11 +340,17 @@ Log destination is configured in the base.yaml file under logging.

## Standard reports versioning

Standard reports include versions for updates of reports. Open mSupply central will automatically sync all standard reports.
Standard reports include a version parameter to control what reports are used and displayed by the front end.
Report use is controlled by version and code parameters. One report will be used per code.
For a given code, priority is given first to custom reports of a code, and then standard reports if no custom reports exist. The report with the latest compatible version will be used for each report code.
Version compatibility is measured by being less than or equal to the app major and minor version. Reports with the same major and minor versions but later patch versions are considered compatible with the app.

2.4 release will include filtering checks to show only the most up to date report compatible with the remote site build of open mSupply.
> eg: 2.4.12 version report will be compatible with a 2.4.2 app. But a 2.5.0 report will not be compatible.

In the case where there are custom reports, but none are compatible with the app version, the highest compatibly versioned standard report will be used.

This system allows OMS to have multiple reports upserted (and later synced) to distributed instances of different versions, and be able to function with compatible reports.

<!-- For future release - confirm this will occur -->
<!-- For example:

for report_versions = [2.3.0, 2.3.5, 2.8.2, 2.8.3, 3.0.1, 3.5.1]
Expand All @@ -348,11 +360,18 @@ if remote omSupply.version = 2.8 selected report = 2.8.3
if remote omSupply.version = 3.2 selected report = 3.0.1
if remote omSupply.version = 4.5 selected report = 3.5.1 -->

### Adding a new standard report version
### Adding a new standard report version or custom report

The simplest way is to:

- Copy the version dir of the report you want to make a custom or new standard report for, and paste it into the same location.
- Bump the version number
- Change the version dir name to match the version number.
- Change the is_custom boolean to true if the report will be a custom report.

Add a new version dir within the directory for the report you are adding a new version to.
> The dir names are for developer convenience only. The name and version of each report read by omSupply is from the manifest.json file.

> Note that the dir names are for developer convenience only. The name and version of each report read by omSupply is from the manifest.json file.
New report versions must be compatible with the matching major and minor versions of the OMS app.

## Developing standard reports

Expand All @@ -361,7 +380,7 @@ Reports can be built and tested directly from the 'open-msupply' repo, but devel

Once changes are satisfactory, new reports can be moved directly into the OMS repo under a new version dir.

Note that reports won't show up in OMS unless they are built into the generated json using the `build-standard-reports` command.
> reports won't show up in OMS unless they are built into the generated json using the `build-standard-reports` command, and then upserted with the `upsert-reports-json` command.

## Localising JSON form fields

Expand Down
81 changes: 70 additions & 11 deletions server/repository/src/db_diesel/report.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use super::{
form_schema_row,
form_schema_row::form_schema::dsl as form_schema_dsl,
report_row::{report, report::dsl as report_dsl},
ContextType, ReportRow, ReportType, StorageConnection,
form_schema_row::{self, form_schema::dsl as form_schema_dsl},
report_row::report::{self, dsl as report_dsl},
ContextType, ReportMetaDataRow, ReportRow, ReportType, StorageConnection,
};

use crate::{
diesel_macros::{apply_equal_filter, apply_sort_no_case},
migrations::Version,
schema_from_row, FormSchema, FormSchemaRow,
};
use crate::{EqualFilter, Pagination, Sort, StringFilter};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have removed pagination as we won't use it for these queries.

use crate::{EqualFilter, Sort, StringFilter};

use crate::{diesel_macros::apply_string_filter, DBType, RepositoryError};

Expand All @@ -22,13 +22,34 @@ pub struct Report {
pub argument_schema: Option<FormSchema>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct ReportMetaData {
Copy link
Collaborator

Choose a reason for hiding this comment

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

our domain object are composites atm, we don't really copy them (a bit less mapping and less to change when new fields are added)

Copy link
Collaborator

Choose a reason for hiding this comment

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

So should just have Version as another key (of type Version)

pub id: String,
pub is_custom: bool,
pub version: Version,
pub code: String,
}

impl ReportMetaData {
pub fn from_domain(report: ReportMetaDataRow) -> ReportMetaData {
ReportMetaData {
id: report.id,
is_custom: report.is_custom,
version: Version::from_str(&report.version),
code: report.code,
}
}
}

#[derive(Debug, Clone, Default)]
pub struct ReportFilter {
pub id: Option<EqualFilter<String>>,
pub name: Option<StringFilter>,
pub r#type: Option<EqualFilter<ReportType>>,
pub context: Option<EqualFilter<ContextType>>,
pub sub_context: Option<EqualFilter<String>>,
pub code: Option<EqualFilter<String>>,
pub is_custom: Option<bool>,
}

#[derive(PartialEq, Debug)]
Expand Down Expand Up @@ -63,6 +84,16 @@ impl ReportFilter {
self.context = Some(filter);
self
}

pub fn code(mut self, filter: EqualFilter<String>) -> Self {
self.code = Some(filter);
self
}

pub fn is_custom(mut self, value: bool) -> Self {
self.is_custom = Some(value);
self
}
}

impl ReportType {
Expand Down Expand Up @@ -98,12 +129,11 @@ impl<'a> ReportRepository<'a> {
}

pub fn query_by_filter(&self, filter: ReportFilter) -> Result<Vec<Report>, RepositoryError> {
self.query(Pagination::new(), Some(filter), None)
self.query(Some(filter), None)
}

pub fn query(
&self,
pagination: Pagination,
filter: Option<ReportFilter>,
sort: Option<ReportSort>,
) -> Result<Vec<Report>, RepositoryError> {
Expand All @@ -118,16 +148,39 @@ impl<'a> ReportRepository<'a> {
}
}
}
let result = query
.offset(pagination.offset as i64)
.limit(pagination.limit as i64)
.load::<ReportJoin>(self.connection.lock().connection())?;
let result = query.load::<ReportJoin>(self.connection.lock().connection())?;

result
.into_iter()
.map(map_report_row_join_to_report)
.collect::<Result<Vec<Report>, RepositoryError>>()
}

pub fn query_meta_data(
&self,
filter: Option<ReportFilter>,
sort: Option<ReportSort>,
) -> Result<Vec<ReportMetaData>, RepositoryError> {
let mut query = create_filtered_query(filter);
if let Some(sort) = sort {
match sort.key {
ReportSortField::Id => {
apply_sort_no_case!(query, sort, report_dsl::id);
}
ReportSortField::Name => {
apply_sort_no_case!(query, sort, report_dsl::name);
}
}
}

let result = query
.select(ReportMetaDataRow::as_select())
.load::<ReportMetaDataRow>(self.connection.lock().connection())?;
Ok(result
.into_iter()
.map(ReportMetaData::from_domain)
.collect())
}
}

type BoxedStoreQuery =
Expand All @@ -145,13 +198,19 @@ fn create_filtered_query(filter: Option<ReportFilter>) -> BoxedStoreQuery {
r#type,
context,
sub_context,
code,
is_custom,
} = f;

apply_equal_filter!(query, id, report_dsl::id);
apply_string_filter!(query, name, report_dsl::name);
apply_equal_filter!(query, r#type, report_dsl::type_);
apply_equal_filter!(query, context, report_dsl::context);
apply_equal_filter!(query, sub_context, report_dsl::sub_context);
apply_equal_filter!(query, code, report_dsl::code);
if let Some(is_custom) = is_custom {
query = query.filter(report_dsl::is_custom.eq(is_custom));
}
}

query
Expand Down
33 changes: 20 additions & 13 deletions server/repository/src/db_diesel/report_row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ impl Default for ReportRow {
}
}

#[derive(Clone, Insertable, Queryable, Debug, PartialEq, Eq, AsChangeset, Selectable)]
Copy link
Contributor

Choose a reason for hiding this comment

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

You can default here so you don't need to impl default (:

#[diesel(table_name = report)]
pub struct ReportMetaDataRow {
pub id: String,
pub is_custom: bool,
pub version: String,
pub code: String,
}

impl Default for ReportMetaDataRow {
fn default() -> Self {
Self {
id: Default::default(),
is_custom: true,
version: Default::default(),
code: Default::default(),
}
}
}

pub struct ReportRowRepository<'a> {
connection: &'a StorageConnection,
}
Expand All @@ -110,19 +130,6 @@ impl<'a> ReportRowRepository<'a> {
Ok(result)
}

pub fn find_one_by_code_and_version(
&self,
code: &str,
version: &str,
) -> Result<Option<ReportRow>, RepositoryError> {
let result = report_dsl::report
.filter(report_dsl::code.eq(code))
.filter(report_dsl::version.eq(version))
.first(self.connection.lock().connection())
.optional()?;
Ok(result)
}

Comment on lines -113 to -125
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removing as not anymore

pub fn upsert_one(&self, row: &ReportRow) -> Result<(), RepositoryError> {
diesel::insert_into(report_dsl::report)
.values(row)
Expand Down
Loading
Loading