Skip to content

Commit cff76dd

Browse files
authored
Add pipeline step selection for a run execution #1303 (#1310)
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent 2ed734e commit cff76dd

30 files changed

+238
-6
lines changed

CHANGELOG.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
Changelog
22
=========
33

4+
v34.7.1 (unreleased)
5+
--------------------
6+
7+
- Add pipeline step selection for a run execution.
8+
This allows to run a pipeline in an advanced mode allowing to skip some steps,
9+
or restart from a step, like the last failed step.
10+
The steps can be edited from the Run "status" modal using the "Select steps" button.
11+
This is an advanced feature and should we used with caution.
12+
https://github.com/nexB/scancode.io/issues/1303
13+
414
v34.7.0 (2024-07-02)
515
--------------------
616

scanpipe/api/serializers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class Meta:
114114
"status",
115115
"description",
116116
"selected_groups",
117+
"selected_steps",
117118
"project",
118119
"uuid",
119120
"created_date",

scanpipe/forms.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from taggit.forms import TagWidget
3030

3131
from scanpipe.models import Project
32+
from scanpipe.models import Run
3233
from scanpipe.pipelines import convert_markdown_to_html
3334
from scanpipe.pipes import fetch
3435

@@ -119,9 +120,11 @@ def handle_inputs(self, project):
119120
project.add_input_source(download_url=url)
120121

121122

122-
class GroupChoiceField(forms.MultipleChoiceField):
123+
class CheckboxChoiceField(forms.MultipleChoiceField):
123124
widget = forms.CheckboxSelectMultiple
124125

126+
127+
class SelectedGroupsCheckboxChoiceField(CheckboxChoiceField):
125128
def valid_value(self, value):
126129
"""Accept all values."""
127130
return True
@@ -137,7 +140,7 @@ class PipelineBaseForm(forms.Form):
137140
initial=True,
138141
required=False,
139142
)
140-
selected_groups = GroupChoiceField(required=False)
143+
selected_groups = SelectedGroupsCheckboxChoiceField(required=False)
141144

142145
def handle_pipeline(self, project):
143146
pipeline = self.cleaned_data["pipeline"]
@@ -531,3 +534,31 @@ def clean_clone_name(self):
531534

532535
def save(self, *args, **kwargs):
533536
return self.project.clone(**self.cleaned_data)
537+
538+
539+
class PipelineRunStepSelectionForm(forms.ModelForm):
540+
selected_steps = CheckboxChoiceField(required=False)
541+
542+
class Meta:
543+
model = Run
544+
fields = [
545+
"selected_steps",
546+
]
547+
548+
def __init__(self, *args, **kwargs):
549+
if not kwargs.get("instance"):
550+
raise ValueError("An Run object is required to instantiate this form.")
551+
552+
super().__init__(*args, **kwargs)
553+
pipeline_class = self.instance.pipeline_class
554+
choices = self.get_step_choices(pipeline_class)
555+
self.fields["selected_steps"].choices = choices
556+
557+
# All step checkboxes are selected by default unless already defined on the run
558+
if not self.instance.selected_steps:
559+
self.initial["selected_steps"] = [choice[0] for choice in choices]
560+
561+
@staticmethod
562+
def get_step_choices(pipeline_class):
563+
"""Return a `choices` list of tuple suitable for a Django ChoiceField."""
564+
return [(step.__name__, step.__name__) for step in pipeline_class.get_steps()]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 5.0.6 on 2024-07-03 09:59
2+
3+
import scanpipe.models
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('scanpipe', '0062_dependency_resolver_update'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='run',
16+
name='selected_steps',
17+
field=models.JSONField(blank=True, null=True, validators=[scanpipe.models.validate_none_or_list]),
18+
),
19+
]

scanpipe/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,6 +1775,9 @@ class Run(UUIDPKModel, ProjectRelatedModel, AbstractTaskFieldsModel):
17751775
selected_groups = models.JSONField(
17761776
null=True, blank=True, validators=[validate_none_or_list]
17771777
)
1778+
selected_steps = models.JSONField(
1779+
null=True, blank=True, validators=[validate_none_or_list]
1780+
)
17781781

17791782
objects = RunQuerySet.as_manager()
17801783

scanpipe/pipelines/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ def execute(self):
197197
self.log(f"Pipeline [{self.pipeline_name}] starting")
198198

199199
steps = self.get_steps(groups=self.run.selected_groups)
200+
selected_steps = self.run.selected_steps
200201

201202
if self.download_inputs:
202203
steps = (self.__class__.download_missing_inputs,) + steps
@@ -207,6 +208,10 @@ def execute(self):
207208
for current_index, step in enumerate(steps, start=1):
208209
step_name = step.__name__
209210

211+
if selected_steps and step_name not in selected_steps:
212+
self.log(f"Step [{step_name}] skipped")
213+
continue
214+
210215
self.run.set_current_step(f"{current_index}/{steps_count} {step_name}")
211216
self.log(f"Step [{step_name}] starting")
212217
step_start_time = timer()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<div id="run-step-selection-box" class="box has-background-warning-light">
2+
<div class="has-text-weight-semibold mb-3">
3+
Warning: Step Selection is an advanced feature.<br>
4+
Removing steps may compromise data integrity. Use with caution.
5+
</div>
6+
{% include 'scanpipe/includes/form_errors.html' %}
7+
<form hx-post="{% url 'project_run_step_selection' run.uuid %}"
8+
hx-target="#run-step-selection-box"
9+
hx-swap="outerHTML"
10+
>{% csrf_token %}
11+
<div class="field">
12+
<label class="label">Select steps:</label>
13+
<div class="control">
14+
{{ form.selected_steps }}
15+
</div>
16+
</div>
17+
<button class="button is-smaller is-link">Update steps</button>
18+
</form>
19+
</div>

scanpipe/templates/scanpipe/modals/run_modal.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
if (execute_pipeline_links) {
3434
execute_pipeline_links.addEventListener('click', displayOverlay);
3535
}
36+
htmx.process($modal);
3637
}).catch(function (error) {
3738
console.warn('Error:', error);
3839
});

scanpipe/templates/scanpipe/modals/run_modal_content.html

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,18 @@
2929
</div>
3030
{% endif %}
3131
{% if run.status == run.Status.NOT_STARTED or run.status == run.Status.QUEUED %}
32-
<a href="{% url 'project_delete_pipeline' project.slug run.uuid %}" class="execute-pipeline-link has-text-danger ml-3">
32+
<a href="{% url 'project_delete_pipeline' project.slug run.uuid %}" class="execute-pipeline-link has-text-danger ml-2">
3333
<i class="fa-solid fa-trash-alt"></i>
3434
Delete pipeline
3535
</a>
36+
<a href="#" class="ml-4"
37+
hx-get="{% url 'project_run_step_selection' run.uuid %}"
38+
hx-target="#run-step-selection-box"
39+
hx-swap="outerHTML"
40+
>
41+
<i class="fa-regular fa-square-check"></i>
42+
Select steps
43+
</a>
3644
{% endif %}
3745
{% if run.task_exitcode %}
3846
<div class="control">
@@ -59,6 +67,7 @@
5967
</a>
6068
{% endif %}
6169
</div>
70+
<div id="run-step-selection-box"></div>
6271
<div class="field is-grouped is-grouped-multiline">
6372
{% if run.execution_time %}
6473
<div class="control">

scanpipe/tests/data/asgiref/asgiref-3.3.0_load_inventory_expected.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"pipeline_name": "load_inventory",
1717
"status": "not_started",
1818
"selected_groups": null,
19+
"selected_steps": null,
1920
"scancodeio_version": "",
2021
"task_id": null,
2122
"task_start_date": null,

0 commit comments

Comments
 (0)