From 3d8a1dc9e97905bea9ed0c12c0c7edcb49b4f1c6 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 31 May 2022 17:04:06 +0300 Subject: [PATCH 01/22] feat: Use d3 for doc dependencies --- ietf/group/urls.py | 1 + ietf/group/views.py | 45 ++++++++++++++++ ietf/templates/group/group_about.html | 77 ++++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/ietf/group/urls.py b/ietf/group/urls.py index 07eb2c4b631..7a1392d500e 100644 --- a/ietf/group/urls.py +++ b/ietf/group/urls.py @@ -28,6 +28,7 @@ url(r'^history/addcomment/$',views.add_comment), url(r'^email/$', views.email), url(r'^deps/(?P[\w-]+)/$', views.dependencies), + url(r'^deps.json$', views.dependencies_json), url(r'^meetings/$', views.meetings), url(r'^edit/$', views.edit, {'action': "edit"}), url(r'^edit/(?P[-a-z0-9_]+)/?$', views.edit, {'action': "edit"}), diff --git a/ietf/group/views.py b/ietf/group/views.py index 88267e78835..b9cf6d54655 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -41,6 +41,7 @@ import math import os import re +import json from tempfile import mkstemp from collections import OrderedDict, defaultdict @@ -751,6 +752,50 @@ def dependencies(request, acronym, group_type=None, output_type="pdf"): output_type = "image/svg+xml" return HttpResponse(out, content_type=output_type) + +@cache_page(60 * 60) +def dependencies_json(request, acronym, group_type=None): + group = get_group_or_404(acronym, group_type) + if not group.features.has_documents: + raise Http404 + + references = Q( + source__group=group, source__type="draft", relationship__slug__startswith="ref" + ) + # both_rfcs = Q(source__states__slug="rfc", target__docs__states__slug="rfc") + # inactive = Q(source__states__slug__in=["expired", "repl"]) + # removed = Q(source__states__slug__in=["auth-rm", "ietf-rm"]) + # relations = ( + # RelatedDocument.objects.filter(references) + # .exclude(both_rfcs) + # .exclude(inactive) + # .exclude(removed) + # ) + relations = RelatedDocument.objects.filter(references) + + edges = set() + for x in relations: + # target_state = x.target.document.get_state_slug('draft') + edges.add(x) + + # replacements = RelatedDocument.objects.filter( + # relationship__slug="replaces", + # target__docs__in=[x.target.document for x in edges], + # ) + + # for x in replacements: + # edges.add(x) + + nodes = set([x.source for x in edges]).union([x.target.document for x in edges]) + + graph = { + "nodes": [{"id": x.name} for x in nodes], + "edges": [{"source": x.source.name, "target": x.target.name}], + } + print(graph) + + return HttpResponse(json.dumps(graph), content_type="application/json") + def email_aliases(request, acronym=None, group_type=None): group = get_group_or_404(acronym,group_type) if acronym else None diff --git a/ietf/templates/group/group_about.html b/ietf/templates/group/group_about.html index 44906a3faa9..c3d5a2046cf 100644 --- a/ietf/templates/group/group_about.html +++ b/ietf/templates/group/group_about.html @@ -1,7 +1,7 @@ {% extends "group/group_base.html" %} {# Copyright The IETF Trust 2015, All Rights Reserved #} {% load origin %} -{% load ietf_filters %} +{% load static ietf_filters %} {% load markup_tags %} {% load textfilters group_filters %} {% block group_content %} @@ -126,6 +126,33 @@ href="{% url 'ietf.group.views.dependencies' group_type=group.type_id acronym=group.acronym output_type='svg' %}"> SVG + + {% endif %} @@ -340,3 +367,51 @@

{% endif %} {% endif %} {% endblock %} +{% block js %} + + +{% endblock %} \ No newline at end of file From 84a7f5dffca32b706a9d3522f2fef2743f914c0c Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 6 Jun 2022 09:45:07 +0300 Subject: [PATCH 02/22] Interim commit --- ietf/group/urls.py | 1 + ietf/group/views.py | 88 ++++++++++--- ietf/templates/group/group_deps.html | 185 +++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 20 deletions(-) create mode 100644 ietf/templates/group/group_deps.html diff --git a/ietf/group/urls.py b/ietf/group/urls.py index 7a1392d500e..fd15155613d 100644 --- a/ietf/group/urls.py +++ b/ietf/group/urls.py @@ -20,6 +20,7 @@ url(r'^documents/subscription/$', community_views.subscription), url(r'^charter/$', views.group_about), url(r'^about/$', views.group_about), + url(r'^deps/$', views.group_deps), url(r'^about/rendertest/$', views.group_about_rendertest), url(r'^about/status/$', views.group_about_status), url(r'^about/status/edit/$', views.group_about_status_edit), diff --git a/ietf/group/views.py b/ietf/group/views.py index b9cf6d54655..40b67a27663 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -558,6 +558,40 @@ def group_about(request, acronym, group_type=None): "closing_note": e, })) +def group_deps(request, acronym, group_type=None): + group = get_group_or_404(acronym, group_type) + + # fill_in_charter_info(group) + + # e = group.latest_event(type__in=("changed_state", "requested_close",)) + # requested_close = group.state_id != "conclude" and e and e.type == "requested_close" + + # e = None + # if group.state_id == "conclude": + # e = group.latest_event(type='closing_note') + + # can_manage = can_manage_all_groups_of_type(request.user, group.type_id) + # charter_submit_url = "" + # if group.features.has_chartering_process: + # charter_submit_url = urlreverse('ietf.doc.views_charter.submit', kwargs={ "name": charter_name_for_group(group) }) + + # can_provide_update = can_provide_status_update(request.user, group) + # status_update = group.latest_event(type="status_update") + + + return render(request, 'group/group_deps.html', + construct_group_menu_context(request, group, "deps", group_type, { + # "milestones_in_review": group.groupmilestone_set.filter(state="review"), + # "milestone_reviewer": milestone_reviewer_for_group_type(group_type), + # "requested_close": requested_close, + # "can_manage": can_manage, + # "can_provide_status_update": can_provide_update, + # "status_update": status_update, + # "charter_submit_url": charter_submit_url, + # "editable_roles": group.used_roles or group.features.default_used_roles, + # "closing_note": e, + })) + def all_status(request): wgs = Group.objects.filter(type='wg',state__in=['active','bof']) rgs = Group.objects.filter(type='rg',state__in=['active','proposed']) @@ -762,40 +796,54 @@ def dependencies_json(request, acronym, group_type=None): references = Q( source__group=group, source__type="draft", relationship__slug__startswith="ref" ) - # both_rfcs = Q(source__states__slug="rfc", target__docs__states__slug="rfc") - # inactive = Q(source__states__slug__in=["expired", "repl"]) - # removed = Q(source__states__slug__in=["auth-rm", "ietf-rm"]) - # relations = ( - # RelatedDocument.objects.filter(references) - # .exclude(both_rfcs) - # .exclude(inactive) - # .exclude(removed) - # ) - relations = RelatedDocument.objects.filter(references) + both_rfcs = Q(source__states__slug="rfc", target__docs__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') - edges.add(x) + target_state = x.target.document.get_state_slug("draft") + if target_state != "rfc" or x.is_downref(): + edges.add(x) - # replacements = RelatedDocument.objects.filter( - # relationship__slug="replaces", - # target__docs__in=[x.target.document for x in edges], - # ) + replacements = RelatedDocument.objects.filter( + relationship__slug="replaces", + target__docs__in=[x.target.document for x in edges], + ) - # for x in replacements: - # edges.add(x) + for x in replacements: + edges.add(x) nodes = set([x.source for x in edges]).union([x.target.document for x in edges]) graph = { - "nodes": [{"id": x.name} for x in nodes], - "edges": [{"source": x.source.name, "target": x.target.name}], + "nodes": [ + { + "id": x.name, + "rfc": x.get_state("draft").slug == "rfc", + "dead": not x.get_state("draft-iesg").slug + in ["idexists", "watching", "dead"], + "expired": x.get_state("draft").slug == "expired", + "replaced": x.get_state("draft").slug == "repl", + } + for x in nodes + ], + "edges": [{"source": x.source.name, "target": x.target.name} for x in edges], } print(graph) return HttpResponse(json.dumps(graph), content_type="application/json") + + def email_aliases(request, acronym=None, group_type=None): group = get_group_or_404(acronym,group_type) if acronym else None diff --git a/ietf/templates/group/group_deps.html b/ietf/templates/group/group_deps.html new file mode 100644 index 00000000000..ecfda4355c2 --- /dev/null +++ b/ietf/templates/group/group_deps.html @@ -0,0 +1,185 @@ +{% extends "group/group_base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin static %} +{% block morecss %} +#deps-modal .modal-body { +height: 100vh; +} +{% endblock %} +{% block group_content %} +{% origin %} + + +{% endblock %} +{% block js %} + + +{% endblock %} \ No newline at end of file From 2b525e3da251217b3fe8454239ded96d6b71cd4c Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 6 Jun 2022 11:32:34 +0300 Subject: [PATCH 03/22] Progress --- ietf/group/views.py | 14 ++++- ietf/templates/group/group_deps.html | 82 +++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 16 deletions(-) diff --git a/ietf/group/views.py b/ietf/group/views.py index 40b67a27663..c602f79cd60 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -833,12 +833,22 @@ def dependencies_json(request, acronym, group_type=None): in ["idexists", "watching", "dead"], "expired": x.get_state("draft").slug == "expired", "replaced": x.get_state("draft").slug == "repl", + "individual": x.group.acronym == "none", + "group": x.group == group, } for x in nodes ], - "edges": [{"source": x.source.name, "target": x.target.name} for x in edges], + "edges": [ + { + "source": x.source.name, + "target": x.target.document.name, + "rel": "downref" + if x.is_downref() + else x.relationship.slug, + } + for x in edges + ], } - print(graph) return HttpResponse(json.dumps(graph), content_type="application/json") diff --git a/ietf/templates/group/group_deps.html b/ietf/templates/group/group_deps.html index ecfda4355c2..008a5a148cc 100644 --- a/ietf/templates/group/group_deps.html +++ b/ietf/templates/group/group_deps.html @@ -5,6 +5,50 @@ #deps-modal .modal-body { height: 100vh; } +circle { +fill: white; +stroke: black; +stroke-width: 2; +} + +.replaced circle { +fill: orange; +} + +.dead circle { +fill: grey; +} + +.expired circle { +fill: lightgrey; +} + +.individual circle { +stroke-dasharray: 5; +} + +.group circle { +stroke-width: 4; +} + +line { +stroke: black; +stroke-width: 2; +} + +line.refinfo { +stroke-dasharray: 5; +} + +line.replaces { +stroke: orange; +} + +line.downref { +stroke: red; +stroke-width: 4; +} + {% endblock %} {% block group_content %} {% origin %} @@ -86,11 +130,25 @@