From 0222dadc97ac43904332e5e4bad4c8b967fb0bbe Mon Sep 17 00:00:00 2001
From: Janpot <2109932+Janpot@users.noreply.github.com>
Date: Wed, 10 Jun 2026 16:24:24 +0200
Subject: [PATCH 1/8] Migrate Query OSS Insight to dashboard, deprecate unused
Toolpad pages
Migrates the queryOssInsight tool from the Toolpad app to code-infra-dashboard
as a new /query-oss-insight page backed by a server-side /api/oss-insight proxy
(OSS Insight does not send CORS headers for our origin).
Deprecates auditClosedIssues, auditLabelChanges, communityCore and
communityPerMonth: their Toolpad pages are stripped to a notice (keeping their
aliases so old URLs don't 404) and the now-orphaned backend functions and
resource files are removed.
---
.../(dashboard)/query-oss-insight/page.tsx | 9 +
.../app/api/oss-insight/route.ts | 54 ++++
.../src/views/Landing.tsx | 7 +
.../src/views/QueryOssInsight.tsx | 150 +++++++++
.../toolpad/pages/auditClosedIssues/page.yml | 63 +---
.../toolpad/pages/auditLabelChanges/page.yml | 64 +---
.../toolpad/pages/communityCore/page.yml | 288 +----------------
.../toolpad/pages/communityPerMonth/page.yml | 163 +---------
.../toolpad/pages/queryOssInsight/page.yml | 72 +----
.../toolpad/resources/functions.ts | 293 ------------------
.../resources/queryAuditClosedIssues.ts | 101 ------
.../resources/queryAuditLabelChanges.ts | 133 --------
.../toolpad/resources/queryOssInsight.ts | 18 --
13 files changed, 247 insertions(+), 1168 deletions(-)
create mode 100644 apps/code-infra-dashboard/app/(dashboard)/query-oss-insight/page.tsx
create mode 100644 apps/code-infra-dashboard/app/api/oss-insight/route.ts
create mode 100644 apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
delete mode 100644 apps/tools-public/toolpad/resources/queryAuditClosedIssues.ts
delete mode 100644 apps/tools-public/toolpad/resources/queryAuditLabelChanges.ts
delete mode 100644 apps/tools-public/toolpad/resources/queryOssInsight.ts
diff --git a/apps/code-infra-dashboard/app/(dashboard)/query-oss-insight/page.tsx b/apps/code-infra-dashboard/app/(dashboard)/query-oss-insight/page.tsx
new file mode 100644
index 000000000..1cdc86832
--- /dev/null
+++ b/apps/code-infra-dashboard/app/(dashboard)/query-oss-insight/page.tsx
@@ -0,0 +1,9 @@
+import * as React from 'react';
+import type { Metadata } from 'next';
+import QueryOssInsight from '@/views/QueryOssInsight';
+
+export const metadata: Metadata = { title: 'Query OSS Insight' };
+
+export default function QueryOssInsightPage() {
+ return ;
+}
diff --git a/apps/code-infra-dashboard/app/api/oss-insight/route.ts b/apps/code-infra-dashboard/app/api/oss-insight/route.ts
new file mode 100644
index 000000000..03b59cf56
--- /dev/null
+++ b/apps/code-infra-dashboard/app/api/oss-insight/route.ts
@@ -0,0 +1,54 @@
+import { type NextRequest, NextResponse } from 'next/server';
+
+const OSS_INSIGHT_ORIGIN = 'https://api.ossinsight.io';
+
+export async function GET(request: NextRequest) {
+ const slug = request.nextUrl.searchParams.get('slug');
+
+ if (!slug) {
+ return NextResponse.json({ error: 'Missing required parameter: slug' }, { status: 400 });
+ }
+
+ const response = await fetch(`${OSS_INSIGHT_ORIGIN}/gh/repo/${slug}`, {
+ next: { revalidate: 3600 },
+ });
+
+ if (!response.ok) {
+ return NextResponse.json(
+ { error: `OSS Insight returned ${response.status} for ${slug}` },
+ { status: response.status },
+ );
+ }
+
+ const json = await response.json();
+ return NextResponse.json({ id: json.data.id });
+}
+
+export async function POST(request: NextRequest) {
+ const body = await request.json();
+ const { repositoryId, sql } = body;
+
+ if (!repositoryId || !sql) {
+ return NextResponse.json(
+ { error: 'Missing required parameters: repositoryId and sql' },
+ { status: 400 },
+ );
+ }
+
+ const response = await fetch(`${OSS_INSIGHT_ORIGIN}/q/playground`, {
+ method: 'POST',
+ headers: { 'content-type': 'application/json' },
+ body: JSON.stringify({ type: 'repo', sql, id: repositoryId }),
+ });
+
+ if (!response.ok) {
+ const detail = (await response.text()).slice(0, 500);
+ return NextResponse.json(
+ { error: `OSS Insight returned ${response.status}: ${detail}` },
+ { status: response.status },
+ );
+ }
+
+ const json = await response.json();
+ return NextResponse.json({ rows: json.data });
+}
diff --git a/apps/code-infra-dashboard/src/views/Landing.tsx b/apps/code-infra-dashboard/src/views/Landing.tsx
index 93d69b7ec..5a929cdde 100644
--- a/apps/code-infra-dashboard/src/views/Landing.tsx
+++ b/apps/code-infra-dashboard/src/views/Landing.tsx
@@ -19,6 +19,7 @@ import DownloadIcon from '@mui/icons-material/Download';
import FindInPageIcon from '@mui/icons-material/FindInPage';
import SpeedIcon from '@mui/icons-material/Speed';
import PeopleIcon from '@mui/icons-material/People';
+import StorageIcon from '@mui/icons-material/Storage';
import Link from '@mui/material/Link';
import CardActionArea from '@mui/material/CardActionArea';
import Heading from '../components/Heading';
@@ -87,6 +88,12 @@ const tools: Tool[] = [
icon: ,
path: '/mui-about',
},
+ {
+ name: 'Query OSS Insight',
+ description: 'Run arbitrary SQL against the OSS Insight playground for a GitHub repository.',
+ icon: ,
+ path: '/query-oss-insight',
+ },
];
export default function Landing() {
diff --git a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
new file mode 100644
index 000000000..d9732fbb5
--- /dev/null
+++ b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
@@ -0,0 +1,150 @@
+'use client';
+
+import * as React from 'react';
+import { useMutation, useQuery } from '@tanstack/react-query';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import NoSsr from '@mui/material/NoSsr';
+import TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+import { DataGridPremium, type GridColDef } from '@mui/x-data-grid-premium';
+import Heading from '../components/Heading';
+import ErrorDisplay from '../components/ErrorDisplay';
+import { useSearchParamsState } from '../hooks/useSearchParamsState';
+import { fetchJson } from '../utils/http';
+
+interface RepoDetails {
+ id: number;
+}
+
+type QueryRow = Record;
+
+// Injected on each grid row to give DataGrid a stable id; excluded from the
+// generated columns since those derive from the raw query result keys.
+const ROW_ID = '__rowIndex';
+
+interface QueryResult {
+ rows: QueryRow[];
+}
+
+async function runQuery(repositoryId: number, sql: string): Promise {
+ const response = await fetch('/api/oss-insight', {
+ method: 'POST',
+ headers: { 'content-type': 'application/json' },
+ body: JSON.stringify({ repositoryId, sql }),
+ });
+ if (!response.ok) {
+ const body = await response.json().catch(() => null);
+ throw new Error(body?.error ?? `HTTP ${response.status}: ${response.statusText}`);
+ }
+ return response.json();
+}
+
+export default function QueryOssInsight() {
+ const [searchParams, setSearchParams] = useSearchParamsState(
+ { slug: { defaultValue: 'mui/material-ui' } },
+ { replace: true },
+ );
+ const { slug } = searchParams;
+
+ const [sql, setSql] = React.useState('');
+
+ const repoQuery = useQuery({
+ queryKey: ['oss-insight-repo', slug],
+ queryFn: () => fetchJson(`/api/oss-insight?${new URLSearchParams({ slug })}`),
+ enabled: Boolean(slug),
+ staleTime: 5 * 60 * 1000,
+ retry: false,
+ });
+
+ const repositoryId = repoQuery.data?.id ?? null;
+
+ const mutation = useMutation({
+ mutationFn: () => runQuery(repositoryId!, sql),
+ });
+
+ const rows = React.useMemo(() => mutation.data?.rows ?? [], [mutation.data]);
+
+ const gridRows = React.useMemo(
+ () => rows.map((row, index) => ({ ...row, [ROW_ID]: index })),
+ [rows],
+ );
+
+ const columns = React.useMemo(
+ () =>
+ rows.length > 0
+ ? Object.keys(rows[0]).map((field) => ({ field, flex: 1, minWidth: 120 }))
+ : [],
+ [rows],
+ );
+
+ return (
+
+ Query OSS Insight
+
+ Run arbitrary SQL against the{' '}
+
+ OSS Insight
+ {' '}
+ playground for a GitHub repository.
+
+
+ setSearchParams({ slug: event.target.value })}
+ sx={{ minWidth: 280 }}
+ />
+
+ {repositoryId !== null ? `Repository ID: ${repositoryId}` : 'Not found'}
+
+
+ setSql(event.target.value)}
+ multiline
+ minRows={4}
+ fullWidth
+ slotProps={{ htmlInput: { style: { fontFamily: 'monospace' } } }}
+ sx={{ mb: 2 }}
+ />
+
+
+
+ {mutation.isError ? (
+
+ ) : null}
+
+ {/* Remove once https://github.com/mui/mui-x/issues/17077 is fixed */}
+
+ row[ROW_ID] as number}
+ loading={mutation.isPending}
+ density="compact"
+ disableRowSelectionOnClick
+ sx={{ height: '100%' }}
+ />
+
+
+
+ );
+}
diff --git a/apps/tools-public/toolpad/pages/auditClosedIssues/page.yml b/apps/tools-public/toolpad/pages/auditClosedIssues/page.yml
index 53d97cb6f..ac8052f14 100644
--- a/apps/tools-public/toolpad/pages/auditClosedIssues/page.yml
+++ b/apps/tools-public/toolpad/pages/auditClosedIssues/page.yml
@@ -3,69 +3,16 @@
apiVersion: v1
kind: page
spec:
- displayName: Audit closed issues
+ title: Audit closed Issue
alias:
- xj43hyd
- title: Audit closed Issue
+ displayName: Audit closed issues
content:
- - component: PageRow
- name: pageRow1
- children:
- - component: Text
- name: text
- layout:
- columnSize: 0.6565656565656566
- verticalAlign: center
- props:
- value: 'Filter for GitHub slug:'
- - component: TextField
- name: gitHubSlug
- layout:
- columnSize: 1.3434343434343434
- props:
- label: GitHub slug
- defaultValue: linear
- fullWidth: true
- component: Text
- name: text1
+ name: text
layout:
columnSize: 1
props:
mode: markdown
- value: Find issues closed by a slug.
- - component: DataGrid
- name: dataGrid
- layout:
- columnSize: 1
- height: 548
- props:
- rows:
- $$jsExpression: >-
- queryAuditClosedIssues.rows
- .filter((issue) => {
- return issue.timelineItems.some((event) => event.actor === gitHubSlug.value)
- })
- .map((issue) => ({
- ...issue,
- closedAt: issue.timelineItems[0].createdAt,
- }))
- columns:
- - field: title
- type: string
- width: 279
- headerName: Title
- - field: url
- type: link
- width: 362
- headerName: URL
- - field: closedAt
- type: string
- width: 200
- headerName: Closed at
- height: 480
- queries:
- - name: queryAuditClosedIssues
- mode: query
- query:
- function: queryAuditClosedIssues.ts#queryAuditClosedIssues
- kind: local
+ value: This page is deprecated and no longer maintained.
+ display: shell
diff --git a/apps/tools-public/toolpad/pages/auditLabelChanges/page.yml b/apps/tools-public/toolpad/pages/auditLabelChanges/page.yml
index 445f83f46..617ace944 100644
--- a/apps/tools-public/toolpad/pages/auditLabelChanges/page.yml
+++ b/apps/tools-public/toolpad/pages/auditLabelChanges/page.yml
@@ -3,70 +3,16 @@
apiVersion: v1
kind: page
spec:
- displayName: Audit label activity
+ title: Audit label activity
alias:
- xj43hyd
- title: Audit label activity
+ displayName: Audit label activity
content:
- - component: PageRow
- name: pageRow1
- children:
- - component: Text
- name: text
- layout:
- columnSize: 0.6565656565656566
- verticalAlign: center
- props:
- value: 'Filter for GitHub slub:'
- - component: TextField
- name: gitHubSlug
- layout:
- columnSize: 1.3434343434343434
- props:
- label: GitHub slug
- defaultValue: zannager
- fullWidth: true
- component: Text
- name: text1
+ name: text
layout:
columnSize: 1
props:
mode: markdown
- value: "Build for:
- https://www.notion.so/mui-org/GitHub-community-issues-PRs-Tier-1-12a8\
- 4fdf50e44595afc55343dac00fca#0711365e6f2343bfbbb0c9c78bb2bc8d."
- - component: DataGrid
- name: dataGrid
- layout:
- columnSize: 1
- height: 548
- props:
- rows:
- $$jsExpression: >
- queryAuditLabelChanges.rows
- .filter((issue) => {
- return issue.timelineItems.some((event) => event.actor === gitHubSlug.value)
- })
- .map((issue) => ({
- ...issue,
- timelineItems: issue.timelineItems.map((event) => event.label),
- }))
- columns:
- - field: title
- type: string
- width: 321
- headerName: Title
- - field: url
- type: link
- width: 341
- headerName: URL
- - field: timelineItems
- type: json
- width: 515
- headerName: Labels
- height: 480
- queries:
- - name: queryAuditLabelChanges
- query:
- function: queryAuditLabelChanges.ts#queryAuditLabelChanges
- kind: local
+ value: This page is deprecated and no longer maintained.
+ display: shell
diff --git a/apps/tools-public/toolpad/pages/communityCore/page.yml b/apps/tools-public/toolpad/pages/communityCore/page.yml
index 91ffc308e..f4626b331 100644
--- a/apps/tools-public/toolpad/pages/communityCore/page.yml
+++ b/apps/tools-public/toolpad/pages/communityCore/page.yml
@@ -4,291 +4,15 @@ apiVersion: v1
kind: page
spec:
title: Community Core
+ alias:
+ - 9r8fshsf
+ displayName: Community Core
content:
- - component: DataGrid
- name: DataGrid
- layout:
- columnSize: 1
- props:
- rows:
- $$jsExpression: >
- PRsOpenandReviewedQuery.data.map((item) => ({
- ...item, // use the spread operator to copy existing properties
- ratio: Math.round((item.reviewed * 100) / item.opened) / 100, // add a new property to each object
- }))
- columns:
- - field: event_month
- type: string
- width: 105
- - field: reviewed_by
- type: string
- width: 165
- - field: reviewed
- type: number
- width: 138
- - field: opened
- type: number
- width: 151
- - field: ratio
- type: number
- width: 141
- component: Text
name: text
layout:
columnSize: 1
props:
- value: Community PRs reviews
- - component: Chart
- name: reviews
- layout:
- columnSize: 1
- props:
- data:
- - kind: line
- label: michaldudak
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "michaldudak")
- xKey: event_month
- yKey: reviewed
- color: '#1976d2'
- - kind: line
- label: mnajdova
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "mnajdova")
- xKey: event_month
- yKey: reviewed
- color: '#9c27b0'
- - label: siriwatknp
- kind: line
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "siriwatknp")
- color: '#e91e63'
- xKey: event_month
- yKey: reviewed
- - label: mj12albert
- kind: line
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "mj12albert")
- color: '#009688'
- xKey: event_month
- yKey: reviewed
- - label: DiegoAndai
- kind: line
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "DiegoAndai")
- color: '#ff5722'
- xKey: event_month
- yKey: reviewed
- - label: brijeshb42
- kind: line
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "brijeshb42")
- color: '#ff9800'
- xKey: event_month
- yKey: reviewed
- - component: Text
- name: text1
- layout:
- columnSize: 1
- props:
- value: PRs created
- - component: Chart
- name: reviews1
- layout:
- columnSize: 1
- props:
- data:
- - kind: line
- label: michaldudak
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "michaldudak")
- xKey: event_month
- yKey: opened
- color: '#1976d2'
- - kind: line
- label: mnajdova
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "mnajdova")
- xKey: event_month
- yKey: opened
- color: '#9c27b0'
- - label: siriwatknp
- kind: line
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "siriwatknp")
- color: '#e91e63'
- xKey: event_month
- yKey: opened
- - label: mj12albert
- kind: line
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "mj12albert")
- color: '#009688'
- xKey: event_month
- yKey: opened
- - label: DiegoAndai
- kind: line
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "DiegoAndai")
- color: '#ff5722'
- xKey: event_month
- yKey: opened
- - label: brijeshb42
- kind: line
- data:
- $$jsExpression: |
- [...PRsOpenandReviewedQuery.data]
- .reverse()
- .filter((entry) => entry.reviewed_by === "brijeshb42")
- color: '#ff9800'
- xKey: event_month
- yKey: opened
- - component: Text
- name: text2
- layout:
- columnSize: 1
- props:
- value: Community support ratio
- - component: Chart
- name: reviews2
- layout:
- columnSize: 1
- props:
- data:
- - kind: line
- label: michaldudak
- data:
- $$jsExpression: >
- [...PRsOpenandReviewedQuery.data]
- .map((entry) => ({ ...entry, ratio: entry.reviewed / entry.opened }))
- .reverse()
- .filter((entry) => entry.reviewed_by === "michaldudak")
- xKey: event_month
- yKey: ratio
- color: '#1976d2'
- - kind: line
- label: mnajdova
- data:
- $$jsExpression: >
- [...PRsOpenandReviewedQuery.data]
- .map((entry) => ({ ...entry, ratio: entry.reviewed / entry.opened }))
- .reverse()
- .filter((entry) => entry.reviewed_by === "mnajdova")
- xKey: event_month
- yKey: ratio
- color: '#9c27b0'
- - label: siriwatknp
- kind: line
- data:
- $$jsExpression: >
- [...PRsOpenandReviewedQuery.data]
- .map((entry) => ({ ...entry, ratio: entry.reviewed / entry.opened }))
- .reverse()
- .filter((entry) => entry.reviewed_by === "siriwatknp")
- color: '#e91e63'
- xKey: event_month
- yKey: ratio
- - label: mj12albert
- kind: line
- data:
- $$jsExpression: >
- [...PRsOpenandReviewedQuery.data]
- .map((entry) => ({ ...entry, ratio: entry.reviewed / entry.opened }))
- .reverse()
- .filter((entry) => entry.reviewed_by === "mj12albert")
- color: '#009688'
- xKey: event_month
- yKey: ratio
- - label: DiegoAndai
- kind: line
- data:
- $$jsExpression: >
- [...PRsOpenandReviewedQuery.data]
- .map((entry) => ({ ...entry, ratio: entry.reviewed / entry.opened }))
- .reverse()
- .filter((entry) => entry.reviewed_by === "DiegoAndai")
- color: '#ff5722'
- xKey: event_month
- yKey: ratio
- - label: brijeshb42
- kind: line
- data:
- $$jsExpression: >
- [...PRsOpenandReviewedQuery.data]
- .map((entry) => ({ ...entry, ratio: entry.reviewed / entry.opened }))
- .reverse()
- .filter((entry) => entry.reviewed_by === "brijeshb42")
- color: '#ff9800'
- xKey: event_month
- yKey: ratio
- - component: Chart
- name: chart
- layout:
- columnSize: 1
- props:
- data:
- - kind: line
- label: pr_community_count
- data:
- $$jsExpression: |
- PrsPerMonth.data
- xKey: event_month
- yKey: pr_community_count
- color: '#7cb342'
- - kind: line
- label: pr_maintainers_count
- data:
- $$jsExpression: |
- PrsPerMonth.data
- xKey: event_month
- yKey: pr_maintainers_count
- color: '#27aeef'
- height: 300
- queries:
- - name: PRsOpenandReviewedQuery
- query:
- function: PRsOpenandReviewedQuery
- kind: local
- - name: PrsPerMonth
- query:
- function: functions.ts#PRsPerMonth
- kind: local
- parameters:
- - name: repositoryId
- value: '23083156'
- alias:
- - 9r8fshsf
- displayName: Community Core
+ mode: markdown
+ value: This page is deprecated and no longer maintained.
+ display: shell
diff --git a/apps/tools-public/toolpad/pages/communityPerMonth/page.yml b/apps/tools-public/toolpad/pages/communityPerMonth/page.yml
index c35f040f6..22bf53e27 100644
--- a/apps/tools-public/toolpad/pages/communityPerMonth/page.yml
+++ b/apps/tools-public/toolpad/pages/communityPerMonth/page.yml
@@ -4,166 +4,15 @@ apiVersion: v1
kind: page
spec:
title: Community per month
+ alias:
+ - ck33hgb
+ displayName: Community per month
content:
- - component: TextField
- name: slug
- layout:
- columnSize: 1
- props:
- label: Repository slug
- defaultValue: mui/material-ui
- component: Text
name: text
layout:
columnSize: 1
props:
- value:
- $$jsExpression: |
- (() => {
- if (getRepositoryDetails.data) {
- return `Repository ID: ${getRepositoryDetails.data.data.id}`
- } else {
- return "Not found"
- }
- })()
- - component: Text
- name: text1
- layout:
- columnSize: 1
- props:
- value: 'Community: PRs merged per month'
- variant: h6
- sx:
- mt: 2
- - component: Chart
- name: chart
- layout:
- columnSize: 1
- props:
- data:
- - kind: line
- label: pr_community_count
- data:
- $$jsExpression: >
- PRsPerMonth.rows.map((row) => ({
- ...row,
- ratio:
- Math.round((row.pr_community_count / row.pr_maintainers_count) * 1000) / 10,
- }))
- xKey: event_month
- yKey: pr_community_count
- color: '#7cb342'
- - kind: line
- label: pr_maintainers_count
- data:
- $$jsExpression: >
- PRsPerMonth.rows.map((row) => ({
- ...row,
- ratio:
- Math.round((row.pr_community_count / row.pr_maintainers_count) * 1000) / 10,
- }))
- xKey: event_month
- yKey: pr_maintainers_count
- color: '#27aeef'
- - kind: line
- label: ratio
- data:
- $$jsExpression: >
- PRsPerMonth.rows.map((row) => ({
- ...row,
- ratio:
- Math.round((row.pr_community_count / row.pr_maintainers_count) * 1000) / 10,
- }))
- xKey: event_month
- yKey: ratio
- color: '#ea5545'
- height: 300
- - component: Text
- name: text2
- layout:
- columnSize: 1
- props:
- value: 'Community: Unique contributors per month'
- variant: h6
- sx:
- mt: 2
- - component: Chart
- name: chart1
- layout:
- columnSize: 1
- props:
- data:
- - kind: line
- label: community_count
- data:
- $$jsExpression: >
- ContributorsPerMonth.rows.map((row) => ({
- ...row,
- community_count: row.pr_community_count,
- maintainers_count: row.pr_maintainers_count,
- ratio:
- Math.round((row.pr_community_count / row.pr_maintainers_count) * 100) / 100,
- }))
- xKey: event_month
- yKey: community_count
- color: '#7cb342'
- - kind: line
- label: maintainers_count
- data:
- $$jsExpression: >
- ContributorsPerMonth.rows.map((row) => ({
- ...row,
- community_count: row.pr_community_count,
- maintainers_count: row.pr_maintainers_count,
- ratio:
- Math.round((row.pr_community_count / row.pr_maintainers_count) * 100) / 100,
- }))
- xKey: event_month
- yKey: maintainers_count
- color: '#27aeef'
- - kind: line
- label: ratio
- data:
- $$jsExpression: >
- ContributorsPerMonth.rows.map((row) => ({
- ...row,
- community_count: row.pr_community_count,
- maintainers_count: row.pr_maintainers_count,
- ratio:
- Math.round((row.pr_community_count / row.pr_maintainers_count) * 100) / 100,
- }))
- xKey: event_month
- yKey: ratio
- color: '#ea5545'
- height: 300
- queries:
- - name: PRsPerMonth
- query:
- function: PRsPerMonth
- kind: local
- parameters:
- - name: repositoryId
- value:
- $$jsExpression: |
- getRepositoryDetails.data.data.id
- - name: getRepositoryDetails
- query:
- function: getRepositoryDetails
- kind: local
- parameters:
- - name: slug
- value:
- $$jsExpression: |
- slug.value
- - name: ContributorsPerMonth
- query:
- function: ContributorsPerMonth
- kind: local
- parameters:
- - name: repositoryId
- value:
- $$jsExpression: |
- getRepositoryDetails.data.data.id
- alias:
- - ck33hgb
- displayName: Community per month
+ mode: markdown
+ value: This page is deprecated and no longer maintained.
+ display: shell
diff --git a/apps/tools-public/toolpad/pages/queryOssInsight/page.yml b/apps/tools-public/toolpad/pages/queryOssInsight/page.yml
index d31ebe74f..39c69a0f2 100644
--- a/apps/tools-public/toolpad/pages/queryOssInsight/page.yml
+++ b/apps/tools-public/toolpad/pages/queryOssInsight/page.yml
@@ -4,77 +4,15 @@ apiVersion: v1
kind: page
spec:
title: Query OSS Insight
- display: shell
- authorization:
- allowAll: true
+ displayName: Query OSS Insight
content:
- - component: TextField
- name: slug
- props:
- label: Repository slug
- defaultValue: mui/material-ui
- layout:
- columnSize: 1
- component: Text
name: text
layout:
columnSize: 1
- horizontalAlign: start
- verticalAlign: center
props:
+ mode: markdown
value:
- $$jsExpression: |
- (() => {
- if (getRepositoryDetails.data) {
- return `Repository ID: ${getRepositoryDetails.data.data.id}`
- } else {
- return "Not found"
- }
- })()
- - component: codeComponent.Textarea
- name: textarea
- - component: Button
- name: button
- props:
- content: Run query
- onClick:
- $$jsExpressionAction: queryOssInsight.fetch()
- loading:
- $$jsExpression: queryOssInsight.isLoading
- - component: Paper
- name: paper
- children:
- - component: Text
- name: text1
- props:
- value:
- $$jsExpression: queryOssInsight.error?.message ?? ''
- - component: codeComponent.AutoDataGrid
- name: autoDataGrid
- props:
- rows:
- $$jsExpression: queryOssInsight.rows
- queries:
- - name: queryOssInsight
- mode: mutation
- query:
- function: queryOssInsight.ts#queryOssInsight
- kind: local
- parameters:
- - name: repositoryId
- value:
- $$jsExpression: getRepositoryDetails.data?.data?.id
- - name: query
- value:
- $$jsExpression: textarea.value
- enabled: true
- - name: getRepositoryDetails
- query:
- function: functions.ts#getRepositoryDetails
- kind: local
- parameters:
- - name: slug
- value:
- $$jsExpression: |
- slug.value
- displayName: Query OSS Insight
+ "This page has moved to the code-infra-dashboard:
+ [Query OSS Insight](https://frontend-public.mui.com/query-oss-insight)"
+ display: shell
diff --git a/apps/tools-public/toolpad/resources/functions.ts b/apps/tools-public/toolpad/resources/functions.ts
index 0617e4ad5..76aa415c9 100644
--- a/apps/tools-public/toolpad/resources/functions.ts
+++ b/apps/tools-public/toolpad/resources/functions.ts
@@ -2,111 +2,6 @@ import { request } from 'graphql-request';
import mysql from 'mysql2/promise';
import SSH2Promise from 'ssh2-promise';
-export async function getRepositoryDetails(slug: string) {
- const res = await fetch(`https://api.ossinsight.io/gh/repo/${slug}`, {
- method: 'GET',
- });
- if (res.status !== 200) {
- throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 500)}`);
- }
- return res.json();
-}
-
-export async function PRsOpenandReviewedQuery() {
- const openQuery = `
-with pr_opened as (
- SELECT
- number,
- date_format(created_at, '%Y-%m-01') AS event_month,
- actor_login
- FROM
- github_events ge
- WHERE
- type = 'PullRequestEvent'
- AND action = 'opened'
- AND repo_id = 23083156
- AND ge.created_at >= '2021-12-01'
- AND actor_login NOT LIKE '%bot'
- AND actor_login NOT LIKE '%[bot]'
- AND ge.actor_login NOT LIKE 'mnajdova'
- AND ge.actor_login NOT LIKE 'michaldudak'
- AND ge.actor_login NOT LIKE 'siriwatknp'
- AND ge.actor_login NOT LIKE 'oliviertassinari'
- AND ge.actor_login NOT LIKE 'mj12albert'
- AND ge.actor_login NOT LIKE 'DiegoAndai'
- AND ge.actor_login NOT LIKE 'brijeshb42'
-), pr_reviewed as (
- SELECT
- number,
- date_format(created_at, '%Y-%m-01') AS event_month,
- actor_login
- FROM
- github_events ge
- WHERE
- ge.repo_id = 23083156
- AND ge.type = 'PullRequestReviewEvent'
- AND ge.action = 'created'
- AND ge.created_at >= '2021-12-01'
- AND ge.actor_login NOT LIKE '%bot'
- AND ge.actor_login NOT LIKE '%[bot]'
- AND ge.actor_login IN ('mnajdova','michaldudak','siriwatknp','oliviertassinari','mj12albert', 'DiegoAndai', 'brijeshb42')
-), pr_reviewed_with_open_by as (
- SELECT
- pr_reviewed.event_month,
- pr_reviewed.number,
- pr_reviewed.actor_login as reviewed_by,
- pr_opened.actor_login as open_by
- FROM pr_reviewed
- JOIN pr_opened on pr_opened.number = pr_reviewed.number)
-, pr_open_by_core as (
- SELECT
- number,
- date_format(created_at, '%Y-%m-01') AS event_month,
- actor_login
- FROM
- github_events ge
- WHERE
- type = 'PullRequestEvent'
- AND action = 'opened'
- AND repo_id = 23083156
- AND ge.created_at >= '2021-12-01'
- -- AND ge.created_at < '2023-01-01'
- AND actor_login NOT LIKE '%bot'
- AND actor_login NOT LIKE '%[bot]'
- AND ge.actor_login IN
- ('mnajdova','michaldudak','siriwatknp','oliviertassinari','mj12albert', 'DiegoAndai', 'brijeshb42')
-), final_table AS (
- SELECT
- n.event_month,
- n.reviewed_by,
- COUNT(DISTINCT n.number) as reviewed,
- COUNT(DISTINCT p.number) as opened
- FROM pr_reviewed_with_open_by n
- JOIN
- pr_open_by_core p ON p.actor_login = n.reviewed_by AND p.event_month = n.event_month
- GROUP BY
- event_month,
- reviewed_by
- ORDER BY
- event_month DESC
-)
-
-SELECT * FROM final_table
- `;
- const res = await fetch('https://api.ossinsight.io/q/playground', {
- headers: {
- 'content-type': 'application/json',
- },
- body: JSON.stringify({ sql: openQuery, type: 'repo', id: '23083156' }),
- method: 'POST',
- });
- if (res.status !== 200) {
- throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 500)}`);
- }
- const data = await res.json();
- return data.data;
-}
-
export async function queryCommitStatuses(repository: string) {
if (!process.env.GITHUB_TOKEN) {
throw new Error(`Env variable GITHUB_TOKEN not configured`);
@@ -257,191 +152,3 @@ FROM
return ratio[0];
}
-
-export async function PRsPerMonth(repositoryId: string, startDate: string) {
- if (!repositoryId) {
- return [];
- }
-
- startDate = startDate || '2016-01-01';
-
- const openQuery = `
-with maintainers as (
- SELECT
- DISTINCT ge.actor_login
- FROM
- github_events ge
- WHERE
- ge.repo_id = ${repositoryId}
- AND ge.type = 'PullRequestEvent'
- /* maintainers are defined as the ones that are allowed to merge PRs */
- AND ge.action = 'closed'
- AND ge.pr_merged = 1
- AND ge.created_at >= '2016-01-01'
-), pr_merged AS (
- SELECT
- number,
- date_format(created_at, '%Y-%m-01') AS event_month,
- actor_login
- FROM
- github_events ge
- WHERE
- type = 'PullRequestEvent'
- AND action = 'closed'
- AND ge.pr_merged = 1
- AND repo_id = ${repositoryId}
- AND ge.created_at >= '${startDate}'
-), pr_opened as (
- SELECT
- number,
- date_format(created_at, '%Y-%m-01') AS event_month,
- actor_login
- FROM
- github_events ge
- WHERE
- type = 'PullRequestEvent'
- AND action = 'opened'
- AND repo_id = ${repositoryId}
- AND ge.created_at >= '2016-01-01'
- AND actor_login NOT LIKE '%bot'
- AND actor_login NOT LIKE '%[bot]'
-), pr_merged_with_open_by as (
- SELECT
- pr_merged.event_month,
- pr_merged.number,
- pr_opened.actor_login as open_by,
- pr_merged.actor_login as merged_by
- FROM
- pr_merged
- JOIN pr_opened on pr_opened.number = pr_merged.number
-), pr_stats as (
- SELECT
- pr_community.event_month,
- COUNT(DISTINCT pr_community.number) AS pr_community_count,
- COUNT(DISTINCT pr_maintainers.number) AS pr_maintainers_count
- FROM pr_merged_with_open_by as pr_community
- LEFT JOIN pr_merged_with_open_by as pr_maintainers
- ON pr_community.event_month = pr_maintainers.event_month
- WHERE
- pr_community.open_by NOT IN (SELECT actor_login FROM maintainers)
- AND pr_maintainers.open_by IN (SELECT actor_login FROM maintainers)
- GROUP BY
- pr_community.event_month
- ORDER BY
- pr_community.event_month asc
-)
-
-SELECT * FROM pr_stats ge;
- `;
-
- const res = await fetch('https://api.ossinsight.io/q/playground', {
- headers: {
- 'content-type': 'application/json',
- },
- body: JSON.stringify({
- sql: openQuery,
- type: 'repo',
- id: repositoryId,
- }),
- method: 'POST',
- });
- if (res.status !== 200) {
- throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 500)}`);
- }
- const data = await res.json();
- return data.data.map((x) => ({ x: x.month, y: x.prs, ...x }));
-}
-
-export async function ContributorsPerMonth(repositoryId: string, startDate: string) {
- if (!repositoryId) {
- return [];
- }
-
- startDate = startDate || '2016-01-01';
-
- const openQuery = `
-with maintainers as (
- SELECT
- DISTINCT ge.actor_login
- FROM
- github_events ge
- WHERE
- ge.repo_id = ${repositoryId}
- AND ge.type = 'PullRequestEvent'
- /* maintainers are defined as the ones that are allowed to merge PRs */
- AND ge.action = 'closed'
- AND ge.pr_merged = 1
- AND ge.created_at >= '2016-01-01'
-), pr_merged AS (
- SELECT
- number,
- date_format(created_at, '%Y-%m-01') AS event_month,
- actor_login
- FROM
- github_events ge
- WHERE
- type = 'PullRequestEvent'
- AND action = 'closed'
- AND ge.pr_merged = 1
- AND repo_id = ${repositoryId}
- AND ge.created_at >= '${startDate}'
-), pr_opened as (
- SELECT
- number,
- date_format(created_at, '%Y-%m-01') AS event_month,
- actor_login
- FROM
- github_events ge
- WHERE
- type = 'PullRequestEvent'
- AND action = 'opened'
- AND repo_id = ${repositoryId}
- AND ge.created_at >= '2016-01-01'
- AND actor_login NOT LIKE '%bot'
- AND actor_login NOT LIKE '%[bot]'
-), pr_merged_with_open_by as (
- SELECT
- pr_merged.event_month,
- pr_merged.number,
- pr_opened.actor_login as open_by,
- pr_merged.actor_login as merged_by
- FROM
- pr_merged
- JOIN pr_opened on pr_opened.number = pr_merged.number
-), pr_stats as (
- SELECT
- pr_community.event_month,
- COUNT(DISTINCT pr_community.open_by) AS pr_community_count,
- COUNT(DISTINCT pr_maintainers.open_by) AS pr_maintainers_count
- FROM pr_merged_with_open_by as pr_community
- LEFT JOIN pr_merged_with_open_by as pr_maintainers
- ON pr_community.event_month = pr_maintainers.event_month
- WHERE
- pr_community.open_by NOT IN (SELECT actor_login FROM maintainers)
- AND pr_maintainers.open_by IN (SELECT actor_login FROM maintainers)
- GROUP BY
- pr_community.event_month
- ORDER BY
- pr_community.event_month asc
-)
-
-SELECT * FROM pr_stats ge;
- `;
-
- const res = await fetch('https://api.ossinsight.io/q/playground', {
- headers: {
- 'content-type': 'application/json',
- },
- body: JSON.stringify({
- sql: openQuery,
- type: 'repo',
- id: repositoryId,
- }),
- method: 'POST',
- });
- if (res.status !== 200) {
- throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 500)}`);
- }
- const data = await res.json();
- return data.data.map((x) => ({ x: x.month, y: x.prs, ...x }));
-}
diff --git a/apps/tools-public/toolpad/resources/queryAuditClosedIssues.ts b/apps/tools-public/toolpad/resources/queryAuditClosedIssues.ts
deleted file mode 100644
index 10ac70d98..000000000
--- a/apps/tools-public/toolpad/resources/queryAuditClosedIssues.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { request } from 'graphql-request';
-
-interface CloseTimelineItem {
- actor: {
- login: string;
- };
- createdAt: string;
-}
-
-interface Issue {
- number: number;
- url: string;
- title: string;
- timelineItems: {
- nodes: CloseTimelineItem[];
- };
-}
-
-const query1 = `
-issues(first: 100, orderBy: { direction: DESC, field: UPDATED_AT }) {
- nodes {
- number
- url
- title
- timelineItems(itemTypes: CLOSED_EVENT, last: 10) {
- nodes {
- ... on ClosedEvent {
- actor {
- login
- }
- createdAt
- }
- }
- }
- }
-}
-`;
-
-export async function queryAuditClosedIssues(githubUser?: string) {
- if (!process.env.GITHUB_TOKEN) {
- throw new Error(`Env variable GITHUB_TOKEN not configured`);
- }
-
- const endpoint = 'https://api.github.com/graphql';
- const token = process.env.GITHUB_TOKEN;
-
- const query = `
- {
- base_ui: repository(owner: "mui", name: "base-ui") {
- ${query1}
- }
- mui_public: repository(owner: "mui", name: "mui-public") {
- ${query1}
- }
- material_ui: repository(owner: "mui", name: "material-ui") {
- ${query1}
- }
- mui_x: repository(owner: "mui", name: "mui-x") {
- ${query1}
- }
- pigment_css: repository(owner: "mui", name: "pigment-css") {
- ${query1}
- }
- }
- `;
-
- const response: any = await request(
- endpoint,
- query,
- {},
- {
- Authorization: `Bearer ${token}`,
- },
- );
-
- const data = [
- ...response.base_ui.issues.nodes,
- ...response.mui_public.issues.nodes,
- ...response.material_ui.issues.nodes,
- ...response.mui_x.issues.nodes,
- ...response.pigment_css.issues.nodes,
- ]
- .map((issue: Issue) => ({
- ...issue,
- timelineItems: issue.timelineItems.nodes
- .map((item: CloseTimelineItem) => {
- return {
- createdAt: item.createdAt,
- // An actor can delete his account.
- actor: item.actor?.login,
- };
- })
- .filter((item) => !githubUser || item.actor === githubUser),
- }))
- .filter((issue) => issue.timelineItems.length > 0)
- .sort((a, b) => {
- return a.timelineItems[0].createdAt < b.timelineItems[0].createdAt ? 1 : -1;
- });
-
- return data;
-}
diff --git a/apps/tools-public/toolpad/resources/queryAuditLabelChanges.ts b/apps/tools-public/toolpad/resources/queryAuditLabelChanges.ts
deleted file mode 100644
index 5901fdef7..000000000
--- a/apps/tools-public/toolpad/resources/queryAuditLabelChanges.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import { request } from 'graphql-request';
-
-interface LabelTimelineItem {
- label: {
- name: string;
- };
- actor: {
- login: string;
- };
- createdAt: string;
-}
-
-interface Issue {
- number: number;
- url: string;
- title: string;
- timelineItems: {
- nodes: LabelTimelineItem[];
- };
-}
-
-const query1 = `
-pullRequests(first: 50, orderBy: {direction: DESC, field: CREATED_AT}) {
- nodes {
- number
- url
- title
- timelineItems(itemTypes: LABELED_EVENT, first: 100) {
- nodes {
- ... on LabeledEvent {
- label {
- name
- }
- actor {
- login
- }
- createdAt
- }
- }
- }
- }
-}
-issues(first: 50, orderBy: { direction: DESC, field: CREATED_AT }) {
- nodes {
- number
- url
- title
- timelineItems(itemTypes: LABELED_EVENT, first: 100) {
- nodes {
- ... on LabeledEvent {
- label {
- name
- }
- actor {
- login
- }
- createdAt
- }
- }
- }
- }
-}
-`;
-
-export async function queryAuditLabelChanges() {
- if (!process.env.GITHUB_TOKEN) {
- throw new Error(`Env variable GITHUB_TOKEN not configured`);
- }
-
- const endpoint = 'https://api.github.com/graphql';
- const token = process.env.GITHUB_TOKEN;
-
- const query = `
- {
- base_ui: repository(owner: "mui", name: "base-ui") {
- ${query1}
- }
- mui_public: repository(owner: "mui", name: "mui-public") {
- ${query1}
- }
- material_ui: repository(owner: "mui", name: "material-ui") {
- ${query1}
- }
- mui_x: repository(owner: "mui", name: "mui-x") {
- ${query1}
- }
- pigment_css: repository(owner: "mui", name: "pigment-css") {
- ${query1}
- }
- }
- `;
-
- const response: any = await request(
- endpoint,
- query,
- {},
- {
- Authorization: `Bearer ${token}`,
- },
- );
-
- // console.log('response', response.materialui);
-
- const data = [
- ...response.base_ui.pullRequests.nodes,
- ...response.base_ui.issues.nodes,
- ...response.mui_public.pullRequests.nodes,
- ...response.mui_public.issues.nodes,
- ...response.material_ui.pullRequests.nodes,
- ...response.material_ui.issues.nodes,
- ...response.mui_x.pullRequests.nodes,
- ...response.mui_x.issues.nodes,
- ...response.pigment_css.pullRequests.nodes,
- ...response.pigment_css.issues.nodes,
- ]
- .map((issue: Issue) => ({
- ...issue,
- timelineItems: issue.timelineItems.nodes.map((item: LabelTimelineItem) => {
- return {
- createdAt: item.createdAt,
- label: item.label.name,
- // An actor can delete his account.
- actor: item.actor?.login,
- };
- }),
- }))
- .filter((issue) => issue.timelineItems.length > 0)
- .sort((a, b) => {
- return a.timelineItems[0].createdAt < b.timelineItems[0].createdAt ? 1 : -1;
- });
-
- return data;
-}
diff --git a/apps/tools-public/toolpad/resources/queryOssInsight.ts b/apps/tools-public/toolpad/resources/queryOssInsight.ts
deleted file mode 100644
index 7e950cb0c..000000000
--- a/apps/tools-public/toolpad/resources/queryOssInsight.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-export async function queryOssInsight(repositoryId: string, query: string) {
- const res = await fetch('https://api.ossinsight.io/q/playground', {
- headers: {
- 'content-type': 'application/json',
- },
- method: 'POST',
- body: JSON.stringify({
- type: 'repo',
- sql: query,
- id: repositoryId,
- }),
- });
- if (res.status !== 200) {
- throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 500)}`);
- }
- const json = await res.json();
- return json.data;
-}
From b5a1beb63c11d8e5c85c02b131df32037512dd2f Mon Sep 17 00:00:00 2001
From: Janpot <2109932+Janpot@users.noreply.github.com>
Date: Wed, 10 Jun 2026 17:20:19 +0200
Subject: [PATCH 2/8] Lay out Query OSS Insight editor and results side-by-side
The SQL textarea grew unbounded with the query, pushing the results grid
off-screen. Put the editor and grid in two height-bounded panes: side-by-side
on md+, stacked (editor above grid) on small screens, each scrolling
internally.
---
.../src/views/QueryOssInsight.tsx | 121 +++++++++++-------
1 file changed, 75 insertions(+), 46 deletions(-)
diff --git a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
index d9732fbb5..a656aaba7 100644
--- a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
+++ b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
@@ -96,54 +96,83 @@ export default function QueryOssInsight() {
{' '}
playground for a GitHub repository.
-
- setSearchParams({ slug: event.target.value })}
- sx={{ minWidth: 280 }}
- />
-
- {repositoryId !== null ? `Repository ID: ${repositoryId}` : 'Not found'}
-
-
- setSql(event.target.value)}
- multiline
- minRows={4}
- fullWidth
- slotProps={{ htmlInput: { style: { fontFamily: 'monospace' } } }}
- sx={{ mb: 2 }}
- />
-
-
-
- {mutation.isError ? (
-
- ) : null}
-
- {/* Remove once https://github.com/mui/mui-x/issues/17077 is fixed */}
-
- row[ROW_ID] as number}
- loading={mutation.isPending}
- density="compact"
- disableRowSelectionOnClick
- sx={{ height: '100%' }}
+
+ setSearchParams({ slug: event.target.value })}
+ sx={{ flex: 1 }}
+ />
+
+ {repositoryId !== null ? `Repository ID: ${repositoryId}` : 'Not found'}
+
+
+ setSql(event.target.value)}
+ multiline
+ slotProps={{ htmlInput: { style: { fontFamily: 'monospace' } } }}
+ sx={{
+ flex: 1,
+ minHeight: 0,
+ '& .MuiInputBase-root': {
+ height: '100%',
+ alignItems: 'flex-start',
+ overflow: 'auto',
+ },
+ '& .MuiInputBase-inputMultiline': { height: '100% !important' },
+ }}
/>
-
+
+
+
+ {mutation.isError ? (
+
+ ) : null}
+
+
+ {/* Remove once https://github.com/mui/mui-x/issues/17077 is fixed */}
+
+ row[ROW_ID] as number}
+ loading={mutation.isPending}
+ density="compact"
+ disableRowSelectionOnClick
+ sx={{ height: '100%' }}
+ />
+
+
);
From ffaeeb5f09f89340cbc72dcb4cb48ca5a0c8edbc Mon Sep 17 00:00:00 2001
From: Janpot <2109932+Janpot@users.noreply.github.com>
Date: Wed, 10 Jun 2026 17:32:44 +0200
Subject: [PATCH 3/8] Derive query result columns from OSS Insight field schema
Columns were derived from the first row's keys, so a query returning zero
rows rendered 'No columns'. The playground response includes a `fields`
schema (in SELECT order) even for an empty result set; use it to build the
grid columns so they always show and a zero-row result correctly reads as
'No rows'.
---
apps/code-infra-dashboard/app/api/oss-insight/route.ts | 7 ++++++-
apps/code-infra-dashboard/src/views/QueryOssInsight.tsx | 9 ++++-----
2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/apps/code-infra-dashboard/app/api/oss-insight/route.ts b/apps/code-infra-dashboard/app/api/oss-insight/route.ts
index 03b59cf56..790f19bf2 100644
--- a/apps/code-infra-dashboard/app/api/oss-insight/route.ts
+++ b/apps/code-infra-dashboard/app/api/oss-insight/route.ts
@@ -50,5 +50,10 @@ export async function POST(request: NextRequest) {
}
const json = await response.json();
- return NextResponse.json({ rows: json.data });
+ // `fields` carries the column schema in SELECT order even when `data` is
+ // empty, so the grid can render its columns for a zero-row result set.
+ return NextResponse.json({
+ rows: json.data,
+ fields: (json.fields ?? []).map((field: { name: string }) => field.name),
+ });
}
diff --git a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
index a656aaba7..38c042014 100644
--- a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
+++ b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
@@ -25,6 +25,7 @@ const ROW_ID = '__rowIndex';
interface QueryResult {
rows: QueryRow[];
+ fields: string[];
}
async function runQuery(repositoryId: number, sql: string): Promise {
@@ -64,6 +65,7 @@ export default function QueryOssInsight() {
});
const rows = React.useMemo(() => mutation.data?.rows ?? [], [mutation.data]);
+ const fields = React.useMemo(() => mutation.data?.fields ?? [], [mutation.data]);
const gridRows = React.useMemo(
() => rows.map((row, index) => ({ ...row, [ROW_ID]: index })),
@@ -71,11 +73,8 @@ export default function QueryOssInsight() {
);
const columns = React.useMemo(
- () =>
- rows.length > 0
- ? Object.keys(rows[0]).map((field) => ({ field, flex: 1, minWidth: 120 }))
- : [],
- [rows],
+ () => fields.map((field) => ({ field, flex: 1, minWidth: 120 })),
+ [fields],
);
return (
From 368ff3b580385b99e9e51137f36362bfb86aba46 Mon Sep 17 00:00:00 2001
From: Janpot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 11 Jun 2026 09:33:16 +0200
Subject: [PATCH 4/8] Seed Query OSS Insight from URL and add a share link
Both the repository slug and SQL are now backed by the URL search params, so a
link pre-fills the editor and the input survives reloads. Local drafts keep
typing responsive and are committed to the URL on Run. A CopyButton next to Run
copies an absolute link encoding the current input.
---
.../src/components/CopyButton.tsx | 5 +--
.../src/views/QueryOssInsight.tsx | 34 +++++++++++++++----
2 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/apps/code-infra-dashboard/src/components/CopyButton.tsx b/apps/code-infra-dashboard/src/components/CopyButton.tsx
index 9f6352cf1..622c63dfc 100644
--- a/apps/code-infra-dashboard/src/components/CopyButton.tsx
+++ b/apps/code-infra-dashboard/src/components/CopyButton.tsx
@@ -9,10 +9,11 @@ import type { SxProps, Theme } from '@mui/material/styles';
interface CopyButtonProps {
text: string;
+ title?: string;
sx?: SxProps;
}
-export default function CopyButton({ text, sx }: CopyButtonProps) {
+export default function CopyButton({ text, title = 'Copy to clipboard', sx }: CopyButtonProps) {
const [copied, setCopied] = React.useState(false);
const handleCopy = () => {
@@ -23,7 +24,7 @@ export default function CopyButton({ text, sx }: CopyButtonProps) {
};
return (
-
+
{copied ? : }
diff --git a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
index 38c042014..1a677d404 100644
--- a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
+++ b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
@@ -1,6 +1,7 @@
'use client';
import * as React from 'react';
+import { usePathname } from 'next/navigation';
import { useMutation, useQuery } from '@tanstack/react-query';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
@@ -10,6 +11,7 @@ import Typography from '@mui/material/Typography';
import { DataGridPremium, type GridColDef } from '@mui/x-data-grid-premium';
import Heading from '../components/Heading';
import ErrorDisplay from '../components/ErrorDisplay';
+import CopyButton from '../components/CopyButton';
import { useSearchParamsState } from '../hooks/useSearchParamsState';
import { fetchJson } from '../utils/http';
@@ -42,13 +44,18 @@ async function runQuery(repositoryId: number, sql: string): Promise
}
export default function QueryOssInsight() {
+ // Search params are the source of truth: they seed the inputs (so a link can
+ // pre-fill the editor) and survive reloads. Local drafts keep typing snappy
+ // without writing to the URL on every keystroke; they are committed on Run.
const [searchParams, setSearchParams] = useSearchParamsState(
- { slug: { defaultValue: 'mui/material-ui' } },
+ { slug: { defaultValue: 'mui/material-ui' }, sql: { defaultValue: '' } },
{ replace: true },
);
- const { slug } = searchParams;
- const [sql, setSql] = React.useState('');
+ const [slug, setSlug] = React.useState(searchParams.slug);
+ const [sql, setSql] = React.useState(searchParams.sql);
+ React.useEffect(() => setSlug(searchParams.slug), [searchParams.slug]);
+ React.useEffect(() => setSql(searchParams.sql), [searchParams.sql]);
const repoQuery = useQuery({
queryKey: ['oss-insight-repo', slug],
@@ -64,6 +71,20 @@ export default function QueryOssInsight() {
mutationFn: () => runQuery(repositoryId!, sql),
});
+ const handleRun = () => {
+ // Persist the current input to the URL so a reload or the address bar
+ // reflects what was run, then execute.
+ setSearchParams({ slug, sql });
+ mutation.mutate();
+ };
+
+ // Build an absolute, shareable link from the *current* (possibly unsaved)
+ // input. `origin` is read after mount to avoid an SSR/client mismatch.
+ const pathname = usePathname();
+ const [origin, setOrigin] = React.useState('');
+ React.useEffect(() => setOrigin(window.location.origin), []);
+ const shareUrl = `${origin}${pathname}?${new URLSearchParams({ slug, sql })}`;
+
const rows = React.useMemo(() => mutation.data?.rows ?? [], [mutation.data]);
const fields = React.useMemo(() => mutation.data?.fields ?? [], [mutation.data]);
@@ -120,7 +141,7 @@ export default function QueryOssInsight() {
size="small"
label="Repository slug"
value={slug}
- onChange={(event) => setSearchParams({ slug: event.target.value })}
+ onChange={(event) => setSlug(event.target.value)}
sx={{ flex: 1 }}
/>
@@ -144,15 +165,16 @@ export default function QueryOssInsight() {
'& .MuiInputBase-inputMultiline': { height: '100% !important' },
}}
/>
-
+
+
{mutation.isError ? (
From d1f327e55f8a9592e165afaf98a55d6dea286b78 Mon Sep 17 00:00:00 2001
From: Janpot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 11 Jun 2026 09:41:34 +0200
Subject: [PATCH 5/8] Don't rewrite the URL on run in Query OSS Insight
Running a query no longer mutates the address bar. The URL is only read to
seed the inputs; the share-link button is the explicit way to capture the
current input into a URL.
---
.../src/views/QueryOssInsight.tsx | 27 +++++++------------
1 file changed, 10 insertions(+), 17 deletions(-)
diff --git a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
index 1a677d404..ff4bd09d2 100644
--- a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
+++ b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
@@ -44,13 +44,13 @@ async function runQuery(repositoryId: number, sql: string): Promise
}
export default function QueryOssInsight() {
- // Search params are the source of truth: they seed the inputs (so a link can
- // pre-fill the editor) and survive reloads. Local drafts keep typing snappy
- // without writing to the URL on every keystroke; they are committed on Run.
- const [searchParams, setSearchParams] = useSearchParamsState(
- { slug: { defaultValue: 'mui/material-ui' }, sql: { defaultValue: '' } },
- { replace: true },
- );
+ // Search params seed the inputs so a link can pre-fill the editor. They are
+ // only read here — the share link below is the explicit way to capture the
+ // current input into a URL.
+ const [searchParams] = useSearchParamsState({
+ slug: { defaultValue: 'mui/material-ui' },
+ sql: { defaultValue: '' },
+ });
const [slug, setSlug] = React.useState(searchParams.slug);
const [sql, setSql] = React.useState(searchParams.sql);
@@ -71,15 +71,8 @@ export default function QueryOssInsight() {
mutationFn: () => runQuery(repositoryId!, sql),
});
- const handleRun = () => {
- // Persist the current input to the URL so a reload or the address bar
- // reflects what was run, then execute.
- setSearchParams({ slug, sql });
- mutation.mutate();
- };
-
- // Build an absolute, shareable link from the *current* (possibly unsaved)
- // input. `origin` is read after mount to avoid an SSR/client mismatch.
+ // Build an absolute, shareable link from the current input. `origin` is read
+ // after mount to avoid an SSR/client mismatch.
const pathname = usePathname();
const [origin, setOrigin] = React.useState('');
React.useEffect(() => setOrigin(window.location.origin), []);
@@ -168,7 +161,7 @@ export default function QueryOssInsight() {
-
+
+ Permalink
+
{mutation.isError ? (
From 0eb25ecc5009cc568029b2fe57f81abad2895d35 Mon Sep 17 00:00:00 2001
From: Janpot <2109932+Janpot@users.noreply.github.com>
Date: Fri, 12 Jun 2026 12:53:35 +0200
Subject: [PATCH 7/8] Trim dead style override on the SQL query field
The textarea height override was a no-op: the field fills the editor pane via
the InputBase root (height/alignItems/overflow), and the inner textarea keeps
its autosized height regardless. Drop the dead rule and document the rest.
---
apps/code-infra-dashboard/src/views/QueryOssInsight.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
index e7bf28303..e17d6df3a 100644
--- a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
+++ b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
@@ -147,6 +147,9 @@ export default function QueryOssInsight() {
onChange={(event) => setSql(event.target.value)}
multiline
slotProps={{ htmlInput: { style: { fontFamily: 'monospace' } } }}
+ // A multiline TextField's bordered box hugs its content by default.
+ // Stretch it to fill the editor pane (height), keep the text pinned
+ // to the top (alignItems), and scroll once the query overflows.
sx={{
flex: 1,
minHeight: 0,
@@ -155,7 +158,6 @@ export default function QueryOssInsight() {
alignItems: 'flex-start',
overflow: 'auto',
},
- '& .MuiInputBase-inputMultiline': { height: '100% !important' },
}}
/>
From 37ab3146532b82b15eb7833aeefab2feb2e15732 Mon Sep 17 00:00:00 2001
From: Janpot <2109932+Janpot@users.noreply.github.com>
Date: Fri, 12 Jun 2026 13:48:05 +0200
Subject: [PATCH 8/8] Use a native textarea for the SQL query editor
MUI's multiline TextField autosizes to its content, which we had to fight with
height/overflow overrides that distorted its padding. A styled native textarea
takes a fixed height, fills the editor pane, and scrolls natively with a clean,
predictable box model.
---
.../src/views/QueryOssInsight.tsx | 64 +++++++++++++------
1 file changed, 45 insertions(+), 19 deletions(-)
diff --git a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
index e17d6df3a..400cab86f 100644
--- a/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
+++ b/apps/code-infra-dashboard/src/views/QueryOssInsight.tsx
@@ -10,12 +10,40 @@ import Link from '@mui/material/Link';
import NoSsr from '@mui/material/NoSsr';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
+import { styled } from '@mui/material/styles';
import { DataGridPremium, type GridColDef } from '@mui/x-data-grid-premium';
import Heading from '../components/Heading';
import ErrorDisplay from '../components/ErrorDisplay';
import { useSearchParamsState } from '../hooks/useSearchParamsState';
import { fetchJson } from '../utils/http';
+// A native textarea, unlike MUI's multiline TextField, takes a fixed height and
+// scrolls instead of autosizing to its content — which is what we want for a
+// query editor that fills the pane.
+const SqlEditor = styled('textarea')(({ theme }) => ({
+ flex: 1,
+ minHeight: 0,
+ width: '100%',
+ boxSizing: 'border-box',
+ resize: 'none',
+ padding: theme.spacing(1.5),
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
+ fontSize: theme.typography.pxToRem(13),
+ lineHeight: 1.5,
+ color: theme.palette.text.primary,
+ backgroundColor: theme.palette.background.paper,
+ border: `1px solid ${theme.palette.divider}`,
+ borderRadius: theme.shape.borderRadius,
+ outline: 'none',
+ '&:hover': { borderColor: theme.palette.text.primary },
+ '&:focus': {
+ borderColor: theme.palette.primary.main,
+ borderWidth: 2,
+ padding: `calc(${theme.spacing(1.5)} - 1px)`,
+ },
+ '&::placeholder': { color: theme.palette.text.disabled },
+}));
+
interface RepoDetails {
id: number;
}
@@ -141,25 +169,23 @@ export default function QueryOssInsight() {
{repositoryId !== null ? `Repository ID: ${repositoryId}` : 'Not found'}
- setSql(event.target.value)}
- multiline
- slotProps={{ htmlInput: { style: { fontFamily: 'monospace' } } }}
- // A multiline TextField's bordered box hugs its content by default.
- // Stretch it to fill the editor pane (height), keep the text pinned
- // to the top (alignItems), and scroll once the query overflows.
- sx={{
- flex: 1,
- minHeight: 0,
- '& .MuiInputBase-root': {
- height: '100%',
- alignItems: 'flex-start',
- overflow: 'auto',
- },
- }}
- />
+
+
+ SQL query
+
+ setSql(event.target.value)}
+ placeholder="SELECT ..."
+ spellCheck={false}
+ />
+