Skip to content

Improve suffix unit conversion deletion with cascade delete and UI warnings - #1693

Open
BunnyBea83 wants to merge 22 commits into
OpenEnergyDashboard:developmentfrom
BunnyBea83:BunnyBea83/issue1448
Open

BunnyBea83 wants to merge 22 commits into
OpenEnergyDashboard:developmentfrom
BunnyBea83:BunnyBea83/issue1448

Conversation

@BunnyBea83

@BunnyBea83 BunnyBea83 commented Aug 5, 2026 •

Copy link
Copy Markdown

Description

This PR solves OED's opporations for deleting conversions involving suffix units. Upon deletion of a suffix unit related conversion, OED created units and related conversions will be cascade deleted. UI warnings are inplace to notify the user of units and conversions that will be deleted, and groups/meters that will be affected by the deletion. This PR builds off of PR #1561 by @hmhngx by incorporating parts of their work into the first five commits in this PR and PR #1470 that was used as the basis for PR #1561.

Key improvements:

  • Resolved merge conflicts and implemented work from PR Enhance suffix unit conversion deletion with dependency checks #1561
  • Suffix unit validations now account for Suffix Inputs and Suffix Analyzed during the simulateDeletion and Deletion phase of conversion deletion
  • Deletion of a Suffix Unit conversion will cascade delete OED created units in relation to the parent conversion
  • Cascade deletion within DB includes removing a units association to groups/meters, deleting conversions referencing the unit, and deleting units
  • UI warnings include affected meters/groups, units, and conversions related to the deletion of a suffix unit conversion
  • removeAdditionalConversionsAndUnits no longer 'hides' removed units. Removed units are completely deleted from the OED DB
    • Resolves discussion in issue#1448, when a unit is renamed the former unit is completely removed instead of 'hidden'
  • Included passing unit tests to confirm function of cascade deletion within suffixUnitDeletionTests.js

Partly Addresses #1448

Type of change

  • Note merging this changes the database configuration.
  • This change requires a documentation update

Checklist

  • I have followed the OED pull request ideas
  • I have removed text in ( ) from the issue request
  • You acknowledge that every person contributing to this work has signed the OED Contributing License Agreement and each author is listed in the Description section.

Limitations

Multi-hop-generated suffix units are not yet covered by this cascade delete since it likely needs a
design decision before implementation.

This PR resolves cascade deletion for the direct/single-hop case (deleting a
conversion from a suffix root, or from an auto-created child, correctly
cleans up what it should). It does not yet handle units auto-created via
multi-hop paths (e.g. suffix unit → new auto-created unit→ further conversion). Since OED always attaches such units
directly to the root rather than the intermediate unit, deleting the
intermediate conversion currently leaves these orphaned. Possible implementations could include either a re-walk of valid paths after delete, or tracking explicit provenance on auto-created units, both of
which are a larger design change than this PR's scope. Flagging for
follow-up discussion with maintainers.

Before Deletion of multi-hop path 'metric Ton of TestUnit -> gallon'

image

After Deletion of multi-hop path

image

Deletion of TestUnit -> kg removes all OED created units

image

Deferred: comprehensive stale-simulation protection.
The current delete flow re-verifies the cascade logic server-side, but doesn't detect the case where a new connection is added to a unit after the simulation runs and before the admin confirms the delete. That connection would be silently lost without warning. A fuller fix would re-run the simulation at confirm-time, compare it against what was originally shown, and prompt the admin again if they differ. Flagged as low-priority/unlikely per discussion with @huss, but noted here so it isn't lost.

- Add checkUnitDependencies service for validation
- Fix async race condition in removeAdditionalConversionsAndUnits
- Add bidirectional conversion support and depth limiting
- Enhance simulation to account for suffix unit cascades
- Add checkUnitDependencies service for validation
- Fix async race condition in removeAdditionalConversionsAndUnits
- Add bidirectional conversion support and depth limiting
- Enhance simulation to account for suffix unit cascades
- Add getConversionsByUnitId method
- Add deleteConversionAndRelatedSuffixes with dependency checks
- Add dependency checks before suffix unit cleanup
- Handle bidirectional conversions safely
- Add row-level locking for concurrent operations
- Improve error messages with meter/group details
- Show affected meters/groups in deletion warnings
- Improve suffix unit warning formatting
- Add translation keys for dependency warnings
- Removed getConversionsByUnitId method as it is no longer used
- Removed deleteConversionAndRelatedSuffixes method as it is no longer used
- Refactored hide suffix functions to delete suffix functions
- Suffix checks now account for suffix inputs (type of unit = unit, suffix = <contains a string>) when checking for dependencies.
- Added deleteUnitSafely method
- Method checks for dependencies of the unit, clears unit connections to meters, groups, and conversions, then deletes the unit.
- Deletes suffix units instead of hiding them (setting displayable to None)
- Calls deleteUnitSafely to check for unit dependacies, deleting the unit, and cleaning cik
- isSuffixRelated function determines if a unit is a suffix unit based on unit type (Suffix Analyzed) or if the suffix field is filled in (Suffix Input)
Refactor:
- Altered suffix detection cases to call isSuffixRelated. Reduces code redundancy and accounts for Suffix inputs.
- Added code commentry
- Modified suffixTypeUnitsToDelete to a simpler display
- Added conversion.delete.suffix.conversions.to.delete prompt for conversion deletion display.
- Modified relatedConversions
Fix cascade deletion walking backward into parent suffix units

removeAdditionalConversionsAndUnits previously matched any conversion
touching a suffix unit as either source or destination, regardless of
direction. This caused cleanup started from a child unit (e.g.
deleting "kg of X" -> "gallon") to walk backward through the
parent-to-child conversion that created it, incorrectly cascading the
delete up to the parent unit itself.

Now only conversions where the unit is the source are followed,
except for bidirectional conversions, where both directions are
still valid since they genuinely work both ways.

- deleteUnitSafely now occurs after removeAdditionalConversionsAndUnits
Unit tests for suffix unit deletion tests for:
- Deletion of OED created units and their conversions
- Clearing a dependent meter's unit ID to allow the auto-created unit to be deleted
- Clearing converstions that reference OED created units
- Clears a group's default graphic unit reference instead of blocking deletion of the unit it points to
- Recursively cleaning up nested suffix chains
- Prevention of deleting parent unit when deleting conversion from child unit
- Deletion of bidirectional conversions relating to suffix units
- Clearing cik rows before unit deletion
- Prevention of deleting regular units when deleting a suffix related conversion
@huss

huss commented Aug 7, 2026

Copy link
Copy Markdown
Member

@BunnyBea83 Thank you for this PR. It will take me about a week to get to review this. The description seems to be fine from my quick review.

@huss huss left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@BunnyBea83 First, I want to apologize for how long this review took (see Discord msg on developer channel on OED server). I want to thank you for this contribution to OED. It is a larger effort that did many aspects (changes, tests, etc.) and esp. nice given this is your first contribution to OED.

I have tried to make a good first pass on this PR. Overall, it works well and the code is fine. I've made a number of comments for you to consider. Some are to get your thoughts and we can discuss those before changes are made, as appropriate. Given this and the scope of the PR, I am probably going to need to do another pass on aspects of the work and will delay extensive testing until it is closer to finalized (I'm assuming changes will be needed). This is normal on a PR of this size. Please let me know if anything is not clear or you have thoughts/ideas.

Everything below here is consider resolved either by being done, another comment or planned actions outside this PR.*

Below are some comments that seemed best to place here:

  • The description says it Partly Addresses issue 1448. Are there aspects that you feel are not completed in that issue if this PR is merged?

  • I tried to find the old git commit record for the previous PRs but did not readily find it. Am I missing it? If not, I'm not sure you may have caused this. If it is gone then maybe the description should list the previous developers in an appropriate way so it is easy for others to see they worked on this previously. I can do this if you prefer and agree.

  • I had an unusual suffix unit in my setup so I tried to delete it. The visualization shows:

Image

When I went to the conversion and tried to delete it it failed (note lack of failure reason):

Image

and during the process it also a gave warning about what it was going to do:

Image

The terminal where OED was started had these messages:

web-1       | [INFO@2026-09-08T17:12:23.859+00:00] Suffix-related unit 43 is used in conversion deletion as source. Cleaning up related conversions and units.
web-1       | 
web-1       | [INFO@2026-09-08T17:12:23.864+00:00] Suffix-related unit 44 is used in conversion deletion as destination. Cleaning up related conversions and units.
web-1       | 
web-1       | [ERROR@2026-09-08T17:12:23.864+00:00] Error while deleting conversion and updating meters/groups: QueryResultError {
web-1       |     code: queryResultErrorCode.noData
web-1       |     message: "No data returned from the query."
web-1       |     received: 0
web-1       |     query: "SELECT * FROM units WHERE id = 44;"
web-1       | }
web-1       | Stacktrace: 
web-1       | QueryResultError: No data returned from the query.
web-1       |     at Query.<anonymous> (/usr/src/app/node_modules/pg-promise/lib/query.js:217:41)
web-1       |     at Query.handleReadyForQuery (/usr/src/app/node_modules/pg-promise/node_modules/pg/lib/query.js:137:14)
web-1       |     at Client._handleReadyForQuery (/usr/src/app/node_modules/pg-promise/node_modules/pg/lib/client.js:312:19)
web-1       |     at Connection.emit (node:events:514:28)
web-1       |     at /usr/src/app/node_modules/pg-promise/node_modules/pg/lib/connection.js:117:12
web-1       |     at Parser.parse (/usr/src/app/node_modules/pg-protocol/dist/parser.js:36:17)
web-1       |     at Socket.<anonymous> (/usr/src/app/node_modules/pg-protocol/dist/index.js:11:42)
web-1       |     at Socket.emit (node:events:514:28)
web-1       |     at addChunk (node:internal/streams/readable:324:12)
web-1       |     at readableAddChunk (node:internal/streams/readable:297:9)

