From bec2896e64eb0ecd0add7134ff91e0a357575b76 Mon Sep 17 00:00:00 2001 From: Hideyuki Oide <Hideyuki.Oide@cern.ch> Date: Wed, 20 Jul 2022 19:29:40 +0900 Subject: [PATCH 1/7] listing PCBs and bare modules --- viewer/functions/common.py | 81 ++++++++ viewer/pages/#tag.py# | 126 ++++++++++++ viewer/pages/component.py | 5 +- viewer/pages/tag.py | 6 + viewer/pages/toppage.py | 258 ++++++++++++++++++++----- viewer/templates/500.html | 12 +- viewer/templates/components_table.html | 103 ++++------ viewer/templates/parts/nav.html | 10 +- viewer/templates/scan_table.html | 2 +- viewer/templates/toppage.html | 7 +- 10 files changed, 493 insertions(+), 117 deletions(-) create mode 100755 viewer/pages/#tag.py# diff --git a/viewer/functions/common.py b/viewer/functions/common.py index 76d3a969..321ab4ef 100755 --- a/viewer/functions/common.py +++ b/viewer/functions/common.py @@ -613,3 +613,84 @@ def delim_SN( sn ): return ' '.join( [ sn[0:3], sn[3:7], sn[7:9], sn[9:] ] ) else: return sn + + +def SN_typeinfo( sn ): + if sn.find( '20U' ) == 0: + + if sn[:7] == '20UPGFC': + try: + hexName = "{0:#0{1}x}".format(int( name[-7:] ),7) + except: + pass + + batchMap = { '0':'RD53A', '1':'ITkPix' } + + typeinfo = 'Undef' + try: + typeinfo = batchMap[hexName[2]] + except: + pass + + wafer= '-{}-({},{})'.format( hexName[3:4], hexName[5], hexName[6] ) + + typeinfo += wafer + + + elif sn.find( '20UPGXF' ) == 0: + typeinfo = 'Tutorial-FE' + elif sn.find( '20UPGXB' ) == 0: + typeinfo = 'Tutorial-BareModule' + elif sn.find( '20UPGXP' ) == 0: + typeinfo = 'Tutorial-PCB' + elif sn.find( '20UPGPQ' ) == 0: + typeinfo = 'QuadPCB' + elif sn.find( '20UPGPD' ) == 0: + typeinfo = 'DualPCB' + elif sn.find( '20UPIPT' ) == 0: + typeinfo = 'TripletL0StavePCB' + elif sn.find( '20UPIP0' ) == 0: + typeinfo = 'TripletL0R0PCB' + elif sn.find( '20UPIP5' ) == 0: + typeinfo = 'TripletL0R0.5PCB' + elif sn.find( '20UPGB1' ) == 0: + typeinfo = 'Single-BareModule' + elif sn.find( '20UPGB2' ) == 0: + typeinfo = 'Dual-BareModule' + elif sn.find( '20UPGB4' ) == 0: + typeinfo = 'Quad-BareModule' + elif sn.find( '20UPGBS' ) == 0: + typeinfo = 'Digital-Single-BareModule' + elif sn.find( '20UPGBQ' ) == 0: + typeinfo = 'Digital-Quad-BareModule' + elif 'PI' in sn: + typeinfo = "L0/L1 Inner Module" + elif 'PB' in sn: + typeinfo = "Outer Barrel Module" + elif 'PE' in sn: + typeinfo = "Outer Endcap Module" + elif 'PGM2' in sn: + typeinfo = "Outer Quad Module" + elif 'PGR2' in sn: + typeinfo = "Dual Chip Module" + elif 'PGR0' in sn: + typeinfo = "Single Chip Module" + elif 'XM' in sn: + typeinfo = "Tutorial Module" + else: + typeinfo = "Others" + + if 'Module' in typeinfo: + if '0' == sn[7]: + typeinfo+= ' / RD53A' + elif '1' == sn[7]: + typeinfo+=' / ITkPix_v1.0' + elif '2' == sn[7]: + typeinfo+=' / ITkPix_v1.1' + elif '3' == sn[7]: + typeinfo+=' / ITkPix_v2' + + return typeinfo + else: + return "" + diff --git a/viewer/pages/#tag.py# b/viewer/pages/#tag.py# new file mode 100755 index 00000000..b5e92050 --- /dev/null +++ b/viewer/pages/#tag.py# @@ -0,0 +1,126 @@ +from functions.imports import * + +tag_api = Blueprint("tag_api", __name__) + +@tag_api.route("/create_tag", methods=["GET", "POST"]) +def create_tag(): + stage = request.form.get("stage", "input") + + if not session.get("logged_in", False): + return render_template( "401.html" ) + + taginfo = request.form.getlist("taginfo") + if taginfo == []: + session.pop("tag", None) + + if session.get("tag", None): + query = {"name": taginfo[0]} + thistime = datetime.utcnow() + tags = userdb.viewer.tag.categories.find(query) + + if not tags.count() == 0: + text = "This tag is already in use, please input an alternative." + stage = "input" + return render_template( + "create_tag.html", + tagInfo=taginfo, + nametext=text, + stage=stage, + timezones=pytz.all_timezones, + ) + if stage == "input": + return render_template( + "create_tag.html", + tagInfo=taginfo, + stage=stage, + timezones=pytz.all_timezones, + ) + elif stage == "confirm": + return render_template( + "create_tag.html", + tagInfo=taginfo, + stage=stage, + timezones=pytz.all_timezones, + ) + else: + userdb.viewer.tag.categories.insert_one( + { + "sys": {"rev": 0, "cts": thistime, "mts": thistime}, + "name": taginfo[0], + "user_id": session["user_id"], + } + ) + + session.pop("tag") + return render_template( + "create_tag.html", + tagInfo=taginfo, + stage=stage, + timezones=pytz.all_timezones, + ) + + taginfo = [""] + pre_url = request.headers.get("Referer") + session["pre_url"] = pre_url + + session["tag"] = True + + return render_template( + "create_tag.html", tagInfo=taginfo, stage=stage, timezones=pytz.all_timezones + ) + +@tag_api.route("/put_tag", methods=["GET", "POST"]) +def put_tag(): + thistime = datetime.utcnow() + # tag_name = request.args.get('tag_name') + tag_name = request.form.get("tag_name") + tagdocs = { + "sys": {"rev": 0, "cts": thistime, "mts": thistime}, + "name": tag_name, + "user_id": session["user_id"], + } + componentid = request.args.get("componentid", -1) + runId = request.args.get("runId", -1) + + if not componentid == -1: + query = {"name": tag_name, "componentid": componentid} + tags = userdb.viewer.tag.docs.find(query) + if tags.count() == 0: + componentid = { + "componentid": componentid, + } + tagdocs.update(componentid) + userdb.viewer.tag.docs.insert(tagdocs) + else: + userdb.viewer.tag.docs.remove(query) + + if not runId == -1: + query = {"name": tag_name, "runId": runId} + tags = userdb.viewer.tag.docs.find(query) + if tags.count() == 0: + query_doc = userdb.viewer.query.find_one({"runId": runId}) + new_list = query_doc["data"] + new_list.append(str(tag_name)) + userdb.viewer.query.update({"runId": runId}, {"$set": {"data": new_list}}) + runId = { + "runId": runId, + } + tagdocs.update(runId) + userdb.viewer.tag.docs.insert(tagdocs) + else: + query_doc = userdb.viewer.query.find_one({"runId": runId}) + new_list = query_doc["data"] + new_list.remove(str(tag_name)) + userdb.viewer.query.update({"runId": runId}, {"$set": {"data": new_list}}) + userdb.viewer.tag.docs.remove(query) + + return redirect(request.headers.get("Referer")) + + + +@tag_api.route("/archive", methods=["GET", "POST"]) +def archive(): + return render_template( + "toppage.html", tagInfo=taginfo, stage=stage, timezones=pytz.all_timezones + ) + diff --git a/viewer/pages/component.py b/viewer/pages/component.py index a04b1a8d..9464c629 100755 --- a/viewer/pages/component.py +++ b/viewer/pages/component.py @@ -871,7 +871,10 @@ def setComponentInfo(i_oid, i_col): ### component query = {"_id": ObjectId(i_oid)} this = localdb[i_col].find_one(query) - qc_doc = localdb.QC.module.status.find_one( { "component": i_oid,"proddbVersion":proddbv } ) + qc_doc = localdb.QC.module.status.find_one( { "component": i_oid } ) + logger.info( '{}'.format( { "component": i_oid } ) ) + logger.info( '{}'.format( qc_doc ) ) + if qc_doc == None: qc_doc = {} session["unit"] = this["componentType"].lower() ### TODO wakarinikui docs = { diff --git a/viewer/pages/tag.py b/viewer/pages/tag.py index 7bc82c4e..a0346ce2 100755 --- a/viewer/pages/tag.py +++ b/viewer/pages/tag.py @@ -115,3 +115,9 @@ def put_tag(): userdb.viewer.tag.docs.remove(query) return redirect(request.headers.get("Referer")) + + + +@tag_api.route("/archive", methods=["GET", "POST"]) +def archive(): + return diff --git a/viewer/pages/toppage.py b/viewer/pages/toppage.py index 247be723..d211b3bb 100755 --- a/viewer/pages/toppage.py +++ b/viewer/pages/toppage.py @@ -64,7 +64,7 @@ def show_toppage(): def show_comps(): initPage() - table_docs = {"components": { "modules":[] } } + table_docs = {"components": [] } # get keywords if not request.form.get("keywords") == None: @@ -74,7 +74,14 @@ def show_comps(): table_docs["keywords"] = request.args.get("keywords", "") table_docs["match"] = request.args.get("match", "None") - table_docs['view'] = request.args.get("componentType", "module") + table_docs['view'] = request.args.get("view", None) + + if table_docs['view'] == None: + table_docs['view'] = request.form.get("view", "module") + + view = table_docs['view'] + + logger.debug('show_comps(): view = {}'.format( view ) ) compsPerPage = 10 @@ -82,24 +89,165 @@ def show_comps(): if not request.args.get("p", 1) == "": page = int(request.args.get("p", 1)) - query = {"dbVersion": dbv} - entries = localdb.component.find(query) + components = localdb.component.find( {"dbVersion": dbv, "componentType":view } ) cmp_ids = [] - for entry in entries: - cmp_ids.append(str(entry["_id"])) + for component in components: + + # logger.info( 'component: {}'.format( component ) ) + + cmp_id = str(component["_id"]) + + cmp_ids.append( cmp_id ) + + + cps = [] + + cprs1 = localdb.childParentRelation.find( { "parent":cmp_id } ) + cprs2 = localdb.childParentRelation.find( { "child":cmp_id } ) + + for cpr in cprs1: + + child = localdb.component.find_one( { "_id":ObjectId( cpr['child'] ) } ) + + # logger.info( 'child = {}'.format( child ) ) + + child_name = child['name'] + + typeinfo = SN_typeinfo( child_name ) + + cps.append( + {"_id": str( child["_id"] ), + "collection": "component", + "name": delim_SN( child['name'] ), + "name_orig": child['name'], + "grade": {}, + "hex" : hexName if child_name.find('20UPGFC')==0 else '', + "typeinfo" : typeinfo + } ) + + # endfor + + for cpr in cprs2: + + parent = localdb.component.find_one( { "_id":ObjectId( cpr['parent'] ) } ) + + # logger.info( 'parent = {}'.format( parent ) ) + + parent_name = parent['name'] + + typeinfo = SN_typeinfo( parent_name ) + + cps.append( + {"_id": str( parent["_id"] ), + "collection": "component", + "name": delim_SN( parent['name'] ), + "name_orig": parent['name'], + "grade": {}, + "hex" : hexName if parent_name.find('20UPGFC')==0 else '', + "typeinfo" : typeinfo + } ) + + # endfor + + name = component['name'] + + typeinfo = SN_typeinfo( name ) + + qcStatus = localdb.QC.module.status.find_one( {"component": cmp_id} ) + + result = localdb.QC.result.find_one( {"component": cmp_id} ) + ### tags + query = {} + module_tag_candidates = userdb.viewer.tag.categories.find(query) + tag_candidate = [] + for module_tag in module_tag_candidates: + tag_candidate.append(module_tag) + ### put tags + query = {"componentid": cmp_id} + module_tags = userdb.viewer.tag.docs.find(query) + tag = [] + for module_tag in module_tags: + tag.append(module_tag) + + component_data = { + "_id": cmp_id, + "collection": "component", + "name": delim_SN( name ), + "name_orig": name, + "typeinfo": typeinfo, + "cps": cps, + "grade": {}, + "proDB": component.get("proDB", False), + "component_tag_candidate": tag_candidate, + "component_tag": tag, + } + + if qcStatus: + component_data.update( { "stage":qcStatus["currentStage"] } ) + + if result: + component_data.update( { + "runId": str( result["_id"] ), + "datetime": setTime( result["sys"]["cts"], session.get("timezone", str(get_localzone()) ) ), + "testType": result["testType"], + "user": result["user"], + "site": result["address"] } ) + + logger.debug( 'data: {}'.format( component_data ) ) + + # component search field + search_targets = [] + search_targets.append(component_data["name_orig"]) + if 'stage' in component_data: search_targets.append(component_data["stage"]) + if 'user' in component_data: search_targets.append(component_data["user"]) + if 'site' in component_data: search_targets.append(component_data["site"]) + if 'datetime' in component_data: search_targets.append(component_data["datetime"]) + + for tag in component_data["component_tag"]: + search_targets.append(tag["name"]) + + for subcomp in component_data["cps"]: + search_targets.append(subcomp["name_orig"]) + + isKeywordMatched = query_keywords( + table_docs["keywords"], search_targets, table_docs["match"] + ) + + if isKeywordMatched: + table_docs["components"].append( component_data ) + + + #end for component in components: + pageMinIndex = compsPerPage * (page-1) pageMaxIndex = compsPerPage * page - table_docs["components"].update( - { - "modules": [], - "module_num": 0, - "chips": [], - "chip_num": 0 - } + nModulesTotal = len( table_docs['components'] ) + maxPage = len( table_docs['components'] ) // compsPerPage + 1 + minRange = max(1, page - 2) + maxRange = min(maxPage, minRange+3) + pageRange = list( range( minRange, maxRange+1 ) ) + + pagingInfo = { + "thisPage": page, + "range" : pageRange, + "minPage": 1, + "maxPage": maxPage, + "entries" : nModulesTotal + } + + table_docs.update( { 'pagingInfo':pagingInfo } ) + table_docs["page"] = "components" + table_docs["title"] = "Component List" + table_docs["download_module"] = os.path.exists(IF_DIR+"/doing_download.txt") + + return render_template( + "toppage.html", table_docs=table_docs, timezones=pytz.all_timezones ) + #######---------------------------------------------------------------------------------------------- + # Filter modules index = 0 for cmp_id in cmp_ids: @@ -112,20 +260,14 @@ def show_comps(): query = {"parent": cmp_id} else: continue - ''' - elif this_cmp.get("componentType", "front-end_chip").lower() == "front-end_chip" : - key = "chips" - id_key = "parent" - query = {"child": cmp_id} - ''' index += 1 ### cpr entries - entries = localdb.childParentRelation.find(query) + components = localdb.childParentRelation.find(query) cpr_ids = [] - for entry in entries: - cpr_ids.append(entry[id_key]) + for component in components: + cpr_ids.append(component[id_key]) cps = [] for cpr_id in cpr_ids: query = {"_id": ObjectId(cpr_id)} @@ -137,7 +279,7 @@ def show_comps(): name = this_cp["serialNumber"] hexName = '' - if name.find('20UP') == 0: + if name.find('20U') == 0: try: hexName = "{0:#0{1}x}".format(int( name[-7:] ),7) except: @@ -145,41 +287,70 @@ def show_comps(): batchMap = { '0':'RD53A', '1':'ITkPix' } - fetype = 'Undef' + typeinfo = 'Undef' try: - fetype = batchMap[hexName[2]] + typeinfo = batchMap[hexName[2]] except: pass + if 'PI' in name: + typeinfo = "L0/L1 Inner Module" + elif 'PB' in name: + typeinfo = "Outer Barrel Module" + elif 'PE' in name: + typeinfo = "Outer Endcap Module" + elif 'PGM2' in name: + typeinfo = "Outer Quad Module" + elif 'PGR2' in name: + typeinfo = "Dual Chip Module" + elif 'PGR-' in name: + typeinfo = "Single Chip Module" + elif 'XM' in name: + typeinfo = "Tutorial Module" + else: + typeinfo = "Others" + + + if '0' == name[7]: + typeinfo+= ' / RD53A' + elif '1' == name[7]: + typeinfo+=' / ITkPix_v1.0' + elif '2' == name[7]: + typeinfo+=' / ITkPix_v1.1' + elif '3' == name[7]: + typeinfo+=' / ITkPix_v2' + + wafer='' if name.find('20UPGFC') == 0: wafer= '-{}-({},{})'.format( hexName[3:4], hexName[5], hexName[6] ) - elif name.find( '20UPGXF' ) == 0: - fetype = 'Tutorial-FE' + + if name.find( '20UPGXF' ) == 0: + typeinfo = 'Tutorial-FE' elif name.find( '20UPGXB' ) == 0: - fetype = 'Tutorial-BareModule' + typeinfo = 'Tutorial-BareModule' elif name.find( '20UPGXP' ) == 0: - fetype = 'Tutorial-PCB' + typeinfo = 'Tutorial-PCB' elif name.find( '20UPGPQ' ) == 0: - fetype = 'QuadPCB' + typeinfo = 'QuadPCB' elif name.find( '20UPGPD' ) == 0: - fetype = 'DualPCB' + typeinfo = 'DualPCB' elif name.find( '20UPIPT' ) == 0: - fetype = 'TripletL0StavePCB' + typeinfo = 'TripletL0StavePCB' elif name.find( '20UPIP0' ) == 0: - fetype = 'TripletL0R0PCB' + typeinfo = 'TripletL0R0PCB' elif name.find( '20UPIP5' ) == 0: - fetype = 'TripletL0R0.5PCB' + typeinfo = 'TripletL0R0.5PCB' elif name.find( '20UPGB1' ) == 0: - fetype = 'Single-BareModule' + typeinfo = 'Single-BareModule' elif name.find( '20UPGB2' ) == 0: - fetype = 'Dual-BareModule' + typeinfo = 'Dual-BareModule' elif name.find( '20UPGB4' ) == 0: - fetype = 'Quad-BareModule' + typeinfo = 'Quad-BareModule' elif name.find( '20UPGBS' ) == 0: - fetype = 'Digital-Single-BareModule' + typeinfo = 'Digital-Single-BareModule' elif name.find( '20UPGBQ' ) == 0: - fetype = 'Digital-Quad-BareModule' + typeinfo = 'Digital-Quad-BareModule' cps.append( {"_id": cpr_id, @@ -188,8 +359,7 @@ def show_comps(): "name_orig": name, "grade": {}, "hex" : hexName if name.find('20UPGFC')==0 else '', - "fetype" : fetype, - "wafer" : wafer + "typeinfo" : typeinfo+wafer } ) @@ -711,10 +881,10 @@ def query_docs(Keywords, Match): # Here also inside the viewer.query so I need to update this entries = userdb.viewer.query.find({"$and": query_list}) - entries = sorted(entries, key=lambda x: x["timeStamp"], reverse=True) + components = sorted(entries, key=lambda x: x["timeStamp"], reverse=True) - for entry in entries: - run_ids.append(entry["runId"]) + for component in components: + run_ids.append(component["runId"]) return run_ids diff --git a/viewer/templates/500.html b/viewer/templates/500.html index 3f9a1358..7b40d371 100644 --- a/viewer/templates/500.html +++ b/viewer/templates/500.html @@ -9,16 +9,20 @@ </h1> <h3 style="padding-left:30px;"> - LocalDB scripts are currently unable to handle this request. <br> - Please check the error message below, and contact the developpers » <a href="https://mattermost.web.cern.ch/itkpixel/channels/local-database" target="_blank"><button type="button" class="btn btn-outline-dark">Mattermost</button></a></p><br> - </h3> - + LocalDB scripts are currently unable to handle this request. + </h3> + + <ul> + <li>Please check the error message below, and contact the developpers</li> + <li>Please report which LocalDB viewer version is deployed on your site.</li> + </ul> <div style="text-align: left; padding-left:3%; padding-right: 3%;"> <p style="background-color: #e6e6fa; white-space: pre-line; padding-left: 2%; padding-right: 2%;"> <font size="4" color="ff0000">Error message:</font> <font size="2" color="ff0000">{{ message }}</font> </p> </div> + <p> » <a href="https://mattermost.web.cern.ch/itkpixel/channels/local-database" target="_blank"><button type="button" class="btn btn-outline-dark">Mattermost</button></a></p> </div> </div> </div> diff --git a/viewer/templates/components_table.html b/viewer/templates/components_table.html index 0829ea6b..46f5a87d 100644 --- a/viewer/templates/components_table.html +++ b/viewer/templates/components_table.html @@ -58,16 +58,24 @@ {% set pagingInfo = table_docs['pagingInfo'] %} - {% if table_docs['view'] == 'module' %} - <h3>Modules <span style="font-size: 50%;">Found {{ pagingInfo['entries'] }} modules in total</span></h3> + <h3>{{ table_docs['view'].upper() }} <span style="font-size: 50%;">Found {{ pagingInfo['entries'] }} components in total</span></h3> + <form class="form-holizontal" role="form" method="post" action="{{ table_docs['page'] }}" accept-charset="UTF-8"> <div class="row align-items-top card-body" style="margin-top:0; padding-top:0; padding-bottom:0;"> - + <button type="submit" class="btn btn-secondary" style="padding-top: 0.05%; padding-bottom: 0.05%; font-size:90%;">Show All</button> <table class='table table-sm table-hover table-bordered' style='font-size: 10pt; margin-top:0;'> <thead class="table table-sm table-hover table-bordered"> <tr> - <th style="text-align:left;">Module</th> - <th style="word-wrap:break-word; text-align:left;">Sub-Components</th> + <th style="word-wrap:break-word; text-align:center;"> + <div class="balloonoya"> + <button class="btn btn-secondary" style="padding-top:0; padding-bottom:0; font-size: 90%;" id="archive">Archive</button> + <!-- span class="balloon"> + Click the checkbox and press this button to hide the module from the list. + </span--> + </div> + </th> + <th style="text-align:left;">{{ table_docs['view'].upper() }}</th> + <th style="word-wrap:break-word; text-align:left;">Relational Components</th> <th style="word-wrap:break-word; text-align:left;">Current Stage</th> <th style="word-wrap:break-word; text-align:left;">Last QC Test Type</th> <th style="word-wrap:break-word; text-align:left;">Date</th> @@ -87,92 +95,64 @@ </thead> <tbody align="left"> - {% for module in table_docs['components']['modules'] %} - {% set index = table_docs['components']['modules'].index( module ) %} + {% for component in table_docs['components'] %} + {% set index = table_docs['components'].index( component ) %} <tr> + <td style="text-align:center;"><input type="checkbox" id="check_{{ component['name'] }}" value="false"></td> <td style="word-wrap:break-word; text-align:left;"> - {% if module['name'] != '' %} - <a href='{{ url_for('component_api.show_component', id=module['_id'], collection=module['collection']) }}'>{{ module['name'] }}</a> - {% if module['name'].find( '20U' ) != 0 %} + {% if component['name_delim'] != '' %} + <a href='{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection']) }}'>{{ component['name'] }}</a> + {% if component['name'].find( '20U' ) != 0 %} <strong style="color: red;"><br />Not ATLAS SN!</strong> {% else %} - {# Module SN digestion #} - <span style='color: #ccc;'> - {% if 'PI' in module['name'] %} - <br />L0/L1 Inner Module - {% elif 'PB' in module['name'] %} - <br />Outer Barrel Module - {% elif 'PE' in module['name'] %} - <br />Outer Endcap Module - {% elif 'PGM2' in module['name'] %} - <br />Outer Quad Module - {% elif 'PGR2' in module['name'] %} - <br />Dual Chip Module - {% elif 'PGR-' in module['name'] %} - <br />Single Chip Module - {% elif 'XM' in module['name'] %} - <br />Tutorial Module - {% else %} - <br />Others - {% endif %} - - {% if '0' == module['name'][9] %} - / RD53A - {% elif '1' == module['name'][9] %} - / ITkPix_v1.0 - {% elif '2' == module['name'][9] %} - / ITkPix_v1.1 - {% elif '3' == module['name'][9] %} - / ITkPix_v2 - {% endif %} - - </span> + {# Component SN digestion #} + <br /><span style='color: #ccc;'>{{ component['typeinfo'] }}</span> {% endif %} {% else %} - <a href='{{ url_for('component_api.show_component', id=module['_id'], collection=module['collection']) }}'><strong style="color: red;">Missing Module Name!</strong></a> + <a href='{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection']) }}'><strong style="color: red;">Missing Module Name!</strong></a> {% endif %} </td> <td style="word-wrap:break-word; text-align:left;"> - {% for chip in module['cps'] %} - <a href='{{ url_for('component_api.show_component', id=chip['_id'], collection=chip['collection']) }}' >{{ chip['name'] }}</a> - <span style="color:#ccc;">{{ chip['fetype'] }}{{chip['wafer']}}</span> - {% if chip['hex'] != '' %} + {% for subcomponent in component['cps'] %} + <a href='{{ url_for('component_api.show_component', id=subcomponent['_id'], collection=subcomponent['collection']) }}' >{{ subcomponent['name'] }}</a> + <span style="color:#ccc;">{{ subcomponent['typeinfo'] }}</span> + {% if subcomponent['hex'] != '' %} {% endif %} <br /> {% endfor %} </td> - <td style="word-wrap:break-word; text-align:left;">{{ module['stage'] }}</td> + <td style="word-wrap:break-word; text-align:left;">{{ component['stage'] }}</td> <td style="word-wrap:break-word; text-align:left;"> - {% if module['runId'] %} - <a href='{{ url_for('component_api.show_component', id=module['_id'], collection=module['collection'], runId=module['runId']) }}'> - {{ module['testType'] }} + {% if component['runId'] %} + <a href='{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection'], runId=component['runId']) }}'> + {{ component['testType'] }} </a> {% else %} - {{ module['testType'] }} + {{ component['testType'] }} {% endif %} </td> - <!--td style="word-wrap:break-word; text-align:left;">{{ module['user'] }}</td> - <td style="word-wrap:break-word; text-align:left;">{{ module['site'] }}</td--> - <td style="word-wrap:break-word; text-align:left;">{{ module['datetime'] }}</td> + <!--td style="word-wrap:break-word; text-align:left;">{{ component['user'] }}</td> + <td style="word-wrap:break-word; text-align:left;">{{ component['site'] }}</td--> + <td style="word-wrap:break-word; text-align:left;">{{ component['datetime'] }}</td> <!--td style="word-wrap:break-word; text-align:left;"> - {% if module['runId'] %} - <a href='{{ url_for('component_api.show_component', id=module['_id'], collection=module['collection'], runId=module['runId']) }}'>result page</a> + {% if component['runId'] %} + <a href='{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection'], runId=component['runId']) }}'>result page</a> {% endif %} </td--> <td style="word-wrap:break-word; text-align:left;"> {% if session['logged_in'] %} - <form method="post" action="{{ url_for('tag_api.put_tag', componentid=module["_id"]) }}" style="margin-bottom: 0%;"> + <form method="post" action="{{ url_for('tag_api.put_tag', componentid=component["_id"]) }}" style="margin-bottom: 0%;"> <select id='tag_name' name='tag_name' onchange="this.form.submit();"> <option selected="selected" value="">Select a Tag</option> - {% for tag in module['component_tag_candidate'] %} + {% for tag in component['component_tag_candidate'] %} <option value="{{tag['name']}}">{{ tag['name'] }}</option> {% endfor %} </select> </form> {% endif %} - {% for tag in module['component_tag'] %} + {% for tag in component['component_tag'] %} <span class="badge badge-pill badge-primary"><i class="fa fa-tag"></i> {{ tag['name'] }}</span><br> {% endfor %} </td> @@ -180,9 +160,8 @@ {% endfor %} </tbody> </table> - {% else %} - <h3>Others <span style="font-size: 50%;">Found {{ pagingInfo['entries'] }} modules in total</span></h3> - {% endif %} + </form> + </div> {% if pagingInfo["minPage"] != pagingInfo["maxPage"] %} diff --git a/viewer/templates/parts/nav.html b/viewer/templates/parts/nav.html index 22982a35..2f4b4b68 100644 --- a/viewer/templates/parts/nav.html +++ b/viewer/templates/parts/nav.html @@ -16,11 +16,15 @@ <div class="navbar-brand"> <ul class="nav navbar-nav mr-auto"> <b> - <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"><a href="/localdb/">TOP</a></li> + <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"><a href="/localdb/">Top</a></li> <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"> | </li> - <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"><a href="/localdb/components?view=module">MODULES</a></li> + <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"><a href="/localdb/components?view=module">Modules</a></li> <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"> | </li> - <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"><a href="/localdb/scan">SCANS</a></li> + <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"><a href="/localdb/components?view=module_pcb">PCBs</a></li> + <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"> | </li> + <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"><a href="/localdb/components?view=bare_module">Bare Modules</a></li> + <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"> | </li> + <li style="display: inline-block; margin-right: 2pt; font-size: 12pt;"><a href="/localdb/scan">Electrical Scans</a></li> </b> </ul> </div> diff --git a/viewer/templates/scan_table.html b/viewer/templates/scan_table.html index 0c664ef9..2f6d3585 100644 --- a/viewer/templates/scan_table.html +++ b/viewer/templates/scan_table.html @@ -93,7 +93,7 @@ <th scope="col" colspan=1 rowspan=1 style="word-wrap:break-word; text-align:left;"> <div class="balloonoya">Tag <i class="fa fa-question-circle "></i>: <form action="{{ url_for('tag_api.create_tag') }}" style="display: inline-block; margin: 0%;"> - <button class="btn btn-outline-dark btn-sm" style="padding-top:0; padding-bottom:0; font-size: 90%;">Creat</button> + <button class="btn btn-outline-dark btn-sm" style="padding-top:0; padding-bottom:0; font-size: 90%;">Create</button> </form> <span class="balloon"> Create tag with the Link. <br> diff --git a/viewer/templates/toppage.html b/viewer/templates/toppage.html index 7fd052ba..b8a74d8c 100644 --- a/viewer/templates/toppage.html +++ b/viewer/templates/toppage.html @@ -43,6 +43,7 @@ table.toppage td.border_inner { {% if table_docs['page'] == 'components' or table_docs['page']=='scan' %} <form class="form-holizontal" role="form" method="get" action="{{ table_docs['page'] }}" accept-charset="UTF-8"> <h5> + <input type="hidden" name="view" value="{{ table_docs['view'] }}" placeholder="View"> <input type="search" name="keywords" value="{{ table_docs['keywords'] }}" placeholder="Input keywords"> {% if table_docs['match'] == 'perfect' %} <input type="radio" name="match" value="partial"> Partial match @@ -82,8 +83,10 @@ table.toppage td.border_inner { <div class="row align-items-center justify-content-center"> <div class="col"> <h2>Browse LocalDB</h2> - <h4> <a href="{{ url_for('toppage_api.show_comps')}}"><i class="fa fa-desktop "></i> Browse Components</a></h4> - <h4> <a href="{{ url_for('toppage_api.show_scans')}}"><i class="fa fa-desktop "></i> Browse Scans</a></h4> + <h4> <a href="{{ url_for('toppage_api.show_comps', view='module') }}"><i class="fa fa-desktop "></i> Browse Modules</a></h4> + <h4> <a href="{{ url_for('toppage_api.show_comps', view='module_pcb') }}"><i class="fa fa-desktop "></i> Browse PCBs</a></h4> + <h4> <a href="{{ url_for('toppage_api.show_comps', view='bare_module') }}"><i class="fa fa-desktop "></i> Browse Bare Modules</a></h4> + <h4> <a href="{{ url_for('toppage_api.show_scans')}}"><i class="fa fa-desktop "></i> Browse Electrical Scans</a></h4> {% if session['logged_in'] %} <br> <h2>Customize LocalDB</h2> -- GitLab From 2fd5ce64bad4849fa22fde896ea8549ef08fdc6c Mon Sep 17 00:00:00 2001 From: Hideyuki Oide <Hideyuki.Oide@cern.ch> Date: Wed, 20 Jul 2022 22:46:18 +0900 Subject: [PATCH 2/7] supporting archive feature using tags --- viewer/pages/toppage.py | 348 +++++-------------------- viewer/templates/components_table.html | 24 +- 2 files changed, 85 insertions(+), 287 deletions(-) diff --git a/viewer/pages/toppage.py b/viewer/pages/toppage.py index d211b3bb..8af527da 100755 --- a/viewer/pages/toppage.py +++ b/viewer/pages/toppage.py @@ -66,14 +66,6 @@ def show_comps(): table_docs = {"components": [] } - # get keywords - if not request.form.get("keywords") == None: - table_docs["keywords"] = request.form.get("keywords", "") - table_docs["match"] = request.form.get("match", "None") - else: - table_docs["keywords"] = request.args.get("keywords", "") - table_docs["match"] = request.args.get("match", "None") - table_docs['view'] = request.args.get("view", None) if table_docs['view'] == None: @@ -82,9 +74,50 @@ def show_comps(): view = table_docs['view'] logger.debug('show_comps(): view = {}'.format( view ) ) + + + # Action modes + archive_list = [] + doArchive = False + if request.form.get("archive"): + logger.debug('show_comps(): archive = {}'.format( request.form.get("archive") ) ) + doArchive = True + + for key in request.form.keys(): + flag = request.form.get( key ) + if flag: + archive_list.append( key[key.find('_')+1:] ) + + doShowAll = False + if request.form.get("showall"): + logger.debug('show_comps(): showall = {}'.format( request.form.get("archive") ) ) + doShowAll = True + + # get keywords + if request.form.get("keywords"): + table_docs["keywords"] = request.form.get("keywords", "") + table_docs["match"] = request.form.get("match", "None") + else: + table_docs["keywords"] = request.args.get("keywords", "") + table_docs["match"] = request.args.get("match", "None") + + if table_docs["keywords"] != "": + doShowAll = True + compsPerPage = 10 + thistime = datetime.utcnow() + + if not userdb.viewer.tag.categories.find_one( { "name":"archived" } ): + userdb.viewer.tag.categories.insert_one( + { + "sys": {"rev": 0, "cts": thistime, "mts": thistime}, + "name": "archived", + "user_id": None + } + ) + page = 1 if not request.args.get("p", 1) == "": page = int(request.args.get("p", 1)) @@ -94,10 +127,30 @@ def show_comps(): for component in components: # logger.info( 'component: {}'.format( component ) ) - cmp_id = str(component["_id"]) - cmp_ids.append( cmp_id ) + ### tags + tag_candidates = [ c for c in userdb.viewer.tag.categories.find( {} ) ] + + ### put tags + tags = [ t for t in userdb.viewer.tag.docs.find( {"componentid": cmp_id} ) ] + + isArchived = False + for tag in tags: + if tag['name'] == "archived": + isArchived = True + + if (not isArchived) and component['name'] in archive_list: + tagsdocs = { + "sys": {"rev": 0, "cts": thistime, "mts": thistime}, + "name": "archived", + "componentid" : cmp_id + } + userdb.viewer.tag.docs.insert_one( tagsdocs ) + isArchived = True + + if not isArchived: + cmp_ids.append( cmp_id ) cps = [] @@ -157,19 +210,6 @@ def show_comps(): result = localdb.QC.result.find_one( {"component": cmp_id} ) - ### tags - query = {} - module_tag_candidates = userdb.viewer.tag.categories.find(query) - tag_candidate = [] - for module_tag in module_tag_candidates: - tag_candidate.append(module_tag) - ### put tags - query = {"componentid": cmp_id} - module_tags = userdb.viewer.tag.docs.find(query) - tag = [] - for module_tag in module_tags: - tag.append(module_tag) - component_data = { "_id": cmp_id, "collection": "component", @@ -179,8 +219,8 @@ def show_comps(): "cps": cps, "grade": {}, "proDB": component.get("proDB", False), - "component_tag_candidate": tag_candidate, - "component_tag": tag, + "component_tag_candidate": tag_candidates, + "component_tag": tags, } if qcStatus: @@ -214,8 +254,13 @@ def show_comps(): table_docs["keywords"], search_targets, table_docs["match"] ) + logger.info( '{} {}'.format( component_data['name'], component_data["component_tag"] ) ) if isKeywordMatched: - table_docs["components"].append( component_data ) + if doShowAll: + table_docs["components"].append( component_data ) + else: + if not isArchived: + table_docs["components"].append( component_data ) #end for component in components: @@ -239,262 +284,13 @@ def show_comps(): table_docs.update( { 'pagingInfo':pagingInfo } ) table_docs["page"] = "components" - table_docs["title"] = "Component List" + table_docs["title"] = "{} List".format( view.upper() ) table_docs["download_module"] = os.path.exists(IF_DIR+"/doing_download.txt") return render_template( "toppage.html", table_docs=table_docs, timezones=pytz.all_timezones ) - #######---------------------------------------------------------------------------------------------- - - # Filter modules - index = 0 - for cmp_id in cmp_ids: - query = {"_id": ObjectId(cmp_id)} - this_cmp = localdb.component.find_one(query) - - if this_cmp.get("componentType", "front-end_chip").lower() == "module": - key = "modules" - id_key = "child" - query = {"parent": cmp_id} - else: - continue - - index += 1 - - ### cpr entries - components = localdb.childParentRelation.find(query) - cpr_ids = [] - for component in components: - cpr_ids.append(component[id_key]) - cps = [] - for cpr_id in cpr_ids: - query = {"_id": ObjectId(cpr_id)} - this_cp = localdb.component.find_one(query) - if this_cp: - try: - name = this_cp["name"] - except: - name = this_cp["serialNumber"] - - hexName = '' - if name.find('20U') == 0: - try: - hexName = "{0:#0{1}x}".format(int( name[-7:] ),7) - except: - pass - - batchMap = { '0':'RD53A', '1':'ITkPix' } - - typeinfo = 'Undef' - try: - typeinfo = batchMap[hexName[2]] - except: - pass - - if 'PI' in name: - typeinfo = "L0/L1 Inner Module" - elif 'PB' in name: - typeinfo = "Outer Barrel Module" - elif 'PE' in name: - typeinfo = "Outer Endcap Module" - elif 'PGM2' in name: - typeinfo = "Outer Quad Module" - elif 'PGR2' in name: - typeinfo = "Dual Chip Module" - elif 'PGR-' in name: - typeinfo = "Single Chip Module" - elif 'XM' in name: - typeinfo = "Tutorial Module" - else: - typeinfo = "Others" - - - if '0' == name[7]: - typeinfo+= ' / RD53A' - elif '1' == name[7]: - typeinfo+=' / ITkPix_v1.0' - elif '2' == name[7]: - typeinfo+=' / ITkPix_v1.1' - elif '3' == name[7]: - typeinfo+=' / ITkPix_v2' - - - wafer='' - if name.find('20UPGFC') == 0: - wafer= '-{}-({},{})'.format( hexName[3:4], hexName[5], hexName[6] ) - - if name.find( '20UPGXF' ) == 0: - typeinfo = 'Tutorial-FE' - elif name.find( '20UPGXB' ) == 0: - typeinfo = 'Tutorial-BareModule' - elif name.find( '20UPGXP' ) == 0: - typeinfo = 'Tutorial-PCB' - elif name.find( '20UPGPQ' ) == 0: - typeinfo = 'QuadPCB' - elif name.find( '20UPGPD' ) == 0: - typeinfo = 'DualPCB' - elif name.find( '20UPIPT' ) == 0: - typeinfo = 'TripletL0StavePCB' - elif name.find( '20UPIP0' ) == 0: - typeinfo = 'TripletL0R0PCB' - elif name.find( '20UPIP5' ) == 0: - typeinfo = 'TripletL0R0.5PCB' - elif name.find( '20UPGB1' ) == 0: - typeinfo = 'Single-BareModule' - elif name.find( '20UPGB2' ) == 0: - typeinfo = 'Dual-BareModule' - elif name.find( '20UPGB4' ) == 0: - typeinfo = 'Quad-BareModule' - elif name.find( '20UPGBS' ) == 0: - typeinfo = 'Digital-Single-BareModule' - elif name.find( '20UPGBQ' ) == 0: - typeinfo = 'Digital-Quad-BareModule' - - cps.append( - {"_id": cpr_id, - "collection": "component", - "name": name[0:3]+' '+name[3:7]+' '+name[7:9]+' '+name[9:] if name.find('20U') == 0 else name, - "name_orig": name, - "grade": {}, - "hex" : hexName if name.find('20UPGFC')==0 else '', - "typeinfo" : typeinfo+wafer - } - ) - - ### Latest Scan - query = {"component": cmp_id} - run_entries = ( - localdb.QC.result.find(query).sort([("$natural", -1)]).limit(1) - ) - result = { - "stage": None, - "runId": None, - "datetime": None, - "testType": None, - "user": None, - "site": None, - } - for this_run in run_entries: - result.update( - { - "stage": this_run["currentStage"], - "runId": str(this_run["_id"]), - "datetime": setTime( - this_run["sys"]["cts"], - session.get("timezone", str(get_localzone())), - ), - "testType": this_run["testType"], - "user": this_run["user"], - "site": this_run["address"], - } - ) - - ### tags - query = {} - module_tag_candidates = userdb.viewer.tag.categories.find(query) - tag_candidate = [] - for module_tag in module_tag_candidates: - tag_candidate.append(module_tag) - ### put tags - query = {"componentid": cmp_id} - module_tags = userdb.viewer.tag.docs.find(query) - tag = [] - for module_tag in module_tags: - tag.append(module_tag) - - try: - name = this_cmp["name"] - except: - name = this_cmp["serialNumber"] - module_data = { - "_id": cmp_id, - "collection": "component", - "name": name[0:3]+' '+name[3:7]+' '+name[7:9]+' '+name[9:] if name.find('20U') == 0 else name, - "cps": cps, - "grade": {}, - "stage": result["stage"], - "runId": result["runId"], - "datetime": result["datetime"], - "testType": result["testType"], - "user": result["user"], - "site": result["site"], - "proDB": this_cmp.get("proDB", False), - "component_tag_candidate": tag_candidate, - "component_tag": tag, - } - - query_targets = [] - query_targets.append(name) - query_targets.append(module_data["name"]) - query_targets.append(module_data["stage"]) - query_targets.append(module_data["user"]) - query_targets.append(module_data["site"]) - query_targets.append(module_data["datetime"]) - for tag in module_data["component_tag"]: - query_targets.append(tag["name"]) - for child in module_data["cps"]: - query_targets.append(child["name"]) - query_targets.append(child["name_orig"]) - - isKeywordMatched = query_keywords( - table_docs["keywords"], query_targets, table_docs["match"] - ) - - if isKeywordMatched: - table_docs["components"][key].append( module_data ) - - - table_docs["components"].update( - {"module_num": len(table_docs["components"]["modules"])} - ) - - for m in table_docs["components"]["modules"]: - if m["datetime"] == None: - m["datetime"] = '' - - modules = sorted( - table_docs["components"]["modules"], - key=lambda x: x["datetime"], - reverse=True, - ) - table_docs["components"]["modules"] = modules[pageMinIndex:pageMaxIndex] - - nModulesTotal = len( modules ) - maxPage = len(modules) // compsPerPage + 1 - minRange = max(1, page - 2) - maxRange = min(maxPage, minRange+3) - pageRange = list( range( minRange, maxRange+1 ) ) - - pagingInfo = { - "thisPage": page, - "range" : pageRange, - "minPage": 1, - "maxPage": maxPage, - "entries" : nModulesTotal - } - table_docs.update( { 'pagingInfo':pagingInfo } ) - - - table_docs["components"].update( - {"chip_num": len(table_docs["components"]["chips"])} - ) - chips = sorted( - table_docs["components"]["chips"], - key=lambda x: x["name"], - reverse=True, - ) - table_docs["components"]["chips"] = chips - - table_docs["page"] = "components" - table_docs["title"] = "Component List" - table_docs["download_module"] = os.path.exists(IF_DIR+"/doing_download.txt") - - return render_template( - "toppage.html", table_docs=table_docs, timezones=pytz.all_timezones - ) - #################### # Show test run list diff --git a/viewer/templates/components_table.html b/viewer/templates/components_table.html index 46f5a87d..ceacc45f 100644 --- a/viewer/templates/components_table.html +++ b/viewer/templates/components_table.html @@ -58,21 +58,21 @@ {% set pagingInfo = table_docs['pagingInfo'] %} - <h3>{{ table_docs['view'].upper() }} <span style="font-size: 50%;">Found {{ pagingInfo['entries'] }} components in total</span></h3> - <form class="form-holizontal" role="form" method="post" action="{{ table_docs['page'] }}" accept-charset="UTF-8"> + <input type="hidden" name="view" value="{{ table_docs['view'] }}" placeholder="View"> + + <h3><span style="font-size: 50%;">Found {{ pagingInfo['entries'] }} {{ table_docs['view'].upper() }} components in LocalDB. + <button class="btn btn-info" style="padding-top: 0; padding-bottom: 0; font-size:90%; margin-bottom: 5pt;" name="showall" value="true">Show All</button> + </span> + </h3> + <div class="row align-items-top card-body" style="margin-top:0; padding-top:0; padding-bottom:0;"> - <button type="submit" class="btn btn-secondary" style="padding-top: 0.05%; padding-bottom: 0.05%; font-size:90%;">Show All</button> <table class='table table-sm table-hover table-bordered' style='font-size: 10pt; margin-top:0;'> <thead class="table table-sm table-hover table-bordered"> <tr> <th style="word-wrap:break-word; text-align:center;"> - <div class="balloonoya"> - <button class="btn btn-secondary" style="padding-top:0; padding-bottom:0; font-size: 90%;" id="archive">Archive</button> - <!-- span class="balloon"> - Click the checkbox and press this button to hide the module from the list. - </span--> - </div> + <input type="hidden" name="view" value="{{ table_docs['view'] }}" placeholder="View"> + <button class="btn btn-info" style="padding-top:0; padding-bottom:0; font-size: 90%;" name="archive" value="true">Archive</button> </th> <th style="text-align:left;">{{ table_docs['view'].upper() }}</th> <th style="word-wrap:break-word; text-align:left;">Relational Components</th> @@ -82,7 +82,7 @@ <th style="word-wrap:break-word; text-align:left;"><div class="balloonoya">Tag <i class="fa fa-question-circle "></i>: <div class="balloonoya"> <form action="{{ url_for('tag_api.create_tag') }}" style="display: inline-block; margin: 0%;"> - <button class="btn btn-outline-dark btn-sm" style="padding-top:0; padding-bottom:0; font-size: 90%;">Create New</button> + <button class="btn btn-info btn-sm" style="padding-top:0; padding-bottom:0; font-size: 90%;">Create</button> </form> <span class="balloon"> Create tags with the button. <br> @@ -98,7 +98,9 @@ {% for component in table_docs['components'] %} {% set index = table_docs['components'].index( component ) %} <tr> - <td style="text-align:center;"><input type="checkbox" id="check_{{ component['name'] }}" value="false"></td> + <td style="text-align:center;"> + <input class="form-check-input" type="checkbox" name="archive_{{ component['name_orig'] }}" value="false"> + </td> <td style="word-wrap:break-word; text-align:left;"> {% if component['name_delim'] != '' %} <a href='{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection']) }}'>{{ component['name'] }}</a> -- GitLab From a836d05043a7d6a0eb77f638684770f5a54a12eb Mon Sep 17 00:00:00 2001 From: Hideyuki Oide <Hideyuki.Oide@cern.ch> Date: Fri, 22 Jul 2022 14:38:24 +0900 Subject: [PATCH 3/7] archive ans showall features to electrical scans --- viewer/pages/tag.py | 15 +++-- viewer/pages/toppage.py | 65 +++++++++++++++++++++- viewer/templates/components_table.html | 2 +- viewer/templates/scan_table.html | 76 ++++++++++++++------------ 4 files changed, 111 insertions(+), 47 deletions(-) diff --git a/viewer/pages/tag.py b/viewer/pages/tag.py index a0346ce2..80c29927 100755 --- a/viewer/pages/tag.py +++ b/viewer/pages/tag.py @@ -46,8 +46,7 @@ def create_tag(): userdb.viewer.tag.categories.insert_one( { "sys": {"rev": 0, "cts": thistime, "mts": thistime}, - "name": taginfo[0], - "user_id": session["user_id"], + "name": taginfo[0] } ) @@ -76,8 +75,7 @@ def put_tag(): tag_name = request.form.get("tag_name") tagdocs = { "sys": {"rev": 0, "cts": thistime, "mts": thistime}, - "name": tag_name, - "user_id": session["user_id"], + "name": tag_name } componentid = request.args.get("componentid", -1) runId = request.args.get("runId", -1) @@ -109,10 +107,11 @@ def put_tag(): userdb.viewer.tag.docs.insert(tagdocs) else: query_doc = userdb.viewer.query.find_one({"runId": runId}) - new_list = query_doc["data"] - new_list.remove(str(tag_name)) - userdb.viewer.query.update({"runId": runId}, {"$set": {"data": new_list}}) - userdb.viewer.tag.docs.remove(query) + if query_doc: + new_list = query_doc["data"] + new_list.remove(str(tag_name)) + userdb.viewer.query.update({"runId": runId}, {"$set": {"data": new_list}}) + userdb.viewer.tag.docs.remove(query) return redirect(request.headers.get("Referer")) diff --git a/viewer/pages/toppage.py b/viewer/pages/toppage.py index 8af527da..1fb2740b 100755 --- a/viewer/pages/toppage.py +++ b/viewer/pages/toppage.py @@ -306,6 +306,25 @@ def show_scans(): sort_cnt = int(request.args.get("p", 0)) table_docs = {"run": []} + + # Action modes + archive_list = [] + doArchive = False + if request.form.get("archive"): + logger.debug('show_comps(): archive = {}'.format( request.form.get("archive") ) ) + doArchive = True + + for key in request.form.keys(): + flag = request.form.get( key ) + if flag: + archive_list.append( key[key.find('_')+1:] ) + + doShowAll = False + if request.form.get("showall"): + logger.debug('show_comps(): showall = {}'.format( request.form.get("archive") ) ) + doShowAll = True + + # get keywords if not request.form.get("keywords") == None: table_docs["keywords"] = request.form.get("keywords", "") @@ -313,6 +332,9 @@ def show_scans(): else: table_docs["keywords"] = request.args.get("keywords", "") table_docs["match"] = request.args.get("match", "None") + + if table_docs["keywords"] != "": + doShowAll = True print(table_docs["keywords"]) run_ids = query_docs(Keywords=table_docs["keywords"], Match=table_docs["match"]) @@ -331,14 +353,51 @@ def show_scans(): run_num_list.append(sort_cnt * (max_num) + i) + thistime = datetime.utcnow() + + if not userdb.viewer.tag.categories.find_one( { "name":"archived" } ): + userdb.viewer.tag.categories.insert_one( + { + "sys": {"rev": 0, "cts": thistime, "mts": thistime}, + "name": "archived", + "user_id": None + } + ) + run_counts = 0 if not nrun_entries == 0: for i in run_num_list: try: run_data = getScanSummary(run_ids[i]) - run_counts += 1 - table_docs["run"].append({"run_data": run_data, "nrun": run_counts}) - except: + + + isArchived = False + tags = run_data['testRun_tag'] + for tag in tags: + if tag['name'] == 'archived': + isArchived = True + break + + if (not isArchived) and str( run_data['_id'] ) in archive_list: + tagsdocs = { + "sys": {"rev": 0, "cts": thistime, "mts": thistime}, + "name": "archived", + "runId" : str( run_data['_id'] ) + } + userdb.viewer.tag.docs.insert_one( tagsdocs ) + userdb.viewer.query.update( {"runId": str( run_data['_id'] ) }, {"$push": {"data": tagsdocs['name'] } } ) + isArchived = True + + if doShowAll: + table_docs["run"].append( {"run_data": run_data, "nrun": run_counts} ) + run_counts += 1 + + else: + if not isArchived: + table_docs["run"].append( {"run_data": run_data, "nrun": run_counts} ) + run_counts += 1 + except Exception as e: + logger.warning( '{}'.format(e) ) pass table_docs["run"] = sorted( diff --git a/viewer/templates/components_table.html b/viewer/templates/components_table.html index ceacc45f..8ba32126 100644 --- a/viewer/templates/components_table.html +++ b/viewer/templates/components_table.html @@ -99,7 +99,7 @@ {% set index = table_docs['components'].index( component ) %} <tr> <td style="text-align:center;"> - <input class="form-check-input" type="checkbox" name="archive_{{ component['name_orig'] }}" value="false"> + <input class="form-check-input" type="checkbox" name="archive_{{ component['name_orig'] }}" style="-moz-transform:scale(1.4); -webkit-transform:scale(1.4); transform:scale(1.4);" value="false"> </td> <td style="word-wrap:break-word; text-align:left;"> {% if component['name_delim'] != '' %} diff --git a/viewer/templates/scan_table.html b/viewer/templates/scan_table.html index 2f6d3585..1ce35404 100644 --- a/viewer/templates/scan_table.html +++ b/viewer/templates/scan_table.html @@ -52,32 +52,21 @@ <div class="row align-items-left justify-content-left"> <div class="col"> + <form class="form-holizontal" role="form" method="post" action="{{ table_docs['page'] }}" accept-charset="UTF-8"> + <input type="hidden" name="view" value="" placeholder="View"> + + <h3><span style="font-size: 50%;">Found {{ table_docs["run"]|length }} scans in LocalDB. + <button class="btn btn-info" style="padding-top: 0; padding-bottom: 0; font-size:90%; margin-bottom: 5pt;" name="showall" value="true">Show All</button> + </span> + </h3> + <table class="table table-sm toppage" style="font-size: 11pt;"> - <div style="text-align: center; font-size: 14px;"> - <ul class="page-list"> - {% if table_docs['now_cnt']!=0 %} - <li style="display: inline;"> - <a href="{{ url_for('toppage_api.show_scans', p=0, keywords=table_docs['keywords'], match=table_docs['match']) }}">≪</a> - </li> - {% endif %} - {% for i in table_docs['cnt'] %} - {% if i==table_docs['now_cnt'] %} - <li style="display: inline-flex; justify-content: center; align-items: center; flex-flow: column; vertical-align: top; inline; height: 20px; width: 20px; border-radius: 50%; line-height: 20px; background: #FFEEAA; text-align: center; font-size: 14pt"> - {% else %} - <li style="display: inline;"> - {% endif %} - <a href="{{ url_for('toppage_api.show_scans', p=i,keywords=table_docs['keywords'], match=table_docs['match']) }}">{{i+1}}</a> - </li> - {% endfor %} - {% if table_docs['now_cnt']!=table_docs['max_cnt'] %} - <li style="display: inline;"> - <a href="{{ url_for('toppage_api.show_scans', p=table_docs['max_cnt'],keywords=table_docs['keywords'], match=table_docs['match']) }}">≫</a> - </li> - {% endif %} - </ul> - </div> <thead class="table-light"> <tr> + <th scope="col" rowspan=2 style="word-wrap:break-word; text-align:center;"> + <input type="hidden" name="view" value="" placeholder="View"> + <button class="btn btn-info" style="padding-top:0; padding-bottom:0; font-size: 90%;" name="archive" value="true">Archive</button> + </th> <th scope="col" rowspan=2 style="word-wrap:break-word; text-align:left;">Module Name<br />(ATLAS SN)</th> <th scope="col" rowspan=2 style="word-wrap:break-word; text-align:left;">Chip Name<br />(ATLAS SN)</th> <th scope="col" colspan=5 style="word-wrap:break-word; text-align:left;">Test Data</th> @@ -111,6 +100,9 @@ {% else %} <tr style="background: #AABBCC;" name="test-table" disabled="disabled"> {% endif %} + <td style="text-align:center;"> + <input class="form-check-input" type="checkbox" name="archive_{{ run_data['run_data']['_id'] }}" value="false"> + </td> <td style="word-wrap:break-word; text-align:left;"> {% for component in run_data['run_data']['components'] %} {% if component['chip_id']=='module' %} @@ -150,18 +142,6 @@ <td style="word-wrap:break-word; text-align:left;">{{ run_data['run_data']["site"] }}</td> <td style="word-wrap:break-word; text-align:left;">{{ run_data['run_data']["datetime"] }}</td> <td style="word-wrap:break-word; text-align:left;">{{ run_data['run_data']["setT"] }}</td> - <!--td style="word-wrap:break-word; text-align:left;"> - {% set hoge = [] %} - {% for component in run_data['run_data']['components'] %} - {% if component['chip_id']=='module' %} - {% set _ = hoge.append(0)%} - <a href="{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection'], test='electrical', runId=run_data['run_data']['_id']) }}">result page</a> - {% endif %} - {% endfor %} - {% if not hoge %} - result page</a> - {% endif %} - </td--> <td style="word-wrap:break-word; text-align:left; line-height: 1.5;"> {% if session['logged_in'] %} <form method="post" action="{{ url_for('tag_api.put_tag', runId=run_data['run_data']['_id']) }}" style="margin-bottom: 0%;"> @@ -181,6 +161,8 @@ {% endfor %} </tbody> </table> + + </form> <form action=""> <p style="font-size: 8pt;" align="right"> @@ -189,6 +171,30 @@ </p> </form> + <div style="text-align: center; font-size: 14px;"> + <ul class="page-list"> + {% if table_docs['now_cnt']!=0 %} + <li style="display: inline;"> + <a href="{{ url_for('toppage_api.show_scans', p=0, keywords=table_docs['keywords'], match=table_docs['match']) }}">≪</a> + </li> + {% endif %} + {% for i in table_docs['cnt'] %} + {% if i==table_docs['now_cnt'] %} + <li style="display: inline-flex; justify-content: center; align-items: center; flex-flow: column; vertical-align: top; inline; height: 20px; width: 20px; border-radius: 50%; line-height: 20px; background: #FFEEAA; text-align: center; font-size: 14pt"> + {% else %} + <li style="display: inline;"> + {% endif %} + <a href="{{ url_for('toppage_api.show_scans', p=i,keywords=table_docs['keywords'], match=table_docs['match']) }}">{{i+1}}</a> + </li> + {% endfor %} + {% if table_docs['now_cnt']!=table_docs['max_cnt'] %} + <li style="display: inline;"> + <a href="{{ url_for('toppage_api.show_scans', p=table_docs['max_cnt'],keywords=table_docs['keywords'], match=table_docs['match']) }}">≫</a> + </li> + {% endif %} + </ul> + </div> + </div> </div> </div> -- GitLab From 6d2bef1022404dc8708126723802fbf1d5fd6709 Mon Sep 17 00:00:00 2001 From: Hideyuki Oide <Hideyuki.Oide@cern.ch> Date: Sun, 24 Jul 2022 16:48:35 +0900 Subject: [PATCH 4/7] supporting ITkDB testRun result transaction records, overlap removal and deletion of obsolete testRuns on ITkPD --- viewer/itkpd-interface/.auth | Bin 13916 -> 0 bytes viewer/itkpd-interface/lib/PDInterface.py | 12 +- .../itkpd-interface/lib/download_QC_info.py | 31 +- viewer/itkpd-interface/lib/upload_results.py | 1493 ++++++++++------- .../{ => displayResults}/SLDOVIresult.html | 0 .../{ => displayResults}/adc_calibration.html | 0 .../{ => displayResults}/coplanarity.html | 0 .../{ => displayResults}/electrical.html | 0 .../glueattachresult.html | 0 .../{ => displayResults}/irefresult.html | 0 .../{ => displayResults}/massresult.html | 0 .../{ => displayResults}/metresult.html | 0 .../{ => displayResults}/paryleneresult.html | 0 .../{ => displayResults}/pulltest.html | 0 .../{ => displayResults}/pullupresult.html | 0 .../{ => displayResults}/qc_others.html | 0 .../{ => displayResults}/sensorIVresult.html | 0 .../{ => displayResults}/thermalresult.html | 0 .../{ => displayResults}/tuning.html | 0 .../{ => displayResults}/viresult.html | 0 .../wirebondingresult.html | 0 .../wpenvelope_result.html | 0 .../{ => displayResults}/xrayresult.html | 0 viewer/templates/displayTests/massresult.html | 118 ++ viewer/templates/displayTests/viresult.html | 138 ++ .../displayTests/wirebondingresult.html | 148 ++ .../displayTests/wpenvelope_result.html | 111 ++ viewer/templates/displayTests/xrayresult.html | 281 ++++ .../templates/{ => electrical_test}/plot.html | 0 .../{ => electrical_test}/result.html | 0 .../{ => electrical_test}/selection.html | 0 31 files changed, 1694 insertions(+), 638 deletions(-) delete mode 100644 viewer/itkpd-interface/.auth rename viewer/templates/{ => displayResults}/SLDOVIresult.html (100%) rename viewer/templates/{ => displayResults}/adc_calibration.html (100%) rename viewer/templates/{ => displayResults}/coplanarity.html (100%) rename viewer/templates/{ => displayResults}/electrical.html (100%) rename viewer/templates/{ => displayResults}/glueattachresult.html (100%) rename viewer/templates/{ => displayResults}/irefresult.html (100%) rename viewer/templates/{ => displayResults}/massresult.html (100%) rename viewer/templates/{ => displayResults}/metresult.html (100%) rename viewer/templates/{ => displayResults}/paryleneresult.html (100%) rename viewer/templates/{ => displayResults}/pulltest.html (100%) rename viewer/templates/{ => displayResults}/pullupresult.html (100%) rename viewer/templates/{ => displayResults}/qc_others.html (100%) rename viewer/templates/{ => displayResults}/sensorIVresult.html (100%) rename viewer/templates/{ => displayResults}/thermalresult.html (100%) rename viewer/templates/{ => displayResults}/tuning.html (100%) rename viewer/templates/{ => displayResults}/viresult.html (100%) rename viewer/templates/{ => displayResults}/wirebondingresult.html (100%) rename viewer/templates/{ => displayResults}/wpenvelope_result.html (100%) rename viewer/templates/{ => displayResults}/xrayresult.html (100%) create mode 100644 viewer/templates/displayTests/massresult.html create mode 100644 viewer/templates/displayTests/viresult.html create mode 100644 viewer/templates/displayTests/wirebondingresult.html create mode 100644 viewer/templates/displayTests/wpenvelope_result.html create mode 100644 viewer/templates/displayTests/xrayresult.html rename viewer/templates/{ => electrical_test}/plot.html (100%) rename viewer/templates/{ => electrical_test}/result.html (100%) rename viewer/templates/{ => electrical_test}/selection.html (100%) diff --git a/viewer/itkpd-interface/.auth b/viewer/itkpd-interface/.auth deleted file mode 100644 index f8914bfff254170aa357ae102d7efefc273edee8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13916 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 diff --git a/viewer/itkpd-interface/lib/PDInterface.py b/viewer/itkpd-interface/lib/PDInterface.py index 7a00e4fa..09f2d93d 100755 --- a/viewer/itkpd-interface/lib/PDInterface.py +++ b/viewer/itkpd-interface/lib/PDInterface.py @@ -8,8 +8,8 @@ class PDInterface: def __init__(self, code1, code2): self.u = itkdb.core.User(accessCode1=code1, accessCode2=code2) self.pd_client = itkdb.Client(user=self.u) - #with open("{}/itkpd-interface/config/stage_test.json".format(VIEWER_DIR)) as f: - # self.stage_test_map = json.load(f) + with open("{}/itkpd-interface/config/stage_test.json".format(VIEWER_DIR)) as f: + self.stage_test_map = json.load(f) def getCompFromLocalDB(self, sn): query = {"serialNumber": sn,"proDB": True} @@ -19,7 +19,13 @@ class PDInterface: m_doc = self.getCompFromLocalDB(msn) cprs = localdb.childParentRelation.find({"parent":str(m_doc["_id"])}) chip_ids = [ chip["child"] for chip in cprs ] - return [localdb.component.find_one({"_id":ObjectId(chip_id)}) for chip_id in chip_ids] + docs = [] + for chip_id in chip_ids: + doc = localdb.component.find_one({"_id":ObjectId(chip_id), "componentType":"front-end_chip"}) + if doc != None: + logger.info( '{}'.format( doc ) ) + docs.append( doc ) + return docs def getQmsFromLocalDB(self, oid): query = {"component": oid} diff --git a/viewer/itkpd-interface/lib/download_QC_info.py b/viewer/itkpd-interface/lib/download_QC_info.py index 02a2ca1f..52f4b087 100755 --- a/viewer/itkpd-interface/lib/download_QC_info.py +++ b/viewer/itkpd-interface/lib/download_QC_info.py @@ -82,8 +82,22 @@ class ModuleDownloader(PDInterface.PDInterface): continue for testType in stage["testTypes"]: - test_item_map[testType["testType"]["code"]] = testType["testType"]["name"] - test_items.append(testType["testType"]["code"]) + code = testType["testType"]["code"] + name = testType["testType"]["name"] + + temperatures = [30, 20, -15] + excepCodes = [ "READOUT_IN_BASIC_ELECTRICAL_TEST", "TUNING", "BUMP_BOND_QUALITY", "SENSOR_IV" ] + + if code in excepCodes: + for T in temperatures: + code_var = code + "_{}{}DEG".format( "MINUS" if T < 0 else "", abs(T) ) + name_var = name + " ({} degC)".format( T ) + test_item_map[code_var] = name_var + test_items.append( code_var ) + else: + test_item_map[code] = name + test_items.append( code ) + stage_flow.append(stage["code"]) stage_vs_test[stage["code"]] = test_items @@ -112,7 +126,8 @@ class ModuleDownloader(PDInterface.PDInterface): "latestSyncedStage" : cpt_doc["currentStage"]["code"], "status" : "created", "rework_stage" : [], - "QC_results" : {} + "QC_results" : {}, + "QC_results_pdb" : {} } chipType = "" @@ -129,8 +144,10 @@ class ModuleDownloader(PDInterface.PDInterface): for stage in qcStatus["stage_flow"]: tests = qcStatus["stage_test"][stage] doc["QC_results"][stage] = {} + doc["QC_results_pdb"][stage] = {} for test in tests: doc["QC_results"][stage][test] = "-1" + doc["QC_results_pdb"][stage][test] = "-1" return doc @@ -141,6 +158,9 @@ class ModuleDownloader(PDInterface.PDInterface): logger.info("Start downloading info of stages and QC tests") if userdb.QC.status.find_one( {"proddbVersion":proddbv, "code":module_cpt_doc["code"] }) == None: userdb.QC.status.insert_one( self.__create_QC_status_doc(module_cpt_doc) ) + else: + userdb.QC.status.remove( {"proddbVersion":proddbv, "code":module_cpt_doc["code"] }) + userdb.QC.status.insert_one( self.__create_QC_status_doc(module_cpt_doc) ) logger.info("Finished!!\n") return 0 @@ -201,10 +221,11 @@ class ModuleDownloader(PDInterface.PDInterface): # If the QC record is blank, insert one if cpt_qc_localdb_record == None: localdb.QC.module.status.insert_one( cpt_qc_doc ) - logger.info( "Created QC status doc for {} {}.".format( cpt_type, cpt_sn ) ) else: - logger.info( "QC status doc for {} {} is already present.".format( cpt_type, cpt_sn ) ) + localdb.QC.module.status.remove( {"component": cpt_doc["id"], "proddbVersion":proddbv} ) + localdb.QC.module.status.insert_one( cpt_qc_doc ) + logger.info( "Created QC status doc for {} {}.".format( cpt_type, cpt_sn ) ) return cpt_localdb_doc diff --git a/viewer/itkpd-interface/lib/upload_results.py b/viewer/itkpd-interface/lib/upload_results.py index 98de8d21..4f1507e4 100755 --- a/viewer/itkpd-interface/lib/upload_results.py +++ b/viewer/itkpd-interface/lib/upload_results.py @@ -6,46 +6,155 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../ from functions.imports import * class QCResultUploader(PDInterface.PDInterface): + + def __upload( self, testSpecificCallback ): + + # self.ldb_component = super().getCompFromLocalDB(componentSerialNumber) + # self.ldb_componentId = str( self.ldb_component["_id"] ) + # self.ldb_componentType = self.ldb_component["componentType"] + # self.ldb_component_QC_info = super().getQmsFromLocalDB(self.ldb_componentId) + # self.ldb_component_prop_info = super().getPmsFromLocalDB(self.ldb_componentId) + # self.ldb_test_doc = super().getQrFromLocalDB(result_id) + # self.pdb_component_doc = super().getCompFromProdDB(componentSerialNumber) + # self.pdb_prev_testRuns = [test for test in self.pd_client.get("listTestRunsByComponent", json={"component": self.pdb_component_doc["code"] }) ] + + isUploaded = False + + logger.info( '__upload(): processing stage {} / test type {}'.format( self.ldb_test_doc['currentStage'], self.testType ) ) + + timestampAsString = self.__getQCStrTime( self.ldb_test_doc["sys"]["cts"] ) + + isFreshResult = True + + goodTestRunIDs = [] + sameEnvGoodTestRunIDs = [] + + # loop over ProdDB tests + for prevTest in self.pdb_component_doc['tests']: + if prevTest['code'] != self.testType: + continue + + testRuns = prevTest['testRuns'] + + for testRun in testRuns: + if testRun['state'] != 'ready': + continue + + goodTestRunIDs.append( testRun['id'] ) + + try: + env_temp = testRun['properties']["ENVIRONMENT_TEMPERATURE"] + if int( env_temp ) == int( self.testSetTemp ): + sameEnvGoodTestRunIDs.append( testRun['id'] ) + except: + pass + + if testRun['runNumber'] == str( self.ldb_test_doc["_id"] ): + isFreshResult = False + self.pdb_testRunId = testRun['id'] + + logger.info( '__upload(): good testRun ids: {}'.format( goodTestRunIDs ) ) + + + if isFreshResult: + + logger.info( 'This is a fresh test run, uploading' ) + pprint.pprint( testSpecificCallback ) + + # Force set the component stage on ProdDB to this stage + try: + self.pd_client.post( 'setComponentStage', json = { 'component':self.componentSerialNumber, 'stage':self.stage } ) + + except Exception as e: + logger.error( '{}'.format(e) ) + + logger.info( 'Changed the ProdDB {} stage to {}'.format( self.componentSerialNumber, self.stage ) ) + + if self.ldb_componentType == "module": + + if self.testType == "READOUT_IN_BASIC_ELECTRICAL_TEST": + test_result = testSpecificCallback() + self.__createRawResultAttachment( test_result ) + + else: + json = testSpecificCallback( self.__createTestTemplate(timestampAsString), timestampAsString ) + test_result = self.pd_client.post( "uploadTestRunResults", json=json ) + + #pprint.pprint( test_result ) + + self.pdb_testRunId = test_result['testRun']['id'] + + + if self.testType == "OPTICAL": + pic_name = self.ldb_test_doc["currentStage"] + "_optical_img" + ".png" + inspect_pic = self.ldb_test_doc["results"]["img_entire"]["target"] + data = fs.get(ObjectId(str(inspect_pic))).read() + with open(pic_name, 'wb') as f: + f.write(data) + f = open(pic_name, "rb") + os.remove(pic_name) + self.pd_client.post("createBinaryTestRunParameter", data=dict(testRun=test_result["testRun"]["id"], parameter="FULL_IMAGE"), files=dict(data=f) ) + f.close() + + self.__AttachGoldens() + + # if the test is exclusive, delete all the rest + # otherwise, delete only testRuns with the same temperature + if self.isExclusiveTest: + logger.info( 'This is an exclusive test, delete previous TestRun records...' ) + for tr_id in goodTestRunIDs: + self.pd_client.post("deleteTestRun", json = { "testRun": tr_id } ) + logger.info( 'deleted testRun {}'.format( tr_id ) ) + else: + for tr_id in sameEnvGoodTestRunIDs: + self.pd_client.post("deleteTestRun", json = { "testRun": tr_id } ) + logger.info( 'deleted testRun {}'.format( tr_id ) ) + + isUploaded = True + else: + logger.info( 'The test run is already recorded on ProdDB and is a ready state, skipping' ) + - def __upload_functions(self, module_doc, result_doc, test_item, QC_info): - - if len(result_doc) == 1: + logger.info("Finished!!\n") + #time.sleep(1) + + return isUploaded + + + ''' + def __upload_functions(self, test_item, QC_info): + + + if len(self.ldb_test_doc) == 1: logger.info("This result format is not proper.") return - stageVersion = self.stage_test_map["stage_flows"][QC_info.get("stageVersion", "RD53A")] - for test in stageVersion[result_doc["currentStage"]]: - #if test["ldb_testType"] == "METROLOGY": - # if "PI" in module_doc["serialNumber"] and not "M1" in module_doc["serialNumber"] and not "RA" in module_doc["serialNumber"]: - # result_doc["testType"] = "METROLOGY_TRIPLET" - # else: - # result_doc["testType"] = "METROLOGY_QUAD" - if test["ldb_testType"] == result_doc["testType"]: - result_doc["testType"] = test["pdb_testType"] - break - functions = { - "OPTICAL" : self.__upload_VI, - "PIXEL_FAILURE_TEST" : self.__upload_electrical, - "READOUT_IN_BASIC_ELECTRICAL" : self.__upload_electrical, - "METROLOGY" : self.__upload_metrology, - "METROLOGY_QUAD" : self.__upload_metrologyquad, - "METROLOGY_TRIPLET" : self.__upload_metrologytriplet, - "BACKSIDE_COPLANARITY" : self.__upload_coplanarity, - "SENSOR_IV" : self.__upload_SensorIV, - "SLDO_VI" : self.__upload_SLDOVI, - "MASS" : self.__upload_mass, - "GLUE_MODULE_FLEX_ATTACH" : self.__upload_glueInformation, - "WIREBONDING" : self.__upload_wirebonding, - "WIREBOND" : self.__upload_wirebond, - "PARYLENE" : self.__upload_parylene, - "THERMALCYCLE" : self.__upload_thermal, - "ADC_CALIBRATION" : self.__upload_adccalibration, - "TUNING" : self.__upload_tuning + "OPTICAL" : self.__upload_VI, + "READOUT_IN_BASIC_ELECTRICAL_TEST" : self.__upload_electrical, + "METROLOGY" : self.__upload_metrology, + "QUAD_METROLOGY" : self.__upload_metrologyquad, + "TRIPLET_METROLOGY" : self.__upload_metrologytriplet, + "FLATNESS" : self.__upload_coplanarity, + "SENSOR_IV" : self.__upload_SensorIV, + "SLDO_VI" : self.__upload_SLDOVI, + "MASS" : self.__upload_mass, + "GLUE_MODULE_FLEX_ATTACH" : self.__upload_glueInformation, + "WIREBONDING" : self.__upload_wirebonding, + "WIREBOND" : self.__upload_wirebond, + "PARYLENE" : self.__upload_parylene, + "THERMALCYCLE" : self.__upload_thermal, + "ADC_CALIBRATION" : self.__upload_adccalibration, + "TUNING" : self.__upload_tuning #"WP_ENVELOPE" : self.__upload_wpenvelope } - - if result_doc != {} and result_doc["testType"] in functions: functions[result_doc["testType"]](module_doc, result_doc, test_item) + + ti = test_item.replace("_30DEG", "").replace("_20DEG", "").replace("_MINUS15DEG", "") + + self.ldb_test_doc['testType'] = ti + + if self.ldb_test_doc != {} and ti in functions: + functions[ti](self.pdb_component_doc, self.ldb_test_doc, ti ) else: logger.info("There is no uploading function.") return @@ -60,348 +169,349 @@ class QCResultUploader(PDInterface.PDInterface): "ORIENTATION" : self.__upload_orientation } - if prop_doc != {} and prop_doc["testType"] in prop_functions: prop_functions[prop_doc["testType"]](module_doc, prop_doc) + if prop_doc != {} and prop_doc["testType"] in prop_functions: prop_functions[prop_doc["testType"]](self.pdb_component_doc, prop_doc) else: logger.info("There is no uploading function.") return ######################## ## for non-electrical ## - def __upload_VI(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_VI(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createOpticalTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - attach_golden_imgs = self.__AttachGoldens(result_doc, new_test_result, test_item) + new_test_result = self.__createOpticalTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() + attach_golden_imgs = self.__AttachGoldens(new_test_result) else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createOpticalTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - attach_golden_imgs = self.__AttachGoldens(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createOpticalTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() + attach_golden_imgs = self.__AttachGoldens(new_test_result) + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_metrology(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_metrology(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createMetrologyTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createMetrologyTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createMetrologyTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createMetrologyTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_metrologyquad(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_metrologyquad(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createMetrologyQuadTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createMetrologyQuadTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createMetrologyQuadTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createMetrologyQuadTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_metrologytriplet(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_metrologytriplet(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createMetrologyTripletTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createMetrologyTripletTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createMetrologyTripletTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createMetrologyTripletTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_coplanarity(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_coplanarity(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createCoplanarityTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createCoplanarityTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createCoplanarityTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createCoplanarityTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_SensorIV(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_SensorIV(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) - tr_doc["testType"]["code"] = test_item + tr_doc["testType"]["code"] = self.testType try: - old_result_doc = self.__getOldResult(tr_doc) - if old_result_doc["0"]["testType"] == test_item: + previous_ldb_test_doc = self.__getOldResult(tr_doc) + if previous_ldb_test_doc["0"]["testType"] == self.testType: result_counter = result_counter + 1 - old_result_docs.append(old_result_doc["0"]["_id"]) + previous_ldb_test_docs.append(previous_ldb_test_doc["0"]["_id"]) except: logger.warning("this is not localdb results") if result_counter == 0: - new_test_result = self.__createSensorIVTestRun(result_doc, module_doc, str_date, test_item) - result_doc.pop("old_tests") - self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createSensorIVTestRun(timestampAsString, self.testType) + self.ldb_test_doc.pop("pdb_prev_testRuns") + self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: try: shutil.rmtree(cache_dir) except: pass logger.info("This result is already uploaded.") else: - new_test_result = self.__createSensorIVTestRun(result_doc, module_doc, str_date, test_item) - result_doc.pop("old_tests") - self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_docs[i], new_test_result["testRun"]["id"], old_result_docs[i]) + new_test_result = self.__createSensorIVTestRun(timestampAsString, self.testType) + self.ldb_test_doc.pop("pdb_prev_testRuns") + self.__createRawResultAttachment() + #self.__deleteResult(tr_docs[i], new_test_result["testRun"]["id"], previous_ldb_test_docs[i]) logger.info("Finished!!\n") #time.sleep(1) - def __upload_SLDOVI(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_SLDOVI(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createSLDOVITestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createSLDOVITestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createSLDOVITestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createSLDOVITestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_mass(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_mass(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createMassTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createMassTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createMassTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createMassTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_glueInformation(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_glueInformation(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createGlueTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createGlueTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createGlueTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createGlueTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_wirebonding(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_wirebonding(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createWirebondingTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - attach_bonding_program = self.__attachBondingProgram(result_doc, new_test_result) - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createWirebondingTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + attach_bonding_program = self.__attachBondingProgram(self.ldb_test_doc, new_test_result) + new_test_attachment = self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createWirebondingTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - attach_bonding_program = self.__attachBondingProgram(result_doc, new_test_result) - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createWirebondingTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + attach_bonding_program = self.__attachBondingProgram(self.ldb_test_doc, new_test_result) + new_test_attachment = self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) + ''' - def __attachBondingProgram(self, result_doc, new_test_result): + def __attachBondingProgram(self, localdb_test_doc, new_test_result): try: fsb = gridfs.GridFSBucket(localdb) bond_file = open('static/Bonding_Program.dat','wb+') - fsb.download_to_stream(ObjectId(result_doc["results"]["property"]["Bond_program"]), bond_file) + fsb.download_to_stream(ObjectId(self.ldb_test_doc["results"]["property"]["Bond_program"]), bond_file) bond_file.seek(0) contents = bond_file.read() except: @@ -413,109 +523,111 @@ class QCResultUploader(PDInterface.PDInterface): self.pd_client.post( "createTestRunAttachment", data=dict( testRun=new_test_result["testRun"]["id"], type="file" ), files=page_attachment ) return os.remove('static/Bonding_Program.dat') - def __upload_wirebond(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + ''' + def __upload_wirebond(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createWirebondTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createWirebondTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createWirebondTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createWirebondTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_parylene(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_parylene(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun": tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createParyleneTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createParyleneTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createParyleneTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createParyleneTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_thermal(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_thermal(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createThermalTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - attach_bonding_program = self.__attachThermalProgram(result_doc, new_test_result) - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createThermalTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + attach_bonding_program = self.__attachThermalProgram(self.ldb_test_doc, new_test_result) + new_test_attachment = self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createThermalTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - attach_bonding_program = self.__attachThermalProgram(result_doc, new_test_result) - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createThermalTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + attach_bonding_program = self.__attachThermalProgram(self.ldb_test_doc, new_test_result) + new_test_attachment = self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) + ''' - def __attachThermalProgram(self, result_doc, new_test_result): + def __attachThermalProgram(self, localdb_test_doc, new_test_result): try: fsb = gridfs.GridFSBucket(localdb) temp_file = open('static/Thermal_temp.json', 'wb+') - fsb.download_to_stream(ObjectId(result_doc["results"]["Temperature_log"]), temp_file) + fsb.download_to_stream(ObjectId(self.ldb_test_doc["results"]["Temperature_log"]), temp_file) temp_file.seek(0) contents = temp_file.read() except: @@ -531,7 +643,7 @@ class QCResultUploader(PDInterface.PDInterface): fsb = gridfs.GridFSBucket(localdb) humid_file = open('static/Thermal_humid.json', 'wb+') fsb.download_to_stream( - ObjectId(result_doc["results"]["Humidity_log"]), humid_file) + ObjectId(self.ldb_test_doc["results"]["Humidity_log"]), humid_file) humid_file.seek(0) contents = humid_file.read() except: @@ -550,129 +662,132 @@ class QCResultUploader(PDInterface.PDInterface): return - def __upload_adccalibration(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + ''' + def __upload_adccalibration(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun": tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createADCcalibrationTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createADCcalibrationTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createADCcalibrationTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createADCcalibrationTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_tuning(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_tuning(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun": tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createTuningTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createTuningTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createTuningTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createTuningTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) - def __upload_wpenvelope(self, module_doc, result_doc, test_item): - qr_oid = result_doc["_id"] + def __upload_wpenvelope(self): + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + previous_ldb_test_docs = [] + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: result_counter = result_counter + 1 tr_doc = self.pd_client.get("getTestRun", json={"testRun": tr["id"]}) try: - old_result_doc = self.__getOldResult(tr_doc) - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_doc = self.__getOldResult(tr_doc) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) except: logger.warning("This is not LocalDB result") if result_counter == 0: - new_test_result = self.__createWPenvelopeTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) + new_test_result = self.__createWPenvelopeTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: # shutil.rmtree("{}/attachments".format(".")) logger.info("This result is already uploaded.") else: - new_test_result = self.__createWPenvelopeTestRun(result_doc, module_doc, str_date) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_test_result, test_item) - #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], old_result_doc) + new_test_result = self.__createWPenvelopeTestRun(timestampAsString) + self.ldb_test_doc.pop("pdb_prev_testRuns") + new_test_attachment = self.__createRawResultAttachment() + #self.__deleteResult(tr_doc, new_test_result["testRun"]["id"], previous_ldb_test_doc) logger.info("Finished!!\n") #time.sleep(1) + ''' -#################### -## for electrical ## -## main function - def __upload_electrical(self, module_doc, result_doc, test_item): + #################### + ## for electrical ## + ## main function + ''' + def __upload_electrical(self): #check if a result is already uploaded or not - qr_oid = result_doc["_id"] + qr_oid = self.ldb_test_doc["_id"] qr_doc = super().getQrFromLocalDB(qr_oid) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) result_counter = 0 - old_result_docs = [] + previous_ldb_test_docs = [] tr_docs = [] - for tr in result_doc["old_tests"]: - if tr["state"] == "ready" and tr["testType"]["code"] == result_doc["testType"] and tr["stage"]["code"] == result_doc["currentStage"]: + for tr in self.ldb_test_doc["pdb_prev_testRuns"]: + if tr["state"] == "ready" and tr["testType"]["code"] == self.testType and tr["stage"]["code"] == self.ldb_test_doc["currentStage"]: tr_doc = self.pd_client.get("getTestRun", json={"testRun":tr["id"]}) - tr_doc["testType"]["code"] = test_item + tr_doc["testType"]["code"] = self.testType try: - old_result_doc = self.__getOldResult(tr_doc) - if old_result_doc["0"]["testType"] == test_item: + previous_ldb_test_doc = self.__getOldResult(tr_doc) + if previous_ldb_test_doc["0"]["testType"] == self.testType: result_counter = result_counter + 1 - old_result_docs.append(old_result_doc['0']['_id']) + previous_ldb_test_docs.append(previous_ldb_test_doc['0']['_id']) tr_docs.append(tr_doc) except: logger.warning("Results not uploaded from LocalDB exist") if result_counter == 0: - self.__create_electrial_pdb(module_doc, result_doc, test_item) + self.__create_electrial_pdb(self.pdb_component_doc, self.ldb_test_doc, self.testType) else: - if str(result_doc["_id"]) in old_result_docs: + if str(self.ldb_test_doc["_id"]) in previous_ldb_test_docs: try: shutil.rmtree(cache_dir) except: @@ -680,25 +795,26 @@ class QCResultUploader(PDInterface.PDInterface): logger.info("This result is already uploaded.") return else: - self.__create_electrial_pdb(module_doc, result_doc, test_item) + self.__create_electrial_pdb(self.pdb_component_doc, self.ldb_test_doc, self.testType) #for result in tr_docs[i]['results']: # if result['code'] == "RESULT_IDS": # for scan_id in result['value']: # self.pd_client.post("deleteTestRun", json={"testRun": scan_id}) - #self.__deleteResult(tr_docs[i], new_test_result["testRun"]["id"], old_result_docs[i]) + #self.__deleteResult(tr_docs[i], new_test_result["testRun"]["id"], previous_ldb_test_docs[i]) logger.info("Finished!!\n") return #time.sleep(1) + ''' - def __create_electrial_pdb(self, module_doc, result_doc, test_item): + def __create_electrial_pdb(self): cp_test_map = {} - str_date = None + timestampAsString = None scans = [] #create cache files - for i, d_ in enumerate(result_doc["results"]["scans"]): + for i, d_ in enumerate(self.ldb_test_doc["results"]["scans"]): scanType, tr_oid = d_["name"], d_["runId"] scans.append(d_["name"]) path = "{}/{}".format(IF_DIR,d_["name"]) @@ -706,54 +822,55 @@ class QCResultUploader(PDInterface.PDInterface): command = ["{}/itkpd-interface/localdb-interface/bin/localdbtool-retrieve".format(VIEWER_DIR), "pull", "--test", d_["runId"], "--directory", path] subprocess.call(command) if i == 0: - qr_doc = super().getQrFromLocalDB(result_doc["_id"]) - str_date = self.__getQCStrTime(qr_doc["sys"]["cts"]) -# tr_doc = super().getTrFromLocalDB(tr_oid) -# str_date = self.__getStrTime(tr_doc["timestamp"]) - -# fix scan result's files if component name is changed + qr_doc = super().getQrFromLocalDB(self.ldb_test_doc["_id"]) + timestampAsString = self.__getQCStrTime(qr_doc["sys"]["cts"]) + # tr_doc = super().getTrFromLocalDB(tr_oid) + # timestampAsString = self.__getStrTime(tr_doc["timestamp"]) + + # fix scan result's files if component name is changed scan_files = os.listdir(path) # create relationship between past & new components - old_chips = [] + previous_chips = [] new_chips = [] - old_module_name = "" + previous_module_name = "" new_module_name = "" compTestRun = localdb.componentTestRun.find({"testRun" : d_["runId"]}) for item in compTestRun: if "scanSN" in item: if item["chip"] == "module": - old_module_name = item["scanSN"] + previous_module_name = item["scanSN"] new_module_name = item["name"] else: - old_chips.append(item["scanSN"]) + previous_chips.append(item["scanSN"]) new_chips.append(item["name"]) - if old_module_name: + if previous_module_name: # fix contents of scan files for file in scan_files: f1 = open(path + "/" + file, 'r', encoding='utf-8').read() f2 = open(path + "/" + file, 'w', encoding='utf-8') - for i in range(len(old_chips)): - f1 = f1.replace(old_chips[i], new_chips[i]) + for i in range(len(previous_chips)): + f1 = f1.replace(previous_chips[i], new_chips[i]) if file == "connectivity.json" or file == "scanLog.json": - f1 = f1.replace(old_module_name, new_module_name) + f1 = f1.replace(previous_module_name, new_module_name) f1 = f1.replace(path + "//", "/") f2.write(f1) # fix file name for file in scan_files: - for i in range(len(old_chips)): - if old_chips[i] in file: - new_file = file.replace(old_chips[i], new_chips[i]) + for i in range(len(previous_chips)): + if previous_chips[i] in file: + new_file = file.replace(previous_chips[i], new_chips[i]) os.rename(path + "/" + file, path + "/" + new_file) #for FE chips - fe_docs = super().getFEchipsDocs(module_doc["serialNumber"]) + fe_docs = super().getFEchipsDocs(self.pdb_component_doc["serialNumber"]) for fe_doc in fe_docs: - new_test_result = self.__createElecTestRunForFEChip(result_doc, fe_doc["serialNumber"], str_date) + new_test_result = self.__createElecTestRunForFEChip(fe_doc["serialNumber"], timestampAsString) cp_test_map[fe_doc["serialNumber"]] = new_test_result["testRun"]["id"] for scan in scans: + logger.info('{}'.format( scan ) ) zip_name = "{}_{}".format(fe_doc["serialNumber"],scan) zip_path = "{0}/{1}.zip".format(IF_DIR, zip_name) if os.path.exists(zip_path): os.remove(zip_path) @@ -766,11 +883,13 @@ class QCResultUploader(PDInterface.PDInterface): self.__attachFileToTestRun(zip_path, scan + ".zip", cp_test_map[fe_doc["serialNumber"]]) #for module - new_module_result = self.__createElecTestRunForModule(result_doc, module_doc, str_date, cp_test_map, test_item) - result_doc.pop("old_tests") - new_test_attachment = self.__createRawResultAttachment(result_doc, new_module_result, test_item) + new_module_result = self.__createElecTestRunForModule(timestampAsString, cp_test_map, self.testType) + logger.info('here') + new_test_attachment = self.__createRawResultAttachment( new_module_result ) + logger.info('here') for i, scan in enumerate(scans): - zip_name = "{}_{}".format(module_doc["serialNumber"],scan) + logger.info('here2 {}'.format( scan ) ) + zip_name = "{}_{}".format(self.pdb_component_doc["serialNumber"],scan) zip_path = "{0}/{1}.zip".format(IF_DIR, zip_name) if os.path.exists(zip_path): os.remove(zip_path) zip_file = zipfile.ZipFile(zip_path, "a", zipfile.ZIP_DEFLATED) @@ -779,7 +898,7 @@ class QCResultUploader(PDInterface.PDInterface): filename = filepath.split("/")[-1] if i == 0 and (filename.split(".")[0] in [ "dbCfg", "userCfg", "siteCfg" ]): self.__attachFileToTestRun(filepath, filename, new_module_result["testRun"]["id"]) if filename.split(".")[0] in [ scan, "scanLog"]: - if filename.split(".")[0] == "scanLog": self.__modifyScanLog(filepath, result_doc["currentStage"]) + if filename.split(".")[0] == "scanLog": self.__modifyScanLog(filepath, self.ldb_test_doc["currentStage"]) zip_file.write(filepath, filename) zip_file.close() @@ -787,8 +906,9 @@ class QCResultUploader(PDInterface.PDInterface): # attach the best configs to the module's top page. for scan in scans: + logger.info('here3 {}'.format( scan ) ) if scan == "std_thresholdscan": - zip_name = "{}_{}_degree_{}".format("BestCfg",str(self.__getElecTestSettingTemp(result_doc, test_item)).replace("-","minus"), result_doc["currentStage"]) + zip_name = "{}_{}_degree_{}".format("BestCfg",str(self.testSetTemp).replace("-","minus"), self.ldb_test_doc["currentStage"]) zip_path = "{0}/{1}.zip".format(IF_DIR, zip_name) if os.path.exists(zip_path): os.remove(zip_path) zip_file = zipfile.ZipFile(zip_path, "a", zipfile.ZIP_DEFLATED) @@ -796,7 +916,7 @@ class QCResultUploader(PDInterface.PDInterface): filepath = "{}/{}/{}".format(IF_DIR,scan,fe_doc["serialNumber"] + ".json.after") if os.path.exists(filepath): zip_file.write(filepath, filepath.split("/")[-1]) zip_file.close() - self.__attachFileToComp(zip_path, zip_name + ".zip", module_doc) + self.__attachFileToComp(zip_path, zip_name + ".zip", self.pdb_component_doc) return new_module_result @@ -808,24 +928,24 @@ class QCResultUploader(PDInterface.PDInterface): def __upload_pullupregistor(self, module_doc, prop_doc): pr_oid = prop_doc["_id"] pr_doc = super().getQrFromLocalDB(pr_oid) - new_prop_result = self.__createPullupTestRun(prop_doc, module_doc) - new_prop_attachment = self.__createPropertyAttachment(prop_doc, module_doc) + new_prop_result = self.__createPullupTestRun(prop_doc, self.pdb_component_doc) + new_prop_attachment = self.__createPropertyAttachment(prop_doc, self.pdb_component_doc) logger.info("Finished!!\n") def __upload_ireftrim(self, module_doc, prop_doc): pr_oid = prop_doc["_id"] pr_doc = super().getQrFromLocalDB(pr_oid) - new_prop_result = self.__createIreftrimTestRun(prop_doc, module_doc) - new_prop_attachment = self.__createPropertyAttachment(prop_doc, module_doc) + new_prop_result = self.__createIreftrimTestRun(prop_doc, self.pdb_component_doc) + new_prop_attachment = self.__createPropertyAttachment(prop_doc, self.pdb_component_doc) logger.info("Finished!!\n") def __upload_orientation(self, module_doc, prop_doc): pr_oid = prop_doc["_id"] pr_doc = super().getQrFromLocalDB(pr_oid) - new_prop_result = self.__createOrientationTestRun(prop_doc, module_doc) - new_prop_attachment = self.__createPropertyAttachment(prop_doc, module_doc) + new_prop_result = self.__createOrientationTestRun(prop_doc, self.pdb_component_doc) + new_prop_attachment = self.__createPropertyAttachment(prop_doc, self.pdb_component_doc) logger.info("Finished!!\n") @@ -845,81 +965,81 @@ class QCResultUploader(PDInterface.PDInterface): return this_time - def __createTestTemplate(self, doc, testType, str_date): - project = {"project": doc["project"]["code"]} - componentType = {"componentType": doc["componentType"]["code"]} - pd_testType = {"code": testType} + def __createTestTemplate(self, timestampAsString): + + project = {"project": self.pdb_component_doc["project"]["code"]} + componentType = {"componentType": self.pdb_component_doc["componentType"]["code"]} + pd_testType = {"code": self.testType} test_template = self.pd_client.get("generateTestTypeDtoSample", json={**project, **componentType, **pd_testType}) + json_template = { **test_template, - "component": doc["serialNumber"], - "institution": doc["currentLocation"]["code"], - "date": str_date + "component": self.pdb_component_doc["serialNumber"], + "institution": self.pdb_component_doc["currentLocation"]["code"], + "date": timestampAsString, + "runNumber": str( self.ldb_test_doc["_id"] ) } return json_template - def __createRawResultAttachment(self, result_doc, new_test_result, test_item): - attachment_doc = result_doc + def __createRawResultAttachment(self, test_result ): + attachment_doc = self.ldb_test_doc attachment_doc["_id"] = str(attachment_doc["_id"]) - pdb_testType = result_doc["testType"] - attachment_doc["sys"]["cts"] = result_doc["sys"]["cts"].isoformat() - attachment_doc["sys"]["mts"] = result_doc["sys"]["mts"].isoformat() - if "PIXEL_FAILURE_TEST" in result_doc["testType"]: + pdb_testType = self.testType + + pprint.pprint( self.ldb_test_doc ) + + try: + attachment_doc["sys"]["cts"] = self.ldb_test_doc["sys"]["cts"].isoformat() + attachment_doc["sys"]["mts"] = self.ldb_test_doc["sys"]["mts"].isoformat() + attachment_doc["startTime"] = self.ldb_test_doc["startTime"].isoformat() + except: try: - attachment_doc["startTime"] = result_doc["startTime"].isoformat() + attachment_doc["sys"]["cts"] = self.ldb_test_doc["sys"]["cts"] + attachment_doc["sys"]["mts"] = self.ldb_test_doc["sys"]["mts"] + attachment_doc["startTime"] = self.ldb_test_doc["startTime"] except: pass - f = open(result_doc["testType"] + '_results.json', 'w') - attachment_doc["testType"] = str(test_item) + + f = open(self.testType + '_results.json', 'w') + attachment_doc["testType"] = str(self.testType) + #logger.info( 'attachment_doc = {}'.format( attachment_doc ) ) json.dump(attachment_doc, f, indent=4) f.close() attachment_file = open(pdb_testType + '_results.json', 'rb') page_attachment = {"data": (pdb_testType + '_results.json', attachment_file, "json")} - self.pd_client.post( "createTestRunAttachment", data=dict( testRun=new_test_result["testRun"]["id"], type="file" ), files=page_attachment ) + self.pd_client.post( "createTestRunAttachment", data=dict( testRun = test_result["testRun"]["id"], type="file" ), files=page_attachment ) return os.remove(pdb_testType + '_results.json') ####################### ## for Optical Inspection - def __createOpticalTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) + def __createOpticalTestRun(self, json, timestampAsString): json["results"]["FULL_IMAGE"] = "" - new_test_result = self.pd_client.post("uploadTestRunResults",json=json) - - pic_name = result_doc["currentStage"] + "_optical_img" + ".png" - inspect_pic = result_doc["results"]["img_entire"]["target"] - data = fs.get(ObjectId(str(inspect_pic))).read() - with open(pic_name, 'wb') as f: - f.write(data) - f = open(pic_name, "rb") - os.remove(pic_name) - self.pd_client.post("createBinaryTestRunParameter", data=dict(testRun=new_test_result["testRun"]["id"], parameter="FULL_IMAGE"), files=dict(data=f) ) - f.close() - return new_test_result + return json - def __AttachGoldens(self, result_doc, new_test_result, test_item): - target_name = str(result_doc["results"]["img_entire"]["target"]) + ".png" - target_pic = result_doc["results"]["img_entire"]["target"] + def __AttachGoldens(self, new_test_result): + target_name = str(self.ldb_test_doc["results"]["img_entire"]["target"]) + ".png" + target_pic = self.ldb_test_doc["results"]["img_entire"]["target"] data_target = fs.get(ObjectId(str(target_pic))).read() with open(target_name, 'wb') as f: f.write(data_target) - golden_name = str(result_doc["results"]["img_entire"]["reference"]) + ".png" - golden_pic = result_doc["results"]["img_entire"]["reference"] + golden_name = str(self.ldb_test_doc["results"]["img_entire"]["reference"]) + ".png" + golden_pic = self.ldb_test_doc["results"]["img_entire"]["reference"] data_golden = fs.get(ObjectId(str(golden_pic))).read() with open(golden_name, 'wb') as f: f.write(data_golden) tiles = [] - for i, item in enumerate(result_doc["results"]["img_tile"]): + for i, item in enumerate(self.ldb_test_doc["results"]["img_tile"]): tiles.append({}) - tiles[i]["name"] = str(result_doc["results"]["img_tile"][item]) + ".png" - tiles[i]["pic"] = result_doc["results"]["img_tile"][item] + tiles[i]["name"] = str(self.ldb_test_doc["results"]["img_tile"][item]) + ".png" + tiles[i]["pic"] = self.ldb_test_doc["results"]["img_tile"][item] tiles[i]["data"] = fs.get(ObjectId(str(tiles[i]["pic"]))).read() with open(tiles[i]["name"], 'wb') as f: f.write(tiles[i]["data"]) @@ -964,67 +1084,63 @@ class QCResultUploader(PDInterface.PDInterface): ####################### ## for Mass measurement - def __createMassTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) - json["properties"]["ACCURACY"] = float('{:.3g}'.format(float(result_doc["results"]["property"]["Scale_accuracy"]))) - json["results"]["MASS"] = float('{:.3g}'.format(float(result_doc["results"]["mass_value"]))) - json["comments"] = [result_doc["results"]["comment"]] + def __createMassTestRun(self, json, timestampAsString): + json["properties"]["ACCURACY"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["property"]["Scale_accuracy"]))) + json["results"]["MASS"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["mass_value"]))) + json["comments"] = [self.ldb_test_doc["results"]["comment"]] - return self.pd_client.post("uploadTestRunResults",json=json) + return json ################ ## for Metrology - def __createMetrologyTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) - json["results"]["DISTANCE_TOP"] = float('{:.3g}'.format(float(result_doc["results"]["distance top"]))) - json["results"]["DISTANCE_LEFT"] = float('{:.3g}'.format(float(result_doc["results"]["distance left"]))) - json["results"]["DISTANCE_RIGHT"] = float('{:.3g}'.format(float(result_doc["results"]["distance right"]))) - json["results"]["DISTANCE_BOTTOM"] = float('{:.3g}'.format(float(result_doc["results"]["distance bottom"]))) + def __createMetrologyTestRun(self, json, timestampAsString): + json["results"]["DISTANCE_TOP"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["distance top"]))) + json["results"]["DISTANCE_LEFT"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["distance left"]))) + json["results"]["DISTANCE_RIGHT"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["distance right"]))) + json["results"]["DISTANCE_BOTTOM"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["distance bottom"]))) json["results"]["ANGLE_OF_BARE_VS_FLEX"] = [ - float('{:.3g}'.format(float(result_doc["results"]["angle top-left"]))), - float('{:.3g}'.format(float(result_doc["results"]["angle top-right"]))), - float('{:.3g}'.format(float(result_doc["results"]["angle bottom-left"]))), - float('{:.3g}'.format(float(result_doc["results"]["angle bottom-right"]))) + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["angle top-left"]))), + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["angle top-right"]))), + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["angle bottom-left"]))), + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["angle bottom-right"]))) ] json["results"]["MODULE_THICKNESS_PICKUP_AREA"] = [ - float('{:.3g}'.format(float(result_doc["results"]["module thickness pickup area chip1"]))), - float('{:.3g}'.format(float(result_doc["results"]["module thickness pickup area chip2"]))), - float('{:.3g}'.format(float(result_doc["results"]["module thickness pickup area chip3"]))), - float('{:.3g}'.format(float(result_doc["results"]["module thickness pickup area chip4"]))) + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["module thickness pickup area chip1"]))), + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["module thickness pickup area chip2"]))), + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["module thickness pickup area chip3"]))), + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["module thickness pickup area chip4"]))) ] try: json["results"]["MODULE_THICKNESS_EDGE"] = [ - float('{:.3g}'.format(float(result_doc["results"]["module thickness edge chip1"]))), - float('{:.3g}'.format(float(result_doc["results"]["module thickness edge chip2"]))), - float('{:.3g}'.format(float(result_doc["results"]["module thickness edge chip3"]))), - float('{:.3g}'.format(float(result_doc["results"]["module thickness edge chip4"]))) + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["module thickness edge chip1"]))), + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["module thickness edge chip2"]))), + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["module thickness edge chip3"]))), + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["module thickness edge chip4"]))) ] except: json["results"]["MODULE_THICKNESS_EDGE"] = [1.0, 1.0, 1.0, 1.0] - json["results"]["MODULE_THICKNESS_HV_CAPACITOR"] = float('{:.3g}'.format(float(result_doc["results"]["module thickness HV capacitor"]))) - json["results"]["MODULE_THICKNESS_DATA_CONNECTOR"] = float('{:.3g}'.format(float(result_doc["results"]["module thickness Data connector"]))) + json["results"]["MODULE_THICKNESS_HV_CAPACITOR"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["module thickness HV capacitor"]))) + json["results"]["MODULE_THICKNESS_DATA_CONNECTOR"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["module thickness Data connector"]))) try: - json["results"]["PLANARITY_VACUUM_ON"] = float('{:.3g}'.format(float(result_doc["results"]["planarity vacuum on"]))) - json["results"]["PLANARITY_VACUUM_OFF"] = float('{:.3g}'.format(float(result_doc["results"]["planarity vacuum off"]))) - json["results"]["PLANARITY_VACUUM_ON_STD_DEV"] = float('{:.3g}'.format(float(result_doc["results"]["planarity vacuum on std dev"]))) - json["results"]["PLANARITY_VACUUM_OFF_STD_DEV"] = float('{:.3g}'.format(float(result_doc["results"]["planarity vacuum off std dev"]))) + json["results"]["PLANARITY_VACUUM_ON"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["planarity vacuum on"]))) + json["results"]["PLANARITY_VACUUM_OFF"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["planarity vacuum off"]))) + json["results"]["PLANARITY_VACUUM_ON_STD_DEV"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["planarity vacuum on std dev"]))) + json["results"]["PLANARITY_VACUUM_OFF_STD_DEV"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["planarity vacuum off std dev"]))) except: json["results"]["PLANARITY_VACUUM_ON"] = 1.0 json["results"]["PLANARITY_VACUUM_OFF"] = 1.0 json["results"]["PLANARITY_VACUUM_ON_STD_DEV"] = 1.0 json["results"]["PLANARITY_VACUUM_OFF_STD_DEV"] = 1.0 - json["comments"] = [result_doc["results"]["comment"], "No results about Thickness Edge and Planarity Vacuum on/off"] + json["comments"] = [self.ldb_test_doc["results"]["comment"], "No results about Thickness Edge and Planarity Vacuum on/off"] - return self.pd_client.post("uploadTestRunResults",json=json) - - def __createMetrologyQuadTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) + return json + def __createMetrologyQuadTestRun(self, json, timestampAsString): json["results"]["DISTANCE_TOP"] = "" json["results"]["Z_AVERAGE"] = "" json["results"]["DISTANCE_LEFT"] = "" @@ -1039,13 +1155,11 @@ class QCResultUploader(PDInterface.PDInterface): json["results"]["THICKNESS_LEFT_EDGE"] = "" json["results"]["THICKNESS_RIGHT_EDGE"] = "" - json["comments"] = [result_doc["results"]["comment"]] + json["comments"] = [self.ldb_test_doc["results"]["comment"]] - return self.pd_client.post("uploadTestRunResults",json=json) - - def __createMetrologyTripletTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) + return json + def __createMetrologyTripletTestRun(self, json, timestampAsString): json["results"]["DISPLACEMENT_X"] = [] json["results"]["DISPLACEMENT_Y"] = [] json["results"]["THICKNESS_GLUE"] = [] @@ -1058,36 +1172,33 @@ class QCResultUploader(PDInterface.PDInterface): json["results"]["THICKNESS_GLUE_STD_DEV"] = [] json["results"]["FLATNESS"] = [] - json["comments"] = [result_doc["results"]["comment"]] + json["comments"] = [self.ldb_test_doc["results"]["comment"]] - return self.pd_client.post("uploadTestRunResults",json=json) + return json ################## ## for coplanarity - def __createCoplanarityTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) - + def __createCoplanarityTestRun(self, json, timestampAsString): json["results"]["ANGLE"] = [ - float('{:.3g}'.format(float(result_doc["results"]["angle alpha"]))), - float('{:.3g}'.format(float(result_doc["results"]["angle beta"]))) + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["angle alpha"]))), + float('{:.3g}'.format(float(self.ldb_test_doc["results"]["angle beta"]))) ] - json["results"]["BACKSIDE_COPLANARITY"] = float('{:.3g}'.format(float(result_doc["results"]["coplanarity"]))) + json["results"]["BACKSIDE_COPLANARITY"] = float('{:.3g}'.format(float(self.ldb_test_doc["results"]["coplanarity"]))) - json["comments"] = [result_doc["results"]["comment"]] + json["comments"] = [self.ldb_test_doc["results"]["comment"]] - return self.pd_client.post("uploadTestRunResults",json=json) + return json ################ ## for Sensor IV - def __createSensorIVTestRun(self, result_doc, module_doc, str_date, test_item): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) + def __createSensorIVTestRun(self, json, timestampAsString): json["results"]["TIME"] = [] json["results"]["CURRENT_MEAN"] = [] json["results"]["CURRENT_SIGMA"] = [] json["results"]["VOLTAGE"] = [] json["results"]["HUMIDITY"] = [] json["results"]["TEMPERATURE"] = [] - for items in result_doc["results"]["Sensor_IV"]: + for items in self.ldb_test_doc["results"]["Sensor_IV"]: json["results"]["TIME"].append(float('{:.3g}'.format(items["Time"]))) json["results"]["CURRENT_MEAN"].append(float('{:.3g}'.format(items["Current_mean"]))) json["results"]["CURRENT_SIGMA"].append(float('{:.3g}'.format(items["Current_sigma"]))) @@ -1096,29 +1207,21 @@ class QCResultUploader(PDInterface.PDInterface): json["results"]["HUMIDITY"].append(float('{:.3g}'.format(items["Humidity"]))) if "Temperature" in items: json["results"]["TEMPERATURE"].append(float('{:.3g}'.format(items["Temperature"]))) - json["comments"] = [result_doc["results"]["comment"]] - if test_item == "SENSOR_IV_30_DEGREE": - json["properties"]["ENVIRONMENT_TEMPERATURE"] = 30 - elif test_item == "SENSOR_IV_20_DEGREE": - json["properties"]["ENVIRONMENT_TEMPERATURE"] = 20 - elif test_item == "SENSOR_IV_min15_DEGREE": - json["properties"]["ENVIRONMENT_TEMPERATURE"] = -15 - else: - json["properties"]["ENVIRONMENT_TEMPERATURE"] = 99999 + json["comments"] = [self.ldb_test_doc["results"]["comment"]] + json["properties"]["ENVIRONMENT_TEMPERATURE"] = self.testSetTemp - return self.pd_client.post("uploadTestRunResults",json=json) + return json ############## ## for SLDO VI - def __createSLDOVITestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) + def __createSLDOVITestRun(self, json, timestampAsString): json["results"]["TIME"] = [] json["results"]["CURRENT"] = [] json["results"]["VOLTAGE_MEAN"] = [] json["results"]["VOLTAGE_SIGMA"] = [] json["results"]["HUMIDITY"] = [] json["results"]["TEMPERATURE"] = [] - for items in result_doc["results"]["SLDO_VI"]: + for items in self.ldb_test_doc["results"]["SLDO_VI"]: json["results"]["TIME"].append(float('{:.3g}'.format(items["Time"]))) json["results"]["CURRENT"].append(float('{:.3g}'.format(items["Current"]))) json["results"]["VOLTAGE_MEAN"].append(float('{:.3g}'.format(items["Voltage_mean"]))) @@ -1127,124 +1230,113 @@ class QCResultUploader(PDInterface.PDInterface): json["results"]["HUMIDITY"].append(float('{:.3g}'.format(items["Humidity"]))) if "Temperature" in items: json["results"]["TEMPERATURE"].append(float('{:.3g}'.format(items["Temperature"]))) - json["comments"] = [result_doc["results"]["comment"]] + json["comments"] = [self.ldb_test_doc["results"]["comment"]] - return self.pd_client.post("uploadTestRunResults",json=json) + return json ################################ ## for Glue module + flex attach - def __createGlueTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) - json["properties"]["GLUE_TYPE"] = result_doc["results"]["property"]["Glue_name"] - Main_A = result_doc["results"]["property"]["Volume_ratio_of_glue_mixture"].split(":")[1].split(",")[0] - Sub_B = result_doc["results"]["property"]["Volume_ratio_of_glue_mixture"].split(":")[2] + def __createGlueTestRun(self, json, timestampAsString): + json["properties"]["GLUE_TYPE"] = self.ldb_test_doc["results"]["property"]["Glue_name"] + Main_A = self.ldb_test_doc["results"]["property"]["Volume_ratio_of_glue_mixture"].split(":")[1].split(",")[0] + Sub_B = self.ldb_test_doc["results"]["property"]["Volume_ratio_of_glue_mixture"].split(":")[2] json["properties"]["RATIO"] = float(Main_A)/float(Sub_B) - json["properties"]["NAME"] = result_doc["user"] - json["properties"]["BATCH_NUMBER"] = result_doc["results"]["property"]["Glue_batch_number"] - json["results"]["TEMP"] = result_doc["results"]["Room_temperature"] - json["results"]["HUMIDITY"] = result_doc["results"]["Humidity"] - json["comments"] = [result_doc["results"]["comment"]] + json["properties"]["NAME"] = self.ldb_test_doc["user"] + json["properties"]["BATCH_NUMBER"] = self.ldb_test_doc["results"]["property"]["Glue_batch_number"] + json["results"]["TEMP"] = self.ldb_test_doc["results"]["Room_temperature"] + json["results"]["HUMIDITY"] = self.ldb_test_doc["results"]["Humidity"] + json["comments"] = [self.ldb_test_doc["results"]["comment"]] - return self.pd_client.post("uploadTestRunResults",json=json) + return json ################## ## for Wirebonding - def __createWirebondingTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) - json["properties"]["MACHINE"] = result_doc["results"]["property"]["Machine"] - json["properties"]["OPERATOR"] = result_doc["results"]["property"]["Operator_name"] - json["properties"]["BOND_WIRE_BATCH"] = result_doc["results"]["property"]["Bond_wire_batch"] - if result_doc["results"]["property"]["Bond_program"] != '': + def __createWirebondingTestRun(self, json, timestampAsString): + json["properties"]["MACHINE"] = self.ldb_test_doc["results"]["property"]["Machine"] + json["properties"]["OPERATOR"] = self.ldb_test_doc["results"]["property"]["Operator_name"] + json["properties"]["BOND_WIRE_BATCH"] = self.ldb_test_doc["results"]["property"]["Bond_wire_batch"] + if self.ldb_test_doc["results"]["property"]["Bond_program"] != '': json["properties"]["BOND_PROGRAM"] = "Attached Bond Program" else: json["properties"]["BOND_PROGRAM"] = "No Bond Program" - json["properties"]["BONDING_JIG"] = result_doc["results"]["property"]["Bonding_jig"] - json["results"]["TEMPERATURE"] = result_doc["results"]["Temperature"] - json["results"]["HUMIDITY"] = result_doc["results"]["Humidity"] - json["comments"] = [result_doc["results"]["comment"]] + json["properties"]["BONDING_JIG"] = self.ldb_test_doc["results"]["property"]["Bonding_jig"] + json["results"]["TEMPERATURE"] = self.ldb_test_doc["results"]["Temperature"] + json["results"]["HUMIDITY"] = self.ldb_test_doc["results"]["Humidity"] + json["comments"] = [self.ldb_test_doc["results"]["comment"]] - return self.pd_client.post("uploadTestRunResults",json=json) + return json ############### ## for Wirebond - def __createWirebondTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) + def __createWirebondTestRun(self, json, timestampAsString): json["properties"]["MACHINE"] = "No result" json["properties"]["SPEED"] = 0 json["properties"]["SHEAR"] = 0 json["properties"]["LOAD"] = 0 json["properties"]["OPERATOR"] = "No result" - json["results"]["MIN_LOAD"] = result_doc["results"]["minimum_load"] - json["results"]["MAX_LOAD"] = result_doc["results"]["maximum_load"] - json["results"]["MEAN_LOAD"] = result_doc["results"]["mean_load"] - json["results"]["STD_DEV_LOAD"] = result_doc["results"]["load_standard_deviation"] - json["results"]["PERCENT_HEEL_BREAKS"] = result_doc["results"]["percentage_of_heel_breaks"] - json["comments"] = ["No properties for this test run" , result_doc["results"]["comment"]] + json["results"]["MIN_LOAD"] = self.ldb_test_doc["results"]["minimum_load"] + json["results"]["MAX_LOAD"] = self.ldb_test_doc["results"]["maximum_load"] + json["results"]["MEAN_LOAD"] = self.ldb_test_doc["results"]["mean_load"] + json["results"]["STD_DEV_LOAD"] = self.ldb_test_doc["results"]["load_standard_deviation"] + json["results"]["PERCENT_HEEL_BREAKS"] = self.ldb_test_doc["results"]["percentage_of_heel_breaks"] + json["comments"] = ["No properties for this test run" , self.ldb_test_doc["results"]["comment"]] - return self.pd_client.post("uploadTestRunResults",json=json) + return json ##################### ## for Parylene info. - def __createParyleneTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"],str_date) + def __createParyleneTestRun(self, json, timestampAsString): + json["properties"]["BATCH_NUMBER"] = self.ldb_test_doc["results"]["property"]["Parylene_Batch_Number"] + json["properties"]["PARYLENE_TYPE"] = self.ldb_test_doc["results"]["property"]["Parylene_Type"] + json["properties"]["MASKING_OPERATOR"] = self.ldb_test_doc["results"]["property"]["Institution_of_Masking_Operator"] + json["propertie"]["REMOVING_MASK_OPERATOR"] = self.ldb_test_doc["results"]["property"]["Institution_of_Operator_Removing_Mask"] - json["properties"]["BATCH_NUMBER"] = result_doc["results"]["property"]["Parylene_Batch_Number"] - json["properties"]["PARYLENE_TYPE"] = result_doc["results"]["property"]["Parylene_Type"] - json["properties"]["MASKING_OPERATOR"] = result_doc["results"]["property"]["Institution_of_Masking_Operator"] - json["propertie"]["REMOVING_MASK_OPERATOR"] = result_doc["results"]["property"]["Institution_of_Operator_Removing_Mask"] + json["results"]["THICKNESS"] = self.ldb_test_doc["results"]["Parylene_thickness_measured_by_vendor"] + json["results"]["THICKNESS_ITK"] = self.ldb_test_doc["results"]["Parylene_thickness_measured_by_ITk_Institute"] - json["results"]["THICKNESS"] = result_doc["results"]["Parylene_thickness_measured_by_vendor"] - json["results"]["THICKNESS_ITK"] = result_doc["results"]["Parylene_thickness_measured_by_ITk_Institute"] - - return self.pd_client.post("uploadTestRunResults", json=json) + return json ##################### ### for Thermal Cycle - def __createThermalTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"], str_date) - - json["properties"]["MACHINE"] = result_doc["results"]["property"]["Machine"] - json["properties"]["MIN_TEMP"] = result_doc["results"]["property"]["Temp_min_value"] - json["properties"]["MAX_TEMP"] = result_doc["results"]["property"]["Temp_max_value"] - json["properties"]["NUM_CYCLES"] = result_doc["results"]["property"]["N_cycle"] - json["properties"]["CYCLING_SPEED"] = result_doc["results"]["property"]["Cycle_speed_value"] + def __createThermalTestRun(self, json, timestampAsString): + json["properties"]["MACHINE"] = self.ldb_test_doc["results"]["property"]["Machine"] + json["properties"]["MIN_TEMP"] = self.ldb_test_doc["results"]["property"]["Temp_min_value"] + json["properties"]["MAX_TEMP"] = self.ldb_test_doc["results"]["property"]["Temp_max_value"] + json["properties"]["NUM_CYCLES"] = self.ldb_test_doc["results"]["property"]["N_cycle"] + json["properties"]["CYCLING_SPEED"] = self.ldb_test_doc["results"]["property"]["Cycle_speed_value"] - return self.pd_client.post("uploadTestRunResults",json=json) + return json ###################### ## for ADC calibration - def __createADCcalibrationTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"],str_date) - json["results"]["FLAG"] = eval(result_doc["results"]["flag"]) + def __createADCcalibrationTestRun(self, json, timestampAsString): + json["results"]["FLAG"] = eval(self.ldb_test_doc["results"]["flag"]) - return self.pd_client.post("uploadTestRunResults", json=json) + return json ############# ## for Tuning - def __createTuningTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"],str_date) - json["results"]["FLAG"] = eval(result_doc["results"]["flag"]) + def __createTuningTestRun(self, json, timestampAsString): + json["results"]["FLAG"] = eval(self.ldb_test_doc["results"]["flag"]) - return self.pd_client.post("uploadTestRunResults", json=json) + return json ############# ## for WP envelope metrology - def __createWPenvelopeTestRun(self, result_doc, module_doc, str_date): - json = self.__createTestTemplate(module_doc, result_doc["testType"],str_date) - + def __createWPenvelopeTestRun(self, json, timestampAsString): json["results"]["DISTANCE_X"] = [] json["results"]["DISTANCE_Y"] = [] json["results"]["THICKNESS_MEAN"] = [] json["results"]["THICKNESS_STDDEV"] = [] - return self.pd_client.post("uploadTestRunResults", json=json) + return json ################# ## for electrical - def __getElecTestAnalysisResult(self, result_doc, chip_name): + def __getElecTestAnalysisResult(self, localdb_test_doc, chip_name): dict_ = {} - if "analysis" in result_doc["results"]: - for analysis in result_doc["results"]["analysis"]: + if "analysis" in self.ldb_test_doc["results"]: + for analysis in self.ldb_test_doc["results"]["analysis"]: if analysis["name"] == "bad_pixel_analysis": for chip_result in analysis["result"]: if chip_result["chip"] == chip_name: @@ -1252,22 +1344,7 @@ class QCResultUploader(PDInterface.PDInterface): dict_[criteria["criteria"].upper()] = criteria["num"] return dict_ - def __getElecTestSettingTemp(self, result_doc, test_item): - temp = result_doc["results"]["setting_temp"] - try: - temp = int(temp) - except: - if test_item == "READOUT_IN_BASIC_ELECTRICAL_TEST_30_DEGREE" or "PIXEL_FAILURE_TEST_30_DEGREE": - temp = 30 - elif test_item == "READOUT_IN_BASIC_ELECTRICAL_TEST_20_DEGREE" or "PIXEL_FAILURE_TEST_20_DEGREE": - temp = 20 - elif test_item == "READOUT_IN_BASIC_ELECTRICAL_TEST_min15_DEGREE" or "PIXEL_FAILURE_TEST_min15_DEGREE": - temp = -15 - else: - temp = 99999 - return temp - - def __createElecTestTemplate(self, doc, testType, str_date): + def __createElecTestTemplate(self, doc, testType, timestampAsString): project = {"project": doc["project"]["code"]} componentType = {"componentType": doc["componentType"]["code"]} pd_testType = {"code": testType} @@ -1278,15 +1355,16 @@ class QCResultUploader(PDInterface.PDInterface): **test_template, "component": doc["serialNumber"], "institution": doc["currentLocation"]["code"], - "date": str_date, + "date": timestampAsString, "properties":{}, "results":{} } return json - def __createElecTestRunForModule(self, result_doc, module_doc, str_date, cp_test_map, test_item): - json = self.__createElecTestTemplate(module_doc, result_doc["testType"], str_date) - json["properties"] = {"ENVIRONMENT_TEMPERATURE":self.__getElecTestSettingTemp(result_doc, test_item)} + def __createElecTestRunForModule(self, timestampAsString, cp_test_map, test_item): + json = self.__createElecTestTemplate(self.pdb_component_doc, self.testType, timestampAsString) + json["properties"] = {"ENVIRONMENT_TEMPERATURE":self.testSetTemp} + json["runNumber"] = str( self.ldb_test_doc["_id"] ) json["results"]["RESULT_IDS"] = [v for i,v in cp_test_map.items()] json["results"]["DIGITAL_DEAD"] = [] json["results"]["DIGITAL_BAD"] = [] @@ -1297,25 +1375,31 @@ class QCResultUploader(PDInterface.PDInterface): json["results"]["TUNING_BAD_TOT"] = [] json["results"]["HIGH_ENC"] = [] json["results"]["NOISY"] = [] - for chip in result_doc["results"]["analysis"][0]["result"]: - for values in chip["result"]: - if values["criteria"] == "digital_dead": json["results"]["DIGITAL_DEAD"].append(values["num"]) - if values["criteria"] == "digital_bad": json["results"]["DIGITAL_BAD"].append(values["num"]) - if values["criteria"] == "analog_dead": json["results"]["ANALOG_DEAD"].append(values["num"]) - if values["criteria"] == "analog_bad": json["results"]["ANALOG_BAD"].append(values["num"]) - if values["criteria"] == "tuning_failed": json["results"]["TUNING_FAILED"].append(values["num"]) - if values["criteria"] == "tuning_bad_threshold": json["results"]["TUNING_BAD_THRESHOLD"].append(values["num"]) - if values["criteria"] == "tuning_bad_tot": json["results"]["TUNING_BAD_TOT"].append(values["num"]) - if values["criteria"] == "high_enc": json["results"]["HIGH_ENC"].append(values["num"]) - if values["criteria"] == "noisy": json["results"]["NOISY"].append(values["num"]) + + try: + for chip in self.ldb_test_doc["results"]["analysis"][0]["result"]: + logger.info( 'createElecTestRunForModule(): chip = {}'.format( chip ) ) + for values in chip["result"]: + if values["criteria"] == "digital_dead": json["results"]["DIGITAL_DEAD"].append(values["num"]) + if values["criteria"] == "digital_bad": json["results"]["DIGITAL_BAD"].append(values["num"]) + if values["criteria"] == "analog_dead": json["results"]["ANALOG_DEAD"].append(values["num"]) + if values["criteria"] == "analog_bad": json["results"]["ANALOG_BAD"].append(values["num"]) + if values["criteria"] == "tuning_failed": json["results"]["TUNING_FAILED"].append(values["num"]) + if values["criteria"] == "tuning_bad_threshold": json["results"]["TUNING_BAD_THRESHOLD"].append(values["num"]) + if values["criteria"] == "tuning_bad_tot": json["results"]["TUNING_BAD_TOT"].append(values["num"]) + if values["criteria"] == "high_enc": json["results"]["HIGH_ENC"].append(values["num"]) + if values["criteria"] == "noisy": json["results"]["NOISY"].append(values["num"]) + except Exception as e: + logger.warning( '{}'.format( e ) ) return self.pd_client.post("uploadTestRunResults",json=json) - def __createElecTestRunForFEChip(self, result_doc, chip_name, str_date): + def __createElecTestRunForFEChip(self, chip_name, timestampAsString): child_doc = super().getCompFromProdDB(chip_name) - json = self.__createElecTestTemplate(child_doc, result_doc["testType"], str_date) - json["properties"] = {"QC_STAGE":result_doc["currentStage"]} - json["results"] = self.__getElecTestAnalysisResult(result_doc, chip_name) + json = self.__createElecTestTemplate(child_doc, self.testType, timestampAsString) + json["properties"] = {"QC_STAGE":self.ldb_test_doc["currentStage"]} + json["results"] = self.__getElecTestAnalysisResult(self.ldb_test_doc, chip_name) + json["runNumber"] = str( self.ldb_test_doc["_id"] ): return self.pd_client.post( "uploadTestRunResults", json=json) @@ -1328,15 +1412,15 @@ class QCResultUploader(PDInterface.PDInterface): return def __attachFileToComp(self, filepath, filename, module_doc): - for attachment in module_doc["attachments"]: + for attachment in self.pdb_component_doc["attachments"]: if attachment["filename"] == filename: - self.pd_client.post( "deleteComponentAttachment", json = {"component":module_doc["serialNumber"], "code": attachment["code"]} ) + self.pd_client.post( "deleteComponentAttachment", json = {"component":self.pdb_component_doc["serialNumber"], "code": attachment["code"]} ) logger.info("Delete an old config for the module page: " + filename) with open(filepath, "rb") as f: upload_file = f upload_filetype = filename.split(".")[-1] page_attachment = { "data": (filename, upload_file, upload_filetype)} - self.pd_client.post( "createComponentAttachment", data=dict( component=module_doc["serialNumber"], type="file" ), files=page_attachment ) + self.pd_client.post( "createComponentAttachment", data=dict( component=self.pdb_component_doc["serialNumber"], type="file" ), files=page_attachment ) logger.info("Attach a new config for the module page: " + filename) return @@ -1355,7 +1439,7 @@ class QCResultUploader(PDInterface.PDInterface): n = i + 1 json_name = "json" + str(n) json_name = { - "component": module_doc["serialNumber"], + "component": self.pdb_component_doc["serialNumber"], "code": "RD53A_PULL-UP_RESISTOR" + str(n), "value": prop_doc["results"]["value"]["chip"+str(n)] } @@ -1370,7 +1454,7 @@ class QCResultUploader(PDInterface.PDInterface): n = i + 1 json_name = "json" + str(n) json_name = { - "component": module_doc["serialNumber"], + "component": self.pdb_component_doc["serialNumber"], "code": "IREFTRIM_FE" + str(n), "value": prop_doc["results"]["value"]["chip"+str(n)] } @@ -1382,7 +1466,7 @@ class QCResultUploader(PDInterface.PDInterface): ## for Orientation registor def __createOrientationTestRun(self, prop_doc, module_doc): json_name = { - "component": module_doc["serialNumber"], + "component": self.pdb_component_doc["serialNumber"], "code": "ORIENTATION", "value": prop_doc["results"]["orientation"] } @@ -1403,13 +1487,13 @@ class QCResultUploader(PDInterface.PDInterface): attachment_file = open(prop_doc["testType"] + '_detail.json', 'rb') page_attachment = {"data": (prop_doc["testType"] + '_detail.json', attachment_file, "json")} - self.pd_client.post( "createComponentAttachment", data=dict( component=module_doc["serialNumber"], type="file" ), files=page_attachment ) + self.pd_client.post( "createComponentAttachment", data=dict( component=self.pdb_component_doc["serialNumber"], type="file" ), files=page_attachment ) return os.remove(prop_doc["testType"] + '_detail.json') def __deletePropertyAttachment(self, module_doc): - for item in module_doc["attachments"]: + for item in self.pdb_component_doc["attachments"]: if "detail.json" in item["filename"]: - self.pd_client.post("deleteComponentAttachment", json={"component": module_doc["code"], "code": item["code"]} ) + self.pd_client.post("deleteComponentAttachment", json={"component": self.pdb_component_doc["code"], "code": item["code"]} ) return @@ -1417,7 +1501,7 @@ class QCResultUploader(PDInterface.PDInterface): ## set stage of ITkPD ## def __setStage(self, module_doc, module_QC_info): nextStage = module_QC_info["currentStage"] - json_stage = {"component": module_doc["serialNumber"], "stage": nextStage} + json_stage = {"component": self.pdb_component_doc["serialNumber"], "stage": nextStage} return self.pd_client.post("setComponentStage", json=json_stage ) @@ -1431,21 +1515,21 @@ class QCResultUploader(PDInterface.PDInterface): except: pass os.mkdir(cache_dir) - old_result_doc = {} + previous_ldb_test_doc = {} for attachment in tr_doc["attachments"]: jfile = self.pd_client.get("uu-app-binarystore/getBinaryData",json={"code": attachment["code"]}).content if "_results.json" in attachment["filename"]: with open(os.path.join(cache_dir,"{}".format(attachment["filename"])),"wb") as f: f.write(jfile) with open(os.path.join(cache_dir,"{}".format(attachment["filename"])),"r") as f: - old_result_doc["0"] = json.load(f) + previous_ldb_test_doc["0"] = json.load(f) - return old_result_doc + return previous_ldb_test_doc -# def __deleteResult(self, tr_doc, new_result_id, old_result_doc): +# def __deleteResult(self, tr_doc, new_result_id, previous_ldb_test_doc): # cache_dir = "{}/attachments".format(".") # attachment_file = open('attachments/' + "delete.json", 'rb') -# page_attachment = {"data": (old_result_doc["0"]["testType"] + "_deletedResults.json", attachment_file, "json")} +# page_attachment = {"data": (previous_ldb_test_doc["0"]["testType"] + "_deletedResults.json", attachment_file, "json")} # attach_ins = self.pd_client.post("createTestRunAttachment", data=dict( testRun=new_result_id, type="file" ), files=page_attachment ) # # self.pd_client.post("deleteTestRun", json={"testRun": tr_doc["id"]}) @@ -1455,76 +1539,224 @@ class QCResultUploader(PDInterface.PDInterface): ################### ## main function ## - def upload_results(self, msn): + def upload_results(self, componentSerialNumber): + + self.componentSerialNumber = componentSerialNumber + if not os.path.exists(IF_DIR): os.makedirs(IF_DIR) - do_filepath = IF_DIR + "/doing_upload_" + msn + ".txt" - with open(do_filepath, "w") as f: + doFilePath = IF_DIR + "/doing_upload_" + componentSerialNumber + ".txt" + with open(doFilePath, "w") as f: f.write("doing now") - if os.path.exists(IF_DIR + '/doing_upload_' + msn + '_fail' + '.txt'): - os.remove(IF_DIR + '/doing_upload_' + msn + '_fail' + '.txt') + if os.path.exists(IF_DIR + '/doing_upload_' + componentSerialNumber + '_fail' + '.txt'): + os.remove(IF_DIR + '/doing_upload_' + componentSerialNumber + '_fail' + '.txt') + + logger.info("Module name: " + componentSerialNumber) + + + self.doUploadFuncs = { "module": + { + "ADC_CALIBRATION" : self.__createADCcalibrationTestRun, + "FLATNESS" : self.__createCoplanarityTestRun, + "READOUT_IN_BASIC_ELECTRICAL_TEST" : self.__create_electrial_pdb, + "GLUE_MODULE_FLEX_ATTACH" : self.__createGlueTestRun, + "MASS" : self.__createMassTestRun, + "METROLOGY" : self.__createMetrologyTestRun, + "OPTICAL" : self.__createOpticalTestRun, + "PARYLENE" : self.__createParyleneTestRun, + "SLDO_VI" : self.__createSLDOVITestRun, + "SENSOR_IV" : self.__createSensorIVTestRun, + "THERMALCYCLE" : self.__createThermalTestRun, + "TUNING" : self.__createTuningTestRun, + "WP_ENVELOPE" : self.__createWPenvelopeTestRun, + "WIREBOND" : self.__createWirebondTestRun, + "WIREBONDING" : self.__createWirebondingTestRun + } + } + + + #---------------------------------------------------------------------------------------------- + # Collect informations from LocalDB + # Hereafter, LocalDB variables start from prefix "ldb_" + + self.ldb_component = super().getCompFromLocalDB(componentSerialNumber) + self.ldb_componentId = str( self.ldb_component["_id"] ) + self.ldb_componentType = self.ldb_component["componentType"] + self.ldb_component_QC_info = super().getQmsFromLocalDB(self.ldb_componentId) + self.ldb_component_prop_info = super().getPmsFromLocalDB(self.ldb_componentId) + + if not self.ldb_componentType in self.doUploadFuncs: + logger.error( 'Component type "{}" is not supported yet.'.format( self.ldb_componentType ) ) + return + + stageFlow = [item for item in self.ldb_component_QC_info["QC_results"] ] + + print( super().getCompFromLocalDB(componentSerialNumber) ) + #print( '\n\n\n\n' ) + #pprint.pprint( 'self.ldb_component_QC_info: ' ) + #pprint.pprint( self.ldb_component_QC_info ) + + try: + self.ldb_upload_status = self.ldb_component_QC_info["upload_status"] + except: + self.ldb_upload_status = {} + for stage in self.ldb_component_QC_info["stage_flow"]: + self.ldb_upload_status[stage] = "-1" + localdb.QC.module.status.update_one( {"_id": ObjectId(str(self.ldb_component_QC_info["_id"]))}, {"$set": {"upload_status": self.ldb_upload_status }} ) + + + #pprint.pprint( 'self.ldb_upload_status: ') + #pprint.pprint( self.ldb_upload_status ) + + + # Include up to the previous stage wrt the currentStage as a transaction stage + transactionStages = [] + for stage in stageFlow: + if stage == self.ldb_component_QC_info["currentStage"]: + break + transactionStages.append(stage) + + #print( '\n\n\n\n' ) + logger.info('Transaction Stages: {}'.format( transactionStages ) ) + + #---------------------------------------------------------------------------------------------- + # Collect ProdDB Component and TestRun docs + # Hereafter, LocalDB variables start from prefix "pdb_" + + self.pdb_component_doc = super().getCompFromProdDB(componentSerialNumber) + self.pdb_prev_testRuns = [test for test in self.pd_client.get("listTestRunsByComponent", json={"component": self.pdb_component_doc["code"] }) ] + + #print( '\n\n\n\n' ) + #pprint.pprint('self.pdb_prev_testRuns:') + #pprint.pprint( self.pdb_prev_testRuns ) + + + #---------------------------------------------------------------------------------------------- + # Loop over stages + + for stage in transactionStages: + + self.stage = stage + + isAtLeastOneTestUploaded = False + + # Loop over tests of the stage + for testType, result_id in self.ldb_component_QC_info["QC_results"][stage].items(): + + # If the result is blank, skip + if result_id == "-1": continue + + logger.info("Test : {}.{}".format( stage, testType ) ) + + self.ldb_test_doc = super().getQrFromLocalDB(result_id) + + #pprint.pprint( 'self.ldb_test_doc:' ) + #pprint.pprint( self.ldb_test_doc ) + + self.testSetTemp = -9999 + if testType.find("30DEG") >=0 or testType.find("30_DEG") >= 0: + self.testSetTemp = 30 + elif testType.find("20DEG") >=0 or testType.find("20_DEG") >= 0: + self.testSetTemp = 20 + if testType.find("MINUS15DEG") >=0 or testType.find("min15_DEG") >= 0: + self.testSetTemp = -15 + + self.testType = testType.replace("_30DEG", "").replace("_20DEG", "").replace("_MINUS15DEG", "") + + self.isExclusiveTest = ( self.testType == testType ) + + testSpecificCallback = None + try: + + testSpecificCallback = self.doUploadFuncs[ self.ldb_componentType ][ self.testType ] + + except Exception as e: + logger.error( 'Error in self.doUploadFuncs[ self.testType ] ()' ) + logger.error( 'self.ldb_componentType = {}'.format( self.ldb_componentType ) ) + logger.error( 'testType = {}'.format( self.testType ) ) + logger.error( '{}'.format( e ) ) + + # Execute uploading + if testSpecificCallback: + + isUploaded = self.__upload( testSpecificCallback ) + + if isUploaded: + isAtLeastOneTestUploaded = True + + # Update the QC status in LocalDB + if isUploaded: + localdb.QC.module.status.update_one( + { "_id": self.ldb_component_QC_info["_id"] }, + {"$set": {"QC_results_pdb.{}.{}".format( stage, testType ) : self.pdb_testRunId }} ) + + + #endfor testType, result_id in self.ldb_component_QC_info["QC_results"][stage].items() + + # Sign-off the stage and go to the next stage + localdb.QC.module.status.update_one( {"_id": self.ldb_component_QC_info["_id"]}, {"$set": {"upload_status.{}".format(stage): "1" }} ) + + next_stage = stageFlow[ stageFlow.index(stage)+1 ] + localdb.QC.module.status.update_one( {"_id": self.ldb_component_QC_info["_id"]}, {"$set": {"currentStage": next_stage}} ) + localdb.QC.module.status.update_one( {"_id": self.ldb_component_QC_info["_id"]}, {"$set": {"latestSyncedStage": stage}} ) + + # endfor stage in transactionStages + + if not os.path.exists(IF_DIR + '/doing_upload_' + componentSerialNumber + "_fail" + '.txt'): + stage_doc = self.__setStage(self.pdb_component_doc, self.ldb_component_QC_info) + logger.info( "Chenged Stage in ITk production DB to " + stage_doc["currentStage"] + "." ) + else: + logger.info("Not changed Stage in ITk production DB.") - module_info = super().getCompFromLocalDB(msn) - QC_info = localdb.QC.module.status.find_one({"component": str(module_info["_id"])}) - QC_info["stage_flow"] = [item for item in QC_info["QC_results"]] - module_QC_info = super().getQmsFromLocalDB(str(module_info["_id"])) - module_prop_info = super().getPmsFromLocalDB(str(module_info["_id"])) - logger.info("Module name: " + msn) - module_doc = super().getCompFromProdDB(msn) - old_test_list = self.pd_client.get("listTestRunsByComponent", json={"component":module_doc["code"]}) - old_tests = [item for item in old_test_list] + logger.info("Finished for all results!!\n") + os.remove(doFilePath) try: - upload_status = module_QC_info["upload_status"] + shutil.rmtree("{}/attachments".format(".")) except: - upload_status = {} - for stage in QC_info["stage_flow"]: - upload_status[stage] = "-1" - localdb.QC.module.status.update_one( {"_id": ObjectId(str(module_QC_info["_id"]))}, {"$set": {"upload_status": upload_status }} ) - - ## Below IF statement will be used for the case you want to procced stage of LDB and PDB at the same time. -# if not module_doc["currentStage"]["code"] == module_QC_info["latestSyncedStage"]: -# logger.info( "Stage is not corresponded to ITk production DB. Please check it" ) -# else: -## get the parent information and setting upload parameter - ustages = QC_info["stage_flow"] - upload_stage = [] - for stage in ustages: - if stage == module_QC_info["currentStage"]: - break - upload_stage.append(stage) + pass + + if os.path.exists(IF_DIR + '/doing_upload_' + componentSerialNumber + '.txt'): + os.remove(IF_DIR + '/doing_upload_' + componentSerialNumber + '.txt') + + return - for stage in upload_stage: + + ''' + #---------------------------------------------------------------------------------------------- + # Loop over stages + + for stage in transactionStages: try: - if upload_status[stage] == "-1": - json_stage = {"component": module_doc["serialNumber"], "stage": stage} + if self.ldb_upload_status[stage] == "-1": + json_stage = {"component": pdb_component_doc["serialNumber"], "stage": stage} self.pd_client.post("setComponentStage", json=json_stage ) logger.info("Stage: " + str(stage)) - for test_item, result_id in module_QC_info["QC_results"][stage].items(): + for test_item, result_id in self.ldb_component_QC_info["QC_results"][stage].items(): logger.info("Test Item: " + str(test_item)) - result_doc = super().getQrFromLocalDB(result_id) if result_id != "-1" else {} - result_doc["old_tests"] = old_tests + self.ldb_test_doc = super().getQrFromLocalDB(result_id) if result_id != "-1" else {} + self.ldb_test_doc["pdb_prev_testRuns"] = pdb_prev_testRuns if result_id != "-1": - new_result_id = self.__upload_functions(module_doc, result_doc, test_item, QC_info) + new_result_id = self.__upload_functions(pdb_component_doc, self.ldb_test_doc, test_item, self.ldb_component_QC_info) if stage == "MODULEWIREBONDING": - self.__deletePropertyAttachment(module_doc) - for prop_item, prop_id in module_prop_info["QC_properties"].items(): + self.__deletePropertyAttachment(pdb_component_doc) + for prop_item, prop_id in self.ldb_component_prop_info["QC_properties"].items(): logger.info("Property Item: " + str(prop_item)) if prop_id != "": prop_doc = super().getPrFromLocalDB(prop_id) - self.__upload_prop_functions(module_doc, prop_doc) + self.__upload_prop_functions(pdb_component_doc, prop_doc) if stage == "MODULERECEPTION": next_stage = "complete" - localdb.QC.module.status.update_one( {"_id": ObjectId(str(module_QC_info["_id"]))}, {"$set": {"latestSyncedStage": next_stage}} ) + localdb.QC.module.status.update_one( {"_id": ObjectId(str(self.ldb_component_QC_info["_id"]))}, {"$set": {"latestSyncedStage": next_stage}} ) else: - next_stage = QC_info["stage_flow"][QC_info["stage_flow"].index(stage)+1] - #pd_client.post("setComponentStage", json = {"component":msn, "stage":next_stage}) - localdb.QC.module.status.update_one( {"_id": ObjectId(str(module_QC_info["_id"]))}, {"$set": {"latestSyncedStage": next_stage}} ) - upload_status[stage] = "0" - localdb.QC.module.status.update_one( {"_id": ObjectId(str(module_QC_info["_id"]))}, {"$set": {"upload_status": upload_status }} ) + next_stage = self.ldb_component_QC_info["stage_flow"][self.ldb_component_QC_info["stage_flow"].index(stage)+1] + #pd_client.post("setComponentStage", json = {"component":componentSerialNumber, "stage":next_stage}) + localdb.QC.module.status.update_one( {"_id": ObjectId(str(self.ldb_component_QC_info["_id"]))}, {"$set": {"latestSyncedStage": next_stage}} ) + self.ldb_upload_status[stage] = "0" + localdb.QC.module.status.update_one( {"_id": ObjectId(str(self.ldb_component_QC_info["_id"]))}, {"$set": {"upload_status": self.ldb_upload_status }} ) logger.info("") except Exception as e: @@ -1535,22 +1767,23 @@ class QCResultUploader(PDInterface.PDInterface): logger.info("Failed to upload result of " + str(test_item) + "...") if not os.path.exists(IF_DIR): os.makedirs(IF_DIR) - with open(IF_DIR + '/doing_upload_' + msn + "_fail" + '.txt', 'w') as f: + with open(IF_DIR + '/doing_upload_' + componentSerialNumber + "_fail" + '.txt', 'w') as f: f.write(str(test_item) + '\n' + result_id) logger.info("Finished for all results!!\n") - if not os.path.exists(IF_DIR + '/doing_upload_' + msn + "_fail" + '.txt'): - stage_doc = self.__setStage(module_doc, module_QC_info) + if not os.path.exists(IF_DIR + '/doing_upload_' + componentSerialNumber + "_fail" + '.txt'): + stage_doc = self.__setStage(pdb_component_doc, self.ldb_component_QC_info) logger.info( "Chenged Stage in ITk production DB to " + stage_doc["currentStage"] + "." ) else: logger.info("Not changed Stage in ITk production DB.") - os.remove(do_filepath) + os.remove(doFilePath) try: shutil.rmtree("{}/attachments".format(".")) except: pass - if os.path.exists(IF_DIR + '/doing_upload_' + msn + '.txt'): - os.remove(IF_DIR + '/doing_upload_' + msn + '.txt') + if os.path.exists(IF_DIR + '/doing_upload_' + componentSerialNumber + '.txt'): + os.remove(IF_DIR + '/doing_upload_' + componentSerialNumber + '.txt') + ''' diff --git a/viewer/templates/SLDOVIresult.html b/viewer/templates/displayResults/SLDOVIresult.html similarity index 100% rename from viewer/templates/SLDOVIresult.html rename to viewer/templates/displayResults/SLDOVIresult.html diff --git a/viewer/templates/adc_calibration.html b/viewer/templates/displayResults/adc_calibration.html similarity index 100% rename from viewer/templates/adc_calibration.html rename to viewer/templates/displayResults/adc_calibration.html diff --git a/viewer/templates/coplanarity.html b/viewer/templates/displayResults/coplanarity.html similarity index 100% rename from viewer/templates/coplanarity.html rename to viewer/templates/displayResults/coplanarity.html diff --git a/viewer/templates/electrical.html b/viewer/templates/displayResults/electrical.html similarity index 100% rename from viewer/templates/electrical.html rename to viewer/templates/displayResults/electrical.html diff --git a/viewer/templates/glueattachresult.html b/viewer/templates/displayResults/glueattachresult.html similarity index 100% rename from viewer/templates/glueattachresult.html rename to viewer/templates/displayResults/glueattachresult.html diff --git a/viewer/templates/irefresult.html b/viewer/templates/displayResults/irefresult.html similarity index 100% rename from viewer/templates/irefresult.html rename to viewer/templates/displayResults/irefresult.html diff --git a/viewer/templates/massresult.html b/viewer/templates/displayResults/massresult.html similarity index 100% rename from viewer/templates/massresult.html rename to viewer/templates/displayResults/massresult.html diff --git a/viewer/templates/metresult.html b/viewer/templates/displayResults/metresult.html similarity index 100% rename from viewer/templates/metresult.html rename to viewer/templates/displayResults/metresult.html diff --git a/viewer/templates/paryleneresult.html b/viewer/templates/displayResults/paryleneresult.html similarity index 100% rename from viewer/templates/paryleneresult.html rename to viewer/templates/displayResults/paryleneresult.html diff --git a/viewer/templates/pulltest.html b/viewer/templates/displayResults/pulltest.html similarity index 100% rename from viewer/templates/pulltest.html rename to viewer/templates/displayResults/pulltest.html diff --git a/viewer/templates/pullupresult.html b/viewer/templates/displayResults/pullupresult.html similarity index 100% rename from viewer/templates/pullupresult.html rename to viewer/templates/displayResults/pullupresult.html diff --git a/viewer/templates/qc_others.html b/viewer/templates/displayResults/qc_others.html similarity index 100% rename from viewer/templates/qc_others.html rename to viewer/templates/displayResults/qc_others.html diff --git a/viewer/templates/sensorIVresult.html b/viewer/templates/displayResults/sensorIVresult.html similarity index 100% rename from viewer/templates/sensorIVresult.html rename to viewer/templates/displayResults/sensorIVresult.html diff --git a/viewer/templates/thermalresult.html b/viewer/templates/displayResults/thermalresult.html similarity index 100% rename from viewer/templates/thermalresult.html rename to viewer/templates/displayResults/thermalresult.html diff --git a/viewer/templates/tuning.html b/viewer/templates/displayResults/tuning.html similarity index 100% rename from viewer/templates/tuning.html rename to viewer/templates/displayResults/tuning.html diff --git a/viewer/templates/viresult.html b/viewer/templates/displayResults/viresult.html similarity index 100% rename from viewer/templates/viresult.html rename to viewer/templates/displayResults/viresult.html diff --git a/viewer/templates/wirebondingresult.html b/viewer/templates/displayResults/wirebondingresult.html similarity index 100% rename from viewer/templates/wirebondingresult.html rename to viewer/templates/displayResults/wirebondingresult.html diff --git a/viewer/templates/wpenvelope_result.html b/viewer/templates/displayResults/wpenvelope_result.html similarity index 100% rename from viewer/templates/wpenvelope_result.html rename to viewer/templates/displayResults/wpenvelope_result.html diff --git a/viewer/templates/xrayresult.html b/viewer/templates/displayResults/xrayresult.html similarity index 100% rename from viewer/templates/xrayresult.html rename to viewer/templates/displayResults/xrayresult.html diff --git a/viewer/templates/displayTests/massresult.html b/viewer/templates/displayTests/massresult.html new file mode 100644 index 00000000..52785fcd --- /dev/null +++ b/viewer/templates/displayTests/massresult.html @@ -0,0 +1,118 @@ +<style> +input[type="checkbox"].data { +display: none; +margin: 0px; +padding: 0px; +background-color: #ffffff; +} +input[type="checkbox"].data + label { +margin: 0px; +padding: 0px; +border: none; +background-color: #ffffff; +} +input[type="checkbox"].data:checked + label { +margin: 0px; +padding: 0px; +border: none; +color: #3cb371; +background-color: #ffffff; +} +input[type="checkbox"].data:checked + label:hover { +margin: 0px; +padding: 0px; +color: #3cb371; +border: none; +background-color: #ffffff; +} +label.data { +margin: 0px; +padding: 0px; +text-align: center; +cursor: pointer; +color: #dddddd; +background-color: #ffffff; +border: none; +} +</style> +<div class='row align-items-top justify-content-left'> + <div class='col'> + <h4 style='font-size: 13pt;'><i class='fa fa-cog'></i> Result of Mass Measurement</h4> + </div> +</div> +<div class='row align-items-top justify-content-left'> + <div class='col-md-6'> + <h4 style='font-size: 10pt;'> + {% if component['qctest']['results'] %} + <i class='fa fa-file'></i> Scan + {% endif %} + </h4> + {% if component['qctest']['results'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + <tr> + <th scope='raw' class='text-left'>component id</td> + <td class='text-left'>{{ component['qctest']['results']['component'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>Stage</td> + <td class='text-left'>{{ component['qctest']['results']['currentStage'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>test Type</td> + <td class='text-left'>{{ component['qctest']['results']['testType'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>institute</td> + <td class='text-left'>{{ component['qctest']['results']['institute'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>user</td> + <td class='text-left'>{{ component['qctest']['results']['user'] }}</td> + </tr> + </tbody> + </table> + {% endif %} + </div> +</div> + +<div class='row align-items-top justify-content-left'> + <div class='col-md-6'> + <h4 style='font-size: 10pt;'> + {% if component['qctest']['results'] %} + <i class='fa fa-file'></i> Result + {% endif %} + </h4> + {% if component['qctest']['results'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + {% for keys in component['qctest']['results']['results'] %} + {% if keys == 'property' %} + <tr> + <th scope='raw' class='text-left'> Scale_accuracy </td> + <td class='text-left'>{{ component['qctest']['results']['results'][keys]['Scale_accuracy'] }}</td> + </tr> + {% else %} + <tr> + <th scope='raw' class='text-left'> {{ keys }} </td> + <td class='text-left'>{{ component['qctest']['results']['results'][keys] }}</td> + </tr> + {% endif %} + {% endfor %} + </tbody> + </table> + {% endif %} + </div> +</div> diff --git a/viewer/templates/displayTests/viresult.html b/viewer/templates/displayTests/viresult.html new file mode 100644 index 00000000..57d6c7fb --- /dev/null +++ b/viewer/templates/displayTests/viresult.html @@ -0,0 +1,138 @@ +<style> +input[type="checkbox"].data { +display: none; +margin: 0px; +padding: 0px; +background-color: #ffffff; +} +input[type="checkbox"].data + label { +margin: 0px; +padding: 0px; +border: none; +background-color: #ffffff; +} +input[type="checkbox"].data:checked + label { +margin: 0px; +padding: 0px; +border: none; +color: #3cb371; +background-color: #ffffff; +} +input[type="checkbox"].data:checked + label:hover { +margin: 0px; +padding: 0px; +color: #3cb371; +border: none; +background-color: #ffffff; +} +label.data { +margin: 0px; +padding: 0px; +text-align: center; +cursor: pointer; +color: #dddddd; +background-color: #ffffff; +border: none; +} +</style> + {% if not component["qctest"]["results"]["img"] %} +<p><font color=#ff0000>### No defects were identified ###</font></p> +{% endif %} + + +{% if component["qctest"]["results"]["img"] %} +<h4><i class="fa fa-paint-brush"></i> Pictures of Visual Inspection</h4> + <div class="row align-items-left justify-content-flex-start"> + <div class="col"> + <table class="table table-sm table-bordered" style="font-size: 8pt; table-layout: fixed;"> + <thead class="table-light"> + <tr> + <th scope='col' class='text-left' style="word-wrap:break-word;" width=100 colspan=1 >Keys</th> + <th ~~~ scope="col" class="text-left" style="word-wrap:break-word;" width=200*{{component["qctest"]["results"]["anomalyNum"]["image"]}} colspan={{component["qctest"]["results"]["anomalyNum"]["image"]}}> Pictuite with anomaly </th> + </tr> + <th scope='col' class='text-left' style="word-wrap:break-word;" width=100 colspan=1 >anomaly</th> + {% for i in component["qctest"]["results"]["img"] %} + {% if component["qctest"]["results"]["results"]["anomaly"][i] %} + <th scope='col' class='text-left' style="word-wrap:break-word;" width=200 colspan=1 >{{ component["qctest"]["results"]["results"]["anomaly"][i] }}</th> + {% else %} + <th scope='col' class='text-left' style="word-wrap:break-word;" width=200 colspan=1 > comment only </th> + {% endif %} + {% endfor %} + </thead> + <tbody> + <tr> + <th scope='raw' class='text-left'> picture </td> + {% for i in component["qctest"]["results"]["img"] %} + <td scope="col" class="text-left" style="word-wrap:break-word;"> + <a href={{ component["qctest"]["results"]["img"][i] }} target="_brank" rel="noopener noreferrer"> + <img src={{ component["qctest"]["results"]["img"][i] }} title= "Click to expand" width="300px"></img> + </a> + </td> + {% endfor %} + </tr> + <tr> + <th scope='raw' class='text-left'> comment </td> + {% for i in component["qctest"]["results"]["img"] %} + <td scope="col" class="text-left" style="word-wrap:break-word;"> {{component["qctest"]["results"]["results"]["comment"][i]}} </td> + {% endfor %} + </tr> + </tbody> + </table> + </div> + </div> +{% endif %} + + +<p><a href="{{ component['qctest']['results']['orig_cache'] }}"><img src={{ component["qctest"]["results"]["thumb"] }} /></a></p> + +<p> + <a href="{{ component['qctest']['results']['orig_cache'] }}" download> + <button type="button" class="btn btn-info">Download Full Image</button> + </a> + <a href="{{ url_for('picture_data_api.picData', id=component['_id'], collection=component['collection'], runId=component['qctest']['results']['runId']) }}"> + <button type="button" class="btn btn-info">Compare w/ Refs.</button> + </a> +</p> + +<div class='row align-items-top justify-content-left' style="margin-top: 20px;"> + <div class='col-md-6'> + <h4 style='font-size: 10pt;'> + {% if component['qctest']['results'] %} + <i class='fa fa-file'></i> Scan + {% endif %} + </h4> + {% if component['qctest']['results'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + <tr> + <th scope='raw' class='text-left'>component id</td> + <td class='text-left'>{{ component['qctest']['results']['component'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>Stage</td> + <td class='text-left'>{{ component['qctest']['results']['currentStage'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>test Type</td> + <td class='text-left'>{{ component['qctest']['results']['testType'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>institute</td> + <td class='text-left'>{{ component['qctest']['results']['institute'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>user</td> + <td class='text-left'>{{ component['qctest']['results']['user'] }}</td> + </tr> + </tbody> + </table> + {% endif %} + </div> +</div> + diff --git a/viewer/templates/displayTests/wirebondingresult.html b/viewer/templates/displayTests/wirebondingresult.html new file mode 100644 index 00000000..e42f747c --- /dev/null +++ b/viewer/templates/displayTests/wirebondingresult.html @@ -0,0 +1,148 @@ +<style> +input[type="checkbox"].data { +display: none; +margin: 0px; +padding: 0px; +background-color: #ffffff; +} +input[type="checkbox"].data + label { +margin: 0px; +padding: 0px; +border: none; +background-color: #ffffff; +} +input[type="checkbox"].data:checked + label { +margin: 0px; +padding: 0px; +border: none; +color: #3cb371; +background-color: #ffffff; +} +input[type="checkbox"].data:checked + label:hover { +margin: 0px; +padding: 0px; +color: #3cb371; +border: none; +background-color: #ffffff; +} +label.data { +margin: 0px; +padding: 0px; +text-align: center; +cursor: pointer; +color: #dddddd; +background-color: #ffffff; +border: none; +} +</style> +<div class='row align-items-top justify-content-left'> + <div class='col'> + <h4 style='font-size: 13pt;'><i class='fa fa-cog'></i> Result of Wirebonding Information</h4> + </div> +</div> +<div class='row align-items-top justify-content-left'> + <div class='col-md-6'> + <h4 style='font-size: 10pt;'> + {% if component['qctest']['results'] %} + <i class='fa fa-file'></i> Scan + {% endif %} + </h4> + {% if component['qctest']['results'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + <tr> + <th scope='raw' class='text-left'>component id</td> + <td class='text-left'>{{ component['qctest']['results']['component'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>Stage</td> + <td class='text-left'>{{ component['qctest']['results']['currentStage'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>test Type</td> + <td class='text-left'>{{ component['qctest']['results']['testType'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>institute</td> + <td class='text-left'>{{ component['qctest']['results']['institute'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>user</td> + <td class='text-left'>{{ component['qctest']['results']['user'] }}</td> + </tr> + </tbody> + </table> + {% endif %} + </div> +</div> + +<div class='row align-items-top justify-content-left'> + <div class='col-md-6'> + <h4 style='font-size: 10pt;'> + <i class='fa fa-file'></i> Result + </h4> + {% if component['qctest']['results'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + {% for keys in component['qctest']['results']['results'] %} + {% if keys != 'property' %} + <tr> + <th scope='raw' class='text-left'> {{ keys }} </td> + <td class='text-left'>{{ component['qctest']['results']['results'][keys] }}</td> + </tr> + {% endif %} + {% endfor %} + </tbody> + </table> + {% endif %} + </div> + <div class='col-md-6'> + <h4 style='font-size: 10pt;'> + <i class='fa fa-file'></i> Property + </h4> + {% if component['qctest']['results'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + {% for keys in component['qctest']['results']['results']['property'] %} + {% if keys != 'Bond_program' %} + <tr> + <th scope='raw' class='text-left'> {{ keys }} </td> + <td class='text-left'>{{ component['qctest']['results']['results']['property'][keys] }}</td> + </tr> + {% else %} + {% if component['qctest']['results']['results']['property'][keys] != "" %} + <tr> + <th scope='raw' class='text-left'> {{ keys }} </td> + <td class='text-left'><a href="/localdb/static/qc_attachment/Bonding_Program.dat" download><i class="fa fa-download "></i> Download Bonding Program</a></td> + </tr> + {% else %} + <tr> + <th scope='raw' class='text-left'> {{ keys }} </td> + <td class='text-left'><font color=#ff0000> No Bonding Program </font></td> + </tr> + {% endif %} + {% endif %} + {% endfor %} + </tbody> + </table> + {% endif %} + </div> +</div> diff --git a/viewer/templates/displayTests/wpenvelope_result.html b/viewer/templates/displayTests/wpenvelope_result.html new file mode 100644 index 00000000..0a9963d9 --- /dev/null +++ b/viewer/templates/displayTests/wpenvelope_result.html @@ -0,0 +1,111 @@ +<style> +input[type="checkbox"].data { +display: none; +margin: 0px; +padding: 0px; +background-color: #ffffff; +} +input[type="checkbox"].data + label { +margin: 0px; +padding: 0px; +border: none; +background-color: #ffffff; +} +input[type="checkbox"].data:checked + label { +margin: 0px; +padding: 0px; +border: none; +color: #3cb371; +background-color: #ffffff; +} +input[type="checkbox"].data:checked + label:hover { +margin: 0px; +padding: 0px; +color: #3cb371; +border: none; +background-color: #ffffff; +} +label.data { +margin: 0px; +padding: 0px; +text-align: center; +cursor: pointer; +color: #dddddd; +background-color: #ffffff; +border: none; +} +</style> +<div class='row align-items-top justify-content-left'> + <div class='col'> + <h4 style='font-size: 13pt;'><i class='fa fa-cog'></i> Result of This Test</h4> + </div> +</div> +<div class='row align-items-top justify-content-left'> + <div class='col-md-6'> + <h4 style='font-size: 10pt;'> + {% if component['qctest']['results'] %} + <i class='fa fa-file'></i> Scan + {% endif %} + </h4> + {% if component['qctest']['results'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + <tr> + <th scope='raw' class='text-left'>component id</td> + <td class='text-left'>{{ component['qctest']['results']['component'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>current Stage</td> + <td class='text-left'>{{ component['qctest']['results']['currentStage'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>test Type</td> + <td class='text-left'>{{ component['qctest']['results']['testType'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>institute</td> + <td class='text-left'>{{ component['qctest']['results']['institute'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>user name</td> + <td class='text-left'>{{ component['qctest']['results']['user'] }}</td> + </tr> + </tbody> + </table> + {% endif %} + </div> +</div> + +<div class='row align-items-top justify-content-left'> + <div class='col-md-6'> + <h4 style='font-size: 10pt;'> + {% if component['qctest']['results'] %} + <i class='fa fa-file'></i> Result + {% endif %} + </h4> + {% if component['qctest']['results'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + {% for keys in component['qctest']['results']['results'] %} + <tr> + <th scope='raw' class='text-left'> {{ keys }} </td> + <td class='text-left'>{{ component['qctest']['results']['results'][keys] }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + </div> +</div> diff --git a/viewer/templates/displayTests/xrayresult.html b/viewer/templates/displayTests/xrayresult.html new file mode 100644 index 00000000..197154b1 --- /dev/null +++ b/viewer/templates/displayTests/xrayresult.html @@ -0,0 +1,281 @@ +<style> +input[type="checkbox"].data { +display: none; +margin: 0px; +padding: 0px; +background-color: #ffffff; +} +input[type="checkbox"].data + label { +margin: 0px; +padding: 0px; +border: none; +background-color: #ffffff; +} +input[type="checkbox"].data:checked + label { +margin: 0px; +padding: 0px; +border: none; +color: #3cb371; +background-color: #ffffff; +} +input[type="checkbox"].data:checked + label:hover { +margin: 0px; +padding: 0px; +color: #3cb371; +border: none; +background-color: #ffffff; +} +label.data { +margin: 0px; +padding: 0px; +text-align: center; +cursor: pointer; +color: #dddddd; +background-color: #ffffff; +border: none; +} +</style> +<div class='row align-items-top justify-content-left'> + <div class='col'> + <h4 style='font-size: 13pt;'><i class='fa fa-cog'></i> Result of This Test</h4> + </div> +</div> +<div class='row align-items-top justify-content-left'> + <div class='col-md-6'> + <h4 style='font-size: 10pt;'> + {% if component['qctest']['results'] %} + <i class='fa fa-file'></i> Scan + {% endif %} + </h4> + {% if component['qctest']['results'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + <tr> + <th scope='raw' class='text-left'>component id</td> + <td class='text-left'>{{ component['qctest']['results']['component'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>Stage</td> + <td class='text-left'>{{ component['qctest']['results']['currentStage'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>test Type</td> + <td class='text-left'>{{ component['qctest']['results']['testType'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>institute</td> + <td class='text-left'>{{ component['qctest']['results']['institute'] }}</td> + </tr> + <tr> + <th scope='raw' class='text-left'>user name</td> + <td class='text-left'>{{ component['qctest']['results']['user'] }}</td> + </tr> + </tbody> + </table> + {% endif %} + </div> +</div> + +<div class='row align-items-top justify-content-left'> + <div class='col-md-6'> + <h4 style='font-size: 10pt;'> + {% if component['qctest']['results'] %} + <i class='fa fa-file'></i> Result + {% endif %} + </h4> + {% if component['qctest']['results'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + {% for keys in component['qctest']['results']['results'] %} + <tr> + <th scope='raw' class='text-left'> {{ keys }} </td> + <td class='text-left'>{{ component['qctest']['results']['results'][keys] }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + {% if component['qctest']['results']['summery'] %} + <table class='table table-sm table-bordered' style='font-size: 8pt;'> + <thead class='table-light'> + <tr> + <th scope='col' class='text-left'>Key</th> + <th scope='col' class='text-left'>Data</th> + </tr> + </thead> + <tbody> + {% for keys in component['qctest']['results']['summery'] %} + <tr> + <th scope='raw' class='text-left'> {{ keys }} </td> + <td class='text-left'>{{ component['qctest']['results']['summery'][keys] }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + </div> +</div> + +{% for scan in component['qctest']["results"]["plots"] %} +<!--<h2 style="font-size: 20pt;"><i class="fa fa-caret-right"></i> {{ scan }} + {% if not component['qctest']["results"]["plots"][scan]["rootsw"] %} + <font color=#ff0000>### NO PLOTTING SOFTWARE ###</font> + {% endif %} +</h2> + +<div class="row align-items-center justify-content-flex-start"> + <div class="col"> + {% for result in component['qctest']["results"]["plots"][scan]["results"] %} + <h4 style="font-size: 10pt;"> + <i class="fa fa-caret-right"></i> {{ result["mapType"] }} + <a href="/localdb/plotData?testRunId={{ result['runId'] }}&mapType={{ result['mapType'] }}">plotly</a> + </h4> + <table class="table table-sm table-bordered" style="font-size: 8pt; table-layout: fixed;"> + <thead class="table-light"> + <tr> + {% for chip, data in result["chips"].items() %} + {% if not data["num"]==0 %} + <th ~~~ scope="col" class="text-left" style="word-wrap:break-word;" width={{ data["length"] }} colspan={{ + data["num"] }}>{{ chip }}</th> + {% endif %} + {% endfor %} + </tr> + </thead> + <tbody> + <tr> + {% for chip, data in result["chips"].items() %} + {% for plot in data["plots"] %} + {{ chip }} + <td scope="col" class="text-left" style="word-wrap:break-word;"> + <a href={{ plot["url"] }} target="_brank" rel="noopener noreferrer"> + <img src={{ plot["url"] }} title={{ plot["name"] }} width={{ data["width"] }} height={{ data["width"] + }}></img> + </a> + </td> + {% endfor %} + {% endfor %} + </tr> + </tbody> + </table> + {% endfor %} + </div> +</div> +--> + + +<style> + img.plot-img:hover { + border: 0.5px solid #CCC; + transform: scale(2.5); + } +</style> +<script> + document.addEventListener("DOMContentLoaded", function () { + var lazyImages = [].slice.call(document.querySelectorAll("img.plot-img")); + + if ("IntersectionObserver" in window) { + let lazyImageObserver = new IntersectionObserver(function (entries, observer) { + entries.forEach(function (entry) { + if (entry.isIntersecting) { + let lazyImage = entry.target; + lazyImage.src = lazyImage.dataset.src; + if (typeof lazyImage.dataset.srcset === "undefined") { + } else { + lazyImage.srcset = lazyImage.dataset.srcset; + } + lazyImage.classList.remove("plot-img"); + lazyImageObserver.unobserve(lazyImage); + } + }); + }); + + lazyImages.forEach(function (lazyImage) { + lazyImageObserver.observe(lazyImage); + }); + } else { + // Possibly fall back to a more compatible method here + } + }); +</script> +<h3><i class='fa fa-cog'></i> Result Plots for {{ scan }}</h3> + + {% for result in component['qctest']["results"]["plots"][scan]["results"]|sort(attribute='mapType')|reverse %} + <h4> + <i class="fa fa-caret-right"></i> <b>{{ result["mapType"] }}</b> + <a href="/localdb/plotData?testRunId={{ result['runId'] }}&mapType={{ result['mapType'] }}" + style="font-size: 50%;">[plotly]</a> + </h4> + <div style="display: flex; flex-wrap: wrap;"> + {% for chip, data in result["chips"].items() %} + {% if not data["num"]==0 %} + <div width={{ data["length"] }} align="left"> + <table class="table table-sm table-bordered" style="font-size: 8pt;"> + <thead class="table-light"> + <tr> + <th ~~~ scope="col" class="text-left" style="word-wrap:break-word;" colspan={{ data["num"] }}>{{ chip }} + </th> + </tr> + </thead> + <tbody> + <tr style="display: flex; flex-wrap: wrap; background: #DDD; max-width: 100%"> + {% for plot in data["plots"] %} + <td scope="col" class="text-left" style="word-wrap:break-word;"> + <a href={{ plot["url"] }} target="_brank" rel="noopener noreferrer"> + <img class="plot-img" src="static/assets/images/dummy.png" data-src={{ plot["url"] }} title={{ + plot["name"] }} width={{ data["width"] }} height={{ data["height"] }}></img> + </a> + </td> + {% endfor %} + </tr> + </tbody> + </table> + </div> + {% endif %} + {% endfor %} + </div> + {% endfor %} + + <!--<h4 style="font-size: 10pt;"><i class="fa fa-paint-brush"></i> PLOT + {% if not component['qctest']["results"]["plots"][scan]["rootsw"] %} + <font color=#ff0000>### NO PLOTTING SOFTWARE ###</font> + {% elif component['qctest']["results"]["plots"][scan]["jsroot"] %} + <a href="javascript:displayRoot()">JSROOT</a> + {% endif %} + </h4> + {% if component['qctest']["results"]["plots"][scan]["jsroot"] %} + <div class="row align-items-center justify-content-flex-start"> + <script src="static/jsroot/scripts/JSRootCore.js?gui"></script> + <div id="simpleGUI" path={{ component['qctest']["results"]["plots"][scan]["jsroot"]["path"] }} files={{ + component['qctest']["results"]["plots"][scan]["jsroot"]["files"] }} + style="width:100%;height:600px;margin-bottom:10px;position:relative"> + loading scripts ... + </div> + <script type="text/javascript"> + document.getElementById("simpleGUI").style.display = "none"; + function displayRoot() { + const p1 = document.getElementById("simpleGUI"); + if (p1.style.display == "block") { + p1.style.display = "none"; + } else { + p1.style.display = "block"; + } + } + </script> + </div> + {% endif %}--> + + + +{% endfor %} diff --git a/viewer/templates/plot.html b/viewer/templates/electrical_test/plot.html similarity index 100% rename from viewer/templates/plot.html rename to viewer/templates/electrical_test/plot.html diff --git a/viewer/templates/result.html b/viewer/templates/electrical_test/result.html similarity index 100% rename from viewer/templates/result.html rename to viewer/templates/electrical_test/result.html diff --git a/viewer/templates/selection.html b/viewer/templates/electrical_test/selection.html similarity index 100% rename from viewer/templates/selection.html rename to viewer/templates/electrical_test/selection.html -- GitLab From 116c7c0516313ddc0a42346b8506a82b5044baab Mon Sep 17 00:00:00 2001 From: Hideyuki Oide <Hideyuki.Oide@cern.ch> Date: Sun, 24 Jul 2022 16:56:41 +0900 Subject: [PATCH 5/7] fixing typo --- viewer/itkpd-interface/lib/upload_results.py | 2 +- viewer/pages/component.py | 42 ++- viewer/templates/comment.html | 17 +- viewer/templates/component.html | 158 ++++------ viewer/templates/components_table.html | 18 -- .../templates/displayResults/qc_others.html | 13 +- viewer/templates/displayResults/tuning.html | 13 +- viewer/templates/displayTests/massresult.html | 118 -------- viewer/templates/displayTests/viresult.html | 138 --------- .../displayTests/wirebondingresult.html | 148 --------- .../displayTests/wpenvelope_result.html | 111 ------- viewer/templates/displayTests/xrayresult.html | 281 ------------------ viewer/templates/latest_test_index.html | 22 +- viewer/templates/nomodule_result.html | 2 +- viewer/templates/result_tranceiver.html | 2 +- .../templates/show_analysis_result.html.tmp~ | 41 --- viewer/templates/show_analysis_result.html~ | 41 --- viewer/templates/summary_index.html | 15 +- viewer/templates/upload_flag.html | 4 +- 19 files changed, 140 insertions(+), 1046 deletions(-) delete mode 100644 viewer/templates/displayTests/massresult.html delete mode 100644 viewer/templates/displayTests/viresult.html delete mode 100644 viewer/templates/displayTests/wirebondingresult.html delete mode 100644 viewer/templates/displayTests/wpenvelope_result.html delete mode 100644 viewer/templates/displayTests/xrayresult.html delete mode 100644 viewer/templates/show_analysis_result.html.tmp~ delete mode 100644 viewer/templates/show_analysis_result.html~ diff --git a/viewer/itkpd-interface/lib/upload_results.py b/viewer/itkpd-interface/lib/upload_results.py index 4f1507e4..38e4ad6b 100755 --- a/viewer/itkpd-interface/lib/upload_results.py +++ b/viewer/itkpd-interface/lib/upload_results.py @@ -1399,7 +1399,7 @@ class QCResultUploader(PDInterface.PDInterface): json = self.__createElecTestTemplate(child_doc, self.testType, timestampAsString) json["properties"] = {"QC_STAGE":self.ldb_test_doc["currentStage"]} json["results"] = self.__getElecTestAnalysisResult(self.ldb_test_doc, chip_name) - json["runNumber"] = str( self.ldb_test_doc["_id"] ): + json["runNumber"] = str( self.ldb_test_doc["_id"] ) return self.pd_client.post( "uploadTestRunResults", json=json) diff --git a/viewer/pages/component.py b/viewer/pages/component.py index 9464c629..7c27a009 100755 --- a/viewer/pages/component.py +++ b/viewer/pages/component.py @@ -36,6 +36,7 @@ def show_component(): # component info this_component_doc = setComponentInfo(session.get("this", None), session["collection"]) this_component_doc["name_delim"] = delim_SN( this_component_doc['name'] ) + this_component_doc["typeinfo"] = SN_typeinfo( this_component_doc['name'] ) # comment comments = setComments(session.get("this", None), session.get("runId",None), this_component_doc) @@ -54,6 +55,7 @@ def show_component(): # set summary summary = setQCSummary(session.get("this", None)) + summary_pdb = setQCSummaryAtProdDB(session.get("this", None)) latest_qc_status = localdb.QC.module.status.find_one( {"component":session.get("this", None),"proddbVersion":proddbv} ) @@ -150,6 +152,7 @@ def show_component(): "electrical": electrical, "qctest":qctest, "summary": summary, + "summary_pdb": summary_pdb, "upload_status": upload_status, "flow_version": flow_version, "latest_tests": latest_tests, @@ -397,9 +400,10 @@ def analyze_scan(): for _id in this_run_ids: if _id != "": run_ids_without_blank.append(_id) + table_docs["run"] = ea.createPartSummary( this_run_ids, table_docs["scans"]["scantypes"]) for this_run_id in this_run_ids: - table_docs["plots"].append(setPlots(table_docs["_id"], "collection", this_run_id, True, False)) + table_docs["plots"].append(setPlots(table_docs["_id"], "collection", this_run_id, True, True)) commit_doc = ea.createElecTestDocs( run_ids_without_blank, table_docs["scans"] ) if table_docs["scans"]["analysis"]["run"]: @@ -455,7 +459,7 @@ def analyze_scan(): table_docs["total"] = len(table_docs["run"]) - return render_template( "selection.html", table_docs=table_docs, timezones=pytz.all_timezones ) + return render_template( "electrical_test/selection.html", table_docs=table_docs, timezones=pytz.all_timezones ) @component_api.route("/upload_flag", methods=["GET", "POST"]) def upload_flag(): @@ -484,7 +488,7 @@ def upload_flag(): result_para["flag"] = table_docs["flag"] elif "TUNING" in table_docs["test"]: result_para["flag"] = table_docs["flag"] - temp = table_docs["test"].split("_")[1].replace("min", "-") + temp = table_docs["test"].split("_")[1].replace("MINUS", "-").replace("DEG","") result_para["setting_temp"] = int(temp) new_flag_doc = { @@ -661,18 +665,18 @@ def select_test(): elif table_docs["pstage"] == "nextcomplete": if not table_docs["nstage"]: - table_docs["text"] = "Please select stage from pull-down menue..." + table_docs["text"] = "Please select stage from pull-down menu..." table_docs["pstage"] = "complete" qc_status = localdb.QC.module.status.find_one({"component" : str(this_cp["_id"])}) stage_list = [item for item in qc_status["QC_results"]] table_docs["stage"] = stage_list[stage_list.index(table_docs["stage"])-1] table_docs["selectstage"] = skipNextStage(this_cp["_id"], table_docs["stage"]) - return render_template("selection.html", table_docs=table_docs, timezones=pytz.all_timezones) + return render_template("electrical_test/selection.html", table_docs=table_docs, timezones=pytz.all_timezones) localdb.QC.module.status.update({"component":str(this_cp["_id"]),"proddbVersion":proddbv},{"$set":{"currentStage": table_docs["nstage"]}}) #table_docs["stage"] = table_docs["nstage"] table_docs["total"] = len(table_docs["run"]) - return render_template( "selection.html", table_docs=table_docs, timezones=pytz.all_timezones ) + return render_template( "electrical_test/selection.html", table_docs=table_docs, timezones=pytz.all_timezones ) @component_api.route("/set_stage", methods=["GET", "POST"]) @@ -879,6 +883,7 @@ def setComponentInfo(i_oid, i_col): session["unit"] = this["componentType"].lower() ### TODO wakarinikui docs = { "name": this["name"], + "component":i_oid, "stage": qc_doc.get("currentStage","..."), "parents": parents, "children": children, @@ -925,6 +930,24 @@ def setQCSummary(i_oid): summary[stage] = doc["QC_results"][stage] return summary +def setQCSummaryAtProdDB(i_oid): + summary = {} + doc = localdb.QC.module.status.find_one( {"component":i_oid,"proddbVersion":proddbv} ) + + + if doc == None: + return summary + + qcDoc = userdb.QC.status.find_one( {"code":doc['componentType'] } ) + stage_flow = qcDoc['stage_flow'] + + cstage = doc["currentStage"] + for stage in stage_flow: + if stage == cstage: + break + summary[stage] = doc["QC_results_pdb"][stage] + return summary + def setLatestQCSummary(i_oid): tests = {} @@ -1492,12 +1515,17 @@ def setCount(): # TODO ./dev/component.py # Set Result Plots by ROOT CXX def setPlots(i_oid, i_col, i_tr_oid, selection = False, dumpPlots = True): # TODO ./dev/component.py plots = {} + + logger.info( 'setPlots(): {}, {}, {}, {}, {}'.format( i_oid, i_col, i_tr_oid, selection, dumpPlots ) ) + if not i_tr_oid: return plots if localdb.testRun.find_one({"_id": ObjectId(i_tr_oid)}) == None: return plots + logger.info( 'setPlots(): found {} in localdb.testRun'.format( i_tr_oid ) ) + plot_root.retrieveFiles(localdb, i_tr_oid, selection) if dumpPlots: @@ -2040,7 +2068,7 @@ class ElectricalAnalyzer: def createPartSummary( self, run_ids, scantype ): list_ = [] for index,tr_oid in enumerate(run_ids): - if tr_oid: + if tr_oid != "": query = {"_id": ObjectId(tr_oid)} this_run = localdb.testRun.find_one(query) testType = this_run["testType"] diff --git a/viewer/templates/comment.html b/viewer/templates/comment.html index bb833a44..fa9edae7 100644 --- a/viewer/templates/comment.html +++ b/viewer/templates/comment.html @@ -6,8 +6,9 @@ <tr> <th scope="col" width="50%" class="text-left">Comment</th> <th scope="col" width="15%" class="text-left">Name</th> - <th scope="col" width="15%" class="text-left">Institution</th> + <!--th scope="col" width="15%" class="text-left">Institution</th--> <th scope="col" width="10%" class="text-left">Date</th> + <th scope="col" class="text-left"></th> </tr> </thead> {% endif %} @@ -19,24 +20,28 @@ <th scope="col" class="text-left"><h4 style="font-size: 8pt;">{{ comment['comment'] | safe }}</h4></th> <!--th scope="col" class="text-left"><h4 style="font-size: 8pt;"></h4>{{ comment['componentType'] }}</th--> <th scope="col" class="text-left"><h4 style="font-size: 8pt;">{{ comment['name'] }}</h4></th> - <th scope="col" class="text-left"><h4 style="font-size: 8pt;">{{ comment['institution'] }}</h4></th> + <!--th scope="col" class="text-left"><h4 style="font-size: 8pt;">{{ comment['institution'] }}</h4></th--> <th scope="col" class="text-left"><h4 style="font-size: 8pt;">{{ comment['datetime'] }}</h4></th> + <th scope="col" class="text-left"></th> </tr> {% endfor %} {% endif %} {% if session['logged_in'] %} + <tr></tr> <tr> <form action="{{ url_for('component_api.edit_comment', id=component['_id'], collection=component['collection'], runId=session['runId']) }}" method="post" enctype="multipart/form-data"> - <td scope="col" align="center" colspan="1"><textarea required="required" class="form-control input-normal" placeholder="comment" name = "text"></textarea></td> + <td scope="col" align="center" colspan="1"><textarea required="required" class="form-control input-normal" placeholder="Fill your comment here..." name = "text"></textarea></td> <td scope="col" align="center" colspan="1"><input type="text" required="required" class="form-control input-normal" value = "{{ session['fullname'] }}" placeholder="name" name = "text2"></td> - <td scope="col" align="center" colspan="1"><input type="text" required="required" class="form-control input-normal" value = "{{ session['institution'] }}" placeholder="institution" name = "text3"></td> + <!--td scope="col" align="center" colspan="1"><input type="text" required="required" class="form-control input-normal" value = "{{ session['institution'] }}" placeholder="institution" name = "text3"></td--> + <td /> <td scope="col" align="center middle" > - <input type="hidden" value="{{ session['user_name'] }}" name="user"> {{ session['user_name'] }} + <input type="hidden" value="{{ session['user_name'] }}" name="user"> <input type="hidden" name="id" value={{ component['_id'] }} > <input type="hidden" name="type" value={{ component['type'] }} > - <p><input type = "submit" value = "submit" style="font-size: 10pt;"></p> + <button type="button" class="btn btn-primary btn-sm" type="submit" value="submit">Save</button> + <!--p><input type = "submit" value = "submit" style="font-size: 10pt;"></p--> </td> </form> </tr> diff --git a/viewer/templates/component.html b/viewer/templates/component.html index f008a5ad..11d70b1e 100644 --- a/viewer/templates/component.html +++ b/viewer/templates/component.html @@ -71,70 +71,7 @@ th { background-color: #eeeeff; } {% set name = component['info']['name'] %} <h1 style="margin-top: 20px;">{{ component['info']['name_delim'] }}</br /> <span style='font-size: 50%;'> - {% if 'PI' == name[3:5] %} - {% set isModule = True %} - L0/L1 Inner Module - - {% elif 'PB' == name[3:5] %} - {% set isModule = True %} - Outer Barrel Module - - {% elif 'PE' == name[3:5] %} - {% set isModule = True %} - Outer Endcap Module - - {% elif 'PGM2' in name[3:7] %} - {% set isModule = True %} - Outer Quad Module - - {% elif 'PGR2' in name[3:7] %} - {% set isModule = True %} - Dual Chip Module - - {% elif 'PGR0' in name[3:7] %} - {% set isModule = True %} - Single Chip Module - - {% elif 'B' in name[5] %} - {% set isModule = False %} - Bare Module - - {% elif 'P' in name[5] %} - {% set isModule = False %} - PCB - - {% elif 'XM' in name[5:7] %} - {% set isModule = True %} - Tutorial Module - - {% elif 'XP' in name[5:7] %} - {% set isModule = False %} - Tutorial PCB - - {% elif 'XB' in name[5:7] %} - {% set isModule = False %} - Tutorial Bare Module - - {% else %} - {% set isModule = False %} - Others - {% endif %} - - - {% if isModule == True %} - - / - {% if '0' == name[7] %} - RD53A - {% elif '1' == name[7] %} - ITkPix_v1.0 - {% elif '2' == name[7] %} - ITkPix_v1.1 - {% elif '3' == name[7] %} - ITkPix_v2 - {% endif %} - {% endif %} - + {{ component['info']['typeinfo'] }} </span><br /> <a href='https://uuappg01-eu-w-1.plus4u.net/ucl-itkpd-maing01/dcb3f6d1f130482581ba1e7bbe34413c/componentView?code={{component["info"]["proID"]}}' target="_blank" rel="noopener noreferrer" style="font-size: 30%;"><i class="fa fa-external-link "></i> [ITkPD: {{component['info']['proID']}}]</a></h1> @@ -159,7 +96,7 @@ th { background-color: #eeeeff; } </h2> {% endif %} - <h3><span style="font-size: 70%;">Current QC Stage:</span> {{ component['info']['stage'] }}</h3> + <!--h3><span style="font-size: 70%;">Current QC Stage:</span> {{ component['info']['stage'] }}</h3--> {% if session['logged_in'] and ( component['info']['type'] == "module" or component['info']['type'] == "bare_module" or component['info']['type'] == "module_pcb" ) %} <a href="{{ url_for('component_api.customize_module', module=component["info"]["name"])}}"><button type="button" class="btn btn-primary" style="margin-left:10px;"><i class="fa fa-gear "></i> Customize Module Stage</button></a> <a href="{{ url_for('component_api.result_transceiver', module=component["info"]["name"], stage='select')}}"><button type="button" class="btn btn-primary" style="margin-left:10px;"><i class="fa fa-paper-plane-o "></i> Upload/Download QC-test results</button></a> @@ -181,21 +118,29 @@ th { background-color: #eeeeff; } <table class="table table-sm table-bordered" style="font-size: 8pt;"> <thead class="table-light"> <tr> - <th scope="col" class="text-left" width="100">Item</th> + <th scope="col" class="text-left" width="200">Item</th> <th scope="col" class="text-left">Value</th> </tr> </thead> <tbody> <tr> - <th scope="raw" class="text-left" width="100"> + <th scope="raw" class="text-left" width="200"> Serial Number </th> <td> - {{ component['info']['name_delim'] }} + {{ component['info']['name'] }} + </td> + </tr> + <tr> + <th scope="raw" class="text-left" width="200"> + LocalDB Component ID + </th> + <td> + {{ component['info']['component'] }} </td> </tr> <tr> - <th scope="raw" class="text-left" width="100"> + <th scope="raw" class="text-left" width="200"> Component Type </th> <td> @@ -204,7 +149,7 @@ th { background-color: #eeeeff; } </tr> {% if isModule == True %} <tr> - <th scope="raw" class="text-left" width="100"> + <th scope="raw" class="text-left" width="200"> FE type </th> <td> @@ -214,7 +159,7 @@ th { background-color: #eeeeff; } {% endif %} {% if component['info']['parents'] %} <tr> - <th scope="raw" class="text-left" width="100"> + <th scope="raw" class="text-left" width="200"> Parent Component </th> <td> @@ -228,7 +173,7 @@ th { background-color: #eeeeff; } {% endif %} {% if component['info']['children'] %} <tr> - <th scope="raw" class="text-left" width="100">Sub-Components</th> + <th scope="raw" class="text-left" width="200">Sub-Components</th> <td> {% for child in component['info']['children'] %} @@ -264,22 +209,24 @@ th { background-color: #eeeeff; } </div> - + <!-- Test Result Section --> + + <!-- For Electrical Scan Result --> {% if component['electrical']['results'] %} <hr /> - <h3>Scan Result: {{ component['electrical']['results']['info']['testType'] }} / run {{ component['electrical']['results']['info']['runNumber'] }} + <h3>Scan Result: {{ component['electrical']['results']['info']['testType'] }} / run {{ component['electrical']['results']['info']['runNumber'] }} (Stage: {{ component['electrical']['results']['info']['stage'] }}) <br /><span style="font-size: 50%;">(ObjectId: {{ component['_id'] }})</span></h3> {% endif %} {% if component['electrical']['plots'] %} - <br> - {% include "plot.html" %} + <br> + {% include "electrical_test/plot.html" %} {% endif %} - + {% if component['electrical']['results'] %} - <br> - {% include "result.html" %} + <br> + {% include "electrical_test/result.html" %} {% endif %} - + {% if component['electrical']['dcs'] %} <h4 style="font-size: 10pt;"><i class="fa fa-paint-brush"></i> DCS {% if not component['electrical']['dcs']['dcs_data_exist'] %} @@ -289,36 +236,41 @@ th { background-color: #eeeeff; } {% endif %} </h4> {% endif %} - + + + <!-- For Component Properties --> {% if component['property']['properties'] %} - <br> - <h3><i class="fa fa-angle-double-right"></i> <b><span style="color: #AAA;">Test:</span> {{ component['property']['properties']['testType'] }}</b> / {{ component['property']['properties']['currentStage'] }} </h3> - {% if component['property']['properties']['results'] %} - <br> - {% if component['property']['properties']['testType'] in component['property']['prop_item']['test'] %} - {% include component['property']['prop_item']['html'][component['property']['properties']['testType']] %} - {% endif %} - {% else %} - <br> - <h4 style='font-size: 13pt;'>This result page is being constructed.</h4> + {% set properties = component['property']['properties'] %} + <br> + <h3><i class="fa fa-angle-double-right"></i> <b><span style="color: #AAA;">Test:</span> {{ properties['testType'] }}</b> (Stage: {{ properties['currentStage'] }}) </h3> + {% if properties['results'] %} + <br> + {% if properties['testType'] in component['property']['prop_item']['test'] %} + {% include component['property']['prop_item']['html'][properties['testType']] %} {% endif %} + {% else %} + <br> + <h4 style='font-size: 13pt;'>This result page is being constructed.</h4> {% endif %} - - {% if component['qctest']['results'] %} + {% endif %} + + <!-- For Component Results --> + {% if component['qctest']['results'] %} + {% set results = component['qctest']['results'] %} + <br> + <h3><i class="fa fa-angle-double-right"></i> <b><span style="color: #AAA;">Test:</span> {{ results['testType'] }}</b> (Stage: {{ results['currentStage'] }})</h3> + {% if results['results'] %} <br> - <h3><i class="fa fa-angle-double-right"></i> <b><span style="color: #AAA;">Test:</span> {{ component['qctest']['results']['testType'] }}</b> / {{ component['qctest']['results']['currentStage'] }} </h3> - {% if component['qctest']['results']['results'] %} - <br> - {% if component['qctest']['results']['testType'] in component['qctest']['test_item']['test'] %} - {% include component['qctest']['test_item']['html'][component['qctest']['results']['testType']] %} - {% else %} - {% include "qc_others.html" %} - {% endif %} + {% if results['testType'] in component['qctest']['test_item']['test'] %} + {% include component['qctest']['test_item']['html'][results['testType']] %} {% else %} - <br> - <h4 style='font-size: 13pt;'>This result page is being constructed.</h4> + {% include "displayResults/qc_others.html" %} {% endif %} + {% else %} + <br> + <h4 style='font-size: 13pt;'>This result page is being constructed.</h4> {% endif %} + {% endif %} {% if component['latest_tests'] %} diff --git a/viewer/templates/components_table.html b/viewer/templates/components_table.html index 8ba32126..b3275b71 100644 --- a/viewer/templates/components_table.html +++ b/viewer/templates/components_table.html @@ -1,50 +1,32 @@ <style type="text/css"> .balloonoya { position: relative; - /* 指定ã—ãŸåˆ†ã ã‘相対的ã«ç§»å‹• */ } .balloonoya:hover .balloon { display: inline; - /* インラインè¦ç´ ã¨ã—ã¦è¡¨ç¤º */ } .balloon { position: absolute; - /* 親è¦ç´ を基準 */ display: none; - /* è¦ç´ ã‚’éžè¡¨ç¤º */ padding: 2px; - /* テã‚ストã®å‰å¾Œã®ä½™ç™½ */ background-color: rgba(26, 148, 255, 0.312); - /* 背景色(é€æ˜Žåº¦ï¼‰ */ width: 180px; - /* å¹ã出ã—全体ã®å¹… */ left: 0%; - /* 表示ä½ç½® */ bottom: 100%; - /* 表示ä½ç½® */ margin-bottom: 12px; - /* 表示ä½ç½® */ font-size: 80%; - /* æ–‡å—サイズ */ } .balloon:after { border-top: 12px solid rgba(26, 148, 255, 0.312); - /* å¹ã出ã—å£ã®é«˜ã•ãƒ»è‰² */ border-left: 10px solid transparent; - /* å¹ã出ã—å£ã®å¹…1ï¼ï¼’ */ border-right: 10px solid transparent; - /* å¹ã出ã—å£ã®å¹…1ï¼ï¼’ */ bottom: -12px; - /* å¹ã出ã—å£ã®ä½ç½®èª¿æ•´ */ left: 5%; - /* å¹ã出ã—å£ã®æ¨ªä½ç½® */ content: ""; - /* コンテンツã®æŒ¿å…¥ */ position: absolute; - /* 親è¦ç´ を基準 */ } </style> diff --git a/viewer/templates/displayResults/qc_others.html b/viewer/templates/displayResults/qc_others.html index 9bd2603e..f820b7f9 100644 --- a/viewer/templates/displayResults/qc_others.html +++ b/viewer/templates/displayResults/qc_others.html @@ -36,16 +36,11 @@ border: none; } </style> <h4>This page is being constructed.(only show raw data)</h4> -<div class='row align-items-top justify-content-left'> - <div class='col'> - <h4 style='font-size: 13pt;'><i class='fa fa-cog'></i> Result of This Test</h4> - </div> -</div> <div class='row align-items-top justify-content-left'> <div class='col-md-6'> - <h4 style='font-size: 10pt;'> + <h4> {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Scan + <i class='fa fa-file'></i> Header Info {% endif %} </h4> {% if component['qctest']['results'] %} @@ -85,9 +80,9 @@ border: none; <div class='row align-items-top justify-content-left'> <div class='col-md-6'> - <h4 style='font-size: 10pt;'> + <h4> {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Result + <i class='fa fa-file'></i> Contents {% endif %} </h4> {% if component['qctest']['results'] %} diff --git a/viewer/templates/displayResults/tuning.html b/viewer/templates/displayResults/tuning.html index 8a4a20f3..5106efbe 100644 --- a/viewer/templates/displayResults/tuning.html +++ b/viewer/templates/displayResults/tuning.html @@ -35,16 +35,11 @@ background-color: #ffffff; border: none; } </style> -<div class='row align-items-top justify-content-left'> - <div class='col'> - <h4 style='font-size: 13pt;'><i class='fa fa-cog'></i> Result of This Test</h4> - </div> -</div> <div class='row align-items-top justify-content-left'> <div class='col-md-6'> - <h4 style='font-size: 10pt;'> + <h4> {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Scan + <i class='fa fa-file'></i> Header Info {% endif %} </h4> {% if component['qctest']['results'] %} @@ -84,9 +79,9 @@ border: none; <div class='row align-items-top justify-content-left'> <div class='col-md-6'> - <h4 style='font-size: 10pt;'> + <h4> {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Result + <i class='fa fa-file'></i> Contents {% endif %} </h4> {% if component['qctest']['results'] %} diff --git a/viewer/templates/displayTests/massresult.html b/viewer/templates/displayTests/massresult.html deleted file mode 100644 index 52785fcd..00000000 --- a/viewer/templates/displayTests/massresult.html +++ /dev/null @@ -1,118 +0,0 @@ -<style> -input[type="checkbox"].data { -display: none; -margin: 0px; -padding: 0px; -background-color: #ffffff; -} -input[type="checkbox"].data + label { -margin: 0px; -padding: 0px; -border: none; -background-color: #ffffff; -} -input[type="checkbox"].data:checked + label { -margin: 0px; -padding: 0px; -border: none; -color: #3cb371; -background-color: #ffffff; -} -input[type="checkbox"].data:checked + label:hover { -margin: 0px; -padding: 0px; -color: #3cb371; -border: none; -background-color: #ffffff; -} -label.data { -margin: 0px; -padding: 0px; -text-align: center; -cursor: pointer; -color: #dddddd; -background-color: #ffffff; -border: none; -} -</style> -<div class='row align-items-top justify-content-left'> - <div class='col'> - <h4 style='font-size: 13pt;'><i class='fa fa-cog'></i> Result of Mass Measurement</h4> - </div> -</div> -<div class='row align-items-top justify-content-left'> - <div class='col-md-6'> - <h4 style='font-size: 10pt;'> - {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Scan - {% endif %} - </h4> - {% if component['qctest']['results'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - <tr> - <th scope='raw' class='text-left'>component id</td> - <td class='text-left'>{{ component['qctest']['results']['component'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>Stage</td> - <td class='text-left'>{{ component['qctest']['results']['currentStage'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>test Type</td> - <td class='text-left'>{{ component['qctest']['results']['testType'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>institute</td> - <td class='text-left'>{{ component['qctest']['results']['institute'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>user</td> - <td class='text-left'>{{ component['qctest']['results']['user'] }}</td> - </tr> - </tbody> - </table> - {% endif %} - </div> -</div> - -<div class='row align-items-top justify-content-left'> - <div class='col-md-6'> - <h4 style='font-size: 10pt;'> - {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Result - {% endif %} - </h4> - {% if component['qctest']['results'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - {% for keys in component['qctest']['results']['results'] %} - {% if keys == 'property' %} - <tr> - <th scope='raw' class='text-left'> Scale_accuracy </td> - <td class='text-left'>{{ component['qctest']['results']['results'][keys]['Scale_accuracy'] }}</td> - </tr> - {% else %} - <tr> - <th scope='raw' class='text-left'> {{ keys }} </td> - <td class='text-left'>{{ component['qctest']['results']['results'][keys] }}</td> - </tr> - {% endif %} - {% endfor %} - </tbody> - </table> - {% endif %} - </div> -</div> diff --git a/viewer/templates/displayTests/viresult.html b/viewer/templates/displayTests/viresult.html deleted file mode 100644 index 57d6c7fb..00000000 --- a/viewer/templates/displayTests/viresult.html +++ /dev/null @@ -1,138 +0,0 @@ -<style> -input[type="checkbox"].data { -display: none; -margin: 0px; -padding: 0px; -background-color: #ffffff; -} -input[type="checkbox"].data + label { -margin: 0px; -padding: 0px; -border: none; -background-color: #ffffff; -} -input[type="checkbox"].data:checked + label { -margin: 0px; -padding: 0px; -border: none; -color: #3cb371; -background-color: #ffffff; -} -input[type="checkbox"].data:checked + label:hover { -margin: 0px; -padding: 0px; -color: #3cb371; -border: none; -background-color: #ffffff; -} -label.data { -margin: 0px; -padding: 0px; -text-align: center; -cursor: pointer; -color: #dddddd; -background-color: #ffffff; -border: none; -} -</style> - {% if not component["qctest"]["results"]["img"] %} -<p><font color=#ff0000>### No defects were identified ###</font></p> -{% endif %} - - -{% if component["qctest"]["results"]["img"] %} -<h4><i class="fa fa-paint-brush"></i> Pictures of Visual Inspection</h4> - <div class="row align-items-left justify-content-flex-start"> - <div class="col"> - <table class="table table-sm table-bordered" style="font-size: 8pt; table-layout: fixed;"> - <thead class="table-light"> - <tr> - <th scope='col' class='text-left' style="word-wrap:break-word;" width=100 colspan=1 >Keys</th> - <th ~~~ scope="col" class="text-left" style="word-wrap:break-word;" width=200*{{component["qctest"]["results"]["anomalyNum"]["image"]}} colspan={{component["qctest"]["results"]["anomalyNum"]["image"]}}> Pictuite with anomaly </th> - </tr> - <th scope='col' class='text-left' style="word-wrap:break-word;" width=100 colspan=1 >anomaly</th> - {% for i in component["qctest"]["results"]["img"] %} - {% if component["qctest"]["results"]["results"]["anomaly"][i] %} - <th scope='col' class='text-left' style="word-wrap:break-word;" width=200 colspan=1 >{{ component["qctest"]["results"]["results"]["anomaly"][i] }}</th> - {% else %} - <th scope='col' class='text-left' style="word-wrap:break-word;" width=200 colspan=1 > comment only </th> - {% endif %} - {% endfor %} - </thead> - <tbody> - <tr> - <th scope='raw' class='text-left'> picture </td> - {% for i in component["qctest"]["results"]["img"] %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href={{ component["qctest"]["results"]["img"][i] }} target="_brank" rel="noopener noreferrer"> - <img src={{ component["qctest"]["results"]["img"][i] }} title= "Click to expand" width="300px"></img> - </a> - </td> - {% endfor %} - </tr> - <tr> - <th scope='raw' class='text-left'> comment </td> - {% for i in component["qctest"]["results"]["img"] %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> {{component["qctest"]["results"]["results"]["comment"][i]}} </td> - {% endfor %} - </tr> - </tbody> - </table> - </div> - </div> -{% endif %} - - -<p><a href="{{ component['qctest']['results']['orig_cache'] }}"><img src={{ component["qctest"]["results"]["thumb"] }} /></a></p> - -<p> - <a href="{{ component['qctest']['results']['orig_cache'] }}" download> - <button type="button" class="btn btn-info">Download Full Image</button> - </a> - <a href="{{ url_for('picture_data_api.picData', id=component['_id'], collection=component['collection'], runId=component['qctest']['results']['runId']) }}"> - <button type="button" class="btn btn-info">Compare w/ Refs.</button> - </a> -</p> - -<div class='row align-items-top justify-content-left' style="margin-top: 20px;"> - <div class='col-md-6'> - <h4 style='font-size: 10pt;'> - {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Scan - {% endif %} - </h4> - {% if component['qctest']['results'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - <tr> - <th scope='raw' class='text-left'>component id</td> - <td class='text-left'>{{ component['qctest']['results']['component'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>Stage</td> - <td class='text-left'>{{ component['qctest']['results']['currentStage'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>test Type</td> - <td class='text-left'>{{ component['qctest']['results']['testType'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>institute</td> - <td class='text-left'>{{ component['qctest']['results']['institute'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>user</td> - <td class='text-left'>{{ component['qctest']['results']['user'] }}</td> - </tr> - </tbody> - </table> - {% endif %} - </div> -</div> - diff --git a/viewer/templates/displayTests/wirebondingresult.html b/viewer/templates/displayTests/wirebondingresult.html deleted file mode 100644 index e42f747c..00000000 --- a/viewer/templates/displayTests/wirebondingresult.html +++ /dev/null @@ -1,148 +0,0 @@ -<style> -input[type="checkbox"].data { -display: none; -margin: 0px; -padding: 0px; -background-color: #ffffff; -} -input[type="checkbox"].data + label { -margin: 0px; -padding: 0px; -border: none; -background-color: #ffffff; -} -input[type="checkbox"].data:checked + label { -margin: 0px; -padding: 0px; -border: none; -color: #3cb371; -background-color: #ffffff; -} -input[type="checkbox"].data:checked + label:hover { -margin: 0px; -padding: 0px; -color: #3cb371; -border: none; -background-color: #ffffff; -} -label.data { -margin: 0px; -padding: 0px; -text-align: center; -cursor: pointer; -color: #dddddd; -background-color: #ffffff; -border: none; -} -</style> -<div class='row align-items-top justify-content-left'> - <div class='col'> - <h4 style='font-size: 13pt;'><i class='fa fa-cog'></i> Result of Wirebonding Information</h4> - </div> -</div> -<div class='row align-items-top justify-content-left'> - <div class='col-md-6'> - <h4 style='font-size: 10pt;'> - {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Scan - {% endif %} - </h4> - {% if component['qctest']['results'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - <tr> - <th scope='raw' class='text-left'>component id</td> - <td class='text-left'>{{ component['qctest']['results']['component'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>Stage</td> - <td class='text-left'>{{ component['qctest']['results']['currentStage'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>test Type</td> - <td class='text-left'>{{ component['qctest']['results']['testType'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>institute</td> - <td class='text-left'>{{ component['qctest']['results']['institute'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>user</td> - <td class='text-left'>{{ component['qctest']['results']['user'] }}</td> - </tr> - </tbody> - </table> - {% endif %} - </div> -</div> - -<div class='row align-items-top justify-content-left'> - <div class='col-md-6'> - <h4 style='font-size: 10pt;'> - <i class='fa fa-file'></i> Result - </h4> - {% if component['qctest']['results'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - {% for keys in component['qctest']['results']['results'] %} - {% if keys != 'property' %} - <tr> - <th scope='raw' class='text-left'> {{ keys }} </td> - <td class='text-left'>{{ component['qctest']['results']['results'][keys] }}</td> - </tr> - {% endif %} - {% endfor %} - </tbody> - </table> - {% endif %} - </div> - <div class='col-md-6'> - <h4 style='font-size: 10pt;'> - <i class='fa fa-file'></i> Property - </h4> - {% if component['qctest']['results'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - {% for keys in component['qctest']['results']['results']['property'] %} - {% if keys != 'Bond_program' %} - <tr> - <th scope='raw' class='text-left'> {{ keys }} </td> - <td class='text-left'>{{ component['qctest']['results']['results']['property'][keys] }}</td> - </tr> - {% else %} - {% if component['qctest']['results']['results']['property'][keys] != "" %} - <tr> - <th scope='raw' class='text-left'> {{ keys }} </td> - <td class='text-left'><a href="/localdb/static/qc_attachment/Bonding_Program.dat" download><i class="fa fa-download "></i> Download Bonding Program</a></td> - </tr> - {% else %} - <tr> - <th scope='raw' class='text-left'> {{ keys }} </td> - <td class='text-left'><font color=#ff0000> No Bonding Program </font></td> - </tr> - {% endif %} - {% endif %} - {% endfor %} - </tbody> - </table> - {% endif %} - </div> -</div> diff --git a/viewer/templates/displayTests/wpenvelope_result.html b/viewer/templates/displayTests/wpenvelope_result.html deleted file mode 100644 index 0a9963d9..00000000 --- a/viewer/templates/displayTests/wpenvelope_result.html +++ /dev/null @@ -1,111 +0,0 @@ -<style> -input[type="checkbox"].data { -display: none; -margin: 0px; -padding: 0px; -background-color: #ffffff; -} -input[type="checkbox"].data + label { -margin: 0px; -padding: 0px; -border: none; -background-color: #ffffff; -} -input[type="checkbox"].data:checked + label { -margin: 0px; -padding: 0px; -border: none; -color: #3cb371; -background-color: #ffffff; -} -input[type="checkbox"].data:checked + label:hover { -margin: 0px; -padding: 0px; -color: #3cb371; -border: none; -background-color: #ffffff; -} -label.data { -margin: 0px; -padding: 0px; -text-align: center; -cursor: pointer; -color: #dddddd; -background-color: #ffffff; -border: none; -} -</style> -<div class='row align-items-top justify-content-left'> - <div class='col'> - <h4 style='font-size: 13pt;'><i class='fa fa-cog'></i> Result of This Test</h4> - </div> -</div> -<div class='row align-items-top justify-content-left'> - <div class='col-md-6'> - <h4 style='font-size: 10pt;'> - {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Scan - {% endif %} - </h4> - {% if component['qctest']['results'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - <tr> - <th scope='raw' class='text-left'>component id</td> - <td class='text-left'>{{ component['qctest']['results']['component'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>current Stage</td> - <td class='text-left'>{{ component['qctest']['results']['currentStage'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>test Type</td> - <td class='text-left'>{{ component['qctest']['results']['testType'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>institute</td> - <td class='text-left'>{{ component['qctest']['results']['institute'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>user name</td> - <td class='text-left'>{{ component['qctest']['results']['user'] }}</td> - </tr> - </tbody> - </table> - {% endif %} - </div> -</div> - -<div class='row align-items-top justify-content-left'> - <div class='col-md-6'> - <h4 style='font-size: 10pt;'> - {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Result - {% endif %} - </h4> - {% if component['qctest']['results'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - {% for keys in component['qctest']['results']['results'] %} - <tr> - <th scope='raw' class='text-left'> {{ keys }} </td> - <td class='text-left'>{{ component['qctest']['results']['results'][keys] }}</td> - </tr> - {% endfor %} - </tbody> - </table> - {% endif %} - </div> -</div> diff --git a/viewer/templates/displayTests/xrayresult.html b/viewer/templates/displayTests/xrayresult.html deleted file mode 100644 index 197154b1..00000000 --- a/viewer/templates/displayTests/xrayresult.html +++ /dev/null @@ -1,281 +0,0 @@ -<style> -input[type="checkbox"].data { -display: none; -margin: 0px; -padding: 0px; -background-color: #ffffff; -} -input[type="checkbox"].data + label { -margin: 0px; -padding: 0px; -border: none; -background-color: #ffffff; -} -input[type="checkbox"].data:checked + label { -margin: 0px; -padding: 0px; -border: none; -color: #3cb371; -background-color: #ffffff; -} -input[type="checkbox"].data:checked + label:hover { -margin: 0px; -padding: 0px; -color: #3cb371; -border: none; -background-color: #ffffff; -} -label.data { -margin: 0px; -padding: 0px; -text-align: center; -cursor: pointer; -color: #dddddd; -background-color: #ffffff; -border: none; -} -</style> -<div class='row align-items-top justify-content-left'> - <div class='col'> - <h4 style='font-size: 13pt;'><i class='fa fa-cog'></i> Result of This Test</h4> - </div> -</div> -<div class='row align-items-top justify-content-left'> - <div class='col-md-6'> - <h4 style='font-size: 10pt;'> - {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Scan - {% endif %} - </h4> - {% if component['qctest']['results'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - <tr> - <th scope='raw' class='text-left'>component id</td> - <td class='text-left'>{{ component['qctest']['results']['component'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>Stage</td> - <td class='text-left'>{{ component['qctest']['results']['currentStage'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>test Type</td> - <td class='text-left'>{{ component['qctest']['results']['testType'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>institute</td> - <td class='text-left'>{{ component['qctest']['results']['institute'] }}</td> - </tr> - <tr> - <th scope='raw' class='text-left'>user name</td> - <td class='text-left'>{{ component['qctest']['results']['user'] }}</td> - </tr> - </tbody> - </table> - {% endif %} - </div> -</div> - -<div class='row align-items-top justify-content-left'> - <div class='col-md-6'> - <h4 style='font-size: 10pt;'> - {% if component['qctest']['results'] %} - <i class='fa fa-file'></i> Result - {% endif %} - </h4> - {% if component['qctest']['results'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - {% for keys in component['qctest']['results']['results'] %} - <tr> - <th scope='raw' class='text-left'> {{ keys }} </td> - <td class='text-left'>{{ component['qctest']['results']['results'][keys] }}</td> - </tr> - {% endfor %} - </tbody> - </table> - {% endif %} - {% if component['qctest']['results']['summery'] %} - <table class='table table-sm table-bordered' style='font-size: 8pt;'> - <thead class='table-light'> - <tr> - <th scope='col' class='text-left'>Key</th> - <th scope='col' class='text-left'>Data</th> - </tr> - </thead> - <tbody> - {% for keys in component['qctest']['results']['summery'] %} - <tr> - <th scope='raw' class='text-left'> {{ keys }} </td> - <td class='text-left'>{{ component['qctest']['results']['summery'][keys] }}</td> - </tr> - {% endfor %} - </tbody> - </table> - {% endif %} - </div> -</div> - -{% for scan in component['qctest']["results"]["plots"] %} -<!--<h2 style="font-size: 20pt;"><i class="fa fa-caret-right"></i> {{ scan }} - {% if not component['qctest']["results"]["plots"][scan]["rootsw"] %} - <font color=#ff0000>### NO PLOTTING SOFTWARE ###</font> - {% endif %} -</h2> - -<div class="row align-items-center justify-content-flex-start"> - <div class="col"> - {% for result in component['qctest']["results"]["plots"][scan]["results"] %} - <h4 style="font-size: 10pt;"> - <i class="fa fa-caret-right"></i> {{ result["mapType"] }} - <a href="/localdb/plotData?testRunId={{ result['runId'] }}&mapType={{ result['mapType'] }}">plotly</a> - </h4> - <table class="table table-sm table-bordered" style="font-size: 8pt; table-layout: fixed;"> - <thead class="table-light"> - <tr> - {% for chip, data in result["chips"].items() %} - {% if not data["num"]==0 %} - <th ~~~ scope="col" class="text-left" style="word-wrap:break-word;" width={{ data["length"] }} colspan={{ - data["num"] }}>{{ chip }}</th> - {% endif %} - {% endfor %} - </tr> - </thead> - <tbody> - <tr> - {% for chip, data in result["chips"].items() %} - {% for plot in data["plots"] %} - {{ chip }} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href={{ plot["url"] }} target="_brank" rel="noopener noreferrer"> - <img src={{ plot["url"] }} title={{ plot["name"] }} width={{ data["width"] }} height={{ data["width"] - }}></img> - </a> - </td> - {% endfor %} - {% endfor %} - </tr> - </tbody> - </table> - {% endfor %} - </div> -</div> ---> - - -<style> - img.plot-img:hover { - border: 0.5px solid #CCC; - transform: scale(2.5); - } -</style> -<script> - document.addEventListener("DOMContentLoaded", function () { - var lazyImages = [].slice.call(document.querySelectorAll("img.plot-img")); - - if ("IntersectionObserver" in window) { - let lazyImageObserver = new IntersectionObserver(function (entries, observer) { - entries.forEach(function (entry) { - if (entry.isIntersecting) { - let lazyImage = entry.target; - lazyImage.src = lazyImage.dataset.src; - if (typeof lazyImage.dataset.srcset === "undefined") { - } else { - lazyImage.srcset = lazyImage.dataset.srcset; - } - lazyImage.classList.remove("plot-img"); - lazyImageObserver.unobserve(lazyImage); - } - }); - }); - - lazyImages.forEach(function (lazyImage) { - lazyImageObserver.observe(lazyImage); - }); - } else { - // Possibly fall back to a more compatible method here - } - }); -</script> -<h3><i class='fa fa-cog'></i> Result Plots for {{ scan }}</h3> - - {% for result in component['qctest']["results"]["plots"][scan]["results"]|sort(attribute='mapType')|reverse %} - <h4> - <i class="fa fa-caret-right"></i> <b>{{ result["mapType"] }}</b> - <a href="/localdb/plotData?testRunId={{ result['runId'] }}&mapType={{ result['mapType'] }}" - style="font-size: 50%;">[plotly]</a> - </h4> - <div style="display: flex; flex-wrap: wrap;"> - {% for chip, data in result["chips"].items() %} - {% if not data["num"]==0 %} - <div width={{ data["length"] }} align="left"> - <table class="table table-sm table-bordered" style="font-size: 8pt;"> - <thead class="table-light"> - <tr> - <th ~~~ scope="col" class="text-left" style="word-wrap:break-word;" colspan={{ data["num"] }}>{{ chip }} - </th> - </tr> - </thead> - <tbody> - <tr style="display: flex; flex-wrap: wrap; background: #DDD; max-width: 100%"> - {% for plot in data["plots"] %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href={{ plot["url"] }} target="_brank" rel="noopener noreferrer"> - <img class="plot-img" src="static/assets/images/dummy.png" data-src={{ plot["url"] }} title={{ - plot["name"] }} width={{ data["width"] }} height={{ data["height"] }}></img> - </a> - </td> - {% endfor %} - </tr> - </tbody> - </table> - </div> - {% endif %} - {% endfor %} - </div> - {% endfor %} - - <!--<h4 style="font-size: 10pt;"><i class="fa fa-paint-brush"></i> PLOT - {% if not component['qctest']["results"]["plots"][scan]["rootsw"] %} - <font color=#ff0000>### NO PLOTTING SOFTWARE ###</font> - {% elif component['qctest']["results"]["plots"][scan]["jsroot"] %} - <a href="javascript:displayRoot()">JSROOT</a> - {% endif %} - </h4> - {% if component['qctest']["results"]["plots"][scan]["jsroot"] %} - <div class="row align-items-center justify-content-flex-start"> - <script src="static/jsroot/scripts/JSRootCore.js?gui"></script> - <div id="simpleGUI" path={{ component['qctest']["results"]["plots"][scan]["jsroot"]["path"] }} files={{ - component['qctest']["results"]["plots"][scan]["jsroot"]["files"] }} - style="width:100%;height:600px;margin-bottom:10px;position:relative"> - loading scripts ... - </div> - <script type="text/javascript"> - document.getElementById("simpleGUI").style.display = "none"; - function displayRoot() { - const p1 = document.getElementById("simpleGUI"); - if (p1.style.display == "block") { - p1.style.display = "none"; - } else { - p1.style.display = "block"; - } - } - </script> - </div> - {% endif %}--> - - - -{% endfor %} diff --git a/viewer/templates/latest_test_index.html b/viewer/templates/latest_test_index.html index 64514ccb..3511b01e 100644 --- a/viewer/templates/latest_test_index.html +++ b/viewer/templates/latest_test_index.html @@ -23,7 +23,7 @@ <div class="row align-items-top"> <div style="padding-left: 30px;"> {% if component["latest_tests"]["results"] %} - <table class="table table-lg table-bordered" style="width:500px;"> + <table class="table table-lg table-bordered" style="width:700;"> <thead class="bg-warning"> <tr> <th scope="col" class="text-left">Test Type</th> @@ -34,19 +34,25 @@ {% for test in component["latest_tests"]["results"] %} <tr> <td style="word-wrap:break-word;">{{ component['qctest']['test_items'][test['name']] }}<br /><span style="color: #ccc;">({{ test["name"] }})</span></td> + {% if test["runId"] %} <td style="word-wrap:break-word; background-color:rgb(204, 255, 223);"> - <a href="{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection'], test='qctest', runId=test['runId']) }}">Registered [{{ test["runId"] }}]</a> + <!-- Result is registered --> + <a href="{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection'], test='qctest', runId=test['runId']) }}">Registered [{{ test["runId"] }}]</a> {% else %} <td style="word-wrap:break-word; background-color:#fcc;"> + <!-- Result is not registered yet --> <strong><font style="color: red;">No result!</font></strong> {% endif %} - {% if test["scan_selection"] and session['logged_in'] %} - <a href="{{ url_for('component_api.analyze_scan', id=component['_id'], stage=component['info']['stage'], test=test['name'])}}"><button type="button" class="btn btn-primary">Select scan results</button></a> - {% elif "TUNING" in test["name"] and session['logged_in'] or test["name"] == "ADC_CALIBRATION" and session['logged_in'] %} - <a - href="{{ url_for('component_api.upload_flag', id=component['_id'], stage=component['info']['stage'], test=test['name'])}}"><button - type="button" class="btn btn-primary"> Create a result </button></a> + + {% if session['logged_in'] %} + {% if test["scan_selection"] %} + <a href="{{ url_for('component_api.analyze_scan', id=component['_id'], stage=component['info']['stage'], test=test['name'])}}"><button type="button" class="btn btn-secondary" style="font-size: 70%;">Checkout Scans</button></a> + {% elif "TUNING" in test["name"] and session['logged_in'] or test["name"] == "ADC_CALIBRATION" and session['logged_in'] %} + <a href="{{ url_for('component_api.upload_flag', id=component['_id'], stage=component['info']['stage'], test=test['name'])}}"><button type="button" class="btn btn-secondary" style="font-size: 70%;"> Checkout </button></a> + {% else %} + (Upload via QCHelper) + {% endif %} {% endif %} </td> </tr> diff --git a/viewer/templates/nomodule_result.html b/viewer/templates/nomodule_result.html index fa0f5a1c..2c4acad8 100644 --- a/viewer/templates/nomodule_result.html +++ b/viewer/templates/nomodule_result.html @@ -77,7 +77,7 @@ border: none; {% if component['electrical']['plots'] %} <br> - {% include "plot.html" %} + {% include "electrical_test/plot.html" %} {% endif %} <div class='row align-items-top justify-content-center'> diff --git a/viewer/templates/result_tranceiver.html b/viewer/templates/result_tranceiver.html index a17c0813..c2f7cee7 100644 --- a/viewer/templates/result_tranceiver.html +++ b/viewer/templates/result_tranceiver.html @@ -433,7 +433,7 @@ table.toppage td.border_inner { <div class="container"> <div class="row align-items-center justify-content-center"> <div class="col"> - {% if doc["download_list"] != [] %} + {% if doc["download_list"] %} <h3> Download has finished!</h3> <h4><i class='fa fa-file'></i> List of downloaded QC-test results</h4> <div class="row align-items-top card-body"> diff --git a/viewer/templates/show_analysis_result.html.tmp~ b/viewer/templates/show_analysis_result.html.tmp~ deleted file mode 100644 index 0dcb869d..00000000 --- a/viewer/templates/show_analysis_result.html.tmp~ +++ /dev/null @@ -1,41 +0,0 @@ -<br> -<h4 style='font-size: 10pt;'> - <i class="fa fa-file"></i> Plot -</h4> -<table class="table table-sm table-bordered" style="font-size: 10pt;"> - {% for step in chip_result['plot_info'] %} - <p>{{ step }}</p> - <thead class="table-light"> - <tr> - <th ~~~ scope="col" class="text-left" style="word-wrap:break-word;" >{{ step["analysis"] }}</th> - </tr> - </thead> - <tbody> - <tr> - {% if step["plots"].find("map") %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href="{{ {{ step["plots"]["map"] }} }}" target="_brank" rel="noopener noreferrer"> - <img src="{{ step["plots"]["map"] }}" title="{{ step["analysis"] }}" width=300 height=300 ></img> - </a> - </td> - {% endif %} - {% if step["plots"].find("proj") %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href="{{ {{ step["plots"]["proj"] }} }}" target="_brank" rel="noopener noreferrer"> - <img src="{{ step["plots"]["proj"] }}" title="{{ step["analysis"] }}" width=300 height=300 ></img> - </a> - </td> - {% endif %} - {% if step["plots"].find("badpixels") %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href="{{ {{ step["plots"]["badpixels"] }} }}" target="_brank" rel="noopener noreferrer"> - <img src="{{ step["plots"]["badpixels"] }}" title="{{ step["analysis"] }}" width=300 height=300 ></img> - </a> - </td> - {% endif %} - </tr> - </tbody> - {% endfor %} -</table> - - diff --git a/viewer/templates/show_analysis_result.html~ b/viewer/templates/show_analysis_result.html~ deleted file mode 100644 index 1c0de6fc..00000000 --- a/viewer/templates/show_analysis_result.html~ +++ /dev/null @@ -1,41 +0,0 @@ -<br> -<h4 style='font-size: 10pt;'> - <i class="fa fa-file"></i> Plot -</h4> -<table class="table table-sm table-bordered" style="font-size: 10pt;"> - {% for step in chip_result['plot_info'] %} - <p>{{ step }}</p> - <thead class="table-light"> - <tr> - <th ~~~ scope="col" class="text-left" style="word-wrap:break-word;" >{{ step["analysis"] }}</th> - </tr> - </thead> - <tbody> - <tr> - {% if 'map' in step['img'] %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href="{{ step['img']['map'] }}" target="_brank" rel="noopener noreferrer"> - <img src="{{ step['img']['map'] }}" title="{{ step["analysis"] }}" width=250 height=250 ></img> - </a> - </td> - {% endif %} - {% if 'proj' in step['img'] %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href="{{ step['img']['proj'] }}" target="_brank" rel="noopener noreferrer"> - <img src="{{ step['img']['proj'] }}" title="{{ step["analysis"] }}" width=250 height=250 ></img> - </a> - </td> - {% endif %} - {% if 'badpixels' in step['img'] %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href="{{ step['img']['badpixels'] }}" target="_brank" rel="noopener noreferrer"> - <img src="{{ step['img']['badpixels'] }}" title="{{ step["analysis"] }}" width=250 height=250 ></img> - </a> - </td> - {% endif %} - </tr> - </tbody> - {% endfor %} -</table> - - diff --git a/viewer/templates/summary_index.html b/viewer/templates/summary_index.html index 0108488d..d776ddac 100644 --- a/viewer/templates/summary_index.html +++ b/viewer/templates/summary_index.html @@ -14,7 +14,7 @@ {% endif %} </h4> <table class="table table-bordered" style="font-size: 80%; margin-left: 3%; width: 500px;"> - <thead><th>Test</th><th>Result</th></thead> + <thead><th>Test</th><th>Result</th><th>ProdDB Record</th></thead> <tbody> {% for test in component["summary"][stage] %} <tr> @@ -25,11 +25,20 @@ </td> {% elif component["upload_status"][stage] == '0' or component["upload_status"][stage] == '1' %} <td align="center" style="word-wrap:break-word; background-color:rgba(204, 255, 223, 0.75);"> - <a href="{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection'], test='qctest', runId=component['summary'][stage][test]) }}"><i class="fa fa-external-link "></i> {{ component['summary'][stage][test] }}</a> + <a href="{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection'], test='qctest', runId=component['summary'][stage][test]) }}"><i class="fa fa-external-link "></i> Registered {{ component['summary'][stage][test] }}</a> </td> {% else %} <td align="center" style="word-wrap:break-word; background-color:rgba(255, 242, 204, 0.75);"> - <a href="{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection'], test='qctest', runId=component['summary'][stage][test]) }}"><i class="fa fa-external-link "></i></a> + <a href="{{ url_for('component_api.show_component', id=component['_id'], collection=component['collection'], test='qctest', runId=component['summary'][stage][test]) }}"><i class="fa fa-external-link "></i> Registered {{ component['summary'][stage][test] }}</a> + </td> + {% endif %} + {% if component["summary_pdb"][stage][test] == "-1" %} + <td align="center" style="word-wrap:break-word; background-color:rgba(255, 204, 204, 0.75);"> + N/A + </td> + {% else %} + <td align="center" style="word-wrap:break-word; background-color:rgba(204, 255, 223, 0.75);"> + <a href="https://uuappg01-eu-w-1.plus4u.net/ucl-itkpd-maing01/dcb3f6d1f130482581ba1e7bbe34413c/testRunView?id={{ component['summary_pdb'][stage][test] }}"><i class="fa fa-external-link "></i> Registered {{ component['summary_pdb'][stage][test] }}</a> </td> {% endif %} </tr> diff --git a/viewer/templates/upload_flag.html b/viewer/templates/upload_flag.html index ba2e83b4..b64b2b50 100644 --- a/viewer/templates/upload_flag.html +++ b/viewer/templates/upload_flag.html @@ -57,8 +57,8 @@ table.toppage td.border_inner { <form class="form-holizontal" role="form" method="post" action="{{ url_for('component_api.upload_flag', id=table_docs['_id'], stage=table_docs['stage'], test=table_docs['test']) }}" accept-charset="UTF-8"> - <h4><input type="radio" name="flag" value="True" required="required" checked> Did {{ table_docs["test"] }}</h4> - <h4><input type="radio" name="flag" value="False" required="required"> Did not {{ table_docs["test"] }}</h4> + <h4><input type="radio" name="flag" value="True" required="required" checked> Carried out {{ table_docs["test"] }}</h4> + <h4><input type="radio" name="flag" value="False" required="required"> Did not carry out {{ table_docs["test"] }}</h4> <button type="submit" class="btn btn-primary" name="pstage" value="complete">Create flag</button> </form> -- GitLab From 1183d16ba263a553f4dce2b185fc0e0cdda2be71 Mon Sep 17 00:00:00 2001 From: Hideyuki Oide <Hideyuki.Oide@cern.ch> Date: Sun, 24 Jul 2022 17:20:47 +0900 Subject: [PATCH 6/7] fixing sensorIV display --- ...eaed37f7eac6b96452b1f0f99b9d8644c8632a4399 | Bin 1311 -> 0 bytes ...4051ef85d91ebdb09361d2df3bebf2a6c8db0ce648 | Bin 81446 -> 0 bytes ...13d36dcdf65a57d09218794f7055a2ec28aea08d26 | Bin 1140 -> 0 bytes ...df59fac1100a31693bb9472500cd169ceb358af463 | Bin 1344 -> 0 bytes viewer/json-lists/scan_list.json | 34 +++---- viewer/json-lists/supported_test.json | 94 +++++++++--------- viewer/pages/component.py | 2 +- .../displayResults/sensorIVresult.html | 3 +- 8 files changed, 69 insertions(+), 64 deletions(-) delete mode 100644 viewer/itkpd-interface/bin/.webcache/3/2/7/6/0/32760c98b5940eeaed37f7eac6b96452b1f0f99b9d8644c8632a4399 delete mode 100644 viewer/itkpd-interface/bin/.webcache/3/8/b/c/c/38bccf53de1cc64051ef85d91ebdb09361d2df3bebf2a6c8db0ce648 delete mode 100644 viewer/itkpd-interface/bin/.webcache/7/d/6/7/f/7d67f527ce702613d36dcdf65a57d09218794f7055a2ec28aea08d26 delete mode 100644 viewer/itkpd-interface/bin/.webcache/c/c/8/d/1/cc8d164ab3f348df59fac1100a31693bb9472500cd169ceb358af463 diff --git a/viewer/itkpd-interface/bin/.webcache/3/2/7/6/0/32760c98b5940eeaed37f7eac6b96452b1f0f99b9d8644c8632a4399 b/viewer/itkpd-interface/bin/.webcache/3/2/7/6/0/32760c98b5940eeaed37f7eac6b96452b1f0f99b9d8644c8632a4399 deleted file mode 100644 index 4dd75339f5063115f5cbebb0f0aecdc56e61b72a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1311 zcmb7EO^@S55Jeo9KVcamffQ%DJGS#N${~{pGh${nXyRRQGwtqj(yZO>ZFff-Rx7Qz zaNu7+G>b;C65107E=Wim_yhA(xY2fEm=)qcj@7TK-h1^N#-?HKtM4_`m6A1ly~`Be z{`T&l&mVa1=cj`E6VHzcM;UMe7>_bO=0iFf<%Gn3Zx3UtbnPI`9?Wvc!VyG(2|)<a z7)3eFJFu-mH#lLQJY`*xa_%_PP5oS~*4{#bUTwV@V^DymMrrRFYM`2}y(_(<QhZ5` zP}2MGVsY_NH-%ZP9gC%*D`-YNygGe!xk#=)ou70N1GP!ERr`E)`Dl^uG3m#bi_g+o zlFa7k9kkvyeD+SXZZOjlB#4jz4Fen{2v0Dcgrtwi&?!B1w&3#rfHiE~D%869;$W)t z!xrt3dlV|ID$r*87O{5USz#fSv}~2lG%dI=;1PuJJytF9u_|l{sdH7RRORWa=`6)F zWA(myf_o>Ef3>v>GHTd5mA1_GM@&v;e#^R<d|=)#xmK!74U~>8Jq#wpKH6(*5y?M~ zsg<J54u{qJ9$o+3mcr?1<c85(-QCOItsh)>hij+1NS22{QjgoR+<F`8gznjP9`o!m z=-TDTEx>(*AG~<A2FhLSo82ij@JnK$=i#yUi7lL$gm@^Npz#ES-r2+CW(j%&`es&9 zwuYaVMhj+wL{sU$CT)TxxIjAgckh?<S>Q4`9pGpXyPzC<LbAegSlaAVl~l<32d1QP zj05A+x%;A4@=aIa!p*BillQww$U;as_Ht1`bMEPXq&Pj~r{9d)f%_!xe1AB=gpDGX zRzAX<Bh2G?#K(k0z%s~l|3}tH37XAX6>mD;G3t4h78_^D>yDLMzcTCZhB3Zff(hnI zJrQvCxwU;-RehG(>Drj8o*W%n>nZ_<O}{E^9ooJGb7WZ&xW9mk2aWs{!bhBCgFNOq z#{(3ONHoG3#W2h=7=$4nu;YrF_4M`vpbBP%*uYOll%TAcMm^6losOg1xsnoC<J3Le tO>JBT>py;f<*wCS`@I_t)Y<aw)rEU&uJ+Dr4oq=K59)s1ZK&S9{0pY6k@^4t diff --git a/viewer/itkpd-interface/bin/.webcache/3/8/b/c/c/38bccf53de1cc64051ef85d91ebdb09361d2df3bebf2a6c8db0ce648 b/viewer/itkpd-interface/bin/.webcache/3/8/b/c/c/38bccf53de1cc64051ef85d91ebdb09361d2df3bebf2a6c8db0ce648 deleted file mode 100644 index 9754ff2e233f2f32b2a073d455be5ca8914014ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81446 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�o2g<4M59IIG_8J~FEbG26t1 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 diff --git a/viewer/itkpd-interface/bin/.webcache/7/d/6/7/f/7d67f527ce702613d36dcdf65a57d09218794f7055a2ec28aea08d26 b/viewer/itkpd-interface/bin/.webcache/7/d/6/7/f/7d67f527ce702613d36dcdf65a57d09218794f7055a2ec28aea08d26 deleted file mode 100644 index c52cdc17ff030b7f0abf5ea29797ec5fad57427a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1140 zcmb7D%Wm676b-seeqz9E5<sM9_>d?WTDZ26qORefRJ&D&GndjN<dB_*P&EipWRpey zA`zUzXbluzb<s_M0$ub6{5PGUB*b2Z%X`n9d(J(Vj7`(Qn_m^^u9X_zZpxOg|M`Nx zdWhT?%moi7C>X{Zml?}}ki6t$KB9SE#c>u4wlLN<?m0-a1+xmuG=~gG9Ak{>5N8#w z_F$_)d7LmWe`Nb2<=kn|zW$Y1EYYb1wOFGg#-Ij8O{>tipn)p3L>Fp7rTCc|(Mt5> z?DXvEo`qSZoQlHG1$e6Si<9rZIh|cRKmKwLF`&(C-EG@PMX|5zwI9B<-CJj-R}<qn z4)G`?*$m@J5>JveN{DlXcQ1Qz^EqGzD>n-Ft9!lMpfhjlp5%^%PPH8<v%V*+pI0zi z!<;f_m@?nT5yoW8<pKT+?t|u5iawu3&`<V|-Si&aP_5@HT3f%GIEwT96J5|ZKM~yN zxagZ*@Dy>|G;6daxo3};--FUFFUL(HOdh`exCF{w?cL^t8u)8wVSvaOeQj&xSB`Kx z!J|n=(6jT|WdUjh>hh?gYzcoBMhRxZnWEBNtukQ&ZV$q&Zyz`Gr_g0TO-PmuT}FpU zNLE`8g)L9oh6<^FW34n!acJBXY+mSA-s~$}I6f`>UEKyk)<VJ|szeQvHZVPR*D~Mb z6!~`>aCdea1boC3_g^H{uyluBW^q!DleD6Q$0L%I5d6-(?$D1>ZGE#BIm83hDY0_? zulEw%tc6*A7~=6&0VX_d+m`}vf3P;9T^F&^PM5}XdU9}Jt*e9_R#8`5o!Ur(Ik2n_ z-G9~b(0g5Aa=@7v#z{pIoaS+sladk`l_ex;N)mS1QL~(0odI;=Q7u;RhY=05)=aYj za!RM;(baJ)B{091JGe`2sImILe?Pite6)`)6;QX9_ZMfb=OcP>&4INX<~w(PZ&p;T GfB7F7?O@3O diff --git a/viewer/itkpd-interface/bin/.webcache/c/c/8/d/1/cc8d164ab3f348df59fac1100a31693bb9472500cd169ceb358af463 b/viewer/itkpd-interface/bin/.webcache/c/c/8/d/1/cc8d164ab3f348df59fac1100a31693bb9472500cd169ceb358af463 deleted file mode 100644 index a7d747d5c2d95f7d2cf4bece51a9661322c3794d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1344 zcmYk6eKb^g7{{lyQd+g6lBh;;RNQ%=44M*QN^MP8yQJK?^J6aK&h5^<lfl{Elv-Nv zTggc|GObKeB+60rq9#^EGUc42nmRVUAz|#;)1I^c{+{1+zMt>+InRS2BEI|na|BS~ zN=yY(i=|4L=C*~;k~AxtvZGx}vv?Y6zzo7U<MZ})r7}C!8#Z2HDt~-Y63{Oc&#g2T zS;=?x$=xJVH&DHnrr|~5YRFC#8C5ZL*5Q2pi4&?j$u38`Zanp^TAuE5ukd1Vrn8eR zbH#g~8w(b^&fUg=?M+R47r42fPP25XzN?BA7j<7+?YQK7W6d;ReND8Go3<t;xFwbE zcxl(hap(88UiKAT6KyWtQ7^)>JKCnWhlORN{Zd<d?Yhr-dfsS)6F(soGSF8?B*{Oe zaJK4#!h&=z^6c^{D?(svN%pO`^DUiOx!!$6{BnoPp|Uj%2cD;lK5VVhMo@;I;*K3k z4_G$VCZuT|JTfLm`U0DKmh08MMvnm7Lf2o}AML55^X@aY#-)YV3=LT<4l4AA-NE9( z8VB6*tK@jb!|_&AbN{PHLwBX0`kUt(n>$ZN+vlD>sH@sN+cKr)R9#+=Rp{6pn_Ee# z74^3L?fWa<T-j%<uw32ZxA<~$!E~EdGaD3(3Zss-r>5GzqbCjj!Ps>EOoJxt8HO{n zzHDE*&cqVS(&<M`JL&KYWW$ZX+|&ud5%Hm`60zo0V9kNEzE)3ing(C&8hUip^rlfd zG=k=rwCgjp+b-zmU$40z7fH$WB8NT6%b(vq8SZu|W^P;9vGUm1mt@}O5P!e<gCPf3 zetPWs`FKX7__x;CQA<0yO^sbKp4NVQ+=a3eIWy$WL#}}ZYkDI0?5I`bj>zrKey1z1 z9B7pk*tJw>TdHp*y<my$x5$sDj~w1?wEI|6{Fn2A>@U__L(aQr==R;Wt#j{W!QF!n zrykc|O?2?dmy})WnU>*rDn3A&H9V_-#@gMRmb7<_C)Xx?6)cUHh=g|<SN?r7T=&D< z=grU<zrHZC47>QMx-N0;yoH@r*$2A|)EUF-fpVu9eYFEwO`q9xh8&>p5c_)T{XSUa z@QQ5>f9lfuFknF2A5>l#+{PdMn)woj>jlKmM||s8AAS##3=HeIb_RW~v8T5pK0iDn z)1zF#tUO5{iI8`6?AXT{y>c&4_xip*7I3s55^wf+q`x!Gt~_VsU*NiS`{lmshgrsp z-pQsZG}^?31+5hG9XmXoM$^gxECYlpt$005g1TS|b7yco7#k@CgUjY}8Ej84F3(FK zU<7UsDG3II8W1Jxa2Sz;nqZPZ5fTa^V9dOf1PKKLgd$K<^JG~(oB+WwK*Zw+c&pfK zwl@RC5CtUz!IX5pG9E@TRT-khNPv+Lslh=#jN=Lvfk{+}v0_z9%$tG8VL}B+QD{g2 zB-A03iqK>Wp;Tyb0#%!7wIBgU%@?6&k`!WlKpc;dNnU{$kK@K>d$HN&|A_z$QOZy( zrZ^@M#Y=+$3Hd3NaVU5aMNv$%I1`alA~{LoDlZm`qRcOl45*p7f>QA*CI(0rg(x7D zjKgKnBz_E=!;&FVUi2y%Cz``!^Mzc2kRydTz(XnpJU*YpL%eaAl#5D(0KlPj3RDd$ zNHiWODN-bGXP6Tbd3u)mDKQKnleYi=@83wH0Rj1c!`32l3X21>5)}!Pl<Gl~);tH5 kxy!nQ0I)f)qAfw@+T=WF7i7Tf73}<Gy`fkQ6PmsM0C1ILtN;K2 diff --git a/viewer/json-lists/scan_list.json b/viewer/json-lists/scan_list.json index d577ef99..87804d09 100644 --- a/viewer/json-lists/scan_list.json +++ b/viewer/json-lists/scan_list.json @@ -6,22 +6,22 @@ }, "READOUT_IN_BASIC_ELECTRICAL_TEST":{ "setting_temp":null, - "scantypes":["std_digitalscan","std_analogscan","std_thresholdscan","std_totscan","std_discbumpscan.json"], + "scantypes":["std_digitalscan","std_analogscan","std_thresholdscan","std_totscan","std_discbumpscan"], "analysis": { "run":false, "name":"" } }, - "READOUT_IN_BASIC_ELECTRICAL_TEST_30_DEGREE":{ + "READOUT_IN_BASIC_ELECTRICAL_TEST_30DEG":{ "setting_temp":30, - "scantypes":["std_digitalscan","std_analogscan","std_thresholdscan","std_totscan","std_discbumpscan.json"], + "scantypes":["std_digitalscan","std_analogscan","std_thresholdscan","std_totscan","std_discbumpscan"], "analysis": { "run":false, "name":"" } }, - "READOUT_IN_BASIC_ELECTRICAL_TEST_20_DEGREE":{ + "READOUT_IN_BASIC_ELECTRICAL_TEST_20DEG":{ "setting_temp":20, - "scantypes":["std_digitalscan","std_analogscan","std_thresholdscan","std_totscan","std_discbumpscan.json"], + "scantypes":["std_digitalscan","std_analogscan","std_thresholdscan","std_totscan","std_discbumpscan"], "analysis": { "run":false, "name":"" } }, - "READOUT_IN_BASIC_ELECTRICAL_TEST_min15_DEGREE":{ + "READOUT_IN_BASIC_ELECTRICAL_TEST_MINUS15DEG":{ "setting_temp":-15, - "scantypes":["std_digitalscan","std_analogscan","std_thresholdscan","std_totscan","std_discbumpscan.json"], + "scantypes":["std_digitalscan","std_analogscan","std_thresholdscan","std_totscan","std_discbumpscan"], "analysis": { "run":false, "name":"" } }, "PIXEL_FAILURE_TEST":{ @@ -29,17 +29,17 @@ "scantypes":["std_digitalscan","std_analogscan","std_thresholdscan","std_totscan","std_noisescan","std_crosstalkscan"], "analysis": { "run":true, "name":"bad_pixel_analysis" } }, - "PIXEL_FAILURE_TEST_30_DEGREE":{ + "PIXEL_FAILURE_TEST_30DEG":{ "setting_temp":30, "scantypes":["std_digitalscan", "syn_analogscan", "lindiff_analogscan", "syn_thresholdscan", "lin_thresholdscan", "diff_thresholdscan", "syn_totscan", "lin_totscan", "diff_totscan", "syn_discbumpscan", "lin_discbumpscan", "diff_discbumpscan" ], "analysis": { "run":true, "name":"bad_pixel_analysis" } }, - "PIXEL_FAILURE_TEST_20_DEGREE":{ + "PIXEL_FAILURE_TEST_20DEG":{ "setting_temp":20, "scantypes":["std_digitalscan", "syn_analogscan", "lindiff_analogscan", "syn_thresholdscan", "lin_thresholdscan", "diff_thresholdscan", "syn_totscan", "lin_totscan", "diff_totscan", "syn_discbumpscan", "lin_discbumpscan", "diff_discbumpscan" ], "analysis": { "run":true, "name":"bad_pixel_analysis" } }, - "PIXEL_FAILURE_TEST_min15_DEGREE":{ + "PIXEL_FAILURE_TEST_MINUS15DEG":{ "setting_temp":-15, "scantypes":["std_digitalscan", "syn_analogscan", "lindiff_analogscan", "syn_thresholdscan", "lin_thresholdscan", "diff_thresholdscan", "syn_totscan", "lin_totscan", "diff_totscan", "syn_discbumpscan", "lin_discbumpscan", "diff_discbumpscan" ], "analysis": { "run":true, "name":"bad_pixel_analysis" } @@ -51,22 +51,22 @@ }, "BUMP_BOND_QUALITY":{ "setting_temp":null, - "scantypes":["std_noisescan.json","std_discbumpscan.json"], + "scantypes":["std_noisescan.json","std_discbumpscan"], "analysis": { "run":false, "name":"" } }, - "BUMP_BOND_QUALITY_30_DEGREE":{ + "BUMP_BOND_QUALITY_30DEG":{ "setting_temp":30, - "scantypes":["std_noisescan.json","std_discbumpscan.json"], + "scantypes":["std_noisescan.json","std_discbumpscan"], "analysis": { "run":false, "name":"" } }, - "BUMP_BOND_QUALITY_20_DEGREE":{ + "BUMP_BOND_QUALITY_20DEG":{ "setting_temp":20, - "scantypes":["std_noisescan.json","std_discbumpscan.json"], + "scantypes":["std_noisescan.json","std_discbumpscan"], "analysis": { "run":false, "name":"" } }, - "BUMP_BOND_QUALITY_min15_DEGREE":{ + "BUMP_BOND_QUALITY_MINUS15DEG":{ "setting_temp":-15, - "scantypes":["std_noisescan.json","std_discbumpscan.json"], + "scantypes":["std_noisescan.json","std_discbumpscan"], "analysis": { "run":false, "name":"" } } } diff --git a/viewer/json-lists/supported_test.json b/viewer/json-lists/supported_test.json index 621ea515..c5bc13cd 100644 --- a/viewer/json-lists/supported_test.json +++ b/viewer/json-lists/supported_test.json @@ -23,64 +23,68 @@ "OPTICAL", "PARYLENE", "PIXEL_FAILURE_TEST", - "PIXEL_FAILURE_TEST_20_DEGREE", - "PIXEL_FAILURE_TEST_30_DEGREE", - "PIXEL_FAILURE_TEST_min15_DEGREE", + "PIXEL_FAILURE_TEST_20DEG", + "PIXEL_FAILURE_TEST_30DEG", + "PIXEL_FAILURE_TEST_MINUS15DEG", "RD53A_PULL-UP_RESISTOR", - "READOUT_IN_BASIC_ELECTRICAL_TEST", - "READOUT_IN_BASIC_ELECTRICAL_TEST_30_DEGREE", - "READOUT_IN_BASIC_ELECTRICAL_TEST_20_DEGREE", - "READOUT_IN_BASIC_ELECTRICAL_TEST_min15_DEGREE", + "READOUT_IN_BASIC_ELECTRICAL_TEST_30DEG", + "READOUT_IN_BASIC_ELECTRICAL_TEST_20DEG", + "READOUT_IN_BASIC_ELECTRICAL_TEST_MINUS15DEG", "REGISTER_TEST", "SENSOR_IV", - "SENSOR_IV_20_DEGREE", - "SENSOR_IV_30_DEGREE", - "SENSOR_IV_min15_DEGREE", + "SENSOR_IV_20DEG", + "SENSOR_IV_30DEG", + "SENSOR_IV_MINUS15DEG", "SLDO_VI", - "XRAY_SCAN_20_DEGREE", - "XRAY_SCAN_30_DEGREE", - "XRAY_SCAN_min15_DEGREE", + "XRAY_SCAN_20DEG", + "XRAY_SCAN_30DEG", + "XRAY_SCAN_min15DEG", "THERMAL_CYCLING", "WIREBOND", "WIREBONDING", "ADC_CALIBRATION", "TUNING", + "TUNING_30DEG", + "TUNING_20DEG", + "TUNING_MINUS15DEG", "PARYLENE", "WP_ENVELOPE" ], "html":{ - "OPTICAL": "viresult.html", - "MASS": "massresult.html", - "METROLOGY": "metresult.html", - "BACKSIDE_COPLANARITY": "coplanarity.html", - "FLATNESS": "coplanarity.html", - "GLUE_MODULE_FLEX_ATTACH":"glueattachresult.html", - "WIREBONDING":"wirebondingresult.html", - "WIREBOND":"pulltest.html", - "SENSOR_IV": "sensorIVresult.html", - "SENSOR_IV_min15_DEGREE": "sensorIVresult.html", - "SENSOR_IV_20_DEGREE": "sensorIVresult.html", - "SENSOR_IV_30_DEGREE": "sensorIVresult.html", - "SLDO_VI": "SLDOVIresult.html", - "XRAY_SCAN_min15_DEGREE": "xrayresult.html", - "XRAY_SCAN_20_DEGREE": "xrayresult.html", - "XRAY_SCAN_30_DEGREE": "xrayresult.html", - "RD53A_PULL-UP_RESISTOR": "pullupresult.html", - "IREFTRIM_FE": "irefresult.html", - "READOUT_IN_BASIC_ELECTRICAL_TEST": "electrical.html", - "READOUT_IN_BASIC_ELECTRICAL_TEST_30_DEGREE": "electrical.html", - "READOUT_IN_BASIC_ELECTRICAL_TEST_20_DEGREE": "electrical.html", - "READOUT_IN_BASIC_ELECTRICAL_TEST_min15_DEGREE": "electrical.html", - "PIXEL_FAILURE_TEST": "electrical.html", - "PIXEL_FAILURE_TEST_30_DEGREE": "electrical.html", - "PIXEL_FAILURE_TEST_20_DEGREE": "electrical.html", - "PIXEL_FAILURE_TEST_min15_DEGREE": "electrical.html", - "ELECTRICAL_TEST": "electrical.html", - "WP_ENVELOPE": "wpenvelope_result.html", - "PARYLENE": "paryleneresult.html", - "THERMAL_CYCLING": "thermalresult.html", - "ADC_CALIBRATION": "adc_calibration.html", - "TUNING": "tuning.html" + "OPTICAL" : "displayResults/viresult.html", + "MASS" : "displayResults/massresult.html", + "METROLOGY" : "displayResults/metresult.html", + "BACKSIDE_COPLANARITY" : "displayResults/coplanarity.html", + "FLATNESS" : "displayResults/coplanarity.html", + "GLUE_MODULE_FLEX_ATTACH" : "displayResults/glueattachresult.html", + "WIREBONDING" : "displayResults/wirebondingresult.html", + "WIREBOND" : "displayResults/pulltest.html", + "SENSOR_IV" : "displayResults/sensorIVresult.html", + "SENSOR_IV_MINUS15DEG" : "displayResults/sensorIVresult.html", + "SENSOR_IV_20DEG" : "displayResults/sensorIVresult.html", + "SENSOR_IV_30DEG" : "displayResults/sensorIVresult.html", + "SLDO_VI" : "displayResults/SLDOVIresult.html", + "XRAY_SCAN_MINUS15DEG" : "displayResults/xrayresult.html", + "XRAY_SCAN_20DEG" : "displayResults/xrayresult.html", + "XRAY_SCAN_30DEG" : "displayResults/xrayresult.html", + "RD53A_PULL-UP_RESISTOR" : "displayResults/pullupresult.html", + "IREFTRIM_FE" : "displayResults/irefresult.html", + "READOUT_IN_BASIC_ELECTRICAL_TEST" : "displayResults/electrical.html", + "READOUT_IN_BASIC_ELECTRICAL_TEST_30DEG" : "displayResults/electrical.html", + "READOUT_IN_BASIC_ELECTRICAL_TEST_20DEG" : "displayResults/electrical.html", + "READOUT_IN_BASIC_ELECTRICAL_TEST_MINUS15DEG" : "displayResults/electrical.html", + "PIXEL_FAILURE_TEST" : "displayResults/electrical.html", + "PIXEL_FAILURE_TEST_30DEG" : "displayResults/electrical.html", + "PIXEL_FAILURE_TEST_20DEG" : "displayResults/electrical.html", + "PIXEL_FAILURE_TEST_MINUS15DEG" : "displayResults/electrical.html", + "ELECTRICAL_TEST" : "displayResults/electrical.html", + "WP_ENVELOPE" : "displayResults/wpenvelope_result.html", + "PARYLENE" : "displayResults/paryleneresult.html", + "THERMAL_CYCLING" : "displayResults/thermalresult.html", + "ADC_CALIBRATION" : "displayResults/adc_calibration.html", + "TUNING_30DEG" : "displayResults/tuning.html", + "TUNING_20DEG" : "displayResults/tuning.html", + "TUNING_MINUS15DEG" : "displayResults/tuning.html" }, "description":[ "Visual Inspection (OPTICAL)", diff --git a/viewer/pages/component.py b/viewer/pages/component.py index 7c27a009..18a36cfc 100755 --- a/viewer/pages/component.py +++ b/viewer/pages/component.py @@ -1269,7 +1269,7 @@ def setQCResults(i_oid, i_col, i_tr_oid): analysis = {} results.update({"results":this_tr["results"]["scans"], "plots": plots, "analysis_result": analysis }) - elif this_tr["testType"] == "SENSOR_IV" or this_tr["testType"] == "SENSOR_IV_min15_DEGREE" or this_tr["testType"] == "SENSOR_IV_20_DEGREE" or this_tr["testType"] == "SENSOR_IV_30_DEGREE": + elif "SENSOR_IV" in this_tr["testType"]: results.update({ "results": this_tr["results"], "graph": SensorIV_graph(this_tr), diff --git a/viewer/templates/displayResults/sensorIVresult.html b/viewer/templates/displayResults/sensorIVresult.html index 29314cb8..fc3d1315 100644 --- a/viewer/templates/displayResults/sensorIVresult.html +++ b/viewer/templates/displayResults/sensorIVresult.html @@ -102,7 +102,8 @@ border: none; {% if keys!="unit" and keys!="Sensor_IV" %} <tr> <th scope='raw' class='text-left'> {{ keys }} </td> - <td class='text-left'>{{ component['qctest']['results']['results'][keys] }} {{component['qctest']['results']['unit_list'][keys]}} </td> + <td class='text-left'>{{ component['qctest']['results']['results'][keys] }}</td> + </tr> {% endif %} {% endfor %} -- GitLab From 90a6de9ea841e5b2047839d69cce93642ce0070a Mon Sep 17 00:00:00 2001 From: Hideyuki Oide <Hideyuki.Oide@cern.ch> Date: Sun, 24 Jul 2022 17:50:59 +0900 Subject: [PATCH 7/7] reorganizing template html --- viewer/templates/#select_table.html# | 166 ------------------ viewer/templates/component.html | 14 +- .../{ => displayComponent}/comment.html | 0 .../{ => displayComponent}/flow_image.html | 0 .../latest_test_index.html | 0 .../moduleprop_index.html | 0 .../{ => displayComponent}/qctest_index.html | 0 .../{ => displayComponent}/summary_index.html | 0 .../{ => displayComponent}/test_index.html | 0 .../{ => displayResults}/orienresult.html | 0 .../templates/show_analysis_result.html.tmp | 41 ----- 11 files changed, 7 insertions(+), 214 deletions(-) delete mode 100644 viewer/templates/#select_table.html# rename viewer/templates/{ => displayComponent}/comment.html (100%) rename viewer/templates/{ => displayComponent}/flow_image.html (100%) rename viewer/templates/{ => displayComponent}/latest_test_index.html (100%) rename viewer/templates/{ => displayComponent}/moduleprop_index.html (100%) rename viewer/templates/{ => displayComponent}/qctest_index.html (100%) rename viewer/templates/{ => displayComponent}/summary_index.html (100%) rename viewer/templates/{ => displayComponent}/test_index.html (100%) rename viewer/templates/{ => displayResults}/orienresult.html (100%) delete mode 100644 viewer/templates/show_analysis_result.html.tmp diff --git a/viewer/templates/#select_table.html# b/viewer/templates/#select_table.html# deleted file mode 100644 index 72ca65ca..00000000 --- a/viewer/templates/#select_table.html# +++ /dev/null @@ -1,166 +0,0 @@ -<div class="container"> - <div class="row align-items-center justify-content-center"> - <div class="col"> - - {% if not table_docs["text"] == '' %} - <p><div class="col-md-2"></div><div class="col-md-10"><font color=#ff0000>{{ table_docs["text"] }}</font></div></p> - {% endif %} - - {% if table_docs['total']!=0 %} - <h5>Please select one result for each test items.</h5> - <form class="form-holizontal" role="form" method="post" action="{{ url_for('component_api.analyze_scan', id=table_docs['_id'], stage=table_docs['stage'], test=table_docs['test']) }}" accept-charset="UTF-8"> - {% for scantype in table_docs["scans"]["scantypes"]%} - {% set outer_loop = loop %} - <h3><i class="fa fa-caret-right"></i>{{ scantype }}</h3> - {% if table_docs["pstage"] == "input" %} - <table class="table table-sm toppage" style="font-size: 10pt;"> - <thead class="table-light"> - <tr> - <th scope="col" rowspan=2>run Number</th> - <th scope="col" colspan=6>Test Data</th> - <th scope="col" colspan=1 rowspan=2>Tag</th> - <th scope="col" colspan=1 rowspan=2>Select</th> - </tr> - <tr> - <th scope="col">Stage</th> - <th scope="col">Test Type</th> - <th scope="col">User</th> - <th scope="col">Site</th> - <th scope="col">Date</th> - <th scope="col">Link</th> - </tr> - </thead> - <tbody> - - {% for run_data in table_docs["run"] %} - {% if run_data['run_data']["testType"] == table_docs["scans"]["scantypes"][outer_loop.index0] %} - {% if run_data['run_data']['plots'] %} - <tr> - {% else %} - <tr style="background: #AABBCC;"> - {% endif %} - <td style="word-wrap:break-word;">{{ run_data['run_data']["runNumber"] }}</td> - <td style="word-wrap:break-word;">{{ run_data['run_data']["stage"] }}</td> - <td style="word-wrap:break-word;">{{ run_data['run_data']["testType"] }}</td> - <td style="word-wrap:break-word;">{{ run_data['run_data']["user"] }}</td> - <td style="word-wrap:break-word;">{{ run_data['run_data']["site"] }}</td> - <td style="word-wrap:break-word;">{{ run_data['run_data']["datetime"] }}</td> - <td style="word-wrap:break-word;"> - <a href="{{ url_for('component_api.show_component', runId=run_data['run_data']["_id"], collection='component', test='electrical', id='' ) }}">result page</a> - </td> - <td style="word-wrap:break-word;"> - {% for tag in run_data['run_data']['testRun_tag'] %} - <div>{{ tag['name'] }}</div> - {% endfor %} - </td> - <td style="word-wrap:break-word;"> - <div class="form-group row"> - <div class="col-md-10"> - <input type="radio" name="{{ scantype }}_id" value="{{ run_data['run_data']['_id'] }}" required="required"> - </div> - </div> - </td> - </tr> - {% endif %} - {% endfor %} - </tbody> - </table> - - {% elif table_docs["pstage"] == "confirm" or table_docs["pstage"] == "edit" %} - <input type="hidden" value="{{ table_docs["run"][loop.index0]["_id"] }}" name="selectinfo"> - <table class="table table-sm toppage" style="font-size: 10pt;"> - <thead class="table-light"> - <tr> - <th scope="col">Test Type</th> - <th scope="col">run Number</th> - <th scope="col">Test Date</th> - <th scope="col">Setting Temperature around Module Ntc</th> - </tr> - </thead> - <tbody> - <tr> - <td style="word-wrap:break-word;">{{ table_docs["run"][loop.index0]["testType"] }}</td> - <td style="word-wrap:break-word;">{{ table_docs["run"][loop.index0]["rn"] }}</td> - <td style="word-wrap:break-word;">{{ table_docs["run"][loop.index0]["time"] }}</td> - <td style="word-wrap:break-word;">{{ table_docs["run"][loop.index0]["temp"] }}</td> - </tr> - </tbody> - </table> - - <div class="row align-items-center justify-content-flex-start"> - <div class="col"> - {% for result in table_docs["plots"][loop.index0]["results"] %} - <h4 style="font-size: 10pt;"> - <i class="fa fa-caret-right"></i> {{ result["mapType"] }} - <a href="/localdb/plotData?testRunId={{ result['runId'] }}&mapType={{ result['mapType'] }}">plotly</a> - </h4> - <table class="table table-sm table-bordered" style="font-size: 8pt; table-layout: fixed;"> - <thead class="table-light"> - <tr> - {% for chip, data in result["chips"].items() %} - {% if not data["num"]==0 %} - <th ~~~ scope="col" class="text-left" style="word-wrap:break-word;" width={{ data["length"] }} colspan={{ data["num"] }}>{{ chip }}</th> - {% endif %} - {% endfor %} - </tr> - </thead> - <tbody> - <tr> - {% for chip, data in result["chips"].items() %} - {% for plot in data["plots"] %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href={{ plot["url"] }} target="_brank" rel="noopener noreferrer"> - <img src={{ plot["url"] }} title={{ plot["name"] }} width={{ data["width"] }} height={{ data["width"] }}></img> - </a> - </td> - {% endfor %} - {% endfor %} - </tr> - </tbody> - </table> - {% endfor %} - </div> - </div> - - {% endif %} - {% endfor %} - - <br> - <br> - {% if table_docs["scans"]["analysis"]["run"] %} - {% if table_docs["pstage"] == "confirm" %} - <h3><i class="fa fa-caret-right"></i>Bad Pixel Analysis</h3> - {% endif %} - {% for chip_result in table_docs["scans"]["analysis_result"] %} - <h3>Chip: {{ chip_result['chipname'] }}</h3> - {% if table_docs["pstage"] == "confirm" and chip_result["tool"] == True %} - - <h4 style="font-size: 12pt;"> - {% if not chip_result["tool"] %} - <font color=#ff0000>### NO SOFTWARE ###</font> - {% endif %} - </h4> - {% include "show_analysis_result.html" %} - {% else %} - <font color=#ff0000> {{ table_docs }} </font> - {% endif %} - {% endfor %} - {% endif %} - - <div class="col-md-12"> - {% if table_docs["pstage"] == "input" %} - <button type="submit" class="btn btn-primary" name="pstage" value="confirm">Proceed</button><br />* After pushing the button the processing takes a few tens seconds; please wait. - {% elif table_docs["pstage"] == "confirm" %} - <button type="submit" class="btn btn-primary" name="pstage" value="scancomplete">Register</button> - <button type="submit" class="btn btn-primary" name="pstage" value="input">Back page</button> - {% elif table_docs["pstage"] == "edit" %} - <h3>Do you delete this commit ?</h3> - <button type="submit" class="btn btn-primary" name="pstage" value="delete">Delete</button> - {% endif %} - </div> - </form> - - {% endif %} - </div> - </div> -</div> diff --git a/viewer/templates/component.html b/viewer/templates/component.html index 11d70b1e..e9378f38 100644 --- a/viewer/templates/component.html +++ b/viewer/templates/component.html @@ -205,7 +205,7 @@ th { background-color: #eeeeff; } </div> <br> <div class="row align-items-top justify-content-left"> - {% include "comment.html" %} + {% include "displayComponent/comment.html" %} </div> @@ -276,13 +276,13 @@ th { background-color: #eeeeff; } {% if component['latest_tests'] %} <div style="margin-bottom: 50px;"></div> - {% include "latest_test_index.html" %} + {% include "displayComponent/latest_test_index.html" %} <div style="margin-bottom: 50px;"></div> {% endif %} {% if component['summary'] %} - {% include "summary_index.html" %} + {% include "displayComponent/summary_index.html" %} {% endif %} <div id="accordion"> @@ -290,20 +290,20 @@ th { background-color: #eeeeff; } <div style="margin-bottom: 50px;"></div> {% if component['property']['propIndex'] %} - {% include "moduleprop_index.html" %} + {% include "displayComponent/moduleprop_index.html" %} {% endif %} {% if component['qctest']['resultIndex'] %} - {% include "qctest_index.html" %} + {% include "displayComponent/qctest_index.html" %} {% endif %} {% if component['electrical']['resultIndex'] %} - {% include "test_index.html" %} + {% include "displayComponent/test_index.html" %} {% endif %} - {% include "flow_image.html" %} + {% include "displayComponent/flow_image.html" %} <!--div class="card"> <div class="card-header" id="heading-supportedTests"> diff --git a/viewer/templates/comment.html b/viewer/templates/displayComponent/comment.html similarity index 100% rename from viewer/templates/comment.html rename to viewer/templates/displayComponent/comment.html diff --git a/viewer/templates/flow_image.html b/viewer/templates/displayComponent/flow_image.html similarity index 100% rename from viewer/templates/flow_image.html rename to viewer/templates/displayComponent/flow_image.html diff --git a/viewer/templates/latest_test_index.html b/viewer/templates/displayComponent/latest_test_index.html similarity index 100% rename from viewer/templates/latest_test_index.html rename to viewer/templates/displayComponent/latest_test_index.html diff --git a/viewer/templates/moduleprop_index.html b/viewer/templates/displayComponent/moduleprop_index.html similarity index 100% rename from viewer/templates/moduleprop_index.html rename to viewer/templates/displayComponent/moduleprop_index.html diff --git a/viewer/templates/qctest_index.html b/viewer/templates/displayComponent/qctest_index.html similarity index 100% rename from viewer/templates/qctest_index.html rename to viewer/templates/displayComponent/qctest_index.html diff --git a/viewer/templates/summary_index.html b/viewer/templates/displayComponent/summary_index.html similarity index 100% rename from viewer/templates/summary_index.html rename to viewer/templates/displayComponent/summary_index.html diff --git a/viewer/templates/test_index.html b/viewer/templates/displayComponent/test_index.html similarity index 100% rename from viewer/templates/test_index.html rename to viewer/templates/displayComponent/test_index.html diff --git a/viewer/templates/orienresult.html b/viewer/templates/displayResults/orienresult.html similarity index 100% rename from viewer/templates/orienresult.html rename to viewer/templates/displayResults/orienresult.html diff --git a/viewer/templates/show_analysis_result.html.tmp b/viewer/templates/show_analysis_result.html.tmp deleted file mode 100644 index 1c0de6fc..00000000 --- a/viewer/templates/show_analysis_result.html.tmp +++ /dev/null @@ -1,41 +0,0 @@ -<br> -<h4 style='font-size: 10pt;'> - <i class="fa fa-file"></i> Plot -</h4> -<table class="table table-sm table-bordered" style="font-size: 10pt;"> - {% for step in chip_result['plot_info'] %} - <p>{{ step }}</p> - <thead class="table-light"> - <tr> - <th ~~~ scope="col" class="text-left" style="word-wrap:break-word;" >{{ step["analysis"] }}</th> - </tr> - </thead> - <tbody> - <tr> - {% if 'map' in step['img'] %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href="{{ step['img']['map'] }}" target="_brank" rel="noopener noreferrer"> - <img src="{{ step['img']['map'] }}" title="{{ step["analysis"] }}" width=250 height=250 ></img> - </a> - </td> - {% endif %} - {% if 'proj' in step['img'] %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href="{{ step['img']['proj'] }}" target="_brank" rel="noopener noreferrer"> - <img src="{{ step['img']['proj'] }}" title="{{ step["analysis"] }}" width=250 height=250 ></img> - </a> - </td> - {% endif %} - {% if 'badpixels' in step['img'] %} - <td scope="col" class="text-left" style="word-wrap:break-word;"> - <a href="{{ step['img']['badpixels'] }}" target="_brank" rel="noopener noreferrer"> - <img src="{{ step['img']['badpixels'] }}" title="{{ step["analysis"] }}" width=250 height=250 ></img> - </a> - </td> - {% endif %} - </tr> - </tbody> - {% endfor %} -</table> - - -- GitLab