def schema_selection(db_alias: str): """Asynchronously computes the list of schemas with foreign key constraints""" schemas_with_fk_constraints = schemas_with_foreign_key_constraints(db_alias) if not schemas_with_fk_constraints or len(schemas_with_fk_constraints) == 0: return str(_.i['No schemas with foreign key constraints found']) return ''.join(xml.render([ [_.div(class_='form-check form-check-inline')[ _.label(class_='form-check-label')[ _.input(class_="form-check-input schema-checkbox", type="checkbox", value=schema_name)[ ''], ' ', schema_name]] for schema_name in sorted(schemas_with_fk_constraints)], '   ', _.div(class_='form-check form-check-inline')[ _.label(class_='form-check-label')[ _.input(class_="form-check-input", id='hide-columns-checkbox', type="checkbox")[ ''], ' ', 'hide columns']], '   ', _.div(class_='form-check form-check-inline')[ _.label(class_='form-check-label')[ 'graphviz engine ', _.select(id='engine', style='border:none;background-color:white;')[ [_.option(value=engine)[engine] for engine in ['neato', 'dot', 'twopi', 'fdp']] ]]], _.script[''' var schemaPage = SchemaPage("''' + flask.url_for('mara_db.index_page', db_alias=db_alias) + '''", "''' + db_alias + '''"); ''']]))
def pipeline_children_table(path: str): """Creates a table that documents all child nodes of a table""" pipeline, __ = pipelines.find_node(path.split('/')) assert (isinstance(pipeline, pipelines.Pipeline)) node_durations_and_run_times = node_cost.node_durations_and_run_times(pipeline.path()) rows = [] for node in pipeline.nodes.values(): [avg_duration, avg_run_time] = node_durations_and_run_times.get(tuple(node.path()), ['', '']) rows.append( _.tr[_.td[_.a(href=views.node_url(node))[node.id.replace('_', '_<wbr>')]], _.td[node.description], _.td[views.format_labels(node)], _.td[node_cost.format_duration(avg_duration)], _.td(style='color:#bbb' if avg_duration == avg_run_time else '')[ node_cost.format_duration(avg_run_time)], _.td[node_cost.format_duration( node_cost.compute_cost(node, node_durations_and_run_times))], _.td[(_.input(class_='pipeline-node-checkbox', type='checkbox', value=node.id, name='ids[]', onchange='runButtons.update()') if config.allow_run_from_web_ui() else '')]]) return \ str(_.script['var runButtons = new PipelineRunButtons();']) \ + str(bootstrap.table(['ID', 'Description', '', 'Avg duration', 'Avg run time', 'Cost', ''], rows)) \ + str(_.script['floatMaraTableHeaders();'])
def schema_selection(db_alias: str): """Asynchronously computes the list of schemas with foreign key constraints""" schemas_with_fk_constraints = [] with mara_db.postgresql.postgres_cursor_context(db_alias) as cursor: cursor.execute(''' SELECT array_cat(array_agg(DISTINCT constrained_table_schema.nspname), array_agg(DISTINCT referenced_table_schema.nspname)) FROM pg_constraint JOIN pg_class constrained_table ON constrained_table.oid = pg_constraint.conrelid JOIN pg_namespace constrained_table_schema ON constrained_table.relnamespace = constrained_table_schema.oid JOIN pg_class referenced_table ON referenced_table.oid = pg_constraint.confrelid JOIN pg_namespace referenced_table_schema ON referenced_table.relnamespace = referenced_table_schema.oid''' ) result = cursor.fetchone() if result != (None, ): schemas_with_fk_constraints = sorted(list(set(result[0]))) if len(schemas_with_fk_constraints) == 0: return str(_.i['No schemas with foreign key constraints found']) return ''.join( xml.render( [[ _.div(class_='form-check form-check-inline')[_.label( class_='form-check-label')[ _.input(class_="form-check-input schema-checkbox", type="checkbox", value=schema_name)[''], ' ', schema_name]] for schema_name in sorted(schemas_with_fk_constraints) ], '   ', _.div(class_='form-check form-check-inline')[_.label( class_='form-check-label')[_.input(class_="form-check-input", id='hide-columns-checkbox', type="checkbox")[''], ' ', 'hide columns']], '   ', _.div(class_='form-check form-check-inline')[_.label( class_='form-check-label' )['graphviz engine ', _.select(id='engine', style='border:none;background-color:white;')[[ _.option(value=engine)[engine] for engine in ['neato', 'dot', 'twopi', 'fdp'] ]]]], _.script[''' var schemaPage = SchemaPage("''' + flask.url_for('mara_db.index_page', db_alias=db_alias) + '''", "''' + db_alias + '''"); ''']]))
def data_set_page(data_set_id, query_id): from .data_set import find_data_set ds = find_data_set(data_set_id) if not ds: flask.flash(f'Data set "{data_set_id}" does not exist anymore', category='danger') return flask.redirect(flask.url_for('mara_data_explorer.index_page')) action_buttons = [] action_buttons.append(response.ActionButton(action='javascript:dataSetPage.downloadCSV()', icon='download', label='CSV', title='Download as CSV')) if config.google_sheet_oauth2_client_config(): action_buttons.append(response.ActionButton(action='javascript:dataSetPage.exportToGoogleSheet()', icon='cloud-upload', label='Google sheet', title='Export to a Google sheet')) action_buttons.append(response.ActionButton(action='javascript:dataSetPage.load()', icon='folder-open', label='Load', title='Load previously saved query')) action_buttons.append(response.ActionButton(action='javascript:dataSetPage.save()', icon='save', label='Save', title='Save query')) action_buttons.append(response.ActionButton(action='javascript:dataSetPage.displayQuery()', icon='eye', label='SQL', title='Display query')) if query_id: action_buttons.insert(1, response.ActionButton( action=flask.url_for('mara_data_explorer._delete_query', data_set_id=data_set_id, query_id=query_id), icon='trash', label='Delete', title='Delete query')) return response.Response( title=f'Query "{query_id}" on "{ds.name}"' if query_id else f'New query on "{ds.name}"', html=[_.div(class_='row')[ _.div(class_='col-md-3')[ bootstrap.card(header_left='Query', body=_.div(id='query-details')[html.spinner()]), bootstrap.card(header_left='Columns', header_right=_.a(id='select-all', href='#')[' Select all'], body=[_.div(class_="form-group")[ _.input(type="search", class_="columns-search form-control", value="", placeholder="Filter")], _.div(id='columns-list')[html.spinner()]])], _.div(class_='col-md-9')[ bootstrap.card( id='filter-card', header_left=[_.div(class_="dropdown")[ _.a(**{'class': 'dropdown-toggle', 'data-toggle': 'dropdown', 'href': '#'})[ _.span(class_='fa fa-plus')[' '], ' Add filter'], _.div(class_="dropdown-menu", id='filter-menu')[ _.div(class_="dropdown-item")[ _.input(type="text", class_="columns-search form-control", value="", placeholder="Filter")]]]], fixed_header_height=False, body=_.div(id='filters')[html.spinner()]), bootstrap.card(header_left=_.div(id='row-counts')[html.spinner()], header_right=_.div(id='pagination')[html.spinner()], body=_.div(id='preview')[html.spinner()]), _.div(class_='row', id='distribution-charts')[''] ]], _.script[f""" var dataSetPage = null; document.addEventListener('DOMContentLoaded', function() {{ dataSetPage = DataSetPage('{flask.url_for('mara_data_explorer.index_page')}', {json.dumps( {'data_set_id': data_set_id, 'query_id': query_id, 'query': flask.request.get_json()})}, 15, '{config.charts_color()}'); }}); """], html.spinner_js_function(), _.div(class_='col-xl-4 col-lg-6', id='distribution-chart-template', style='display: none')[ bootstrap.card(header_left=html.spinner(), body=_.div(class_='chart-container google-chart')[ html.spinner()])], _.div(class_='modal fade', id='load-query-dialog', tabindex="-1")[ _.div(class_='modal-dialog', role='document')[ _.div(class_='modal-content')[ _.div(class_='modal-header')[ _.h5(class_='modal-title')['Load query'], _.button(**{'type': "button", 'class': "close", 'data-dismiss': "modal", 'aria-label': "Close"})[ _.span(**{'aria-hidden': 'true'})['×']]], _.div(class_='modal-body', id='query-list')[''] ] ] ], _.div(class_='modal fade', id='display-query-dialog', tabindex="-1")[ _.div(class_='modal-dialog', role='document')[ _.div(class_='modal-content')[ _.div(class_='modal-header')[ _.h5(class_='modal-title')['Query statement'], _.button(**{'type': "button", 'class': "close", 'data-dismiss': "modal", 'aria-label': "Close"})[ _.span(**{'aria-hidden': 'true'})['×']]], _.div(class_='modal-body', id='query-display')[''] ] ] ], _.form(action=flask.url_for('mara_data_explorer.download_csv', data_set_id=data_set_id), method='post')[ _.div(class_="modal fade", id="download-csv-dialog", tabindex="-1")[ _.div(class_="modal-dialog", role='document')[ _.div(class_="modal-content")[ _.div(class_="modal-header")[ _.h5(class_='modal-title')['Download as CSV'], _.button(**{'type': "button", 'class': "close", 'data-dismiss': "modal", 'aria-label': "Close"})[ _.span(**{'aria-hidden': 'true'})['×']]], _.div(class_="modal-body")[ 'Delimiter:  ', _.input(type="radio", value="\t", name="delimiter", checked="checked"), ' tab   ', _.input(type="radio", value=";", name="delimiter"), ' semicolon   ', _.input(type="radio", value=",", name="delimiter"), ' comma   ', _.hr, 'Number format:  ', _.input(type="radio", value=".", name="decimal-mark", checked="checked"), ' 42.7   ', _.input(type="radio", value=",", name="decimal-mark"), ' 42,7   ', _.input(type="hidden", name="query")], _.div(class_="modal-footer")[ _.button(id="csv-download-button", type="submit", class_="btn btn-primary")[ 'Download']]]]]], _.form(action=flask.url_for('mara_data_explorer.oauth2_export_to_google_sheet', data_set_id=data_set_id), method='post', target="_blank")[ _.div(class_="modal fade", id="google-sheet-export-dialog", tabindex="-1")[ _.div(class_="modal-dialog", role='document')[ _.div(class_="modal-content")[ _.div(class_="modal-header")[ _.h5(class_='modal-title')['Google sheet export'], _.button(**{'type': "button", 'class': "close", 'data-dismiss': "modal", 'aria-label': "Close"})[ _.span(**{'aria-hidden': 'true'})['×']]], _.div(class_="modal-body")[ 'Number format:  ', _.input(type="radio", value=".", name="decimal-mark", checked="checked"), ' 42.7   ', _.input(type="radio", value=",", name="decimal-mark"), ' 42,7   ', _.hr, 'Array format:  ', _.input(type="radio", value="curly", name="array-format", checked="checked"), ' {"a", "b"}   ', _.input(type="radio", value="normal", name="array-format"), ' ["a", "b"]   ', _.input(type="radio", value="tuple", name="array-format"), ' ("a", "b")   ', _.hr, 'By clicking Export below:', _.br, _.ul[ _.li['Google authentication will be required.'], _.li['A maximum limit of 100.000 rows will be applied.'], _.li['A maximum limit of 50.000 characters per cell will be applied.'], _.li['A Google sheet with the selected data will be available in a new tab.'] ], _.input(type="hidden", name="query") ], _.div(class_="modal-footer")[ _.button(id="export-to-google-sheet", type="submit", class_="btn btn-primary")[ 'Export']]]]]] ], action_buttons=action_buttons, js_files=['https://www.gstatic.com/charts/loader.js', flask.url_for('mara_data_explorer.static', filename='tagsinput.js'), flask.url_for('mara_data_explorer.static', filename='typeahead.js'), flask.url_for('mara_data_explorer.static', filename='data-sets.js')], css_files=[flask.url_for('mara_data_explorer.static', filename='tagsinput.css'), flask.url_for('mara_data_explorer.static', filename='data-sets.css')])
def data_set_page(id: str) -> response.Response: """Renders the pages for individual data sets""" from ..config import data_sets data_set = next( (data_set for data_set in data_sets() if data_set.id() == id), None) if not data_set: flask.flash(f'Could not find data set "{id}"', category='warning') return flask.redirect(flask.url_for('mara_schema.index_page')) base_url = flask.url_for('mara_schema.data_set_sql_query', id=data_set.id()) def attribute_rows(data_set: DataSet) -> []: rows = [] for path, attributes in data_set.connected_attributes().items(): if path: rows.append(_.tr[_.td( colspan=3, style='border-top:none; padding-top: 20px;' )[[[ '→ ', _.a( href=data_set_url(entity.data_set) )[link_title] if entity.data_set else link_title, ' ' ] for entity, link_title in [( entity_link.target_entity, entity_link.prefix or entity_link.target_entity.name) for entity_link in path]], [' ', _. i[path[-1].description]] if path[-1].description else '']]) for prefixed_name, attribute in attributes.items(): rows.append(_.tr[_.td[escape(prefixed_name)], _.td[_.i[escape( attribute.description )]], _.td[_.tt[escape( f'{path[-1].target_entity.table_name + "." if path else ""}{attribute.column_name}' )]]]) return rows return response.Response( html=[ bootstrap.card( header_left=_.i[escape(data_set.entity.description)], body=[ _.p['Entity table: ', _.code[escape( f'{data_set.entity.schema_name}.{data_set.entity.table_name}' )]], html.asynchronous_content( flask.url_for('mara_schema.data_set_graph', id=data_set.id())), ]), bootstrap.card( header_left='Metrics', body=[ html.asynchronous_content( flask.url_for('mara_schema.metrics_graph', id=data_set.id())), bootstrap.table(['Name', 'Description', 'Computation'], [[ _.tr[_.td[escape(metric.name)], _.td[_.i[escape(metric.description)]], _.td[_.code[escape(metric.display_formula())]]] for metric in data_set.metrics.values() ]]), ]), bootstrap.card(header_left='Attributes', body=bootstrap.table( ["Name", "Description", "Column name"], attribute_rows(data_set))), bootstrap.card( header_left=[ 'Data set sql query: ', [ _.div(class_='form-check form-check-inline') [" ", _.label(class_='form-check-label')[ _.input(class_="form-check-input param-checkbox", type="checkbox", value=param)[''], ' ', param]] for param in [ 'human readable columns', 'pre-computed metrics', 'star schema', 'personal data', 'high cardinality attributes', ] ] ], body=[ _.div(id='sql-container')[html.asynchronous_content( base_url, 'sql-container')], _.script[''' document.addEventListener('DOMContentLoaded', function() { DataSetSqlQuery("''' + base_url + '''"); }); '''] ]) ], title=f'Data set "{data_set.name}"', js_files=[ flask.url_for('mara_schema.static', filename='data-set-sql-query.js') ], )
def data_set_page(data_set_id, query_id): ds = find_data_set(data_set_id) if not ds: flask.flash(f'Data set "{data_set_id}" does not exist anymore', category='danger') return flask.redirect(flask.url_for('data_sets.index_page')) action_buttons = [ response.ActionButton(action='javascript:dataSetPage.downloadCSV()', icon='download', label='CSV', title='Download as CSV'), response.ActionButton(action='javascript:dataSetPage.load()', icon='folder-open', label='Load', title='Load previously saved query'), response.ActionButton(action='javascript:dataSetPage.save()', icon='save', label='Save', title='Save query') ] if query_id: action_buttons.insert( 1, response.ActionButton(action=flask.url_for( 'data_sets._delete_query', data_set_id=data_set_id, query_id=query_id), icon='trash', label='Delete', title='Delete query')) return response.Response( title=f'Query "{query_id}" on "{ds.name}"' if query_id else f'New query on "{ds.name}"', html=[ _.div( class_='row')[_.div( class_='col-md-3' )[bootstrap.card(header_left='Query', body=_.div( id='query-details')[html.spinner()]), bootstrap. card(header_left='Columns', body=[ _.div( class_="form-group" )[_.input(type="search", class_="columns-search form-control", value="", placeholder="Filter")], _.div(id='columns-list')[html.spinner()] ])], _.div(class_='col-md-9')[bootstrap.card( id='filter-card', header_left=[ _.div(class_="dropdown")[_.a( **{ 'class': 'dropdown-toggle', 'data-toggle': 'dropdown', 'href': '#' } )[_.span( class_='fa fa-plus')[' '], ' Add filter'], _. div(class_= "dropdown-menu", id= 'filter-menu' ) [_.div( class_ ="dropdown-item" )[_.input( type ="text", class_= "columns-search form-control", value="", placeholder= "Filter")]]] ], fixed_header_height=False, body=_.div(id='filters')[html.spinner()]), bootstrap. card(header_left=_.div( id='row-counts' )[html.spinner()], header_right=_.div( id='pagination' )[html.spinner()], body=_.div( id='preview' )[html.spinner()]), _. div(class_='row', id= 'distribution-charts' )['']]], _.script[f""" var dataSetPage = null; document.addEventListener('DOMContentLoaded', function() {{ dataSetPage = DataSetPage('{flask.url_for('data_sets.index_page')}', {json.dumps({'data_set_id': data_set_id, 'query_id': query_id, 'query': flask.request.get_json()})}, 15, '{config.charts_color()}'); }}); """], html.spinner_js_function(), _.div(class_='col-xl-4 col-lg-6', id='distribution-chart-template', style='display: none')[bootstrap.card( header_left=html.spinner(), body=_.div(class_='chart-container google-chart')[ html.spinner()])], _.div( class_='modal fade', id='load-query-dialog', tabindex="-1")[_.div( class_='modal-dialog', role='document')[_.div( class_='modal-content')[_.div(class_='modal-header')[ _.h5(class_='modal-title')['Load query'], _.button( **{ 'type': "button", 'class': "close", 'data-dismiss': "modal", 'aria-label': "Close" })[_.span( **{'aria-hidden': 'true'})['×']]], _.div(class_='modal-body', id='query-list')['']]]], _.form( action=flask.url_for('data_sets.download_csv'), method='post')[_.div( class_="modal fade", id="download-csv-dialog", tabindex="-1" )[_.div(class_="modal-dialog", role='document')[_.div( class_="modal-content" )[_.div(class_="modal-header") [_.h5(class_='modal-title')['Download as CSV'], _.button( **{ 'type': "button", 'class': "close", 'data-dismiss': "modal", 'aria-label': "Close" })[_.span( **{'aria-hidden': 'true'})['×']]], _.div(class_="modal-body")[ 'Delimiter:  ', _.input(type="radio", value="\t", name="delimiter", checked="checked"), ' tab   ', _.input(type="radio", value=";", name="delimiter"), ' semicolon   ', _.input(type="radio", value=",", name="delimiter"), ' comma   ', _.hr, 'Number format:  ', _.input(type="radio", value=".", name="decimal-mark", checked="checked"), ' 42.7   ', _.input(type="radio", value=",", name="decimal-mark"), ' 42,7   ', _.input(type="hidden", name="query")], _.div(class_="modal-footer" )[_.button(id="csv-download-button", type="submit", class_="btn btn-primary")['Download']]]]]] ], action_buttons=action_buttons, js_files=[ 'https://www.gstatic.com/charts/loader.js', flask.url_for('data_sets.static', filename='tagsinput.js'), flask.url_for('data_sets.static', filename='typeahead.js'), flask.url_for('data_sets.static', filename='data-sets.js') ], css_files=[ flask.url_for('data_sets.static', filename='tagsinput.css'), flask.url_for('data_sets.static', filename='data-sets.css') ])