-
Notifications
You must be signed in to change notification settings - Fork 29
chore(refactor): add unit selectors and utils #2220
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
base: main
Are you sure you want to change the base?
Conversation
f6f4875 to
aafb632
Compare
andogq
left a comment
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.
- With all the unit selectors, the extra machinery to handle parent/subordinate really complicates things. Is it possible to unify/simplify the logic dealing with subordinates, even just within each method?
| export const getAppScale = ( | ||
| appId: string, | ||
| applications: Record<string, ApplicationStatus>, | ||
| ): number => { | ||
| const app = appId in applications ? applications[appId] : null; | ||
| if (!app) { | ||
| return 0; | ||
| } | ||
| return app["subordinate-to"].length | ||
| ? app["subordinate-to"].reduce<number>( | ||
| (count, parentId) => | ||
| count + | ||
| (parentId in applications | ||
| ? // A subordinate exists on every parent unit, so just count the parent units rather than | ||
| // checking for the subordinate id. | ||
| Object.keys(applications[parentId].units ?? {}).length | ||
| : 0), | ||
| 0, | ||
| ) | ||
| : Object.keys(app.units ?? {}).length; | ||
| }; |
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 a little bit of a ternary hater in favour of clarity, but feel free to take it or leave it.
| export const getAppScale = ( | |
| appId: string, | |
| applications: Record<string, ApplicationStatus>, | |
| ): number => { | |
| const app = appId in applications ? applications[appId] : null; | |
| if (!app) { | |
| return 0; | |
| } | |
| return app["subordinate-to"].length | |
| ? app["subordinate-to"].reduce<number>( | |
| (count, parentId) => | |
| count + | |
| (parentId in applications | |
| ? // A subordinate exists on every parent unit, so just count the parent units rather than | |
| // checking for the subordinate id. | |
| Object.keys(applications[parentId].units ?? {}).length | |
| : 0), | |
| 0, | |
| ) | |
| : Object.keys(app.units ?? {}).length; | |
| }; | |
| export const getAppUnits = (app: ApplicationStatus): number => { | |
| return Object.keys(app.units ?? {}).length; | |
| } | |
| export const getAppScale = ( | |
| appId: string, | |
| applications: Record<string, ApplicationStatus>, | |
| ): number => { | |
| if (!(appId in applications)) { | |
| return 0; | |
| } | |
| const app = applications[appId]; | |
| if (app["subordinate-to"].length > 0) { | |
| return app["subordinate-to"] | |
| .map((parentId) => applications[parentId]) | |
| .filter((parent) => parent !== undefined) | |
| // A subordinate exists on every parent unit, so just count the parent units rather than | |
| // checking for the subordinate id. | |
| .reduce((count, parent) => count + getAppUnits(parent), 0); | |
| } else { | |
| return getAppUnits(app); | |
| } | |
| }; |
| applications: null | Record<string, ApplicationStatus>, | ||
| ): number => { | ||
| if (!applications) { | ||
| return 0; | ||
| } |
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 sure where this is called (I assume in the next PR), but can we avoid having applications potentially be null? This check feels better suited at the callsite, and would keep things consistent with getAppScale and other calls.
| applications: null | Record<string, ApplicationStatus>, | |
| ): number => { | |
| if (!applications) { | |
| return 0; | |
| } | |
| applications: Record<string, ApplicationStatus>, | |
| ): number => { |
| if (applications[appId]["subordinate-to"].length) { | ||
| return ( | ||
| count + | ||
| getAppScale( | ||
| appId, | ||
| // Only include the parent if its id is not in the applicationIds list. | ||
| applications[appId]["subordinate-to"].reduce< | ||
| Record<string, ApplicationStatus> | ||
| >( | ||
| (parents, subordinateTo) => { | ||
| if ( | ||
| applicationIds.includes(subordinateTo) || | ||
| counted.includes(subordinateTo) | ||
| ) { | ||
| // Ignore it if the parent application will be counted. | ||
| return parents; | ||
| } | ||
| counted.push(subordinateTo); | ||
| parents[subordinateTo] = applications[subordinateTo]; | ||
| return parents; | ||
| }, | ||
| { [appId]: applications[appId] }, | ||
| ), | ||
| ) | ||
| ); | ||
| } | ||
| return count + getAppScale(appId, applications); |
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 quite difficult to follow. Potentially breaking the selection of applications out may help, but I'm not 100% sure. The interaction between this method pre-empting work done by the getAppScale method (in terms of searching for parent units) feels quite complex, and some of the logic feels very similar. Could these two be simplified or unified in some way?
| if (applications[appId]["subordinate-to"].length) { | |
| return ( | |
| count + | |
| getAppScale( | |
| appId, | |
| // Only include the parent if its id is not in the applicationIds list. | |
| applications[appId]["subordinate-to"].reduce< | |
| Record<string, ApplicationStatus> | |
| >( | |
| (parents, subordinateTo) => { | |
| if ( | |
| applicationIds.includes(subordinateTo) || | |
| counted.includes(subordinateTo) | |
| ) { | |
| // Ignore it if the parent application will be counted. | |
| return parents; | |
| } | |
| counted.push(subordinateTo); | |
| parents[subordinateTo] = applications[subordinateTo]; | |
| return parents; | |
| }, | |
| { [appId]: applications[appId] }, | |
| ), | |
| ) | |
| ); | |
| } | |
| return count + getAppScale(appId, applications); | |
| let candidateApplications = applications; | |
| if (applications[appId]["subordinate-to"].length) { | |
| // Only include the parent if its id is not in the applicationIds list. | |
| candidateApplications = applications[appId]["subordinate-to"].reduce< | |
| Record<string, ApplicationStatus> | |
| >( | |
| (parents, subordinateTo) => { | |
| if ( | |
| !applicationIds.includes(subordinateTo) && | |
| !counted.includes(subordinateTo) | |
| ) { | |
| // Only count if the parent application won't be counted. | |
| counted.push(subordinateTo); | |
| parents[subordinateTo] = applications[subordinateTo]; | |
| } | |
| return parents; | |
| }, | |
| { [appId]: applications[appId] }, | |
| ); | |
| } | |
| return count + getAppScale(appId, candidateApplications); |
| applications: null | Record<string, ApplicationStatus>, | ||
| ): Record<string, ApplicationStatus> => { | ||
| const appsOnMachine: Record<string, ApplicationStatus> = {}; | ||
| if (!applications) { | ||
| return appsOnMachine; | ||
| } |
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.
Same deal here with applications being null
| if (!applications) { | ||
| return appsOnMachine; | ||
| } | ||
| Object.entries(applications).forEach(([appName, app]) => { |
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.
🚨🚨🚨 MEGA nit 🚨🚨🚨, but using a normal for loop is significantly faster than .forEach (or any iterable method for that matter) (same goes for below)
| for (const unitInfo of Object.values(app.units ?? {})) { | ||
| if (machineId === unitInfo.machine) { | ||
| appsOnMachine[appName] = app; | ||
| Object.keys(unitInfo.subordinates ?? {}).forEach((subordinateName) => { | ||
| const [parentAppName] = subordinateName.split("/"); | ||
| const parentApp = | ||
| parentAppName in applications ? applications[parentAppName] : null; | ||
| if ( | ||
| parentApp && | ||
| parentAppName in applications && | ||
| !(parentAppName in appsOnMachine) | ||
| ) { | ||
| appsOnMachine[parentAppName] = parentApp; | ||
| } | ||
| }); | ||
| // Only one unit of each application can be on the machine | ||
| // so exit the loop if a unit was found. | ||
| break; | ||
| } | ||
| } |
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 feels a little difficult to follow. Could it be split into two parts, first finding any applications with a unit on the machine, and then finding any parent applications for those applications?
andogq
left a comment
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.
- With all the unit selectors, the extra machinery to handle parent/subordinate really complicates things. Is it possible to unify/simplify the logic dealing with subordinates, even just within each method?
- Iterable
forEachcould probably be swapped out for a standardforloop. Similar or other iterable methods wouldn't hurt either applicationsparameter beingnullshould be handled at the callsite, rather than being an allowed type- There are a lot of checks for a given
appIdbeing present inapplications. Is it actually possible forapplicationsto be partially populated, missing an application that's defined elsewhere (aside from that one method where we selectively add applications to search) collection && key in collection ? collection[key] : nullcould probably be simplified tocollection?.[key] ?? null(unless out eslint rules prevent this, can't remember what they let us do in this circumstance)
| for (const unitInfo of Object.values(app.units ?? {})) { | ||
| if (machineId === unitInfo.machine) { |
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.
Can this method be implemented using getMachineUnits? Logic seems very similar
| */ | ||
| export const getMachineUnits = ( | ||
| machineId: string, | ||
| applications: null | Record<string, ApplicationStatus>, |
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.
applications being null again
| Object.entries(unitInfo.subordinates ?? {}).forEach( | ||
| ([subordinateId, subordinate]) => { | ||
| unitsOnMachine[subordinateId] = subordinate; | ||
| }, | ||
| ); |
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.
Could use Object.assign here instead
| const [appId] = unitId.split("/"); | ||
| const app = appId in applications ? applications[appId] : null; | ||
| if (!app?.["subordinate-to"].length) { | ||
| return null; | ||
| } |
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 similar logic for selecting a parent application is also present in getMachineApps. Perhaps another selector could be used to simplify this?
| for (const subordinateName of Object.keys( | ||
| parentUnit.subordinates ?? {}, | ||
| )) { | ||
| if (subordinateName === unitId) { | ||
| return parentUnit; | ||
| } | ||
| } |
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.
Am I misunderstanding this, or is it searching through the parent unit's subordinates to verify that the unitId is present?
Won't this always be true if an app's subordinate-to is populated? Or is it possible for these to be mis-matched, hence the extra check required?
| /** | ||
| * Get the application for a unit. | ||
| */ | ||
| export const getUnitApp = ( |
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 probably could be used in quite a few of the other selectors
| /** | ||
| * Get a unit from its parent if it is a subordinate, or else get the unit from the unit's application. | ||
| */ | ||
| export const getUnitMachine = ( |
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 also believe this could be used in some other methods.
| const application = | ||
| applications && appId in applications ? applications[appId] : null; | ||
| if (!applications || !machines || !application) { | ||
| return machinesForApp; |
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 seems a little inconsistent, as in other methods null is returned as the default case. I don't mind which is used (null vs empty object), but I feel like keeping them the same wouldn't hurt.
| if (application["subordinate-to"].length) { | ||
| application["subordinate-to"].forEach((appName) => { | ||
| const app = appName in applications ? applications[appName] : null; | ||
| if (app) { | ||
| Object.values(app.units ?? {}).forEach((parentUnit) => { | ||
| if (parentUnit.machine in machines) { | ||
| machinesForApp[parentUnit.machine] = machines[parentUnit.machine]; | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| } else { | ||
| Object.values(application.units ?? {}).forEach((unitData) => { | ||
| if (unitData.machine in machines) { | ||
| machinesForApp[unitData.machine] = machines[unitData.machine]; | ||
| } | ||
| }); | ||
| } |
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 (application["subordinate-to"].length) { | |
| application["subordinate-to"].forEach((appName) => { | |
| const app = appName in applications ? applications[appName] : null; | |
| if (app) { | |
| Object.values(app.units ?? {}).forEach((parentUnit) => { | |
| if (parentUnit.machine in machines) { | |
| machinesForApp[parentUnit.machine] = machines[parentUnit.machine]; | |
| } | |
| }); | |
| } | |
| }); | |
| } else { | |
| Object.values(application.units ?? {}).forEach((unitData) => { | |
| if (unitData.machine in machines) { | |
| machinesForApp[unitData.machine] = machines[unitData.machine]; | |
| } | |
| }); | |
| } | |
| let targetUnits; | |
| if (application["subordinate-to"].length) { | |
| targetUnits = application["subordinate-to"] | |
| .map((appName) => applications[appName]?.units ?? {}) | |
| .reduce((targetUnits, units) => Object.assign(targetUnits, units), {}); | |
| } else { | |
| targetUnits = application.units; | |
| } | |
| for (const unitData of Object.values(targetUnits)) { | |
| if (unitData.machine in machines) { | |
| machinesForApp[unitData.machine] = machines[unitData.machine]; | |
| } | |
| } |
Separating the selection of units from the processing of them might help cut down on duplicated code.
| if (application["subordinate-to"].length) { | ||
| const unitsForApp: Record<string, UnitStatus> = {}; | ||
| application["subordinate-to"].forEach((appName) => { | ||
| const app = appName in applications ? applications[appName] : null; | ||
| if (app) { | ||
| Object.values(app.units ?? {}).forEach((parentUnit) => { | ||
| Object.entries(parentUnit.subordinates ?? {}).forEach( | ||
| ([subordinateName, subordinateUnit]) => { | ||
| if (subordinateName.split("/")[0] === appId) { | ||
| unitsForApp[subordinateName] = subordinateUnit; | ||
| } | ||
| }, | ||
| ); | ||
| }); | ||
| } | ||
| }); | ||
| return unitsForApp; | ||
| } | ||
| return application.units; |
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.
Could getParentOrUnit be used here instead?
Done
Precursor to #2219