Skip to content

Conversation

@beygorghor
Copy link
Collaborator

@beygorghor beygorghor commented Nov 26, 2025

Adapt IASO to fit to the latest LQAS pipeline on Openhexa

Related JIRA tickets : IA-4626

Self proofreading checklist

  • Did I use eslint and ruff formatters?
  • Is my code clear enough and well documented?
  • Are my typescript files well typed?
  • New translations have been added or updated if new strings have been introduced in the frontend
  • My migrations file are included
  • Are there enough tests?
  • Documentation has been included (for new feature)

Doc

Changes

  • allow patch on task to save result
  • refresh org unit when pipeline is done and display only org unit that are in the group that the pipeline created

How to test

  • Make sure you have a Openhexa workspace and instances configured like this (you can find the token on openhexa)
Screenshot 2025-11-26 at 10 29 13 Screenshot 2025-11-26 at 10 29 04 - Make sure you have a planning using lqas pipeline: Screenshot 2025-11-26 at 10 33 18 - click on view planning Screenshot 2025-11-26 at 10 34 32
  • make sure basic organisation type is at a level you actually see something on the map, health facility , ...
  • click on sampling, play with the levels and the quantity, you need to arrive at the same level as the basic organisation type you selected before
  • Launch the pipeline and wait (don't forget your worker here)
  • when pipeline is successful, org unit on the map should be different, displaying selected org unit selected by the LQAS pipeline.

Print screen / video

Screen.Recording.2025-11-28.at.11.34.26.mov

Notes

  • planning view should be refactor and will be part of another PR
  • code of the pipeline is in another repo and can be probably optimized too

Follow the Conventional Commits specification

The merge message of a pull request must follow the Conventional Commits specification.

This convention helps to automatically generate release notes.

Use lowercase for consistency.

Example:

fix: empty instance pop up

Refs: IA-3665

Note that the Jira reference is preceded by a line break.

Both the line break and the Jira reference are entered in the Add an optional extended description… field.

@beygorghor beygorghor marked this pull request as ready for review November 28, 2025 11:06
@beygorghor beygorghor added the postrelease Should be merged just after the release label Nov 28, 2025
Copy link
Member

@tdethier tdethier left a comment

Choose a reason for hiding this comment

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

The feature works fine 👍

I'm requesting changes because I think that we are missing some checks in geo restrictions

Comment on lines 88 to 93
org_units = validated_data.pop("org_units", [])
default_version = self._fetch_user_default_source_version()
validated_data["source_version"] = default_version
return super().create(validated_data)
group = super().create(validated_data)
if org_units:
group.org_units.set(org_units)
Copy link
Member

Choose a reason for hiding this comment

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

I thought that org_units was the field used for read operations and org_unit_ids for write operations. Why do you use org_units in create()?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines 250 to 255
marvel = self.raccoon.iaso_profile.account
marvel_data_source = m.DataSource.objects.create(name="Marvel source")
marvel_version = m.SourceVersion.objects.create(data_source=marvel_data_source, number=1)
marvel_org_unit_type = m.OrgUnitType.objects.create(name="Marvel HC", short_name="MHC")
marvel_org_unit = m.OrgUnit.objects.create(
name="Marvel Org Unit", version=marvel_version, org_unit_type=marvel_org_unit_type
Copy link
Member

Choose a reason for hiding this comment

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

Someone is not going to be happy with these names 😁

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 followed the mood of the file sorry

Copy link
Member

Choose a reason for hiding this comment

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

That would be me. I'm sorry, but I'm blocking over this. Please rename using expressive names like user_from_different_account. It really makes understanding, debugging and refactoring easier for everyone

Comment on lines 294 to 311
def test_groups_create_with_org_units_user_with_no_restrictions(self):
"""POST /groups/ user with no editable_org_unit_types restrictions can add any org unit"""

special_org_unit_type = m.OrgUnitType.objects.create(name="Special Type", short_name="ST")
special_org_unit = m.OrgUnit.objects.create(
name="Special Org Unit", version=self.source_version_2, org_unit_type=special_org_unit_type
)

self.client.force_authenticate(self.yoda)
response = self.client.post(
"/api/groups/",
data={"name": "test group", "org_unit_ids": [special_org_unit.id, self.org_unit_1.id]},
format="json",
)
self.assertJSONResponse(response, 201)

group = m.Group.objects.get(id=response.json()["id"])
self.assertEqual(group.org_units.count(), 2)
Copy link
Member

Choose a reason for hiding this comment

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

That's already what you check in the happy path, I'd say we can get rid of this test

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines +69 to +71
editable_org_unit_type_ids = profile.get_editable_org_unit_type_ids()
if editable_org_unit_type_ids:
queryset = queryset.filter(org_unit_type_id__in=editable_org_unit_type_ids)
Copy link
Member

@tdethier tdethier Dec 1, 2025

Choose a reason for hiding this comment

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

That's one half of the geo restrictions covered, great!

A user can also be restricted to some branches of a pyramid (org_units in Profile) , so I guess we need to make sure that the received IDs belong to the pyramid section of that user 😅

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Member

@quang-le quang-le left a comment

Choose a reason for hiding this comment

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

Just code review without feature test at this stage.
Nice job with spotting the buggy behaviour with staleTime

selectedItem,
search,
}: Props): Result => {
const [extraFilters, setExtraFilters] = useState<Record<string, any>>({});
Copy link
Member

Choose a reason for hiding this comment

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

if it goes in the params isn't it Record<string,string>?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

setSelectedPipelineId(value);
setParameterValues(undefined);
setAllowConfirm(false);
}, []);
Copy link
Member

