Exemple #1
0
def delete_ats_account(user_id, ats_account_id):
    """
    Remove an ATS account and all of its candidates.

    :param int ats_account_id: id of the ATS account.
    :rtype: None
    """
    # First, verify the user and account
    account = ATSAccount.get(ats_account_id)
    if not account:
        raise NotFoundError('delete_ats_account: No such account {}'.format(ats_account_id))

    user = User.get(user_id)
    if not user:
        raise NotFoundError('delete_ats_account: No such user {}'.format(user_id))

    # Next remove all candidates and candidate attributes from the account
    candidate_list = ATSCandidate.query.filter(ATSCandidate.ats_account_id == ats_account_id).all()
    for candidate in candidate_list:
        profile = ATSCandidateProfile.get(candidate.profile_id)
        ATSCandidateProfile.delete(profile)

    # Then remove the account credentials
    credentials = ATSCredential.get(account.ats_credential_id)
    ATSCredential.delete(credentials)

    # Remove the account
    ATSAccount.delete(account)

    # If this is the only ATS account for this user, mark the user as not ATS enabled
    all_accounts = ATSAccount.query.filter(ATSAccount.user_id == user_id).all()
    if not all_accounts:
        update_dict = {'ats_enabled': False}
        User.query.filter(User.id == user_id).update(update_dict)
        db.session.commit()
Exemple #2
0
    def get(self, account_id):
        """
        GET /v1/ats-accounts/:account_id

        Retrieve an ATS accoun.

        :param account_id: int, id of the ATS account.
        :rtype string, JSON describing the account.
        """

        ats_service.app.logger.info("{} {} {} {}".format(
            request.method, request.path, request.user.email, request.user.id))
        account = ATSAccount.get_by_id(account_id)
        if not account:
            raise NotFoundError(
                'Account id not found',
                additional_error_info=dict(account_id=account_id))

        account_dict = account.to_dict()
        credentials = ATSCredential.get_by_id(account.ats_credential_id)
        ats = ATS.get_by_id(account.ats_id)
        account_dict.update({
            'credentials': credentials.credentials_json,
            'ats_name': ats.name,
            'ats_homepage': ats.homepage_url,
            'ats_login': ats.login_url
        })

        response = json.dumps(account_dict)
        headers = dict(Location=ATSServiceApiUrl.ACCOUNT % account_id)
        return ApiResponse(response, headers=headers, status=codes.OK)
Exemple #3
0
    def post(self, account_id):
        """
        POST /v1/ats-candidates/:account_id

        Create a new ATS candidate.

        :param account_id: int, id of the ATS account.
        :rtype string, JSON indicating success.
        """

        ats_service.app.logger.info("{} {} {} {}".format(
            request.method, request.path, request.user.email, request.user.id))
        data = get_valid_json_data(request)

        # Validate data fields
        validate_ats_candidate_data(data)

        # Validate ATS Account
        account = ATSAccount.get_by_id(account_id)
        if not account:
            raise NotFoundError(
                'Account id not found',
                additional_error_info=dict(account_id=account_id))

        # Create the candidate. No attempt to determine if duplicate.
        candidate = new_ats_candidate(account_id, data)

        response = json.dumps(
            dict(id=candidate.id,
                 message="ATS candidate successfully created."))
        headers = dict(Location=ATSServiceApiUrl.CANDIDATES % candidate.id)
        return ApiResponse(response, headers=headers, status=codes.CREATED)
Exemple #4
0
    def get(self, account_id):
        """
        GET /v1/ats-candidates/:account_id

        Retrieve all ATS candidates stored locally associated with an ATS account

        :param account_id: int, id of the ATS account.
        :rtype string, JSON with all candidates.
        """

        ats_service.app.logger.info("{} {} {} {}".format(
            request.method, request.path, request.user.email, request.user.id))
        account = ATSAccount.get_by_id(account_id)
        if not account:
            raise NotFoundError(
                'Account id not found',
                additional_error_info=dict(account_id=account_id))

        candidates = ATSCandidate.get_all(account_id)
        if candidates:
            # TODO: Consider adding the ATS-particular entry.
            response = json.dumps(dict(candidate_list=candidates))
            headers = dict(Location=ATSServiceApiUrl.CANDIDATES % account_id)
            return ApiResponse(response, headers=headers, status=codes.OK)

        ats_service.app.logger.info(
            "No candidates in ATS account {}".format(account_id))
        response = json.dumps(
            dict(account_id=account_id,
                 message="No candidates in ATS account {}".format(account_id)))
        headers = dict(Location=ATSServiceApiUrl.CANDIDATES % account_id)
        return ApiResponse(response, headers=headers, status=codes.NOT_FOUND)
