Skip to content

Commit 0816477

Browse files
update: Flowr now handles user error when building and running flows
1 parent c43d891 commit 0816477

1 file changed

Lines changed: 145 additions & 127 deletions

File tree

app/api/utils/flowr.py

Lines changed: 145 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def get_node_by_id(self, id: str=None) -> dict:
240240

241241

242242

243-
243+
244244
def objects_are_complete(self, object_list: list=[]) -> bool:
245245
"""
246246
Iterates through the object_list of a given node
@@ -382,6 +382,81 @@ def finalize_node(self, index: int=None) -> None:
382382

383383

384384

385+
def complete_flowrun(self, current_data: dict=None) -> None:
386+
"""
387+
Checks for run completion and updates final
388+
run status.
389+
390+
Expects: {
391+
'current_data': dict
392+
}
393+
394+
Returns: `FlowRun` object
395+
"""
396+
397+
# defaults
398+
status = 'passed'
399+
nodes = self.flowrun.nodes
400+
logs = self.flowrun.logs
401+
402+
# mark current current node as finalized
403+
# and update status
404+
if current_data:
405+
index = current_data['index']
406+
current_status = nodes[int(index)]['data']['status']
407+
408+
nodes[int(index)]['data']['finalized'] = True
409+
nodes[int(index)]['data']['status'] = 'passed' if current_status == 'working' else current_status
410+
411+
# check all nodes statuses
412+
for node in FlowRun.objects.get(id=self.flowrun_id).nodes:
413+
414+
# check for non-current 'working' nodes
415+
if node['data']['status'] == 'working':
416+
if current_data:
417+
if node['id'] != current_data['node']['id']:
418+
return self.flowrun
419+
else:
420+
return self.flowrun
421+
422+
# check for any 'failed' nodes
423+
if node['data']['status'] == 'failed':
424+
status = 'failed'
425+
426+
# check for current node failure
427+
if current_data:
428+
current_status = nodes[int(current_data['index'])]['data']['status']
429+
status = current_status if current_status == 'failed' else status
430+
431+
# build log data
432+
logs.append({
433+
'timestamp' : self.build_timestamp(),
434+
'step' : current_data['node']['id'] if current_data else self.get_last_node_id(),
435+
'message' : (
436+
f'flowrun completed with status: {"✅ PASSED" if status == "passed" else "❌ FAILED"}'
437+
),
438+
})
439+
# sort logs
440+
logs = sorted(logs, key=lambda l: int(l['step']),)
441+
442+
# update flowrun
443+
self.flowrun.time_completed = self.build_timestamp()
444+
self.flowrun.status = status
445+
self.flowrun.logs = logs
446+
self.flowrun.nodes = nodes
447+
self.flowrun.save()
448+
449+
# run alert if requested
450+
alert_id = current_data['node']['data'].get('alert_id') if current_data else None
451+
if alert_id:
452+
Alerter(alert_id=alert_id, object_id=str(self.flowrun_id)).run_alert()
453+
454+
# return flowrun
455+
return self.flowrun
456+
457+
458+
459+
385460
def run_next(self) -> None:
386461
"""
387462
Checks for the next step and executes
@@ -421,12 +496,17 @@ def run_next(self) -> None:
421496
}
422497
objs = [alert_obj,] if step_data['node']['data']['task_type'] in self.alert_types else []
423498

499+
# catch empty task_type
500+
if not step_data['node']['data']['task_type']:
501+
self.complete_flowrun(current_data=step_data)
502+
return self.flowrun
503+
424504
# run first step
425505
print('running first step')
426506
self.execute_step(step_data=step_data, objects=objs)
427507
return self.flowrun
428508

429-
509+
430510
# catch updates without a current_node
431511
if current_data['node'] is None:
432512
return self.flowrun
@@ -443,11 +523,11 @@ def run_next(self) -> None:
443523
self.finalize_node(index=current_data['index'])
444524

445525
# set defaults
446-
true_outcomes = []
447-
false_outcomes = []
448-
run_as_cumulative = False
449-
false_child_ran = False
450-
true_child_ran = False
526+
true_outcomes = []
527+
false_outcomes = []
528+
run_as_cumulative = False
529+
false_child_ran = False
530+
true_child_ran = False
451531

452532
# iterate through the objects and run conditions for each
453533
for obj_data in current_data['node']['data'].get('objects', []):
@@ -472,15 +552,15 @@ def run_next(self) -> None:
472552
# sorting
473553
if outcome == True:
474554
true_outcomes.append({
475-
'parent': str(parentID),
476-
'id': obj_data['id'],
477-
'status': 'working'
555+
'parent' : str(parentID),
556+
'id' : obj_data['id'],
557+
'status' : 'working'
478558
})
479559
if outcome == False:
480560
false_outcomes.append({
481-
'parent': str(parentID),
482-
'id': obj_data['id'],
483-
'status': 'working'
561+
'parent' : str(parentID),
562+
'id' : obj_data['id'],
563+
'status' : 'working'
484564
})
485565

