-
Notifications
You must be signed in to change notification settings - Fork 5
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
Mailchimp #1332
Mailchimp #1332
Changes from 17 commits
e4f4ef8
51ec5fd
4a7a770
84949f9
34c6650
5d55071
0305ed4
6b6aa6b
4d9a4a9
a4083db
4491f0f
3a84785
78de270
b7b41ad
6d0f6ce
f71eac0
5dbf3e4
e93b0ca
1bd3858
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,54 @@ | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
metadata: | ||
name: mailchimp-syncer | ||
labels: | ||
app: mailchimp-syncer | ||
deploymentType: with-init-container | ||
needs-db: "true" | ||
spec: | ||
replicas: 1 | ||
selector: | ||
matchLabels: | ||
app: mailchimp-syncer | ||
template: | ||
metadata: | ||
annotations: | ||
linkerd.io/inject: enabled | ||
labels: | ||
app: mailchimp-syncer | ||
spec: | ||
containers: | ||
- name: mailchimp-syncer | ||
image: headless-lms | ||
command: ["bin/run", "mailchimp-syncer"] | ||
resources: | ||
requests: | ||
memory: 100Mi | ||
cpu: 20m | ||
limits: | ||
memory: 300Mi | ||
cpu: 200m | ||
envFrom: | ||
- secretRef: | ||
name: headless-lms-secrets | ||
initContainers: | ||
- name: headless-lms-wait-for-db | ||
image: headless-lms | ||
command: | ||
- bash | ||
- "-c" | ||
- | | ||
echo Waiting for postgres to be available | ||
timeout 120 ./wait-for-db.sh | ||
./wait-for-db-migrations.sh | ||
resources: | ||
requests: | ||
memory: 100Mi | ||
cpu: 20m | ||
limits: | ||
memory: 300Mi | ||
cpu: 200m | ||
envFrom: | ||
- secretRef: | ||
name: headless-lms-secrets |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,13 @@ import { UseMutationResult, useQuery } from "@tanstack/react-query" | |
import React, { useEffect, useState } from "react" | ||
import { useTranslation } from "react-i18next" | ||
|
||
import { fetchBackgroundQuestionsAndAnswers } from "../../services/backend" | ||
import { | ||
fetchBackgroundQuestionsAndAnswers, | ||
fetchCourseById, | ||
updateMarketingConsent, | ||
} from "../../services/backend" | ||
|
||
import SelectMarketingConsentForm from "./SelectMarketingConsentForm" | ||
|
||
import { CourseInstance, NewCourseBackgroundQuestionAnswer } from "@/shared-module/common/bindings" | ||
import Button from "@/shared-module/common/components/Button" | ||
|
@@ -40,24 +46,41 @@ interface SelectCourseInstanceFormProps { | |
> | ||
initialSelectedInstanceId?: string | ||
dialogLanguage: string | ||
selectedLangCourseId: string | ||
} | ||
|
||
const SelectCourseInstanceForm: React.FC< | ||
React.PropsWithChildren<SelectCourseInstanceFormProps> | ||
> = ({ courseInstances, submitMutation, initialSelectedInstanceId, dialogLanguage }) => { | ||
> = ({ | ||
courseInstances, | ||
submitMutation, | ||
initialSelectedInstanceId, | ||
dialogLanguage, | ||
selectedLangCourseId, | ||
}) => { | ||
const { t } = useTranslation("course-material", { lng: dialogLanguage }) | ||
const [selectedInstanceId, setSelectedInstanceId] = useState( | ||
figureOutInitialValue(courseInstances, initialSelectedInstanceId), | ||
) | ||
const [additionalQuestionAnswers, setAdditionalQuestionAnswers] = useState< | ||
NewCourseBackgroundQuestionAnswer[] | ||
>([]) | ||
|
||
const [isMarketingConsentChecked, setIsMarketingConsentChecked] = useState(false) | ||
const [isEmailSubscriptionConsentChecked, setIsEmailSubscriptionConsentChecked] = useState(false) | ||
|
||
const additionalQuestionsQuery = useQuery({ | ||
queryKey: ["additional-questions", selectedInstanceId], | ||
queryFn: () => fetchBackgroundQuestionsAndAnswers(assertNotNullOrUndefined(selectedInstanceId)), | ||
enabled: selectedInstanceId !== undefined, | ||
}) | ||
|
||
const getCourse = useQuery({ | ||
queryKey: ["courses", selectedLangCourseId], | ||
queryFn: () => fetchCourseById(selectedLangCourseId as NonNullable<string>), | ||
enabled: selectedLangCourseId !== null, | ||
}) | ||
Comment on lines
+78
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct the 'enabled' condition in the The |
||
|
||
useEffect(() => { | ||
if (!additionalQuestionsQuery.data) { | ||
return | ||
|
@@ -100,6 +123,14 @@ const SelectCourseInstanceForm: React.FC< | |
backgroundQuestionAnswers: additionalQuestionAnswers, | ||
}) | ||
} | ||
if (getCourse.isSuccess) { | ||
await updateMarketingConsent( | ||
getCourse.data.id, | ||
getCourse.data.course_language_group_id, | ||
isEmailSubscriptionConsentChecked, | ||
isMarketingConsentChecked, | ||
) | ||
} | ||
Comment on lines
+126
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for Currently, |
||
} | ||
|
||
const additionalQuestions = additionalQuestionsQuery.data?.background_questions | ||
|
@@ -191,12 +222,25 @@ const SelectCourseInstanceForm: React.FC< | |
{additionalQuestionsQuery.error && ( | ||
<ErrorBanner variant="readOnly" error={additionalQuestionsQuery.error} /> | ||
)} | ||
{getCourse.data?.ask_marketing_consent && ( | ||
<div> | ||
<SelectMarketingConsentForm | ||
courseId={selectedLangCourseId} | ||
onEmailSubscriptionConsentChange={setIsEmailSubscriptionConsentChecked} | ||
onMarketingConsentChange={setIsMarketingConsentChecked} | ||
/> | ||
</div> | ||
)} | ||
<div> | ||
<Button | ||
size="medium" | ||
variant="primary" | ||
onClick={enrollOnCourse} | ||
disabled={!selectedInstanceId || additionalQuestionsQuery.isPending} | ||
disabled={ | ||
!selectedInstanceId || | ||
additionalQuestionsQuery.isPending || | ||
(getCourse.data?.ask_marketing_consent && !isEmailSubscriptionConsentChecked) | ||
} | ||
Comment on lines
+239
to
+243
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure both consents are required before enabling the 'Continue' button The |
||
data-testid="select-course-instance-continue-button" | ||
> | ||
{t("continue")} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { useQuery } from "@tanstack/react-query" | ||
import { t } from "i18next" | ||
import React, { useEffect, useState } from "react" | ||
|
||
import { fetchUserMarketingConsent } from "@/services/backend" | ||
import CheckBox from "@/shared-module/common/components/InputFields/CheckBox" | ||
import { assertNotNullOrUndefined } from "@/shared-module/common/utils/nullability" | ||
|
||
interface SelectMarketingConsentFormProps { | ||
courseId: string | ||
onEmailSubscriptionConsentChange: (isChecked: boolean) => void | ||
onMarketingConsentChange: (isChecked: boolean) => void | ||
} | ||
|
||
const SelectMarketingConsentForm: React.FC<SelectMarketingConsentFormProps> = ({ | ||
courseId, | ||
onEmailSubscriptionConsentChange, | ||
onMarketingConsentChange, | ||
}) => { | ||
const [marketingConsent, setMarketingConsent] = useState(false) | ||
const [emailSubscriptionConsent, setEmailSubscriptionConsent] = useState(false) | ||
|
||
const fetchInitialMarketingConsent = useQuery({ | ||
queryKey: ["marketing-consent", courseId], | ||
queryFn: () => fetchUserMarketingConsent(assertNotNullOrUndefined(courseId)), | ||
enabled: courseId !== undefined, | ||
}) | ||
|
||
const handleEmailSubscriptionConsentChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const isChecked = event.target.checked | ||
onEmailSubscriptionConsentChange(isChecked) | ||
setEmailSubscriptionConsent(isChecked) | ||
} | ||
|
||
const handleMarketingConsentChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const isChecked = event.target.checked | ||
onMarketingConsentChange(isChecked) | ||
setMarketingConsent(isChecked) | ||
} | ||
|
||
useEffect(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this you useQuery hook and after that useEffect if needed |
||
if (fetchInitialMarketingConsent.isSuccess) { | ||
setMarketingConsent(fetchInitialMarketingConsent.data.consent) | ||
const emailSub = | ||
fetchInitialMarketingConsent.data.email_subscription_in_mailchimp === "subscribed" | ||
setEmailSubscriptionConsent(emailSub) | ||
} | ||
}, [fetchInitialMarketingConsent.data, fetchInitialMarketingConsent.isSuccess]) | ||
|
||
return ( | ||
<> | ||
<CheckBox | ||
label={t("marketing-consent-checkbox-text")} | ||
type="checkbox" | ||
checked={marketingConsent} | ||
onChange={handleMarketingConsentChange} | ||
></CheckBox> | ||
<CheckBox | ||
label={t("marketing-consent-privacy-policy-checkbox-text")} | ||
type="checkbox" | ||
checked={emailSubscriptionConsent} | ||
onChange={handleEmailSubscriptionConsentChange} | ||
></CheckBox> | ||
</> | ||
) | ||
} | ||
|
||
export default SelectMarketingConsentForm |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,12 @@ import React, { useContext, useEffect, useId, useState } from "react" | |
import { useTranslation } from "react-i18next" | ||
|
||
import PageContext from "../../contexts/PageContext" | ||
import { fetchCourseInstances, postSaveCourseSettings } from "../../services/backend" | ||
import { | ||
fetchCourseById, | ||
fetchCourseInstances, | ||
fetchUserMarketingConsent, | ||
postSaveCourseSettings, | ||
} from "../../services/backend" | ||
import SelectCourseLanguage from "../SelectCourseLanguage" | ||
import SelectCourseInstanceForm from "../forms/SelectCourseInstanceForm" | ||
|
||
|
@@ -23,6 +28,7 @@ import LoginStateContext from "@/shared-module/common/contexts/LoginStateContext | |
import useToastMutation from "@/shared-module/common/hooks/useToastMutation" | ||
import { baseTheme, fontWeights, primaryFont, typography } from "@/shared-module/common/styles" | ||
import { LANGUAGE_COOKIE_KEY } from "@/shared-module/common/utils/constants" | ||
import { assertNotNullOrUndefined } from "@/shared-module/common/utils/nullability" | ||
import withErrorBoundary from "@/shared-module/common/utils/withErrorBoundary" | ||
|
||
export interface CourseSettingsModalProps { | ||
|
@@ -71,6 +77,18 @@ const CourseSettingsModal: React.FC<React.PropsWithChildren<CourseSettingsModalP | |
}) | ||
sortInstances() | ||
|
||
const askMarketingConsent = useQuery({ | ||
queryKey: ["courses", selectedLangCourseId], | ||
queryFn: () => fetchCourseById(selectedLangCourseId as NonNullable<string>), | ||
enabled: selectedLangCourseId !== null, | ||
}).data?.ask_marketing_consent | ||
|
||
Comment on lines
+80
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle loading and error states for The |
||
const checkUserMarketingConsent = useQuery({ | ||
queryKey: ["marketing-consent", selectedLangCourseId], | ||
queryFn: () => fetchUserMarketingConsent(assertNotNullOrUndefined(selectedLangCourseId)), | ||
enabled: selectedLangCourseId !== undefined, | ||
}).data?.email_subscription_in_mailchimp | ||
|
||
useEffect(() => { | ||
getCourseInstances.refetch() | ||
sortInstances() | ||
|
@@ -88,8 +106,12 @@ const CourseSettingsModal: React.FC<React.PropsWithChildren<CourseSettingsModalP | |
const shouldChooseInstance = | ||
pageState.state === "ready" && pageState.instance === null && pageState.settings === null | ||
|
||
setOpen((signedIn && shouldChooseInstance) || (signedIn && manualOpen)) | ||
}, [loginState, pageState, manualOpen]) | ||
setOpen( | ||
(signedIn && shouldChooseInstance) || | ||
(signedIn && manualOpen) || | ||
(signedIn && askMarketingConsent === true && checkUserMarketingConsent === "unsubscribed"), | ||
) | ||
}, [loginState, pageState, manualOpen, askMarketingConsent, checkUserMarketingConsent]) | ||
Comment on lines
+109
to
+114
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improve the condition for opening the modal The condition for setting |
||
|
||
const languageChanged = savedOrDefaultLangCourseId !== selectedLangCourseId | ||
|
||
|
@@ -182,9 +204,11 @@ const CourseSettingsModal: React.FC<React.PropsWithChildren<CourseSettingsModalP | |
pageState.settings?.current_course_instance_id ?? pageState.instance?.id | ||
} | ||
dialogLanguage={dialogLanguage} | ||
selectedLangCourseId={selectedLangCourseId} | ||
/> | ||
)} | ||
</div> | ||
|
||
{languageChanged && ( | ||
<div | ||
className={css` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -68,6 +68,10 @@ fn main() -> Result<()> { | |
name: "chatbot-syncer", | ||
execute: Box::new(|| tokio_run(programs::chatbot_syncer::main())), | ||
}, | ||
Program { | ||
name: "mailchimp-syncer", | ||
execute: Box::new(|| tokio_run(programs::mailchimp_syncer::main())), | ||
}, | ||
Comment on lines
+71
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Documentation is missing for the mailchimp-syncer program The search results confirm that while there are extensive code implementations related to marketing consent and the mailchimp-syncer program, there is no operator documentation describing:
The program appears to be a critical component that synchronizes marketing consents with Mailchimp, and proper documentation would help operators maintain and troubleshoot it effectively. 🔗 Analysis chainConsider adding operator documentation for the new program. Since this is a new program that handles marketing consent synchronization with Mailchimp, it would be helpful to document:
Let's check if documentation exists: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Look for documentation about the mailchimp-syncer program
# Expected: Find documentation in README or docs
# Search for documentation files
fd -t f "README|DOCUMENTATION|\.md$"
# Search for mailchimp-syncer mentions in documentation
rg -i "mailchimp.?syncer|marketing.?consent"
Length of output: 18085 |
||
]; | ||
|
||
let program_name = std::env::args().nth(1).unwrap_or_else(|| { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
ALTER TABLE courses DROP COLUMN ask_marketing_consent; | ||
DROP TABLE user_marketing_consents; | ||
DROP TABLE marketing_mailing_list_access_tokens; |
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.
🛠️ Refactor suggestion
Review database wait timeout and add health checks.
Two potential improvements:
Add health check probes: