[RPP Insight] Add preconnect estimate saving table in insight sidebar

This CL adds the code to render the origin <-> Estimate LCP saving table in the sidebar.

screenshot: https://screenshot.googleplex.com/6TTKbRbYS7nW9KJ
Bug: 412974537
Change-Id: I7216b58aad1186a1623cdeb92c6c3ea47ba5de69
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6486626
Reviewed-by: Connor Clark <[email protected]>
Commit-Queue: Connor Clark <[email protected]>
diff --git a/front_end/models/trace/insights/NetworkDependencyTree.ts b/front_end/models/trace/insights/NetworkDependencyTree.ts
index 9229249..0eaa383 100644
--- a/front_end/models/trace/insights/NetworkDependencyTree.ts
+++ b/front_end/models/trace/insights/NetworkDependencyTree.ts
@@ -49,6 +49,27 @@
   columnRequest: 'Request',
   /** Label for a column in a data table; entries will be the time from main document till current network request. */
   columnTime: 'Time',
+  /**
+   * @description Text status indicating that there isn't preconnect candidates.
+   */
+  noPreconnectCandidates: 'No origins are good candidates for preconnecting',
+  /**
+   * @description Title of the table that shows the origins that the page should have preconnected to.
+   */
+  estSavingTableTitle: 'Preconnect candidates',
+  /**
+   * @description Description of the table that recommends preconnecting to the origins to save time.
+   */
+  estSavingTableDescription:
+      'Add [preconnect](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/?utm_source=lighthouse&utm_medium=devtools) hints to your most important origins, but try to use fewer than 4.',
+  /**
+   * @description Label for a column in a data table; entries will be the origin of a web resource
+   */
+  columnOrigin: 'Origin',
+  /**
+   * @description Label for a column in a data table; entries will be the number of milliseconds the user could reduce page load by if they implemented the suggestions.
+   */
+  columnWastedMs: 'Est LCP savings',
 } as const;
 
 const str_ = i18n.i18n.registerUIStrings('models/trace/insights/NetworkDependencyTree.ts', UIStrings);
diff --git a/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts b/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts
index 81b5e06..fd16980 100644
--- a/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts
+++ b/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts
@@ -12,6 +12,7 @@
 import * as Trace from '../../../../models/trace/trace.js';
 import * as Lit from '../../../../ui/lit/lit.js';
 import type * as Overlays from '../../overlays/overlays.js';
+import {md} from '../../utils/Helpers.js';
 
 import {BaseInsightComponent} from './BaseInsightComponent.js';
 import {eventRef} from './EventRef.js';
@@ -135,9 +136,53 @@
     // clang-format on
   }
 
+  #renderEstSavingTable(): Lit.LitTemplate {
+    if (!this.model) {
+      return Lit.nothing;
+    }
+
+    const estSavingTableTitle = html`
+      <style>${networkDependencyTreeInsightStyles.cssText}</style>
+      <div class='section-title'>${i18nString(UIStrings.estSavingTableTitle)}</div>
+      <div class="insight-description">${md(i18nString(UIStrings.estSavingTableDescription))}</div>
+    `;
+
+    if (!this.model.preconnectCandidates.length) {
+      // clang-format off
+      return html`
+        <div class="insight-section">
+          ${estSavingTableTitle}
+          ${i18nString(UIStrings.noPreconnectCandidates)}
+        </div>
+      `;
+      // clang-format on
+    }
+
+    const rows: TableDataRow[] = this.model.preconnectCandidates.map(
+        candidate => ({
+          values: [candidate.origin, i18n.TimeUtilities.millisToString(candidate.wastedMs)],
+        }));
+
+    // clang-format off
+    return html`
+      <div class="insight-section">
+        ${estSavingTableTitle}
+        <devtools-performance-table
+          .data=${{
+            insight: this,
+            headers: [i18nString(UIStrings.columnOrigin), i18nString(UIStrings.columnWastedMs)],
+            rows,
+          } as TableData}>
+        </devtools-performance-table>
+      </div>
+    `;
+    // clang-format on
+  }
+
   override renderContent(): Lit.LitTemplate {
     return html`
       ${this.#renderNetworkTreeSection()}
+      ${this.#renderEstSavingTable()}
     `;
   }
 }
diff --git a/front_end/panels/timeline/components/insights/networkDependencyTreeInsight.css b/front_end/panels/timeline/components/insights/networkDependencyTreeInsight.css
index b1d7eed..161ef56 100644
--- a/front_end/panels/timeline/components/insights/networkDependencyTreeInsight.css
+++ b/front_end/panels/timeline/components/insights/networkDependencyTreeInsight.css
@@ -15,4 +15,9 @@
       color: var(--sys-color-error);
     }
   }
+
+  .section-title{
+    font: var(--sys-typescale-body4-bold);
+    padding-bottom: var(--sys-size-2);
+  }
 }