486566
# get child edges
@@ -535,42 +615,17 @@ def run_next(self) -> None:
535615
if len(children) == 1:
536616
if children[0] is not None:
537617
next_step = children[0]
538-
print('running next step after "PASSED" non-conditional step')
539-
objs = []
540-
if next_step['node']['data']['task_type'] in self.alert_types:
541-
objs = current_data['node']['data'].get('objects', [])
542-
self.execute_step(step_data=next_step, objects=objs)
543-
return self.flowrun
544-
545-
# check for other working nodes
546-
for node in FlowRun.objects.get(id=self.flowrun_id).nodes:
547-
if node['data']['status'] == 'working':
548-
return self.flowrun
549-
550-
# if no children and no node is 'working'
551-
# then end flowrun as 'passed' and update logs
552-
logs = self.flowrun.logs
553-
logs.append({
554-
'timestamp':self.build_timestamp(),
555-
'message': (
556-
f'flowrun completed with status: ✅ PASSED'
557-
),
558-
'step': self.get_last_node_id()
559-
})
560-
# sort logs
561-
logs = sorted(logs, key=lambda l: int(l['step']),)
562-
563-
# update flowrun
564-
self.flowrun.time_completed = self.build_timestamp()
565-
self.flowrun.status = 'passed'
566-
self.flowrun.logs = logs
567-
self.flowrun.save()
568-
569-
# run alert if requested
570-
alert_id = current_data['node']['data'].get('alert_id')
571-
if alert_id:
572-
Alerter(alert_id=alert_id, object_id=str(self.flowrun_id)).run_alert()
573-
618+
if next_step['node']['data']['task_type']:
619+
print('running next step after "PASSED" non-conditional step')
620+
objs = []
621+
if next_step['node']['data']['task_type'] in self.alert_types:
622+
objs = current_data['node']['data'].get('objects', [])
623+
self.execute_step(step_data=next_step, objects=objs)
624+
return self.flowrun
625+
626+
# if no children, end flowrun and update logs
627+
self.complete_flowrun(current_data=current_data)
628+
574629
# return flowrun
575630
return self.flowrun
576631

@@ -582,36 +637,11 @@ def run_next(self) -> None:
582637
# finialize node
583638
self.finalize_node(index=current_data['index'])
584639

585-
# define failed log
586-
failed_log = {
587-
'timestamp':self.build_timestamp(),
588-
'message': (
589-
f'flowrun completed with status: ❌ FAILED'
590-
),
591-
'step': self.get_last_node_id()
592-
}
593-
594640
# end flowrun if requested
595641
if self.flowrun.configs.get('end_on_fail', True):
596642

597643
print('--- ending run early due to failure ---')
598-
599-
# update logs
600-
logs = self.flowrun.logs
601-
logs.append(failed_log)
602-
# sort logs
603-
logs = sorted(logs, key=lambda l: (int(l['step'])),)
604-
605-
# update & end flowrun
606-
self.flowrun.time_completed = self.build_timestamp()
607-
self.flowrun.status = 'failed'
608-
self.flowrun.logs = logs
609-
self.flowrun.save()
610-
611-
# run alert if requested
612-
alert_id = current_data['node']['data'].get('alert_id')
613-
if alert_id:
614-
Alerter(alert_id=alert_id, object_id=str(self.flowrun_id)).run_alert()
644+
self.complete_flowrun(current_data=current_data)
615645