Exemple #5
0
def match_ats_and_gt_candidates(logger, account_id, method, link=False):
    """
    Search for getTalent condidates which appear to match ATS candidates using a particular method.

    :param object logger: object to use for logging.
    :param int account_id: ATS account to search within.
    :param string method: matching technique to use.
    :param boolean link: Whether to link the candidates matched or not.
    :rtype int: Number of matches found.
    """
    if method not in MATCH_DICT:
        raise UnprocessableEntity("match_ats_and_gt_candidates: Invalid match method", additional_error_info=dict(unsupported_method=method))

    match_method = MATCH_DICT[method]

    account = ATSAccount.get(account_id)
    if not account:
        raise NotFoundError('ATS Account id not found', additional_error_info=dict(account_id=account_id))

    # Now get the user that owns this account
    user = User.get(account.user_id)
    # Get all candidates from user's domain
    gt_candidate_list = db.session.query(Candidate).join(User).filter(User.domain_id == user.domain_id).all()
    # Get a list of our ATS candidates
    ats_candidate_list = ATSCandidate.get_all(account_id)

    matches = 0
    for gt_candidate in gt_candidate_list:
        for ats_candidate in ats_candidate_list:
            if match_method(gt_candidate, ats_candidate):
                matches += 1
                if link:
                    link_ats_candidate(gt_candidate.id, ats_candidate.id)

    return matches
Exemple #6
0
def emails_match(gt_candidate, ats_candidate):
    """
    Determine if there are matching email addresses between a GT candidate and a Workday individual.
    Workday individuals have only one, but this returns a list so that all ATS may have the same method signature.
    :param Candidate gt_candidate: getTalent candidate.
    :param ATSCandidate ats_candidate: Workday individual.
    :rtype boolean:
    """
    if gt_candidate.is_archived:
        return False

    # Get the ATS candidate email address with an ATS-specific static method
    ats_email_list = ATS_CONSTRUCTORS[ATSAccount.get(ats_candidate.ats_id).name].get_individual_contact_email_addresses(ats_candidate)
    if not ats_email_list:
        return False

    # Compare to GT candidate email address(es)
    if not gt_candidate.emails:
        return False

    # Compare. This makes a 4-deep for loop, but we expect the lists to be very small. For Workday, there'll be only one email address.
    for gt_email in candidate.emails:
        for ats_email in ats_email_list:
            if gt_email == ats_email:
                return True

    return False
Exemple #7
0
def fetch_auth_data(account_id):
    """
    Return the values needed to authenticate to an ATS account.

    :param int account_id: Primary key of the account.
    :rtype string: ATS name.
    :rtype string: Login URL.
    :rtype: tuple[str] | tuple[str] | tuple[int] | tuple[ATSCredential]:
    """
    # Validate ATS Account
    account = ATSAccount.get(account_id)
    if not account:
        raise InvalidUsage("ATS account {} not found.".format(account_id))

    if not account.active:
        return None, None, None, None

    ats = ATS.get(account.ats_id)
    if not ats:
        raise UnprocessableEntity("Invalid ats id", additional_error_info=dict(id=account.ats_id))

    credentials = ATSCredential.get(account.ats_credential_id)
    if not credentials:
        raise UnprocessableEntity("No credentials for account", additional_error_info=dict(id=account.ats_id))

    return ats.name, ats.login_url, account.user_id, credentials
Exemple #8
0
def new_ats_candidate(account_id, data):
    """
    Register an ATS candidate with an ATS account.

    :param obj account: an ATS account object.
    :param dict data: keys and values describing the candidate.
    :rtype: ATSCandidate
    """
    gt_candidate_id = None
    if 'gt_candidate_id' in data:
        gt_candidate_id = data.get('gt_candidate_id', None)
    account = ATSAccount.get(account_id)
    if not account:
        raise InvalidUsage("ATS account {} not found.".format(account_id))
    profile = ATSCandidateProfile(active=True, profile_json=data['profile_json'], ats_id=account.ats_id)
    profile.save()
    candidate = ATSCandidate(ats_account_id=account.id, ats_remote_id=data['ats_remote_id'], gt_candidate_id=gt_candidate_id, profile_id=profile.id)
    if 'ats_table_id' in data:
        candidate.ats_table_id = data.get('ats_table_id', None)
    candidate.save()

    update_dict = {}
    now = datetime.datetime.utcnow()
    update_dict = {'updated_at' : now}
    account.update(**update_dict)

    return candidate
Exemple #9
0
def emails_and_phones_match(gt_candidate, ats_candidate):
    """
    Determine if there are matching email addresses and phone numbers between a GT candidate and a Workday individual.
    Workday indidviduals have only one, but this returns a list so that all ATS may have the same method signature.
    :param Candidate gt_candidate: getTalent candidate.
    :param ATSCandidate ats_candidate: Workday individual.
    :rtype boolean:
    """
    if gt_candidate.is_archived:
        return False

    # Get the ATS candidate email address with an ATS-specific static method
    ats_phone_list = ATS_CONSTRUCTORS[ATSAccount.get(ats_candidate.ats_id).name].get_individual_contact_phone_numbers(ats_candidate)
    if not ats_phone_list:
        return False

    # Compare to GT candidate email address(es)
    if not gt_candidate.phones:
        return False

    # Get the ATS candidate email address with an ATS-specific static method
    ats_email_list = ATS_CONSTRUCTORS[ATSAccount.get(ats_candidate.ats_id).name].get_individual_contact_email_addresses(ats_candidate)
    if not ats_email_list:
        return False

    # Compare to GT candidate email address(es)
    if not gt_candidate.emails:
        return False

    # Compare emails.
    for gt_email in candidate.emails:
        for ats_email in ats_email_list:
            if gt_email == ats_email:
                email_match = True

    # Compare phones.
    phone_match = normalized_phones_match(candidate.phones, ats_phone_list)

    return email_match and phone_match
Exemple #10
0
    def get(self):
        """
        GET /v1/ats-accounts

        Retrieve all ATS accounts of a user.

        :rtype string, JSON describing account.
        """

        ats_service.app.logger.info("{} {} {} {}".format(
            request.method, request.path, request.user.email, request.user.id))

        account_list = dict(
            ats_accounts=ATSAccount.get_user_accounts(request.user.id))
        response = json.dumps(account_list)
        headers = dict(Location=ATSServiceApiUrl.ACCOUNTS)
        return ApiResponse(response, headers=headers, status=codes.OK)
Exemple #11
0
def update_ats_account(account_id, new_data):
    """
    Update the values of an ATS account.

    :param int account_id: primary key of the account.
    :param dict new_data: New values for the account.
    :rtype: None
    """
    # Search for the account, complain if it doesn't exist
    account = ATSAccount.get(account_id)
    if not account:
        raise UnprocessableEntity("Invalid ats account id", additional_error_info=dict(id=account_id))
    ats = ATS.get(account.ats_id)
    if not ats:
        raise UnprocessableEntity("Invalid ats id", additional_error_info=dict(id=account.ats_id))

    # Update the ATS info
    update_dict = {}
    if 'ats_name' in new_data:
        update_dict['name'] = new_data['ats_name']
    if 'ats_homepage' in new_data:
        update_dict['homepage_url'] = new_data['ats_homepage']
    if 'ats_login' in new_data:
        update_dict['login_url'] = new_data['ats_login']
    if 'ats_auth_type' in new_data:
        update_dict['auth_type'] = new_data['ats_auth_type']
    if len(update_dict) > 0:
        ats.update(**update_dict)

    update_dict = {}
    now = datetime.datetime.utcnow()
    update_dict = {'updated_at' : now}
    if 'active' in new_data:
        if new_data['active'] == "False":
            update_dict['active'] = False
        else:
            update_dict['active'] = True

        account.update(**update_dict)

    # If they're changing credentials, find those. Presumably auth_type won't change.
    if 'ats_credentials' in new_data:
        credentials = ATSCredential.get(account.ats_credential_id)
        update_dict = {'credentials_json' : new_data['ats_credentials']}
        credentials.update(**update_dict)
Exemple #12
0
    def post(self):
        """
        POST /v1/ats-accounts/

        Register an ATS account for a user.

        :rtype string, JSON indicating success.
        """

        ats_service.app.logger.info("{} {} {} {}".format(
            request.method, request.path, request.user.email, request.user.id))
        authenticated_user = request.user
        # data['active'] = True
        data = get_valid_json_data(request)

        # Validate data fields
        validate_ats_account_data(data)

        # Search for this ATS account entry already existing
        account = ATSAccount.get_account(authenticated_user.id,
                                         data['ats_name'])
        if account:
            raise NotFoundError(
                'Account id not found',
                additional_error_info=dict(account_id=account_id))

        # Search for ATS entry, create if absent
        ats = ATS.get_by_name(data['ats_name'])
        if not ats:
            ats = new_ats(data)
            ats_service.app.logger.info(
                "Adding new ATS account {}, id {}".format(ats.name, ats.id))

        # Create ATS account for user
        account = new_ats_account(authenticated_user.id, ats.id, data)
        ats_service.app.logger.info("Added new ATS account {}.".format(
            account.id))

        response = json.dumps(
            dict(id=account.id, message="ATS account successfully created."))
        headers = dict(Location=ATSServiceApiUrl.ACCOUNT % account.id)
        return ApiResponse(response, headers=headers, status=codes.CREATED)
Exemple #13
0
def delete_ats_candidate(candidate_id):
    """
    Remove an ATS candidate from the database.

    :param int candidate_id: The id of the candidate.
    :rtype: None
    """
    candidate = ATSCandidate.get(candidate_id)
    if not candidate:
        raise InvalidUsage('delete_ats_candidate: No such candidate {}'.format(candidate_id))

    profile = ATSCandidateProfile.get(candidate.profile_id)
    if profile:
        ATSCandidateProfile.delete(profile)

    account = ATSAccount.get(candidate.ats_account_id)
    if not account:
        raise InvalidUsage("ATS account {} not found.".format(candidate.ats_account_id))
    now = datetime.datetime.utcnow()
    update_dict = {'updated_at' : now}
    account.update(**update_dict)
Exemple #14
0
def update_ats_candidate(account_id, candidate_id, new_data):
    """
    Update the profile of an ATS candidate.

    :param int account_id: primary key of the account the candidate belongs to.
    :param int candidate_id: primary key of the candidate.
    :param dict new_data: values to update for the candidate.
    :rtype: None
    """
    if 'profile_json' not in new_data:
        raise InvalidUsage("profile_json not found.")

    # Validate ATS Account
    account = ATSAccount.get(account_id)
    if not account:
        raise InvalidUsage("ATS account {} not found.".format(account_id))

    # Validate candidate id
    candidate = ATSCandidate.get(candidate_id)
    if not candidate:
        raise UnprocessableEntity("Invalid candidate id", additional_error_info=dict(id=account.candidate_id))

    # Validate profile id
    profile = ATSCandidateProfile.get(candidate.profile_id)
    if not profile:
        raise UnprocessableEntity("Invalid candidate profile id", additional_error_info=dict(id=candidate.profile_id))

    if 'ats_table_id' in new_data:
        candidate.ats_table_id = new_data.get('ats_table_id', None)

    now = datetime.datetime.utcnow()
    update_dict = {'profile_json' : new_data['profile_json'], 'updated_at' : now}
    profile.update(**update_dict)

    update_dict = {'updated_at' : now}
    candidate.update(**update_dict)
    account.update(**update_dict)

    return candidate
Exemple #15
0
def new_ats_account(user_id, ats_id, data):
    """
    Register an ATS account for a user.

    :param int user_id: id of the user to associate the account with.
    :param int ats_id: id of the ATS system.
    :param dict data: keys and values describing the account.
    :rtype: ATS
    """
    # Create account and credential entries
    account = ATSAccount(active=True, ats_id=ats_id, user_id=user_id, ats_credential_id=0)
    account.save()
    credentials = ATSCredential(ats_account_id=0, auth_type=data['ats_auth_type'], credentials_json=data['ats_credentials'])
    credentials.save()

    # Now make the two rows point to each other
    update_dict = {'ats_credential_id': credentials.id}
    account.update(**update_dict)
    update_dict = {'ats_account_id': account.id}
    credentials.update(**update_dict)
    update_dict = {'ats_enabled': True}
    User.get(user_id).update(**update_dict)

    return account