Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

v34.1.0 (unreleased)
--------------------

- The pipeline help modal is now available from all project views: form, list, details.
The docstring are converted from markdown to html for proper rendering.
https://github.com/nexB/scancode.io/pull/1105

v34.0.0 (2024-03-04)
--------------------

Expand Down
23 changes: 21 additions & 2 deletions scanpipe/pipelines/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

from django.utils import timezone

import bleach
from markdown_it import MarkdownIt
from pyinstrument import Profiler

from scanpipe import humanize_time
Expand All @@ -55,6 +57,15 @@ def decorator(obj):
return decorator


def convert_markdown_to_html(markdown_text):
"""Convert Markdown text to sanitized HTML."""
# Using the "js-default" for safety.
html_content = MarkdownIt("js-default").renderInline(markdown_text)
# Sanitize HTML using bleach.
sanitized_html = bleach.clean(html_content)
return sanitized_html


class BasePipeline:
"""Base class for all pipelines."""

Expand Down Expand Up @@ -116,13 +127,21 @@ def get_graph(cls):
]

@classmethod
def get_info(cls):
def get_info(cls, as_html=False):
"""Get a dictionary of combined information data about this pipeline."""
summary, description = splitdoc(cls.get_doc())
steps = cls.get_graph()

if as_html:
summary = convert_markdown_to_html(summary)
description = convert_markdown_to_html(description)
for step in steps:
step["doc"] = convert_markdown_to_html(step["doc"])

return {
"summary": summary,
"description": description,
"steps": cls.get_graph(),
"steps": steps,
"available_groups": cls.get_available_groups(),
}

Expand Down
4 changes: 2 additions & 2 deletions scanpipe/pipelines/deploy_to_develop.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ class DeployToDevelop(Pipeline):

This pipeline requires a minimum of two archive files, each properly tagged with:

- "from" for archives containing the development source code.
- "to" for archives containing the deployment compiled code.
- **from** for archives containing the development source code.
- **to** for archives containing the deployment compiled code.

When using download URLs as inputs, the "from" and "to" tags can be
provided by adding a "#from" or "#to" fragment at the end of the download URLs.
Expand Down
1 change: 1 addition & 0 deletions scanpipe/templates/scanpipe/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
.menu-list a {border-radius: 4px;}
.is-sticky {position: sticky;}
.top-0 {top: 0;}
.is-line-height-normal {line-height: normal;}
textarea.is-dynamic {min-height: 70px;}
#project-extra-data figure.highlight {overflow-y: scroll;}
#project-extra-data .is-more-clipped figure.highlight {max-height: 250px;}
Expand Down
7 changes: 5 additions & 2 deletions scanpipe/templates/scanpipe/includes/project_list_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
<td>
{% for run in project.runs.all %}
<div class="is-flex is-justify-content-space-between {% if not forloop.first %}mt-1{% endif %}">
<span class="mr-1">{{ run.pipeline_name }}</span>
<a class="modal-button is-black-link mr-2" data-target="pipeline-help-modal" data-pipeline-name="{{ run.pipeline_name }}" aria-haspopup="true">
{{ run.pipeline_name }}
</a>
<a class="modal-button" data-target="run-detail-modal" data-uuid="{{ run.uuid }}" aria-haspopup="true">
{% include "scanpipe/includes/run_status_tag.html" with run=run only %}
</a>
Expand All @@ -68,4 +70,5 @@
</tr>
{% endfor %}
</tbody>
</table>
</table>
{% include "scanpipe/modals/pipeline_help_modal.html" %}
34 changes: 34 additions & 0 deletions scanpipe/templates/scanpipe/modals/pipeline_help_modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div id="pipeline-help-modal" class="modal is-desktop-size"></div>
<script>
document.addEventListener("openModal", function(event) {
let modal_id = event.detail.modal;
if (modal_id !== "pipeline-help-modal") return;

let $modal = document.getElementById(modal_id);
$modal.innerHTML = "";

let pipeline_name = event.detail.$button.dataset.pipelineName;
let pipeline_help_path = `pipeline/${pipeline_name}/help/`;

// Construct the full URL by combining the current origin and the relative path.
// It's important to use the URL constructor, as directly providing the relative
// path to the `fetch` function may not work correctly, especially for URLs
// that include credentials such as "user:pass@domain.com".
let pipeline_help_url = new URL(pipeline_help_path, window.location.origin);

fetch(pipeline_help_url).then(function (response) {
if (response.ok) {
return response.text();
} else {
closeModals();
throw Error(response.statusText);
}
}).then(function (html) {
$modal.innerHTML = html;
setupCloseModalButtons();
}).catch(function (error) {
console.warn('Error:', error);
});

});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">
<strong>{{ pipeline_name }}</strong>
</p>
<button class="delete" aria-label="close"></button>
</header>

<section class="modal-card-body border-bottom-radius p-0">
<div class="notification has-background-info-light is-radiusless mb-0 py-2">
<p class="has-text-weight-bold">
{{ pipeline_info.summary }}
</p>
{% if pipeline_info.description %}
<p class="mt-3">
{{ pipeline_info.description|safe|linebreaksbr }}
</p>
{% endif %}
</div>

