Skip to content

Commit c48328d

Browse files
authored
add links to edit descriptions of dojos, modules, and challenges (#803)
* add links to edit descriptions of dojos, modules, and challenges * add edit links to markdown resources * working * cleanup * cleanup * properly set the branch name
1 parent 9d864d0 commit c48328d

File tree

6 files changed

+162
-2
lines changed

6 files changed

+162
-2
lines changed

dojo_plugin/pages/dojo.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import collections
2+
import subprocess
23
import traceback
34
import datetime
45
import sys
6+
import re
57

68
from flask import Blueprint, render_template, abort, send_file, redirect, url_for, Response, stream_with_context, request, g
79
from sqlalchemy.exc import IntegrityError
@@ -21,6 +23,48 @@
2123
#pylint:disable=redefined-outer-name
2224

2325

26+
def find_description_edit_url(dojo, relative_paths, search_pattern=None):
27+
if not (dojo.official and dojo.repository):
28+
return None
29+
30+
branch = "main"
31+
if dojo.path.exists():
32+
try:
33+
result = subprocess.run(
34+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
35+
cwd=dojo.path,
36+
capture_output=True,
37+
text=True,
38+
timeout=1
39+
)
40+
if result.returncode == 0:
41+
branch = result.stdout.strip()
42+
except (subprocess.TimeoutExpired, FileNotFoundError):
43+
pass
44+
45+
for relative_path in relative_paths:
46+
full_path = dojo.path / relative_path
47+
48+
if not full_path.exists():
49+
continue
50+
51+
line_num = 1
52+
if relative_path.endswith('.yml') and search_pattern:
53+
try:
54+
pattern = re.compile(search_pattern)
55+
with open(full_path, 'r') as f:
56+
for i, line in enumerate(f, 1):
57+
if pattern.search(line):
58+
line_num = i
59+
break
60+
except (IOError, re.error):
61+
pass
62+
63+
return f"https://github.com/{dojo.repository}/edit/{branch}/{relative_path}#L{line_num}"
64+
65+
return None
66+
67+
2468
@dojo.route("/<dojo>")
2569
@dojo.route("/<dojo>/")
2670
@dojo_route
@@ -36,6 +80,11 @@ def listing(dojo):
3680
if container["dojo"] == dojo.reference_id
3781
)
3882
stats["active"] = sum(module_container_counts.values())
83+
84+
description_edit_url = None
85+
if dojo.description and dojo.path.exists():
86+
description_edit_url = find_description_edit_url(dojo, ["DESCRIPTION.md", "dojo.yml"], r"^description:")
87+
3988
return render_template(
4089
"dojo.html",
4190
dojo=dojo,
@@ -45,6 +94,7 @@ def listing(dojo):
4594
infos=infos,
4695
awards=awards,
4796
module_container_counts=module_container_counts,
97+
description_edit_url=description_edit_url,
4898
)
4999

50100

@@ -285,6 +335,36 @@ def view_module(dojo, module):
285335
for container in get_container_stats()
286336
if container["module"] == module.id and container["dojo"] == dojo.reference_id
287337
)
338+
339+
module_description_edit_url = None
340+
challenge_description_edit_urls = {}
341+
resource_description_edit_urls = {}
342+
343+
if dojo.path.exists():
344+
if module.description:
345+
module_description_edit_url = find_description_edit_url(dojo, [
346+
f"{module.id}/DESCRIPTION.md",
347+
f"{module.id}/module.yml",
348+
"dojo.yml"
349+
], r"^description:")
350+
351+
for challenge in module.challenges:
352+
if challenge.description:
353+
# Search for "- id: challenge_name" with optional quotes
354+
challenge_description_edit_urls[challenge.id] = find_description_edit_url(dojo, [
355+
f"{module.id}/{challenge.id}/DESCRIPTION.md",
356+
f"{module.id}/{challenge.id}/challenge.yml",
357+
f"{module.id}/module.yml",
358+
"dojo.yml"
359+
], rf"^\s*-?\s*id:\s*[\"']?{re.escape(challenge.id)}[\"']?")
360+
361+
for resource in module.resources:
362+
if resource.type == "markdown":
363+
# Search for "- name: Resource Name" with optional quotes
364+
resource_description_edit_urls[resource.resource_index] = find_description_edit_url(
365+
dojo, [f"{module.id}/module.yml", "dojo.yml"],
366+
rf"^\s*-?\s*name:\s*[\"']?{re.escape(resource.name)}[\"']?"
367+
)
288368

289369
return render_template(
290370
"module.html",
@@ -297,6 +377,9 @@ def view_module(dojo, module):
297377
current_dojo_challenge=current_dojo_challenge,
298378
assessments=assessments,
299379
challenge_container_counts=challenge_container_counts,
380+
module_description_edit_url=module_description_edit_url,
381+
challenge_description_edit_urls=challenge_description_edit_urls,
382+
resource_description_edit_urls=resource_description_edit_urls,
300383
)
301384

302385

