summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurent Alfonsi <laurent.alfonsi@linaro.org>2023-01-09 16:00:44 +0100
committerLaurent Alfonsi <laurent.alfonsi@linaro.org>2023-01-12 10:21:18 +0100
commit9c00c362022a9945e05f8fdcc9c4128b50c47d1c (patch)
tree67f46e475805cc778dce222bcfa3ab6b88a7b57c
parentc62043198b6f37a61ac2e15be4eac1a889f78131 (diff)
tcwg-gen-ci-status-dashboard: Create first draft for developer dashboard
Change-Id: I75506f23cd228c44a76c26bbfd8196443d0a9113
-rwxr-xr-xtcwg-gen-ci-status-dashboard.py632
-rw-r--r--tcwg-gen-ci-status-dashboard/BOARD-MAINTENANCE.yaml33
-rw-r--r--tcwg-gen-ci-status-dashboard/BUILD-KERNEL.yaml29
-rw-r--r--tcwg-gen-ci-status-dashboard/TCWG_BMK_CI.yaml15
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/.prettierrc.js7
-rwxr-xr-xtcwg-gen-ci-status-dashboard/sorting-table-css/CODE_OF_CONDUCT.md46
-rwxr-xr-xtcwg-gen-ci-status-dashboard/sorting-table-css/CONTRIBUTING.md5
-rwxr-xr-xtcwg-gen-ci-status-dashboard/sorting-table-css/LICENSE24
-rwxr-xr-xtcwg-gen-ci-status-dashboard/sorting-table-css/PULL_REQUEST_TEMPLATE.md13
-rwxr-xr-xtcwg-gen-ci-status-dashboard/sorting-table-css/README.md302
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/example.css138
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/example.css.map1
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/example.min.css1
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/example.min.css.map1
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/example.scss42
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.css45
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.css.map1
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.min.css1
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.min.css.map1
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.scss68
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.css82
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.css.map1
-rwxr-xr-xtcwg-gen-ci-status-dashboard/sorting-table-css/sortable.js128
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.css1
-rw-r--r--tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.css.map1
-rwxr-xr-xtcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.js2
-rwxr-xr-xtcwg-gen-ci-status-dashboard/sorting-table-css/sortable.scss44
27 files changed, 1664 insertions, 0 deletions
diff --git a/tcwg-gen-ci-status-dashboard.py b/tcwg-gen-ci-status-dashboard.py
new file mode 100755
index 0000000..9f99acb
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard.py
@@ -0,0 +1,632 @@
+#!/usr/bin/python3
+
+# Usage :
+# gen-ci-status.py ci-status.config.yaml ci-status-example.html
+#
+
+import sys
+import json
+import os
+import yaml
+import datetime
+import re
+import tempfile
+
+scripts_dir=os.path.dirname(sys.argv[0])
+
+
+########################################################################################################################
+# Basic, low level functions
+def nice_print(data):
+ json_formatted_str = json.dumps(data, indent=4)
+ print(json_formatted_str)
+
+def download_and_open(url):
+ tmpf=tempfile.mkstemp()
+ os.system("wget " + "-o /dev/null " + "-O " + tmpf + " " + ci_url_job+pjt_infos['project_name']+"/lastCompletedBuild"+"/console")
+ file = open(tmpf,'r')
+ return file, tmpf
+
+def close_and_remove(file, tmpf):
+ file.close()
+ os.remove(tmpf)
+
+########################################################################################################################
+## CONFIG FILE
+ci_url_view="https://ci.linaro.org/view/"
+ci_url_job="https://ci.linaro.org/job/"
+ci={}
+
+"""
+Read and load yaml config file as it is."""
+def get_config(config_name):
+ with open(config_name, 'r') as file:
+ config = yaml.safe_load(file)
+ #nice_print(config)
+ config_sanity_check(config)
+ config['filename']=config_name
+ return config
+
+def config_sanity_check(config):
+ #nice_print(config)
+ if 'summary_config' not in config:
+ assert("summary_config not exists")
+
+ for grp in 'group_1' 'group_2':
+ #grp="group_1"
+ grp="group_1"
+ if grp not in config['summary_config']:
+ assert("summary_config not exists")
+ #print("= "+grp+" is in config['summary_config']")
+ #nice_print(config['summary_config'][0])
+ if 'columns' not in config['summary_config'][0]:
+ assert("columns not exists")
+ if 'lines' not in config['summary_config'][0]:
+ assert("lines not exists")
+ for col in config['summary_config'][0]['columns']:
+ if len(col.keys()) != 1:
+ assert("ERROR")
+ for pjt in config['summary_config'][0]['lines']:
+ if len(col.keys()) != 1:
+ assert("ERROR")
+
+
+########################################################################################################################
+## COMPUTE MESSAGE ROUTINES
+"""
+Compute best message to display using internal ci-status representation
+- compute_smart_status()
+- compute_smart_diag()
+- compute_color()
+"""
+
+########################
+# compute_smart_status
+"""
+compute_smart_status()
+
+Status reported can any stage of RR algorithm :
+ init / success / reducing / bisecting / forced / failure
+"""
+def compute_smart_status(pjt_infos):
+ ret_attr={'text':"-", 'hlink':"", 'class':"", 'color':""}
+
+ # default status (Success, failure, aborted)
+ ret_attr['text']=pjt_infos['lastCompletedBuild']['result']
+ ret_attr['color']=compute_color(pjt_infos['lastCompletedBuild'], ret_attr['text'])
+
+ # refine with , displayName, nb_components
+ displayname=pjt_infos['lastCompletedBuild']['displayName']
+ components=re.sub("^#[0-9]*(.*-)R.*",r'\1',displayname)
+ nb_components=components.count("-")-1
+
+ if re.search(r'.*-force', displayname):
+ ret_attr['text']="FORCED"
+ elif re.search(r'.*-init', displayname):
+ ret_attr['text']="INIT"
+ elif re.search(r'.*-trigger-bisect', displayname):
+ ret_attr['text']="BISECTING"
+ elif nb_components==1:
+ ret_attr['text']="REDUCING"
+ elif re.search(r'slowed down|grew in size|vect reduced|sve reduced|failed to build', displayname):
+ ret_attr['text']="REGRESSED"
+
+ return ret_attr;
+
+########################
+# compute_smart_diag
+"""
+compute_smart_diag()
+
+It mainly reads lastBuild project, artifacts/results, and console to compute the best diag for this build
+"""
+def compute_smart_diag(pjt_infos):
+ ret_attr={'text':"-", 'hlink':"", 'class':"", 'color':""}
+ #ret_attr['hlink']=ci_url_job+pjt_infos['project_name']+"/lastCompletedBuild"+"/artifact/artifacts/results"+"/*view*/"
+ if pjt_infos['lastCompletedBuild']['result'] == "SUCCESS":
+ return ret_attr
+
+ last_step="-"
+ print("wget "+ci_url_job+pjt_infos['project_name']+"/lastCompletedBuild"+"/artifact/artifacts/results")
+ file, tmpf = download_and_open(ci_url_job+pjt_infos['project_name']+"/lastCompletedBuild"+"/artifact/artifacts/results")
+ results_file = []
+ for items in file:
+ results_file.append(items)
+ close_and_remove(file, tmpf)
+
+ # return diag if found
+ for items in results_file:
+ if re.search("Benchmarking infra is offline", items):
+ ret_attr['text']="Board is offline"
+ ret_attr['class']='diag'
+ break
+ elif re.search("slowed down", items):
+ ret_attr['text']="slowed down"
+ ret_attr['class']='diag'
+ break
+ elif re.search("grew in size", items):
+ ret_attr['text']="grew in size"
+ ret_attr['class']='diag'
+ break
+ elif re.search(r'vect reduced|sve reduced', items):
+ ret_attr['text']="vect/sve reduced"
+ ret_attr['class']='diag'
+ break
+
+ elif re.search("build errors in logs", items):
+ ret_attr['text']="Build errors in : "+last_step
+ break
+
+ elif re.search("internal compiler error", items):
+ ret_attr['text']="ICE in : "+last_step
+ break
+
+ elif re.search(r'^# .*(reset_artifacts|build_abe|build_llvm|benchmark|linux_n_obj)', items):
+ last_step=re.sub("^# ","", items)
+ last_step=re.sub(" --.*","", last_step)
+ last_step=re.sub(":","", last_step)
+ ret_attr['text']=last_step
+
+ file, tmpf = download_and_open(ci_url_job+pjt_infos['project_name']+"/lastCompletedBuild"+"/console")
+ build_machine=""
+ for items in file:
+ if re.search("No space left on device", items):
+ ret_attr['text']="No space left on device "+build_machine
+ ret_attr['class']='diag'
+ break
+ elif re.search(r'java.*Exception', items):
+ ret_attr['text']="Java Exception "+build_machine
+ ret_attr['class']='diag'
+ break
+ break
+ elif re.search("Build timed out", items):
+ ret_attr['text']="Build timed out"
+ ret_attr['class']='diag'
+ break
+ close_and_remove(file, tmpf)
+
+ if ret_attr['color'] == "":
+ ret_attr['color']=compute_color(pjt_infos['lastCompletedBuild'], ret_attr['text'])
+
+ if ret_attr['text'] != "-":
+ return ret_attr
+
+ # Otherwise returns last step
+ ret_attr['text']=last_step
+ return ret_attr
+
+
+"""
+compute_color()
+
+Choose best color
+"""
+def compute_color(pjt_info_build, text):
+ # CI failure
+ if re.search(r'ABORTED', pjt_info_build['result']):
+ return "purple"
+ elif re.search(r'Board is offline|Java Exception|Board is offline|No space left on device', text):
+ return "purple"
+
+ # failure (normal flow)
+ elif re.search(r'FAILURE', text):
+ return "red"
+ elif re.search(r'REGRESSED', text):
+ return "boldorange"
+ elif re.search(r'BISECTING', text):
+ return "boldorange"
+
+ # Sucess (normal flow)
+ elif re.search(r'FORCED', text):
+ return "green"
+ elif re.search(r'INIT', text):
+ return "green"
+ elif re.search(r'SUCCESS', pjt_info_build['result']):
+ return "green"
+ return ""
+
+
+
+########################################################################################################################
+## BUILD CI STATUS REPRESENTATION
+"""
+Get info from CI server and build ci-status representation
+- get_ci_page()
+- get_ci_project_infos()
+- get_ci_project_attribute()
+- get_ci_project_state()
+- get_ci_state()
+"""
+
+"""
+get_ci_page()
+
+Download CI page as json.
+"""
+ci_pages={}
+def get_ci_page(url, request=""):
+ global ci_pages
+ if url+request in ci_pages:
+ return ci_pages[url+request]
+ else:
+ #print(".", end='')
+ try:
+ print("wget "+url+"/api/json"+request)
+ os.system("wget " + "-o /dev/null " + "-O /tmp/download.json " + url + "/api/json" + request)
+ f = open('/tmp/download.json')
+ ci_pages[url+request]=json.load(f)
+ f.close()
+ except:
+ ci_pages[url+request]={}
+ return ci_pages[url+request]
+
+
+"""
+get_ci_project_infos()
+
+Retrieve the information from a given project out of the CI server
+"""
+def get_ci_project_infos(pjt_name):
+ ci_pjt_infos={}
+ usual_requests="?tree=number,result,timestamp,displayName"
+ ci_pjt_infos['lastCompletedBuild']=get_ci_page(ci_url_job+pjt_name+"/lastCompletedBuild", request=usual_requests)
+ ci_pjt_infos['lastFailedBuild']=get_ci_page(ci_url_job+pjt_name+"/lastFailedBuild", request=usual_requests)
+ ci_pjt_infos['project_name']=pjt_name
+ return ci_pjt_infos
+
+"""
+get_ci_project_attribute()
+
+Get one info from the CI server (1 project - 1 attribute)
+"""
+dt_now=datetime.datetime.now()
+def get_ci_project_attribute(pjt_infos, attr_name):
+ ret_attr={'text':"-", 'hlink':"", 'class':"", 'color':""}
+
+ try:
+ if attr_name=="project":
+ ret_attr['text']=pjt_infos['project_name']
+ ret_attr['hlink']=ci_url_job+pjt_infos['project_name']
+
+ elif attr_name=="status":
+ ret_attr=compute_smart_status(pjt_infos)
+
+ elif attr_name=="diag":
+ ret_attr=compute_smart_diag(pjt_infos)
+
+ elif attr_name=="status_and_diag":
+ ret_attr=compute_smart_status(pjt_infos)
+ status=ret_attr['text']
+
+ ret_attr=compute_smart_diag(pjt_infos)
+ if ret_attr['text'] != "-":
+ ret_attr['text']=status+" ("+ret_attr['text']+")"
+ else:
+ ret_attr['text']=status
+ ret_attr['color']=compute_color(pjt_infos['lastCompletedBuild'], ret_attr['text'])
+
+ elif attr_name=="time_since_last_build":
+ timestamp=pjt_infos['lastCompletedBuild']['timestamp']
+ ret_attr['text']=str( round(((int(dt_now.strftime('%s')) - round(timestamp/1000)) / 3600) / 24)) + " days"
+
+ elif attr_name=="time_since_last_fail":
+ timestamp=pjt_infos['lastFailedBuild']['timestamp']
+ ret_attr['text']=str( round(((int(dt_now.strftime('%s')) - round(timestamp/1000)) / 3600) / 24)) + " days"
+
+ elif attr_name=="build_number":
+ ret_attr['text']=pjt_infos['lastCompletedBuild']['number']
+ ret_attr['hlink']=ci_url_job+pjt_infos['project_name']+"/"+str(pjt_infos['lastCompletedBuild']['number'])
+
+ elif attr_name=="result_file":
+ ret_attr['text']="res"
+ ret_attr['hlink']=ci_url_job+pjt_infos['project_name']+"/"+str(pjt_infos['lastCompletedBuild']['number'])+"/artifact/artifacts/results/*view*/"
+
+ elif attr_name=="mail_reported":
+ ret_attr=compute_smart_diag(pjt_infos)
+ if re.search(r'slowed down|grew in size|vect reduced|sve reduced', ret_attr['text']):
+ ret_attr['text']="reported"
+ ret_attr['hlink']=ci_url_job+pjt_infos['project_name']+"/"+str(pjt_infos['lastCompletedBuild']['number'])+"/artifact/artifacts/mail/mail-body.txt/*view*/"
+ else:
+ ret_attr['text']="-"
+
+ elif attr_name=="console_file":
+ ret_attr['text']="console"
+ ret_attr['hlink']=ci_url_job+pjt_infos['project_name']+"/"+str(pjt_infos['lastCompletedBuild']['number'])+"/console"
+
+ elif attr_name=="bmk_job":
+ file, tmpf = download_and_open(ci_url_job+pjt_infos['project_name']+"/lastCompletedBuild"+"/artifact/artifacts/results_id")
+ for items in file:
+ ret_attr['text']=re.sub(".*/","", items)
+ ret_attr['hlink']=ci_url_job+"tcwg-benchmark"+"/"+ret_attr['text']
+ close_and_remove(file, tmpf)
+
+ elif attr_name=="display_name":
+ ret_attr['text']=pjt_infos['lastCompletedBuild']['displayName']
+ ret_attr['hlink']=ci_url_job+pjt_infos['project_name']+"/"+str(pjt_infos['lastCompletedBuild']['number'])
+
+ elif attr_name=="components":
+ ret_attr['text']=re.sub("^#[0-9]*-(.*)-R.*",r'\1',pjt_infos['lastCompletedBuild']['displayName'])
+
+ elif attr_name=="failing_step":
+ ret_attr=compute_smart_diag(pjt_infos)
+
+ except:
+ ret_attr['text']="-"
+
+ return ret_attr
+
+"""
+get_ci_project_state()
+
+Get ci-status internal representation for a project (PJT)
+Iterates over the CONFIG columns, and request the infos for each one
+"""
+def get_ci_project_state(config_grp, ci, pjt_name):
+ ci_project={}
+ pjt_infos=get_ci_project_infos(pjt_name)
+ for attr in config_grp['columns']:
+ attr=next(iter(attr))
+ ci_project[attr] = get_ci_project_attribute(pjt_infos, attr)
+ return ci_project
+
+"""
+get_ci_state()
+
+Get ci-status internal representation main routine.
+Iterates over the CONFIG project, and request the infos for each one
+"""
+def get_ci_state(config):
+ #nice_print(config['summary_config'][0]['lines'])
+ ci={}
+ i=0
+ for grp in config['summary_config']:
+ for pjt in config['summary_config'][i]['lines']:
+ if type(pjt) is str:
+ ci[pjt]=get_ci_project_state(config['summary_config'][i], ci, pjt)
+ #print(ci[pjt])
+
+ elif type(pjt) is dict and 'job' in pjt:
+ ci[pjt['job']]=get_ci_project_state(config['summary_config'][i], ci, pjt['job'])
+ elif type(pjt) is dict and 'new_table' in pjt:
+
+ continue
+ elif type(pjt) is dict and 'view' in pjt:
+ view_name=pjt['view']
+ ci[view_name]=get_ci_page(ci_url_view+view_name)
+ for job in ci[view_name]['jobs']:
+ if 'pattern' in pjt and not re.search(pjt['pattern'], job['name']):
+ continue
+ ci[job['name']]=get_ci_project_state(config['summary_config'][i], ci, job['name'])
+
+
+
+ i=i+1
+
+ return ci
+
+
+
+########################################################################################################################
+## DUMP HTML
+
+"""
+dump html routines
+- dump_html_one_line
+- dump_html_new_table
+- dump_html
+"""
+
+html_header = """<html>
+<style>
+ table, td, th {
+ border-collapse: collapse;
+ }
+ tbody tr:nth-child(even) td {
+ background-color: #ededed;
+ }
+</style>
+<head>
+<title>CI Status - %s</title>
+<link rel="stylesheet" type="text/css" href="sorting-table-css/example.css" />
+</head>
+<body>
+<h1>CI Status - %s</h1>
+"""
+
+
+html_footer = """
+ <script>
+ var table = document.querySelector('.massive')
+ var tbody = table.tBodies[0]
+ var rows = [].slice.call(tbody.rows, 0)
+ var fragment = document.createDocumentFragment()
+
+ for (var k = 0; k < 50; k++) {
+ for (var i = 0; i < rows.length; i++) {
+ fragment.appendChild(rows[i].cloneNode(true))
+ }
+ }
+ tbody.innerHTML = ''
+ tbody.appendChild(fragment)
+ </script>
+ <!-- <script type="text/javascript" src="sortable.js"></script> -->
+ <script src="sorting-table-css/sortable.js"></script>
+ <script>
+ function prepareAdvancedTable() {
+ function convertSizeToBytes(str) {
+ var matches = str.match(/^([0-9.]+)(\w+)$/)
+ if (matches) {
+ var vals = {
+ kB: 1, // 1024 B
+ KiB: 1,// 1024 B
+ MB: 2, // 1024 * 1024 B
+ GB: 3, // 1024 * 1024 * 1024 B
+ TB: 4, // 1024 * 1024 * 1024 *1024 B
+ }
+ return (matches[1] || 0) * Math.pow(1024, vals[matches[2]])
+ }
+ return str
+ }
+
+ var size_table = document.querySelector('.advanced-table')
+ var rows = size_table.tBodies[0].rows
+ for (let i = 0; i < rows.length; i++) {
+ const date_element = rows[i].cells[2]
+ const size_element = rows[i].cells[1]
+ date_element.setAttribute('data-sort', date_element.innerText.replace(/(\d+)\/(\d+)\/(\d+)/, '$3$1$2'))
+ size_element.setAttribute('data-sort', convertSizeToBytes(size_element.innerText))
+ }
+ }
+ prepareAdvancedTable()
+ </script>
+</body>
+</html>
+"""
+
+def dump_html_one_line(f, config_grp, ci_pjt):
+ f.write(" <tr>\n")
+ for attr in config_grp['columns']:
+ attr=next(iter(attr))
+ f.write(" <td>")
+ if ci_pjt[attr]['color']:
+ f.write("<font color=\""+ci_pjt[attr]['color']+"\">")
+ if ci_pjt[attr]['hlink']:
+ f.write("<a href='"+ci_pjt[attr]['hlink']+"'>")
+ f.write(str(ci_pjt[attr]['text']))
+ if ci_pjt[attr]['hlink']:
+ f.write("</a>")
+ if ci_pjt[attr]['color']:
+ f.write("</font>")
+ f.write("</td>\n");
+ f.write(" </tr>\n")
+
+def dump_html_new_table(f, config_grp, new_title):
+ f.write("<h3>"+new_title+"</h3>\n")
+ f.write("<table border=1 cellspacing=1 cellpadding=3 class=\"sortable\">\n")
+ f.write(" <thead>\n")
+ for col in config_grp['columns']:
+ k=next(iter(col))
+ f.write(" <th>"+col[k]+"</th>\n")
+ f.write(" </thead>\n")
+
+def dump_html(html_name, config, ci):
+ in_a_table=False
+
+ html_dirname=os.path.dirname(html_name)+"/"
+
+ print("# Copy yaml : "+ config['filename'])
+ os.system("cp " + config['filename'] + " " + html_dirname)
+
+ print("# Copy css : sorting-table-css")
+ os.system("cp -ar "+scripts_dir+"/tcwg-gen-ci-status-dashboard/sorting-table-css " + os.path.dirname(html_name))
+
+ print("# Emit html : "+ html_name)
+ f = open(html_name, 'w')
+ f.write(html_header % (os.path.basename(config['filename']), os.path.basename(config['filename'])))
+ f.write("<p> date = "+str(dt_now)+"</p>\n")
+ f.write("<p> config = <a href="+os.path.basename(config['filename'])+">"+os.path.basename(config['filename'])+"</a></p>\n")
+
+ i=0
+ for grp in config['summary_config']:
+ if grp['title']:
+ f.write("<h2>"+grp['title']+"</h2>\n")
+
+ # table body
+ for pjt in config['summary_config'][i]['lines']:
+ if type(pjt) is str:
+ dump_html_one_line(f, config['summary_config'][i], ci[pjt])
+
+ elif type(pjt) is dict and 'new_table' in pjt:
+ if in_a_table:
+ f.write("</table>\n")
+ in_a_table=False
+ dump_html_new_table(f, config['summary_config'][i], pjt['new_table'])
+ in_a_table=True
+
+ elif type(pjt) is dict and 'view' in pjt:
+ view_name=pjt['view']
+ for job in ci[view_name]['jobs']:
+ if 'pattern' in pjt and not re.search(pjt['pattern'], job['name']):
+ continue
+ dump_html_one_line(f, config['summary_config'][i], ci[job['name']])
+
+ elif not in_a_table:
+ dump_html_new_table(f, config['summary_config'][i], "")
+ in_a_table=True
+
+ if in_a_table:
+ f.write("</table>\n")
+ in_a_table=False
+ i=i+1
+
+ f.write(html_footer)
+ f.close()
+
+
+########################################################################################################################
+## BASIC ASCII DUMP ROUTINES
+
+"""
+Basic ascii dump routines
+- dump_ascii_one_line
+- dump_ascii
+"""
+def dump_ascii_one_line(config_grp, ci, pjt_name):
+ for attr in config_grp['columns']:
+ attr=next(iter(attr))
+ if attr=="project":
+ fmt="%70s,"
+ else:
+ fmt="%20s,"
+ print(fmt % (ci[pjt_name][attr]['text']), end='')
+ print("")
+
+def dump_ascii(config, ci):
+ i=0
+ for grp in config['summary_config']:
+ print("=== " + config['summary_config'][i]['title'] + "\n")
+
+ # Array header
+ for col in config['summary_config'][i]['columns']:
+ k=next(iter(col))
+ if k=="project":
+ fmt="%70s,"
+ else:
+ fmt="%20s,"
+ print(fmt % (col[k]), end='');
+ print("")
+
+ for pjt in config['summary_config'][i]['lines']:
+ if type(pjt) is str:
+ dump_ascii_one_line(config['summary_config'][i], ci, pjt)
+ elif type(pjt) is dict and 'new_table' in pjt:
+ print("\n== %20s" % (pjt['new_table']));
+ continue
+ elif type(pjt) is dict and 'view' in pjt:
+ view_name=pjt['view']
+ for job in ci[view_name]['jobs']:
+ if 'pattern' in pjt and not re.search(pjt['pattern'], job['name']):
+ continue
+ dump_ascii_one_line(config['summary_config'][i], ci, job['name'])
+
+ i=i+1
+
+
+########################################################################################################################
+## MAIN PROCEDURE
+if __name__ == "__main__":
+ if len(sys.argv) <= 2:
+ print("USAGE : gen-ci-status.py report-config.yaml <report-config.html>")
+ config_name=sys.argv[1]
+ if len(sys.argv) == 2:
+ html_name=config_name+".html"
+ else:
+ html_name=sys.argv[2]
+
+ config=get_config(config_name)
+
+ ci=get_ci_state(config)
+
+ # dump_ascii(config, ci)
+ dump_html(html_name, config, ci)
+
diff --git a/tcwg-gen-ci-status-dashboard/BOARD-MAINTENANCE.yaml b/tcwg-gen-ci-status-dashboard/BOARD-MAINTENANCE.yaml
new file mode 100644
index 0000000..5d09a32
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/BOARD-MAINTENANCE.yaml
@@ -0,0 +1,33 @@
+summary_config:
+ -
+ title: "CI maintenance - Boards view"
+ columns:
+ - project: "Project"
+ - status_and_diag: "Status"
+ - time_since_last_build: "Since last build"
+ - bmk_job: "Bmk job"
+ lines:
+ - new_table: "APM boards"
+ - view: tcwg_bmk-ci
+ pattern: apm.*build
+
+ - new_table: "TX1 boards"
+ - view: tcwg_bmk-ci
+ pattern: tx1.*build
+
+ - new_table: "TK1 boards"
+ - view: tcwg_bmk-ci
+ pattern: tk1.*build.*
+
+ - new_table: "SQ boards"
+ - view: tcwg_bmk-ci
+ pattern: sq.*build
+
+ - new_table: "FX700 boards"
+ - view: tcwg_bmk-ci
+ pattern: fx.*build
+
+ - new_table: "STM32 boards"
+ - view: tcwg_bmk-ci
+ pattern: stm32.*build
+
diff --git a/tcwg-gen-ci-status-dashboard/BUILD-KERNEL.yaml b/tcwg-gen-ci-status-dashboard/BUILD-KERNEL.yaml
new file mode 100644
index 0000000..150dad4
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/BUILD-KERNEL.yaml
@@ -0,0 +1,29 @@
+summary_config:
+ -
+ title: "Kernel builds (llvm)"
+ columns:
+ - project: "Project"
+ - status_and_diag: "Status"
+ - time_since_last_build: "Since last build"
+ - time_since_last_fail: "Since last fail"
+ - display_name: "Title"
+ - result_file: "Result"
+ lines:
+ # GNU
+ - new_table: "Kernel builds : gnu master"
+ - view: tcwg_kernel-gnu
+ pattern: tcwg_kernel-gnu-build-gnu-master-.*
+
+ - new_table: "Kernel builds : gnu release"
+ - view: tcwg_kernel-gnu
+ pattern: tcwg_kernel-gnu-build-gnu-release-.*
+
+ # LLVM
+ - new_table: "Kernel llvm master"
+ - view: tcwg_kernel-llvm
+ pattern: tcwg_kernel-llvm-build-llvm-master-.*
+
+ - new_table: "Kernel llvm release"
+ - view: tcwg_kernel-llvm
+ pattern: tcwg_kernel-llvm-build-llvm-release-.*
+
diff --git a/tcwg-gen-ci-status-dashboard/TCWG_BMK_CI.yaml b/tcwg-gen-ci-status-dashboard/TCWG_BMK_CI.yaml
new file mode 100644
index 0000000..18ecdba
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/TCWG_BMK_CI.yaml
@@ -0,0 +1,15 @@
+summary_config:
+ -
+ title: "All Benchmarking"
+ columns:
+ - project: "Project"
+ - status_and_diag: "status_and_diag"
+ - time_since_last_build: "Since last build"
+ - time_since_last_fail: "Since last fail"
+ - display_name: "Title"
+ - result_file: "Result"
+ - bmk_job: "Bmk job"
+ lines:
+ - new_table: "tcwg_bmk-ci"
+ - view: tcwg_bmk-ci
+ pattern: build
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/.prettierrc.js b/tcwg-gen-ci-status-dashboard/sorting-table-css/.prettierrc.js
new file mode 100644
index 0000000..24df7e8
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/.prettierrc.js
@@ -0,0 +1,7 @@
+module.exports = {
+ singleQuote: true,
+ semi: false,
+ trailingComma: 'all',
+ tabWidth: 2,
+ printWidth: 120,
+}
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/CODE_OF_CONDUCT.md b/tcwg-gen-ci-status-dashboard/sorting-table-css/CODE_OF_CONDUCT.md
new file mode 100755
index 0000000..b2e87d7
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jonas@earendel.se. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/CONTRIBUTING.md b/tcwg-gen-ci-status-dashboard/sorting-table-css/CONTRIBUTING.md
new file mode 100755
index 0000000..89ad019
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+# Contributing
+
+I am grateful for any and all contributions.
+
+If it's a minor thing I guess it's easier to open an issue, but if you prefer creating a fork, go ahead! :)
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/LICENSE b/tcwg-gen-ci-status-dashboard/sorting-table-css/LICENSE
new file mode 100755
index 0000000..cf1ab25
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org>
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/PULL_REQUEST_TEMPLATE.md b/tcwg-gen-ci-status-dashboard/sorting-table-css/PULL_REQUEST_TEMPLATE.md
new file mode 100755
index 0000000..0674d91
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,13 @@
+# Pull request template
+
+## Purpose
+
+_Describe the problem or feature in addition to a link to the issues._
+
+## Approach
+
+_How does this change address the problem?_
+
+## Fixes
+
+_List of resolved issues._
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/README.md b/tcwg-gen-ci-status-dashboard/sorting-table-css/README.md
new file mode 100755
index 0000000..f4143d6
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/README.md
@@ -0,0 +1,302 @@
+# sortable - a tiny, vanilla JS table sorter
+
+Makes any table with **class="sortable"**, er, sortable. That is the user can click on a table header and change the sorting of the table rows.
+
+Just include the JavaScript and it will work. No function calls needed, all is done with an **eventListener**.
+(the CSS is not strictly needed, but makes it ~pretty and user friendly)
+
+- [sortable - a tiny, vanilla JS table sorter](#sortable---a-tiny-vanilla-js-table-sorter)
+ - [Factoids](#factoids)
+ - [...with a little help from my friends](#with-a-little-help-from-my-friends)
+ - [Demo](#demo)
+ - [A basic example](#a-basic-example)
+ - [Non-sortable field](#non-sortable-field)
+ - [...using `class` and `css`](#using-class-and-css)
+ - [...using `css` only](#using-css-only)
+ - [Indicators/arrows on the left side](#indicatorsarrows-on-the-left-side)
+ - [Note about css/scss](#note-about-cssscss)
+ - [Sort on value other than the one shown](#sort-on-value-other-than-the-one-shown)
+ - [Alternative sorting](#alternative-sorting)
+ - [Specify which column should be sorted](#specify-which-column-should-be-sorted)
+ - [Ascending sort](#ascending-sort)
+ - [Sort on load](#sort-on-load)
+
+## Factoids
+
+- **921 bytes** minified. (541 bytes gzipped)
+
+- Works with **JavaScript generated tables**. (since we are using an eventListener)
+
+- **Lightning fast**. _Huge_ tables will make it slow and may freeze the browser, especially for mobiles, so you know...
+
+- Requires **thead** and **tbody**.
+
+- **cross browser**, ie9+ (I think, there have been a _whole_ bunch of changes since I last tested it on ie9 🤷)
+
+- ~~eventListeners attached to the rows _WILL_ be removed~~
+
+- eventListeners are no longer removed! 😊
+
+- NOT tested with React, Angular, Vue, etc.
+
+- Works with [Svelte](https://svelte.dev/)!
+
+### ...with a little help from my friends
+
+- `table` > `class="sortable asc"` let's you [sort ascending](#ascending-sort) as default. Thanks [
+ Nikita Dunajevs](https://github.com/dunajevs)!
+
+- `data-sort-alt` in `tbody` > `td` allows for [alternative sorting](#alternative-sorting) while holding `shift` or `alt`. Thanks [wodny](https://github.com/wodny)!
+
+- `data-sort-col` in `thead` > `th` allows you to [specify which column should be sorted](#specify-which-column-should-be-sorted), in case you are using `colspan`, for instance. Thanks [Nick Kocharhook](https://github.com/nk9)!
+
+- **Nested elements** inside `th` now works. Thanks [mxve](https://github.com/mxve)!
+
+- [Sort on load](#sort-on-load) example. Thanks [Christian Petersson](https://github.com/Issen007) and [Abit Salihu](https://github.com/abitsalihu)!
+
+- Thanks to [chatcoda](https://github.com/chatcoda) for the `<td></td>` / `<td>0</td>` sorting bug fix!
+
+## Demo
+
+You can find a simple demo on <https://tofsjonas.github.io/sortable/>
+
+## A basic example
+
+```html
+<table class="sortable">
+ <thead>
+ <tr>
+ <th><span>Role</span></th>
+ <th>Name</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Genius</td>
+ <td>Rick</td>
+ </tr>
+ <tr>
+ <td><a href="javascript:alert('Inline javascript works!');">Sidekick</a></td>
+ <td>Morty</td>
+ </tr>
+ </tbody>
+</table>
+<link href="https://cdn.jsdelivr.net/gh/tofsjonas/sortable/sortable.min.css" rel="stylesheet" />
+<script src="https://cdn.jsdelivr.net/gh/tofsjonas/sortable/sortable.min.js"></script>
+```
+
+_(The `span` is just there to prove that elements inside `th` works)_
+
+## Non-sortable field
+
+### ...using `class` and `css`
+
+If you wish to disable sorting for a specific field, the easiest way is to add a class to it, like so:
+
+```html
+<tr>
+ <th class="no-sort">Role</th>
+ <th>Name</th>
+</tr>
+```
+
+and then use css to block clicks. like so:
+
+```css
+.sortable th.no-sort {
+ pointer-events: none;
+}
+```
+
+### ...using `css` only
+
+This is a bit trickier, but it doesn't require any changes to the html, so I guess it could be worth it in some cases.
+
+```css
+/* the first column in every sortable table should not be sortable*/
+.sortable th:nth-child(1) {
+ pointer-events: none;
+}
+
+/* the seventh column in the second .sortable table should not be sortable*/
+.sortable:nth-of-type(2) th:nth-child(7) {
+ pointer-events: none;
+}
+```
+
+## Indicators/arrows on the left side
+
+If you have text that is aligned on the right side, you may want to have the arrows on the left side.
+
+This is solved by adding a class to the css and using `::before` instead of `::after`.
+
+(You can of course use a pure css solution, without class names - just like with the [non-sortable field](#non-sortable-field) - but _that_ I will leave for you to figure out.)
+
+```css
+.sortable th.indicator-left::after {
+ content: '';
+}
+.sortable th.indicator-left::before {
+ margin-right: 3px;
+ content: 'â–¸';
+}
+/* etc. */
+```
+
+> _Full example: [CSS](https://github.com/tofsjonas/sortable/blob/main/sortable-base.css), [SCSS](https://github.com/tofsjonas/sortable/blob/main/sortable-base.scss)_
+
+## Note about css/scss
+
+The `css/scss` in this repo was only ever meant as an example. It was never intended to be actually _used_.
+
+That said, if you're feeling lazy, here are two stylesheets you can use:
+
+```html
+<!-- This will add arrows only -->
+<link href="https://cdn.jsdelivr.net/gh/tofsjonas/sortable/sortable-base.min.css" rel="stylesheet" />
+
+<!-- This will make it look like the tables in the example, with arrows, striped rows etc. -->
+<link href="https://cdn.jsdelivr.net/gh/tofsjonas/sortable/sortable.min.css" rel="stylesheet" />
+```
+
+## Sort on value other than the one shown
+
+Using the `data-sort` attribute in `tbody` > `td` you can have one visible value and one sortable value.
+This is useful in case you have for instance sizes like kb, Mb, GB, etc.
+
+```html
+<table class="sortable">
+ <thead>
+ <tr>
+ <th>Movie Name</th>
+ <th>Size</th>
+ <th>Release date</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Zack Snyder's Justice League</td>
+ <td data-sort="943718400">900MB</td>
+ <td data-sort="20210318">03/18/2021</td>
+ </tr>
+ <tr>
+ <td>The Sound of Music</td>
+ <td data-sort="1610612736">1.5GB</td>
+ <td data-sort="19651209">12/09/1965</td>
+ </tr>
+ </tbody>
+</table>
+```
+
+## Alternative sorting
+
+If you click on a table header while holding **shift** or **alt** an alternative
+`data-sort-alt` attribute will override `data-sort`.
+
+```html
+<table class="sortable">
+ <thead>
+ <tr>
+ <th>Movie Name</th>
+ <th>Size</th>
+ <th>Release date</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Something</td>
+ <td data-sort-alt="c" data-sort="a">A</td>
+ <td data-sort-alt="b" data-sort="c">B</td>
+ <td data-sort-alt="a" data-sort="b">C</td>
+ </tr>
+ <tr>
+ <td>Something else</td>
+ <td data-sort-alt="e" data-sort="f">D</td>
+ <td data-sort-alt="f" data-sort="e">E</td>
+ <td data-sort-alt="d" data-sort="d">F</td>
+ </tr>
+ </tbody>
+</table>
+```
+
+## Specify which column should be sorted
+
+Using the `data-sort-col` attribute in `thead` > `th`, you can sort on a different column than the one that was clicked. For instance if you want to have colspans. Like so:
+
+```html
+<thead>
+ <tr>
+ <th></th>
+ <th>Category</th>
+ <th class="show_name">Show</th>
+ <th colspan="2">Overall</th>
+ <th colspan="2" data-sort-col="5">On Our Dates</th>
+ <th data-sort-col="7">First Sold Out</th>
+ </tr>
+</thead>
+<tbody>
+ <tr>
+ <td class="tags">&nbsp;</td>
+ <td class="category">Comedy</td>
+ <td class="show_name">Show 1</td>
+ <td class="ratio all" data-sort="72">18/25</td>
+ <td class="pct all">72%</td>
+ <td class="ratio ours" data-sort="75">3/4</td>
+ <td class="pct ours">75%</td>
+ <td>2022-07-30</td>
+ </tr>
+ ...
+</tbody>
+```
+
+## Ascending sort
+
+By adding `asc` to `table`, the default sorting direction will be **ascending** instead of descending
+
+```html
+<table class="sortable asc">
+ <thead>
+ ...
+ </thead>
+ <tbody>
+ ...
+ </tbody>
+</table>
+```
+
+## Sort on load
+
+If you wish to sort a table on load, I would recommend doing something like this:
+
+```html
+<table class="sortable">
+ <thead>
+ <tr>
+ <th>Movie Name</th>
+ <th id="movie-size">Size</th>
+ <th>Release date</th>
+ </tr>
+ </thead>
+ <tbody>
+ ...
+ </tbody>
+</table>
+
+<script>
+ window.addEventListener('load', function () {
+ const el = document.getElementById('movie-size')
+ // without id:
+ // const el = document.querySelector('.sortable th:first-child')
+ // const el = document.querySelector('.sortable th:nth-child(2)')
+ // const el = document.querySelectorAll('.sortable')[3].querySelector('th:nth-child(7)')
+ // etc.
+ if (el) {
+ el.click()
+ }
+ })
+</script>
+```
+
+Combine this with `<table class="sortable asc">` to reverse the sort order. Or do `el.click()` twice!
+
+[![jsdelivr](https://data.jsdelivr.com/v1/package/gh/tofsjonas/sortable/badge)](https://www.jsdelivr.com/package/gh/tofsjonas/sortable)
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/example.css b/tcwg-gen-ci-status-dashboard/sorting-table-css/example.css
new file mode 100644
index 0000000..7e503fb
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/example.css
@@ -0,0 +1,138 @@
+@charset "UTF-8";
+
+.failure { color: red; }
+
+.sortable th {
+ cursor: pointer;
+}
+.sortable th.no-sort {
+ pointer-events: none;
+}
+.sortable th::after, .sortable th::before {
+ transition: color 0.1s ease-in-out;
+ font-size: 1.2em;
+ color: transparent;
+}
+.sortable th::after {
+ margin-left: 3px;
+ content: "â–¸";
+}
+.sortable th:hover::after {
+ color: inherit;
+}
+.sortable th.dir-d::after {
+ color: inherit;
+ content: "â–¾";
+}
+.sortable th.dir-u::after {
+ color: inherit;
+ content: "â–´";
+}
+.sortable th.indicator-left::after {
+ content: "";
+}
+.sortable th.indicator-left::before {
+ margin-right: 3px;
+ content: "â–¸";
+}
+.sortable th.indicator-left:hover::before {
+ color: inherit;
+}
+.sortable th.indicator-left.dir-d::before {
+ color: inherit;
+ content: "â–¾";
+}
+.sortable th.indicator-left.dir-u::before {
+ color: inherit;
+ content: "â–´";
+}
+
+.success { color: green; }
+.none { color: black; }
+.failure { color: red; }
+.aborted { color: darkred; }
+.diag { color: purple; }
+
+
+.sortable {
+ --stripe-color: #c2c2c2;
+ --th-color: #fff;
+ --th-bg: #404040;
+ --td-color: #000;
+ --td-on-stripe-color: #000;
+ border-spacing: 0;
+}
+.sortable tbody tr:nth-child(odd) {
+ background-color: var(--stripe-color);
+ color: var(--td-on-stripe-color);
+}
+.sortable th {
+ background: var(--th-bg);
+ color: var(--th-color);
+ font-weight: normal;
+ text-align: left;
+ text-transform: capitalize;
+ vertical-align: baseline;
+ white-space: nowrap;
+}
+.sortable td {
+ color: var(--td-color);
+}
+.sortable td,
+.sortable th {
+ padding: 5px;
+}
+.sortable td:first-child,
+.sortable th:first-child {
+ border-top-left-radius: 4px;
+}
+.sortable td:last-child,
+.sortable th:last-child {
+ border-top-right-radius: 4px;
+}
+
+body {
+ font-size: 14px;
+}
+
+
+p {
+ line-height: 1.7em;
+}
+
+code {
+ font-family: monospace;
+ background: #eee;
+ padding: 5px;
+ border-radius: 2px;
+}
+
+* {
+ box-sizing: border-box;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.sortable:nth-of-type(4) th:nth-child(7),
+th.no-sort {
+ background: pink;
+ color: red;
+ pointer-events: none;
+}
+
+.sortable:nth-of-type(4) th:nth-child(7)::after {
+ color: red;
+ content: "(also not sortable)";
+ font-size: 0.9em;
+ display: block;
+}
+
+.lefty td:nth-child(2),
+.lefty th:nth-child(2) {
+ width: 80px;
+ text-align: right;
+}/*# sourceMappingURL=example.css.map */
+
+
+.failure { color: red; }
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/example.css.map b/tcwg-gen-ci-status-dashboard/sorting-table-css/example.css.map
new file mode 100644
index 0000000..e5d7a9c
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/example.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["example.css","sortable-base.scss","sortable.scss","example.scss"],"names":[],"mappings":"AAAA,gBAAgB;ACCd;EACE,eAAA;ADCJ;ACCI;EACE,oBAAA;ADCN;ACCI;EAEE,kCAAA;EACA,gBAAA;EACA,kBAAA;ADAN;ACGI;EACE,gBAAA;EACA,YAAA;ADDN;ACIM;EACE,cAAA;ADFR;ACOM;EACE,cAAA;EACA,YAAA;ADLR;ACUM;EACE,cAAA;EACA,YAAA;ADRR;ACYM;EACE,WAAA;ADVR;ACYM;EACE,iBAAA;EACA,YAAA;ADVR;ACcQ;EACE,cAAA;ADZV;ACiBQ;EACE,cAAA;EACA,YAAA;ADfV;ACoBQ;EACE,cAAA;EACA,YAAA;ADlBV;;AE1CA;EACE,uBAAA;EACA,gBAAA;EACA,gBAAA;EACA,gBAAA;EACA,0BAAA;EAEA,iBAAA;AF4CF;AExCM;EACE,qCAAA;EACA,gCAAA;AF0CR;AEtCE;EACE,wBAAA;EACA,sBAAA;EACA,mBAAA;EACA,gBAAA;EACA,0BAAA;EACA,wBAAA;EACA,mBAAA;AFwCJ;AEtCE;EACE,sBAAA;AFwCJ;AEtCE;;EAEE,aAAA;AFwCJ;AEtCI;;EACE,2BAAA;AFyCN;AEtCI;;EACE,4BAAA;AFyCN;;AGhFA;EACE,eAAA;AHmFF;;AGhFA;EACE,kBAAA;AHmFF;;AGhFA;EACE,sBAAA;EACA,gBAAA;EACA,YAAA;EACA,kBAAA;AHmFF;;AGhFA;EACE,sBAAA;EACA,8JAAA;EAEA,mCAAA;EACA,kCAAA;AHkFF;;AGhFA;;EAEE,gBAAA;EACA,UAAA;EACA,oBAAA;AHmFF;;AGjFA;EACE,UAAA;EACA,8BAAA;EACA,gBAAA;EACA,cAAA;AHoFF;;AGjFE;;EAEE,WAAA;EACA,iBAAA;AHoFJ","file":"example.css"} \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/example.min.css b/tcwg-gen-ci-status-dashboard/sorting-table-css/example.min.css
new file mode 100644
index 0000000..86aa6c1
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/example.min.css
@@ -0,0 +1 @@
+.sortable th{cursor:pointer}.sortable th.no-sort{pointer-events:none}.sortable th::after,.sortable th::before{transition:color .1s ease-in-out;font-size:1.2em;color:rgba(0,0,0,0)}.sortable th::after{margin-left:3px;content:"â–¸"}.sortable th:hover::after{color:inherit}.sortable th.dir-d::after{color:inherit;content:"â–¾"}.sortable th.dir-u::after{color:inherit;content:"â–´"}.sortable th.indicator-left::after{content:""}.sortable th.indicator-left::before{margin-right:3px;content:"â–¸"}.sortable th.indicator-left:hover::before{color:inherit}.sortable th.indicator-left.dir-d::before{color:inherit;content:"â–¾"}.sortable th.indicator-left.dir-u::before{color:inherit;content:"â–´"}.sortable{--stripe-color: #e4e4e4;--th-color: #fff;--th-bg: #808080;--td-color: #000;--td-on-stripe-color: #000;border-spacing:0}.sortable tbody tr:nth-child(odd){background-color:var(--stripe-color);color:var(--td-on-stripe-color)}.sortable th{background:var(--th-bg);color:var(--th-color);font-weight:normal;text-align:left;text-transform:capitalize;vertical-align:baseline;white-space:nowrap}.sortable td{color:var(--td-color)}.sortable td,.sortable th{padding:10px}.sortable td:first-child,.sortable th:first-child{border-top-left-radius:4px}.sortable td:last-child,.sortable th:last-child{border-top-right-radius:4px}body{font-size:14px}p{line-height:1.7em}code{font-family:monospace;background:#eee;padding:5px;border-radius:2px}*{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.sortable:nth-of-type(4) th:nth-child(7),th.no-sort{background:pink;color:red;pointer-events:none}.sortable:nth-of-type(4) th:nth-child(7)::after{color:red;content:"(also not sortable)";font-size:.9em;display:block}.lefty td:nth-child(2),.lefty th:nth-child(2){width:80px;text-align:right}/*# sourceMappingURL=example.min.css.map */ \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/example.min.css.map b/tcwg-gen-ci-status-dashboard/sorting-table-css/example.min.css.map
new file mode 100644
index 0000000..c748cb8
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/example.min.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["example.min.css","sortable-base.scss","sortable.scss","example.scss"],"names":[],"mappings":"AAAA,aCCE,cACE,CAAA,qBAEA,mBACE,CAAA,yCAEF,gCAEE,CAAA,eACA,CAAA,mBACA,CAAA,oBAGF,eACE,CAAA,WACA,CAAA,0BAGA,aACE,CAAA,0BAKF,aACE,CAAA,WACA,CAAA,0BAKF,aACE,CAAA,WACA,CAAA,mCAIF,UACE,CAAA,oCAEF,gBACE,CAAA,WACA,CAAA,0CAIA,aACE,CAAA,0CAKF,aACE,CAAA,WACA,CAAA,0CAKF,aACE,CAAA,WACA,CAAA,UC5DV,uBACE,CAAA,gBACA,CAAA,gBACA,CAAA,gBACA,CAAA,0BACA,CAAA,gBAEA,CAAA,kCAII,oCACE,CAAA,+BACA,CAAA,aAIN,uBACE,CAAA,qBACA,CAAA,kBACA,CAAA,eACA,CAAA,yBACA,CAAA,uBACA,CAAA,kBACA,CAAA,aAEF,qBACE,CAAA,0BAEF,YAEE,CAAA,kDAEA,0BACE,CAAA,gDAGF,2BACE,CAAA,KCvCN,cACE,CAAA,EAGF,iBACE,CAAA,KAGF,qBACE,CAAA,eACA,CAAA,WACA,CAAA,iBACA,CAAA,EAGF,qBACE,CAAA,mJACA,CAAA,kCAEA,CAAA,iCACA,CAAA,oDAEF,eAEE,CAAA,SACA,CAAA,mBACA,CAAA,gDAEF,SACE,CAAA,6BACA,CAAA,cACA,CAAA,aACA,CAAA,8CAGA,UAEE,CAAA,gBACA","file":"example.min.css"} \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/example.scss b/tcwg-gen-ci-status-dashboard/sorting-table-css/example.scss
new file mode 100644
index 0000000..2dfb47e
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/example.scss
@@ -0,0 +1,42 @@
+@import 'sortable.scss';
+body {
+ font-size: 14px;
+}
+
+p {
+ line-height: 1.7em;
+}
+
+code {
+ font-family: monospace;
+ background: #eee;
+ padding: 5px;
+ border-radius: 2px;
+}
+
+* {
+ box-sizing: border-box;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
+ 'Droid Sans', 'Helvetica Neue', sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+.sortable:nth-of-type(4) th:nth-child(7),
+th.no-sort {
+ background: pink;
+ color: red;
+ pointer-events: none;
+}
+.sortable:nth-of-type(4) th:nth-child(7)::after {
+ color: red;
+ content: '(also not sortable)';
+ font-size: 0.9em;
+ display: block;
+}
+.lefty {
+ td:nth-child(2),
+ th:nth-child(2) {
+ width: 80px;
+ text-align: right;
+ }
+}
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.css b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.css
new file mode 100644
index 0000000..39d5078
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.css
@@ -0,0 +1,45 @@
+@charset "UTF-8";
+.sortable th {
+ cursor: pointer;
+}
+.sortable th.no-sort {
+ pointer-events: none;
+}
+.sortable th::after, .sortable th::before {
+ transition: color 0.1s ease-in-out;
+ font-size: 1.2em;
+ color: transparent;
+}
+.sortable th::after {
+ margin-left: 3px;
+ content: "â–¸";
+}
+.sortable th:hover::after {
+ color: inherit;
+}
+.sortable th.dir-d::after {
+ color: inherit;
+ content: "â–¾";
+}
+.sortable th.dir-u::after {
+ color: inherit;
+ content: "â–´";
+}
+.sortable th.indicator-left::after {
+ content: "";
+}
+.sortable th.indicator-left::before {
+ margin-right: 3px;
+ content: "â–¸";
+}
+.sortable th.indicator-left:hover::before {
+ color: inherit;
+}
+.sortable th.indicator-left.dir-d::before {
+ color: inherit;
+ content: "â–¾";
+}
+.sortable th.indicator-left.dir-u::before {
+ color: inherit;
+ content: "â–´";
+}/*# sourceMappingURL=sortable-base.css.map */ \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.css.map b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.css.map
new file mode 100644
index 0000000..d6b8cdc
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["sortable-base.css","sortable-base.scss"],"names":[],"mappings":"AAAA,gBAAgB;ACCd;EACE,eAAA;ADCJ;ACCI;EACE,oBAAA;ADCN;ACCI;EAEE,kCAAA;EACA,gBAAA;EACA,kBAAA;ADAN;ACGI;EACE,gBAAA;EACA,YAAA;ADDN;ACIM;EACE,cAAA;ADFR;ACOM;EACE,cAAA;EACA,YAAA;ADLR;ACUM;EACE,cAAA;EACA,YAAA;ADRR;ACYM;EACE,WAAA;ADVR;ACYM;EACE,iBAAA;EACA,YAAA;ADVR;ACcQ;EACE,cAAA;ADZV;ACiBQ;EACE,cAAA;EACA,YAAA;ADfV;ACoBQ;EACE,cAAA;EACA,YAAA;ADlBV","file":"sortable-base.css"} \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.min.css b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.min.css
new file mode 100644
index 0000000..3a2dde2
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.min.css
@@ -0,0 +1 @@
+.sortable th{cursor:pointer}.sortable th.no-sort{pointer-events:none}.sortable th::after,.sortable th::before{transition:color .1s ease-in-out;font-size:1.2em;color:rgba(0,0,0,0)}.sortable th::after{margin-left:3px;content:"â–¸"}.sortable th:hover::after{color:inherit}.sortable th.dir-d::after{color:inherit;content:"â–¾"}.sortable th.dir-u::after{color:inherit;content:"â–´"}.sortable th.indicator-left::after{content:""}.sortable th.indicator-left::before{margin-right:3px;content:"â–¸"}.sortable th.indicator-left:hover::before{color:inherit}.sortable th.indicator-left.dir-d::before{color:inherit;content:"â–¾"}.sortable th.indicator-left.dir-u::before{color:inherit;content:"â–´"}/*# sourceMappingURL=sortable-base.min.css.map */ \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.min.css.map b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.min.css.map
new file mode 100644
index 0000000..df0a5ba
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.min.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["sortable-base.min.css","sortable-base.scss"],"names":[],"mappings":"AAAA,aCCE,cACE,CAAA,qBAEA,mBACE,CAAA,yCAEF,gCAEE,CAAA,eACA,CAAA,mBACA,CAAA,oBAGF,eACE,CAAA,WACA,CAAA,0BAGA,aACE,CAAA,0BAKF,aACE,CAAA,WACA,CAAA,0BAKF,aACE,CAAA,WACA,CAAA,mCAIF,UACE,CAAA,oCAEF,gBACE,CAAA,WACA,CAAA,0CAIA,aACE,CAAA,0CAKF,aACE,CAAA,WACA,CAAA,0CAKF,aACE,CAAA,WACA","file":"sortable-base.min.css"} \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.scss b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.scss
new file mode 100644
index 0000000..ee059b5
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable-base.scss
@@ -0,0 +1,68 @@
+.sortable {
+ th {
+ cursor: pointer;
+
+ &.no-sort {
+ pointer-events: none;
+ }
+ &::after,
+ &::before {
+ transition: color 0.1s ease-in-out;
+ font-size: 1.2em;
+ color: transparent;
+ }
+
+ &::after {
+ margin-left: 3px;
+ content: '\025B8';
+ }
+ &:hover {
+ &::after {
+ color: inherit;
+ }
+ }
+
+ &.dir-d {
+ &::after {
+ color: inherit;
+ content: '\025BE';
+ }
+ }
+
+ &.dir-u {
+ &::after {
+ color: inherit;
+ content: '\025B4';
+ }
+ }
+ &.indicator-left {
+ &::after {
+ content: '';
+ }
+ &::before {
+ margin-right: 3px;
+ content: '\025B8';
+ }
+
+ &:hover {
+ &::before {
+ color: inherit;
+ }
+ }
+
+ &.dir-d {
+ &::before {
+ color: inherit;
+ content: '\025BE';
+ }
+ }
+
+ &.dir-u {
+ &::before {
+ color: inherit;
+ content: '\025B4';
+ }
+ }
+ }
+ }
+}
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.css b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.css
new file mode 100644
index 0000000..e6d321a
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.css
@@ -0,0 +1,82 @@
+@charset "UTF-8";
+.sortable th {
+ cursor: pointer;
+}
+.sortable th.no-sort {
+ pointer-events: none;
+}
+.sortable th::after, .sortable th::before {
+ transition: color 0.1s ease-in-out;
+ font-size: 1.2em;
+ color: transparent;
+}
+.sortable th::after {
+ margin-left: 3px;
+ content: "â–¸";
+}
+.sortable th:hover::after {
+ color: inherit;
+}
+.sortable th.dir-d::after {
+ color: inherit;
+ content: "â–¾";
+}
+.sortable th.dir-u::after {
+ color: inherit;
+ content: "â–´";
+}
+.sortable th.indicator-left::after {
+ content: "";
+}
+.sortable th.indicator-left::before {
+ margin-right: 3px;
+ content: "â–¸";
+}
+.sortable th.indicator-left:hover::before {
+ color: inherit;
+}
+.sortable th.indicator-left.dir-d::before {
+ color: inherit;
+ content: "â–¾";
+}
+.sortable th.indicator-left.dir-u::before {
+ color: inherit;
+ content: "â–´";
+}
+
+.sortable {
+ --stripe-color: #e4e4e4;
+ --th-color: #fff;
+ --th-bg: #808080;
+ --td-color: #000;
+ --td-on-stripe-color: #000;
+ border-spacing: 0;
+}
+.sortable tbody tr:nth-child(odd) {
+ background-color: var(--stripe-color);
+ color: var(--td-on-stripe-color);
+}
+.sortable th {
+ background: var(--th-bg);
+ color: var(--th-color);
+ font-weight: normal;
+ text-align: left;
+ text-transform: capitalize;
+ vertical-align: baseline;
+ white-space: nowrap;
+}
+.sortable td {
+ color: var(--td-color);
+}
+.sortable td,
+.sortable th {
+ padding: 10px;
+}
+.sortable td:first-child,
+.sortable th:first-child {
+ border-top-left-radius: 4px;
+}
+.sortable td:last-child,
+.sortable th:last-child {
+ border-top-right-radius: 4px;
+}/*# sourceMappingURL=sortable.css.map */ \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.css.map b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.css.map
new file mode 100644
index 0000000..4e0279f
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["sortable.css","sortable-base.scss","sortable.scss"],"names":[],"mappings":"AAAA,gBAAgB;ACCd;EACE,eAAA;ADCJ;ACCI;EACE,oBAAA;ADCN;ACCI;EAEE,kCAAA;EACA,gBAAA;EACA,kBAAA;ADAN;ACGI;EACE,gBAAA;EACA,YAAA;ADDN;ACIM;EACE,cAAA;ADFR;ACOM;EACE,cAAA;EACA,YAAA;ADLR;ACUM;EACE,cAAA;EACA,YAAA;ADRR;ACYM;EACE,WAAA;ADVR;ACYM;EACE,iBAAA;EACA,YAAA;ADVR;ACcQ;EACE,cAAA;ADZV;ACiBQ;EACE,cAAA;EACA,YAAA;ADfV;ACoBQ;EACE,cAAA;EACA,YAAA;ADlBV;;AE1CA;EACE,uBAAA;EACA,gBAAA;EACA,gBAAA;EACA,gBAAA;EACA,0BAAA;EAEA,iBAAA;AF4CF;AExCM;EACE,qCAAA;EACA,gCAAA;AF0CR;AEtCE;EACE,wBAAA;EACA,sBAAA;EACA,mBAAA;EACA,gBAAA;EACA,0BAAA;EACA,wBAAA;EACA,mBAAA;AFwCJ;AEtCE;EACE,sBAAA;AFwCJ;AEtCE;;EAEE,aAAA;AFwCJ;AEtCI;;EACE,2BAAA;AFyCN;AEtCI;;EACE,4BAAA;AFyCN","file":"sortable.css"} \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.js b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.js
new file mode 100755
index 0000000..4622eb6
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.js
@@ -0,0 +1,128 @@
+/**
+ * sortable 1.5.1 (or something, I always forget to update this)
+ *
+ * Makes html tables sortable, ie9+
+ *
+ * Styling is done in css.
+ *
+ * Copyleft 2017 Jonas Earendel
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * For more information, please refer to <http://unlicense.org>
+ *
+ */
+
+// sort is super fast, even with huge tables, so that is probably not the issue
+// Not solved with documentFragment, same issue... :(
+// My guess is that it is simply too much to hold in memory, since
+// it freezes even before sortable is called if the table is too big in index.html
+
+document.addEventListener('click', function (e) {
+ try {
+ // allows for elements inside TH
+ function findElementRecursive(element, tag) {
+ return element.nodeName === tag ? element : findElementRecursive(element.parentNode, tag)
+ }
+
+ var descending_th_class = ' dir-d '
+ var ascending_th_class = ' dir-u '
+ var ascending_table_sort_class = 'asc'
+ var regex_dir = / dir-(u|d) /
+ var regex_table = /\bsortable\b/
+ var alt_sort = e.shiftKey || e.altKey
+ var element = findElementRecursive(e.target, 'TH')
+ var tr = findElementRecursive(element, 'TR')
+ var table = findElementRecursive(tr, 'TABLE')
+
+ function reClassify(element, dir) {
+ element.className = element.className.replace(regex_dir, '') + dir
+ }
+
+ function getValue(element) {
+ // If you aren't using data-sort and want to make it just the tiniest bit smaller/faster
+ // comment this line and uncomment the next one
+ var value =
+ (alt_sort && element.getAttribute('data-sort-alt')) || element.getAttribute('data-sort') || element.innerText
+ return value
+ // return element.innerText
+ }
+ if (regex_table.test(table.className)) {
+ var column_index
+ var nodes = tr.cells
+
+ // Reset thead cells and get column index
+ for (var i = 0; i < nodes.length; i++) {
+ if (nodes[i] === element) {
+ column_index = element.getAttribute('data-sort-col') || i
+ } else {
+ reClassify(nodes[i], '')
+ }
+ }
+
+ var dir = descending_th_class
+
+ // Check if we're sorting ascending or descending
+ if (
+ element.className.indexOf(descending_th_class) !== -1 ||
+ (table.className.indexOf(ascending_table_sort_class) !== -1 &&
+ element.className.indexOf(ascending_th_class) == -1)
+ ) {
+ dir = ascending_th_class
+ }
+
+ // Update the `th` class accordingly
+ reClassify(element, dir)
+
+ // Extract all table rows
+ var org_tbody = table.tBodies[0]
+
+ // Get the array rows in an array, so we can sort them...
+ var rows = [].slice.call(org_tbody.rows, 0)
+
+ var reverse = dir === ascending_th_class
+
+ // Sort them using Array.prototype.sort()
+ rows.sort(function (a, b) {
+ var x = getValue((reverse ? a : b).cells[column_index])
+ var y = getValue((reverse ? b : a).cells[column_index])
+ var bool = x.length && y.length && !isNaN(x - y) ? x - y : x.localeCompare(y)
+ return bool
+ })
+
+ // Make a clone without content
+ var clone_tbody = org_tbody.cloneNode()
+
+ // Fill it with the sorted values
+ while (rows.length) {
+ clone_tbody.appendChild(rows.splice(0, 1)[0])
+ }
+
+ // And finally replace the unsorted table with the sorted one
+ table.replaceChild(clone_tbody, org_tbody)
+ }
+ } catch (error) {
+ // console.log(error)
+ }
+})
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.css b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.css
new file mode 100644
index 0000000..59abc45
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.css
@@ -0,0 +1 @@
+.sortable th{cursor:pointer}.sortable th.no-sort{pointer-events:none}.sortable th::after,.sortable th::before{transition:color .1s ease-in-out;font-size:1.2em;color:rgba(0,0,0,0)}.sortable th::after{margin-left:3px;content:"â–¸"}.sortable th:hover::after{color:inherit}.sortable th.dir-d::after{color:inherit;content:"â–¾"}.sortable th.dir-u::after{color:inherit;content:"â–´"}.sortable th.indicator-left::after{content:""}.sortable th.indicator-left::before{margin-right:3px;content:"â–¸"}.sortable th.indicator-left:hover::before{color:inherit}.sortable th.indicator-left.dir-d::before{color:inherit;content:"â–¾"}.sortable th.indicator-left.dir-u::before{color:inherit;content:"â–´"}.sortable{--stripe-color: #e4e4e4;--th-color: #fff;--th-bg: #808080;--td-color: #000;--td-on-stripe-color: #000;border-spacing:0}.sortable tbody tr:nth-child(odd){background-color:var(--stripe-color);color:var(--td-on-stripe-color)}.sortable th{background:var(--th-bg);color:var(--th-color);font-weight:normal;text-align:left;text-transform:capitalize;vertical-align:baseline;white-space:nowrap}.sortable td{color:var(--td-color)}.sortable td,.sortable th{padding:10px}.sortable td:first-child,.sortable th:first-child{border-top-left-radius:4px}.sortable td:last-child,.sortable th:last-child{border-top-right-radius:4px}/*# sourceMappingURL=sortable.min.css.map */ \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.css.map b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.css.map
new file mode 100644
index 0000000..ec7420b
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["sortable.min.css","sortable-base.scss","sortable.scss"],"names":[],"mappings":"AAAA,aCCE,cACE,CAAA,qBAEA,mBACE,CAAA,yCAEF,gCAEE,CAAA,eACA,CAAA,mBACA,CAAA,oBAGF,eACE,CAAA,WACA,CAAA,0BAGA,aACE,CAAA,0BAKF,aACE,CAAA,WACA,CAAA,0BAKF,aACE,CAAA,WACA,CAAA,mCAIF,UACE,CAAA,oCAEF,gBACE,CAAA,WACA,CAAA,0CAIA,aACE,CAAA,0CAKF,aACE,CAAA,WACA,CAAA,0CAKF,aACE,CAAA,WACA,CAAA,UC5DV,uBACE,CAAA,gBACA,CAAA,gBACA,CAAA,gBACA,CAAA,0BACA,CAAA,gBAEA,CAAA,kCAII,oCACE,CAAA,+BACA,CAAA,aAIN,uBACE,CAAA,qBACA,CAAA,kBACA,CAAA,eACA,CAAA,yBACA,CAAA,uBACA,CAAA,kBACA,CAAA,aAEF,qBACE,CAAA,0BAEF,YAEE,CAAA,kDAEA,0BACE,CAAA,gDAGF,2BACE","file":"sortable.min.css"} \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.js b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.js
new file mode 100755
index 0000000..629fa50
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.min.js
@@ -0,0 +1,2 @@
+document.addEventListener("click",function(b){try{var p=function(a){return v&&a.getAttribute("data-sort-alt")||a.getAttribute("data-sort")||a.innerText},q=function(a,c){a.className=a.className.replace(w,"")+c},f=function(a,c){return a.nodeName===c?a:f(a.parentNode,c)},w=/ dir-(u|d) /,v=b.shiftKey||b.altKey,e=f(b.target,"TH"),r=f(e,"TR"),g=f(r,"TABLE");if(/\bsortable\b/.test(g.className)){var l,d=r.cells;for(b=0;b<d.length;b++)d[b]===e?l=e.getAttribute("data-sort-col")||b:q(d[b],"");d=" dir-d ";if(-1!==
+e.className.indexOf(" dir-d ")||-1!==g.className.indexOf("asc")&&-1==e.className.indexOf(" dir-u "))d=" dir-u ";q(e,d);var m=g.tBodies[0],n=[].slice.call(m.rows,0),t=" dir-u "===d;n.sort(function(a,c){var h=p((t?a:c).cells[l]),k=p((t?c:a).cells[l]);return h.length&&k.length&&!isNaN(h-k)?h-k:h.localeCompare(k)});for(var u=m.cloneNode();n.length;)u.appendChild(n.splice(0,1)[0]);g.replaceChild(u,m)}}catch(a){}}); \ No newline at end of file
diff --git a/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.scss b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.scss
new file mode 100755
index 0000000..6996535
--- /dev/null
+++ b/tcwg-gen-ci-status-dashboard/sorting-table-css/sortable.scss
@@ -0,0 +1,44 @@
+@import 'sortable-base.scss';
+
+.sortable {
+ --stripe-color: #e4e4e4;
+ --th-color: #fff;
+ --th-bg: #808080;
+ --td-color: #000;
+ --td-on-stripe-color: #000;
+
+ border-spacing: 0;
+
+ tbody {
+ tr {
+ &:nth-child(odd) {
+ background-color: var(--stripe-color);
+ color: var(--td-on-stripe-color);
+ }
+ }
+ }
+ th {
+ background: var(--th-bg);
+ color: var(--th-color);
+ font-weight: normal;
+ text-align: left;
+ text-transform: capitalize;
+ vertical-align: baseline;
+ white-space: nowrap;
+ }
+ td {
+ color: var(--td-color);
+ }
+ td,
+ th {
+ padding: 10px;
+
+ &:first-child {
+ border-top-left-radius: 4px;
+ }
+
+ &:last-child {
+ border-top-right-radius: 4px;
+ }
+ }
+}