<div class="has-text-centered py-2">
{% for step in pipeline_info.steps %}
<span class="tag is-info has-text-weight-semibold">{{ step.name }}</span>
{% if step.groups %}
{% for group in step.groups %}
<span class="tag is-warning has-text-weight-semibold">{{ group }}</span>
{% endfor %}
{% endif %}
<div>{{ step.doc|safe }}</div>
{% if not forloop.last %}<div class="is-line-height-normal is-size-7">&darr;</div>{% endif %}
{% endfor %}
</div>
</section>
</div>
14 changes: 10 additions & 4 deletions scanpipe/templates/scanpipe/panels/project_pipelines.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
Pipelines
</p>
{% for run in pipeline_runs %}
<a class="panel-block modal-button is-justify-content-space-between" data-target="run-detail-modal" data-uuid="{{ run.uuid }}" aria-haspopup="true">
<span class="mr-1">{{ run.pipeline_name }}</span>
{% include "scanpipe/includes/run_status_tag.html" with run=run display_current_step=True only %}
</a>
<div class="panel-block is-justify-content-space-between">
<a class="modal-button is-black-link" data-target="pipeline-help-modal" data-pipeline-name="{{ run.pipeline_name }}" aria-haspopup="true">
{{ run.pipeline_name }}
<i class="fa-regular fa-circle-question"></i>
</a>
<a class="modal-button" data-target="run-detail-modal" data-uuid="{{ run.uuid }}" aria-haspopup="true">
{% include "scanpipe/includes/run_status_tag.html" with run=run display_current_step=True only %}
</a>
</div>
{% endfor %}
<div class="panel-block">
{% if project.is_archived %}
Expand All @@ -25,5 +30,6 @@
{% endif %}
{% endif %}
</div>
{% include "scanpipe/modals/pipeline_help_modal.html" %}
{% include "scanpipe/modals/add_pipeline_modal.html" %}
</article>
48 changes: 6 additions & 42 deletions scanpipe/templates/scanpipe/project_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,52 +88,16 @@ <h2 class="subtitle mb-0 mb-4">
<div class="column has-background-light has-border-radius mb-3">
<h3 class="subtitle mb-3">Pipelines:</h3>
{% for pipeline_name, pipeline_info in pipelines.items %}
<div {% if not forloop.last %}class="mb-3"{% endif %}>
<div>
<a class="modal-button" data-target="{{ pipeline_name }}-modal" aria-haspopup="true">
<strong>{{ pipeline_name }}</strong>
<i class="fa-regular fa-circle-question"></i>
</a>
<div id="{{ pipeline_name }}-modal" class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">
<strong>{{ pipeline_name }}</strong>
</p>
<button class="delete" aria-label="close"></button>
</header>
<div class="notification has-background-info-light has-text-weight-semibold is-radiusless mb-0"
style="max-height: 300px; overflow-y: scroll;"
>
<p>{{ pipeline_info.summary }}</p>
{% if pipeline_info.description %}
<p class="mt-3">
{{ pipeline_info.description|linebreaksbr }}
</p>
{% endif %}
</div>
<section class="modal-card-body has-text-centered border-bottom-radius">
{% for step in pipeline_info.steps %}
<span class="tag is-info">{{ step.name }}</span>
{% if step.groups %}
<div class="has-text-weight-semibold">
{% for group in step.groups %}
<span class="tag is-warning">{{ group }}</span>
{% endfor %}
</div>
{% endif %}
<div>{{ step.doc }}</div>
{% if not forloop.last %}<div>&darr;</div>{% endif %}
{% endfor %}
</section>
</div>
</div>
</div>
<div {% if not forloop.last %}class="mb-2"{% endif %}>
<a class="modal-button is-block" data-target="pipeline-help-modal" data-pipeline-name="{{ pipeline_name }}" aria-haspopup="true">
<strong>{{ pipeline_name }}</strong>
<i class="fa-regular fa-circle-question"></i>
</a>
{{ pipeline_info.summary }}
</div>
{% endfor %}
</div>
{% include "scanpipe/modals/pipeline_help_modal.html" %}
</div>
</section>
</div>
Expand Down
14 changes: 14 additions & 0 deletions scanpipe/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,20 @@ def test_scanpipe_views_run_status_view(self):
expected = '<span class="tag is-danger">Stopped</span>'
self.assertContains(response, expected)

def test_scanpipe_views_pipeline_help_view(self):
url = reverse("pipeline_help", args=["not_existing_pipeline"])
response = self.client.get(url)
self.assertEqual(404, response.status_code)

url = reverse("pipeline_help", args=["map_deploy_to_develop"])
response = self.client.get(url)
expected = "<strong>map_deploy_to_develop</strong>"
self.assertContains(response, expected, html=True)
expected = (
"<div>Locate the <code>from</code> and <code>to</code> input files.</div>"
)
self.assertContains(response, expected, html=True)

def test_scanpipe_views_codebase_resource_details_view_tab_image(self):
resource1 = make_resource_file(self.project1, "file1.ext")
response = self.client.get(resource1.get_absolute_url())
Expand Down
5 changes: 5 additions & 0 deletions scanpipe/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@
views.run_status_view,
name="run_status",
),
path(
"pipeline/<str:pipeline_name>/help/",
views.pipeline_help_view,
name="pipeline_help",
),
path(
"project/<slug:slug>/results/<str:format>/",
views.ProjectResultsView.as_view(),
Expand Down
16 changes: 16 additions & 0 deletions scanpipe/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,22 @@ def run_status_view(request, uuid):
return render(request, template, context)


@conditional_login_required
def pipeline_help_view(request, pipeline_name):
template = "scanpipe/modals/pipeline_help_modal_content.html"

pipeline_class = scanpipe_app.pipelines.get(pipeline_name)
if not pipeline_class:
raise Http404

context = {
"pipeline_name": pipeline_name,
"pipeline_info": pipeline_class.get_info(as_html=True),
}

return render(request, template, context)


class CodebaseResourceRawView(
ConditionalLoginRequired,
ProjectRelatedViewMixin,
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ install_requires =
matchcode-toolkit==3.0.0
# Univers
univers==30.11.0
# Markdown
markdown-it-py==3.0.0
bleach==6.1.0

[options.extras_require]
dev =
Expand Down