<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Filipe Oliveira </title>
    <description>The latest articles on DEV Community by Filipe Oliveira  (@fmooliveira).</description>
    <link>https://dev.to/fmooliveira</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F915910%2Fc148d614-2be6-4c1f-b89e-564b105c03a6.jpg</url>
      <title>DEV Community: Filipe Oliveira </title>
      <link>https://dev.to/fmooliveira</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fmooliveira"/>
    <language>en</language>
    <item>
      <title>Building an Enterprise-Ready SonarCloud Dashboard with Streamlit &amp; Azure</title>
      <dc:creator>Filipe Oliveira </dc:creator>
      <pubDate>Fri, 10 Apr 2026 17:37:43 +0000</pubDate>
      <link>https://dev.to/fmooliveira/building-an-enterprise-ready-sonarcloud-dashboard-with-streamlit-azure-3o10</link>
      <guid>https://dev.to/fmooliveira/building-an-enterprise-ready-sonarcloud-dashboard-with-streamlit-azure-3o10</guid>
      <description>&lt;h2&gt;
  
  
  The Catalyst: Chasing KPIs Without an Enterprise License
&lt;/h2&gt;

&lt;p&gt;We’ve all been there: SonarCloud gives you fantastic insights for a single repository, but the moment your engineering manager asks for a "birds-eye view" of security debt and code coverage across 50 microservices, things get messy. &lt;/p&gt;

&lt;p&gt;My journey started exactly there. I needed to provide weekly auditing KPIs, but our organization didn't have access to the built-in PDF reporting reserved for SonarCloud Enterprise plans. I refused to compile these reports manually every Friday. &lt;/p&gt;

&lt;p&gt;What started as a quick, dirty Proof of Concept to pull a few metrics evolved into a full-fledged internal tool. However, taking a Streamlit app from a simple script to an enterprise-grade, performant application introduced unique hurdles—specifically around memory limits, storage coupling, and corporate authentication. &lt;/p&gt;

&lt;p&gt;Here is a deep dive into the architecture, the trade-offs I made, and how I tackled memory bloat and separation of concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Moving Beyond the Monolith: Architectural Trade-offs
&lt;/h2&gt;

&lt;p&gt;Streamlit is famous for fast prototyping, but a single, monolithic &lt;code&gt;app.py&lt;/code&gt; script becomes unmanageable quickly in a production environment. To prioritize maintainability and team collaboration, I adopted a modular folder structure that mimics a traditional MVC (Model-View-Controller) framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design Decision:&lt;/strong&gt; By strictly separating routing (&lt;code&gt;app.py&lt;/code&gt;), business logic (&lt;code&gt;data_service.py&lt;/code&gt;), and presentation (&lt;code&gt;dashboard_view.py&lt;/code&gt;), the application becomes highly testable. The trade-off is a slightly higher initial cognitive load compared to standard Streamlit scripts, but the return on investment for long-term maintainability is immense.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tackling Out-of-Memory (OOM) Errors: Parquet Compression
&lt;/h2&gt;

&lt;p&gt;Retrieving massive datasets—thousands of SonarCloud metric rows spanning months of history—quickly bloats Streamlit's &lt;code&gt;Session State&lt;/code&gt; and RAM usage. In my early PoC, appending historical data for multiple projects often led to container restarts due to memory exhaustion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Optimization:&lt;/strong&gt; Instead of storing raw Pandas DataFrames in memory, I implemented a mechanism to compress DataFrames into &lt;strong&gt;Parquet binaries&lt;/strong&gt; before storing them in Streamlit's state limit. During UI rendering, the active data is rapidly decompressed on the fly, and the uncompressed DataFrame is explicitly deleted from memory the moment it is no longer needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In our main orchestration file (app.py):
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metrics_data_parquet&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Decompress the lightweight binary on the fly
&lt;/span&gt;    &lt;span class="n"&gt;metrics_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decompress_from_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metrics_data_parquet&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;metrics_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Render the UI components
&lt;/span&gt;        &lt;span class="nf"&gt;display_dashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metrics_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;data_project&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 2. Explicitly release the uncompressed DataFrame from memory
&lt;/span&gt;        &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;metrics_data&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Trade-off:&lt;/strong&gt; This introduces slight CPU overhead during decompression, but it completely stabilizes memory consumption, preventing orphaned DataFrames from lingering and ensuring optimal performance under heavy load.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Storage Factory Pattern: Decoupling the Database
&lt;/h2&gt;

