From fa8c3a4b8b1c2c567d1ec12bcf8c24ae908da254 Mon Sep 17 00:00:00 2001 From: jaylin <chieh.lin@rwth-aachen.de> Date: Sun, 10 Mar 2024 19:24:33 +0100 Subject: [PATCH] Extract Event and Measurement table --- my_flask_app/__pycache__/main.cpython-39.pyc | Bin 15047 -> 25204 bytes my_flask_app/main.py | 969 ++++++++---------- .../models/__pycache__/models.cpython-39.pyc | Bin 9605 -> 9641 bytes my_flask_app/models/models.py | 6 +- my_flask_app/templates/app.html | 599 +++++++++-- 5 files changed, 919 insertions(+), 655 deletions(-) diff --git a/my_flask_app/__pycache__/main.cpython-39.pyc b/my_flask_app/__pycache__/main.cpython-39.pyc index 197f3a0c6f0b12d1beee27123df9ba4302797d1b..870e9cbe212add277a10cf6075c2b75ab29566f5 100644 GIT binary patch literal 25204 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 D*D8vZ delta 6180 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 diff --git a/my_flask_app/main.py b/my_flask_app/main.py index 5ad81ab..3a3f7ba 100644 --- a/my_flask_app/main.py +++ b/my_flask_app/main.py @@ -1,14 +1,16 @@ +from datetime import datetime import pandas as pd from my_flask_app import app from .models.models import CustomTable, CustomColumn, Theme, CompressedDataType, Observation_Spec, RegType, RegRole from flask_sqlalchemy import SQLAlchemy from flask import jsonify, redirect, render_template, request, session, url_for, json -from sqlalchemy import ARRAY, BIGINT, BOOLEAN, DOUBLE_PRECISION, FLOAT, INTEGER, JSON, NUMERIC, SMALLINT, TIMESTAMP, UUID, VARCHAR, MetaData, String, create_engine, text, inspect +from sqlalchemy import ARRAY, BIGINT, BOOLEAN, DOUBLE_PRECISION, FLOAT, INT, INTEGER, JSON, NUMERIC, SMALLINT, TIMESTAMP, UUID, VARCHAR, MetaData, String, create_engine, text, inspect import pydot, base64, os, logging from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.dialects.postgresql.base import ischema_names from sqlalchemy.dialects.postgresql import JSONB, TSTZRANGE, INTERVAL, BYTEA, JSON, UUID, DOUBLE_PRECISION, BYTEA, ARRAY, REAL, TSTZRANGE, UUID, BYTEA, JSONB, JSON, ARRAY, FLOAT, INTEGER, TIMESTAMP, TEXT, BOOLEAN, VARCHAR, NUMERIC, REAL from sqlalchemy.dialects.sqlite import JSON, FLOAT, INTEGER, TIMESTAMP, TEXT, BOOLEAN, VARCHAR, NUMERIC, REAL +from bs4 import BeautifulSoup # Set up database (call db.engine) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False @@ -49,7 +51,9 @@ def index(): session['self_defined_labels'] = [] session['schema_selected'] = '' session['show_all'] = False + session['object_name'] = {} session['data_header']={'event':[], 'measurement':[], 'segment':[], 'segmentData':[]} + session['current_data_header'] = {'tablename': '', 'type': '', 'label': [], 'features_name': []} print("4") # Initialize inspector here, outside the inner if-else print("4.5") @@ -98,6 +102,9 @@ def index(): image2 = generate_erd(graph_DOT2) print("222") + + extract_ME_table(engine, 'event_data', 'E', 'time', ['col', 'machine_id'], [' col ', ' name ', ' Z_ACTUAL_ZERO_POINT '], ['value(fine, coarse)'], datetime(2022, 11, 1, 17, 5), datetime(2022, 11, 3, 0, 0)) + return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, image1=image1, image2=image2, dropped_items=dropped_items, self_labels=self_labels) else: @@ -108,8 +115,6 @@ def index(): return f"An error occurred: {str(e)}", 400 - - @app.route('/handle-drop', methods=['POST']) def handle_drop(): data = request.json @@ -222,32 +227,189 @@ def add_label(): @app.route('/add-data-header', methods=['POST']) def add_data_header(): data_header = session.get('data_header', {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}) + data = request.json type = data.get('type') if data.get('type') != None else '' - label = data.get('label') if data.get('label') != None else '' + label_list = eval(data.get('label'))if data.get('label') != None else ['','',''] object_column = data.get('object_column') if data.get('object_column') != None else '' value_list = data.get('value_list') if data.get('value_list') != None else [] + current_table = session.get('target_table_name', '') + if type == 'event': - observation = Observation_Spec(type, label, value_list) - data_header['event'].append(observation.to_dict()) + observation = Observation_Spec(current_table, 'E', label_list, value_list) + if observation.to_dict() not in data_header['event']: + data_header['event'].append(observation.to_dict()) elif type == 'measurement': - observation = Observation_Spec(type, label, value_list) - data_header['measurement'].append(observation.to_dict()) + observation = Observation_Spec(current_table, 'M', label_list, value_list) + if observation.to_dict() not in data_header['measurement']: + data_header['measurement'].append(observation.to_dict()) elif type == 'segment': - observation = Observation_Spec(type, label, value_list) - data_header['segment'].append(observation.to_dict()) + observation = Observation_Spec(current_table, 'S', label_list, []) + if observation.to_dict() not in data_header['segment']: + data_header['segment'].append(observation.to_dict()) elif type == 'segmentData': - observation = Observation_Spec(type, label, value_list) - data_header['segmentData'].append(observation.to_dict()) - + observation = Observation_Spec(current_table, 'SD', label_list, value_list) + if observation.to_dict() not in data_header['segmentData']: + data_header['segmentData'].append(observation.to_dict()) + elif type == 'object': + obj = session.get('object_name', {}) + obj[current_table] = object_column + session['object_name'] = obj + return jsonify() + + print(observation.to_dict()) print("88888") print(data_header) session['data_header'] = data_header - return jsonify({'data_header': data_header}) + data_header_table = generate_html_header_table() + + return jsonify({'data_header': data_header, 'data_header_table': data_header_table}) + + +@app.route('/reset-data-header-table', methods=['POST']) +def reset_data_header_table(): + session['data_header'] = {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]} + data_header_table = generate_html_header_table() + + return jsonify({'data_header_table': data_header_table}) + + +@app.route('/init-data-header-table', methods=['POST']) +def init_data_header_table(): + data_header_table = generate_html_header_table() + return jsonify({'data_header_table': data_header_table}) + + +@app.route('/delete-data-header', methods=['POST']) +def delete_data_header(): + data_header = session.get('data_header', {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}) + if data_header == {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}: + data_header_table = generate_html_header_table() + return jsonify({'data_header_table': data_header_table}) + + data = request.json + str = data['value'] + res = readHeaderHTML(str) + type = res['type'] + + if type == 'M': + print(data_header['measurement']) + data_header['measurement'].remove(res) + elif type == 'E': + data_header['event'].remove(res) + elif type == 'S': + data_header['segment'].remove(res) + elif type == 'SD': + data_header['segmentData'].remove(res) + session['data_header'] = data_header + + data_header_table = generate_html_header_table() + return jsonify({'data_header_table': data_header_table}) + + +@app.route('/get-MD-info', methods=['POST']) +def get_MD_info(): + data = request.json + str = data['value'] + soup = BeautifulSoup(str, 'html.parser') + tds = soup.find_all('td') + res = readHeaderHTML(str) # res = {'tablename': '', 'type': '', 'label': [], 'features_name': []} + type = res['type'] + label_column = res['label'][0] + session['current_data_header'] = res + print(res) + + if type == 'E' or type == 'M': + time = getTimeColumns(res['tablename']) # list + object = getObjectColumns(res['tablename']) # list + if res['label'][0] == 'col' and res['label'][1] in object: + object.remove(res['label'][1]) + + return jsonify({'time': time, 'object': object}) + elif type == 'S': + time = getTimeColumns(res['tablename']) # list + object = getObjectColumns(res['tablename']) # list + # index = + return jsonify({'time': time, 'object': object}) + elif type == 'SD': + time = getTimeColumns(res['tablename']) # list + object = getObjectColumns(res['tablename']) + # index = + return jsonify({'time': time, 'object': object}) + + +@app.route('/get-ME-table', methods=['POST']) +def get_ME_table(): + engine = create_engine(session.get('db_uri', '')) + data = request.json + current_data_header = session.get('current_data_header', {'tablename': '', 'type': '', 'label': [], 'features_name': []}) + table_name = current_data_header['tablename'] + type = current_data_header['type'] + time_column = data['time_column'] + object_column = data['object_column'] + optgroupLabel = data['optgroupLabel'] + object_list = [optgroupLabel, object_column] + label_list = current_data_header['label'] + features = [] + for feature in current_data_header['features_name']: + if '(' in feature and ')' in feature: # "value('coarse', 'fine')" + feature = feature.replace("'", "") + features.append(feature) + else: + features.append(feature) + start_time = datetime.strptime(data['minDatetime'], '%Y-%m-%d %H:%M:%S') if 'minDatetime' in data else None + end_time = datetime.strptime(data['maxDatetime'], '%Y-%m-%d %H:%M:%S') if 'maxDatetime' in data else None + + print(table_name) + print(type) + print(time_column) + print(object_list) + print(label_list) + print(features) + + query_result = extract_ME_table(engine, table_name, type, time_column, object_list, label_list, features, start_time, end_time) + table_HTML = get_ME_table_HTML(query_result) + + if start_time == None and end_time == None: + min_datetime, max_datetime = get_min_max_datetime(engine, table_name, time_column) + return jsonify({'table_HTML': table_HTML, 'min_datetime': min_datetime, 'max_datetime': max_datetime}) + + return jsonify({'table_HTML': table_HTML}) + + +def readHeaderHTML(str): + soup = BeautifulSoup(str, 'html.parser') + tds = soup.find_all('td') + + res = {'tablename': '', 'type': '', 'label': [], 'features_name': []} + res['tablename'] = tds[1].get_text() + res['type'] = tds[2].get_text() + res['label'] = json.loads(tds[3]['data-value']) + features = [] + for td in tds[4:]: + data_value = td['data-value'] + value_list = json.loads(data_value) + if value_list[0] == '': + a = value_list[1] + features.append(a) + else: + a = value_list[0] + '(' + for i in range(len(value_list)-1): + a += "'" + value_list[i+1] + "', " + a = a[:len(a)-2] + a += ')' + if a not in features: + features.append(a) + res['features_name'] = features # ['machine_id', "value('coarse', 'fine')"] + + print(features) + + return res -def check_json_column(engine, table_name): + +def check_json_column(engine, table_name) -> list: insp = inspect(engine) schema = getTableSchema(table_name) if insp.dialect.name == 'postgresql' else insp.default_schema_name json_column_names = [] @@ -297,6 +459,81 @@ def database_name_from_uri(engine, database_uri: str): return 'Unknown' +def count_feature_columns(data_header) -> int: + max_count = 0 + for key in data_header.keys(): + for dict_item in data_header[key]: + count = 0 + for value in dict_item.get('features_name', []): + num = tuple(value.split('(')[1].split(')')[0].replace("'","").split(', ')) if '(' in value and ')' in value else 1 + count += len(num) if type(num) == tuple else 1 + if count > max_count: + max_count = count + + return max_count + + +def count_data_header(data_header): + count = 0 + for key in data_header.keys(): + count += len(data_header[key]) + + return count + + +def generate_html_header_table(): + # session['data_header'] = {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]} + content = session.get('data_header', {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}) + count_f_columns= count_feature_columns(content) + count_d_headers = count_data_header(content) + + # Generate column headers + table_html = "<table class='uk-table uk-table-small uk-table-hover uk-table-divider uk-table-striped' style='cursor: pointer;'><thead><tr>" + table_html += "<th></th>" + table_html += "<th class='uk-table-expand'>Table name</th>" + table_html += "<th>Type</th>" + table_html += "<th class='uk-table-expand'>Label</th>" + for i in range(count_f_columns): + table_html += f"<th>F_{i+1}</th>" + table_html += "</tr></thead><tbody>" + + # Generate table rows + index = 0 + for key in content.keys(): + for dict_item in content[key]: + table_html += f"<tr onclick='getRowData(this)'>" + table_html += "<td><button type='button' class='btn-close' aria-label='Close' data-bs-dismiss='modal' onclick='deleteRow1(event, this)'></button></td>" + table_html += f"<td>{dict_item.get('tablename', '')}</td>" + table_html += f"<td data-id>{dict_item.get('type', '')}</td>" + print("723723") + print(dict_item.get('label', '')) + label_value = json.dumps(dict_item.get('label', '')) + table_html += f"<td data-value='{label_value}'>{dict_item.get('label', '')[2]}</td>" + print("823823") + for value in dict_item.get('features_name', []): + if '(' in value and ')' in value: + feature_cloumn = value.split('(')[0] + print(feature_cloumn) + multiple_columns = tuple(value.split('(')[1].split(')')[0].replace("'","").split(', ')) + print(multiple_columns) + for column in multiple_columns: + print("624624") + tmp = [feature_cloumn] + for col in multiple_columns: + tmp.append(col) + print(tmp) + data_value = json.dumps(tmp) + table_html += f"<td data-value='{data_value}'>{column}</td>" + else: + data_value = json.dumps(["", value]) + table_html += f"<td data-value='{data_value}'>{value}</td>" + index += 1 + table_html += "</tr>" + table_html += "</tbody></table>" + print(table_html) + return table_html + + def generate_html_table(content): if not content: return "No data found." @@ -318,7 +555,29 @@ def generate_html_table(content): return table_html -def query_database_for_table_content(engine, table_name, number=20): +def getTimeColumns(table_name:str) -> list: + engine = create_engine(session.get('db_uri', '')) + insp = inspect(engine) + schema = getTableSchema(table_name) if insp.dialect.name == 'postgresql' else insp.default_schema_name + columns = insp.get_columns(table_name, schema) + timestamp_columns = [column['name'] for column in columns if str(column['type']) == 'TIMESTAMP'] + print(timestamp_columns) + + return timestamp_columns + + +def getObjectColumns(table_name:str) -> list: + engine = create_engine(session.get('db_uri', '')) + insp = inspect(engine) + schema = getTableSchema(table_name) if insp.dialect.name == 'postgresql' else insp.default_schema_name + columns = insp.get_columns(table_name, schema) + object_columns = [column['name'] for column in columns if str(column['type']) == 'VARCHAR' or str(column['type']) == 'INTEGER' or str(column['type']) == 'NUMERIC'] + print(object_columns) + + return object_columns + + +def query_database_for_table_content(engine, table_name, number=100): # Initialize content list content_list = [] # Create a connection from the engine @@ -371,7 +630,7 @@ def getSchema(insp): def getTableInstance(engine, table_name): insp = inspect(engine) - table = importMetadata(engine, None, [table_name], False)[table_name] + table = importMetadata(engine, None, [table_name], True)[table_name] return table @@ -392,6 +651,144 @@ def showDistinctValues(engine, table_name, column_name): return names +def get_min_max_datetime(engine, table_name, time_column, start_time=None, end_time=None): + schema = getTableSchema(table_name) if engine.dialect.name == 'postgresql' else engine.dialect.default_schema_name + # Formulate the SQL query using the text function + query = text(f"SELECT MIN({time_column}) AS start_datetime, MAX({time_column}) AS end_datetime FROM {schema}.{table_name};") + + # Execute the query + with engine.connect() as connection: + row = connection.execute(query).mappings().fetchone() + + + # Extract the min and max datetime values + if row: + min_datetime, max_datetime = row['start_datetime'], row['end_datetime'] + print("Minimum datetime:", min_datetime) + print("Maximum datetime:", max_datetime) + return min_datetime, max_datetime + else: + print("No datetimes found.") + return None, None + + + +def extract_ME_table(engine, table_name: str, type: str, time_column: str, object: list, label: list, features_name: list, start_time: datetime = None, end_time: datetime = None) -> list: + conn = engine.connect() + table_instance = getTableInstance(engine, table_name) + label_column = label[1].strip() + label_value = label[2].strip() + object_column_value = object[1].strip() + join_clause = '' + + full_table_name = f"{table_instance.schema}.{table_instance.name}" if table_instance.schema else table_instance.name + sql_columns = [f"{full_table_name}.{time_column}"] + + # Handling object_column logic + if object[0].strip() != 'self' and object[0].strip() == 'col': + print("1") + object_column = table_instance.getColumn(object_column_value) + object_column_name = object_column.fkof.table.name if object_column.fkof else '' + if object_column and object_column.fkof and object_column_name in session.get('object_name', {}): + related_table_instance = getTableInstance(engine, object_column_name) + full_related_table_name = f"{related_table_instance.schema}.{related_table_instance.name}" if related_table_instance.schema else related_table_instance.name + join_clause = f"LEFT JOIN {full_related_table_name} ON {full_table_name}.{object_column.name} = {full_related_table_name}.{object_column.fkof.name}" + sql_columns.append(f"{full_related_table_name}.{session.get('object_name').get(object_column_name)} AS {object_column_value}") + else: + sql_columns.append(f"{full_table_name}.{object_column.name}") + print("12") + # If label[0] is not 'self', add it to SQL columns + if label[0].strip() != 'self': + sql_columns.append(f"{full_table_name}.{label_column}") + print("123") + # Handling JSON extractions + json_extractions = [] + for feature in features_name: + if '(' in feature and ')' in feature: + column_name, keys = feature[:-1].split('(') + keys = keys.split(', ') + for key in keys: + json_extraction = f"{full_table_name}.{column_name}->>'{key}' AS {key}" + json_extractions.append(json_extraction) + else: + sql_columns.append(f"{full_table_name}.{feature}") + print("1234") + # Adding JSON extractions to the select clause + sql_select = ', '.join(sql_columns + json_extractions) + + # Constructing SQL query + sql_joins = join_clause + if label[0].strip() == 'col': + sql_where = f"WHERE {full_table_name}.{label_column} = :label_value" + if start_time: + sql_where += f" AND {full_table_name}.{time_column} >= :start_time" + if end_time: + sql_where += f" AND {full_table_name}.{time_column} <= :end_time" + else: + sql_where = '' + if start_time: + sql_where += f"WHERE {full_table_name}.{time_column} >= :start_time" + if end_time: + sql_where += f" AND {full_table_name}.{time_column} <= :end_time" + + sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {time_column} ASC LIMIT 500" + print("12345") + # Executing the query + params = {'label_value': label_value} + if start_time: + params['start_time'] = start_time + if end_time: + params['end_time'] = end_time + + + # res = conn.execute(text(sql_query), params).fetchall() + + # Print the query for debugging + print("SQL Query:", sql_query) + print("Parameters:", params) + + # Executing the query + try: + res = conn.execute(text(sql_query), params).fetchall() + except Exception as e: + print(f"Error executing query: {e}") + return [] + + # Append object and label values if necessary + final_res = [] + for row in res: + modified_row = list(row) + if object[0].strip() == 'self': + modified_row.insert(1, object_column_value) + if label[0].strip() == 'self': + label_index = 2 if object[0].strip() != 'self' else 1 + modified_row.insert(label_index, label[2]) + modified_row.insert(2, type) + final_res.append(modified_row) + + for row in final_res: + print(row) + + return final_res + + +def get_ME_table_HTML(data: list) -> str: + # Start the HTML <body> content with an opening <table> tag + html_content = "" + + # Iterate over the data to populate the table rows + for row in data: + html_content += "<tr><td><input class='uk-checkbox' type='checkbox' aria-label='Checkbox'></td>" + for cell in row: + if isinstance(cell, datetime): + # cell = cell.isoformat() + cell = cell.strftime('%Y-%m-%d %H:%M:%S:%f') + html_content += f"<td>{cell}</td>" + html_content += "</tr>\n" + + return html_content + + def importMetadata(engine, schema=None, tables_selected=None, show_all=False): tables = {} if engine == None: @@ -643,540 +1040,4 @@ def getThemes(): if __name__ == "__main__": - app.run(debug=True) - - - - - - - - -# from my_flask_app import app -# from flask_sqlalchemy import SQLAlchemy -# from flask import jsonify, render_template, request, session, send_file -# from sqlalchemy import text -# import re, pydot, base64 -# import pydot - - -# # Set up database -# app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -# app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://postgres:password@localhost:5432/test" - -# db = SQLAlchemy() -# db.init_app(app) - -# app.secret_key = 'my_secret_key' # Needed for session management -# dropped_items = [] - -# @app.route('/', methods=['POST', 'GET']) -# def index(): -# # Initialize variables -# database = db.engine.url.database -# tables_selected = [] -# # tables_selected = ['machine_sensor', 'machine_tool', 'machine_trace'] -# schemas = getSchema() -# themes = getThemes() - -# schema_Selected = request.form.get('schema', None) -# show_all = request.form.get('show_all') == 'True' - -# tables1 = importMetadata(database, schema_Selected, tables_selected, show_all) -# graph_DOT1 = createGraph(tables1, themes["Blue Navy"], True, True, True) -# image1 = generate_erd(graph_DOT1) - -# if dropped_items==[]: -# image2 = "" -# else: -# tables2 = importMetadata(database, None, dropped_items, False) -# graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True) -# image2 = generate_erd(graph_DOT2) - -# print(getTableSchema('event_data')) - -# return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, image1=image1, image2=image2, dropped_items=dropped_items) - - - -# @app.route('/handle-drop', methods=['POST']) -# def handle_drop(): -# data = request.json -# item_name = data.get('item') -# action = data.get('action') - -# if action == 'added': -# dropped_items.append(item_name) -# elif action == 'removed' and item_name in dropped_items: -# dropped_items.remove(item_name) - -# # Regenerate ERD based on the updated dropped_items -# database = db.engine.url.database -# themes = getThemes() -# tables2 = importMetadata(database, None, dropped_items, False) -# graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True) -# image2 = generate_erd(graph_DOT2) - -# return jsonify(image2=image2) - - -# @app.route('/get-table-data', methods=['POST']) -# def get_table_data(): -# data = request.json -# table_name = data.get('table_name') -# print(table_name) - -# # Query your database to get the data for the table_name -# content = query_database_for_table_content(table_name) - -# # Convert content to HTML table format -# html_table = generate_html_table(content) - -# return jsonify({'html_table': html_table}) - - -# def generate_html_table(content): -# if not content: -# return "No data found." -# # Generate column headers -# columns = content[0] -# table_html = "<table class='uk-table uk-table-small uk-table-hover uk-table-divider'><thead><tr>" -# for col in columns: -# table_html += f"<th>{col}</th>" - -# table_html += "</tr></thead><tbody>" - -# # Generate table rows -# for i in range(1, len(content)): -# table_html += "<tr>" -# for item in content[i]: -# table_html += f"<td>{item}</td>" -# table_html += "</tr>" -# table_html += "</tbody></table>" -# return table_html - - -# def query_database_for_table_content(table_name, number=20): -# # Initialize content list -# content_list = [] - -# # Get the schema of the table -# schema = getTableSchema(table_name) -# # Query the database to get the content of the table -# sql_content = text(f"""SELECT * FROM {schema}.{table_name} LIMIT {number};""") -# result = db.session.execute(sql_content, {'table_name': table_name, 'number': number}).fetchall() - -# if not result: -# return [] - -# # Get the column names -# sql_columns = text(""" -# SELECT column_name -# FROM information_schema.columns -# WHERE table_name = :table_name; -# """) -# column_names = db.session.execute(sql_columns, {'table_name': table_name}).fetchall() - -# # Prepare column names -# columns = [column_name[0] for column_name in column_names] -# content_list.append(columns) - -# # Append rows to content list -# for row in result: -# content_list.append(list(row)) - -# return content_list - - -# def getTableSchema(table_name): -# sql= text(f""" -# SELECT table_schema -# FROM information_schema.tables -# WHERE table_name = :table_name; -# """) -# schema = db.session.execute(sql, {'table_name': table_name}).fetchone()[0] -# return schema - - -# def getSchema(): -# sql = text("""SELECT schema_name FROM information_schema.schemata;""") -# result = db.session.execute(sql) -# schemas = [row[0] for row in result] -# return schemas - - -# def importMetadata(database, schema=None, tables_selected=None, show_all=False): -# tables = {} -# if database == '': -# return tables - -# # Convert tables_selected to a list to ensure compatibility with SQL IN operation. -# tables_selected_list = list(tables_selected) if tables_selected else None - -# # Fetch initial tables based on schema and table_names. -# tables = fetch_initial_tables(schema, tables_selected_list) - -# # If show_all is True, expand the list to include related tables. -# if show_all: -# expand_to_include_related_tables(tables) - -# # Fetch columns for each table. -# fetch_columns_for_tables(tables) - -# # Fetch constraints (PK, FK, Unique) for each table. -# fetch_constraints_for_tables(tables) - -# return tables - - -# def fetch_initial_tables(schema, tables_selected_list): -# tables = {} - -# # Construct WHERE clauses based on input parameters. -# schema_condition = "AND table_schema = :schema" if schema else "" -# tables_selected_condition = "AND table_name = ANY(:tables_selected)" if tables_selected_list else "" - -# # Fetching tables with dynamic schema and table name filtering. -# sql_tables = text(f""" -# SELECT table_name, table_schema -# FROM information_schema.tables -# WHERE table_type = 'BASE TABLE' -# {schema_condition} {tables_selected_condition}; -# """) - -# # Adjust parameters based on the conditions. -# params = {} -# if schema: -# params['schema'] = schema -# if tables_selected_list: -# params['tables_selected'] = tables_selected_list - -# result = db.session.execute(sql_tables, params) - -# for row in result: -# tableName, tableSchema = row -# table = Table(tableName, tableSchema) # adding schema to the table comment -# tables[tableName] = table -# table.label = f"n{len(tables)}" - -# return tables - - -# def expand_to_include_related_tables(tables): -# # This dictionary will temporarily store related tables to fetch later. -# related_tables_to_fetch = {} - -# # Iterate over initially fetched tables to find foreign key relationships. -# for tableName, table in tables.items(): -# # Fetch foreign key relationships for the current table. -# sql_fk = text(""" -# SELECT -# ccu.table_name AS pk_table_name, -# ccu.table_schema AS pk_table_schema -# FROM -# information_schema.table_constraints AS tc -# JOIN information_schema.key_column_usage AS kcu -# ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name -# JOIN information_schema.constraint_column_usage AS ccu -# ON ccu.constraint_name = tc.constraint_name -# WHERE -# tc.constraint_type = 'FOREIGN KEY' -# AND tc.table_name = :table_name -# """) -# fk_result = db.session.execute(sql_fk, {'table_name': tableName}) - - -# for referenced_table_name, referenced_schema in fk_result: -# if referenced_table_name not in tables and referenced_table_name not in related_tables_to_fetch: -# related_tables_to_fetch[referenced_table_name] = referenced_schema - -# # Fetch and add related tables. -# for tableName, tableSchema in related_tables_to_fetch.items(): -# # Assuming a function fetch_table_details(tableName, tableSchema) that fetches and returns -# # a Table object with columns and constraints populated. -# table = Table(tableName, tableSchema) -# tables[tableName] = table - -# return tables - - -# def fetch_columns_for_tables(tables): -# for tableName, table in tables.items(): -# sql_columns = text(""" -# SELECT column_name, data_type, is_nullable, column_default -# FROM information_schema.columns -# WHERE table_name = :table_name; -# """) -# column_result = db.session.execute(sql_columns, {'table_name': tableName}) - -# for col in column_result: -# name, datatype, nullable, default = col -# column = Column(table, name, '') -# column.setDataType({ -# "type": datatype, -# "nullable": nullable == 'YES', -# "default": default -# }) -# table.columns.append(column) -# return tables - - -# def fetch_constraints_for_tables(tables): -# # Fetching Unique Constraints -# for tableName, table in tables.items(): -# sql_unique = text(""" -# SELECT kcu.column_name, tc.constraint_name -# FROM information_schema.table_constraints AS tc -# JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name -# WHERE tc.table_name = :table_name AND tc.constraint_type = 'UNIQUE'; -# """) -# unique_result = db.session.execute(sql_unique, {'table_name': tableName}) -# for col in unique_result: -# name, constraintName = col -# column = table.getColumn(name) -# if column: -# column.isunique = True -# if constraintName not in table.uniques: -# table.uniques[constraintName] = [] -# table.uniques[constraintName].append(column) - - -# # Primary Keys -# for tableName, table in tables.items(): -# sql_pk = text(""" -# SELECT kcu.column_name, tc.constraint_name, kcu.ordinal_position -# FROM information_schema.table_constraints AS tc -# JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name -# WHERE tc.table_name = :table_name AND tc.constraint_type = 'PRIMARY KEY'; -# """) -# pk_result = db.session.execute(sql_pk, {'table_name': tableName}) -# for col in pk_result: -# name, constraintName, ordinal_position = col -# column = table.getColumn(name) -# if column: -# column.ispk = True -# column.pkconstraint = constraintName -# # Assuming you want to order PKs, though not directly used in provided class - - -# # Fetching Foreign Keys for each table -# for tableName, table in tables.items(): -# sql_fk = text(""" -# SELECT -# tc.constraint_name, -# tc.table_name AS fk_table_name, -# kcu.column_name AS fk_column_name, -# ccu.table_name AS pk_table_name, -# ccu.column_name AS pk_column_name, -# ccu.table_schema AS pk_table_schema -# FROM -# information_schema.table_constraints AS tc -# JOIN information_schema.key_column_usage AS kcu -# ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name -# JOIN information_schema.constraint_column_usage AS ccu -# ON ccu.constraint_name = tc.constraint_name -# WHERE -# tc.constraint_type = 'FOREIGN KEY' -# AND tc.table_name = :table_name -# """) - -# fk_result = db.session.execute(sql_fk, {'table_name': tableName}) -# for row in fk_result: -# constraintName, fkTableName, fkColumnName, pkTableName, pkColumnName, pkTableSchema = row - -# # Ensure the foreign key table is the current table being processed -# if fkTableName != tableName: -# continue - - -# fkTable = tables.get(fkTableName) -# pkTable = tables.get(pkTableName) - -# if fkTable and pkTable: -# fkColumn = fkTable.getColumn(fkColumnName) -# pkColumn = pkTable.getColumn(pkColumnName) - -# if fkColumn and pkColumn: -# # Here, instead of assigning pkColumn directly, store relevant info -# fkColumn.fkof = pkColumn # Adjust based on your application's needs -# if constraintName not in fkTable.fks: -# fkTable.fks[constraintName] = [] -# fkTable.fks[constraintName].append(fkColumn) -# return tables - - -# def createGraph(tables, theme, showColumns, showTypes, useUpperCase): -# s = ('digraph {\n' -# + ' graph [ rankdir="LR" bgcolor="#ffffff" ]\n' -# + f' node [ style="filled" shape="{theme.shape}" gradientangle="180" ]\n' -# + ' edge [ arrowhead="none" arrowtail="none" dir="both" ]\n\n') - -# for name in tables: -# s += tables[name].getDotShape(theme, showColumns, showTypes, useUpperCase) -# s += "\n" -# for name in tables: -# s += tables[name].getDotLinks(theme) -# s += "}\n" -# return s - - -# def generate_erd(graph_DOT): -# graph_module = pydot.graph_from_dot_data(graph_DOT) -# graph = graph_module[0] -# png_image_data = graph.create_png() -# encoded_image = base64.b64encode(png_image_data).decode('utf-8') -# return encoded_image - - -# def getThemes(): -# return { -# "Common Gray": Theme("#6c6c6c", "#e0e0e0", "#f5f5f5", -# "#e0e0e0", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"), -# "Blue Navy": Theme("#1a5282", "#1a5282", "#ffffff", -# "#1a5282", "#000000", "#ffffff", "rounded", "Mrecord", "#0078d7", "2"), -# #"Gradient Green": Theme("#716f64", "#008080:#ffffff", "#008080:#ffffff", -# # "transparent", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"), -# #"Blue Sky": Theme("#716f64", "#d3dcef:#ffffff", "#d3dcef:#ffffff", -# # "transparent", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"), -# "Common Gray Box": Theme("#6c6c6c", "#e0e0e0", "#f5f5f5", -# "#e0e0e0", "#000000", "#000000", "rounded", "record", "#696969", "1") -# } - - -# if __name__ == "__main__": -# app.run(port=5001) - - -# class Theme: -# def __init__(self, color, fillcolor, fillcolorC, -# bgcolor, icolor, tcolor, style, shape, pencolor, penwidth): -# self.color = color -# self.fillcolor = fillcolor -# self.fillcolorC = fillcolorC -# self.bgcolor = bgcolor -# self.icolor = icolor -# self.tcolor = tcolor -# self.style = style -# self.shape = shape -# self.pencolor = pencolor -# self.penwidth = penwidth - - -# class Table: -# def __init__(self, name, comment): -# self.name = name -# self.comment = comment if comment is not None and comment != 'None' else '' -# self.label = None - -# self.columns = [] # list of all columns -# self.uniques = {} # dictionary with UNIQUE constraints, by name + list of columns -# self.pks = [] # list of PK columns (if any) -# self.fks = {} # dictionary with FK constraints, by name + list of FK columns - - -# @classmethod -# def getClassName(cls, name, useUpperCase, withQuotes=True): -# if re.match("^[A-Z_0-9]*$", name) == None: -# return f'"{name}"' if withQuotes else name -# return name.upper() if useUpperCase else name.lower() - -# def getName(self, useUpperCase, withQuotes=True): -# return Table.getClassName(self.name, useUpperCase, withQuotes) - -# def getColumn(self, name): -# for column in self.columns: -# if column.name == name: -# return column -# return None - -# def getDotShape(self, theme, showColumns, showTypes, useUpperCase): -# fillcolor = theme.fillcolorC if showColumns else theme.fillcolor -# colspan = "2" if showTypes else "1" -# tableName = self.getName(useUpperCase, False) - -# s = (f' {self.label} [\n' -# + f' fillcolor="{fillcolor}" color="{theme.color}" penwidth="1"\n' -# + f' label=<<table style="{theme.style}" border="0" cellborder="0" cellspacing="0" cellpadding="1">\n' -# + f' <tr><td bgcolor="{theme.bgcolor}" align="center"' -# + f' colspan="{colspan}"><font color="{theme.tcolor}"><b>{tableName}</b></font></td></tr>\n') - -# if showColumns: -# for column in self.columns: -# name = column.getName(useUpperCase, False) -# if column.ispk: name = f"<u>{name}</u>" -# if column.fkof != None: name = f"<i>{name}</i>" -# if column.nullable: name = f"{name}*" -# if column.identity: name = f"{name} I" -# if column.isunique: name = f"{name} U" -# datatype = column.datatype -# if useUpperCase: datatype = datatype.upper() - -# if showTypes: -# s += (f' <tr><td align="left"><font color="{theme.icolor}">{name} </font></td>\n' -# + f' <td align="left"><font color="{theme.icolor}">{datatype}</font></td></tr>\n') -# else: -# s += f' <tr><td align="left"><font color="{theme.icolor}">{name}</font></td></tr>\n' - -# return s + ' </table>>\n ]\n' - - -# def getDotLinks(self, theme): -# s = "" -# for constraint in self.fks: -# fks = self.fks[constraint] -# fk1 = fks[0] -# dashed = "" if not fk1.nullable else ' style="dashed"' -# arrow = "" if fk1.ispk and len(self.pks) == len(fk1.fkof.table.pks) else ' arrowtail="crow"' -# s += (f' {self.label} -> {fk1.fkof.table.label}' -# + f' [ penwidth="{theme.penwidth}" color="{theme.pencolor}"{dashed}{arrow} ]\n') -# return s - - -# class Column: -# def __init__(self, table, name, comment): -# self.table = table -# self.name = name -# self.comment = comment if comment is not None and comment != 'None' else '' -# self.nullable = True -# self.datatype = None # with (length, or precision/scale) -# self.identity = False - -# self.isunique = False -# self.ispk = False -# self.pkconstraint = None -# self.fkof = None # points to the PK column on the other side - - -# def getName(self, useUpperCase, withQuotes=True): -# return Table.getClassName(self.name, useUpperCase, withQuotes) - - -# def setDataType(self, datatype): -# self.datatype = datatype["type"] -# self.nullable = bool(datatype["nullable"]) - -# if self.datatype == "FIXED": -# self.datatype = "NUMBER" -# elif "fixed" in datatype: -# fixed = bool(datatype["fixed"]) -# if self.datatype == "TEXT": -# self.datatype = "CHAR" if fixed else "VARCHAR" - -# if "length" in datatype: -# self.datatype += f"({str(datatype['length'])})" -# elif "scale" in datatype: -# if int(datatype['precision']) == 0: -# self.datatype += f"({str(datatype['scale'])})" -# if self.datatype == "TIMESTAMP_NTZ(9)": -# self.datatype = "TIMESTAMP" -# elif "scale" in datatype and int(datatype['scale']) == 0: -# self.datatype += f"({str(datatype['precision'])})" -# if self.datatype == "NUMBER(38)": -# self.datatype = "INT" -# elif self.datatype.startswith("NUMBER("): -# self.datatype = f"INT({str(datatype['precision'])})" -# elif "scale" in datatype: -# self.datatype += f"({str(datatype['precision'])},{str(datatype['scale'])})" -# #if column.datatype.startswith("NUMBER("): -# # column.datatype = f"FLOAT({str(datatype['precision'])},{str(datatype['scale'])})" -# self.datatype = self.datatype.lower() \ No newline at end of file + app.run(debug=True) \ No newline at end of file diff --git a/my_flask_app/models/__pycache__/models.cpython-39.pyc b/my_flask_app/models/__pycache__/models.cpython-39.pyc index c934da4ed83aa67dbcb94493d6d76507c6c13579..85c8c24503febc2cee3793115e586cf4f4bf73ef 100644 GIT binary patch delta 3086 zcmZqmUg^!7$ji&cz`(%3Ci*&c^G4nijOuI*3=9qo3=G9y3=9mZ3{i|J3{gxeys6Bo z%&9D?%#sYLEGbNLSSEjCbmL*U#aWV=l#`m5n43D;mPyu|6=W6^gV;<A3=Ga7!&Dd; z7-|@67~&afm}(f}8Ecqp7~+{~Sc;@-Sn`<C8EP2fnNt{o8CEj-6^Sx1Fle$)p3fx8 zStQB8z_5~`NM`a5rcibckjac}lZBZ5ZKXhpn85P4*b)m0Qu9()G8IWPFfiO=0Xe5g zhJk?rLdb#yxf4@T;?q(SOG=AUc_+_f4!1xtp(r&szbv�j>$G9jucNA_vnfFj<$y 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 T<&_pt;N$0u;qw(>5nurTpF3Al delta 3064 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 diff --git a/my_flask_app/models/models.py b/my_flask_app/models/models.py index f19dfef..f01eeb3 100644 --- a/my_flask_app/models/models.py +++ b/my_flask_app/models/models.py @@ -4,9 +4,10 @@ import re class Observation_Spec: - def __init__(self, type:str, label:str, features_name:list = None): + def __init__(self, tablename:str, type:str, label:list[str], features_name:list = None): + self.tablename = tablename self.type = type # 'event' or 'measurement' - self.label = label + self.label = label # (self-define or from column, column name, column value orself-define label) self.features_name = features_name if features_name else [] def add_feature(self, name): @@ -17,6 +18,7 @@ class Observation_Spec: def to_dict(self): return { + 'tablename': self.tablename, 'type': self.type, 'label': self.label, 'features_name': self.features_name diff --git a/my_flask_app/templates/app.html b/my_flask_app/templates/app.html index 0d04707..d22226f 100644 --- a/my_flask_app/templates/app.html +++ b/my_flask_app/templates/app.html @@ -106,6 +106,8 @@ padding: 0; margin-top: 0px; border: none; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; } .custom-zoom-button { font-size: 14px; @@ -115,6 +117,7 @@ background-color: rgba(173, 216, 230, 0.5); padding: 0; border: none; /* Remove border to make it seamless */ + border-radius: 5px; } button { width: 80%; } .headerButton { @@ -279,6 +282,7 @@ position: sticky; top: 0; } + </style> </head> <body> @@ -487,6 +491,13 @@ </div> </div> + + <div class="mb-3" id="self-defined-object-selection" style="display: none; border-bottom: 1px dashed black;"> + <label class="form-label">Self-defined Object</label><br> + <input type="text" id="defined-object" class="form-control headerSelect"> + <button class="btn btn-primary headerButton" onclick="selfdefinedObject()">add</button> + </div> + <div class="mb-3" id="object-column-selection" style="display: none;"> <label class="form-label">Object name</label><br> <select id="select_column_object" class="form-select headerSelect" name="column" style="margin-right: 5px; display: inline;"> @@ -512,7 +523,7 @@ {% endfor %} </select> - <select id=label_values class="form-select headerSelect " style="margin-top: 2px; margin-right: 5px; display: inline;"> + <select id='label_values' class="form-select headerSelect " style="margin-top: 2px; margin-right: 5px; display: inline;"> <option>Label select...</option> <optgroup id="defined_label_values" label="self-defined label"> {% for self_label in self_labels %} @@ -550,87 +561,88 @@ <li> <div style="display: flex; flex-direction: column; height: 87vh;"> - <div class="table-container" style="flex: 1; overflow-y: auto;"> - <table class="uk-table uk-table-striped" style="overflow-y: scroll;"> + <div class="mb-3"> + <h4 style="display: inline; margin-left: .2em;">Data Header Table</h4> + <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="resetDataHeaderTable()">Reset</button> + </div> + <div class="uk-overflow-auto" id="data-header-table" style="flex-grow: 1; overflow-y: auto; max-height: max-content; margin-top: -20px;"> + <table class='uk-table uk-table-small uk-table-hover uk-table-divider'> <thead> <tr> - <th>Table Heading</th> - <th>Table Heading</th> - <th>Table Heading</th> + <th></th> + <th>Table name</th> + <th>Type</th> + <th>Label</th> </tr> </thead> - <tbody> - <tr> - <td>Table Data</td> - <td>Table Data</td> - <td>Table Data</td> - </tr> - <tr> - <td>Table Data</td> - <td>Table Data</td> - <td>Table Data</td> - </tr> - <tr> - <td>Table Data</td> - <td>Table Data</td> - <td>Table Data</td> + </table> + </div> + <div class="mb-3"> + <h4 style="display: inline; margin-left: .2em;">Machine Data Table (LIMIT 500)</h4> + <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="resetMachineDataTable()">Reset</button> + </div> + <div class="uk-overflow-auto" id="machine-data-table" style="flex-grow: 1; overflow-y: auto; max-height: max-content; margin-top: -20px;" > + <table id="MD-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;"> + <thead> + <tr class="uk-table-middle"> + <th class="uk-table-shrink"> + <input class="uk-checkbox" onclick="click_all_MD_table(this)" type="checkbox" aria-label="Checkbox" style="margin-top: 7px;"> + </th> + + <th data-id="time" class="uk-table-expand"> + <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> + time + <select id="table_select_time" class="time-object-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <option>Select a data header first...</option> + </select> + </span> + </th> + + <th data-id="object" class="uk-table-expand"> + <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> + object + <select id="table_object" class="time-object-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <option value="no">Select a data header first...</option> + + <optgroup id="defined_object_values" label="self-defined object"> + </optgroup> + + <optgroup id="table_select_object" label="object column"> + </optgroup > + </select> + + </span> + </th> + + <th data-id="type" class="uk-width-small">type</th> + <th data-id="label" class="uk-table-expand">label</th> + <th data-id="segment" class="uk-table-expand">segment</th> + <th data-id="index" class="uk-table-expand">index</th> + + <th data-id="starttime" class="uk-table-expand"> + <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> + start time + <select id="starttime-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <option val="no">Select a column first...</option> + </select> + </span> + </th> + + <th data-id="endtime" class="uk-table-expand"> + <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> + end time + <select id="endtime-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <option val="no">Select a column first...</option> + </select> + </span> + </th> </tr> + </thead> + <tbody> + </tbody> </table> </div> - <div class="table-container" style="flex: 1; overflow-y: auto;"> - <div class="uk-overflow-auto"> - <table class="uk-table uk-table-hover uk-table-middle uk-table-divider" style="overflow-y: scroll;"> - <thead> - <tr> - <th class="uk-table-shrink"></th> - <th class="uk-table-shrink">Preserve</th> - <th class="uk-table-expand">Expand + Link</th> - <th class="uk-width-small">Truncate</th> - <th class="uk-table-shrink uk-text-nowrap">Shrink + Nowrap</th> - </tr> - </thead> - <tbody> - <tr> - <td><input class="uk-checkbox" type="checkbox" aria-label="Checkbox"></td> - <td><img class="uk-preserve-width uk-border-circle" src="images/avatar.jpg" width="40" height="40" alt=""></td> - <td class="uk-table-link"> - <a class="uk-link-reset" href="">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</a> - </td> - <td class="uk-text-truncate">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</td> - <td class="uk-text-nowrap">Lorem ipsum dolor</td> - </tr> - <tr> - <td><input class="uk-checkbox" type="checkbox" aria-label="Checkbox"></td> - <td><img class="uk-preserve-width uk-border-circle" src="images/avatar.jpg" width="40" height="40" alt=""></td> - <td class="uk-table-link"> - <a class="uk-link-reset" href="">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</a> - </td> - <td class="uk-text-truncate">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</td> - <td class="uk-text-nowrap">Lorem ipsum dolor</td> - </tr> - <tr> - <td><input class="uk-checkbox" type="checkbox" aria-label="Checkbox"></td> - <td><img class="uk-preserve-width uk-border-circle" src="images/avatar.jpg" width="40" height="40" alt=""></td> - <td class="uk-table-link"> - <a class="uk-link-reset" href="">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</a> - </td> - <td class="uk-text-truncate">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</td> - <td class="uk-text-nowrap">Lorem ipsum dolor</td> - </tr> - <tr> - <td><input class="uk-checkbox" type="checkbox" aria-label="Checkbox"></td> - <td><img class="uk-preserve-width uk-border-circle" src="images/avatar.jpg" width="40" height="40" alt=""></td> - <td class="uk-table-link"> - <a class="uk-link-reset" href="">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</a> - </td> - <td class="uk-text-truncate">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</td> - <td class="uk-text-nowrap">Lorem ipsum dolor</td> - </tr> - </tbody> - </table> - </div> - </div> </div> </li> </ul> @@ -655,32 +667,109 @@ <div id="sidebar2"> <ul uk-accordion> - <li class="uk-open"> - <a class="uk-accordion-title" href="#">Item 1</a> - <div class="uk-accordion-content"> - <p>Content for item 1.</p> - </div> - </li> <li> - <a class="uk-accordion-title" href="#">Item 2</a> + <a class="uk-accordion-title" href="#">Filter</a> <div class="uk-accordion-content"> - <p>Content for item 2.</p> + + <div class="accordion"> + <div class="accordion-item"> + <h2 class="accordion-header"> + <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="true" aria-controls="collapseTwo"> + Time + </button> + </h2> + <div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#accordionExample"> + <div class="accordion-body"> + <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> + <script src="//code.jquery.com/jquery-3.6.0.min.js"></script> + <script src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script> + + <p style="margin: -4px -21px 5px -17px; padding: 0px;"> + <label for="amount1">From: </label><br> + <input type="text" id="amount1" class="form-control" style="display: flex; border: 0; color: #5ea9e2; font-weight:bold;"><br> + <label for="amount2">Until: </label><br> + <input type="text" id="amount2" class="form-control" style="border:0; color: #5ea9e2; font-weight:bold;"> + </p> + <div id="slider-range"></div> + <button type="submit" class="btn btn-primary headerButton" style="margin: 10px 0px -5px -10px;" onclick="filter_ME_data_table()">Submit</button> + </div> + </div> + </div> + </div> + </div> </li> </ul> - <!-- set_database.html - <form method="post" action="/"> - <div class="mb-3"> - <label for="exampleInputPassword1" name="database_uri" class="form-label">Database URI</label> - <input type="text" class="form-control" id="exampleInputPassword1" name="database_uri" placeholder="Enter Database URI"> - <button type="submit" class="btn btn-primary">Submit</button> - </div> - </form> --> + <div id="resize-handle-left" class="resize-handle-left"></div> </div> + <script> + var jq = jQuery.noConflict(); + function initializeSlider(minDatetime, maxDatetime) { + // Default values if min or max datetime is not provided + var defaultMinDatetime = '2022-11-01 16:00:00.000000+00:00'; + var defaultMaxDatetime = '2022-11-02 16:00:00.000000+00:00'; + + // Use default values if min or max datetime is empty + minDatetime = minDatetime || defaultMinDatetime; + maxDatetime = maxDatetime || defaultMaxDatetime; + + // Function to parse date string with milliseconds + function parseDateTime(str) { + var date = new Date(str); + if (isNaN(date.getTime())) { + console.error('The datetime format is incorrect:', str); + return null; + } + return date; + } + + function toTimestamp(strDate) { + var date = parseDateTime(strDate); + return date ? date.getTime() : null; + } + + + // Function to format date to string with milliseconds + function formatDateTime(date) { + var hours = ('0' + date.getHours()).slice(-2); + var minutes = ('0' + date.getMinutes()).slice(-2); + var seconds = ('0' + date.getSeconds()).slice(-2); + + return jq.datepicker.formatDate('yy-mm-dd', date) + + ' ' + hours + ':' + + minutes + ':' + + seconds; + } + + // Initialize the slider with dynamic datetime values + console.log("initialize slider with dynamic datetime values"); + jq("#slider-range").slider({ + range: true, + min: toTimestamp(minDatetime), // Use minDatetime from the server + max: toTimestamp(maxDatetime), // Use maxDatetime from the server + step: 1, // Step is now 1 millisecond + values: [ + toTimestamp(minDatetime), // Set the lower handle to minDatetime + toTimestamp(maxDatetime) // Set the upper handle to maxDatetime + ], + slide: function(event, ui) { + var startDateTime = new Date(ui.values[0]); + var endDateTime = new Date(ui.values[1]); + jq("#amount1").val(formatDateTime(startDateTime)); + jq("#amount2").val(formatDateTime(endDateTime)); + } + }); + } + + + + + + // function select_schema(){ // document.getElementById('schemaSelect').addEventListener('changed', function() { // var selectedSchema = this.value; @@ -747,20 +836,322 @@ // }); // } + function click_all_MD_table(element) { + var checkboxes = document.querySelectorAll('#MD-table tbody input[type="checkbox"]'); + checkboxes.forEach(function(checkbox) { + checkbox.checked = element.checked; + }); + } + + + function resetMachineDataTable() { + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + } + + + function getRowData(r) { + r.parentNode.querySelectorAll("tr").forEach(function(t) { + t.style.backgroundColor = "white"; + }); + var value = r.innerHTML; + r.style.backgroundColor = "#5ea9e2"; + const type = r.querySelectorAll("td")[2].innerHTML; // type: M, E, S, SD + var numColumns = r.querySelectorAll("td").length; + + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + + var tr = document.getElementById('MD-table').getElementsByTagName('tr')[0]; + var ths = Array.from(tr.querySelectorAll("th")); + // Start from the end of the NodeList and move backward + for (var i = ths.length - 1; i > 0; i--) { + if (i > 8) { + ths[i].remove(); // Remove the <th> element + } else { + // Safe to apply style because ths[i] exists + ths[i].style.display = "none"; + } + } + + if (type == "M" || type == "E") { + [1, 2, 3, 4].forEach(k => { + tr.querySelectorAll("th")[k].style.display = "table-cell"; + }); + for (let i = 1; i <= numColumns - 4; i++) { + let th = tr.appendChild(document.createElement('th')); + th.classList.add("uk-table-shrink"); + th.innerHTML = "F_" + i; + } + } else if (type == "S") { + [2, 4, 6, 7, 8].forEach(k => { + tr.querySelectorAll("th")[k].style.display = "table-cell"; + }); + } else if (type == "SD") { + [2, 4, 5, 6].forEach(k => { + tr.querySelectorAll("th")[k].style.display = "table-cell"; + }); + for (let i = 1; i <= numColumns - 4; i++) { + let th = tr.appendChild(document.createElement('th')); + th.classList.add("uk-table-shrink"); + th.innerHTML = "F_" + i; + } + } + + fetch('/get-MD-info', { + method: 'POST', + body: JSON.stringify({ 'value': value }), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + // Set all the select elements to the default value + var selectTime = document.getElementById('table_select_time'); + selectTime.value = "no"; + var selectObject = document.getElementById('table_object'); + selectObject.value = "no"; + + ////////////////////////////////////////////////////////////////////////// + if (type == "M" || type == "E") { + // Update the time select with the new time columns + const selectTime = document.getElementById('table_select_time'); + selectTime.innerHTML = ''; // Clear existing options + const init = document.createElement('option'); + init.value = "no"; + init.textContent = "Select a column..."; + selectTime.appendChild(init); + data['time'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectTime.appendChild(optionElement); + }); + // Update the object select with the new object columns + const selectObject = document.getElementById('table_select_object'); + selectObject.innerHTML = ''; // Clear existing options + data['object'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectObject.appendChild(optionElement); + }); + } else if (type == "S") { + } else if (type == "SD") { + } + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + + + + // Select only the specified select elements + const time_object_selects = document.querySelectorAll('.time-object-select'); + + function checkSelects() { + for (let select of time_object_selects) { + if (select.value === 'no') { + return false; // If any watched select is not chosen, return false + } + } + return true; // If all watched selects have a value, return true + } + + time_object_selects.forEach(select => { + select.addEventListener('change', () => { + if (checkSelects()) { + var $ = function(id) { return document.getElementById(id); }; + // All specified selects have a value, call the fetch function + var time_column = document.getElementById('table_select_time').value; + var object_column = document.getElementById('table_object').value; + + var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "self-defined object") { + optgroupLabel = "self"; + } else if (optgroupLabel == "object column") { + optgroupLabel = "col"; + } + console.log(optgroupLabel); + console.log(object_column); + console.log(time_column); + + fetch('/get-ME-table', { + method: 'POST', + body: JSON.stringify({ 'time_column': time_column, 'object_column': object_column, 'optgroupLabel': optgroupLabel }), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + + if ('min_datetime' in data && 'max_datetime' in data) { + // Initialize the slider with the min and max datetime from the server + console.log("initialize slider with min and max datetime from the server"); + initializeSlider(data['min_datetime'], data['max_datetime']); + } + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + }); + }); + + function filter_ME_data_table() { + var $ = function(id) { return document.getElementById(id); }; + // Get min and max datetime from the slider + var minDatetime = $("amount1").value; + var maxDatetime = $("amount2").value; + + // All specified selects have a value, call the fetch function + var time_column = document.getElementById('table_select_time').value; + var object_column = document.getElementById('table_object').value; + + var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "self-defined object") { + optgroupLabel = "self"; + } else if (optgroupLabel == "object column") { + optgroupLabel = "col"; + } + console.log(optgroupLabel); + console.log(object_column); + console.log(time_column); + + fetch('/get-ME-table', { + method: 'POST', + body: JSON.stringify({ 'time_column': time_column, 'object_column': object_column, 'optgroupLabel': optgroupLabel, 'minDatetime': minDatetime, 'maxDatetime': maxDatetime}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + + function selfdefinedObject() { + var object = document.getElementById('defined-object').value; + var select = document.getElementById('defined_object_values'); + var opt = document.createElement('option'); + opt.value = object; + opt.innerHTML = object; + select.appendChild(opt); + } + + + function deleteRow1(event, r) { + event.stopPropagation(); // Stop the event from bubbling up to the <tr> + var i = r.parentNode.parentNode.rowIndex; + var table = r.closest("table"); // Find the closest table ancestor + table.deleteRow(i); + // var divv = document.getElementById("data-header-table"); + // divv.getElementsByTagName("table")[0].deleteRow(i); + + var value = r.parentNode.parentNode.innerHTML + + console.log(value); + console.log("YYYYY"); + fetch('/delete-data-header', { + method: 'POST', + body: JSON.stringify({ 'value': value }), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + const table = document.getElementById('data-header-table'); + table.innerHTML = data['data_header_table']; + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + + } + + + function resetDataHeaderTable() { + fetch('/reset-data-header-table', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + const table = document.getElementById('data-header-table'); + table.innerHTML = data['data_header_table']; + // "<table class='uk-table uk-table-small uk-table-hover uk-table-divider'><thead><tr><th>Table name</th><th>Type</th><th>Label</th></tr></thead></table>"; + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + + + function initializeDataHeaderTable() { + fetch('/init-data-header-table', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + const table = document.getElementById('data-header-table'); + table.innerHTML = data['data_header_table']; + // "<table class='uk-table uk-table-small uk-table-hover uk-table-divider'><thead><tr><th>Table name</th><th>Type</th><th>Label</th></tr></thead></table>"; + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + function submitDataHeader() { var $ = function(id) { return document.getElementById(id); }; var isValid = true; var radioSelect = document.querySelector('input[name="radio1"]:checked'); - console.log(radioSelect.value); - var label = $("label_values").value; - console.log(label); + + var label_column = $("select_column").value; + var label = $("label_values").value; + var selectedOption = $("label_values").options[$("label_values").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "label column data") { + optgroupLabel = "col"; + } else if (optgroupLabel == "self-defined label") { + optgroupLabel = "self"; + } + var label_list = ' [" ' + optgroupLabel + ' "," ' + label_column + ' "," ' + label + ' "] '; + var object_column = $("select_column_object").value; - console.log(object_column); + var tmp = $("select_features").selectedOptions; var features_list = Array.from(tmp).map(({ value }) => value); - console.log(features_list); if (radioSelect.value == 'measurement') { if (label == 'Label select...' || label == null || label == "") { @@ -836,20 +1227,22 @@ $("span").nextElementSibling.textContent = "*"; $("select_column_object").nextElementSibling.textContent = "*"; - console.log("HERE"); - console.log(radioSelect, label, object_column, features_list); + // Send the value to the server with fetch API fetch('/add-data-header', { method: 'POST', - body: JSON.stringify({ 'type': radioSelect.value, 'label': label, 'object_column': object_column, 'value_list': features_list}), + body: JSON.stringify({ 'type': radioSelect.value, 'label': label_list, 'object_column': object_column, 'value_list': features_list}), headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { - + if (radioSelect.value != 'object') { + const table = document.getElementById('data-header-table'); + table.innerHTML = data['data_header_table']; + } }) .catch(error => { // Handle any error that occurred during the fetch @@ -1136,6 +1529,7 @@ var objectType = document.getElementById('objectType'); var featuresSelection = document.getElementById('featuresSelection'); + var selfdefinedObject = document.getElementById('self-defined-object-selection'); var objectSelection = document.getElementById('object-column-selection'); var selfdefinedlabelSelection = document.getElementById('self-defined-label-selection'); var labelSelection = document.getElementById('label-selection'); @@ -1143,22 +1537,26 @@ if (segmentType.checked && radio.value === "segment") { featuresSelection.style.display = 'none'; objectSelection.style.display = 'none'; + selfdefinedObject.style.display = 'none'; selfdefinedlabelSelection.style.display = 'block'; labelSelection.style.display = 'block'; } else if (objectType.checked && objectType.value === "object"){ featuresSelection.style.display = 'none'; objectSelection.style.display = 'block'; + selfdefinedObject.style.display = 'block'; selfdefinedlabelSelection.style.display = 'none'; labelSelection.style.display = 'none'; } else { featuresSelection.style.display = 'block'; objectSelection.style.display = 'none'; + selfdefinedObject.style.display = 'none'; selfdefinedlabelSelection.style.display = 'block'; labelSelection.style.display = 'block'; } }); }); + document.addEventListener('DOMContentLoaded', function() { // Show the ERD canvas showErdCanvas("erdCanvas1", "zoomInBtn1", "zoomOutBtn1", '{{ image1 }}'); @@ -1171,6 +1569,9 @@ makeTerminalResizable(); toggleTerminal(); + // Initialize the data header table + initializeDataHeaderTable(); + // Listen for the 'added' event on the target sortable list UIkit.util.on('#dropped_items', 'added removed', function (event) { var item = event.detail[1]; // Adjusted to access the second item -- GitLab