The web console had an uncaught exception:

Image

I have not tried to trace this down. When I deleted the other suffix conversion (to 100 w bulb) it worked fine and cleaned up the other one too.

  • Small point but could space be added before the final delete statement at the end of the conversion delete modal so it is separated. I might also make it bold to stand out more.
Image
  • If I try to delete the standard OED test conversion of kg CO2 -> kg it has in the warning (meter one is similar):
Image

The admin is not used to seeing the connection to kg and Metric ton as these are artifacts of OED's processing of the suffix unit. This is seen in the unit visualization on the lower analyzed graphic. I don't know if it is easy to remove these or somehow label them/improve the message so it is clear for an admin. I'm happy to discuss possibilities.

  • The delete seemed to work fine when done in this case. I'm noting that the link to kg CO2 for the meters stays and in the analyzed unit graphic it is shown as orphaned (as it is). This is a suffix unit and I welcome thoughts on if/how there might be another/better way to deal with it. At a minimum, the help system should note this if it stays.
  • Also relating to deleting the standard OED test conversion of kg CO2 -> kg, It has:
Affected meters:
    "Electric Utility kWh in MTon CO₂"

A lot of other meters are listed below that have affected so I think it may be confusing to the admin. Is it possible to clarify how they differ in the text?

Comment thread src/server/services/graph/checkUnitDependencies.js
Comment thread src/server/services/graph/checkUnitDependencies.js Outdated
Comment thread src/server/routes/conversions.js Outdated
Comment thread src/server/services/graph/checkUnitDependencies.js Outdated
Comment thread src/server/services/graph/handleSuffixUnits.js Outdated
Comment thread src/server/routes/conversions.js
Comment thread src/server/routes/conversions.js
Comment thread src/server/routes/conversions.js Outdated
Comment thread src/server/routes/units.js
@BunnyBea83

BunnyBea83 commented Sep 11, 2026 •

Copy link
Copy Markdown
Author

@huss added 3 Oct 26: See later comment on the resolution of all of these.*

The description says it Partly Addresses issue 1448. Are there aspects that you feel are not completed in that issue if this PR is merged?

Issue 1448 feels completed in terms of successfully removing chains of auto-created units. However, I listed the PR as partially addressing the issue because it currently cannot delete multi-hop paths, as described in the “Limitation” section of the PR. This limitation may be addressed as I make the changes requested during this PR review.

I tried to find the old git commit record for the previous PRs but did not readily find it. Am I missing it? If not, I'm not sure you may have caused this. If it is gone then maybe the description should list the previous developers in an appropriate way so it is easy for others to see they worked on this previously. I can do this if you prefer and agree.

I was unable to successfully merge the previous PR due to the merge conflicts it was presenting. Because of those conflicts, I manually incorporated the code from that PR into my branch and used the same commit messages that were present in the original PR. I would appreciate any advice on the most appropriate way to credit the contributors for their previous work.

The following commits contain the features from hmhngx's PR that I manually implemented from PR #1561:

Hmhngx mentioned that their work was built off of PR #1470, so I referenced both PRs in this PR to keep the overlapping work documented in one place and to credit the contributors who previously worked on this issue. The remaining commits contain my own implementations and changes.

When I went to the conversion and tried to delete it it failed (note lack of failure reason):

This failure occurs in lines 243 to 252 of conversions.js. When removeAdditionalConversionsAndUnits is called on sourceInTx, it recursively deletes connected suffix units and their conversions. removeAdditionalConversionsAndUnits is then called on destInTx. If the destination unit was already deleted during the cleanup initiated by sourceInTx, the subsequent call attempts to operate on a unit that no longer exists, resulting in the error.

I will address this by handling this edge case so that a unit that has already been deleted during the source cleanup is not processed again during the destination cleanup.

The admin is not used to seeing the connection to kg and Metric ton as these are artifacts of OED's processing of the suffix unit. This is seen in the unit visualization on the lower analyzed graphic. I don't know if it is easy to remove these or somehow label them/improve the message so it is clear for an admin.

I'm not sure these can be removed outright because they're reachable through the conversion chain and used for real unit-compatibility calculations. However, I agree that the messaging wasn't clear enough as to why they're relevant to an admin who didn't set the connection themselves.

I've now added labeling instead where OED created units are now tagged in the affected meters/groups lists. This way the admin can tell which units they set up directly vs internal artifacts. I also restructured the surrounding lists so meters/groups sharing the same lost units are grouped together more clearly. Each group now gets its own heading with a bulleted list of lost units underneath, and groupings are separated by a light divider. I recognize that the style of formatting is slightly different compared to other UI notifications. Let me know if this addresses your concern or if you have something more specific in mind.

The delete seemed to work fine when done in this case. I'm noting that the link to kg CO2 for the meters stays and in the analyzed unit graphic it is shown as orphaned (as it is). This is a suffix unit and I welcome thoughts on if/how there might be another/better way to deal with it. At a minimum, the help system should note this if it stays.

Thanks for flagging this. Based on the structure of the code the real underlying conversion kg CO2 is never actually deleted by this feature, only the derived suffix units/conversions are. removeAdditionalConversionsAndUnits resets the parent's displayable back to visible at the end of its cleanup regardless of why it was called. So the children are removed, kg CO2 is left visible and still connected to its never deleted conversion (e.g. electric_utility -> kg CO2), and the next graph rebuild regenerates the derived units from it. Which is why it shows as 'orphaned' in the visualization.

One direction I'm considering is blocking the deletion of a derived suffix conversion directly, and instead redirecting the admin to the action that resolves the issue. Either deleting the real underlying conversion, or clearing the unit's suffix field via /edit. The catch is that the real conversion might not be visible in the UI once the parent unit is hidden, so I'd want to confirm that's reachable before committing in that direction. Open to your thoughts.

[src/server/routes/conversions.js] comment on line 186
Could you help me understand why this is cleaning up items even after the simulation that I think happened before this request. Is it to be safe? I have not tried to figure this out so I appreciate your thoughts.

Good question. Yes, it's intentional for safety. /simulate-delete and /delete are separate requests with no shared transaction. There's a real time gap between when the simulation runs (when the pop up opens) and when the admin actually confirms. In that window, the database could change (e.g. another session editing units, conversions, etc.) so the simulation results could be stale by the time the admin confirms the delete. Re-checking and re-computing inside the actual delete transaction guarantees the cleanup is correct against the live state at commit time, rather than acting on a possibly outdated preview.

That said, I think the delete functionality can use a split solution as opposed to a blanket solution since not everything carries the same risk of stale:

  • The Cascade Delete Logic : The functionality should keep re-verifying independetly as it does currently. This is the part where stale data could cause problems since it actually removes data (e.g. a unit is classified to a meter between simulation and confirm).
  • Meter and Group IDs: These could continue to use the meter and group IDs supplied by the client from the simulation results. These operations clear references rather than deleting the meters or groups themselves, so they have a different risk profile. This is also how the current code behaves: /delete uses the meterIds and groupIds sent by the client, which were derived from the /simulate-delete response rather than recomputed server-side during /delete.

A more thorough approach, worth considering separately, would be to re-run the simulation when the admin confirms the deletion and compare the results against what was originally shown. If they differ, the updated results could be displayed to the admin and require them to reconfirm before proceeding.

This would close the staleness gap more completely than either trusting the original simulation or silently re-verifying during deletion. However, I think this would be a larger change than what is currently included in the PR, so I wanted to raise it as a possible future approach rather than implement it without first confirming that it is desired.

You raised related versions of the same question on the units.js and EditConversionModalComponent.tsx threads as well. I think the same reasoning applies to both, but I wanted to consolidate the core discussion here first.

BunnyBea83 and others added 4 commits September 17, 2026 09:17
- Corrected spelling errors
- Removed unused functions
- Added translations for French and Spanish
- Added `utils` conversion arrow for UI display
- Added Bold casing to UI display
- Removed `.trim()` in suffix checks
- Removed single letter const identifiers
- Fixed formatting issues
- Replaces rew SQL calls with SQL files and associated imports.
- Added suffixUnitCheck.js to `utils` for consistent function calls.
- Added UI notification if a unit may be orphaned due to conversion deletion
- Simulation now detects for orphaned units.
- Implemented orphan unit check post actual deletion, and the admin is notified of said units.
- Replaced teargeted `cik`row deletion with `redoCik` after a completed delete transaction.Cik is rebuilt once per request rather than per unit to avoid redundant recalculation on deeper suffix chains.
- Reformatted references to former `hide` functionality into current `delete` functionality.
- Conversion deletion logic now throws an error as opposed to logging an error.
- Resolved formatting issues.
- Refactored unit tests to call from `util/insertData` and simplified function calls
- Added clarifying notes to code functionality
- Implemented CSS features for UI display during conversion deletion
- Conversion deletion UI now created blocked segments for groups/meters that will lose the same units.
Comment thread src/client/app/components/conversion/EditConversionModalComponent.tsx Outdated
Comment thread src/client/app/components/conversion/EditConversionModalComponent.tsx Outdated
Comment thread src/client/app/components/conversion/EditConversionModalComponent.tsx Outdated
Comment thread src/client/app/components/conversion/EditConversionModalComponent.tsx Outdated
Comment thread src/server/services/graph/handleSuffixUnits.js Outdated
Comment thread src/server/services/graph/handleSuffixUnits.js
const gps = new Point(90, 45);
const { removeAdditionalConversionsAndUnits } = require('../../services/graph/handleSuffixUnits');

mocha.describe('Suffix unit cascade deletion', () => {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thank you for letting me know about the utility functions. I rewrote the tests to use insertUnits,insertConversions, and insertGroups instead of making the objects directly. Since the insertMeters appeared to require a file or data array, I kept the meter test on a direct constructor as my tests only needed a meter with a given unitID. insertStandardUnits didn't appear to have typeOfUnit === SUFFIX or non-empty suffix feilds so I kept my parent/child implementation and condensed their duplicated calls into one helper function.

Comment thread src/server/test/db/suffixUnitDeletionTests.js Outdated
Comment thread src/server/test/db/suffixUnitDeletionTests.js Outdated
@huss huss removed this from OED PR status Sep 22, 2026
@huss huss moved this to Waiting for comments to be addressed & submission updated in OED PR status Sep 22, 2026
@BunnyBea83
BunnyBea83 requested a review from huss September 23, 2026 18:00
@huss huss moved this from Waiting to Ready for review in OED PR status Sep 23, 2026
@huss

huss commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Everything below here is consider resolved by being done, another comment or planned actions outside this PR.*

I would appreciate any advice on the most appropriate way to credit the contributors for their previous work.

I think what you did was good. I appreciate your careful enumeration of what you did. I suggest the following modification to the description text:

This PR builds off of PR 1561 and PR 1470.

to be precise where you can change the wording if you think that would be better:

This PR builds off of PR #1561 by @hmhngx by incorporating parts of their work into the first five commits in this PR and PR #1470 that was used as the basis for PR #1561.

@huss

huss commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Everything below here is consider resolved either by being done, another comment or planned actions outside this PR.*

[src/server/routes/conversions.js] comment on line 186
Could you help me understand why this is cleaning up items even after the simulation that I think happened before this request. Is it to be safe? I have not tried to figure this out so I appreciate your thoughts.

Good question. Yes, it's intentional for safety. /simulate-delete and /delete are separate requests with no shared transaction. There's a real time gap between when the simulation runs (when the pop up opens) and when the admin actually confirms. In that window, the database could change (e.g. another session editing units, conversions, etc.) so the simulation results could be stale by the time the admin confirms the delete. Re-checking and re-computing inside the actual delete transaction guarantees the cleanup is correct against the live state at commit time, rather than acting on a possibly outdated preview.

That said, I think the delete functionality can use a split solution as opposed to a blanket solution since not everything carries the same risk of stale:

* **The Cascade Delete Logic** : The functionality should keep re-verifying independetly as it does currently. This is the part where stale data could cause problems since it actually removes data (e.g. a unit is classified to a meter between simulation and confirm).

* **Meter and Group IDs**: These could continue to use the meter and group IDs supplied by the client from the simulation results. These operations clear references rather than deleting the meters or groups themselves, so they have a different risk profile. This is also how the current code behaves: `/delete` uses the `meterIds` and `groupIds` sent by the client, which were derived from the` /simulate-delete` response rather than recomputed server-side during `/delete`.

A more thorough approach, worth considering separately, would be to re-run the simulation when the admin confirms the deletion and compare the results against what was originally shown. If they differ, the updated results could be displayed to the admin and require them to reconfirm before proceeding.

This would close the staleness gap more completely than either trusting the original simulation or silently re-verifying during deletion. However, I think this would be a larger change than what is currently included in the PR, so I wanted to raise it as a possible future approach rather than implement it without first confirming that it is desired.

You raised related versions of the same question on the units.js and EditConversionModalComponent.tsx threads as well. I think the same reasoning applies to both, but I wanted to consolidate the core discussion here first.

Thanks for carefully thinking about this. Here is my reflection on what you said:

  • OED has many cases where the admin can be tripped up with stale data. I believe the help pages warn admins that they should avoid having two open OED windows and interleaving changes. This includes two different admins doing it accidentally. I'll also note that if an admin changes the OED setup and then a user who already has OED loaded in the web browser does a request based on the old data it can lead to issues. This was partly addressed when the Redux Toolkit was incorporated where I believe the client cashed state automatically goes stale. When this was done it addressed a long-standing concern where an admin logged in, went away more than a day later and then tried to do work. On the submit it would fail due to expiration of the login token and work could be lost. It also has many other good attributes. Given all of this, this seems a part of a larger issue. OED has considered more comprehensive fixes but nothing has been done as it is an unusual edge case and appears to be a lot of work.
  • Your proposal to remember the original check result and validate against the final/actual delete seems good to me and I agree it is nontrivial work. Given the bullet above about all the other cases I agree it does not need to be done now.
  • Given the last two points, I wonder if this could work as a reasonable protection without too much work. I propose this since this isn't very likely to ever happen. I'll separate the mistakes/failures into a few cases:
    • A new connected conversion/unit is added and its connection is lost during the final delete without informing the admin. I think this requires something similar to your comprehensive proposal to catch but in many cases the admin might choose to remove it anyway after the second warning.
    • Something was already removed and the final cleanup tries to remove or modify it. This seems unlikely as normal checks would stop (or at least warn) the other attempt given the existing relationships. This was part of the reason this issue was addressed because of the steps needed to remove everything by hand. As long as the code is using ids to do the record and modification, they will be there unless it is removed. So, the code might silently not do the operation or it could generate an error when this happens. If it is silent then it does not seem an issue. The server code could catch any error and, if possible, continue to try to do the other steps. Continuing means more careful checks but it hopefully can be done without too much work. The admin could be told via a return message if the code wants to be really nice but may not be essential as the needed items are gone anyway.
  • A final option is not to do this in any way. If the entire process was wrapped in a transaction then any error should cause it to undo all the steps and the client can be informed that it failed so try again to see if it was transient.

There might be other cases/considerations that others see. I'm open to other ideas and modification of what I am thinking. I think either option is okay. If the (hopefully) modest fix is not done then it could become a future issue and include the full fix as an option/information so this is not lost.

@BunnyBea83

BunnyBea83 commented Sep 27, 2026 •

Copy link
Copy Markdown
Author

From @huss on 3 Oct 26: All items in this comment are considered resolved.

  • Something was already removed and the final cleanup tries to remove or modify it. This seems unlikely as normal checks would stop (or at least warn) the other attempt given the existing relationships. This was part of the reason this issue was addressed because of the steps needed to remove everything by hand. As long as the code is using ids to do the record and modification, they will be there unless it is removed. So, the code might silently not do the operation or it could generate an error when this happens. If it is silent then it does not seem an issue

As you described, the code I implemented handles the case silently. removeAdditionalConversionsAndUnits checks whether a unit still exists ,Unit.exists, before acting on it. If it's already been removed then the function logs a warning, cleans up the now stale conversion reference. and continues without stopping the rest of the cascade. Since it's silent and non-blocking, I agree an admin facing message isn't necessary here.

  • A final option is not to do this in any way. If the entire process was wrapped in a transaction then any error should cause it to undo all the steps and the client can be informed that it failed so try again to see if it was transient.

I've also implemented this option. Both /delete routes run inside conn.tx, so any unexpected errors trigger a full rollback and the client is told it failed.

  • A new connected conversion/unit is added and its connection is lost during the final delete without informing the admin. I think this requires something similar to your comprehensive proposal to catch...

I agree that this case would need an implementation of the re-simulate and compare functionality, and this implementation could be left as a follow-up/deferred.

Given that the recently pushed code covers a few of the fail cases you mentioned, I don't think additional implementation is needed here, and I can document the deferrance of re-simulate and compare into the PR description.

@huss

This comment was marked as duplicate.

@huss

huss commented Oct 4, 2026

Copy link
Copy Markdown
Member

The description says it Partly Addresses issue 1448. ...

Issue 1448 feels completed in terms of successfully removing chains ..

This is now considered resolved for this PR. The undone issues on chains will be future work and a new issue if desired (unsure if want to). The issue with bidrectional was supposed to be solved but my open PR will be augmented to address the issue I found.

I tried to find the old git commit record ...

I was unable to successfully merge the previous PR ...

The new PR description gives credit so this is now resolved.

When I went to the conversion and tried to delete it it failed (note lack of failure reason):

This failure occurs in lines 243 to 252 of conversions.js. ...

I will address this by handling this edge case ...

Done.

The admin is not used to seeing the connection to kg and Metric ton ...

I'm not sure these can be removed outright ...

Done

The delete seemed to work fine when done in this case. ...

Thanks for flagging this. Based on the structure of the code the real underlying conversion kg CO2 is never ...

I think admins should be told not to do this unless they mean it. I think the message makes it clear that it was not removed. I consider this resolved for this PR.

[src/server/routes/conversions.js] comment on line 186
Could you help me understand why this is cleaning up items ...

Good question. Yes, it's intentional for safety. ...

This is complicated and not likely to be an issue so I consider it resolved for this PR (and maybe forever).

@huss

huss commented Oct 4, 2026

Copy link
Copy Markdown
Member

This is to note that the only items left before the recent review is this comment. All others are either resolved or have a bold comment indicating that the comment is resolved. I left the comment chain so it is available for the future.

@huss huss left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@BunnyBea83 Thank you for the considerable updates to address my previous comments. I'm sorry this took a little while to review but I was trying to be systematic and not put aside anything I wanted to consider. Overall, your changes work well and I appreciate you going beyond the basic comments to make further changes. I have made some comments to consider but I think they are becoming more straightforward. I have resolved previous comments and only items with new comments are still under consideration. FYI, I feel I will need to do one complete pass of all changes when this is close to ready. This is because it is so large and comprehensive so I will want to do another high-level evaluation to be sure nothing slipped in.

These are comments that were more appropriate here rather than in files:

  • The npm run check shows these issues: Resolved.
/usr/src/app/src/client/app/components/conversion/EditConversionModalComponent.tsx
  267:80  error  Missing semicolon                                       semi
  364:47  error  Unexpected parentheses around single function argument  arrow-parens
  399:18  error  Strings must use singlequote                            quotes
  414:41  error  'i' is defined but never used                           @typescript-eslint/no-unused-vars
  441:18  error  Strings must use singlequote                            quotes
  456:41  error  'i' is defined but never used                           @typescript-eslint/no-unused-vars

✖ 6 problems (6 errors, 0 warnings)
  • I did the following: Later comments address this.
    • Create meter unit aum
    • Create unit of type unit aus with auss for suffix. Doing this requires the patch from the PR I recently opened and linked to this PR. It may work the same way if it is of type suffix but I did not try that.
    • Create unit of type unit aux & auz
    • Create conversion aux <-> auz
    • Create conversion aus -> aux
    • Create conversion from aum -> aus
    • Delete aum -> aus. It works but it incorrectly states it will remove units aux of auss & auz of auss and conversions aus -> aux of auss & auz -> aux of auss. They are not removed. Maybe they should be left so message is wrong or do you think otherwise?

Comment thread src/server/sql/group/clear_group_default_graphic_unit.sql
Comment thread src/server/sql/unit/find_orphaned_units.sql Outdated
Comment thread src/server/models/Unit.js
Comment thread src/server/sql/conversion/get_conversions_by_unit_id.sql
orphaned: boolean;
}

export interface SimulateDeletePotentiallyOrphanedUnit {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see this used outside of this file and removing the export does not seem to cause any issues for me. Is it needed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The export isn't needed so I removed the export. The two existing SimulateDeleteAffected__ interfaces are also only used within this file. However, I've left them as is since they predate this PR. I can unexport them if you'd like.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you are willing to remove those too that would be nice. Thanks for catching them.

Comment thread src/server/services/graph/handleSuffixUnits.js
Comment thread src/server/test/db/suffixUnitDeletionTests.js Outdated
Comment thread src/server/services/graph/checkUnitDependencies.js Outdated
Comment thread src/server/services/graph/checkUnitDependencies.js Outdated
const gps = new Point(90, 45);
const { removeAdditionalConversionsAndUnits } = require('../../services/graph/handleSuffixUnits');

mocha.describe('Suffix unit cascade deletion', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is okay but it would not, I think, be hard to use those functions. The insertMeters has if (meterData.file) { to load from a file so not having a proper value (I think missing is okay?) skips doing that. It then goes to a else that uses meterData.data. If it was changes to an else if that checked if meterData.data as the file check does then it would skip it if there is not data. I think that might do what is desired without impacting other uses. What do you think? If it seems okay then you can either do it or an issue can be opened up to make the change and modify your new tests as needed.

- Resolved npm run check errors
- Removed export from SimulateDeletePotentiallyOrphanedUnit
- Corrected spelling errors and formatted descriptions
- Altered SQL files to include the name of the model they are referencing
- Modified the model files calling the SQL functions to include the changed names.
- Modified `findOrphanedUnits`  to reference a specified limit
- Implemented a deleteAll feature for Cik
- redoCik is called in conversions.js and units.js
- Modified CheckUnitDependencies to call the deleteAll on Cik as opposed to deleteByUnitId
- Removed deleteByUnitId SQL call
While testing, the delete confirmation listed an already orphaned unit as a unit that would be orphaned due to the deletion. `potentiallyOrphanedUnits` has now been restricted to units that lose their last conversion in the delete.
@BunnyBea83

Copy link
Copy Markdown
Author

I did the following:

  • Create meter unit aum
  • Create unit of type unit aus with auss for suffix. Doing this requires the patch from the PR I recently opened and linked to this PR. It may work the same way if it is of type suffix but I did not try that.
  • Create unit of type unit aux & auz
  • Create conversion aux <-> auz
  • Create conversion aus -> aux
  • Create conversion from aum -> aus
  • Delete aum -> aus. It works but it incorrectly states it will remove units aux of auss & auz of auss and conversions aus -> aux of auss & auz -> aux of auss. They are not removed. Maybe they should be left so message is wrong or do you think otherwise?

The units are removed and then recreated. Deleting aum → aus calls the cleanup for aus, since it has a suffix, and then redoCik runs handleSuffixUnits, which regenerates aux of auss and auz of auss because aus still has those paths. In a test of this conversion delete, I saw the client call refresh right after delete, which is consistent with that rebuild. So the end state looks right, but the warning is misleading and any admin edits to those derived units would be lost. I agree they should be left alone when deleting an incoming conversion. Would you like me to skip the cleanup and the warning in that case, or should it be handled in a follow-up?

@BunnyBea83
BunnyBea83 requested a review from huss October 5, 2026 03:08
@huss

huss commented Oct 6, 2026

Copy link
Copy Markdown
Member

Would you like me to skip the cleanup and the warning in that case, or should it be handled in a follow-up?

Thanks for clearly explaining what was happening. Assuming the change does not impact other cases then I think it should be skipped. I'm assuming doing this is not hard.

@huss huss left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@BunnyBea83 Thank you again for the quick update that addressed my comments along with the comprehensive evaluation. Overall, most items are now resolved. I made a few comments to clarify what I prefer or for minor items. I wanted to get this back quickly so I have not done my final sweep - it would be final anyway if all changes were basically done so waiting would be easier. Please let me know if anything is not clear or you have any thoughts.

orphaned: boolean;
}

export interface SimulateDeletePotentiallyOrphanedUnit {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you are willing to remove those too that would be nice. Thanks for catching them.

Comment thread src/server/sql/conversion/get_conversions_by_unit_id.sql
Comment thread src/server/models/Unit.js
Comment thread src/server/models/Unit.js
* Finds units that may have been orphaned (still displayable but with no
* remaining conversions). Used as a post-delete sanity check, not a guarantee.
* @param {*} conn The connection to use.
* @param {number} [limit=100] The maximum number of units to return.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

From what I see, the JSDoc displayed (at least in VSC) does not show the limit. I think in other places OED puts the default value in the description text and that might be a benefit here. It isn't a big deal as the function signature shows the default.

const gps = new Point(90, 45);
const { removeAdditionalConversionsAndUnits } = require('../../services/graph/handleSuffixUnits');

mocha.describe('Suffix unit cascade deletion', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sounds good. Do you want to open (you certainly did more investigation of it than I did) the issue or should I?

// Full Cik recalculation after all cascading deletes complete, to
// correctly handle any unit pairs whose path routed through a deleted
// unit, not just the units/conversions directly touched above.
await redoCik(conn);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I noticed this since you have changed this units route in this file. Thus, if you are willing and see it the same way, it would be nice to fix up now.

I was also thinking that using the refresh is the way to go but was not sure how you might feel about doing that. Since you raised it, I think it is a good way to go. The refresh api has these invalidations: ['ConversionDetails', 'Cik', 'Readings', 'Units']. Given this, I didn't think those need to be invalidated here (I did not see the other routes using this refresh doing it). If this is right then you would only need to invalidate the meter and group data.

Comment thread src/server/services/graph/handleSuffixUnits.js
@github-project-automation github-project-automation Bot moved this from Ready for review to Waiting in OED PR status Oct 6, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Waiting

Development

Successfully merging this pull request may close these issues.

2 participants