dojo_theme/static/css/custom.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,3 +1062,25 @@ code {
10621062
background-color: rgba(0, 0, 0, 0.8);
10631063
}
10641064
}
1065+
1066+
.github-edit-link {
1067+
float: right;
1068+
padding: 2px 6px;
1069+
margin-left: 10px;
1070+
font-size: 0.7rem;
1071+
color: var(--brand-light-gray);
1072+
text-decoration: none;
1073+
transition: all 0.2s ease;
1074+
opacity: 0.6;
1075+
}
1076+
1077+
.github-edit-link:hover {
1078+
color: var(--brand-green);
1079+
text-decoration: none;
1080+
opacity: 1;
1081+
}
1082+
1083+
.github-edit-link i {
1084+
margin-right: 2px;
1085+
font-size: 0.65rem;
1086+
}

dojo_theme/templates/dojo.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,17 @@ <h1 class="brand-white brand-mono-bold">{{ dojo.name or dojo.id }}<span class="b
1111
{% include "components/errors.html" %}
1212

1313
{% if dojo.description %}
14-
<p>{{ dojo.description | markdown }}</p>
14+
<div>
15+
{% if description_edit_url %}
16+
<a href="{{ description_edit_url }}"
17+
target="_blank"
18+
class="github-edit-link"
19+
title="Edit on GitHub">
20+
<i class="fas fa-edit"></i>
21+
</a>
22+
{% endif %}
23+
{{ dojo.description | markdown }}
24+
</div>
1525
<br>
1626
{% endif %}
1727

dojo_theme/templates/macros/widgets.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ <h2 class="mb-0 button-wrapper">
6363
</div>
6464
{% endmacro %}
6565

66+
6667
{% set svg_add_icon %}
6768
<svg class="w-100 h-100">
6869
<circle cx="50%" cy="50%" r="30%" stroke="gray" fill="none" stroke-width="8" stroke-dasharray="8"></circle>

dojo_theme/templates/module.html

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,17 @@ <h3 class="module-due-date" title="{{ assessment.date }}">{{ assessment.name }}
7171
<div class="container">
7272

7373
{% if module.description %}
74-
<p>{{ module.description | markdown }}</p>
74+
<div>
75+
{% if module_description_edit_url %}
76+
<a href="{{ module_description_edit_url }}"
77+
target="_blank"
78+
class="github-edit-link"
79+
title="Edit on GitHub">
80+
<i class="fas fa-edit"></i>
81+
</a>
82+
{% endif %}
83+
{{ module.description | markdown }}
84+
</div>
7585
<br>
7686
{% endif %}
7787

@@ -114,6 +124,14 @@ <h4 class="accordion-item-name">
114124

115125
{% elif resource.type == "markdown" %}
116126
<div class="embed-responsive">
127+
{% if resource_description_edit_urls.get(resource.resource_index) %}
128+
<a href="{{ resource_description_edit_urls[resource.resource_index] }}"
129+
target="_blank"
130+
class="github-edit-link"
131+
title="Edit on GitHub">
132+
<i class="fas fa-edit"></i>
133+
</a>
134+
{% endif %}
117135
{{ resource.content | markdown }}
118136
</div>
119137
{% endif %}
@@ -184,6 +202,14 @@ <h4 class="accordion-item-name challenge-name {{ active }}" data-challenge-index
184202
{% if lock_challenge %}
185203
<p><em>This challenge is locked</em></p>
186204
{% else %}
205+
{% if challenge_description_edit_urls.get(challenge.id) %}
206+
<a href="{{ challenge_description_edit_urls[challenge.id] }}"
207+
target="_blank"
208+
class="github-edit-link"
209+
title="Edit on GitHub">
210+
<i class="fas fa-edit"></i>
211+
</a>
212+
{% endif %}
187213
{{ challenge.description | markdown }}
188214
{% endif %}
189215
</div>

test/test_edit_links.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from utils import DOJO_URL
2+
3+
def test_github_edit_links_welcome_dojo(admin_session, welcome_dojo):
4+
"""Test that GitHub edit links appear correctly for the welcome dojo"""
5+
6+
response = admin_session.get(f"{DOJO_URL}/{welcome_dojo}")
7+
assert response.status_code == 200, f"Failed to load welcome dojo page: {response.status_code}"
8+
9+
assert "github.com/pwncollege/welcome-dojo/edit/main/" in response.text, \
10+
"Dojo page should have edit link"
11+
assert "fa-edit" in response.text, "Edit links should include Font Awesome edit icon"
12+
assert "github-edit-link" in response.text, "Edit links should have the github-edit-link CSS class"
13+
14+
response = admin_session.get(f"{DOJO_URL}/{welcome_dojo}/welcome")
15+
assert response.status_code == 200, f"Failed to load welcome module page: {response.status_code}"
16+
17+
assert "github.com/pwncollege/welcome-dojo/edit/main/" in response.text, \
18+
"Module page should have edit link"

0 commit comments

Comments
 (0)