-
Notifications
You must be signed in to change notification settings - Fork 428
Template migrations #778
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
Template migrations #778
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
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.
Greptile Summary
This PR implements a comprehensive email template migration system, transitioning from Handlebars-based templates to React-based components using @react-email/components
. The changes include:
- A new migration utility that converts Handlebars syntax to JSX
- Internal API endpoints for template conversion
- UI components for administrators to monitor and control the migration process
- Enhanced type safety improvements across the codebase
- Fallback mechanisms for email themes to ensure system stability during migration
The implementation allows for a controlled, project-by-project migration approach with proper error handling and progress tracking. The code is well-structured with clear separation between the migration utility, API endpoints, and UI components.
Confidence score: 4/5
- This PR is generally safe to merge as it includes proper error handling and fallback mechanisms
- The high score is due to comprehensive test coverage and careful handling of edge cases, only slightly reduced due to the complexity of template conversion
- Key files needing attention:
- apps/backend/src/app/api/latest/internal/email-templates/temp/convert.tsx - Core conversion logic
- apps/backend/src/app/api/latest/internal/email-themes/route.tsx - Theme fallback mechanism
10 files reviewed, 10 comments
Edit PR Review Bot Settings | Greptile
for (const projectId of projectIds) { | ||
const result = await adminApp.convertEmailTemplates(projectId); | ||
setMigrationResults(prev => [...prev, { | ||
projectId, | ||
templatesConverted: result.templates_converted, | ||
totalTemplates: result.total_templates | ||
}]); | ||
} |
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.
style: Migration is sequential and could take a long time for many projects. Consider using Promise.all() or batching for parallel processing
|
||
export function getTransformedTemplateMetadata(metadata: EmailTemplateMetadata) { | ||
const variableNames = metadata.variables.map(v => v.name); | ||
const configuration = metadata.defaultContent[2]; |
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.
logic: Accessing defaultContent[2] directly is fragile. Add type checking or document the expected structure
} catch (error) { | ||
console.error("Error running migration:", error); | ||
} finally { |
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.
logic: Error handling is too generic. Need to handle failed migrations individually and provide retry mechanism
@@ -67,10 +67,10 @@ export function createHistoryAdapter( | |||
return { | |||
async load() { | |||
const { messages } = await adminApp.listChatMessages(threadId); | |||
return { messages } as ExportedMessageRepository; | |||
return { messages } as unknown as ExportedMessageRepository; |
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.
style: Double type assertion (as unknown as
) could mask potential type mismatches. Consider creating a proper type conversion function.
${indent} }} | ||
${indent} target="_blank" | ||
${indent} > |
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.
logic: target="_blank" links should include rel="noopener" for security
${indent} }} | |
${indent} target="_blank" | |
${indent} > | |
${indent} }} | |
${indent} target="_blank" | |
${indent} rel="noopener" | |
${indent} > |
const ids = await adminApp.getProjectIdsForTemplatesMigration(); | ||
setProjectIds(ids); | ||
} catch (error) { | ||
console.error("Error getting project IDs:", error); |
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.
logic: Error not displayed to user. Add error toast/alert for better UX
if (DEFAULT_EMAIL_THEME_ID in themeList) { | ||
newActiveTheme = DEFAULT_EMAIL_THEME_ID; | ||
} else { | ||
newActiveTheme = Object.keys(themeList)[0]; |
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.
logic: consider checking if themeList is empty before accessing index 0
}, | ||
async append(message) { | ||
await adminApp.saveChatMessage(threadId, message); | ||
await adminApp.saveChatMessage(threadId, message as unknown as ChatMessage); |
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.
logic: Use type guard or validation before casting to ChatMessage to ensure message has required structure
const templateMetadata = { | ||
...defaultTemplateMetadata, | ||
defaultContent: { | ||
[2]: template.content, |
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.
style: Magic number '2' used for content version - consider defining this as a constant with documentation on its significance
[2]: template.content, | ||
}, | ||
}; | ||
emailTemplates[configTemplateId] = getTransformedTemplateMetadata(templateMetadata as unknown as EmailTemplateMetadata); |
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.
style: Type assertion to unknown then EmailTemplateMetadata suggests potential type mismatch - consider restructuring to avoid unsafe type casting
if (DEFAULT_EMAIL_THEME_ID in themeList) { | ||
newActiveTheme = DEFAULT_EMAIL_THEME_ID; | ||
} else { | ||
newActiveTheme = Object.keys(themeList)[0]; |
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 code assumes themeList has at least one entry when falling back to Object.keys(themeList)[0]. If themeList is empty, this will assign undefined to newActiveTheme, which will cause a runtime error when used as a configuration value. The code should validate that themeList is not empty before accessing the first key.
newActiveTheme = Object.keys(themeList)[0]; | |
newActiveTheme = Object.keys(themeList).length > 0 ? Object.keys(themeList)[0] : DEFAULT_EMAIL_THEME_ID; |
React with 👍 to tell me that this comment was useful, or 👎 if not (and I'll stop posting more comments like this in the future)
😱 Found 1 issue. Time to roll up your sleeves! 😱 🗒️ View all ignored comments in this repo
Need help? Join our Discord for support! |
@@ -67,10 +67,10 @@ export function createHistoryAdapter( | |||
return { | |||
async load() { | |||
const { messages } = await adminApp.listChatMessages(threadId); | |||
return { messages } as ExportedMessageRepository; | |||
return { messages } as unknown as ExportedMessageRepository; |
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.
Avoid casting with 'as unknown as ...' in type conversions; define proper types to ensure type safety for message repositories and chat messages.
updateNewEmailTemplate(id: string, tsxSource: string): Promise<{ renderedHtml: string }>, | ||
|
||
getProjectIdsForTemplatesMigration(): Promise<string[]>, |
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.
Remember to add end-to-end tests for new backend endpoints (e.g. email templates migration and theme endpoints) to cover the updated behavior.
This comment was generated because it violated a code review rule: mrule_PmPnAQVEfJISxhHC.
Important
This PR adds functionality for migrating email templates and themes, including backend API endpoints and frontend UI components for managing the migration process.
POST
andGET
handlers inroute.tsx
for email template conversion, handling project-specific template migration.convertHandlebarsToJSX
andgenerateTsxSourceFromConfiguration
inconvert.tsx
for transforming email templates.route.tsx
inemail-themes
to manage email themes, ensuring active theme consistency.page-client.tsx
for managing template migration UI, including project ID retrieval and migration execution.page.tsx
to render thePageClient
component.admin-interface.ts
with methods for fetching project IDs and converting email templates.admin-app-impl.ts
andadmin-app.ts
to include new migration methods and cache management.This description was created by
for 871eda9. You can customize this summary. It will automatically update as commits are pushed.