Skip to content

Commit b2f6f8f

Browse files
author
Priyadarshini Piramanayagam
committed
add callback for measurement filtering
1 parent 35b91de commit b2f6f8f

File tree

2 files changed

+83
-36
lines changed

2 files changed

+83
-36
lines changed

nisystemlink/clients/testmonitor/utilities/_dataframe_utilities.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict, List
1+
from typing import Any, Callable, Dict, List, Optional
22

33
import pandas as pd
44
from nisystemlink.clients.testmonitor.models import Result, Step, StepProjection
@@ -39,11 +39,19 @@ def convert_results_to_dataframe(
3939
return normalized_dataframe
4040

4141

42-
def convert_steps_to_dataframe(steps: List[Step]) -> pd.DataFrame:
42+
def convert_steps_to_dataframe(
43+
steps: List[Step],
44+
is_valid_measurement: Optional[Callable[[Dict[str, Any]], bool]] = None,
45+
) -> pd.DataFrame:
4346
"""Converts a list of steps into a normalized dataframe.
4447
4548
Args:
4649
steps: A list of steps.
50+
is_valid_measurement: Optional function to check if a measurement is valid. The method takes
51+
a dictionary as input and returns a boolean value. If the function is not provided, the
52+
default behavior is to keep only those measurements that have both 'name' and 'measurement' fields.
53+
If none of the measurement data have the desired fields, the data.parameters will not
54+
appear in the dataframe.
4755
4856
Returns:
4957
DataFrame:
@@ -57,7 +65,7 @@ def convert_steps_to_dataframe(steps: List[Step]) -> pd.DataFrame:
5765
all other step fields are duplicated.
5866
"""
5967
DATA_PARAMETERS = "data.parameters"
60-
step_dicts = __convert_steps_to_dict(steps)
68+
step_dicts = __convert_steps_to_dict(steps, is_valid_measurement)
6169
steps_dataframe = pd.json_normalize(step_dicts, sep=".")
6270
steps_dataframe = __explode_and_normalize(
6371
steps_dataframe, DATA_PARAMETERS, f"{DATA_PARAMETERS}."
@@ -156,7 +164,10 @@ def __is_property_header(header: str) -> bool:
156164
return header.startswith(DataFrameHeaders.PROPERTY_COLUMN_HEADER_PREFIX)
157165

158166

159-
def __convert_steps_to_dict(steps: List[Step]) -> List[Dict[str, Any]]:
167+
def __convert_steps_to_dict(
168+
steps: List[Step],
169+
is_valid_measurement: Optional[Callable[[Dict[str, Any]], bool]] = None,
170+
) -> List[Dict[str, Any]]:
160171
"""Converts a list of steps to dictionaries, excluding None values.
161172
162173
Args:
@@ -168,12 +179,44 @@ def __convert_steps_to_dict(steps: List[Step]) -> List[Dict[str, Any]]:
168179
steps_dict = []
169180
for step in steps:
170181
single_step_dict = step.dict(exclude_none=True)
182+
183+
if step.data is not None and step.data.parameters is not None:
184+
single_step_dict["data"]["parameters"] = __get_valid_data_parameters(
185+
single_step_dict, is_valid_measurement
186+
)
187+
171188
__normalize_inputs_outputs(single_step_dict, step)
172189
__normalize_step_status(single_step_dict)
173190
steps_dict.append(single_step_dict)
174191
return steps_dict
175192

176193

194+
def __get_valid_data_parameters(
195+
step_dict: Dict[str, Any],
196+
is_valid_measurement: Optional[Callable[[Dict[str, Any]], bool]] = None,
197+
) -> List[Dict[str, Any]]:
198+
"""Gets valid measurement data parameters from the step dictionary.
199+
200+
Args:
201+
step_dict: A dictionary with step information.
202+
is_valid_measurement: Optional callback function to check if a measurement is valid. The method takes
203+
a dictionary as input and returns a boolean value. If the function is not provided, the
204+
default behavior is to keep only those measurements that have both 'name' and 'measurement' fields.
205+
206+
Returns:
207+
List[Dict[str, Any]]: A list of dictionaries containing valid measurement data.
208+
"""
209+
allowed_measurement_keys = ["name", "units"]
210+
valid_measurement_parameters = []
211+
for parameter in step_dict["data"]["parameters"]:
212+
if (is_valid_measurement and is_valid_measurement(parameter)) or all(
213+
key in parameter for key in allowed_measurement_keys
214+
):
215+
valid_measurement_parameters.append(parameter)
216+
217+
return valid_measurement_parameters
218+
219+
177220
def __normalize_inputs_outputs(
178221
step_dict: Dict[str, Any],
179222
step: Step,

tests/testmonitor/test_testmonitor_dataframe_utilities.py

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ def mock_steps_data() -> List[Step]:
174174
"status": "Passed",
175175
"lowLimit": "7.0",
176176
"highLimit": "22.0",
177-
"measurement": "12.0",
178177
"comparisonType": "GTLT",
179178
},
180179
],
@@ -210,38 +209,42 @@ def expected_steps_dataframe(mock_steps_data: List[Step]) -> pd.DataFrame:
210209
if not (step.data and step.data.parameters):
211210
continue
212211
for parametric_data in step.data.parameters:
213-
parameter_data = {
214-
f"data.parameters.{key}": value
215-
for key, value in parametric_data.dict().items()
216-
}
212+
if (
213+
"name" in parametric_data.dict()
214+
and "measurement" in parametric_data.dict()
215+
):
216+
parameter_data = {
217+
f"data.parameters.{key}": value
218+
for key, value in parametric_data.dict().items()
219+
}
217220

218-
restructured_step = {
219-
"name": step.name,
220-
"step_type": step.step_type,
221-
"step_id": step.step_id,
222-
"parent_id": step.parent_id,
223-
"result_id": step.result_id,
224-
"path": step.path,
225-
"path_ids": step.path_ids,
226-
"status": (
227-
step.status.status_type.value
228-
if step.status and step.status.status_type != "CUSTOM"
229-
else step.status.status_name if step.status else None
230-
),
231-
"total_time_in_seconds": step.total_time_in_seconds,
232-
"started_at": step.started_at,
233-
"updated_at": step.updated_at,
234-
"data_model": step.data_model,
235-
"has_children": step.has_children,
236-
"workspace": step.workspace,
237-
"keywords": step.keywords,
238-
**inputs,
239-
**outputs,
240-
"data.text": step.data.text,
241-
**parameter_data,
242-
**properties,
243-
}
244-
restructured_mock_steps.append(restructured_step)
221+
restructured_step = {
222+
"name": step.name,
223+
"step_type": step.step_type,
224+
"step_id": step.step_id,
225+
"parent_id": step.parent_id,
226+
"result_id": step.result_id,
227+
"path": step.path,
228+
"path_ids": step.path_ids,
229+
"status": (
230+
step.status.status_type.value
231+
if step.status and step.status.status_type != "CUSTOM"
232+
else step.status.status_name if step.status else None
233+
),
234+
"total_time_in_seconds": step.total_time_in_seconds,
235+
"started_at": step.started_at,
236+
"updated_at": step.updated_at,
237+
"data_model": step.data_model,
238+
"has_children": step.has_children,
239+
"workspace": step.workspace,
240+
"keywords": step.keywords,
241+
**inputs,
242+
**outputs,
243+
"data.text": step.data.text,
244+
**parameter_data,
245+
**properties,
246+
}
247+
restructured_mock_steps.append(restructured_step)
245248
expected_dataframe = pd.DataFrame(restructured_mock_steps)
246249
expected_column_order = [
247250
"name",
@@ -291,6 +294,7 @@ def empty_steps_data() -> List:
291294

292295

293296
@pytest.mark.enterprise
297+
@pytest.mark.unit
294298
class TestTestmonitorDataframeUtilities:
295299
def test__convert_results_with_all_fields_to_dataframe__returns_whole_results_dataframe(
296300
self, results

0 commit comments

Comments
 (0)