-
-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/enable sync to crm #327
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { | ||
| Container, | ||
| Img, | ||
| Preview, | ||
| Section, | ||
| Text, | ||
| } from "@react-email/components"; | ||
| import * as React from "react"; | ||
| import { getAbsoluteUrl } from "@/utils/appUrl"; | ||
| import { EmailLayout } from "./Layout"; | ||
|
|
||
| export default function TaggingComplete({ | ||
| dataSourceName, | ||
| viewName, | ||
| }: { | ||
| dataSourceName: string; | ||
| viewName: string; | ||
| }) { | ||
| const baseUrl = getAbsoluteUrl(); | ||
|
|
||
| return ( | ||
| <EmailLayout> | ||
| <Preview>Tagging complete for {dataSourceName}</Preview> | ||
| <Container className="mx-auto my-[40px] max-w-[465px] rounded border border-[#eaeaea] border-solid p-[20px]"> | ||
| <Section className="mt-[32px]"> | ||
| <Img | ||
| alt="Mapped logo" | ||
| src={`${baseUrl}logo.png`} | ||
| width="40" | ||
| height="40" | ||
| className="mx-auto my-0" | ||
| /> | ||
| </Section> | ||
|
|
||
| <Text className="text-[14px] text-black font-sans leading-[24px]"> | ||
| Hello, | ||
| </Text> | ||
|
|
||
| <Text className="text-[14px] text-black font-sans leading-[24px]"> | ||
| The tagging job for data source <strong>{dataSourceName}</strong> with | ||
| view <strong>{viewName}</strong> has completed successfully. | ||
| </Text> | ||
|
|
||
| <Text className="text-[14px] text-black font-sans leading-[24px]"> | ||
| All records have been tagged accordingly. | ||
| </Text> | ||
| </Container> | ||
| </EmailLayout> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { | ||
| Container, | ||
| Img, | ||
| Preview, | ||
| Section, | ||
| Text, | ||
| } from "@react-email/components"; | ||
| import * as React from "react"; | ||
|
|
||
| import { getAbsoluteUrl } from "@/utils/appUrl"; | ||
|
|
||
| import { EmailLayout } from "./Layout"; | ||
|
|
||
| export default function TaggingFailed({ | ||
| dataSourceName, | ||
| viewName, | ||
| reason, | ||
| }: { | ||
| dataSourceName: string; | ||
| viewName: string; | ||
| reason: string; | ||
| }) { | ||
| const baseUrl = getAbsoluteUrl(); | ||
|
|
||
| return ( | ||
| <EmailLayout> | ||
| <Preview>Tagging failed for {dataSourceName}</Preview> | ||
| <Container className="mx-auto my-[40px] max-w-[465px] rounded border border-[#eaeaea] border-solid p-[20px]"> | ||
| <Section className="mt-[32px]"> | ||
| <Img | ||
| alt="Mapped logo" | ||
| src={`${baseUrl}logo.png`} | ||
| width="40" | ||
| height="40" | ||
| className="mx-auto my-0" | ||
| /> | ||
| </Section> | ||
| <Text className="text-[14px] text-black font-sans leading-[24px]"> | ||
| Hello, | ||
| </Text> | ||
| <Text className="text-[14px] text-black font-sans leading-[24px]"> | ||
| The tagging job for data source <strong>{dataSourceName}</strong> with | ||
| view <strong>{viewName}</strong> has failed. | ||
| </Text> | ||
| <Text className="text-[14px] text-black font-sans leading-[24px]"> | ||
| Reason: {reason} | ||
| </Text> | ||
| </Container> | ||
| </EmailLayout> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,38 +1,70 @@ | ||||||||||||||||||
| import { DATA_SOURCE_JOB_BATCH_SIZE } from "@/constants"; | ||||||||||||||||||
| import { getDataSourceAdaptor } from "@/server/adaptors"; | ||||||||||||||||||
| import TaggingComplete from "@/server/emails/TaggingComplete"; | ||||||||||||||||||
| import TaggingFailed from "@/server/emails/TaggingFailed"; | ||||||||||||||||||
| import { | ||||||||||||||||||
| countDataRecordsForDataSource, | ||||||||||||||||||
| streamDataRecordsByDataSource, | ||||||||||||||||||
| } from "@/server/repositories/DataRecord"; | ||||||||||||||||||
| import { findDataSourceById } from "@/server/repositories/DataSource"; | ||||||||||||||||||
| import { findMapViewById } from "@/server/repositories/MapView"; | ||||||||||||||||||
| import logger from "@/server/services/logger"; | ||||||||||||||||||
| import { sendEmail } from "@/server/services/mailer"; | ||||||||||||||||||
| import { batchAsync } from "@/server/utils"; | ||||||||||||||||||
| import { findMapById } from "../repositories/Map"; | ||||||||||||||||||
| import type { TaggedRecord } from "@/types"; | ||||||||||||||||||
|
|
||||||||||||||||||
| const sendFailureEmail = async ( | ||||||||||||||||||
| userEmail: string, | ||||||||||||||||||
| dataSourceName: string, | ||||||||||||||||||
| viewName: string, | ||||||||||||||||||
| reason: string, | ||||||||||||||||||
| ) => { | ||||||||||||||||||
| try { | ||||||||||||||||||
| await sendEmail( | ||||||||||||||||||
| userEmail, | ||||||||||||||||||
| "Tagging failed", | ||||||||||||||||||
| TaggingFailed({ dataSourceName, viewName, reason }), | ||||||||||||||||||
| ); | ||||||||||||||||||
| } catch (error) { | ||||||||||||||||||
| logger.error("Failed to send tagging failure email", { error }); | ||||||||||||||||||
| } | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const tagDataSource = async (args: object | null): Promise<boolean> => { | ||||||||||||||||||
| if (!args || !("dataSourceId" in args) || !("viewId" in args)) { | ||||||||||||||||||
| if ( | ||||||||||||||||||
| !args || | ||||||||||||||||||
| !("dataSourceId" in args) || | ||||||||||||||||||
| !("viewId" in args) || | ||||||||||||||||||
| !("userEmail" in args) | ||||||||||||||||||
| ) { | ||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
| const dataSourceId = String(args.dataSourceId); | ||||||||||||||||||
| const viewId = String(args.viewId); | ||||||||||||||||||
| const userEmail = String(args.userEmail); | ||||||||||||||||||
|
||||||||||||||||||
|
|
||||||||||||||||||
| const dataSource = await findDataSourceById(dataSourceId); | ||||||||||||||||||
|
||||||||||||||||||
| if (!dataSource) { | ||||||||||||||||||
| logger.info(`Data source ${dataSourceId} not found.`); | ||||||||||||||||||
| const reason = `Failed to tag data source: ${dataSourceId} not found.`; | ||||||||||||||||||
| logger.warn(reason); | ||||||||||||||||||
| await sendFailureEmail(userEmail, dataSourceId, viewId, reason); | ||||||||||||||||||
|
||||||||||||||||||
| await sendFailureEmail(userEmail, dataSourceId, viewId, reason); | |
| await sendFailureEmail( | |
| userEmail, | |
| "Unknown data source", | |
| "Unknown view", | |
| reason, | |
| ); |
Copilot
AI
Feb 19, 2026
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.
When the view is not found, the email is sent with the viewId as the viewName parameter. This will display a UUID in the email instead of a user-friendly name. Consider using a placeholder like "Unknown View" or formatting the ID more clearly as "View (ID: ...)" to improve the user experience.
Copilot
AI
Feb 19, 2026
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.
When the view is not found, the function passes viewId (a UUID) as the viewName parameter. This will result in the user receiving an email with a technical ID instead of a human-readable name. Consider passing a more user-friendly value or having a fallback message in the email template for unknown views.
| await sendFailureEmail(userEmail, dataSource.name, viewId, reason); | |
| await sendFailureEmail(userEmail, dataSource.name, "Unknown view", reason); |
Copilot
AI
Feb 19, 2026
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.
If sendFailureEmail throws an exception, this failure path will also fail. Consider wrapping the email operation in a try-catch block to ensure that failures to send email notifications don't prevent proper logging and return of the job failure status.
Copilot
AI
Feb 19, 2026
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.
If sendFailureEmail throws an exception here, it will mask the original error. Consider wrapping this email operation in a try-catch block and logging the email sending error separately, while still re-throwing the original error to maintain proper job failure handling.
| await sendFailureEmail(userEmail, dataSource.name, view.name, reason); | |
| try { | |
| await sendFailureEmail(userEmail, dataSource.name, view.name, reason); | |
| } catch (emailError) { | |
| logger.error("Failed to send failure email for tagDataSource job", { | |
| error: emailError, | |
| }); | |
| } |
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 import path is being changed to use PascalCase 'Invite', but the actual file is still named 'invite.tsx' (lowercase). This import will fail on case-sensitive file systems. Either the file needs to be renamed from 'invite.tsx' to 'Invite.tsx', or the import should remain as '@/server/emails/invite'. Based on the codebase convention where other email component files use PascalCase names (ForgotPassword.tsx, TaggingComplete.tsx), the file should be renamed.