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

6650 rename requisition line transformation and expose plugin data query to plugins #6684

Open
wants to merge 4 commits into
base: 5837-requisition-line-column-and-plugin-data
Choose a base branch
from

Conversation

andreievg
Copy link
Collaborator

Fixes #6650

πŸ‘©πŸ»β€πŸ’» What does this PR do?

Sorry I did a bit more then specified by the issue.

Renamed transform_requisition_line to transform_request_requisition_line, while renaming I decided to use PluginType as source of truth, this also included updating types in the backend plugin package.json (see example branch mentioned in testing)

Added get plugin data method that is exposed now to plugins

Exposed request edit line in front end to also have columns shown

I found a bug where plugin data is set for wrong id, because id is overwritten after generating requisition line (since we are using id generated on front end), this was fixed by providing item_id -> id mapping to generate requisition line

πŸ’Œ Any notes for the reviewer?

Plugin data filter is exposed to plugin, we starting to expose types that are deep in our code, which is very convenient but would require testing strategy, which will be implemented for CIV plugins.

πŸ§ͺ Testing

Use this branch of examples: msupply-foundation/open-msupply-plugins#11

Create internal order, see aggregate AMC value appearing in table and edit lines view. Delete a line with 0 aggregate amc. Create manual response requisition with that item, set amc. Insert that one line again in internal order, see aggregate amc change having value. Aggregate AMC should be sum of all of the latest response requisition line AMC for that item in that store

