Skip to content

Commit e78931c

Browse files
committed
Merged [7445] from rjsparks@nostrum.com: Adds dependency graphs for drafts belonging to a group.
Removes links to Bill's dependency tools. Fixes bug ietf-tools#536. - Legacy-Id: 7446 Note: SVN reference [7445] has been migrated to Git commit 74e4ff7
2 parents d4c0102 + 74e4ff7 commit e78931c

6 files changed

Lines changed: 260 additions & 1 deletion

File tree

ietf/settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@ def skip_suspicious_operations(record):
366366
IDSUBMIT_MAX_DAILY_SUBMISSIONS = 1000
367367
IDSUBMIT_MAX_DAILY_SUBMISSIONS_SIZE = 2000 # in MB
368368

369+
DOT_BINARY = '/usr/bin/dot'
370+
UNFLATTEN_BINARY= '/usr/bin/unflatten'
371+
PS2PDF_BINARY = '/usr/bin/ps2pdf'
372+
369373
# Account settings
370374
DAYS_TO_EXPIRE_REGISTRATION_LINK = 3
371375
HTPASSWD_COMMAND = "/usr/bin/htpasswd2"

ietf/templates/doc/document_draft.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@
232232
<div class="links">
233233
<a href="mailto:{{ doc.name }}@tools.ietf.org?subject=Mail%20regarding%20{{ doc.name }}" rel="nofollow">Email Authors</a>
234234
| <a href="{% url "ipr_search" %}?option=document_search&amp;id={{ doc.name }}" rel="nofollow">IPR Disclosures{% if doc.related_ipr %} ({{doc.related_ipr|length}}){% endif %}</a>
235-
| <a href="http://www.fenron.net/~fenner/ietf/deps/index.cgi?dep={{ name }}" rel="nofollow">Dependencies to this document</a>
236235
| <a href="{% url 'doc_references' doc.canonical_name %}" rel="nofollow">References</a>
237236
| <a href="{% url 'doc_referenced_by' doc.canonical_name %}" rel="nofollow">Referenced By</a>
238237
| <a href="http://www.ietf.org/tools/idnits?url=http://www.ietf.org/archive/id/{{ doc.filename_with_rev }}" rel="nofollow" target="_blank">Check nits</a>

ietf/templates/wginfo/dot.txt

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
{% load mail_filters %}{% autoescape off %}
2+
digraph draftdeps {
3+
graph [fontame=Helvetica];
4+
node [fontname=Helvetica];
5+
edge [fontname=Helvetica];
6+
subgraph cluster_key {
7+
graph [label=Key,
8+
rankdir=LR,
9+
margin=.5,
10+
fontname=Helvetica
11+
];
12+
subgraph key_a {
13+
graph [rank=same];
14+
key_colors [color=white,
15+
fontcolor=black,
16+
label="Colors in\nthis row"];
17+
key_wgdoc [color="#0AFE47",
18+
label="Product of\nthis WG",
19+
style=filled,
20+
wg=this];
21+
key_otherwgdoc [color="#9999FF",
22+
label="Product of\nother WG",
23+
style=filled,
24+
wg=blort];
25+
key_individual [color="#FF800D",
26+
label="Individual\nsubmission",
27+
style=filled,
28+
wg=individual];
29+
}
30+
subgraph key_b {
31+
graph [rank=same];
32+
key_shapes [color=white,
33+
fontcolor=black,
34+
label="Shapes in\nthis row"];
35+
key_active [color="#9999FF",
36+
label="Active\ndocument",
37+
style=filled];
38+
key_iesg [color="#9999FF",
39+
label="IESG or\nRFC Queue",
40+
shape=parallelogram,
41+
style=filled];
42+
key_rfc [color="#9999FF",
43+
label="RFC\nPublished",
44+
shape=box,
45+
style=filled];
46+
key_expired [color="#9999FF",
47+
label="Expired!",
48+
peripheries=3,
49+
shape=house,
50+
style=solid];
51+
key_replaced [color="#9999FF",
52+
label="Replaced",
53+
peripheries=3,
54+
shape=ellipse];
55+
}
56+
key_colors -> key_shapes [color=white,
57+
fontcolor=black,
58+
label="Line\ncolor\nreflects\nReference\ntype"];
59+
key_wgdoc -> key_active [color=orange,
60+
label="Orange link:\nUnsplit"];
61+
key_otherwgdoc -> key_active [color=green,
62+
label="Green link:\nInformative"];
63+
key_individual -> key_iesg [color=blue,
64+
label="Blue link:\nNormative"];
65+
key_otherwgdoc -> key_expired [label="Black link:\nUnknown",
66+
style=dashed];
67+
key_wgdoc -> key_rfc [color=red,
68+
label="Red link:\nDownref!",
69+
arrowhead=normalnormal];
70+
key_individual -> key_replaced [color=pink,
71+
label="Pink link:\nReplaces",
72+
style=dashed,
73+
arrowhead=diamond];
74+
}
75+
76+
{% for node in nodes %}
77+
{{ node.nodename }} [ status="{{ node.get_state.slug }}",
78+
wg="{{ node.group.acronym }}",{% for key,value in node.styles.items %}
79+
{{ key }}={{ value }},{% endfor %}
80+
];
81+
{% endfor %}
82+
83+
{% for edge in edges%}
84+
{{ edge.sourcename }} -> {{ edge.targetname }} {% if edge.styles %}[ {% for key,value in edge.styles.items %}{{ key }}={{ value }}{% if not forloop.last %}, {% endif %}{% endfor %} ] {% endif %};{% endfor %}
85+
86+
87+
}
88+
{% endautoescape %}

ietf/templates/wginfo/group_base.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ <h1>{{ group.name}} ({{ group.acronym }})
6969
<a {% if selected == "documents" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.group_documents" acronym=group.acronym %}"{% endif %}>Documents</a> |
7070
<a {% if selected == "charter" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.group_charter" acronym=group.acronym %}"{% endif %}>Charter</a> |
7171
<a {% if selected == "history" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.history" acronym=group.acronym %}"{% endif %}>History</a>
72+
| <a href="{% url 'ietf.wginfo.views.dependencies_pdf' acronym=group.acronym %}">Dependency Graph</a>
7273
{% if group.list_archive|startswith:"http:" or group.list_archive|startswith:"https:" or group.list_archive|startswith:"ftp:" %}
7374
| <a href="{{ group.list_archive }}">List Archive &raquo;</a>
7475
{% endif %}

ietf/wginfo/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
(r'^(?P<acronym>[a-zA-Z0-9-]+)/charter/$', views.group_charter, None, 'group_charter'),
2424
(r'^(?P<acronym>[a-zA-Z0-9-]+)/init-charter/', edit.submit_initial_charter, None, "wg_init_charter"),
2525
(r'^(?P<acronym>[a-zA-Z0-9-]+)/history/$', views.history),
26+
(r'^(?P<acronym>[a-zA-Z0-9-]+)/deps/dot/$', views.dependencies_dot),
27+
(r'^(?P<acronym>[a-zA-Z0-9-]+)/deps/pdf/$', views.dependencies_pdf),
2628
(r'^(?P<acronym>[a-zA-Z0-9-]+)/edit/$', edit.edit, {'action': "edit"}, "group_edit"),
2729
(r'^(?P<acronym>[a-zA-Z0-9-]+)/conclude/$', edit.conclude, None, "wg_conclude"),
2830
(r'^(?P<acronym>[a-zA-Z0-9-]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "wg_edit_milestones"),

ietf/wginfo/views.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,17 @@
3232
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3333
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3434

35+
import os
3536
import itertools
37+
from tempfile import mkstemp
3638

3739
from django.shortcuts import get_object_or_404, render_to_response
40+
from django.template.loader import render_to_string
3841
from django.template import RequestContext
3942
from django.http import HttpResponse
4043
from django.conf import settings
4144
from django.core.urlresolvers import reverse as urlreverse
45+
from django.db.models import Q
4246

4347
from ietf.doc.views_search import SearchForm, retrieve_search_results
4448
from ietf.group.models import Group, GroupURL, Role
@@ -47,6 +51,7 @@
4751
from ietf.group.utils import get_charter_text
4852
from ietf.doc.templatetags.ietf_filters import clean_whitespace
4953
from ietf.ietfauth.utils import has_role
54+
from ietf.utils.pipe import pipe
5055

5156
def roles(group, role_name):
5257
return Role.objects.filter(group=group, name=role_name).select_related("email", "person")
@@ -286,3 +291,163 @@ def history(request, acronym):
286291
construct_group_menu_context(request, group, "history", {
287292
"events": events,
288293
}), RequestContext(request))
294+
295+
296+
def nodename(name):
297+
return name.replace('-','_')
298+
299+
class Edge(object):
300+
def __init__(self,relateddocument):
301+
self.relateddocument=relateddocument
302+
303+
def __hash__(self):
304+
return hash("|".join([str(hash(nodename(self.relateddocument.source.name))),
305+
str(hash(nodename(self.relateddocument.target.document.name))),
306+
self.relateddocument.relationship.slug]))
307+
308+
def __eq__(self,other):
309+
return self.__hash__() == other.__hash__()
310+
311+
def sourcename(self):
312+
return nodename(self.relateddocument.source.name)
313+
314+
def targetname(self):
315+
return nodename(self.relateddocument.target.document.name)
316+
317+
def styles(self):
318+
319+
# Note that the old style=dotted, color=red styling is never used
320+
321+
if self.relateddocument.is_downref():
322+
return { 'color':'red','arrowhead':'normalnormal' }
323+
else:
324+
styles = { 'refnorm' : { 'color':'blue' },
325+
'refinfo' : { 'color':'green' },
326+
'refold' : { 'color':'orange' },
327+
'refunk' : { 'style':'dashed' },
328+
'replaces': { 'color':'pink', 'style':'dashed', 'arrowhead':'diamond' },
329+
}
330+
return styles[self.relateddocument.relationship.slug]
331+
332+
def get_node_styles(node,group):
333+
334+
styles=dict()
335+
336+
# Shape and style (note that old diamond shape is never used
337+
338+
styles['style'] = 'filled'
339+
340+
if node.get_state('draft').slug == 'rfc':
341+
styles['shape'] = 'box'
342+
elif node.get_state('draft-iesg') and not node.get_state('draft-iesg').slug in ['watching','dead']:
343+
styles['shape'] = 'parallelogram'
344+
elif node.get_state('draft').slug == 'expired':
345+
styles['shape'] = 'house'
346+
styles['style'] ='solid'
347+
styles['peripheries'] = 3
348+
elif node.get_state('draft').slug == 'repl':
349+
styles['shape'] = 'ellipse'
350+
styles['style'] ='solid'
351+
styles['peripheries'] = 3
352+
else:
353+
pass # quieter form of styles['shape'] = 'ellipse'
354+
355+
# Color (note that the old 'Flat out red' is never used
356+
if node.group.acronym == 'none':
357+
styles['color'] = '"#FF800D"' # orangeish
358+
elif node.group == group:
359+
styles['color'] = '"#0AFE47"' # greenish
360+
else:
361+
styles['color'] = '"#9999FF"' # blueish
362+
363+
# Label
364+
label = node.name
365+
if label.startswith('draft-'):
366+
if label.startswith('draft-ietf-'):
367+
label=label[11:]
368+
else:
369+
label=label[6:]
370+
try:
371+
t=label.index('-')
372+
label="%s\\n%s" % (label[:t],label[t+1:])
373+
except:
374+
pass
375+
if node.group.acronym != 'none' and node.group != group:
376+
label = "(%s) %s"%(node.group.acronym,label)
377+
if node.get_state('draft').slug == 'rfc':
378+
label = "%s\\n(%s)"%(label,node.canonical_name())
379+
styles['label'] = '"%s"'%label
380+
381+
return styles
382+
383+
def make_dot(group):
384+
385+
references = Q(source__group=group,source__type='draft',relationship__slug__startswith='ref')
386+
both_rfcs = Q(source__states__slug='rfc',target__document__states__slug='rfc')
387+
inactive = Q(source__states__slug__in=['expired','repl'])
388+
attractor = Q(target__name__in=['rfc5000','rfc5741'])
389+
removed = Q(source__states__slug__in=['auth-rm','ietf-rm'])
390+
relations = RelatedDocument.objects.filter(references).exclude(both_rfcs).exclude(inactive).exclude(attractor).exclude(removed)
391+
392+
edges = set()
393+
for x in relations:
394+
target_state = x.target.document.get_state_slug('draft')
395+
if target_state!='rfc' or x.is_downref():
396+
edges.add(Edge(x))
397+
398+
replacements = RelatedDocument.objects.filter(relationship__slug='replaces',target__document__in=[x.relateddocument.target.document for x in edges])
399+
400+
for x in replacements:
401+
edges.add(Edge(x))
402+
403+
nodes = set([x.relateddocument.source for x in edges]).union([x.relateddocument.target.document for x in edges])
404+
405+
for node in nodes:
406+
node.nodename=nodename(node.name)
407+
node.styles = get_node_styles(node,group)
408+
409+
return render_to_string('wginfo/dot.txt',
410+
dict( nodes=nodes, edges=edges )
411+
)
412+
413+
def dependencies_dot(request, acronym):
414+
415+
group = get_object_or_404(Group, acronym=acronym)
416+
417+
return HttpResponse(make_dot(group),
418+
content_type='text/plain; charset=UTF-8'
419+
)
420+
421+
def dependencies_pdf(request, acronym):
422+
423+
group = get_object_or_404(Group, acronym=acronym)
424+
425+
dothandle,dotname = mkstemp()
426+
os.close(dothandle)
427+
dotfile = open(dotname,"w")
428+
dotfile.write(make_dot(group))
429+
dotfile.close()
430+
431+
unflathandle,unflatname = mkstemp()
432+
os.close(unflathandle)
433+
434+
pshandle,psname = mkstemp()
435+
os.close(pshandle)
436+
437+
pdfhandle,pdfname = mkstemp()
438+
os.close(pdfhandle)
439+
440+
pipe("%s -f -l 10 -o %s %s" % (settings.UNFLATTEN_BINARY,unflatname,dotname))
441+
pipe("%s -Tps -Gsize=10.5,8.0 -Gmargin=0.25 -Gratio=auto -Grotate=90 -o %s %s" % (settings.DOT_BINARY,psname,unflatname))
442+
pipe("%s %s %s" % (settings.PS2PDF_BINARY,psname,pdfname))
443+
444+
pdfhandle = open(pdfname,"r")
445+
pdf = pdfhandle.read()
446+
pdfhandle.close()
447+
448+
os.unlink(pdfname)
449+
os.unlink(psname)
450+
os.unlink(unflatname)
451+
os.unlink(dotname)
452+
453+
return HttpResponse(pdf, content_type='application/pdf')

0 commit comments

Comments
 (0)