Skip to content

Commit 86a6b7c

Browse files
authored
feat: store changeset into ingestion logs (#28)
1 parent a5ebe02 commit 86a6b7c

File tree

7 files changed

+124
-30
lines changed

7 files changed

+124
-30
lines changed

docker/requirements-diode-netbox-plugin.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
Brotli==1.1.0
12
certifi==2024.7.4
23
coverage==7.6.0
34
grpcio==1.62.1

netbox_diode_plugin/reconciler/sdk/v1/reconciler_pb2.py

Lines changed: 13 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

netbox_diode_plugin/reconciler/sdk/v1/reconciler_pb2.pyi

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,16 @@ class IngestionMetrics(_message.Message):
8585
no_changes: int
8686
def __init__(self, total: _Optional[int] = ..., new: _Optional[int] = ..., reconciled: _Optional[int] = ..., failed: _Optional[int] = ..., no_changes: _Optional[int] = ...) -> None: ...
8787

88+
class ChangeSet(_message.Message):
89+
__slots__ = ("id", "data")
90+
ID_FIELD_NUMBER: _ClassVar[int]
91+
DATA_FIELD_NUMBER: _ClassVar[int]
92+
id: str
93+
data: bytes
94+
def __init__(self, id: _Optional[str] = ..., data: _Optional[bytes] = ...) -> None: ...
95+
8896
class IngestionLog(_message.Message):
89-
__slots__ = ("id", "data_type", "state", "request_id", "ingestion_ts", "producer_app_name", "producer_app_version", "sdk_name", "sdk_version", "entity", "error")
97+
__slots__ = ("id", "data_type", "state", "request_id", "ingestion_ts", "producer_app_name", "producer_app_version", "sdk_name", "sdk_version", "entity", "error", "change_set")
9098
ID_FIELD_NUMBER: _ClassVar[int]
9199
DATA_TYPE_FIELD_NUMBER: _ClassVar[int]
92100
STATE_FIELD_NUMBER: _ClassVar[int]
@@ -98,6 +106,7 @@ class IngestionLog(_message.Message):
98106
SDK_VERSION_FIELD_NUMBER: _ClassVar[int]
99107
ENTITY_FIELD_NUMBER: _ClassVar[int]
100108
ERROR_FIELD_NUMBER: _ClassVar[int]
109+
CHANGE_SET_FIELD_NUMBER: _ClassVar[int]
101110
id: str
102111
data_type: str
103112
state: State
@@ -109,7 +118,8 @@ class IngestionLog(_message.Message):
109118
sdk_version: str
110119
entity: _ingester_pb2.Entity
111120
error: IngestionError
112-
def __init__(self, id: _Optional[str] = ..., data_type: _Optional[str] = ..., state: _Optional[_Union[State, str]] = ..., request_id: _Optional[str] = ..., ingestion_ts: _Optional[int] = ..., producer_app_name: _Optional[str] = ..., producer_app_version: _Optional[str] = ..., sdk_name: _Optional[str] = ..., sdk_version: _Optional[str] = ..., entity: _Optional[_Union[_ingester_pb2.Entity, _Mapping]] = ..., error: _Optional[_Union[IngestionError, _Mapping]] = ...) -> None: ...
121+
change_set: ChangeSet
122+
def __init__(self, id: _Optional[str] = ..., data_type: _Optional[str] = ..., state: _Optional[_Union[State, str]] = ..., request_id: _Optional[str] = ..., ingestion_ts: _Optional[int] = ..., producer_app_name: _Optional[str] = ..., producer_app_version: _Optional[str] = ..., sdk_name: _Optional[str] = ..., sdk_version: _Optional[str] = ..., entity: _Optional[_Union[_ingester_pb2.Entity, _Mapping]] = ..., error: _Optional[_Union[IngestionError, _Mapping]] = ..., change_set: _Optional[_Union[ChangeSet, _Mapping]] = ...) -> None: ...
113123

114124
class RetrieveIngestionLogsRequest(_message.Message):
115125
__slots__ = ("page_size", "state", "data_type", "request_id", "ingestion_ts_start", "ingestion_ts_end", "page_token", "only_metrics")

netbox_diode_plugin/templates/diode/ingestion_logs_table.html

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,63 @@
2626
<tr>
2727
<td colspan="{{ table.columns|length }}" style="padding: 0 !important;border: 0 !important;">
2828
<div class="tab-pane accordion-collapse collapse border-bottom" id="ingestion-log-{{ row.record.id }}">
29-
<div class="row p-3 pb-0">
30-
<div class="col col-6">
31-
<div class="card">
32-
<h2 class="card-header">{% trans "Ingested Entity" %}</h2>
33-
<pre>{{ row.record.entity|proto_to_json }}</pre>
34-
</div>
29+
<div class="row p-0 m-0">
30+
{% block tabs %}
31+
<div class="page-tabs">
32+
<ul class="nav nav-tabs px-3">
33+
<li class="nav-item" role="presentation">
34+
<a class="nav-link border-bottom-0 active" id="ingested-entity-{{ row.record.id }}-tab" data-bs-toggle="tab" data-bs-target="#ingested-entity-{{ row.record.id }}" type="button" role="tab" aria-selected="true">
35+
{% trans "Ingested Entity" %}
36+
</a>
37+
</li>
38+
<li class="nav-item" role="presentation">
39+
<a class="nav-link border-bottom-0" id="change-set-{{ row.record.id }}-tab" data-bs-toggle="tab" data-bs-target="#change-set-{{ row.record.id }}" type="button" role="tab">
40+
{% trans "Change Set" %}
41+
</a>
42+
</li>
43+
<li class="nav-item" role="presentation">
44+
<a class="nav-link border-bottom-0" id="error-{{ row.record.id }}-tab" data-bs-toggle="tab" data-bs-target="#error-{{ row.record.id }}" type="button" role="tab">
45+
{% trans "Error" %}
46+
</a>
47+
</li>
48+
</ul>
3549
</div>
36-
<div class="col col-6">
37-
<div class="card">
38-
<h2 class="card-header">{% trans "Error" %}</h2>
39-
{% if row.record.error.message != "" %}
40-
<pre>{{ row.record.error|proto_to_json }}</pre>
41-
{% else %}
42-
<pre class="text-muted">None</pre>
43-
{% endif %}
50+
{% endblock tabs %}
51+
<div class="tab-content">
52+
<div class="tab-pane show active" id="ingested-entity-{{ row.record.id }}" role="tabpanel" aria-labelledby="ingested-entity-{{ row.record.id }}-tab">
53+
<div class="row">
54+
<div class="col">
55+
<div class="card border-0 mb-0">
56+
<pre>{{ row.record.entity|proto_to_json }}</pre>
57+
</div>
58+
</div>
59+
</div>
60+
</div>
61+
<div class="tab-pane p-0" id="change-set-{{ row.record.id }}" role="tabpanel" aria-labelledby="change-set-{{ row.record.id }}-tab">
62+
<div class="row">
63+
<div class="col">
64+
<div class="card border-0 mb-0">
65+
{% if row.record.change_set.data != "" and row.record.change_set.data|length > 0 %}
66+
<pre>{{ row.record.change_set|proto_to_json }}</pre>
67+
{% else %}
68+
<pre class="text-muted">None</pre>
69+
{% endif %}
70+
</div>
71+
</div>
72+
</div>
73+
</div>
74+
<div class="tab-pane p-0" id="error-{{ row.record.id }}" role="tabpanel" aria-labelledby="error-{{ row.record.id }}-tab">
75+
<div class="row">
76+
<div class="col">
77+
<div class="card border-0 mb-0">
78+
{% if row.record.error.message != "" %}
79+
<pre>{{ row.record.error|proto_to_json }}</pre>
80+
{% else %}
81+
<pre class="text-muted">None</pre>
82+
{% endif %}
83+
</div>
84+
</div>
85+
</div>
4486
</div>
4587
</div>
4688
</div>

netbox_diode_plugin/templatetags/diode_filters.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#!/usr/bin/env python
22
# Copyright 2024 NetBox Labs Inc
33
"""Diode NetBox Plugin - Template Tags."""
4+
5+
import json
6+
7+
import brotli
48
from django import template
59
from google.protobuf.json_format import MessageToJson
610

@@ -12,10 +16,20 @@
1216
@register.filter("proto_to_json")
1317
def proto_to_json(value):
1418
"""Converts a protobuf message to a JSON string."""
19+
indent = 4
1520
if isinstance(value, reconciler_pb2.IngestionError) and value.message != "":
16-
return MessageToJson(value, indent=4)
21+
return MessageToJson(value, indent=indent)
1722

1823
if isinstance(value, ingester_pb2.Entity):
19-
return MessageToJson(value)
24+
return MessageToJson(value, indent=indent)
25+
26+
if isinstance(value, reconciler_pb2.ChangeSet):
27+
try:
28+
decompressed_data = brotli.decompress(value.data)
29+
decompressed_string = decompressed_data.decode("utf-8")
30+
json_data = json.loads(decompressed_string)
31+
return json.dumps(json_data, indent=indent)
32+
except Exception:
33+
return None
2034

2135
return None

0 commit comments

Comments
 (0)