&lt;p&gt;I started with Azure Table Storage as the backend, but I wanted to avoid tight coupling. If we ever needed to migrate to PostgreSQL or MongoDB, I didn't want to rewrite the service layer.&lt;/p&gt;

&lt;p&gt;I implemented a &lt;strong&gt;Storage Factory Pattern&lt;/strong&gt; in &lt;code&gt;database/factory.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Python&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The service layer requests a client, oblivious to the underlying Azure implementation
&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_storage_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Later, we pass the storage client into our decoupled data service:
&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metrics_data_parquet&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_metrics_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Beyond future-proofing the application, this abstraction dramatically improves the developer experience. Engineers can run Azurite (a local emulator for Azure Storage) via Docker Compose, entirely bypassing the need for cloud credentials during local feature development.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Securing the Application: Entra ID (Azure AD) Integration
&lt;/h2&gt;

&lt;p&gt;Internal corporate metrics are sensitive. Native Streamlit doesn't offer Single Sign-On (SSO) out of the box, so basic auth wasn't going to cut it.&lt;/p&gt;

&lt;p&gt;I integrated the Microsoft Authentication Library (&lt;code&gt;msal&lt;/code&gt;) paired with &lt;code&gt;streamlit-cookies-manager&lt;/code&gt;. The flow checks for a secure, &lt;code&gt;HttpOnly&lt;/code&gt; auth cookie. If missing, it generates a secure state token and redirects the user through an OAuth2 authorization code flow, mitigating Cross-Site Request Forgery (CSRF) risks. Once authenticated, Microsoft Graph populates their user session.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The "Offline" Demo Mode
&lt;/h2&gt;

&lt;p&gt;One of the biggest friction points in open-source or enterprise tooling is the onboarding phase. Waiting for IT to provision API keys or database access kills momentum.&lt;/p&gt;

&lt;p&gt;To solve this, I built a &lt;code&gt;--demo-mode&lt;/code&gt; flag. It bypasses the MSAL authentication and cloud database dependencies entirely, injecting synthetic, locally-generated SonarCloud data into the UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Generate synthetic data locally&lt;/span&gt;
python src/dashboard/demo/demo_generator.py

&lt;span class="c"&gt;# 2. Run the Streamlit app passing the demo flag&lt;/span&gt;
streamlit run src/dashboard/app.py &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--demo-mode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's Build Together
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lwrzig1cwl6lz1ahgbr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lwrzig1cwl6lz1ahgbr.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;br&gt;
What started as a hacked-together script to appease my manager has evolved into a mature, architecturally sound application. However, there is always room for optimization.&lt;/p&gt;

&lt;p&gt;I am open-sourcing this project and actively looking for community contributions! Whether it is optimizing the async API ingestion, polishing the CSS overrides, or adding new storage backends, I'd love your input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clone the repo at &lt;a href="https://github.com/FmoOliveira/SonarCloudDashboard" rel="noopener noreferrer"&gt;SonarCloudDashboard&lt;/a&gt;, spin up the &lt;code&gt;--demo-mode&lt;/code&gt; in under 30 seconds, and let me know what you think.&lt;/strong&gt; What patterns do you rely on to scale your Python dashboards? Drop a comment or open a PR!&lt;/p&gt;

</description>
      <category>python</category>
      <category>azure</category>
      <category>sonarcloud</category>
      <category>development</category>
    </item>
  </channel>
</rss>
