def _get_short_diploma_vars(user: user_pb2.User, *, database: mongo.NoPiiMongoDatabase, **unused_kwargs: Any) -> dict[str, Any]: if not user.projects: raise scoring.NotEnoughDataException('No project yet', {'projects.0'}) project = user.projects[0] if user.projects[0].diagnostic.category_id != 'missing-diploma': raise campaign.DoNotSend('The user has no missing-diploma category') scoring_project = scoring.ScoringProject(project, user, database) login_url = campaign.create_logged_url(user.user_id, f'/projet/{project.project_id}') # TODO(sil): Let's check if this is needed to have access to the method. if not project.target_job.job_group.rome_id: raise scoring.NotEnoughDataException( 'Need a job group to find trainings', # TODO(pascal): Use project_id instead of 0. {'projects.0.targetJob.jobGroup.romeId'}) deep_link_training_url = \ campaign.get_deep_link_advice(user.user_id, project, 'training') return campaign.get_default_coaching_email_vars(user) | { 'deepTrainingAdviceUrl': deep_link_training_url, 'ofJobName': scoring_project.populate_template('%ofJobName'), 'productUrl': f'{login_url}?utm_source=bob-emploi&utm_medium=email', }
def _get_improve_cv_vars(user: user_pb2.User, *, now: datetime.datetime, **unused_kwargs: Any) -> dict[str, Any]: """Compute vars for the "Improve your CV" email.""" if user_profile_pb2.RESUME not in user.profile.frustrations: raise campaign.DoNotSend('User is not frustrated by its CV') if not user.projects: raise scoring.NotEnoughDataException('No project yet', {'projects.0'}) project = user.projects[0] if project.kind == project_pb2.FIND_A_FIRST_JOB: has_experience = 'False' elif project.kind in (project_pb2.FIND_A_NEW_JOB, project_pb2.FIND_ANOTHER_JOB): has_experience = 'True' else: has_experience = '' deep_link_advice_url = \ campaign.get_deep_link_advice(user.user_id, project, 'improve-resume') or \ campaign.get_deep_link_advice(user.user_id, project, 'fresh-resume') return campaign.get_default_coaching_email_vars(user) | { 'deepLinkAdviceUrl': deep_link_advice_url, 'hasExperience': has_experience, 'isSeptember': campaign.as_template_boolean(now.month == 9), 'loginUrl': campaign.create_logged_url(user.user_id) }
def _get_action_plan_vars(user: user_pb2.User, now: datetime.datetime, **unused_kwargs: Any) -> dict[str, Any]: if not user.projects: raise campaign.DoNotSend('User does not have any projects.') project = user.projects[0] plan_actions = sorted( [ action for action in project.actions if (action.status == action_pb2.ACTION_CURRENT or action.status == action_pb2.ACTION_DONE) ], key=lambda action: action.expected_completion_at.ToDatetime()) if not plan_actions or not project.HasField('action_plan_started_at'): raise campaign.DoNotSend('User does not have a ready action plan.') actions_by_sections = _make_action_lists(plan_actions, user.user_id, project.project_id, now) creation_date = i18n.translate_date( project.action_plan_started_at.ToDatetime(), user.profile.locale) # TODO(Sil): Put actions and sections visibility in the same object. # TODO(cyrille) Make the variables so that we can loop on sections directly. return campaign.get_default_coaching_email_vars(user) | { 'actionPlanUrl': campaign.create_logged_url( user.user_id, f'/projet/{project.project_id}/plan-action'), 'actions': actions_by_sections, 'creationDate': creation_date, 'numActionsBySections': { section: len(actions_by_sections.get(section, [])) for section in _ACTIONS_SECTIONS } }
def _format_action(action: action_pb2.Action, user_id: str, project_id: str) -> dict[str, Any]: return { 'title': action.title, 'url': campaign.create_logged_url( user_id, f'/projet/{project_id}/action/{action.action_id}') }
def test_create_logged_url(self) -> None: """Test the create logged url function.""" user_id = '02499e64387edfcc2ab7a948' base_url = 'https://www.bob-emploi.fr/project/0/wow-baker?' regex = re.compile( rf'^{re.escape(base_url)}authToken=(\d+\.[a-f0-9]+)&userId={user_id}$' ) logged_url = campaign.create_logged_url(user_id, '/project/0/wow-baker') self.assertRegex(logged_url, regex) match_token = regex.match(logged_url) assert match_token token = match_token.group(1) self.assertTrue(auth_token.check_token(user_id, token, role='auth'))
def test_logged_url(self) -> None: """Test that logged URL allows for actual authentication.""" user_id = self.create_user_with_token()[0] logged_url = campaign.create_logged_url(user_id, '/foo') params = parse.parse_qs(parse.urlparse(logged_url).query) self.assertEqual([user_id], params['userId']) [token] = params['authToken'] response = self.app.post('/api/user/authenticate', data=json.dumps({ 'userId': user_id, 'authToken': token }), content_type='application/json') auth_response = self.json_from_response(response) user_id = auth_response['authenticatedUser']['userId']
def _get_prepare_your_application_vars(user: user_pb2.User, **unused_kwargs: Any) -> dict[str, Any]: """Compute vars for the "Prepare your application" email.""" if not user.projects: raise scoring.NotEnoughDataException('No project yet', {'projects.0'}) project = user.projects[0] deep_link_motivation_email_url = \ campaign.get_deep_link_advice(user.user_id, project, 'motivation-email') return campaign.get_default_coaching_email_vars(user) | { 'deepLinkMotivationEmailUrl': deep_link_motivation_email_url, 'hasInterviewFrustration': campaign.as_template_boolean(user_profile_pb2.INTERVIEW in user.profile.frustrations), 'hasSelfConfidenceFrustration': campaign.as_template_boolean(user_profile_pb2.SELF_CONFIDENCE in user.profile.frustrations), 'loginUrl': campaign.create_logged_url(user.user_id, f'/projet/{project.project_id}'), }
def _get_find_diploma_vars(user: user_pb2.User, *, database: mongo.NoPiiMongoDatabase, **unused_kwargs: Any) -> dict[str, Any]: """Compute vars for the "Prepare your application" email.""" if not user.projects: raise scoring.NotEnoughDataException('No project yet', {'projects.0'}) project = user.projects[0] scoring_project = scoring.ScoringProject(project, user, database) if not any(s.strategy_id == 'get-diploma' for s in project.opened_strategies): raise campaign.DoNotSend( 'The user has not started a strategy to get a diploma') if not project.target_job.job_group.rome_id: raise scoring.NotEnoughDataException( 'Need a job group to find trainings', # TODO(pascal): Use project_id instead of 0. {'projects.0.targetJob.jobGroup.romeId'}) trainings = scoring_project.get_trainings()[:3] deep_link_training_url = \ campaign.get_deep_link_advice(user.user_id, project, 'training') return campaign.get_default_coaching_email_vars(user) | { 'deepTrainingAdviceUrl': deep_link_training_url, 'inDepartement': scoring_project.populate_template('%inDepartement'), 'loginUrl': campaign.create_logged_url(user.user_id, f'/projet/{project.project_id}'), 'numTrainings': len(trainings), 'ofJobName': scoring_project.populate_template('%ofJobName'), 'trainings': [json_format.MessageToDict(t) for t in trainings], }
def _get_jobbing_vars( user: user_pb2.User, *, database: mongo.NoPiiMongoDatabase, **unused_kwargs: Any) -> dict[str, Any]: """Compute vars for the "Jobbing" email.""" if not user.projects: raise scoring.NotEnoughDataException('No project yet', {'projects.0'}) project = user.projects[0] if not any(s.strategy_id == 'diploma-free-job' for s in project.opened_strategies): raise campaign.DoNotSend( 'The user has not started a strategy to get a job without a diploma') scoring_project = scoring.ScoringProject(project, user, database) model = scoring.get_scoring_model('advice-reorient-jobbing') if not model: raise campaign.DoNotSend('The advice-reorient-jobbing model is not implemented') reorient_jobs = typing.cast( reorient_jobbing_pb2.JobbingReorientJobs, model.get_expanded_card_data(scoring_project), ).reorient_jobbing_jobs if not reorient_jobs: raise campaign.DoNotSend("We didn't find any jobbing jobs to reorient to for the user") if project.target_job.name: of_job_name = scoring_project.populate_template('%ofJobName') else: # This is not translated to fr@tu because the email templates are only in fr for now. of_job_name = 'de definir votre projet professionnel' return campaign.get_default_coaching_email_vars(user) | { 'inDepartement': scoring_project.populate_template('%inDepartement'), 'jobs': [{'name': job.name} for job in reorient_jobs], 'loginUrl': campaign.create_logged_url(user.user_id, f'/projet/{project.project_id}'), 'ofJobName': of_job_name, }
def _account_deletion_notice_vars(user: user_pb2.User, **unused_kwargs: Any) -> dict[str, str]: return dict( campaign.get_default_vars(user), loginUrl=campaign.create_logged_url(user.user_id))
# "des personnes qui travaillent comme {{var:jobNameInDepartement}} ont décroché leur poste" # That'd make a weird but ok sentence and should not happened anyway as this block relies # on job info. genderized_job_name = scoring_project.translate_static_string('vous') job_name_in_departement = f'{genderized_job_name} {in_departement}' of_job_name_in_departement = f'{of_job_name} {in_departement}' return campaign.get_default_coaching_email_vars(user) | { 'applicationModes': _make_section(application_modes_section), 'departements': _make_section(departements_section), 'employmentType': _make_section(employment_types_section), 'imtLink': imt_link, 'inCity': scoring_project.populate_template('%inCity'), 'jobNameInDepartement': job_name_in_departement, 'loginUrl': campaign.create_logged_url(user.user_id), 'marketStress': _make_section(market_stress_section), 'months': _make_section(months_section), 'ofJobNameInDepartement': of_job_name_in_departement, 'ofJobName': of_job_name, } campaign.register_campaign(campaign.Campaign( campaign_id='imt', mongo_filters={ 'projects': { '$elemMatch': { 'isIncomplete': {'$ne': True}, }, },