@@ -5,6 +5,7 @@ import Breadcrumbs from "@mui/material/Breadcrumbs";
55import Link from "@mui/material/Link" ;
66import Typography from "@mui/material/Typography" ;
77import NavigateNextIcon from "@mui/icons-material/NavigateNext" ;
8+ import WorkspacePremiumRoundedIcon from "@mui/icons-material/WorkspacePremiumRounded" ;
89
910import { useDataStore } from "store" ;
1011
@@ -37,6 +38,68 @@ const FolderActivityFlow = () => {
3738
3839 const breadcrumbs = useMemo ( ( ) => getBreadcrumbs ( ) , [ getBreadcrumbs ] ) ;
3940
41+ const { topContributorName, releaseRangeLabel } = useMemo ( ( ) => {
42+ if ( ! totalData || totalData . length === 0 || releaseTopFolderPaths . length === 0 ) {
43+ return {
44+ topContributorName : null ,
45+ releaseRangeLabel : "..." ,
46+ } ;
47+ }
48+
49+ const currentDepth = currentPath === "" ? 1 : currentPath . split ( "/" ) . length + 1 ;
50+ const releaseContributorActivities = extractReleaseBasedContributorActivities (
51+ totalData ,
52+ releaseTopFolderPaths ,
53+ currentDepth
54+ ) ;
55+
56+ if ( releaseContributorActivities . length === 0 ) {
57+ return {
58+ topContributorName : null ,
59+ releaseRangeLabel : "..." ,
60+ } ;
61+ }
62+
63+ const contributorClocs = new Map < string , number > ( ) ;
64+ releaseContributorActivities . forEach ( ( activity ) => {
65+ const current = contributorClocs . get ( activity . contributorName ) || 0 ;
66+ contributorClocs . set ( activity . contributorName , current + activity . changes ) ;
67+ } ) ;
68+
69+ let maxCloc = 0 ;
70+ let mostActiveContributor = "" ;
71+ contributorClocs . forEach ( ( cloc , name ) => {
72+ if ( cloc > maxCloc ) {
73+ maxCloc = cloc ;
74+ mostActiveContributor = name ;
75+ }
76+ } ) ;
77+
78+ const releaseIndices = Array . from (
79+ new Set ( releaseContributorActivities . map ( ( activity ) => activity . releaseIndex ) )
80+ ) . sort ( ( a , b ) => a - b ) ;
81+ const releaseTagByIndex = new Map < number , string > ( ) ;
82+ releaseContributorActivities . forEach ( ( activity ) => {
83+ if ( ! releaseTagByIndex . has ( activity . releaseIndex ) ) {
84+ releaseTagByIndex . set ( activity . releaseIndex , activity . releaseTag ) ;
85+ }
86+ } ) ;
87+
88+ const resolvedTags = releaseIndices . map ( ( index ) => {
89+ const tag = releaseTagByIndex . get ( index ) ;
90+ return tag && tag . trim ( ) . length > 0 ? tag : `Release ${ index } ` ;
91+ } ) ;
92+
93+ const firstReleaseLabel = resolvedTags [ 0 ] || "..." ;
94+ const lastReleaseLabel = resolvedTags [ resolvedTags . length - 1 ] || firstReleaseLabel ;
95+ const rangeLabel = resolvedTags . length <= 1 ? firstReleaseLabel : `${ firstReleaseLabel } to ${ lastReleaseLabel } ` ;
96+
97+ return {
98+ topContributorName : mostActiveContributor || null ,
99+ releaseRangeLabel : rangeLabel || "..." ,
100+ } ;
101+ } , [ totalData , releaseTopFolderPaths , currentPath ] ) ;
102+
40103 useEffect ( ( ) => {
41104 if ( ! totalData || totalData . length === 0 ) {
42105 return ;
@@ -84,43 +147,47 @@ const FolderActivityFlow = () => {
84147 } ) ;
85148 } , [ totalData , releaseGroups , releaseTopFolderPaths , navigateToFolder , currentPath ] ) ;
86149
150+ const topContributorLabel = topContributorName || "..." ;
151+
87152 return (
88153 < div
89154 className = "folder-activity-flow"
90155 ref = { containerRef }
91156 >
92- < div className = "folder-activity-flow__header" >
93- < div >
94- < p className = "folder-activity-flow__title" > Contributors Folder Activity Flow</ p >
95- < div className = "folder-activity-flow__subtitle" > Contributors moving between folders across releases</ div >
157+ < div className = "folder-activity-flow__head" >
158+ < Breadcrumbs
159+ separator = { < NavigateNextIcon fontSize = "small" /> }
160+ aria-label = "breadcrumb"
161+ className = "folder-activity-flow__breadcrumb"
162+ >
163+ { breadcrumbs . map ( ( crumb , index ) => {
164+ const isLast = index === breadcrumbs . length - 1 ;
165+
166+ if ( isLast ) {
167+ return < Typography key = { crumb } > { crumb } </ Typography > ;
168+ }
169+
170+ return (
171+ < Link
172+ key = { crumb }
173+ underline = "none"
174+ component = "button"
175+ onClick = { ( ) => navigateToBreadcrumb ( index , breadcrumbs . length ) }
176+ >
177+ { crumb }
178+ </ Link >
179+ ) ;
180+ } ) }
181+ </ Breadcrumbs >
182+
183+ < div className = "folder-activity-flow__title" >
184+ < WorkspacePremiumRoundedIcon className = "folder-activity-flow__title-icon" />
185+ < span className = "folder-activity-flow__title-text" > Top contributor is { topContributorLabel } </ span >
96186 </ div >
187+
188+ < div className = "folder-activity-flow__subtitle" > { releaseRangeLabel } </ div >
97189 </ div >
98190
99- < Breadcrumbs
100- separator = { < NavigateNextIcon fontSize = "small" /> }
101- aria-label = "breadcrumb"
102- className = "folder-activity-flow__breadcrumb"
103- >
104- { breadcrumbs . map ( ( crumb , index ) => {
105- const isLast = index === breadcrumbs . length - 1 ;
106-
107- if ( isLast ) {
108- return < Typography key = { crumb } > { crumb } </ Typography > ;
109- }
110-
111- return (
112- < Link
113- key = { crumb }
114- underline = "none"
115- component = "button"
116- onClick = { ( ) => navigateToBreadcrumb ( index , breadcrumbs . length ) }
117- sx = { { cursor : "pointer" } }
118- >
119- { crumb }
120- </ Link >
121- ) ;
122- } ) }
123- </ Breadcrumbs >
124191 < svg
125192 className = "folder-activity-flow__chart"
126193 ref = { svgRef }
0 commit comments