Skip to content

Commit

Permalink
Adds dependency graphs for drafts belonging to a group.
Browse files Browse the repository at this point in the history
Removes links to Bill's dependency tools.
Fixes bug #536.
Commit ready to merge.
 - Legacy-Id: 7445
  • Loading branch information
rjsparks committed Mar 6, 2014
1 parent a0311b7 commit 74e4ff7
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 2 deletions.
5 changes: 4 additions & 1 deletion ietf/doc/models.py
Expand Up @@ -177,10 +177,13 @@ def is_downref(self):
if self.source.get_state().slug == 'rfc':
source_lvl = self.source.std_level.slug
elif self.source.intended_std_level:
source_lvl = self.source.intended_std_level.slug
source_lvl = self.source.intended_std_level and self.source.intended_std_level.slug
else:
source_lvl = None

if not source_lvl:
return None

if source_lvl not in ['bcp','ps','ds','std']:
return None

Expand Down
4 changes: 4 additions & 0 deletions ietf/settings.py
Expand Up @@ -366,6 +366,10 @@ def skip_suspicious_operations(record):
IDSUBMIT_MAX_DAILY_SUBMISSIONS = 1000
IDSUBMIT_MAX_DAILY_SUBMISSIONS_SIZE = 2000 # in MB

DOT_BINARY = '/usr/bin/dot'
UNFLATTEN_BINARY= '/usr/bin/unflatten'
PS2PDF_BINARY = '/usr/bin/ps2pdf'

# Account settings
DAYS_TO_EXPIRE_REGISTRATION_LINK = 3
HTPASSWD_COMMAND = "/usr/bin/htpasswd2"
Expand Down
1 change: 0 additions & 1 deletion ietf/templates/doc/document_draft.html
Expand Up @@ -232,7 +232,6 @@
<div class="links">
<a href="mailto:{{ doc.name }}@tools.ietf.org?subject=Mail%20regarding%20{{ doc.name }}" rel="nofollow">Email Authors</a>
| <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>
| <a href="http://www.fenron.net/~fenner/ietf/deps/index.cgi?dep={{ name }}" rel="nofollow">Dependencies to this document</a>
| <a href="{% url 'doc_references' doc.canonical_name %}" rel="nofollow">References</a>
| <a href="{% url 'doc_referenced_by' doc.canonical_name %}" rel="nofollow">Referenced By</a>
| <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>
Expand Down
88 changes: 88 additions & 0 deletions ietf/templates/wginfo/dot.txt
@@ -0,0 +1,88 @@
{% load mail_filters %}{% autoescape off %}
digraph draftdeps {
graph [fontame=Helvetica];
node [fontname=Helvetica];
edge [fontname=Helvetica];
subgraph cluster_key {
graph [label=Key,
rankdir=LR,
margin=.5,
fontname=Helvetica
];
subgraph key_a {
graph [rank=same];
key_colors [color=white,
fontcolor=black,
label="Colors in\nthis row"];
key_wgdoc [color="#0AFE47",
label="Product of\nthis WG",
style=filled,
wg=this];
key_otherwgdoc [color="#9999FF",
label="Product of\nother WG",
style=filled,
wg=blort];
key_individual [color="#FF800D",
label="Individual\nsubmission",
style=filled,
wg=individual];
}
subgraph key_b {
graph [rank=same];
key_shapes [color=white,
fontcolor=black,
label="Shapes in\nthis row"];
key_active [color="#9999FF",
label="Active\ndocument",
style=filled];
key_iesg [color="#9999FF",
label="IESG or\nRFC Queue",
shape=parallelogram,
style=filled];
key_rfc [color="#9999FF",
label="RFC\nPublished",
shape=box,
style=filled];
key_expired [color="#9999FF",
label="Expired!",
peripheries=3,
shape=house,
style=solid];
key_replaced [color="#9999FF",
label="Replaced",
peripheries=3,
shape=ellipse];
}
key_colors -> key_shapes [color=white,
fontcolor=black,
label="Line\ncolor\nreflects\nReference\ntype"];
key_wgdoc -> key_active [color=orange,
label="Orange link:\nUnsplit"];
key_otherwgdoc -> key_active [color=green,
label="Green link:\nInformative"];
key_individual -> key_iesg [color=blue,
label="Blue link:\nNormative"];
key_otherwgdoc -> key_expired [label="Black link:\nUnknown",
style=dashed];
key_wgdoc -> key_rfc [color=red,
label="Red link:\nDownref!",
arrowhead=normalnormal];
key_individual -> key_replaced [color=pink,
label="Pink link:\nReplaces",
style=dashed,
arrowhead=diamond];
}

{% for node in nodes %}
{{ node.nodename }} [ status="{{ node.get_state.slug }}",
wg="{{ node.group.acronym }}",{% for key,value in node.styles.items %}
{{ key }}={{ value }},{% endfor %}
];
{% endfor %}

{% for edge in edges%}
{{ edge.sourcename }} -> {{ edge.targetname }} {% if edge.styles %}[ {% for key,value in edge.styles.items %}{{ key }}={{ value }}{% if not forloop.last %}, {% endif %}{% endfor %} ] {% endif %};{% endfor %}


}
{% endautoescape %}
1 change: 1 addition & 0 deletions ietf/templates/wginfo/group_base.html
Expand Up @@ -69,6 +69,7 @@ <h1>{{ group.name}} ({{ group.acronym }})
<a {% if selected == "documents" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.group_documents" acronym=group.acronym %}"{% endif %}>Documents</a> |
<a {% if selected == "charter" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.group_charter" acronym=group.acronym %}"{% endif %}>Charter</a> |
<a {% if selected == "history" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.history" acronym=group.acronym %}"{% endif %}>History</a>
| <a href="{% url 'ietf.wginfo.views.dependencies_pdf' acronym=group.acronym %}">Dependency Graph</a>
{% if group.list_archive|startswith:"http:" or group.list_archive|startswith:"https:" or group.list_archive|startswith:"ftp:" %}
| <a href="{{ group.list_archive }}">List Archive &raquo;</a>
{% endif %}
Expand Down
2 changes: 2 additions & 0 deletions ietf/wginfo/urls.py
Expand Up @@ -23,6 +23,8 @@
(r'^(?P<acronym>[a-zA-Z0-9-]+)/charter/$', views.group_charter, None, 'group_charter'),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/init-charter/', edit.submit_initial_charter, None, "wg_init_charter"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/history/$', views.history),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/deps/dot/$', views.dependencies_dot),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/deps/pdf/$', views.dependencies_pdf),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/edit/$', edit.edit, {'action': "edit"}, "group_edit"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/conclude/$', edit.conclude, None, "wg_conclude"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "wg_edit_milestones"),
Expand Down
166 changes: 166 additions & 0 deletions ietf/wginfo/views.py
Expand Up @@ -35,10 +35,12 @@
import itertools

