# Copyright (C) 2022 The Qt Company Ltd. # Contact: https://www.qt.io/licensing/ # # You may use this file under the terms of the CC0 license. # See the file LICENSE.CC0 from this package for details. import re import sys from pathlib import Path import pandas as pd from dash import Dash, Input, Output, callback, dcc, html from data_utils import ( download_data, get_collab_chart_data, get_commit_chart_data, get_domain_chart_data, get_ranking_chart_data, ) from web_utils import get_column, get_filter, get_header, get_file_content, encoded_img from quips_utils import resolve_quips_url, get_quip_html, get_quip_number # Main application app = Dash(__name__, suppress_callback_exceptions=True) server = app.server app.title = "The Qt Project" @callback( [ Output("commits-chart", "figure"), Output("collaborators-chart", "figure"), Output("files-changed-chart", "figure"), Output("commit-count-chart", "figure"), Output("insertions-chart", "figure"), Output("deletions-chart", "figure"), Output("domain-chart", "figure"), ], [ Input("module-filter", "value"), Input("year-filter", "value"), Input("contributors-filter", "value"), Input("tqtc-filter", "value"), ], ) def update_charts(module, year, contributors, tqtc): """ This function is in charge of updating the data for all the charts, and the connection is done by the chart-id from each. As Input, we get the values from the combobox: 'module', and 'year', and then we filter the main dataframe, to re-generate the data for all the different plots. """ df = data[module][data[module]["datetime"].dt.year >= int(year)] if "TQtC" not in tqtc: df = df[df.domain != "qt"] commit_data = get_commit_chart_data(df) collab_data = get_collab_chart_data(df) files_changed_data = get_ranking_chart_data(df, top=contributors, column="files_changed") commit_count_data = get_ranking_chart_data(df, top=contributors, column="commit_count") insertions_data = get_ranking_chart_data(df, top=contributors, column="insertions") deletions_data = get_ranking_chart_data(df, top=contributors, column="deletions") domain_data = get_domain_chart_data(df) return ( commit_data, collab_data, files_changed_data, commit_count_data, insertions_data, deletions_data, domain_data, ) def get_structured_data(): # Loading all the data print("Loading the CSV files") modules = [] years = set() data = {} csv_files_count = 0 for f in Path("data").glob("*.csv"): if "email" not in f.name: data[f.stem] = pd.read_csv(f.relative_to(Path(".")), sep=";") data[f.stem]["datetime"] = pd.to_datetime(data[f.stem]["date"], format="%Y-%m-%d") data[f.stem].sort_values("datetime", inplace=True) data[f.stem]["date_week"] = data[f.stem]["datetime"].apply( lambda x: f"{x.year} W{str(x.week).zfill(3)}" ) modules.append(f.stem) years.update(set(data[f.stem]["datetime"].dt.year)) csv_files_count += 1 print(f"Loading the CSV files: [DONE] ({csv_files_count} files)") # Generate entry with all the content data["All Qt"] = pd.concat([i for _, i in data.items()], ignore_index=True) modules.append("All Qt") modules = sorted(modules) years = [i for i in sorted(years)] return data, modules, years # Download data if not download_data(): sys.exit(-1) # Parse data print("Parsing data") data, modules, years = get_structured_data() print("Parsing data: [DONE]") # Reading 'markdown' files main_left_boxes = [ get_file_content("docs/learn_more.md"), get_file_content("docs/get_in_touch.md"), get_file_content("docs/events.md", color="#e7e8ee"), ] main_right_boxes = [ get_file_content("docs/contribute.md"), ] data_left_boxes = html.Div( children=[ get_filter(modules, years), html.Div( children=[ html.Div( children=dcc.Graph(id="files-changed-chart"), className="six columns card", ), html.Div( children=dcc.Graph(id="commit-count-chart"), className="six columns card", ), ], className="row", ), html.Div( children=[ html.Div( children=dcc.Graph(id="insertions-chart"), className="six columns card", ), html.Div( children=dcc.Graph(id="deletions-chart"), className="six columns card", ), ], className="row", ), ], className="six columns", ) data_right_boxes = html.Div( children=[ html.Div( children=dcc.Graph(id="commits-chart"), className="card", ), html.Div( children=dcc.Graph(id="collaborators-chart"), className="card", ), html.Div( children=dcc.Graph(id="domain-chart"), className="card", ), ], className="six columns", ) # Main Layout app.layout = html.Div( [ dcc.Location(id="url", refresh=False), html.Div(id="page-content"), ] ) card_row = html.Div( children=[ # ... html.Div( children=[ html.H4("Modules Data", className="semi-bold"), html.P( "How Qt has been evolving? Check commits, authors, and companies contributions." ), html.A( html.Button( "Data", className="bigbutton", ), href="/https/code.qt.io/data", ), ], className="four columns card main-card", ), # ... html.Div( children=[ html.H4("Contribution Guidelines", className="semi-bold"), html.P( "Read about all the guidelines in each of the ways you can contribute to Qt." ), html.A( html.Button( "Guidelines", className="bigbutton", ), href="/https/code.qt.io/guidelines", ), ], className="four columns card main-card", ), # ... html.Div( children=[ html.H4("QUIPs", className="semi-bold"), html.P("Check the Qt's Utilitarian Improvement Process documents!"), html.A( html.Button( "QUIPs", className="bigbutton", ), href="/https/code.qt.io/quips", ), ], className="four columns card main-card", ), # ... ], className="row", ) index_page = html.Div( children=[ get_header(), html.Div( children=[ card_row, html.Div( children=[], style={"height": "5px"}, className="row", ), html.Div( children=[ get_column(divs=main_left_boxes, columns_number="four"), get_column(divs=main_right_boxes, columns_number="eight"), ], className="row", ), ], className="wrapper", ), html.Div( children=[ html.A( children=[ html.Img(src=f"data:image/svg+xml;base64,{encoded_img['mastodon']}", style={"height": "32px"}) ], rel="me", href="https://floss.social/@qt", style={"margin": "5px"}, ), html.A( children=[ html.Img(src=f"data:image/svg+xml;base64,{encoded_img['bluesky']}", style={"height": "32px"}) ], rel="me", href="https://bsky.app/profile/qt-project.org", style={"margin": "5px"}, ), html.A( children=[ html.Img(src=f"data:image/svg+xml;base64,{encoded_img['x']}", style={"height": "32px"}) ], rel="me", href="https://twitter.com/qtproject", style={"margin": "5px"}, ), ], className="footer", ), ], ) page_data_layout = html.Div( children=[ get_header(), html.Div( children=[ html.Div( children=[ data_left_boxes, data_right_boxes, ], className="row", ), ], className="wrapper", ), ], ) print("Reading QUIPS") QUIPS_HTML_DIR = Path("../quipshtml/") page_quip_layout = { get_quip_number(str(f)): get_quip_html(f) for f in QUIPS_HTML_DIR.glob("quip-*.html") } print("Reading QUIPS: [DONE]") page_guidelines_layout = html.Div( [ get_header(), html.Div( children=[], style={"height": "10px"}, className="row", ), html.Div( children=[ get_column( divs=[ get_file_content("docs/guidelines.md", color="#e7e8ee"), get_file_content("docs/guidelines-community-work.md"), get_file_content("docs/guidelines-write-code.md"), get_file_content("docs/guidelines-spread-the-word.md"), ], columns_number="five", ), get_column( divs=[ get_file_content("docs/governance-model.md", color="#caf3ce"), get_file_content("docs/guidelines-report-bugs.md"), get_file_content("docs/guidelines-support-users.md"), get_file_content("docs/guidelines-write-docs.md"), ], columns_number="five", ), ], className="row justify-center", ), ] ) print("Ready") @callback(Output("page-content", "children"), [Input("url", "pathname")]) def display_page(pathname): if pathname == "/data": return page_data_layout elif pathname == "/guidelines": return page_guidelines_layout elif re.match("/quips[/]?$", pathname): if 0 in page_quip_layout: return page_quip_layout[0] else: return index_page elif resolve_quips_url(page_quip_layout.keys(), pathname): return page_quip_layout[int(pathname.replace("/quips/", ""))] else: return index_page if __name__ == "__main__": app.run_server(debug=False, threaded=True)