Skip to content

Commit 6f2b653

Browse files
authored
Add pipeline help modal from all project views (#1105)
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent 385f3c0 commit 6f2b653

File tree

13 files changed

+159
-52
lines changed

13 files changed

+159
-52
lines changed

CHANGELOG.rst

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

4+
v34.1.0 (unreleased)
5+
--------------------
6+
7+
- The pipeline help modal is now available from all project views: form, list, details.
8+
The docstring are converted from markdown to html for proper rendering.
9+
https://github.com/nexB/scancode.io/pull/1105
10+
411
v34.0.0 (2024-03-04)
512
--------------------
613

scanpipe/pipelines/__init__.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
from django.utils import timezone
3333

34+
import bleach
35+
from markdown_it import MarkdownIt
3436
from pyinstrument import Profiler
3537

3638
from scanpipe import humanize_time
@@ -55,6 +57,15 @@ def decorator(obj):
5557
return decorator
5658

5759

60+
def convert_markdown_to_html(markdown_text):
61+
"""Convert Markdown text to sanitized HTML."""
62+
# Using the "js-default" for safety.
63+
html_content = MarkdownIt("js-default").renderInline(markdown_text)
64+
# Sanitize HTML using bleach.
65+
sanitized_html = bleach.clean(html_content)
66+
return sanitized_html
67+
68+
5869
class BasePipeline:
5970
"""Base class for all pipelines."""
6071

@@ -116,13 +127,21 @@ def get_graph(cls):
116127
]
117128

118129
@classmethod
119-
def get_info(cls):
130+
def get_info(cls, as_html=False):
120131
"""Get a dictionary of combined information data about this pipeline."""
121132
summary, description = splitdoc(cls.get_doc())
133+
steps = cls.get_graph()
134+
135+
if as_html:
136+
summary = convert_markdown_to_html(summary)
137+
description = convert_markdown_to_html(description)
138+
for step in steps:
139+
step["doc"] = convert_markdown_to_html(step["doc"])
140+
122141
return {
123142
"summary": summary,
124143
"description": description,
125-
"steps": cls.get_graph(),
144+
"steps": steps,
126145
"available_groups": cls.get_available_groups(),
127146
}
128147

scanpipe/pipelines/deploy_to_develop.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ class DeployToDevelop(Pipeline):
3636
3737
This pipeline requires a minimum of two archive files, each properly tagged with:
3838
39-
- "from" for archives containing the development source code.
40-
- "to" for archives containing the deployment compiled code.
39+
- **from** for archives containing the development source code.
40+
- **to** for archives containing the deployment compiled code.
4141
4242
When using download URLs as inputs, the "from" and "to" tags can be
4343
provided by adding a "#from" or "#to" fragment at the end of the download URLs.

scanpipe/templates/scanpipe/base.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
.menu-list a {border-radius: 4px;}
7070
.is-sticky {position: sticky;}
7171
.top-0 {top: 0;}
72+
.is-line-height-normal {line-height: normal;}
7273
textarea.is-dynamic {min-height: 70px;}
7374
#project-extra-data figure.highlight {overflow-y: scroll;}
7475
#project-extra-data .is-more-clipped figure.highlight {max-height: 250px;}