from django.shortcuts import get_object_or_404, render_to_response
from django.template.loader import render_to_string
from django.template import RequestContext
from django.http import HttpResponse
from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from django.db.models import Q

from ietf.doc.views_search import SearchForm, retrieve_search_results
from ietf.group.models import Group, GroupURL, Role
Expand All @@ -48,6 +50,10 @@
from ietf.doc.templatetags.ietf_filters import clean_whitespace
from ietf.ietfauth.utils import has_role

from ietf.utils.pipe import pipe
from tempfile import mkstemp
import os

def roles(group, role_name):
return Role.objects.filter(group=group, name=role_name).select_related("email", "person")

Expand Down Expand Up @@ -286,3 +292,163 @@ def history(request, acronym):
construct_group_menu_context(request, group, "history", {
"events": events,
}), RequestContext(request))


def nodename(name):
return name.replace('-','_')

class Edge(object):
def __init__(self,relateddocument):
self.relateddocument=relateddocument

def __hash__(self):
return hash("|".join([str(hash(nodename(self.relateddocument.source.name))),
str(hash(nodename(self.relateddocument.target.document.name))),
self.relateddocument.relationship.slug]))

def __eq__(self,other):
return self.__hash__() == other.__hash__()

def sourcename(self):
return nodename(self.relateddocument.source.name)

def targetname(self):
return nodename(self.relateddocument.target.document.name)

def styles(self):

# Note that the old style=dotted, color=red styling is never used

if self.relateddocument.is_downref():
return { 'color':'red','arrowhead':'normalnormal' }
else:
styles = { 'refnorm' : { 'color':'blue' },
'refinfo' : { 'color':'green' },
'refold' : { 'color':'orange' },
'refunk' : { 'style':'dashed' },
'replaces': { 'color':'pink', 'style':'dashed', 'arrowhead':'diamond' },
}
return styles[self.relateddocument.relationship.slug]

def get_node_styles(node,group):

styles=dict()

# Shape and style (note that old diamond shape is never used

styles['style'] = 'filled'

if node.get_state('draft').slug == 'rfc':
styles['shape'] = 'box'
elif node.get_state('draft-iesg') and not node.get_state('draft-iesg').slug in ['watching','dead']:
styles['shape'] = 'parallelogram'
elif node.get_state('draft').slug == 'expired':
styles['shape'] = 'house'
styles['style'] ='solid'
styles['peripheries'] = 3
elif node.get_state('draft').slug == 'repl':
styles['shape'] = 'ellipse'
styles['style'] ='solid'
styles['peripheries'] = 3
else:
pass # quieter form of styles['shape'] = 'ellipse'

# Color (note that the old 'Flat out red' is never used
if node.group.acronym == 'none':
styles['color'] = '"#FF800D"' # orangeish
elif node.group == group:
styles['color'] = '"#0AFE47"' # greenish
else:
styles['color'] = '"#9999FF"' # blueish

# Label
label = node.name
if label.startswith('draft-'):
if label.startswith('draft-ietf-'):
label=label[11:]
else:
label=label[6:]
try:
t=label.index('-')
label="%s\\n%s" % (label[:t],label[t+1:])
except:
pass
if node.group.acronym != 'none' and node.group != group:
label = "(%s) %s"%(node.group.acronym,label)
if node.get_state('draft').slug == 'rfc':
label = "%s\\n(%s)"%(label,node.canonical_name())
styles['label'] = '"%s"'%label

return styles

def make_dot(group):

references = Q(source__group=group,source__type='draft',relationship__slug__startswith='ref')
both_rfcs = Q(source__states__slug='rfc',target__document__states__slug='rfc')
inactive = Q(source__states__slug__in=['expired','repl'])
attractor = Q(target__name__in=['rfc5000','rfc5741'])
removed = Q(source__states__slug__in=['auth-rm','ietf-rm'])
relations = RelatedDocument.objects.filter(references).exclude(both_rfcs).exclude(inactive).exclude(attractor).exclude(removed)

edges = set()
for x in relations:
target_state = x.target.document.get_state_slug('draft')
if target_state!='rfc' or x.is_downref():
edges.add(Edge(x))

replacements = RelatedDocument.objects.filter(relationship__slug='replaces',target__document__in=[x.relateddocument.target.document for x in edges])

for x in replacements:
edges.add(Edge(x))

nodes = set([x.relateddocument.source for x in edges]).union([x.relateddocument.target.document for x in edges])

for node in nodes:
node.nodename=nodename(node.name)
node.styles = get_node_styles(node,group)

return render_to_string('wginfo/dot.txt',
dict( nodes=nodes, edges=edges )
)

def dependencies_dot(request, acronym):

group = get_object_or_404(Group, acronym=acronym)

return HttpResponse(make_dot(group),
content_type='text/plain; charset=UTF-8'
)

def dependencies_pdf(request, acronym):

group = get_object_or_404(Group, acronym=acronym)

dothandle,dotname = mkstemp()
os.close(dothandle)
dotfile = open(dotname,"w")
dotfile.write(make_dot(group))
dotfile.close()

unflathandle,unflatname = mkstemp()
os.close(unflathandle)

pshandle,psname = mkstemp()
os.close(pshandle)

pdfhandle,pdfname = mkstemp()
os.close(pdfhandle)

pipe("%s -f -l 10 -o %s %s" % (settings.UNFLATTEN_BINARY,unflatname,dotname))
pipe("%s -Tps -Gsize=10.5,8.0 -Gmargin=0.25 -Gratio=auto -Grotate=90 -o %s %s" % (settings.DOT_BINARY,psname,unflatname))
pipe("%s %s %s" % (settings.PS2PDF_BINARY,psname,pdfname))

pdfhandle = open(pdfname,"r")
pdf = pdfhandle.read()
pdfhandle.close()

os.unlink(pdfname)
os.unlink(psname)
os.unlink(unflatname)
os.unlink(dotname)

return HttpResponse(pdf, content_type='application/pdf')

0 comments on commit 74e4ff7

Please sign in to comment.