Skip to content

Commit 2f1a078

Browse files
authored
Merge pull request #412 from i3dnet-akoopen/retn-parser
Add parser for provider/vendor RETN.
2 parents c4ecbee + 8c792fa commit 2f1a078

17 files changed

Lines changed: 1130 additions & 0 deletions

changes/412.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added parser for provider RETN.

circuit_maintenance_parser/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
HGC,
1515
NTT,
1616
PCCW,
17+
RETN,
1718
Apple,
1819
AquaComms,
1920
Arelion,
@@ -66,6 +67,7 @@
6667
NTT,
6768
PacketFabric,
6869
PCCW,
70+
RETN,
6971
Seaborn,
7072
Sparkle,
7173
Tata,
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""RETN parser."""
2+
3+
import logging
4+
import re
5+
from typing import Dict, List
6+
7+
from bs4.element import ResultSet # type: ignore
8+
from dateutil import parser
9+
10+
from circuit_maintenance_parser.parser import EmailSubjectParser, Html, Impact, Status
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
class HtmlParserRETN1(Html):
16+
"""Notifications Parser for RETN notifications."""
17+
18+
def parse_html(self, soup: ResultSet) -> List[Dict]:
19+
"""Execute parsing."""
20+
data: Dict[str] = {"circuits": []}
21+
self.parse_bold(soup.find_all("b"), data)
22+
self.parse_h3(soup.find_all("h3"), data)
23+
self.parse_h4(soup.find_all("h4"), data)
24+
25+
return [data]
26+
27+
def parse_h3(self, headers: ResultSet, data: Dict):
28+
"""Parse H3 elements to find the status."""
29+
for header in headers:
30+
if "Ticket Opened" in header.text:
31+
data["status"] = Status("CONFIRMED")
32+
elif "Ticket Resolved" in header.text:
33+
data["status"] = Status("COMPLETED")
34+
elif "New Update" in header.text:
35+
data["status"] = Status("IN-PROCESS")
36+
elif "Rescheduling Update" in header.text:
37+
data["status"] = Status("RE-SCHEDULED")
38+
39+
if "status" not in data:
40+
data["status"] = Status("NO-CHANGE")
41+
42+
def parse_h4(self, headers: ResultSet, data: Dict):
43+
"""Parse H4 elements to find impact, CID's and summary."""
44+
for header in headers:
45+
# CID's will follow and impact inside of this element
46+
if "service" in header.text.lower():
47+
impact = Impact("NO-IMPACT")
48+
if "complete loss" in header.text:
49+
impact = Impact("OUTAGE")
50+
elif "50 ms flaps" in header.text:
51+
impact = Impact("REDUCED-REDUNDANCY")
52+
elif "services at risk" in header.text:
53+
impact = Impact("REDUCED-REDUNDANCY")
54+
elif "RTT increasing" in header.text:
55+
impact = Impact("REDUCED-REDUNDANCY")
56+
57+
# Find CID's
58+
header_next = header.next_sibling
59+
while header_next:
60+
text = header_next.text.strip()
61+
if "description" in text.lower():
62+
break
63+
64+
if header_next.text.strip() != "":
65+
data["circuits"].append({"circuit_id": text, "impact": impact})
66+
67+
header_next = header_next.next_sibling
68+
69+
# Summary will follow
70+
elif "Description" in header.text:
71+
data["summary"] = header.next_sibling.text.strip()
72+
73+
def parse_bold(self, bolds: ResultSet, data: Dict):
74+
"""Parse B (bold) elements to find start and end time."""
75+
for bold in bolds:
76+
if "planned start time" in bold.text.lower():
77+
data["start"] = self.dt2ts(parser.parse(bold.next_sibling.text.strip(), dayfirst=True))
78+
elif "planned end time" in bold.text.lower():
79+
data["end"] = self.dt2ts(parser.parse(bold.next_sibling.text.strip(), dayfirst=True))
80+
81+
82+
class SubjectParserRETN1(EmailSubjectParser):
83+
"""Parse the subject of an RETN circuit maintenance email. The subject contains the maintenance ID and account."""
84+
85+
def parse_subject(self, subject: str) -> List[Dict]:
86+
"""Parse the RETN Email subject for maintenance ID and account.
87+
88+
Example subject line: [RETN.NET Ticket#PW-40135152]: Planned Works Notification CID#133337
89+
90+
"""
91+
data = {}
92+
parse_subject = re.search(r"Ticket#([A-Z]+-[0-9]+)](.*)CID#([0-9]+)", subject)
93+
if parse_subject:
94+
data["maintenance_id"] = parse_subject[1]
95+
data["account"] = parse_subject[3]
96+
97+
return [data]

circuit_maintenance_parser/provider.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from circuit_maintenance_parser.parsers.netflix import TextParserNetflix1
3434
from circuit_maintenance_parser.parsers.openai import OpenAIParser
3535
from circuit_maintenance_parser.parsers.pccw import HtmlParserPCCW, SubjectParserPCCW
36+
from circuit_maintenance_parser.parsers.retn import HtmlParserRETN1, SubjectParserRETN1
3637
from circuit_maintenance_parser.parsers.seaborn import (
3738
HtmlParserSeaborn1,
3839
HtmlParserSeaborn2,
@@ -489,6 +490,17 @@ class PCCW(GenericProvider):
489490
_default_organizer = "mailto:gsoc-planned-event@pccwglobal.com"
490491

491492

493+
class RETN(GenericProvider):
494+
"""RETN provider custom class."""
495+
496+
_processors: List[GenericProcessor] = PrivateAttr(
497+
[
498+
CombinedProcessor(data_parsers=[EmailDateParser, SubjectParserRETN1, HtmlParserRETN1]),
499+
]
500+
)
501+
_default_organizer = PrivateAttr("no-reply@retn.net")
502+
503+
492504
class Seaborn(GenericProvider):
493505
"""Seaborn provider custom class."""
494506

0 commit comments

Comments
 (0)