-
Notifications
You must be signed in to change notification settings - Fork 0
New stats page scott #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| minimumViews = 0, | ||
| minimumCompletionRate = 0, | ||
| displayLimit = 10, | ||
| }) => { | ||
| const router = useRouter(); | ||
| const [selectedDifficulty, setSelectedDifficulty] = | ||
| useState(difficultyFilter); | ||
| const [selectedSortMethod, setSelectedSortMethod] = useState(sortBy); | ||
| const [hoveredRow, setHoveredRow] = useState(null); | ||
|
|
||
| const processedData = pageViewData | ||
| // Filter by sections (based on the first segment of the path) | ||
| .filter((page) => { | ||
| const pageSection = page.path.split('/')[1]; | ||
| return sections.includes(pageSection); | ||
| }) | ||
| // Filter by difficulty if specified | ||
| .filter((page) => { | ||
| return ( | ||
| selectedDifficulty === 'all' || page.difficulty === selectedDifficulty | ||
| ); | ||
| }) | ||
| // Optionally filter out outdated content | ||
| .filter((page) => { | ||
| if (includeOutdated) return true; | ||
| const lastUpdated = new Date(page.lastUpdated); | ||
| const sixMonthsAgo = new Date(); | ||
| sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6); | ||
| return lastUpdated > sixMonthsAgo; | ||
| }) | ||
| // Apply metric thresholds | ||
| .filter((page) => { | ||
| if (minimumViews > 0 && page.views < minimumViews) return false; | ||
| if ( | ||
| minimumCompletionRate > 0 && | ||
| page.completionRate < minimumCompletionRate / 100 | ||
| ) | ||
| return false; | ||
| return true; | ||
| }) | ||
| // Compute an engagement score for demonstration purposes | ||
| .map((page) => { | ||
| const viewsNorm = Math.min(page.views / 250000, 1); | ||
| const completionNorm = page.completionRate; | ||
| const timeSpentNorm = Math.min(page.avgTimeSpent / 600, 1); | ||
|
|
||
| // Freshness factor computed from last update date | ||
| const lastUpdated = new Date(page.lastUpdated); | ||
| const monthsOld = (new Date() - lastUpdated) / (30 * 24 * 60 * 60 * 1000); | ||
| const freshnessScore = Math.max(0, 1 - monthsOld / 24); | ||
|
|
||
| // Difficulty multiplier for weighting | ||
| let difficultyMultiplier = 1; | ||
| if (page.difficulty === 'beginner') difficultyMultiplier = 0.9; | ||
| if (page.difficulty === 'intermediate') difficultyMultiplier = 1.0; | ||
| if (page.difficulty === 'advanced') difficultyMultiplier = 1.2; | ||
|
|
||
| // Compute weighted engagement score | ||
| const engagementScore = | ||
| (0.4 * viewsNorm + | ||
| 0.3 * completionNorm + | ||
| 0.2 * timeSpentNorm + | ||
| 0.1 * Math.pow(freshnessScore, 2)) * | ||
| difficultyMultiplier; | ||
|
|
||
| return { | ||
| ...page, | ||
| engagementScore, | ||
| }; | ||
| }) | ||
| // Sort the computed results | ||
| .sort((a, b) => { | ||
| let comparison = 0; | ||
| if (selectedSortMethod === 'views') { | ||
| comparison = b.views - a.views; | ||
| } else if (selectedSortMethod === 'completion') { | ||
| comparison = b.completionRate - a.completionRate; | ||
| } else if (selectedSortMethod === 'timeSpent') { | ||
| comparison = b.avgTimeSpent - a.avgTimeSpent; | ||
| } else if (selectedSortMethod === 'engagement') { | ||
| comparison = b.engagementScore - a.engagementScore; | ||
| } else if (selectedSortMethod === 'lastUpdated') { | ||
| comparison = new Date(b.lastUpdated) - new Date(a.lastUpdated); | ||
| } | ||
| return sortOrder === 'desc' ? comparison : -comparison; | ||
| }) | ||
| // Limit the results to displayLimit entries. | ||
| .slice(0, displayLimit); | ||
|
|
||
| const filterControls = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The computation of processedData involves multiple chained array operations (filter, map, sort) directly within the component function body. These operations are re-executed on every render. For potentially large datasets or frequent re-renders (e.g., due to state changes in selectedDifficulty or selectedSortMethod), this can lead to performance degradation. Memoizing processedData using React.useMemo will optimize performance by caching the result and recomputing it only when its dependencies change.
| minimumViews = 0, | |
| minimumCompletionRate = 0, | |
| displayLimit = 10, | |
| }) => { | |
| const router = useRouter(); | |
| const [selectedDifficulty, setSelectedDifficulty] = | |
| useState(difficultyFilter); | |
| const [selectedSortMethod, setSelectedSortMethod] = useState(sortBy); | |
| const [hoveredRow, setHoveredRow] = useState(null); | |
| const processedData = pageViewData | |
| // Filter by sections (based on the first segment of the path) | |
| .filter((page) => { | |
| const pageSection = page.path.split('/')[1]; | |
| return sections.includes(pageSection); | |
| }) | |
| // Filter by difficulty if specified | |
| .filter((page) => { | |
| return ( | |
| selectedDifficulty === 'all' || page.difficulty === selectedDifficulty | |
| ); | |
| }) | |
| // Optionally filter out outdated content | |
| .filter((page) => { | |
| if (includeOutdated) return true; | |
| const lastUpdated = new Date(page.lastUpdated); | |
| const sixMonthsAgo = new Date(); | |
| sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6); | |
| return lastUpdated > sixMonthsAgo; | |
| }) | |
| // Apply metric thresholds | |
| .filter((page) => { | |
| if (minimumViews > 0 && page.views < minimumViews) return false; | |
| if ( | |
| minimumCompletionRate > 0 && | |
| page.completionRate < minimumCompletionRate / 100 | |
| ) | |
| return false; | |
| return true; | |
| }) | |
| // Compute an engagement score for demonstration purposes | |
| .map((page) => { | |
| const viewsNorm = Math.min(page.views / 250000, 1); | |
| const completionNorm = page.completionRate; | |
| const timeSpentNorm = Math.min(page.avgTimeSpent / 600, 1); | |
| // Freshness factor computed from last update date | |
| const lastUpdated = new Date(page.lastUpdated); | |
| const monthsOld = (new Date() - lastUpdated) / (30 * 24 * 60 * 60 * 1000); | |
| const freshnessScore = Math.max(0, 1 - monthsOld / 24); | |
| // Difficulty multiplier for weighting | |
| let difficultyMultiplier = 1; | |
| if (page.difficulty === 'beginner') difficultyMultiplier = 0.9; | |
| if (page.difficulty === 'intermediate') difficultyMultiplier = 1.0; | |
| if (page.difficulty === 'advanced') difficultyMultiplier = 1.2; | |
| // Compute weighted engagement score | |
| const engagementScore = | |
| (0.4 * viewsNorm + | |
| 0.3 * completionNorm + | |
| 0.2 * timeSpentNorm + | |
| 0.1 * Math.pow(freshnessScore, 2)) * | |
| difficultyMultiplier; | |
| return { | |
| ...page, | |
| engagementScore, | |
| }; | |
| }) | |
| // Sort the computed results | |
| .sort((a, b) => { | |
| let comparison = 0; | |
| if (selectedSortMethod === 'views') { | |
| comparison = b.views - a.views; | |
| } else if (selectedSortMethod === 'completion') { | |
| comparison = b.completionRate - a.completionRate; | |
| } else if (selectedSortMethod === 'timeSpent') { | |
| comparison = b.avgTimeSpent - a.avgTimeSpent; | |
| } else if (selectedSortMethod === 'engagement') { | |
| comparison = b.engagementScore - a.engagementScore; | |
| } else if (selectedSortMethod === 'lastUpdated') { | |
| comparison = new Date(b.lastUpdated) - new Date(a.lastUpdated); | |
| } | |
| return sortOrder === 'desc' ? comparison : -comparison; | |
| }) | |
| // Limit the results to displayLimit entries. | |
| .slice(0, displayLimit); | |
| const filterControls = ( | |
| const processedData = React.useMemo(() => { | |
| return pageViewData | |
| // Filter by sections (based on the first segment of the path) | |
| .filter((page) => { | |
| const pageSection = page.path.split('/')[1]; | |
| return sections.includes(pageSection); | |
| }) | |
| // Filter by difficulty if specified | |
| .filter((page) => { | |
| return ( | |
| selectedDifficulty === 'all' || page.difficulty === selectedDifficulty | |
| ); | |
| }) | |
| // Optionally filter out outdated content | |
| .filter((page) => { | |
| if (includeOutdated) return true; | |
| const lastUpdated = new Date(page.lastUpdated); | |
| const sixMonthsAgo = new Date(); | |
| sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6); | |
| return lastUpdated > sixMonthsAgo; | |
| }) | |
| // Apply metric thresholds | |
| .filter((page) => { | |
| if (minimumViews > 0 && page.views < minimumViews) return false; | |
| if ( | |
| minimumCompletionRate > 0 && | |
| page.completionRate < minimumCompletionRate / 100 | |
| ) | |
| return false; | |
| return true; | |
| }) | |
| // Compute an engagement score for demonstration purposes | |
| .map((page) => { | |
| const viewsNorm = Math.min(page.views / 250000, 1); | |
| const completionNorm = page.completionRate; | |
| const timeSpentNorm = Math.min(page.avgTimeSpent / 600, 1); | |
| // Freshness factor computed from last update date | |
| const lastUpdated = new Date(page.lastUpdated); | |
| const monthsOld = (new Date() - lastUpdated) / (30 * 24 * 60 * 60 * 1000); | |
| const freshnessScore = Math.max(0, 1 - monthsOld / 24); | |
| // Difficulty multiplier for weighting | |
| let difficultyMultiplier = 1; | |
| if (page.difficulty === 'beginner') difficultyMultiplier = 0.9; | |
| if (page.difficulty === 'intermediate') difficultyMultiplier = 1.0; | |
| if (page.difficulty === 'advanced') difficultyMultiplier = 1.2; | |
| // Compute weighted engagement score | |
| const engagementScore = | |
| (0.4 * viewsNorm + | |
| 0.3 * completionNorm + | |
| 0.2 * timeSpentNorm + | |
| 0.1 * Math.pow(freshnessScore, 2)) * | |
| difficultyMultiplier; | |
| return { | |
| ...page, | |
| engagementScore, | |
| }; | |
| }) | |
| // Sort the computed results | |
| .sort((a, b) => { | |
| let comparison = 0; | |
| if (selectedSortMethod === 'views') { | |
| comparison = b.views - a.views; | |
| } else if (selectedSortMethod === 'completion') { | |
| comparison = b.completionRate - a.completionRate; | |
| } else if (selectedSortMethod === 'timeSpent') { | |
| comparison = b.avgTimeSpent - a.avgTimeSpent; | |
| } else if (selectedSortMethod === 'engagement') { | |
| comparison = b.engagementScore - a.engagementScore; | |
| } else if (selectedSortMethod === 'lastUpdated') { | |
| comparison = new Date(b.lastUpdated) - new Date(a.lastUpdated); | |
| } | |
| return sortOrder === 'desc' ? comparison : -comparison; | |
| }) | |
| // Limit the results to displayLimit entries. | |
| .slice(0, displayLimit); | |
| }, [sections, selectedDifficulty, includeOutdated, minimumViews, minimumCompletionRate, selectedSortMethod, sortOrder, displayLimit]); |
Review by Conductor
| <div style={{textAlign: 'center', marginTop: '20px'}}> | ||
| <img | ||
| src="/images/meta-gradient.png" | ||
| style={{height: '3px', width: '50%'}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The <img> tag is missing width and height attributes. These attributes are crucial for preventing Cumulative Layout Shift (CLS) because they allow the browser to reserve space for the image before it loads. Additionally, an alt attribute should be provided for accessibility; use alt="" for decorative images.
| <div style={{textAlign: 'center', marginTop: '20px'}}> | |
| <img | |
| src="/images/meta-gradient.png" | |
| style={{height: '3px', width: '50%'}} | |
| <img | |
| src="/images/meta-gradient.png" | |
| width="200" | |
| height="3" | |
| alt="" | |
| style={{height: '3px', width: '50%'}} | |
| /> |
Review by Conductor
No description provided.