scanpipe/templates/scanpipe/includes/project_list_table.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
<td>
5454
{% for run in project.runs.all %}
5555
<div class="is-flex is-justify-content-space-between {% if not forloop.first %}mt-1{% endif %}">
56-
<span class="mr-1">{{ run.pipeline_name }}</span>
56+
<a class="modal-button is-black-link mr-2" data-target="pipeline-help-modal" data-pipeline-name="{{ run.pipeline_name }}" aria-haspopup="true">
57+
{{ run.pipeline_name }}
58+
</a>
5759
<a class="modal-button" data-target="run-detail-modal" data-uuid="{{ run.uuid }}" aria-haspopup="true">
5860
{% include "scanpipe/includes/run_status_tag.html" with run=run only %}
5961
</a>
@@ -68,4 +70,5 @@
6870
</tr>
6971
{% endfor %}
7072
</tbody>
71-
</table>
73+
</table>
74+
{% include "scanpipe/modals/pipeline_help_modal.html" %}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<div id="pipeline-help-modal" class="modal is-desktop-size"></div>
2+
<script>
3+
document.addEventListener("openModal", function(event) {
4+
let modal_id = event.detail.modal;
5+
if (modal_id !== "pipeline-help-modal") return;
6+
7+
let $modal = document.getElementById(modal_id);
8+
$modal.innerHTML = "";
9+
10+
let pipeline_name = event.detail.$button.dataset.pipelineName;
11+
let pipeline_help_path = `pipeline/${pipeline_name}/help/`;
12+
13+
// Construct the full URL by combining the current origin and the relative path.
14+
// It's important to use the URL constructor, as directly providing the relative
15+
// path to the `fetch` function may not work correctly, especially for URLs
16+
// that include credentials such as "user:pass@domain.com".
17+
let pipeline_help_url = new URL(pipeline_help_path, window.location.origin);
18+
19+
fetch(pipeline_help_url).then(function (response) {
20+
if (response.ok) {
21+
return response.text();
22+
} else {
23+
closeModals();
24+
throw Error(response.statusText);
25+
}
26+
}).then(function (html) {
27+
$modal.innerHTML = html;
28+
setupCloseModalButtons();
29+
}).catch(function (error) {
30+
console.warn('Error:', error);
31+
});
32+
33+
});
34+
</script>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<div class="modal-background"></div>
2+
<div class="modal-card">
3+
<header class="modal-card-head">
4+
<p class="modal-card-title">
5+
<strong>{{ pipeline_name }}</strong>
6+
</p>
7+
<button class="delete" aria-label="close"></button>
8+
</header>
9+
10+
<section class="modal-card-body border-bottom-radius p-0">
11+
<div class="notification has-background-info-light is-radiusless mb-0 py-2">
12+
<p class="has-text-weight-bold">
13+
{{ pipeline_info.summary }}
14+
</p>
15+
{% if pipeline_info.description %}
16+
<p class="mt-3">
17+
{{ pipeline_info.description|safe|linebreaksbr }}
18+
</p>
19+
{% endif %}
20+
</div>
21+
22+
<div class="has-text-centered py-2">
23+
{% for step in pipeline_info.steps %}
24+
<span class="tag is-info has-text-weight-semibold">{{ step.name }}</span>
25+
{% if step.groups %}
26+
{% for group in step.groups %}
27+
<span class="tag is-warning has-text-weight-semibold">{{ group }}</span>
28+
{% endfor %}
29+
{% endif %}
30+
<div>{{ step.doc|safe }}</div>
31+
{% if not forloop.last %}<div class="is-line-height-normal is-size-7">&darr;</div>{% endif %}
32+
{% endfor %}
33+
</div>
34+
</section>
35+
</div>

scanpipe/templates/scanpipe/panels/project_pipelines.html

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
Pipelines
44
</p>
55
{% for run in pipeline_runs %}
6-
<a class="panel-block modal-button is-justify-content-space-between" data-target="run-detail-modal" data-uuid="{{ run.uuid }}" aria-haspopup="true">
7-
<span class="mr-1">{{ run.pipeline_name }}</span>
8-
{% include "scanpipe/includes/run_status_tag.html" with run=run display_current_step=True only %}
9-
</a>
6+
<div class="panel-block is-justify-content-space-between">
7+
<a class="modal-button is-black-link" data-target="pipeline-help-modal" data-pipeline-name="{{ run.pipeline_name }}" aria-haspopup="true">
8+
{{ run.pipeline_name }}
9+
<i class="fa-regular fa-circle-question"></i>
10+
</a>
11+
<a class="modal-button" data-target="run-detail-modal" data-uuid="{{ run.uuid }}" aria-haspopup="true">
12+
{% include "scanpipe/includes/run_status_tag.html" with run=run display_current_step=True only %}
13+
</a>
14+
</div>
1015
{% endfor %}
1116
<div class="panel-block">
1217
{% if project.is_archived %}
@@ -25,5 +30,6 @@
2530
{% endif %}
2631
{% endif %}
2732
</div>
33+
{% include "scanpipe/modals/pipeline_help_modal.html" %}
2834
{% include "scanpipe/modals/add_pipeline_modal.html" %}
2935
</article>