Choose a reason for hiding this comment

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

Don't use useCallbackwith empty deps array 😠

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines +176 to +181
useEffect(() => {
if (taskDetails?.result?.group_id) {
setExtraFilters({
group: taskDetails.result.group_id,
});
}
Copy link
Member

Choose a reason for hiding this comment

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

Won't this override any existing extraFilters?
Reminder: there's a useObjectState utils that allows us to use useState syntax with objects similar to the setState of class components

Also, instead of prop drilling setExtraFilters you could pass it through a Context, but maybe there's a perf price to pay

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 would leave it like that, next step is to refactor this part of the code

Copy link
Member

Choose a reason for hiding this comment

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

OK then

Comment on lines 182 to -205
useEffect(() => {
if (
parameterValues?.org_unit_type_sequence_identifiers?.length &&
parameterValues?.org_unit_type_sequence_identifiers?.length > 0 &&
!canAddLevel
org_unit_type_sequence_identifiers?.length &&
org_unit_type_sequence_identifiers?.length > 0
) {
const allLevelsFilled =
parameterValues.org_unit_type_sequence_identifiers?.every(
(level, index) => {
return (
level !== undefined &&
parameterValues.org_unit_type_criteria?.[index] !==
undefined &&
parameterValues.org_unit_type_quantities?.[
index
] !== undefined
);
},
);
setAllowConfirm(Boolean(allLevelsFilled));
const allLevelsFilled = org_unit_type_sequence_identifiers?.every(
(level, index) => {
return (
level !== undefined &&
org_unit_type_criteria?.[index] !== undefined &&
org_unit_type_quantities?.[index] !== undefined
);
},
);

setAllowConfirm(
Boolean(allLevelsFilled) &&
(!planning.target_org_unit_type ||
latestOptions?.value === planning.target_org_unit_type),
);
} else {
setAllowConfirm(false);
}
}, [setAllowConfirm, parameterValues, canAddLevel]);
Copy link
Member

Choose a reason for hiding this comment

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

The goal of this useEffect seems to be to set the value of 'allowConfirm. If so, it would be better to do it via useMemo`

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 don't get it, the setAllowConfirm is level above

Copy link
Member

Choose a reason for hiding this comment

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

OK I get it now

Comment on lines 53 to 54
created_at = TimestampField(read_only=True)
updated_at = TimestampField(read_only=True)
Copy link
Member

Choose a reason for hiding this comment

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

Don't we usually use DateField or DateTimeField for the web ?

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 a bit everywhere

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, but didn't we want to move away from Timestamps in the front-end?

Comment on lines 250 to 255
marvel = self.raccoon.iaso_profile.account
marvel_data_source = m.DataSource.objects.create(name="Marvel source")
marvel_version = m.SourceVersion.objects.create(data_source=marvel_data_source, number=1)
marvel_org_unit_type = m.OrgUnitType.objects.create(name="Marvel HC", short_name="MHC")
marvel_org_unit = m.OrgUnit.objects.create(
name="Marvel Org Unit", version=marvel_version, org_unit_type=marvel_org_unit_type
Copy link
Member

Choose a reason for hiding this comment

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

That would be me. I'm sorry, but I'm blocking over this. Please rename using expressive names like user_from_different_account. It really makes understanding, debugging and refactoring easier for everyone

@beygorghor beygorghor requested a review from tdethier December 1, 2025 16:38
@beygorghor beygorghor requested a review from quang-le December 1, 2025 16:47
Copy link
Member

@tdethier tdethier left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for making the changes 👍

@beygorghor beygorghor merged commit 7488c94 into develop Dec 2, 2025
7 checks passed
@beygorghor beygorghor deleted the feat/IA-4626-lqas-pipeline branch December 2, 2025 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

postrelease Should be merged just after the release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants