✨ Display last repository commit time and organization icon in ProjectItem
trafficstars
https://github.com/user-attachments/assets/bc09f1ce-0fa6-413c-9941-038203a39a10
Display last repository commit time and organization icon in ProjectItem
Overview
- Display the last commit time of the repository linked to a project instead of project creation time
- Show the GitHub organization icon for the repository in the project icon placeholder
Changes
- Added and functions to GitHub API
- Modified project data retrieval to include repository information
- Created server components to fetch and display repository data
- Updated ProjectItem component to show repository information
Link to Devin run: https://app.devin.ai/sessions/8077ac441097437a90cb905785b91dbd
User: [email protected]
⚠️ No Changeset found
Latest commit: 67c5cece34733f81b5d45490b047466be5984d56
Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.
This PR includes no changesets
When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR
The latest updates on your projects. Learn more about Vercel for Git ↗︎
1 Skipped Deployment
| Name |
Status |
Preview |
Comments |
Updated (UTC) |
| liam-docs |
⬜️ Ignored (Inspect) |
Visit Preview |
|
Apr 18, 2025 7:25am |
🤖 Devin AI Engineer
I'll be helping with this pull request! Here's what you should know:
✅ I will automatically:
- Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
- Look at CI failures and help fix them
Note: I can only respond to comments from users who have write access to this repository.
⚙️ Control Options:
- [x] Disable automatic comment and CI monitoring
(aside)
I have made many changes of my own to accommodate console error. I would like someone to review them for me.🙏
To solve this problem to address the following error, we separate the client component from the server component.
OrganizationIcon.tsx:13 Cannot update a component (`Router`) while rendering a different component (`OrganizationIcon`).
To locate the bad setState() call inside `OrganizationIcon`, follow the stack trace as described in https://react.dev/link/setstate-in-render
Like this:
LastCommitInfo.tsx (server component)
↓ Get data
LastCommitInfoClient.tsx (client component)
↓ Display
ProjectItem.tsx (use data acquisition function in client component)
PR Reviewer Guide 🔍
(Review updated until commit https://github.com/liam-hq/liam/commit/f391335b7b78dfa1b5f2c33f9d779ff85adc494f)
Here are some key observations to aid the review process:
| ⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪ |
| 🧪 No relevant tests |
| 🔒 No security concerns identified |
⚡ Recommended focus areas for review
Redundant Component
This component appears to be unused and duplicates functionality already implemented in LastCommitDataWrapper. It performs the same task of fetching and displaying commit information.
import { getLastCommit } from '@liam-hq/github'
interface LastCommitInfoProps {
installationId: number
owner: string
repo: string
defaultDate: string
}
export async function LastCommitInfo({
installationId,
owner,
repo,
defaultDate,
}: LastCommitInfoProps) {
const formatDate = (dateString: string) => {
const date = new Date(dateString)
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
}
try {
const commitInfo = await getLastCommit(installationId, owner, repo)
if (commitInfo) {
return (
<>
<span>{commitInfo.author}</span>
<span>committed</span>
<span>on {formatDate(commitInfo.date)}</span>
</>
)
}
} catch (error) {
console.error('Failed to fetch last commit info:', error)
}
return (
<>
<span>User</span>
<span>committed</span>
<span>on {formatDate(defaultDate)}</span>
</>
)
}
Loading State
The loading state displays "Loading commit info..." text which may cause layout shifts when data loads. Consider using a skeleton loader or maintaining consistent layout during loading.
return <span>Loading commit info...</span>
}
Null Safety
When accessing repository properties like installation_id, owner, and name, there's no null check for the repository object itself, which could lead to runtime errors if repository is undefined.
<LastCommitDataWrapper
installationId={repository.installation_id}
owner={repository.owner}
repo={repository.name}
defaultDate={project.created_at}
|
PR Code Suggestions ✨
Latest suggestions up to f391335
Explore these optional code suggestions:
| Category | Suggestion | Impact |
| General |
✅ Remove duplicate component file
Suggestion Impact:The entire LastCommitInfo.tsx file was removed as suggested, eliminating the duplicate functionality and reducing maintenance overhead
code diff:
-import { getLastCommit } from '@liam-hq/github'
-interface LastCommitInfoProps {
- installationId: number
- owner: string
- repo: string
- defaultDate: string
-}
-
-export async function LastCommitInfo({
- installationId,
- owner,
- repo,
- defaultDate,
-}: LastCommitInfoProps) {
- const formatDate = (dateString: string) => {
- const date = new Date(dateString)
- return date.toLocaleDateString('en-US', {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- })
- }
-
- try {
- const commitInfo = await getLastCommit(installationId, owner, repo)
-
- if (commitInfo) {
- return (
- <>
- <span>{commitInfo.author}</span>
- <span>committed</span>
- <span>on {formatDate(commitInfo.date)}</span>
- </>
- )
- }
- } catch (error) {
- console.error('Failed to fetch last commit info:', error)
- }
-
- return (
- <>
- <span>User</span>
- <span>committed</span>
- <span>on {formatDate(defaultDate)}</span>
- </>
- )
-}
The LastCommitInfo.tsx file appears to be unused in the PR but duplicates functionality already implemented in LastCommitData.tsx and
LastCommitDataWrapper.tsx. This creates maintenance overhead and potential confusion. Consider removing this file to avoid duplication.
frontend/apps/app/features/projects/components/ProjectItem/LastCommitInfo.tsx [1-8]
-import { getLastCommit } from '@liam-hq/github'
+// This file should be removed as it duplicates functionality in LastCommitData.tsx and LastCommitDataWrapper.tsx
-interface LastCommitInfoProps {
- installationId: number
- owner: string
- repo: string
- defaultDate: string
-}
-
[Suggestion has been applied]
Suggestion importance[1-10]: 8
__
Why: The LastCommitInfo.tsx file duplicates functionality already implemented in LastCommitData.tsx and LastCommitDataWrapper.tsx, creating unnecessary maintenance overhead and potential confusion in the codebase.
| Medium
|
Improve loading state consistency
The loading state displays a text that might disrupt the UI layout. Consider using a more subtle loading indicator or maintaining the same structure as the loaded state to prevent layout shifts.
frontend/apps/app/features/projects/components/ProjectItem/LastCommitDataWrapper.tsx [52-54]
if (isLoading) {
- return <span>Loading commit info...</span>
+ return (
+ <>
+ <span>Loading</span>
+ <span>commit</span>
+ <span>info...</span>
+ </>
+ )
}
- [ ] Apply this suggestion
Suggestion importance[1-10]: 5
__
Why: The suggestion improves UI consistency by maintaining the same structure during loading as in the loaded state, which prevents layout shifts and provides a better user experience.
| Low
|
| Possible issue |
Add image error handling
The Image component is missing error handling. If the image fails to load, it will silently fall back to the default Next.js error display. Add an onError handler to gracefully fall back to the ProjectIcon component.
frontend/apps/app/features/projects/components/ProjectItem/OrganizationIcon.tsx [12-26]
export function OrganizationIcon({ avatarUrl, owner }: OrganizationIconProps) {
- if (avatarUrl) {
+ const [hasError, setHasError] = useState(false);
+
+ if (avatarUrl && !hasError) {
return (
<Image
src={avatarUrl}
alt={`${owner} organization icon`}
width={32}
height={32}
className={styles.projectIcon}
+ onError={() => setHasError(true)}
/>
)
}
return <ProjectIcon className={styles.projectIcon} />
}
- [ ] Apply this suggestion
Suggestion importance[1-10]: 7
__
Why: Adding error handling for the Image component is important to gracefully handle cases where GitHub avatar images fail to load, preventing unexpected UI behavior and providing a consistent fallback.
| Medium
|
Learned best practice |
Add stronger input validation with type checking and format validation for function parameters to prevent potential runtime errors
The current validation only checks if the parameters exist but doesn't validate their types or formats. For installationId, we should verify it's a positive number, and for owner and repo, we should check they are non-empty strings with valid GitHub repository naming patterns.
frontend/apps/app/features/projects/components/ProjectItem/LastCommitData.tsx [6-29]
export async function fetchLastCommitData(
installationId: number,
owner: string,
repo: string,
) {
try {
- if (!installationId || !owner || !repo) {
+ // Validate installation ID is a positive number
+ if (!installationId || typeof installationId !== 'number' || installationId <= 0) {
+ console.error('Invalid installation ID:', installationId)
+ return null
+ }
+
+ // Validate owner and repo are non-empty strings with valid GitHub naming patterns
+ if (!owner || typeof owner !== 'string' || !/^[a-zA-Z0-9-]+$/.test(owner)) {
+ console.error('Invalid owner name:', owner)
+ return null
+ }
+
+ if (!repo || typeof repo !== 'string' || !/^[a-zA-Z0-9._-]+$/.test(repo)) {
+ console.error('Invalid repository name:', repo)
return null
}
const commitInfo = await getLastCommit(installationId, owner, repo)
if (commitInfo) {
return {
author: commitInfo.author,
date: commitInfo.date,
}
}
return null
} catch (error) {
console.error('Failed to fetch last commit info:', error)
return null
}
}
[To ensure code accuracy, apply this suggestion manually]
Suggestion importance[1-10]: 6
| Low
|
|
| |
Previous suggestions
✅ Suggestions up to commit 3ee5ddc
| Category | Suggestion | Impact |
| Possible issue |
✅ Prevent state updates after unmount
Suggestion Impact:The commit completely refactored the component to move the data fetching logic out of the component, eliminating the need for the suggested pattern. Instead of implementing the suggested cleanup pattern, the commit solved the underlying problem by moving the async operations to child components.
code diff:
-import { useEffect, useState } from 'react'
import type { FC } from 'react'
-import { getLastCommitData } from './LastCommitInfo'
-import { LastCommitInfoClient } from './LastCommitInfoClient'
-import { getOrganizationData } from './OrganizationIcon'
-import { OrganizationIconClient } from './OrganizationIconClient'
+import { LastCommitInfo } from './LastCommitInfo'
+import { OrganizationIcon } from './OrganizationIcon'
import { ProjectIcon } from './ProjectIcon'
import styles from './ProjectItem.module.css'
@@ -24,14 +19,6 @@
}
export const ProjectItem: FC<ProjectItemProps> = ({ project }) => {
- // State for organization avatar and commit info
- const [avatarUrl, setAvatarUrl] = useState<string | null>(null)
- const [commitInfo, setCommitInfo] = useState<{
- author: string
- date: string
- } | null>(null)
- const [isLoading, setIsLoading] = useState(true)
-
// Format date to "MMM DD, YYYY" format
const formatDate = (dateString: string) => {
const date = new Date(dateString)
@@ -45,39 +32,6 @@
const repositoryName = project.name?.toLowerCase() || 'untitled-project'
const repository = project.ProjectRepositoryMapping?.[0]?.repository
- // Fetch organization and commit data
- useEffect(() => {
- if (repository) {
- const fetchData = async () => {
- setIsLoading(true)
-
- try {
- // Fetch organization data
- const orgData = await getOrganizationData({
- installationId: repository.installationId,
- owner: repository.owner,
- repo: repository.name,
- })
- setAvatarUrl(orgData)
-
- // Fetch commit data
- const commitData = await getLastCommitData({
- installationId: repository.installationId,
- owner: repository.owner,
- repo: repository.name,
- })
- setCommitInfo(commitData)
- } catch (error) {
- console.error('Error fetching data:', error)
- } finally {
- setIsLoading(false)
- }
- }
-
- fetchData()
- }
- }, [repository])
The fetchData function is defined inside a useEffect hook without being wrapped in useCallback, which could lead to unnecessary re-renders. Additionally, there's no cleanup function to handle component unmounting during async operations, which could cause memory leaks or state updates on unmounted components.
frontend/apps/app/features/projects/components/ProjectItem/ProjectItem.tsx [48-79]
// Fetch organization and commit data
useEffect(() => {
+ let isMounted = true;
+
if (repository) {
const fetchData = async () => {
setIsLoading(true)
try {
// Fetch organization data
const orgData = await getOrganizationData({
installationId: repository.installationId,
owner: repository.owner,
repo: repository.name,
})
- setAvatarUrl(orgData)
+ if (isMounted) setAvatarUrl(orgData)
// Fetch commit data
const commitData = await getLastCommitData({
installationId: repository.installationId,
owner: repository.owner,
repo: repository.name,
})
- setCommitInfo(commitData)
+ if (isMounted) setCommitInfo(commitData)
} catch (error) {
console.error('Error fetching data:', error)
} finally {
- setIsLoading(false)
+ if (isMounted) setIsLoading(false)
}
}
fetchData()
}
+
+ return () => {
+ isMounted = false;
+ }
}, [repository])
[Suggestion has been applied]
Suggestion importance[1-10]: 8
__
Why: This suggestion addresses an important React pattern to prevent memory leaks by adding cleanup logic that prevents state updates after component unmounting. This is a significant improvement that helps avoid potential bugs when asynchronous operations complete after a component is no longer mounted.
| Medium
|
| General |
Eliminate duplicate API call
The function duplicates logic already present in getLastCommitData(). Consider reusing that function instead of directly calling getLastCommit() again, which would reduce code duplication and ensure consistent behavior between the two functions.
frontend/apps/app/features/projects/components/ProjectItem/LastCommitInfo.tsx [34-56]
export async function LastCommitInfo({
installationId,
owner,
repo,
defaultDate,
}: LastCommitInfoProps) {
try {
- const commitInfo = await getLastCommit(installationId, owner, repo)
+ const commitData = await getLastCommitData({ installationId, owner, repo })
- if (commitInfo) {
+ if (commitData) {
return (
<LastCommitInfoClient
- author={commitInfo.author}
- date={commitInfo.date}
+ author={commitData.author}
+ date={commitData.date}
/>
)
}
} catch (error) {
console.error('Failed to fetch last commit info:', error)
}
return <LastCommitInfoClient date={defaultDate} />
}
Suggestion importance[1-10]: 7
__
Why: The suggestion correctly identifies code duplication where LastCommitInfo directly calls getLastCommit instead of reusing the existing getLastCommitData function. This refactoring would improve maintainability and ensure consistent behavior between the two functions.
| Medium
|
Learned best practice |
Add input validation for required parameters before processing to prevent potential runtime errors
Add validation for required parameters at the beginning of the function to prevent potential runtime errors. The function should validate that
installationId, owner, and repo are valid values before attempting to fetch commit information.
frontend/apps/app/features/projects/components/ProjectItem/LastCommitInfo.tsx [34-56]
export async function LastCommitInfo({
installationId,
owner,
repo,
defaultDate,
}: LastCommitInfoProps) {
+ if (!installationId || !owner || !repo) {
+ console.error('Missing required parameters for LastCommitInfo');
+ return <LastCommitInfoClient date={defaultDate} />;
+ }
+
try {
const commitInfo = await getLastCommit(installationId, owner, repo)
if (commitInfo) {
return (
<LastCommitInfoClient
author={commitInfo.author}
date={commitInfo.date}
/>
)
}
} catch (error) {
console.error('Failed to fetch last commit info:', error)
}
return <LastCommitInfoClient date={defaultDate} />
}
Suggestion importance[1-10]: 6
| Low
|
|
| |
Suggestions up to commit 3ee5ddc
| Category | Suggestion | Impact |
| General |
Eliminate duplicate code
The function duplicates logic that already exists in getLastCommitData(). You should reuse the existing function to avoid code duplication and ensure consistent behavior.
frontend/apps/app/features/projects/components/ProjectItem/LastCommitInfo.tsx [34-56]
export async function LastCommitInfo({
installationId,
owner,
repo,
defaultDate,
}: LastCommitInfoProps) {
- try {
- const commitInfo = await getLastCommit(installationId, owner, repo)
-
- if (commitInfo) {
- return (
- <LastCommitInfoClient
- author={commitInfo.author}
- date={commitInfo.date}
- />
- )
- }
- } catch (error) {
- console.error('Failed to fetch last commit info:', error)
- }
-
- return <LastCommitInfoClient date={defaultDate} />
+ const commitInfo = await getLastCommitData({ installationId, owner, repo });
+
+ return (
+ <LastCommitInfoClient
+ author={commitInfo?.author}
+ date={commitInfo?.date || defaultDate}
+ />
+ );
}
Suggestion importance[1-10]: 7
__
Why: The suggestion correctly identifies code duplication between LastCommitInfo and getLastCommitData functions. Refactoring to reuse the existing function improves maintainability and reduces the chance of inconsistent behavior between the two implementations.
| Medium
|
Learned best practice |
Add input validation to check for required parameters before making API calls to prevent runtime errors
Add input validation at the beginning of the function to check if required parameters are present and valid before making the API call. This prevents unnecessary API calls and potential runtime errors when parameters are missing or invalid.
frontend/apps/app/features/projects/components/ProjectItem/LastCommitInfo.tsx [34-56]
export async function LastCommitInfo({
installationId,
owner,
repo,
defaultDate,
}: LastCommitInfoProps) {
+ // Validate required parameters
+ if (!installationId || !owner || !repo) {
+ console.error('Missing required parameters for LastCommitInfo')
+ return <LastCommitInfoClient date={defaultDate} />
+ }
+
try {
const commitInfo = await getLastCommit(installationId, owner, repo)
if (commitInfo) {
return (
<LastCommitInfoClient
author={commitInfo.author}
date={commitInfo.date}
/>
)
}
} catch (error) {
console.error('Failed to fetch last commit info:', error)
}
return <LastCommitInfoClient date={defaultDate} />
}
Suggestion importance[1-10]: 6
| Low
|
| Possible issue |
Fix effect dependency
The effect doesn't handle the case when repository is undefined but later becomes defined. Add a condition to also run the effect when isLoading is true to ensure data is fetched when repository becomes available.
frontend/apps/app/features/projects/components/ProjectItem/ProjectItem.tsx [49-79]
useEffect(() => {
if (repository) {
const fetchData = async () => {
setIsLoading(true)
try {
// Fetch organization data
const orgData = await getOrganizationData({
installationId: repository.installationId,
owner: repository.owner,
repo: repository.name,
})
setAvatarUrl(orgData)
// Fetch commit data
const commitData = await getLastCommitData({
installationId: repository.installationId,
owner: repository.owner,
repo: repository.name,
})
setCommitInfo(commitData)
} catch (error) {
console.error('Error fetching data:', error)
} finally {
setIsLoading(false)
}
}
fetchData()
}
-}, [repository])
+}, [repository, isLoading])
Suggestion importance[1-10]: 3
__
Why: Adding isLoading to the dependency array could create an infinite loop since isLoading is set within the effect itself. This would cause the effect to run again whenever isLoading changes, which happens in every execution of the effect.
| Low
|
|
| |
This pull request refactors the database schema by renaming tables, columns, and indexes to snake_case with pluralized names and updates foreign keys accordingly. The key concern is ensuring robust rollback and data integrity during these atomic migration operations. Overall, the changes align with project rules and enhance maintainability and security while warranting thorough testing of performance and RLS impacts.
Migration URL: https://liam-app-git-staging-route-06-core.vercel.app/app/projects/9d777f64-400a-42f3-a60e-98a59fc97279/ref/devin%2F1744807573-project-last-commit-time/migrations/58440237-7d77-44e1-b8ee-5e023ac6117a
ER Diagram:
- View ERD for frontend/packages/db/schema/schema.sql: https://liam-app-git-staging-route-06-core.vercel.app/app/projects/9d777f64-400a-42f3-a60e-98a59fc97279/ref/devin%2F1744807573-project-last-commit-time/schema/frontend/packages/db/schema/schema.sql