616646
# return flowrun
617647
return self.flowrun
@@ -625,35 +655,17 @@ def run_next(self) -> None:
625655
if len(children) == 1:
626656
if children[0] is not None:
627657
next_step = children[0]
628-
print('running next step after "FAILED" non-conditional step')
629-
objs = []
630-
if next_step['node']['data']['task_type'] in self.alert_types:
631-
objs = current_data['node']['data'].get('objects', [])
632-
self.execute_step(step_data=next_step, objects=objs)
633-
return self.flowrun
634-
635-
# check for other working nodes
636-
for node in FlowRun.objects.get(id=self.flowrun_id).nodes:
637-
if node['data']['status'] == 'working':
638-
return self.flowrun
639-
640-
# if no children and no node is 'working'
641-
# then end flowrun as 'failed' and update logs
642-
logs = self.flowrun.logs
643-
logs.append(failed_log)
644-
# sort logs
645-
logs = sorted(logs, key=lambda l: int(l['step']),)
646-
647-
# update & end flowrun
648-
self.flowrun.time_completed = self.build_timestamp()
649-
self.flowrun.status = 'failed'
650-
self.flowrun.logs = logs
651-
self.flowrun.save()
652-
653-
# run alert if requested
654-
alert_id = current_data['node']['data'].get('alert_id')
655-
if alert_id:
656-
Alerter(alert_id=alert_id, object_id=str(self.flowrun_id)).run_alert()
658+
# check for data in next_step
659+
if next_step['node']['data']['task_type']:
660+
print('running next step after "FAILED" non-conditional step')
661+
objs = []
662+
if next_step['node']['data']['task_type'] in self.alert_types:
663+
objs = current_data['node']['data'].get('objects', [])
664+
self.execute_step(step_data=next_step, objects=objs)
665+
return self.flowrun
666+
667+
# if no children, end flowrun as 'failed' and update logs
668+
self.complete_flowrun(current_data=current_data)
657669

658670
# return flowrun
659671
return self.flowrun
@@ -677,28 +689,34 @@ def execute_step(self, step_data: dict=None, objects: list=None) -> None:
677689
"""
678690

679691
if step_data is None:
680-
print('no step_data provided - returning early')
692+
print('no step_data provided - attempting to end run...')
693+
self.complete_flowrun(current_data=step_data)
694+
return
695+
696+
if not step_data['node']['data']['task_type']:
697+
print('no task_type provided - attempting to end run...')
698+
self.complete_flowrun(current_data=step_data)
681699
return
682700

683701
# get step/node data & task_type
684-
node_data = step_data['node']['data']
685-
task_type = node_data['task_type']
686-
node_index = step_data['index']
702+
node_data = step_data['node']['data']
703+
task_type = node_data['task_type']
704+
node_index = step_data['index']
687705
parent_data = None if node_index == 0 else self.get_node_by_id(node_data['parentId'])
688-
message = (
706+
message = (
689707
f'starting job ID: {node_data["id"]} ' +
690708
f'| job type is [ {task_type.upper()} ]'
691709
)
692710

693711
# update self.flowrun logs, nodes, & edges
694-
self.flowrun = FlowRun.objects.get(id=self.flowrun_id)
695-
nodes = self.flowrun.nodes
696-
edges = self.flowrun.edges
697-
logs = self.flowrun.logs
712+
self.flowrun = FlowRun.objects.get(id=self.flowrun_id)
713+
nodes = self.flowrun.nodes
714+
edges = self.flowrun.edges
715+
logs = self.flowrun.logs
698716

699717
# update current node
700-
nodes[step_data['index']]['data']['status'] = 'working'
701-
nodes[step_data['index']]['data']['time_started'] = self.build_timestamp()
718+
nodes[step_data['index']]['data']['status'] = 'working'
719+
nodes[step_data['index']]['data']['time_started'] = self.build_timestamp()
702720

703721
# update node objects only if task_type is not 'issue' or 'report'
704722
nodes[step_data['index']]['data']['objects'] = objects if (task_type != 'issue' and task_type != 'report') else []
@@ -711,27 +729,27 @@ def execute_step(self, step_data: dict=None, objects: list=None) -> None:
711729

712730
# update current logs
713731
logs.append({
714-
'timestamp':self.build_timestamp(),
715-
'message': message,
716-
'step': node_data['id']
732+
'timestamp' : self.build_timestamp(),
733+
'message' : message,
734+
'step' : node_data['id']
717735
})
718736

719737
# sort logs
720738
logs = sorted(logs, key=lambda l: int(l['step']),)
721739

722740
# save updates
723-
self.flowrun.nodes = nodes
724-
self.flowrun.edges = edges
725-
self.flowrun.logs = logs
741+
self.flowrun.nodes = nodes
742+
self.flowrun.edges = edges
743+
self.flowrun.logs = logs
726744
self.flowrun.save()
727745

728746
# build common data
729-
scope = 'account'
730-
configs = node_data['configs']
731-
flowrun_id = str(self.flowrun.id)
732-
account_id = str(self.flowrun.account.id)
733-
types = node_data.get('type')
734-
resources = [{
747+
scope = 'account'
748+
configs = node_data['configs']
749+
flowrun_id = str(self.flowrun.id)
750+
account_id = str(self.flowrun.account.id)
751+
types = node_data.get('type')
752+
resources = [{
735753
'str' : self.flowrun.site.site_url,
736754
'id' : str(self.flowrun.site.id),
737755
'type' : 'site'

0 commit comments

Comments
 (0)