Skip to content

Conversation

@huwshimi
Copy link
Contributor

Done

  • add selectors and utils for getting units from modelData

Precursor to #2219

@webteam-app
Copy link

@huwshimi huwshimi force-pushed the allwatcher-unit-selectors branch from f6f4875 to aafb632 Compare January 28, 2026 22:29
Copy link
Member

@andogq andogq left a 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?

Comment on lines +11 to +31
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;
};
Copy link
Member

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.

Suggested change
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);
}
};

Comment on lines +38 to +42
applications: null | Record<string, ApplicationStatus>,
): number => {
if (!applications) {
return 0;
}
Copy link
Member

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.

Suggested change
applications: null | Record<string, ApplicationStatus>,
): number => {
if (!applications) {
return 0;
}
applications: Record<string, ApplicationStatus>,
): number => {

Comment on lines +51 to +77
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);
Copy link
Member

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?

Suggested change
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);

Comment on lines +86 to +91
applications: null | Record<string, ApplicationStatus>,
): Record<string, ApplicationStatus> => {
const appsOnMachine: Record<string, ApplicationStatus> = {};
if (!applications) {
return appsOnMachine;
}
Copy link
Member

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]) => {
Copy link
Member

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)

Comment on lines +97 to +116
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;
}
}
Copy link
Member

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?

Copy link
Member

@andogq andogq left a 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 forEach could probably be swapped out for a standard for loop. Similar or other iterable methods wouldn't hurt either
  • applications parameter being null should be handled at the callsite, rather than being an allowed type
  • There are a lot of checks for a given appId being present in applications. Is it actually possible for applications to 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] : null could probably be simplified to collection?.[key] ?? null (unless out eslint rules prevent this, can't remember what they let us do in this circumstance)

Comment on lines +97 to +98
for (const unitInfo of Object.values(app.units ?? {})) {
if (machineId === unitInfo.machine) {
Copy link
Member

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>,
Copy link
Member

Choose a reason for hiding this comment

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

applications being null again

Comment on lines +138 to +142
Object.entries(unitInfo.subordinates ?? {}).forEach(
([subordinateId, subordinate]) => {
unitsOnMachine[subordinateId] = subordinate;
},
);
Copy link
Member

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

Comment on lines +163 to +167
const [appId] = unitId.split("/");
const app = appId in applications ? applications[appId] : null;
if (!app?.["subordinate-to"].length) {
return null;
}
Copy link
Member

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?

Comment on lines +175 to +181
for (const subordinateName of Object.keys(
parentUnit.subordinates ?? {},
)) {
if (subordinateName === unitId) {
return parentUnit;
}
}
Copy link
Member

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 = (
Copy link
Member

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 = (
Copy link
Member

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;
Copy link
Member

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.

Comment on lines +269 to +286
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];
}
});
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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.

Comment on lines +302 to +320
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;
Copy link
Member

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?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants