@@ -2,7 +2,6 @@ import { default as AddIcon } from "@mui/icons-material/Add";
22import { default as ChecklistIcon } from "@mui/icons-material/Checklist" ;
33import { default as DeleteIcon } from "@mui/icons-material/Delete" ;
44import { default as FilterIcon } from "@mui/icons-material/FilterAlt" ;
5- import { default as InfoIcon } from "@mui/icons-material/InfoOutlined" ;
65import {
76 Autocomplete ,
87 Badge ,
@@ -21,8 +20,8 @@ import {
2120 IconButton ,
2221 Menu ,
2322 MenuItem ,
23+ Switch ,
2424 TextField ,
25- Tooltip ,
2625 Typography ,
2726} from "@mui/material" ;
2827import axios from "axios" ;
@@ -39,6 +38,11 @@ type StatisticsData = {
3938 data : GraphData ;
4039} ;
4140
41+ type StatisticsResponse = {
42+ total : number ;
43+ statistics : Record < string , Record < string , number > > ;
44+ } ;
45+
4246const allStats : {
4347 section : string ;
4448 stats : { name : string ; title : string ; description : string } [ ] ;
@@ -194,12 +198,19 @@ export function RepoStatistics() {
194198 const [ statistics , setStatistics ] = useState <
195199 Record < string , StatisticsData [ ] >
196200 > ( { Loading : [ ] } ) ;
201+ const [ total , setTotal ] = useState < number > ( ) ;
197202 const [ chooseStatsOpen , setChooseStatsOpen ] = useState ( false ) ;
198203 const [ filtersOpen , setFiltersOpen ] = useState ( false ) ;
204+ const [ includeUnknown , setIncludeUnknown ] = useState ( false ) ;
199205
200206 // ensure user is logged in
201207 useUserToken ( ) ;
202208
209+ const selectedFilters = useMemo (
210+ ( ) => [ ...searchParams . entries ( ) ] . filter ( ( [ k ] ) => k !== "stats" ) ,
211+ [ searchParams ] ,
212+ ) ;
213+
203214 useEffect ( ( ) => {
204215 const loadStatistics = async ( ) => {
205216 setStatistics ( { Loading : [ ] } ) ;
@@ -209,18 +220,24 @@ export function RepoStatistics() {
209220 ) ;
210221 const params = new URLSearchParams ( searchParams ) ;
211222 params . set ( "stats" , selectedStats . join ( "," ) ) ;
212- const { data } = await axios . get <
213- Record < string , Record < string , number > >
214- > ( getApiUrl ( `statistics?${ params } ` ) ) ;
215- const statistics = Object . entries ( data ) . map ( ( [ title , stat ] ) => {
216- const valueCounts = Object . entries ( stat ) . map (
217- ( [ value , count ] ) =>
218- [ value , count ] satisfies [ string , number ] ,
219- ) ;
220- valueCounts . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] ) ;
221- const data : GraphData = [ [ "Value" , "Count" ] , ...valueCounts ] ;
222- return { title, data } ;
223- } ) ;
223+ const { data } = await axios . get < StatisticsResponse > (
224+ getApiUrl ( `statistics?${ params } ` ) ,
225+ ) ;
226+ setTotal ( data . total ) ;
227+ const statistics = Object . entries ( data . statistics ) . map (
228+ ( [ title , stat ] ) => {
229+ const valueCounts = Object . entries ( stat ) . map (
230+ ( [ value , count ] ) =>
231+ [ value , count ] satisfies [ string , number ] ,
232+ ) ;
233+ valueCounts . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] ) ;
234+ const data : GraphData = [
235+ [ "Value" , "Count" ] ,
236+ ...valueCounts ,
237+ ] ;
238+ return { title, data } ;
239+ } ,
240+ ) ;
224241 setStatistics ( {
225242 ...allStats
226243 . filter ( ( { stats } ) =>
@@ -234,9 +251,11 @@ export function RepoStatistics() {
234251 )
235252 . map ( ( { name } ) => ( {
236253 name,
237- data :
254+ data : addUnknown (
238255 statistics . find ( ( s ) => s . title === name )
239256 ?. data ?? [ ] ,
257+ includeUnknown ? data . total : undefined ,
258+ ) ,
240259 } ) )
241260 . filter ( ( { data } ) => data . length ) ;
242261 if ( add . length > 0 ) {
@@ -249,7 +268,7 @@ export function RepoStatistics() {
249268 } ) ;
250269 } ;
251270 loadStatistics ( ) . catch ( console . error ) ;
252- } , [ searchParams ] ) ;
271+ } , [ searchParams , includeUnknown ] ) ;
253272
254273 return (
255274 < >
@@ -300,26 +319,40 @@ export function RepoStatistics() {
300319 ) }
301320 </ Box >
302321 < Box >
303- { [ ...searchParams . entries ( ) ]
304- . filter ( ( [ k ] ) => k !== "stats" )
305- . map ( ( [ key , value ] ) => (
306- < Chip
307- key = { key }
308- label = { `${ getStatTitle ( key ) } : ${ value } ` }
309- sx = { { marginRight : 1 , marginBottom : 1 } }
310- icon = { < FilterIcon /> }
311- onDelete = { ( ) => {
312- const params = new URLSearchParams (
313- searchParams ,
314- ) ;
315- params . delete ( key ) ;
316- setSearchParams ( params ) ;
317- } }
318- />
319- ) ) }
322+ { selectedFilters . map ( ( [ key , value ] ) => (
323+ < Chip
324+ key = { key }
325+ label = { `${ getStatTitle ( key ) } : ${ value } ` }
326+ sx = { { marginRight : 1 , marginBottom : 1 } }
327+ icon = { < FilterIcon /> }
328+ onDelete = { ( ) => {
329+ const params = new URLSearchParams ( searchParams ) ;
330+ params . delete ( key ) ;
331+ setSearchParams ( params ) ;
332+ } }
333+ />
334+ ) ) }
335+ </ Box >
336+ < Box sx = { { marginBottom : 1 } } >
337+ { total !== undefined && (
338+ < Typography >
339+ { selectedFilters . length ? "Found" : "Total" }
340+ { `: ${ total } ` }
341+ GitHub Repositories
342+ </ Typography >
343+ ) }
320344 </ Box >
321- { Object . entries ( statistics ) . map ( ( [ section , stats ] , index ) => (
322- < >
345+ < FormControlLabel
346+ control = {
347+ < Switch
348+ checked = { includeUnknown }
349+ onChange = { ( e ) => setIncludeUnknown ( e . target . checked ) }
350+ />
351+ }
352+ label = "Include unknown"
353+ />
354+ { Object . entries ( statistics ) . map ( ( [ section , stats ] ) => (
355+ < Fragment key = { section } >
323356 < Typography
324357 variant = "h6"
325358 sx = { {
@@ -337,14 +370,31 @@ export function RepoStatistics() {
337370 />
338371 ) ) }
339372 </ CardGrid >
340- </ >
373+ </ Fragment >
341374 ) ) }
342375 </ >
343376 ) ;
344377}
345378
379+ function addUnknown ( data : GraphData , totalCount ?: number ) : GraphData {
380+ if ( totalCount === undefined ) {
381+ return data ;
382+ }
383+ const knownCount = data
384+ . slice ( 1 )
385+ . reduce (
386+ ( acc , [ , count ] ) => acc + ( typeof count === "number" ? count : 0 ) ,
387+ 0 ,
388+ ) ;
389+ const unknownCount = totalCount - knownCount ;
390+ if ( unknownCount <= 0 ) {
391+ return data ;
392+ }
393+ return [ ...data , [ "<unknown>" , unknownCount ] ] ;
394+ }
395+
346396function StatisticsCard ( { name, data } : { name : string ; data : GraphData } ) {
347- const title = useMemo ( ( ) => getStatTitle ( name ) , [ name ] ) ;
397+ const stat = useMemo ( ( ) => getStat ( name ) , [ name ] ) ;
348398 return (
349399 < Card
350400 key = { name }
@@ -380,38 +430,14 @@ function StatisticsCard({ name, data }: { name: string; data: GraphData }) {
380430 } }
381431 >
382432 < Typography gutterBottom variant = "h6" component = "h2" >
383- { title }
384- < StatDescriptionIcon name = { name } />
433+ { stat ?. title }
385434 </ Typography >
435+ < Typography > { stat ?. description } </ Typography >
386436 </ CardContent >
387437 </ Card >
388438 ) ;
389439}
390440
391- function StatDescriptionIcon ( { name } : { name : string } ) {
392- const stat = useMemo ( ( ) => {
393- for ( const s of allStats . flatMap ( ( s ) => s . stats ) ) {
394- if ( s . name === name ) {
395- return s ;
396- }
397- }
398- return null ;
399- } , [ name ] ) ;
400-
401- if ( ! stat ) {
402- return null ;
403- }
404-
405- return (
406- < Tooltip title = { stat . description } arrow >
407- < InfoIcon
408- fontSize = "small"
409- sx = { { marginLeft : 1 , marginTop : "auto" , marginBottom : "auto" } }
410- />
411- </ Tooltip >
412- ) ;
413- }
414-
415441function ChooseStatsDialog ( {
416442 open,
417443 onClose,
@@ -615,10 +641,10 @@ function StatisticsFilter({
615641
616642 useEffect ( ( ) => {
617643 const loadOptions = async ( ) => {
618- const { data } = await axios . get <
619- Record < string , Record < string , number > >
620- > ( getApiUrl ( `statistics?stats= ${ name } ` ) ) ;
621- const stat = data [ name ] ;
644+ const { data } = await axios . get < StatisticsResponse > (
645+ getApiUrl ( `statistics?stats= ${ name } ` ) ,
646+ ) ;
647+ const stat = data . statistics [ name ] ;
622648 setOptions ( Object . keys ( stat ?? { } ) . sort ( ) ) ;
623649 } ;
624650 loadOptions ( ) . catch ( console . error ) ;
0 commit comments