@github-actions github-actions bot added this to the v2.6.0-RC3 milestone Feb 25, 2025
@github-actions github-actions bot added enhancement New feature or request Team Ruru πŸ¦‰ Roxy, Ferg, Noel feature: plugin labels Feb 25, 2025
@@ -173,7 +173,7 @@ You can work on plugins as if they were part of the app (types should be shared,

```bash
# From server directory
cargo run --bin remote_server_cli -- generate-plugin-bundle -i ../client/packages/plugins/mynewplugin/frontend -o pluginbundle.json
cargo run --bin remote_server_cli -- generate-plugin-bundle -i ../client/packages/plugins/myPluginBundle/frontend -o pluginbundle.json
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

decided to change this since submodule example in readme uses myPluginBundle as plugin name

@@ -83,6 +85,9 @@ export const RequestLineEdit = ({
?.sort((a, b) => a.name.name.localeCompare(b.name.name))
.sort((a, b) => b.amcInUnits - a.amcInUnits)
.sort((a, b) => b.stockInUnits - a.stockInUnits);

const line = lines.find(line => line.id === draft?.id);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For fragment to be available to editViewColumns plugin interface

pub enum PluginType {
Amc,
TransformRequisitionLines,
AverageMonthlyConsumption,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was not necessary, but not a huge change, sorry for the extra diff

@@ -8,10 +8,10 @@ use diesel_derive_enum::DbEnum;
use serde::{Deserialize, Serialize};

#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[serde(rename_all = "snake_case")]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

snake case now because this maps directly to main plugins type now, made comments in types/mod.rs

pub struct EqualFilter<T>
where
T: 'static,
{
#[ts(optional)]
Copy link
Collaborator Author

@andreievg andreievg Feb 25, 2025

Choose a reason for hiding this comment

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

Without optional Option is treated as null, with optional treated as undefined (with former have to list all fields ..)

pub(crate) fn call_plugin<I, O>(
input: I,
name: &str,
r#type: &PluginType,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I like this change even though it's a little bit out of scope, it avoids having to hard code string and makes PluginType source of truth

Copy link

Bundle size difference

Comparing this PR to main

Old size New size Diff
5.95 MB 5.95 MB 253 B (0.00%)

let Some(arg) = args.get(index) else {
return Err(Error::NoArgumentAtIndex(index));
};
let arg = args.get(index).ok_or(Error::NoArgumentAtIndex(index))?;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sorry again refactor, i added new method below which uses ok_or so thought to keep consistent, subjectively no difference in readability Β―_(ツ)_/Β―

SerdeError(#[from] serde_json::Error),
}

pub(super) fn get_serde_argument<D: DeserializeOwned>(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is used to get of plugin data filter

@@ -30,6 +31,10 @@ pub trait Trait: Send + Sync {
// TODO as macro ? Can do types here too
impl self::Trait for PluginInstance {
fn call(&self, input: Input) -> PluginResult<Output> {
Ok(call_plugin(input, "average_monthly_consumption", self)?)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This shows why PluginType is used now, rather then a string

@@ -127,6 +129,7 @@ fn generate(
store_id,
&requisition_row,
items_ids_not_in_requisition,
HashMap::new(),
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is the change for item to new line id mapping, see generate_requisition_lines

@lache-melvin lache-melvin self-assigned this Feb 26, 2025
let types = PluginTypes(vec![PluginType::Amc, PluginType::Amc]);
let types = PluginTypes(vec![
PluginType::AverageMonthlyConsumption,
PluginType::AverageMonthlyConsumption,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, but should we use the TransformRequestRequisitionLines here now?


let key = js_string!(name);
let key = js_string!(r#type.as_str());
Copy link
Contributor

Choose a reason for hiding this comment

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

How come this one not using plugin_type_to_string?

JsString::from("get_plugin_data"),
0,
NativeFunction::from_copy_closure(move |_, args, mut ctx| {
// TODO Is this actually safe ? (need to check reference counts after plugin has run)
Copy link
Contributor

Choose a reason for hiding this comment

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

Would like to know about this before we merge..?


// TODO pagination or restrictions ?
let plugin_data: Vec<PluginDataRow> = PluginDataRepository::new(&connection)
.query_by_filter(filter)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does feel pretty loose, had same thought with transform_lines as plugin... would like to hear more about how much we intend to trust the plugins?

@@ -46,6 +49,8 @@ pub fn generate_requisition_lines(
store_id: &str,
requisition_row: &RequisitionRow,
item_ids: Vec<String>,
// Need for plugins since sometimes we set id passed in as parameter in graphql
Copy link
Contributor

Choose a reason for hiding this comment

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

Well, the insert_request_requisition_line that does this is only used from batch mutation, which is not used by our frontend? Do you know if there is an integration that we need to support here, or should we remove the batch mutation and then wouldn't need to do this?

Copy link
Contributor

Choose a reason for hiding this comment

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

I TAKE IT BACK IM BAD AT SEARCHING IT IS INDEED USED

@lache-melvin
Copy link
Contributor

Looking good, would like to know about the todos you left, will test properly in the morning πŸš€

Copy link
Contributor

@lache-melvin lache-melvin left a comment

Choose a reason for hiding this comment

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

Requesting changes at least until hear from you what we do now vs added to carryover:

  • Currently transform_request_requisition_lines is not called by update_request_requisition_line but it is by update_request_requisition - should be added to line update too I think
    • If plugin interface/capability is as generic as "transform" then I think should be called in all cases
    • As discussed, could pass an ActionType enum into plugin, for plugin to determine if runs on all calls, only create etc.
    • Other places currently not using plugin that probably should in this case: R&R form, processors, fine grained calls like use_suggested_quantity (+ maybe more?? see below, I'm worried about this, how do we ensure we don't miss any?)
  • Backend plugin helper methods have: // TODO Is this actually safe ? (need to check reference counts after plugin has run) ... can we remove this if you are happy it is safe?
  • Back and frontend plugins should have same code in package.json
    • I wonder if common frontend should expose usePluginData hook rather than implementing for each plugin
      • This can extract pluginCode from the plugin itself?
    • Query and mutate should only be able to access plugin data for that plugin code
      • restrict get_plugin_data
      • Maybe call_plugin method supports returning of plugin_data rows, can then get processed and plugin code applied, before returned to service call for saving? Though probs requires a second kind of call_plugin_with_data_output or something....
  • For thought - best practices to ensure plugin calls aren't missed in future core dev? Should live in mod file, as generic place as possible... are there better patterns to support the plugin architecture?

FYI at one point you mentioned update would be creating duplicate plugin data, but looks like backend plugin that isn't happening anymore. I wonder about frontend though, whether we should expose upsert plugin data mutation rather than 2 separately

@@ -213,6 +218,10 @@ export const RequestLineEdit = ({
label={t('label.amc')}
sx={{ marginBottom: 1 }}
/>
{line &&
plugins.requestRequisitionColumn?.editViewColumns?.map(
(Column, index) => <Column key={index} line={line} />
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should call this Field, and editViewFields ? Column has me thinking I should be defining a column definition with cell/accessor etc..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request feature: plugin Team Ruru πŸ¦‰ Roxy, Ferg, Noel
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants