From 25a16daba1873361b4616c71618059bd6ba0e53e Mon Sep 17 00:00:00 2001 From: alshapton Date: Wed, 10 Aug 2022 09:05:46 +0100 Subject: [PATCH 01/11] Added new --heading option to change ADR page heading, also fixed mission --template-dir option from README.md --- README.md | 12 +++++++----- adr_viewer/__init__.py | 28 +++++++++++++++++----------- adr_viewer/templates/index.html | 4 ++-- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 27199d1..f784301 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,13 @@ $ python setup.py install Usage: adr-viewer [OPTIONS] Options: - --adr-path TEXT Directory containing ADR files. [default: doc/adr/] - --output TEXT File to write output to. [default: index.html] - --serve Serve content at http://localhost:8000/ - --port INT Custom server port [default: 8000] - --help Show this message and exit. + --adr-path TEXT Directory containing ADR files. [default: doc/adr/] + --output TEXT File to write output to. [default: index.html] + --serve Serve content at http://localhost:8000/ + --port INTEGER Change port for the server [default: 8000] + --template-dir TEXT Template directory + --heading TEXT ADR Page Heading [default: ADR Viewer - ] + --help Show this message and exit ``` The default for `--adr-path` is `doc/adr/` because this is the default path generated by `adr-tools`. diff --git a/adr_viewer/__init__.py b/adr_viewer/__init__.py index 8093691..adddbf6 100644 --- a/adr_viewer/__init__.py +++ b/adr_viewer/__init__.py @@ -46,7 +46,7 @@ def parse_adr_to_config(path): header = soup.find('h1') if header: - return { + return { 'status': status, 'body': adr_as_html, 'title': header.text @@ -80,15 +80,17 @@ def run_server(content, port): run(app, host='localhost', port=port, quiet=True) -def generate_content(path, template_dir_override=None): +def generate_content(path, template_dir_override=None, heading=None): files = get_adr_files("%s/*.md" % path) + if not heading: + heading = 'ADR Viewer - ' + os.path.basename(os.getcwd()) + config = { - 'project_title': os.path.basename(os.getcwd()), + 'heading': heading, 'records': [] } - for index, adr_file in enumerate(files): adr_attributes = parse_adr_to_config(adr_file) @@ -104,16 +106,20 @@ def generate_content(path, template_dir_override=None): @click.command() -@click.option('--adr-path', default='doc/adr/', help='Directory containing ADR files.', show_default=True) -@click.option('--output', default='index.html', help='File to write output to.', show_default=True) -@click.option('--serve', default=False, help='Serve content at http://localhost:8000/', is_flag=True) -@click.option('--port', default=8000, help='Change port for the server', show_default=True) -@click.option('--template-dir', default=None, help='Template directory.', show_default=True) -def main(adr_path, output, serve, port, template_dir): - content = generate_content(adr_path, template_dir) +@click.option('--adr-path', default='doc/adr/', help='Directory containing ADR files.', show_default=True) +@click.option('--output', default='index.html', help='File to write output to.', show_default=True) +@click.option('--serve', default=False, help='Serve content at http://localhost:8000/', is_flag=True) +@click.option('--port', default=8000, help='Change port for the server', show_default=True) +@click.option('--template-dir', default=None, help='Template directory.', show_default=True) +@click.option('--heading', default='ADR Viewer - ', help='ADR Page Heading', show_default=True) +def main(adr_path, output, serve, port, template_dir, heading): + content = generate_content(adr_path, template_dir, heading) if serve: run_server(content, port) else: with open(output, 'w') as out: out.write(content) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/adr_viewer/templates/index.html b/adr_viewer/templates/index.html index 6f5639b..07db540 100644 --- a/adr_viewer/templates/index.html +++ b/adr_viewer/templates/index.html @@ -6,7 +6,7 @@ - ADR Viewer - {{ config.project_title }} + {{ config.heading }} + + + +

ADR Viewer -

+ + + + + \ No newline at end of file diff --git a/adr_viewer/templates/.DS_Store b/adr_viewer/templates/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ead02402fcfc9202364da30e42f60bfd7b3999a9 GIT binary patch literal 6148 zcmeHKJ5Iwu5Pg%RaEOpn(jfT+k#YkQnSwe6E%}KA*>NO3LR9W)xCwVaoP(>to885- z6G90H%}Bd%Kl65WpDm9UfQ)YEL!b{JVo_{$S$$zTuVu$tVMKYNYn)}pY?>8Q_H{wC zzpsEkyAI}<;0BZC^OHBuJ-s_~2dVtB%i(xd@s=2&K)9?;NXutHb^VgGljJ%kC zJHJ`(tljH43b^c6!VRgW66Hu)>CI`B!I_HpHEBY;vIrd_2 zw8t%GggY`G_C|W%@@Ln~6fgx$fnQaCIa@92d9>OTFa=D3N&)>o#4L(Qz|y1pbg*() z0Ah#3&e(Rp{=`@Zh)KZGBS&cBsYFkeaK(^ta}ZDGd=mUikDd;3k-1KYEa8SC;o>2& zbB81ztu_Trfu;g&-|cJuKiz--Z<4HM3YY@_N&)Gl<8;I$h1|MxIL);&%LR*?`js9l k3L7pJ(^pgR5sNd9$J!t!0ZWe@q1lgslff!e;71ks0E4h-`2YX_ literal 0 HcmV?d00001 diff --git a/adr_viewer/templates/timeline/index.html b/adr_viewer/templates/timeline/index.html new file mode 100644 index 0000000..f282b58 --- /dev/null +++ b/adr_viewer/templates/timeline/index.html @@ -0,0 +1,249 @@ + + + + + {{ config.heading }} + + + + + + + +

{{ config.heading }}

+
    + {% for adr_record in config.records %} +
  • + {% set checked = '' %} + {% if adr_record.index == 0 %} + {% set checked = 'checked' %} + {% endif %} + +
    + + {{ adr_record.date }} + +
    +
    +

    + {{ adr_record.body|safe }} +

    +
    +
  • + {% endfor %} +
+ + + + diff --git a/adr_viewer/templates/index.html b/adr_viewer/templates/vanilla/index.html similarity index 100% rename from adr_viewer/templates/index.html rename to adr_viewer/templates/vanilla/index.html diff --git a/documentation/.DS_Store b/documentation/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9a091d9c7b7e07e722f061268c8732409f248110 GIT binary patch literal 6148 zcmeHK&2AGh5FV#V>ja_ZfJ85qeBlF_sPqfwdSSAU$PI{SoP%8>910{9Fud^BXmmCa6M zeMha5wGKIGBz6oZa3@x5--*R43p2nBFaxV&z#W6~!Rm(Ok(dEy;2&Xt&Ig4`=s7Gc znxg{?^8`SwquU7D^e;p?%Ax15w1_Pz!lokHRN23E?z4Fnc-*8l(j literal 0 HcmV?d00001 diff --git a/documentation/source/main/.DS_Store b/documentation/source/main/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b2eec2ada3f7f4ab039324ac235da5b843527fb8 GIT binary patch literal 6148 zcmeHKPfrs;6n_Inw?$-u^2eyj#>T{ihEP!n2jfyOrtv_-5-b5MyX{g|w=;EjONCm~ zkKj!``vLq6-n{75qX)l$7ti=+b}H2BQ6pwwGV^;g@4cCMzwOL)0Dxrc@AX5N_wCNBU&X03s1%*x=_}R{}e5d#qe{~7~)@16k%s$fC|y7wbB@2#5a_`&S#C(_p+8#r@TQ4&gGD6wtqv4#I58brZ9lKFflS_;SrbBH{1UqFLUA7)Lw9Fb=R;OiOjAMibMTsl%qB_}Z&S%ov z+(M?6)|v~~XVcn^xm&GPT$#L@xt-go9vn8GwVuBcSpdQFO31Lp4!lOCD@UiaFLNXx zIyrt;)lQc2#NfH1^TX=M=-5bdWPE&lVsh%j=*3G#b<8qLk1HNu?-FyFmv!rQQ62V? zfn15vyl3xYwuQic8W_$Ynnr&LShdR!kGs|mYUqIU{`Z=m*I?j1uHdf^4D$8N9>6yY?z*B(u$ou*mh0Jc2{VY z1p%uT9L$2RTuf=y%#uSZsXJdoCgUmnTBxaz>X54>JDNn2(&-DKuTe&A49bU=lzv~j zC@>9+V89kUfi}E^H~5o$2Or@Je1ju0L?*};GELItCYdKXStP4ulk5w(BnBA*x}3`e`h z-4S((?GVFoAYnKVIWv(j6e4EFI8C?%(L;>th=GWKZU*|prpoXCyT5<`cLznSh=GWK z|B3+;TQ-)nn3B9(dy?aKt%hX>3m5L!4pD?)C9h+l@T+(Vix9MFTtU<&wnM}ol<1Fu NpdqRv2L37o-vN=}>Jb0{ literal 0 HcmV?d00001 diff --git a/documentation/source/main/images/.DS_Store b/documentation/source/main/images/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a17f35d67c220216fbfd730ec781159397b7be57 GIT binary patch literal 8196 zcmeHM!EVz)5S>j@sS*NOkg6U)^$)nBi9f)Ua^TDlkhsNEB(W9R1Dt$E`~ts#`Z1{A zQgMJcvum?muU$|P3SzhNPGbALnSGvJXSPJ74$qGdh<1r+69?O^JLo)(uk+Dr19OJK z3V5QJGCHSsbVfz%?rPhj_VQM4x1WVX{+I+G@O+2 ztW1TX#5-`2+)gUhHuOP(pg>yzuH7F|LQn8_aIJn{;9Ef}I;Io!F_ko>9G_#{w~ud2 z%3sWjY&I`u;J~y~k74(}OA|-G%RBb-4!+)!cjD(AKD#Av(!qP-@ZPiZokP={PO(qo z)sUV#*t-_C1a67l6)#eKbda^zF`n2BUI)2@y>GlSc7bQUkW6^*_bf4gww`+(GoyQB zrZ4boBeyNTEBIxlcsGeN&_8Oe@ytwQ7MJweua`t_>$NR8#R|`xOv~sE&X#@#b>&3SW*8%$ zJ_gRz{un0x3>%kD3_Gn+Bn<=aNtV#*L(h2nqxs3@%|Xdxd)?c?v&fLQF%*rP$W}O4 zVE!(lb%vbwa>-g}N3(T}>kMoQVa)w_S<`I%LyUb2e{Y-BJCoTo6`qb&XviyAKTC;x znabSkT+3vB*(@LpZH*Kd>R5KU{(s)R|G!2Nhfsn7L4iN5fEpdWJURrcsjXZ5Tx&-- zUg6-z_f^^o1f7)Quu_i0Hvcfh=LoklCN;Lw7H81@`bL0Tweas5`z_V}Jrmyl?Oze$ L{V&=3?{+@`ev$X; literal 0 HcmV?d00001 From 370f3c5b91ef0e4cfb23210f2db6dbbfce3ac7e8 Mon Sep 17 00:00:00 2001 From: alshapton Date: Tue, 23 Aug 2022 08:52:54 +0100 Subject: [PATCH 03/11] Working version of timeline template - stalled until the merges for config.toml can be done to the main branch and inherited by this branch --- adr_viewer/__init__.py | 42 +++++++++++++++++++++-- adr_viewer/templates/.DS_Store | Bin 6148 -> 6148 bytes adr_viewer/templates/timeline/index.html | 23 ++++++++----- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/adr_viewer/__init__.py b/adr_viewer/__init__.py index 20df250..02f82f1 100644 --- a/adr_viewer/__init__.py +++ b/adr_viewer/__init__.py @@ -8,6 +8,39 @@ from bottle import Bottle, run +def extract_from_adr(page_object, find1, node1, node2, node3, txt): + context_section = page_object.find(find1, text=txt) + + if context_section and context_section.nextSibling: + current_node = context_section.nextSibling + + while current_node.name != find1 and current_node.nextSibling: + current_node = current_node.nextSibling + + if current_node.name == node1: + yield current_node.text + elif current_node.name == node2: + yield from (li.text for li in current_node.children if li.name == node3) + else: + continue + + +def extract_context_from_adr(page_object): + context_section = page_object.find('h2', text='Context') + + if context_section and context_section.nextSibling: + current_node = context_section.nextSibling + + while current_node.name != 'h2' and current_node.nextSibling: + current_node = current_node.nextSibling + + if current_node.name == 'p': + yield current_node.text + elif current_node.name == 'ul': + yield from (li.text for li in current_node.children if li.name == "li") + else: + continue + def extract_date_from_adr(page_object): date_section = page_object.find('h1') @@ -24,6 +57,7 @@ def extract_date_from_adr(page_object): else: continue + def extract_statuses_from_adr(page_object): status_section = page_object.find('h2', text='Status') @@ -48,7 +82,9 @@ def parse_adr_to_config(path): status = list(extract_statuses_from_adr(soup)) thedate = list(extract_date_from_adr(soup))[0].replace('Date: ', '') - + context = list(extract_context_from_adr(soup)) + decision = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', 'Decision')) + if any([line.startswith("Amended by") for line in status]): status = 'amended' elif any([line.startswith("Accepted") for line in status]): @@ -67,7 +103,9 @@ def parse_adr_to_config(path): 'status': status, 'date': thedate, 'body': adr_as_html, - 'title': header.text + 'title': header.text, + 'context': context, + 'decision': decision } else: return None diff --git a/adr_viewer/templates/.DS_Store b/adr_viewer/templates/.DS_Store index ead02402fcfc9202364da30e42f60bfd7b3999a9..68b925812aec6189dc905f6f76b2f28d2380241e 100644 GIT binary patch delta 612 zcmZoMXfc=|#>B!ku~2NHo}#D_0|Nsi1A_nqLkUB%XHI@{Qcix-=0@h_%=I8CHikro zVjxUKmIjIg)ie44>Gl7?0LVg9n^az005S?Feln>bC$qT3z~DL~6Eh1d8#@OF7Y7$F zS8Q-betB?7Vo7PSQ({pxh!>Dpl92>u*N5ci=fF52iAiCZspatkBF_1FC5f4NsYPJj znJKA2B{AWdc`5njPWh#IDaBxef*}$d9Go1S@d6Un)h5PfItr$SrnNc>)s{vEItoC6 z+FDKyaaBWG&xG8{s_L5Bx|v{qFfalGgn^+Ri1{G`aB>!qg%Tt=>4w3{`MCvf6=?Fg z`7SQdVBm0)cVy2AKjMgPMs9uzHZ_(g78PXRx2P<*C@&{JFCAz&<7OTvPDW==V6>Gm zR5IiMBTtVZ85E7M_ysBgMW8H@)hFqZf93Y*?kUIGrlRQ5=Lm4m;>T_Yz%&F*C*PST^NTog07HxkXd1)j2$40+0AE0s7XSbN delta 113 zcmZoMXfc=|#>B)qu~2NHo}wrx0|Nsi1A_pAXHI@{QcivnkT0;Ya5*C*=jMZ~oQ#6J z3?&Si47m)c3^_nLk0Et(E}P_JFZR8g6*$;gCN{8aX6NAN02;VikmEb^WPTAx4xkYr MGg&rAh^%1-0Gjg{egFUf diff --git a/adr_viewer/templates/timeline/index.html b/adr_viewer/templates/timeline/index.html index f282b58..9fd9962 100644 --- a/adr_viewer/templates/timeline/index.html +++ b/adr_viewer/templates/timeline/index.html @@ -73,8 +73,8 @@ } .content { - max-height: 20px; - padding: 10px 20px 0; + max-height: 0px; + padding: 10px 0px 0; border-color: transparent; border-width: 2px; border-style: solid; @@ -102,7 +102,7 @@ border-right-color: #48b379; border-width: 17px; top: 50%; - margin-top: -17px; + margin-top: 0px; } .content p { max-height: 0; @@ -111,7 +111,7 @@ word-break: break-word; hyphens: none; overflow: hidden; - padding: 10px 0px 0px 0; + padding: 0px 0px 0px 0; } label { @@ -119,7 +119,7 @@ position: absolute; z-index: 100; cursor: pointer; - top: 0px; + top: -10px; transition: transform 0.2s linear; position: -webkit-sticky; position: sticky; @@ -236,9 +236,16 @@

{{ config.heading }}

-

- {{ adr_record.body|safe }} -

+

Context

+ {% for context_line in adr_record.context %} + {{ context_line|safe }} +
+ {% endfor %} +

Decision

+ {% for decision_line in adr_record.decision %} + {{ decision_line|safe}} +
+ {% endfor %}
{% endfor %} From 0a8855f9b415214e22342772dc7f6799157b884f Mon Sep 17 00:00:00 2001 From: alshapton Date: Thu, 25 Aug 2022 09:34:23 +0100 Subject: [PATCH 04/11] Rationalised some codee --- adr_viewer/__init__.py | 101 +++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 65 deletions(-) diff --git a/adr_viewer/__init__.py b/adr_viewer/__init__.py index 02f82f1..a68e388 100644 --- a/adr_viewer/__init__.py +++ b/adr_viewer/__init__.py @@ -9,10 +9,10 @@ def extract_from_adr(page_object, find1, node1, node2, node3, txt): - context_section = page_object.find(find1, text=txt) + section = page_object.find(find1, text=txt) - if context_section and context_section.nextSibling: - current_node = context_section.nextSibling + if section and section.nextSibling: + current_node = section.nextSibling while current_node.name != find1 and current_node.nextSibling: current_node = current_node.nextSibling @@ -20,57 +20,8 @@ def extract_from_adr(page_object, find1, node1, node2, node3, txt): if current_node.name == node1: yield current_node.text elif current_node.name == node2: - yield from (li.text for li in current_node.children if li.name == node3) - else: - continue - - -def extract_context_from_adr(page_object): - context_section = page_object.find('h2', text='Context') - - if context_section and context_section.nextSibling: - current_node = context_section.nextSibling - - while current_node.name != 'h2' and current_node.nextSibling: - current_node = current_node.nextSibling - - if current_node.name == 'p': - yield current_node.text - elif current_node.name == 'ul': - yield from (li.text for li in current_node.children if li.name == "li") - else: - continue - -def extract_date_from_adr(page_object): - date_section = page_object.find('h1') - - if date_section and date_section.nextSibling: - current_node = date_section.nextSibling - - while current_node.name != 'h1' and current_node.nextSibling: - current_node = current_node.nextSibling - - if current_node.name == 'p': - yield current_node.text - elif current_node.name == 'ul': - yield from (li.text for li in current_node.children if li.name == "li") - else: - continue - - -def extract_statuses_from_adr(page_object): - status_section = page_object.find('h2', text='Status') - - if status_section and status_section.nextSibling: - current_node = status_section.nextSibling - - while current_node.name != 'h2' and current_node.nextSibling: - current_node = current_node.nextSibling - - if current_node.name == 'p': - yield current_node.text - elif current_node.name == 'ul': - yield from (li.text for li in current_node.children if li.name == "li") + yield from (li.text for li in + current_node.children if li.name == node3) else: continue @@ -80,11 +31,12 @@ def parse_adr_to_config(path): soup = BeautifulSoup(adr_as_html, features='html.parser') - status = list(extract_statuses_from_adr(soup)) - thedate = list(extract_date_from_adr(soup))[0].replace('Date: ', '') - context = list(extract_context_from_adr(soup)) + status = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', 'Status')) + thedate = list(extract_from_adr(soup, 'h1', 'p', 'ul', 'li', '' + ))[0].replace('Date: ', '') + context = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', 'Context')) decision = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', 'Decision')) - + if any([line.startswith("Amended by") for line in status]): status = 'amended' elif any([line.startswith("Accepted") for line in status]): @@ -161,12 +113,30 @@ def generate_content(path, template_dir_override=None, heading=None): @click.command() -@click.option('--adr-path', default='doc/adr/', help='Directory containing ADR files.', show_default=True) -@click.option('--output', default='index.html', help='File to write output to.', show_default=True) -@click.option('--serve', default=False, help='Serve content at http://localhost:8000/', is_flag=True) -@click.option('--port', default=8000, help='Change port for the server', show_default=True) -@click.option('--template-dir', default=None, help='Template directory.', show_default=True) -@click.option('--heading', default='ADR Viewer - ', help='ADR Page Heading', show_default=True) +@click.option('--adr-path', + default='doc/adr/', + help='Directory containing ADR files.', + show_default=True) +@click.option('--output', + default='index.html', + help='File to write output to.', + show_default=True) +@click.option('--serve', + default=False, + help='Serve content at http://localhost:8000/', + is_flag=True) +@click.option('--port', + default=8000, + help='Change port for the server', + show_default=True) +@click.option('--template-dir', + default=None, + help='Template directory.', + show_default=True) +@click.option('--heading', + default='ADR Viewer - ', + help='ADR Page Heading', + show_default=True) def main(adr_path, output, serve, port, template_dir, heading): content = generate_content(adr_path, template_dir, heading) @@ -176,5 +146,6 @@ def main(adr_path, output, serve, port, template_dir, heading): with open(output, 'w') as out: out.write(content) + if __name__ == '__main__': - main() \ No newline at end of file + main() From 634f3b4e4b0f425b572ec906ceb1cdd5e0b2cdb8 Mon Sep 17 00:00:00 2001 From: alshapton Date: Thu, 25 Aug 2022 13:46:07 +0100 Subject: [PATCH 05/11] Added Consequences to the ADR box --- adr_viewer/__init__.py | 4 +++- adr_viewer/templates/timeline/index.html | 13 +++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/adr_viewer/__init__.py b/adr_viewer/__init__.py index a68e388..712f752 100644 --- a/adr_viewer/__init__.py +++ b/adr_viewer/__init__.py @@ -36,6 +36,7 @@ def parse_adr_to_config(path): ))[0].replace('Date: ', '') context = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', 'Context')) decision = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', 'Decision')) + consequences = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', 'Consequences')) if any([line.startswith("Amended by") for line in status]): status = 'amended' @@ -57,7 +58,8 @@ def parse_adr_to_config(path): 'body': adr_as_html, 'title': header.text, 'context': context, - 'decision': decision + 'decision': decision, + 'consequences': consequences } else: return None diff --git a/adr_viewer/templates/timeline/index.html b/adr_viewer/templates/timeline/index.html index 9fd9962..c6ec388 100644 --- a/adr_viewer/templates/timeline/index.html +++ b/adr_viewer/templates/timeline/index.html @@ -237,13 +237,18 @@

{{ config.heading }}

Context

- {% for context_line in adr_record.context %} - {{ context_line|safe }} + {% for aline in adr_record.context %} + {{ aline|safe }}
{% endfor %}

Decision

- {% for decision_line in adr_record.decision %} - {{ decision_line|safe}} + {% for aline in adr_record.decision %} + {{ aline|safe}} +
+ {% endfor %} +

Consequences

+ {% for aline in adr_record.consequences %} + {{ aline|safe}}
{% endfor %}
From ddcd778b2f120a69b923c3e5aa97288c49786156 Mon Sep 17 00:00:00 2001 From: alshapton Date: Thu, 25 Aug 2022 22:40:22 +0100 Subject: [PATCH 06/11] Added all individual elements of an ADR separately to the template layout for better clarity --- adr_viewer/__init__.py | 50 ++++++++++++++++- adr_viewer/templates/timeline/index.html | 71 +++++++++++++++++++++--- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/adr_viewer/__init__.py b/adr_viewer/__init__.py index 712f752..d99aa1f 100644 --- a/adr_viewer/__init__.py +++ b/adr_viewer/__init__.py @@ -36,7 +36,46 @@ def parse_adr_to_config(path): ))[0].replace('Date: ', '') context = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', 'Context')) decision = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', 'Decision')) - consequences = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', 'Consequences')) + consequences = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', + 'Consequences')) + references = list(extract_from_adr(soup, 'h2', 'p', 'ul', 'li', + 'References')) + + amended = [] + amends = [] + superceded = [] + supercedes = [] + drivenby = [] + drives = [] + + ' Extract additional status supporting information' + for line in status: + if line.startswith("Superceded") or line.startswith("Superseded"): + for supercededlink in line.split('\n'): + ln = supercededlink.replace("Superseded by ", "" + ).replace("Superceded by ", "") + superceded.append(ln) + if line.startswith("Supercedes") or line.startswith("Supercedes"): + for supercedeslink in line.split('\n'): + ln = supercedeslink.replace("Supersedes ", "" + ).replace("Supercedes ", "") + supercedes.append(ln) + if line.startswith("Amended By"): + for amendedlink in line.split('\n'): + ln = amendedlink.replace("Amended by ", "") + amended.append(ln) + if line.startswith("Amends"): + for amendslink in line.split('\n'): + ln = amendslink.replace("Amends ", "") + amends.append(ln) + if line.startswith("Driven By"): + for drivenbylink in line.split('\n'): + ln = drivenbylink.replace("Driven by ", "") + drivenby.append(ln) + if line.startswith("Drives"): + for driveslink in line.split('\n'): + ln = driveslink.replace("Drives ", "") + drives.append(ln) if any([line.startswith("Amended by") for line in status]): status = 'amended' @@ -59,7 +98,14 @@ def parse_adr_to_config(path): 'title': header.text, 'context': context, 'decision': decision, - 'consequences': consequences + 'consequences': consequences, + 'references': references, + 'superceded': superceded, + 'supercedes': supercedes, + 'amended': amended, + 'amends': amends, + 'drivenby': drivenby, + 'drives': drives } else: return None diff --git a/adr_viewer/templates/timeline/index.html b/adr_viewer/templates/timeline/index.html index c6ec388..c9e28af 100644 --- a/adr_viewer/templates/timeline/index.html +++ b/adr_viewer/templates/timeline/index.html @@ -219,34 +219,87 @@

{{ config.heading }}

{% endif %}
-
-

Context

+ {% if adr_record.superceded|length > 0 %} +

Superceded by:

+ {% for aline in adr_record.superceded %} + {{ aline|safe }} +
+ {% endfor %} + {% endif %} + {% if adr_record.supercedes|length > 0 %} +

Supercedes:

+ {% for aline in adr_record.supercedes %} + {{ aline|safe }} +
+ {% endfor %} + {% endif %} + + {% if adr_record.amended|length > 0 %} +

Amended by:

+ {% for aline in adr_record.amended %} + {{ aline|safe }} +
+ {% endfor %} + {% endif %} + + {% if adr_record.amends|length > 0 %} +

Amends:

+ {% for aline in adr_record.amends %} + {{ aline|safe }} +
+ {% endfor %} + {% endif %} + + {% if adr_record.drivenby|length > 0 %} +

Driven by:

+ {% for aline in adr_record.drivenby %} + {{ aline|safe }} +
+ {% endfor %} + {% endif %} + + {% if adr_record.drives|length > 0 %} +

Drives:

+ {% for aline in adr_record.drives %} + {{ aline|safe }} +
+ {% endfor %} + {% endif %} + +

Context

{% for aline in adr_record.context %} {{ aline|safe }}
{% endfor %} -

Decision

+

Decision

{% for aline in adr_record.decision %} {{ aline|safe}}
{% endfor %} -

Consequences

+

Consequences

{% for aline in adr_record.consequences %} {{ aline|safe}}
From 97b9ddc67637638e1fc6b141988766b0ac6c58a3 Mon Sep 17 00:00:00 2001 From: Michael Schmidt Date: Wed, 18 Jan 2023 16:28:36 +0100 Subject: [PATCH 07/11] Add missing status Proposed adr-viewer implements the status "Pending", but the original blog article and adr-tools-python use the status "Proposed" for new ADRs. This patch adds "Proposed" as synonyme for "Pending". --- adr_viewer/__init__.py | 2 +- adr_viewer/test_adr_viewer.py | 6 +++++- test/adr/0004-proposed-status.md | 19 +++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 test/adr/0004-proposed-status.md diff --git a/adr_viewer/__init__.py b/adr_viewer/__init__.py index 8093691..5c6ab6c 100644 --- a/adr_viewer/__init__.py +++ b/adr_viewer/__init__.py @@ -38,7 +38,7 @@ def parse_adr_to_config(path): status = 'accepted' elif any([line.startswith("Superseded by") for line in status]): status = 'superseded' - elif any([line.startswith("Pending") for line in status]): + elif any([line.startswith("Proposed") or line.startswith("Pending") for line in status]): status = 'pending' else: status = 'unknown' diff --git a/adr_viewer/test_adr_viewer.py b/adr_viewer/test_adr_viewer.py index 8274dbf..36c6f23 100644 --- a/adr_viewer/test_adr_viewer.py +++ b/adr_viewer/test_adr_viewer.py @@ -42,6 +42,10 @@ def test_should_mark_pending_records(): assert config['status'] == 'pending' +def test_should_mark_pproposed_records(): + config = parse_adr_to_config('test/adr/0004-proposed-status.md') + + assert config['status'] == 'pending' def test_should_render_html_with_project_title(): html = render_html({ @@ -85,4 +89,4 @@ def test_should_render_html_with_collapsible_index(): def test_should_ignore_invalid_files(): config = parse_adr_to_config('test/adr/0003-bad-formatting.md') - assert config is None \ No newline at end of file + assert config is None diff --git a/test/adr/0004-proposed-status.md b/test/adr/0004-proposed-status.md new file mode 100644 index 0000000..16e2a2b --- /dev/null +++ b/test/adr/0004-proposed-status.md @@ -0,0 +1,19 @@ +# 1. Record architecture decisions + +Date: 2018-09-02 + +## Status + +Proposed + +## Context + +We need to record the architectural decisions made on this project. + +## Decision + +We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools). From 43b71d5ff2278b9e0f5f09604153b91d49b3ef1d Mon Sep 17 00:00:00 2001 From: alshapton Date: Mon, 6 Feb 2023 13:11:29 +0000 Subject: [PATCH 08/11] Clean-up --- adr_viewer/__init__.py | 2 +- adr_viewer/index.html | 793 ----------------------------------------- 2 files changed, 1 insertion(+), 794 deletions(-) delete mode 100644 adr_viewer/index.html diff --git a/adr_viewer/__init__.py b/adr_viewer/__init__.py index 324de58..f05a5b3 100644 --- a/adr_viewer/__init__.py +++ b/adr_viewer/__init__.py @@ -55,7 +55,7 @@ def parse_adr_to_config(path): ln = supercededlink.replace("Superseded by ", "" ).replace("Superceded by ", "") superceded.append(ln) - if line.startswith("Supercedes") or line.startswith("Supercedes"): + if line.startswith("Supersedes") or line.startswith("Supercedes"): for supercedeslink in line.split('\n'): ln = supercedeslink.replace("Supersedes ", "" ).replace("Supercedes ", "") diff --git a/adr_viewer/index.html b/adr_viewer/index.html deleted file mode 100644 index 64df078..0000000 --- a/adr_viewer/index.html +++ /dev/null @@ -1,793 +0,0 @@ - - - - - ADR Viewer - - - - - - - - -

ADR Viewer -

-
    - -
  • - - - - - -
    - - 2021-09-01 - -
    -
    -

    -

    1. Record architecture decisions

    -

    Date: 2021-09-01

    -

    Status

    -

    Accepted

    -

    Drives 2. Use Structurizr to document Architecture

    -

    Context

    -

    We need to record the architectural decisions made within Hodge

    -

    Decision

    -

    We will use Architecture Decision Records, as described by Michael Nygard.

    -

    Consequences

    -

    See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's adr-tools.

    - -

    -
    -
  • - -
  • - - - -
    - - 2022-08-01 - -
    -
    -

    -

    2. Use Structurizr to document Architecture

    -

    Date: 2022-08-01

    -

    Status

    -

    Accepted

    -

    Driven by 1. Record architecture decisions

    -

    Context

    -

    A tool should be used to document the architecture. A diagramming tool which is typically used to initially document architectures. -Once the architecture is documented in a diagramming tool (for design purposes) it should be moved to a modelling tool which will allow rendering, analysis, and formatting into a multitude of different forms all from the same sourece of truth.

    -

    Decision

    -

    The decision to use Structurizr was made after looking at various other architectural modelling tools.

    -

    Broadly speaking, Structurizr is:

    -
      -
    • Good Value for Money
    • -
    • Easy to use
    • -
    • Aligns with Hodge's "-as-code" philosophy
    • -
    • Allows the models to be stored in a normal GIT repository and version controlled
    • -
    • Will (in time) allow other tools' output to be added to the model automatically
    • -
    -

    Consequences

    -

    Positive outcomes include:

    -
      -
    • Visibility of architecture
    • -
    • Ability to render in different formats
    • -
    • Ability to show stages of emerging architectures
    • -
    • Ability to record additional documentation
    • -
    • Ability to record Architectural Decisions
    • -
    - -

    -
    -
  • - -
  • - - - -
    - - 2021-11-17 - -
    -
    -

    -

    3. Mambu IDs

    -

    Date: 2021-11-17

    -

    Status

    -

    Superceded by 4. Mambu-IDs

    -

    Context

    -

    Mambu does not allow custom fields within products. Therefore, products cannot be filtered on attributes of the product such as length, type etc.

    -

    Persistent Technologies suggested that a structured product ID be introduced, containing component parts relating to attributes of the product to effect sorting and filtering. For example an ID could be:

    -
    -

    B_3Y_M_L_20211001

    -
    -

    Where each part of the name separated by an underscore indicates an encoded pieces of information related to the product. In this example, a 3 year bond, paying monthly interest, for loyal customers only, starting on 01-Oct-2021. An issue with this approach will be future proofing, how do we manage new product flags / settings going forward? Old IDs could become incompatible.

    -

    In addition to product information, there will be digital assets (stored in S3) which will need to be retrieved and rendered within the application. It is proposed that a JSON file be used to link the product information in Mambu with the related digital content in S3 - this would be located within Hodge’s S3 bucket itself.

    -

    Whilst this would no doubt solve the issue, the below illustration shows how the Mambu ID issue can be solved in an innovative manner and still retain control of the digital assets in S3 and Mambu product information from a single location.

    -

    Mambu ID diagram

    -

    Decision

    -

    A product meta data table will be created which will have a primary key of the Mambu product, and provide the extra fields which Mambu is unable to store, for example start date & end date. A web hook will be setup to populate the other product data into this structure as well (term length, interest type etc). Note that the Mambu ID format will be as described above (see caveat below)

    -

    Since the data flow to populate the meta data table is one-way i.e. from Mambu to the meta-data table held within the integration layer, it is a simple task to set up a web hook from Mambu to populate the metadata table upon a change/insert of a product in Mambu.

    -

    For information that cannot be mastered in Mambu, functionality will need to be developed to maintain that information in the meta data table.

    -

    Consequences

    -

    Mambu ID’s are allowed to have a pattern to facilitate speedy human elicitation of product details from records, logs etc. However, this decision is made on the understanding that the Mambu ID will never be used for any other purpose other than linking data sources. That it, any business information that the ID purports to carry will also be carried as a data attribute in its’ own right and filtering, searching, sorting etc will be carried out on that data item instead.

    -

    References

    -

    Original Decision Work

    - -

    -
    -
  • - -
  • - - - -
    - - 2022-08-02 - -
    -
    -

    -

    4. Mambu-IDs

    -

    Date: 2022-08-02

    -

    Status

    -

    Accepted

    -

    Supercedes 3. Mambu IDs

    -

    Context

    -

    Persistent Technologies suggested that a structured product ID be introduced, containing component parts relating to attributes of the product to effect sorting and filtering. For example an ID could be:

    -
    -

    B_3Y_M_L_20211001

    -
    -

    Decision

    -

    The product IDs as described above will be adopted in Mambu (Structure to be provided)

    -

    Consequences

    -

    Mambu ID’s are allowed to have a pattern to facilitate speedy human elicitation of product details from records, logs etc. However, this decision is made on the understanding that the Mambu ID will never be used for any other purpose other than linking data sources. That it, any business information that the ID purports to carry will also be carried as a data attribute in its’ own right and filtering, searching, sorting etc will be carried out on that data item instead.

    - -

    -
    -
  • - -
  • - - - -
    - - 2021-09-28 - -
    -
    -

    -

    5. Java Version

    -

    Date: 2021-09-28

    -

    Status

    -

    Accepted

    -

    Context

    -

    The strategic platform for Hodge’s development is a Java Virtual Machine (JVM). Java has been upgraded over the years, adding new features, fixing bugs and performance improvements etc. in the whole Java ecosystem, tooling and the JVM in itself. -The current set of “systems” within Hodge use a mixture of Java8 and Java11. -The latest LTS version of Java is Java17, released in September 2021 will be supported until at least 2029. The next LTS version of Java (Java21) is due for release in September 2023. -The full lifecycle and support history of the Java platform can be found below.

    -

    Decision

    -

    As the version history shows (below) both Java8 and Java11 are still in support, however, these older platforms lack the level of functionality and performance of Java17. Coupled with the reasons above, it is recommended that a move to Java17 be carried out, and all new development work should be carried out on Java17

    -

    Consequences

    -

    Newer versions of tooling will target newer versions of Java - should tooling and ecosystem products Hodge uses cease to support older versions of Java, then alternatives need to be found.Those alternatives may not exist. -Support for the underlying JVM and JDKs are required in the event of any bugs etc. Using older versions of Java may result in support or fixes not being available.

    -

    Older versions may not have appropriate security patches applied, and therefore present an additional attack surface i.e. a vulnerability.

    -

    References

    -

    Java Support

    -

    Original Decision Work

    - -

    -
    -
  • - -
  • - - - -
    - - 2021-09-29 - -
    -
    -

    -

    6. Templating Engine

    -

    Date: 2021-09-29

    -

    Status

    -

    Accepted

    -

    Context

    -

    We need to decide on which templating engine we will use going forward in the DSP project and in the wider Hodge systems. This is specifically in relation to customer communications such as emails and letters.

    -

    Background

    -

    During the course of a customer journey with Hodge, a number of communications in the form of letters / emails will be sent to them. These communications need to be populated with variable information (such as title, first name, surname) and then converted into a type which is suitable to send or print (i.e. HTML).

    -

    A couple of options were considered Thymeleaf and What is Apache FreeMarker™?

    -

    Both of these operate in a very similar way, an example of both has been given below. As we already have around 30 templates written in FTL (freemarker template) and also numerous on the Holmes project, its probably sensible we just stick with this approach going forward.

    -

    Another point to consider would be if we chose Thymeleaf then we are consolidating our templating libraries into one, for example we will use Thymeleaf for the view part of our MVC web app, and also for communications. However, as Thymeleaf is being phased out in favour of ReactJS, there isn’t much need to consider this point.

    -
    Thymeleaf:
    -
    <!DOCTYPE html>
    -<html xmlns:th="http://www.thymeleaf.org">
    -    <head>
    -        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    -    </head>
    -    <body>
    -        <p th:text="#{greetings(${recipientName})}"></p>
    -        <p th:text="${text}"></p>
    -        <p th:text="#{regards}"></p>
    -        <p>
    -        <em th:text="#{signature(${senderName})}"></em> <br />
    -        </p>    
    -    </body>
    -</html>
    -
    -
    -
    Freemarker:
    -
    <!DOCTYPE html>
    -<html>
    -    <head>
    -    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    -    </head>
    -    <body>
    -        <p>Hi ${recipientName}</p>
    -        <p>${text}</p>
    -        <p>Regards,</p>
    -        <p>
    -        <em>${senderName} at Hodge</em> <br />
    -        </p>
    -    </body>
    -</html>
    -
    -
    -

    Decision

    -

    The library to use going forward should remain as Freemarker, this aligns with the current templates created on DOL and Holmes & Persistent are happy to use it.

    -

    Consequences

    -

    Using a standard, modern templating engine means that:

    -
      -
    • the same engine across Hodge will be used
    • -
    • wider use of a common skill
    • -
    • more flexible use of resources
    • -
    -

    Original Decision Work

    - -

    -
    -
  • - -
  • - - - -
    - - 2021-11-29 - -
    -
    -

    -

    7. Package Naming

    -

    Date: 2021-11-29

    -

    Status

    -

    Accepted

    -

    Context

    -

    We need to decide on naming standards for the code within Hodge services

    -

    Background

    -

    The current naming standards seen on all DOL / Holmes microservices are as follows:

    -
    hodge.co.uk.*
    -
    -
    -

    This is actually the reverse of what is considered normal practice, which should be company domain name reversed, for example:

    -
    org.springframework.*
    -
    -
    -

    Decision

    -

    For any new services going forward we will align to standard practice, starting with the new microservices being created for the DSP project, for example:

    -
    uk.co.hodge.account.*
    -uk.co.hodge.notification.*
    -uk.co.hodge.product.*
    -
    -
    -

    Consequences

    -

    What becomes easier or more difficult to do and any risks introduced by the change that will need to be mitigated.

    -

    Original Decision Work

    - -

    -
    -
  • - -
  • - - - -
    - - 2021-12-02 - -
    -
    -

    -

    8. Static Code Analysis

    -

    Date: 2021-12-02

    -

    Status

    -

    Accepted

    -

    Context

    -

    Static Code analysis is a technique for analysing source code with a view to catching bugs, vulnerabilities and potential damaging issues caused by not using best practice.

    -

    Currently, Hodge use SonarQube for this purpose. -Other tools are available, and a sweep of the marketplace has narrowed this down to two contenders:

    -
      -
    • SonarCloud
    • -
    • Github Actions
    • -
    -

    Decision

    -

    After analysing the two tools that remained in the contender list, *SonarCloud* has been selected to move forward because of the following reasons: -SonarCloud provides several mechanisms for invocation e.g. from the IDE, pre-commit and during the pipeline, whereas Github Actions' dependency checker is only on commit or further down in the pipeline.

    -

    SonarQube is already known to Hodge, and can be simply integrated into the SDLC, as will the cloud-service, SonarCloud.

    -

    Consequences

    -

    Developers will be able to see the results of the SonarCloud analysis early on in the SDLC - enabling deficiencies to be fixed earlier.

    -

    References

    -

    Original Decision Work

    - -

    -
    -
  • - -
  • - - - -
    - - 2021-11-30 - -
    -
    -

    -

    9. React/JSX Coding Standards

    -

    Date: 2021-11-30

    -

    Status

    -

    Accepted

    -

    Context

    -

    In order to promote good consistent and good React coding practices across Hodge, we need to have a defined style and standards guide

    -

    Decision

    -

    JavaScript is weakly typed and can be formatted in a number of ways, for example semi colons are not mandatory on the end of lines and code and there is no whitespace formatting to speak of. Its therefore very important that we adhere to standards, not just to make sure code is clean and issue free, but also to aid future re-use and maintenance.

    -

    An important superset of JavaScript is TypeScript, developed by Microsoft, which has enabled static typing to be introduced. It is important that TypeScript is written in Hodge and not regular JavaScript which is inherently unsafe in comparison. It is also important that a style guide is followed, such as the Airbnb React/JSX guide. This will ensure certain standards are followed, in relation to naming, class structure, whitespace alignment, class structure and method ordering.

    -

    Hodge already have a confluence page which defines React linting, testing and style standards to be followed: Testing, coding standards and linting

    -

    Consequences

    -

    As Hodge has previously used the Airbnb React/JSX stye guide, we believe this is the best solution going forward.

    -

    References

    -

    Original Decision Work

    - -

    -
    -
  • - -
  • - - - -
    - - 2021-12-02 - -
    -
    -

    -

    10. Timezones

    -

    Date: 2021-12-02

    -

    Status

    -

    Accepted

    -

    Context

    -

    To achieve standardisation and consistency across Hodge’s estate, all times stored will be a true reflection of the real world, or a documented deviance from that (e.g. UTC)

    -

    Systems typically have a timezone which can be set at the compute platform level, such that any datestamp-type operation will source its' timestamp from there. Additionally, NTP servers are available (Network Time Protocol) which systems may synchronise with remote atomic clocks to ensure correctness of time. -Cloud-based services and providers (such as AWS and Microsoft) typically have their own NTP servers which their infrastructure will synchronise with. ANY timezone can be selected, not just the local one.

    -

    Decision

    -
      -
    • All dates and times within the scope of the DSP project will be standardised on UK time. That is: Europe/London UTC+0 (however, when BST occurs, we require the changeover to occur automatically to Europe/London - UTC+1 ), such that at ALL times, irrespective of the time of year, dates + times stored are identical to the time at that moment in the real world.

      -
    • -
    • All other systems will have their Timezones documented here (or in a successor to this ADR)

      -
    • -
    -

    Consequences

    -

    All systems can have a view of the time that actions were recorded (logs, transactions etc) which is in the same unit and metric.

    - -

    -
    -
  • - -
  • - - - -
    - - 2021-09-30 - -
    -
    -

    -

    11. http Verbs

    -

    Date: 2021-09-30

    -

    Status

    -

    Accepted

    -

    Context

    -

    There are some 39 HTTP verbs in the HTTP1.1 specification (Hypertext Transfer Protocol). -However, only 9 of those are in general use; those in bold indicate those which are almost always used.

    -
      -
    • GET
    • -
    • HEAD
    • -
    • POST
    • -
    • PUT
    • -
    • DELETE
    • -
    • PATCH
    • -
    • CONNECT
    • -
    • OPTIONS
    • -
    • TRACE
    • -
    -

    Definitions:

    -

    | Term | Meaning | -|------|---------| -| SAFE |In the context of REST APIs, a verb is considered non-SAFE if the API call (with its associated verb) results in a change to the state of a resource.| -| IDEMPOTENT | In the context of REST APIs, when making multiple identical requests has the same effect as making a single request – then that REST API is deemed to be idempotent. Idempotence essentially means that the result of a successfully performed request is independent of the number of times it is executed. |

    -

    The verbs are classified as follows:

    -
      -
    • GET is both safe and idempotent
    • -
    • PUT,DELETE are not safe but idempotent
    • -
    • POST,PATCH are neither safe nor idempotent
    • -
    -

    Being non-SAFE is a quality of a verb which is sometimes desirable and necessary, for example, a POST needs to be non-SAFE to be able to achieve the creation of resources.

    -

    GET -GET is a simple HTTP verb which requests a resource and returns it (if it exists) or an error if not

    -
    GET http://www.hodge.co.uk/customer/73645812/details
    -
    -

    POST -POST requires all the mandatory data items of a new resource, and creates that resource if it doesn't already exist. -Note that POST is neither safe nor idempotent.

    -
    POST /api/2.2/customer/new HTTP/1.1
    -HOST: hodge.co.uk
    -Content-Type:application/json
    -Accept:application/json
    -{
    -  "customer": {
    -    "name": "Tom Baker",
    -    "occupation": "Time Lord",
    -    "details": {
    -      "dob": "N/A",
    -      "place": "Kasterberus",
    -      "age-on-opening": 968,
    -      "initial-balance": "§671"
    -    }
    -  }
    -}
    -
    -

    PUT -PUT is most often used as an update to an already existing resource -PUT is not a safe operation, in that it modifies (or creates) state on the server, but it is idempotent.

    -
    PUT /api/2.2/customer/73645812 HTTP/1.1
    -HOST: hodge.co.uk
    -Content-Type: application/json
    -{
    -  "customer": {
    -    "name": "Peter Davison"
    -  }
    -}
    -
    -

    PATCH -PATCH is most often used as an update to an already existing resource -PATCH is not a safe operation, in that it modifies (or creates) state on the server, nor is it idempotent.

    -
    PATCH /api/2.2/customer/73645812 HTTP/1.1
    -HOST: hodge.co.uk
    -Content-Type: application/json
    -{
    -  "customer": {
    -    "name": "Colin Baker"
    -  }
    -}
    -
    -

    DELETE -Clearly, DELETE is used to delete a resource - at Hodge, we should be soft-deleting only.

    -
    DELETE /api/2.2/customer/73645812 HTTP/1.1
    -HOST: hodge.co.uk
    -
    -

    DELETE is both safe and idempotent - since the first time a DELETE is used, it will delete the resource, subsequent calls will return a 404 Not Found error - which is an identical end result i.e. the resource no longer exists.

    -

    Decision

    -

    The recommendation is that the following verbs be used when creating Hodge APIs: -| Verb | Rationale | -|------|-----------| -| GET | Safe, idempotent verb with no side-effects -| POST |non-Safe (but by necessity), non-idempotent (by necessity) - however, the services should do checking to ensure multiple customers (for example) are not created with the same details -| DELETE |Both safe and idempotent - semantically correct for removing resources -| PUT |PUT is non-Safe (by necessity) but it does have idempotence

    -

    The recommendation is that the following verb NOT be used when creating Hodge APIs:

    -

    | Verb | Rationale | -|------|-----------| -| PATCH |Non-safe and non-idempotent

    -

    Consequences

    -

    Using a single rationale, and therefore a single set of "use-only" verbs will allow Hodge's APIs to become simpler, and more predictable in their behaviour, and more supportable.

    - -

    -
    -
  • - -
  • - - - -
    - - 2022-08-08 - -
    -
    -

    -

    12. Document Storage

    -

    Date: 2022-08-08

    -

    Status

    -

    Amended

    -

    Context

    -

    A storage solution is required which will allow documents to be securely stored, in a managed environment, cloud-based, with accessibility controls, with documents visible to all of Hodge's systems

    -

    Decision

    -

    Option #3 in the Options Paper - a combination of S3 and Azure Storage.

    -

    Consequences

    -

    A single store made up of two logical stores allows the native technologies that Hodge has currently to best use and access document stores - Microsoft Technologies will use Azure Storage and AWS-based applications will use S3.

    -

    Original Options

    - -

    -
    -
  • - -
- - - - \ No newline at end of file From eef5fcc20a9c5e856ceb7aac23bd51da882ab51a Mon Sep 17 00:00:00 2001 From: alshapton Date: Mon, 6 Feb 2023 13:21:47 +0000 Subject: [PATCH 09/11] updated test_adr_viewer.py --- adr_viewer/test_adr_viewer.py | 103 +++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 34 deletions(-) diff --git a/adr_viewer/test_adr_viewer.py b/adr_viewer/test_adr_viewer.py index 36c6f23..40e3281 100644 --- a/adr_viewer/test_adr_viewer.py +++ b/adr_viewer/test_adr_viewer.py @@ -1,92 +1,127 @@ -from adr_viewer import parse_adr_to_config, render_html +# Import system modules +import os +import sys +from ast import literal_eval +import pytest +sys.path.insert(1, '..' + os.sep + 'adr_viewer') -def test_should_extract_title_from_record(): - config = parse_adr_to_config('doc/adr/0001-record-architecture-decisions.md') +from adrviewer import parse_adr_to_config, render_html # noqa + + +@pytest.fixture +def adr0001(): + return '../doc/adr/0001-record-architecture-decisions.md' + + +@pytest.fixture +def html_defaults(): + defaults = literal_eval("{'page': {'background-color': 'blue'}," + + "'accepted': {'icon': 'fa-check', " + + "'background-color': 'lightgreen'}," + + "'amended': { 'icon': 'fa-arrow-down'," + + "'background-color': 'yellow'}," + + "'pending': { 'icon': 'fa-hourglass-half'," + + "'background-color': 'lightblue'}," + + "'superseded': {'icon': 'fa-times'," + + "'background-color': 'lightgrey'," + + "'text-decoration': 'line-through'}," + + "'unknown': {'icon': 'fa-question'," + + "'background-color': 'white'}}") + return defaults + + +def test_should_extract_title_from_record(adr0001): + config = parse_adr_to_config(adr0001) assert config['title'] == '1. Record architecture decisions' -def test_should_extract_status_from_record(): - config = parse_adr_to_config('doc/adr/0001-record-architecture-decisions.md') +def test_should_extract_status_from_record(adr0001): + config = parse_adr_to_config(adr0001) assert config['status'] == 'accepted' -def test_should_include_adr_as_html(): - config = parse_adr_to_config('doc/adr/0001-record-architecture-decisions.md') +def test_should_include_adr_as_html(adr0001): + config = parse_adr_to_config(adr0001) assert '

1. Record architecture decisions

' in config['body'] def test_should_mark_superseded_records(): - config = parse_adr_to_config('doc/adr/0003-use-same-colour-for-all-headers.md') + config = parse_adr_to_config( + '../doc/adr/0003-use-same-colour-for-all-headers.md') assert config['status'] == 'superseded' def test_should_mark_amended_records(): - config = parse_adr_to_config('doc/adr/0004-distinguish-superseded-records-with-colour.md') + adr0004 = '../doc/adr/0004-distinguish-superseded-records-with-colour.md' + config = parse_adr_to_config(adr0004) assert config['status'] == 'amended' def test_should_mark_unknown_records(): - config = parse_adr_to_config('test/adr/0001-unknown-status.md') + config = parse_adr_to_config('../test/adr/0001-unknown-status.md') assert config['status'] == 'unknown' def test_should_mark_pending_records(): - config = parse_adr_to_config('test/adr/0002-pending-status.md') + config = parse_adr_to_config('../test/adr/0002-pending-status.md') assert config['status'] == 'pending' -def test_should_mark_pproposed_records(): - config = parse_adr_to_config('test/adr/0004-proposed-status.md') - - assert config['status'] == 'pending' -def test_should_render_html_with_project_title(): - html = render_html({ - 'project_title': 'my-project' - }) +def test_should_render_html_with_project_title(html_defaults): + content = { + 'heading': 'my-project' + } + content.update(html_defaults) + html = render_html(content) + assert 'my-project' in html - assert 'ADR Viewer - my-project' in html - -def test_should_render_html_with_record_status(): - html = render_html({ +def test_should_render_html_with_record_status(html_defaults): + content = { 'records': [{ 'status': 'accepted', }] - }) + } + content.update(html_defaults) + html = render_html(content) assert '
' in html -def test_should_render_html_with_record_body(): - html = render_html({ +def test_should_render_html_with_record_body(html_defaults): + content = { 'records': [{ 'body': '

This is my ADR

', }] - }) + } + content.update(html_defaults) + html = render_html(content) assert '

This is my ADR

' in html -def test_should_render_html_with_collapsible_index(): - html = render_html({ +def test_should_render_html_with_collapsible_index(html_defaults): + content = { 'records': [{ 'title': 'Record 123', 'index': 123 }] - }) - - assert 'Record 123' in html + } + content.update(html_defaults) + html = render_html(content) + result = 'Record 123' + assert result in html def test_should_ignore_invalid_files(): - config = parse_adr_to_config('test/adr/0003-bad-formatting.md') + config = parse_adr_to_config('../test/adr/0003-bad-formatting.md') - assert config is None + assert config is None \ No newline at end of file From 6b9874564b22206e6086e945af52e4f8eb8bf8c8 Mon Sep 17 00:00:00 2001 From: alshapton Date: Mon, 6 Feb 2023 13:23:25 +0000 Subject: [PATCH 10/11] updated test_adr_viewer.py - added blank line --- adr_viewer/test_adr_viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adr_viewer/test_adr_viewer.py b/adr_viewer/test_adr_viewer.py index 40e3281..587acf9 100644 --- a/adr_viewer/test_adr_viewer.py +++ b/adr_viewer/test_adr_viewer.py @@ -124,4 +124,4 @@ def test_should_render_html_with_collapsible_index(html_defaults): def test_should_ignore_invalid_files(): config = parse_adr_to_config('../test/adr/0003-bad-formatting.md') - assert config is None \ No newline at end of file + assert config is None From 93eec9962d75f665746ba03cff12aa96bbc49461 Mon Sep 17 00:00:00 2001 From: alshapton Date: Mon, 6 Feb 2023 13:27:08 +0000 Subject: [PATCH 11/11] Final? clean-up --- adr_viewer/test_adr_viewer.py | 127 ---------------------------------- 1 file changed, 127 deletions(-) delete mode 100644 adr_viewer/test_adr_viewer.py diff --git a/adr_viewer/test_adr_viewer.py b/adr_viewer/test_adr_viewer.py deleted file mode 100644 index 587acf9..0000000 --- a/adr_viewer/test_adr_viewer.py +++ /dev/null @@ -1,127 +0,0 @@ -# Import system modules -import os -import sys -from ast import literal_eval - -import pytest -sys.path.insert(1, '..' + os.sep + 'adr_viewer') - -from adrviewer import parse_adr_to_config, render_html # noqa - - -@pytest.fixture -def adr0001(): - return '../doc/adr/0001-record-architecture-decisions.md' - - -@pytest.fixture -def html_defaults(): - defaults = literal_eval("{'page': {'background-color': 'blue'}," + - "'accepted': {'icon': 'fa-check', " + - "'background-color': 'lightgreen'}," + - "'amended': { 'icon': 'fa-arrow-down'," + - "'background-color': 'yellow'}," + - "'pending': { 'icon': 'fa-hourglass-half'," + - "'background-color': 'lightblue'}," + - "'superseded': {'icon': 'fa-times'," + - "'background-color': 'lightgrey'," + - "'text-decoration': 'line-through'}," + - "'unknown': {'icon': 'fa-question'," + - "'background-color': 'white'}}") - return defaults - - -def test_should_extract_title_from_record(adr0001): - config = parse_adr_to_config(adr0001) - - assert config['title'] == '1. Record architecture decisions' - - -def test_should_extract_status_from_record(adr0001): - config = parse_adr_to_config(adr0001) - - assert config['status'] == 'accepted' - - -def test_should_include_adr_as_html(adr0001): - config = parse_adr_to_config(adr0001) - - assert '

1. Record architecture decisions

' in config['body'] - - -def test_should_mark_superseded_records(): - config = parse_adr_to_config( - '../doc/adr/0003-use-same-colour-for-all-headers.md') - - assert config['status'] == 'superseded' - - -def test_should_mark_amended_records(): - adr0004 = '../doc/adr/0004-distinguish-superseded-records-with-colour.md' - config = parse_adr_to_config(adr0004) - - assert config['status'] == 'amended' - - -def test_should_mark_unknown_records(): - config = parse_adr_to_config('../test/adr/0001-unknown-status.md') - - assert config['status'] == 'unknown' - - -def test_should_mark_pending_records(): - config = parse_adr_to_config('../test/adr/0002-pending-status.md') - - assert config['status'] == 'pending' - - -def test_should_render_html_with_project_title(html_defaults): - content = { - 'heading': 'my-project' - } - content.update(html_defaults) - html = render_html(content) - assert 'my-project' in html - - -def test_should_render_html_with_record_status(html_defaults): - content = { - 'records': [{ - 'status': 'accepted', - }] - } - content.update(html_defaults) - html = render_html(content) - - assert '
' in html - - -def test_should_render_html_with_record_body(html_defaults): - content = { - 'records': [{ - 'body': '

This is my ADR

', - }] - } - content.update(html_defaults) - html = render_html(content) - - assert '

This is my ADR

' in html - - -def test_should_render_html_with_collapsible_index(html_defaults): - content = { - 'records': [{ - 'title': 'Record 123', - 'index': 123 - }] - } - content.update(html_defaults) - html = render_html(content) - result = 'Record 123' - assert result in html - - -def test_should_ignore_invalid_files(): - config = parse_adr_to_config('../test/adr/0003-bad-formatting.md') - - assert config is None