-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Render stargazers line chart with Observable Plot querying Seafowl
- Loading branch information
1 parent
e297d17
commit e222758
Showing
7 changed files
with
337 additions
and
61 deletions.
There are no files selected for viewing
107 changes: 89 additions & 18 deletions
107
...les/nextjs-import-airbyte-github-export-seafowl/components/RepositoryAnalytics/Charts.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,102 @@ | ||
import style from "./Charts.module.css"; | ||
import { useEffect, useRef } from "react"; | ||
|
||
import type { ImportedRepository } from "../../types"; | ||
import { SqlProvider, makeSeafowlHTTPContext, useSql } from "@madatdata/react"; | ||
|
||
import * as Plot from "@observablehq/plot"; | ||
import { useMemo } from "react"; | ||
|
||
import { | ||
stargazersLineChartQuery, | ||
type StargazersLineChartRow, | ||
} from "./sql-queries"; | ||
|
||
export interface ChartsProps { | ||
importedRepository: ImportedRepository; | ||
} | ||
|
||
export const Charts = ({ | ||
importedRepository: { | ||
githubNamespace, | ||
githubRepository, | ||
splitgraphNamespace, | ||
splitgraphRepository, | ||
}, | ||
}: ChartsProps) => { | ||
// Assume meta namespace contains both the meta tables, and all imported repositories and tables | ||
const META_NAMESPACE = | ||
process.env.NEXT_PUBLIC_SPLITGRAPH_GITHUB_ANALYTICS_META_NAMESPACE; | ||
|
||
export const Charts = ({ importedRepository }: ChartsProps) => { | ||
const seafowlDataContext = useMemo( | ||
() => | ||
makeSeafowlHTTPContext("https://demo.seafowl.cloud", { | ||
dbname: META_NAMESPACE, | ||
}), | ||
[] | ||
); | ||
|
||
return ( | ||
<div className={style.charts}> | ||
Chart for{" "} | ||
<a href={`https://github.com/${githubNamespace}/${githubRepository}`}> | ||
github.com/{githubNamespace}/{githubRepository} | ||
</a> | ||
, based on{" "} | ||
<a | ||
href={`https://www.splitgraph.com/${splitgraphNamespace}/${splitgraphRepository}`} | ||
> | ||
splitgraph.com/{splitgraphNamespace}/{splitgraphRepository} | ||
</a> | ||
<SqlProvider dataContext={seafowlDataContext}> | ||
<StargazersChart {...importedRepository} /> | ||
</SqlProvider> | ||
</div> | ||
); | ||
}; | ||
|
||
const StargazersChart = ({ | ||
splitgraphNamespace, | ||
splitgraphRepository, | ||
}: ImportedRepository) => { | ||
const containerRef = useRef<HTMLDivElement>(); | ||
|
||
const { response, error } = useSql<StargazersLineChartRow>( | ||
stargazersLineChartQuery({ splitgraphNamespace, splitgraphRepository }) | ||
); | ||
|
||
const stargazers = useMemo(() => { | ||
return !response || error | ||
? [] | ||
: (response.rows ?? []).map((r) => ({ | ||
...r, | ||
starred_at: new Date(r.starred_at), | ||
})); | ||
}, [response, error]); | ||
|
||
useEffect(() => { | ||
if (stargazers === undefined) { | ||
return; | ||
} | ||
|
||
const plot = Plot.plot({ | ||
y: { grid: true }, | ||
color: { scheme: "burd" }, | ||
marks: [ | ||
Plot.lineY(stargazers, { | ||
x: "starred_at", | ||
y: "cumulative_stars", | ||
}), | ||
// NOTE: We don't have username when querying Seafowl because it's within a JSON object, | ||
// and seafowl doesn't support querying inside JSON objects | ||
// Plot.tip( | ||
// stargazers, | ||
// Plot.pointer({ | ||
// x: "starred_at", | ||
// y: "cumulative_stars", | ||
// title: (d) => `${d.username} was stargazer #${d.cumulative_stars}`, | ||
// }) | ||
// ), | ||
], | ||
}); | ||
|
||
// There is a bug(?) in useSql where, since we can't give it dependencies, it | ||
// will re-run even with splitgraphNamespace and splitgraphRepository are undefined, | ||
// which results in an error querying Seafowl. So just don't render the chart in that case. | ||
if (splitgraphNamespace && splitgraphRepository) { | ||
containerRef.current.append(plot); | ||
} | ||
|
||
return () => plot.remove(); | ||
}, [stargazers]); | ||
|
||
return ( | ||
<> | ||
<h3>Stargazers</h3> | ||
<div ref={containerRef} /> | ||
</> | ||
); | ||
}; |
3 changes: 3 additions & 0 deletions
3
...byte-github-export-seafowl/components/RepositoryAnalytics/ImportedRepoMetadata.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.importedRepoMetadata { | ||
background: inherit; | ||
} |
129 changes: 129 additions & 0 deletions
129
...ort-airbyte-github-export-seafowl/components/RepositoryAnalytics/ImportedRepoMetadata.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import type { ImportedRepository } from "../../types"; | ||
import style from "./ImportedRepoMetadata.module.css"; | ||
|
||
import { makeStargazersTableQuery } from "./sql-queries"; | ||
|
||
// Assume meta namespace contains both the meta tables, and all imported repositories and tables | ||
const META_NAMESPACE = | ||
process.env.NEXT_PUBLIC_SPLITGRAPH_GITHUB_ANALYTICS_META_NAMESPACE; | ||
|
||
interface ImportedRepoMetadataProps { | ||
importedRepository: ImportedRepository; | ||
} | ||
|
||
export const ImportedRepoMetadata = ({ | ||
importedRepository, | ||
}: ImportedRepoMetadataProps) => { | ||
return ( | ||
<div className={style.importedRepoMetadata}> | ||
<h1> | ||
<GitHubRepoLink {...importedRepository} /> | ||
</h1> | ||
<h2>GitHub Analytics</h2> | ||
|
||
<ul> | ||
<li> | ||
Browse the data: <SplitgraphRepoLink {...importedRepository} /> | ||
</li> | ||
<li> | ||
<SplitgraphQueryLink | ||
importedRepository={importedRepository} | ||
tableName={"stargazers"} | ||
makeQuery={makeStargazersTableQuery} | ||
/> | ||
</li> | ||
<li> | ||
<SeafowlQueryLink | ||
importedRepository={importedRepository} | ||
tableName={"stargazers"} | ||
makeQuery={makeStargazersTableQuery} | ||
/> | ||
</li> | ||
</ul> | ||
</div> | ||
); | ||
}; | ||
|
||
const SplitgraphRepoLink = ({ | ||
splitgraphNamespace, | ||
splitgraphRepository, | ||
}: ImportedRepository) => { | ||
return ( | ||
<a | ||
target="_blank" | ||
href={`https://www.splitgraph.com/${splitgraphNamespace}/${splitgraphRepository}`} | ||
> | ||
{`splitgraph.com/${splitgraphNamespace}/${splitgraphRepository}`} | ||
</a> | ||
); | ||
}; | ||
|
||
const GitHubRepoLink = ({ | ||
githubNamespace, | ||
githubRepository, | ||
}: ImportedRepository) => { | ||
return ( | ||
<a | ||
target="_blank" | ||
href={`https://github.com/${githubNamespace}/${githubRepository}`} | ||
>{`github.com/${githubNamespace}/${githubRepository}`}</a> | ||
); | ||
}; | ||
|
||
const SplitgraphQueryLink = ({ | ||
importedRepository, | ||
makeQuery, | ||
tableName, | ||
}: { | ||
importedRepository: ImportedRepository; | ||
makeQuery: (repo: ImportedRepository) => string; | ||
tableName: string; | ||
}) => { | ||
return ( | ||
<a | ||
href={makeSplitgraphQueryHref(makeQuery(importedRepository))} | ||
target="_blank" | ||
> | ||
Query {tableName} in the Splitgraph Console | ||
</a> | ||
); | ||
}; | ||
|
||
const SeafowlQueryLink = ({ | ||
importedRepository, | ||
makeQuery, | ||
tableName, | ||
}: { | ||
importedRepository: ImportedRepository; | ||
makeQuery: (repo: ImportedRepository) => string; | ||
tableName: string; | ||
}) => { | ||
return ( | ||
<a | ||
href={makeSeafowlQueryHref(makeQuery(importedRepository))} | ||
target="_blank" | ||
> | ||
Query Seafowl table {tableName} using the Splitgraph Console | ||
</a> | ||
); | ||
}; | ||
|
||
/** Return the URL to Splitgraph Console pointing to Splitgraph DDN */ | ||
const makeSplitgraphQueryHref = (sqlQuery: string) => { | ||
const url = `https://www.splitgraph.com/query?${new URLSearchParams({ | ||
sqlQuery: sqlQuery, | ||
flavor: "splitgraph", | ||
}).toString()}`; | ||
|
||
return url; | ||
}; | ||
|
||
/** Return the URL to Splitgraph Console pointing to Seafowl db where we export tables */ | ||
const makeSeafowlQueryHref = (sqlQuery: string) => { | ||
return `https://www.splitgraph.com/query?${new URLSearchParams({ | ||
sqlQuery: sqlQuery, | ||
flavor: "seafowl", | ||
// Splitgraph exports to Seafowl dbname matching the username of the exporting user | ||
"database-name": META_NAMESPACE, | ||
})}`; | ||
}; |
54 changes: 54 additions & 0 deletions
54
...nextjs-import-airbyte-github-export-seafowl/components/RepositoryAnalytics/sql-queries.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import type { ImportedRepository } from "../../types"; | ||
|
||
// Assume meta namespace contains both the meta tables, and all imported repositories and tables | ||
const META_NAMESPACE = | ||
process.env.NEXT_PUBLIC_SPLITGRAPH_GITHUB_ANALYTICS_META_NAMESPACE; | ||
|
||
type TargetSplitgraphRepo = { | ||
splitgraphNamespace?: string; | ||
splitgraphRepository: string; | ||
}; | ||
|
||
/** | ||
* Raw query to select all columns in the stargazers table, which can be | ||
* run on both Splitgraph and Seafowl. | ||
* | ||
* This is meant for linking to the query editor, not for rendering charts. | ||
*/ | ||
export const makeStargazersTableQuery = ({ | ||
splitgraphNamespace = META_NAMESPACE, | ||
splitgraphRepository, | ||
}: TargetSplitgraphRepo) => { | ||
return `SELECT | ||
"repository", | ||
"user_id", | ||
"starred_at", | ||
"user", | ||
"_airbyte_ab_id", | ||
"_airbyte_emitted_at", | ||
"_airbyte_normalized_at", | ||
"_airbyte_stargazers_hashid" | ||
FROM | ||
"${splitgraphNamespace}/${splitgraphRepository}"."stargazers" | ||
LIMIT 100;`; | ||
}; | ||
|
||
/** Shape of row returned by {@link stargazersLineChartQuery} */ | ||
export type StargazersLineChartRow = { | ||
username: string; | ||
cumulative_stars: number; | ||
starred_at: string; | ||
}; | ||
|
||
/** Time series of GitHub stargazers for the given repository */ | ||
export const stargazersLineChartQuery = ({ | ||
splitgraphNamespace = META_NAMESPACE, | ||
splitgraphRepository, | ||
}: TargetSplitgraphRepo) => { | ||
return `SELECT | ||
COUNT(*) OVER (ORDER BY starred_at) AS cumulative_stars, | ||
starred_at | ||
FROM | ||
"${splitgraphNamespace}/${splitgraphRepository}"."stargazers" | ||
ORDER BY starred_at;`; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.