scanpipe/templates/scanpipe/project_form.html

Lines changed: 6 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -88,52 +88,16 @@ <h2 class="subtitle mb-0 mb-4">
8888
<div class="column has-background-light has-border-radius mb-3">
8989
<h3 class="subtitle mb-3">Pipelines:</h3>
9090
{% for pipeline_name, pipeline_info in pipelines.items %}
91-
<div {% if not forloop.last %}class="mb-3"{% endif %}>
92-
<div>
93-
<a class="modal-button" data-target="{{ pipeline_name }}-modal" aria-haspopup="true">
94-
<strong>{{ pipeline_name }}</strong>
95-
<i class="fa-regular fa-circle-question"></i>
96-
</a>
97-
<div id="{{ pipeline_name }}-modal" class="modal">
98-
<div class="modal-background"></div>
99-
<div class="modal-card">
100-
<header class="modal-card-head">
101-
<p class="modal-card-title">
102-
<strong>{{ pipeline_name }}</strong>
103-
</p>
104-
<button class="delete" aria-label="close"></button>
105-
</header>
106-
<div class="notification has-background-info-light has-text-weight-semibold is-radiusless mb-0"
107-
style="max-height: 300px; overflow-y: scroll;"
108-
>
109-
<p>{{ pipeline_info.summary }}</p>
110-
{% if pipeline_info.description %}
111-
<p class="mt-3">
112-
{{ pipeline_info.description|linebreaksbr }}
113-
</p>
114-
{% endif %}
115-
</div>
116-
<section class="modal-card-body has-text-centered border-bottom-radius">
117-
{% for step in pipeline_info.steps %}
118-
<span class="tag is-info">{{ step.name }}</span>
119-
{% if step.groups %}
120-
<div class="has-text-weight-semibold">
121-
{% for group in step.groups %}
122-
<span class="tag is-warning">{{ group }}</span>
123-
{% endfor %}
124-
</div>
125-
{% endif %}
126-
<div>{{ step.doc }}</div>
127-
{% if not forloop.last %}<div>&darr;</div>{% endif %}
128-
{% endfor %}
129-
</section>
130-
</div>
131-
</div>
132-
</div>
91+
<div {% if not forloop.last %}class="mb-2"{% endif %}>
92+
<a class="modal-button is-block" data-target="pipeline-help-modal" data-pipeline-name="{{ pipeline_name }}" aria-haspopup="true">
93+
<strong>{{ pipeline_name }}</strong>
94+
<i class="fa-regular fa-circle-question"></i>
95+
</a>
13396
{{ pipeline_info.summary }}
13497
</div>
13598
{% endfor %}
13699
</div>
100+
{% include "scanpipe/modals/pipeline_help_modal.html" %}
137101
</div>
138102
</section>
139103
</div>

scanpipe/tests/test_views.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,20 @@ def test_scanpipe_views_run_status_view(self):
791791
expected = '<span class="tag is-danger">Stopped</span>'
792792
self.assertContains(response, expected)
793793

794+
def test_scanpipe_views_pipeline_help_view(self):
795+
url = reverse("pipeline_help", args=["not_existing_pipeline"])
796+
response = self.client.get(url)
797+
self.assertEqual(404, response.status_code)
798+
799+
url = reverse("pipeline_help", args=["map_deploy_to_develop"])
800+
response = self.client.get(url)
801+
expected = "<strong>map_deploy_to_develop</strong>"
802+
self.assertContains(response, expected, html=True)
803+
expected = (
804+
"<div>Locate the <code>from</code> and <code>to</code> input files.</div>"
805+
)
806+
self.assertContains(response, expected, html=True)
807+
794808
def test_scanpipe_views_codebase_resource_details_view_tab_image(self):
795809
resource1 = make_resource_file(self.project1, "file1.ext")
796810
response = self.client.get(resource1.get_absolute_url())

0 commit comments

Comments
 (0)