Exemple #1
0
class CandidateMostEngagedPipelines(Resource):
    # Access token decorator
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES)
    def get(self, **kwargs):
        """
        GET /candidates/<candidate_id>/talent-pipelines?limit=5  Fetch Engagement score of a
        candidate in each pipeline
        :return A dictionary containing list of most engaged candidates belonging to a talent-pipeline

        :rtype: dict
        """

        candidate_id = kwargs.get('id')
        limit = request.args.get('limit', 5)

        if not is_number(limit) or int(limit) < 1:
            raise InvalidUsage("Limit should be a positive integer")

        candidate = Candidate.query.get(candidate_id)

        if not candidate:
            raise NotFoundError(error_message="Candidate with id {} doesn't exist in database".format(candidate_id))

        if request.user.role.name != 'TALENT_ADMIN' and candidate.user.domain_id != request.user.domain_id:
            raise ForbiddenError("Logged-in user and candidate belong to different domain")

        return {'talent_pipelines': top_most_engaged_pipelines_of_candidate(candidate_id, int(limit))}
Exemple #2
0
class TalentPipelineCampaigns(Resource):
    # Access token decorator
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CAMPAIGNS)
    def get(self, **kwargs):
        """
        GET /talent-pipelines/<id>/campaigns?fields=id,subject&page=1&per_page=20

        Fetch all campaigns of a talent-pipeline.

        :return A dictionary containing list of campaigns belonging to a talent-pipeline
        :rtype: dict
        """

        # Valid talent pipeline
        talent_pipeline_id = kwargs.get('id')
        talent_pipeline = TalentPipeline.query.get(talent_pipeline_id)
        if not talent_pipeline:
            raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id))

        if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id:
            raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain")

        page = request.args.get('page', DEFAULT_PAGE)
        per_page = request.args.get('per_page', 20)

        if not is_number(page) or not is_number(per_page) or int(page) < 1 or int(per_page) < 1:
            raise InvalidUsage("page and per_page should be positive integers")

        page = int(page)
        per_page = int(per_page)

        # Get the email campaigns
        include_fields = request.values['fields'].split(',') if request.values.get('fields') else None
        email_campaigns = talent_pipeline.get_email_campaigns(page=page, per_page=per_page)

        headers = generate_pagination_headers(talent_pipeline.get_email_campaigns_count(), per_page, page)

        response = {
            'page_number': page, 'email_campaigns_per_page': per_page,
            'total_number_of_email_campaigns': talent_pipeline.get_email_campaigns_count(),
            'email_campaigns': [email_campaign.to_dict(include_fields) for email_campaign in email_campaigns]
        }

        return ApiResponse(response=response, headers=headers, status=200)
Exemple #3
0
class SmartlistCandidates(Resource):

    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_SMART_LISTS)
    def get(self, **kwargs):
        """
        Use this endpoint to retrieve all candidates present in list (smart or dumb list)
        Input:
            URL Arguments `smartlist_id` (Required): id of smartlist
            Accepts (query string parameters):
                fields :: comma separated values
                sort_by ::  Sort by field
                limit :: Size of each page
                page :: Page number or cursor string
        :return : List of candidates present in list (smart list or dumb list)
        :rtype: json
        """
        smartlist_id = kwargs.get('smartlist_id')
        smartlist = Smartlist.query.get(smartlist_id)
        if not smartlist or smartlist.is_hidden:
            raise NotFoundError("List id does not exists.")

        # check whether smartlist belongs to user's domain
        if request.user.role.name != 'TALENT_ADMIN' and smartlist.user.domain_id != request.user.domain_id:
            raise ForbiddenError(
                "Provided list does not belong to user's domain")

        request_params = dict()
        request_params['facets'] = request.args.get('facets', '')
        request_params['fields'] = request.args.get('fields', '')
        request_params['sort_by'] = request.args.get('sort_by', '')
        request_params['limit'] = request.args.get('limit', '')
        request_params['page'] = request.args.get('page', '')

        return get_smartlist_candidates(smartlist, request.oauth_token,
                                        request_params)
Exemple #4
0
class SmartlistResource(Resource):
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_SMART_LISTS)
    def get(self, **kwargs):
        """Retrieve list information
        List must belong to auth user's domain
        Call this resource from url:
            /v1/smartlists?page=1&page_size=10 :: to retrieve all the smartlists in user's domain
            /v1/smartlists/<int:id> :: to get single smartlist

        example: http://localhost:8008/v1/smartlists/2
        Returns: List in following json format
            {
              "smartlist": {
                "total_found": 3,
                "user_id": 1,
                "id": 1,
                "name": "my list"
                "search_params": {"location": "San Jose, CA"}
              }
            }
        """
        list_id = kwargs.get('id')
        candidate_count = request.args.get('candidate-count', False)

        if not is_number(candidate_count) or int(candidate_count) not in (
                True, False):
            raise InvalidUsage("`candidate_count` field value can be 0 or 1")

        auth_user = request.user
        if list_id:
            smartlist = Smartlist.query.get(list_id)
            if not smartlist or smartlist.is_hidden:
                raise NotFoundError("List id does not exists")
            # check whether smartlist belongs to user's domain
            if request.user.role.name != 'TALENT_ADMIN' and smartlist.user.domain_id != auth_user.domain_id:
                raise ForbiddenError("List does not belong to user's domain")
            return {
                'smartlist':
                create_smartlist_dict(smartlist, request.oauth_token,
                                      int(candidate_count))
            }
        else:
            # Return all smartlists from user's domain
            page = request.args.get('page', DEFAULT_PAGE)
            per_page = request.args.get('per_page', DEFAULT_PAGE_SIZE)
            total_number_of_smartlists = Smartlist.query.join(
                Smartlist.user).filter(User.domain_id == auth_user.domain_id,
                                       Smartlist.is_hidden == 0).count()

            if not is_number(page) or not is_number(
                    per_page) or int(page) < 1 or int(per_page) < 1:
                raise InvalidUsage(
                    "page and per_page should be positive integers")

            page = int(page)
            per_page = int(per_page)

            headers = generate_pagination_headers(total_number_of_smartlists,
                                                  per_page, page)

            response = {
                'smartlists':
                get_all_smartlists(auth_user, request.oauth_token, int(page),
                                   int(per_page), candidate_count),
                'page_number':
                page,
                'smartlists_per_page':
                per_page,
                'total_number_of_smartlists':
                total_number_of_smartlists
            }
            return ApiResponse(response=response, headers=headers, status=200)

    @require_all_permissions(Permission.PermissionNames.CAN_ADD_SMART_LISTS)
    def post(self):
        """
        Creates list with search params or with list of candidate ids
        Input data:
            json body having following keys
            "name": Name with which smart list will be created
            "search_params": search parameters for smart list in dictionary format
                or  "candidate_ids": if not search_params then candidate_ids should be present
        :return: smartlist id
        """
        auth_user = request.user
        data = request.get_json(silent=True)
        if not data:
            raise InvalidUsage("Received empty request body")
        # request data must pass through this function, as this will create data in desired format
        data = validate_and_format_smartlist_post_data(data, auth_user)
        smartlist = save_smartlist(
            user_id=auth_user.id,
            name=data.get('name'),
            talent_pipeline_id=data.get('talent_pipeline_id'),
            search_params=data.get('search_params'),
            candidate_ids=data.get('candidate_ids'),
            access_token=request.oauth_token)
        return {'smartlist': {'id': smartlist.id}}, 201

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_SMART_LISTS)
    def patch(self, **kwargs):
        """
        PATCH list with search params or with list of candidate ids
        Input data:
            json body having following keys
            "name": Name with which smart list will be created
            "search_params": search parameters for smart list in dictionary format
                or  "candidate_ids": if not search_params then candidate_ids should be present
        :return: smartlist id
        """
        auth_user = request.user

        list_id = kwargs.get('id')
        data = request.get_json(silent=True)

        if not data:
            raise InvalidUsage("Received empty request body")

        if not list_id or not is_number(list_id):
            raise InvalidUsage(
                "Either List ID is not provided or It's not an integer")

        smart_list = Smartlist.query.get(list_id)

        if not smart_list:
            raise InvalidUsage("No SmartList exists with id: %s" % list_id)

        # request data must pass through this function, as this will create data in desired format
        data = validate_and_format_smartlist_patch_data(data, auth_user)

        if data.get('name'):
            smart_list.name = data.get('name')

        if data.get('talent_pipeline_id'):
            smart_list.talent_pipeline_id = data.get('talent_pipeline_id')

        if data.get('search_params'):
            smart_list.search_params = data.get('search_params')
        elif data.get('remove_candidate_ids'):
            SmartlistCandidate.query.filter(
                SmartlistCandidate.smartlist_id == smart_list.id,
                SmartlistCandidate.candidate_id.in_(
                    data.get('remove_candidate_ids'))).delete()
        elif data.get('add_candidate_ids'):
            for candidate_id in data.get('add_candidate_ids'):
                row = SmartlistCandidate(smartlist_id=smart_list.id,
                                         candidate_id=candidate_id)
                db.session.add(row)

        db.session.commit()

        candidate_ids = data.get('remove_candidate_ids', []) + data.get(
            'add_candidate_ids', [])
        update_candidates_on_cloudsearch(request.oauth_token, candidate_ids)

        return {'smartlist': {'id': smart_list.id}}, 201

    @require_all_permissions(Permission.PermissionNames.CAN_DELETE_SMART_LISTS)
    def delete(self, **kwargs):
        """
        Deletes (hides) the smartlist

        :return: Id of deleted smartlist.
        """
        list_id = kwargs.get('id')
        if not list_id:
            return InvalidUsage("List id is required for deleting a list")

        smartlist = Smartlist.query.get(list_id)
        if not smartlist or smartlist.is_hidden:
            raise NotFoundError("List id does not exists")

        if request.user.role.name == 'USER' and smartlist.user.id != request.user.id:
            raise ForbiddenError(
                "Logged-in user doesn't have appropriate permissions to delete this smartlist"
            )

        # check whether smartlist belongs to user's domain
        if request.user.role.name != 'TALENT_ADMIN' and smartlist.user.domain_id != request.user.domain_id:
            raise ForbiddenError(
                "Logged-in user doesn't have appropriate permissions to delete this smartlist"
            )

        smartlist_candidate_ids = SmartlistCandidate.query.with_entities(
            SmartlistCandidate.candidate_id).filter(
                SmartlistCandidate.smartlist_id == list_id).all()
        smartlist_candidate_ids = [
            smartlist_candidate_id[0]
            for smartlist_candidate_id in smartlist_candidate_ids
        ]
        smartlist.delete()
        if smartlist_candidate_ids:
            logger.info(
                "Candidates %s of SmartList %s are going to be updated in Amazon Cloud "
                "Search" % (smartlist_candidate_ids, list_id))
            update_candidates_on_cloudsearch(request.oauth_token,
                                             smartlist_candidate_ids)
        return {'smartlist': {'id': smartlist.id}}
Exemple #5
0
class TalentPipelineSmartListApi(Resource):
    # Access token decorator
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_TALENT_PIPELINES)
    def get(self, **kwargs):
        """
        GET /talent-pipeline/<id>/smart_lists   Fetch all smartlists of a talent_pipeline

        :return A dictionary containing smartlist objects of a talent_pipeline

        :rtype: dict
        """

        talent_pipeline_id = kwargs.get('id')

        talent_pipeline = TalentPipeline.query.get(talent_pipeline_id)

        if not talent_pipeline:
            raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id))

        if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id:
            raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain")

        page = request.args.get('page', DEFAULT_PAGE)
        per_page = request.args.get('per_page', DEFAULT_PAGE_SIZE)

        if not is_number(page) or not is_number(per_page) or int(page) < 1 or int(per_page) < 1:
            raise InvalidUsage("page and per_page should be positive integers")

        page = int(page)
        per_page = int(per_page)

        total_number_of_smartlists = Smartlist.query.filter_by(talent_pipeline_id=talent_pipeline_id).count()
        smartlists = Smartlist.query.filter_by(talent_pipeline_id=talent_pipeline_id).order_by(
            Smartlist.added_time.desc()).paginate(page, per_page, False)

        smartlists = smartlists.items

        headers = generate_pagination_headers(total_number_of_smartlists, per_page, page)

        response = {
            'page_number': page, 'smartlists_per_page': per_page,
            'total_number_of_smartlists': total_number_of_smartlists,
            'smartlists': [smartlist.to_dict() for smartlist in smartlists]
        }

        return ApiResponse(response=response, headers=headers, status=200)

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_TALENT_PIPELINES)
    def post(self, **kwargs):
        """
        POST /talent-pipeline/<id>/smartlists   Add smartlists to a talent_pipeline

        Take a JSON dictionary containing smartlist_ids

        :return A dictionary containing smartlist_ids successfully added to talent_pipeline

        :rtype: dict
        """

        talent_pipeline_id = kwargs.get('id')

        talent_pipeline = TalentPipeline.query.get(talent_pipeline_id)

        if not talent_pipeline:
            raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id))

        posted_data = request.get_json(silent=True)
        if not posted_data or 'smartlist_ids' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        # Save user object(s)
        smartlist_ids = posted_data['smartlist_ids']

        # Talent_pool object(s) must be in a list
        if not isinstance(smartlist_ids, list):
            raise InvalidUsage("Request body is not properly formatted")

        if request.user.role.name == 'USER' and talent_pipeline.user.id != request.user.id:
            raise ForbiddenError("Logged-in user doesn't have appropriate permissions to edit this talent-pipeline")

        if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id:
            raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain")

        for smartlist_id in smartlist_ids:

            if not is_number(smartlist_id):
                raise InvalidUsage('Smartlist id %s should be an integer'.format(smartlist_id))
            else:
                smartlist_id = int(smartlist_id)

            smartlist = Smartlist.query.get(smartlist_id)

            if smartlist.user.domain_id != talent_pipeline.user.domain_id:
                raise ForbiddenError("Smartlist {} and Talent pipeline {} belong to different domain".format(
                    smartlist_id, talent_pipeline_id))

            if smartlist.talent_pipeline_id == talent_pipeline_id:
                raise InvalidUsage("Smartlist {} already belongs to Talent Pipeline {}".format(
                    smartlist.name, talent_pipeline_id))

            if smartlist.talent_pipeline_id:
                raise ForbiddenError("smartlist {} is already assigned to talent_pipeline {}".format(
                    smartlist.name, smartlist.talent_pipeline_id))

            smartlist.talent_pipeline_id = talent_pipeline_id

        db.session.commit()

        return {
            'smartlist_ids': [int(smartlist_id) for smartlist_id in smartlist_ids]
        }

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_TALENT_PIPELINES)
    def delete(self, **kwargs):
        """
        DELETE /talent-pipeline/<id>/smartlists   Remove smartlists from a talent_pipeline

        Take a JSON dictionary containing smartlist_ids

        :return A dictionary containing smartlist_ids successfully removed from a talent_pipeline

        :rtype: dict
        """

        talent_pipeline_id = kwargs.get('id')

        talent_pipeline = TalentPipeline.query.get(talent_pipeline_id)

        if not talent_pipeline:
            raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id))

        posted_data = request.get_json(silent=True)
        if not posted_data or 'smartlist_ids' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        # Save user object(s)
        smartlist_ids = posted_data['smartlist_ids']

        # Talent_pool object(s) must be in a list
        if not isinstance(smartlist_ids, list):
            raise InvalidUsage("Request body is not properly formatted")

        if request.user.role.name == 'USER' and talent_pipeline.user.id != request.user.id:
            raise ForbiddenError("Logged-in user doesn't have appropriate permissions to edit this talent-pipeline")

        if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id:
            raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain")

        for smartlist_id in smartlist_ids:

            if not is_number(smartlist_id):
                raise InvalidUsage('Smart List id {} should be an integer'.format(smartlist_id))
            else:
                smartlist_id = int(smartlist_id)

            smartlist = Smartlist.query.get(smartlist_id)

            if smartlist.talent_pipeline_id != talent_pipeline_id:
                raise ForbiddenError("smartlist {} doesn't belong to talent_pipeline {}".format(
                    smartlist.name, talent_pipeline_id))

            smartlist.talent_pipeline_id = None

        db.session.commit()

        return {
            'smartlist_ids': [int(smartlist_id) for smartlist_id in smartlist_ids]
        }
Exemple #6
0
class TalentPipelineCandidates(Resource):
    # Access token decorator
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_ADD_TALENT_PIPELINES)
    def post(self, **kwargs):
        """
        POST /talent-pipeline/<id>/candidates  Add candidates to a Talent Pipeline
        :return None

        :rtype: None
        """
        talent_pipeline_id = kwargs.get('id')

        talent_pipeline = TalentPipeline.query.get(talent_pipeline_id)

        if not talent_pipeline:
            raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id))

        posted_data = request.get_json(silent=True)
        if not posted_data or 'candidate_ids' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        candidate_ids = posted_data.get('candidate_ids')

        candidate_ids = list(set(candidate_ids))

        if Candidate.query.filter(Candidate.id.in_(candidate_ids)).count() != len(candidate_ids):
            raise InvalidUsage("Some of the candidates don't exist in DB")

        TalentPipelineExcludedCandidates.query.filter(
            TalentPipelineExcludedCandidates.talent_pipeline_id == talent_pipeline.id,
            TalentPipelineExcludedCandidates.candidate_id.in_(candidate_ids)).delete(synchronize_session='fetch')

        added_candidate_ids = []
        for candidate_id in candidate_ids:
            if not TalentPipelineIncludedCandidates.query.filter_by(talent_pipeline_id=talent_pipeline.id,
                                                                    candidate_id=candidate_id).first():
                added_candidate_ids.append(candidate_id)
                db.session.add(TalentPipelineIncludedCandidates(talent_pipeline_id=talent_pipeline.id,
                                                                candidate_id=candidate_id))

                # Track updates made to candidate's records
                db.session.add(CandidateEdit(
                    user_id=request.user.id,
                    candidate_id=candidate_id,
                    field_id=1801,
                    old_value=None,
                    new_value=talent_pipeline.name,
                    edit_datetime=datetime.utcnow(),
                    is_custom_field=False
                ))
            else:
                db.session.rollback()
                raise InvalidUsage("Candidate: %s is already included in pipeline: %s" % (
                    candidate_id, talent_pipeline.id))

        db.session.commit()

        update_candidates_on_cloudsearch(request.oauth_token, candidate_ids)

        activity_data = {
            'name': talent_pipeline.name
        }
        candidates = Candidate.query.with_entities(Candidate.id, Candidate.formatted_name).filter(
            Candidate.id.in_(added_candidate_ids)).all()

        for candidate_id, formatted_name in candidates:
            activity_data['formattedName'] = formatted_name
            add_activity(user_id=request.user.id, activity_type=Activity.MessageIds.PIPELINE_ADD_CANDIDATE,
                         source_table=Candidate.__tablename__, source_id=candidate_id, params=activity_data)

        return '', 204

    @require_all_permissions(Permission.PermissionNames.CAN_ADD_TALENT_PIPELINES)
    def delete(self, **kwargs):
        """
        DELETE /talent-pipeline/<id>/candidates  Add candidates to a Talent Pipeline
        :return None

        :rtype: None
        """
        talent_pipeline_id = kwargs.get('id')

        talent_pipeline = TalentPipeline.query.get(talent_pipeline_id)

        if not talent_pipeline:
            raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id))

        posted_data = request.get_json(silent=True)
        if not posted_data or 'candidate_ids' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        candidate_ids = posted_data.get('candidate_ids')

        candidate_ids = list(set(candidate_ids))

        if Candidate.query.filter(Candidate.id.in_(candidate_ids)).count() != len(candidate_ids):
            raise InvalidUsage("Some of the candidates don't exist in DB")

        TalentPipelineIncludedCandidates.query.filter(
            TalentPipelineIncludedCandidates.talent_pipeline_id == talent_pipeline.id,
            TalentPipelineIncludedCandidates.candidate_id.in_(candidate_ids)).delete(synchronize_session='fetch')

        removed_candidate_ids = []
        for candidate_id in candidate_ids:
            if not TalentPipelineExcludedCandidates.query.filter_by(talent_pipeline_id=talent_pipeline.id,
                                                                    candidate_id=candidate_id).first():
                removed_candidate_ids.append(candidate_id)

                db.session.add(TalentPipelineExcludedCandidates(talent_pipeline_id=talent_pipeline.id,
                                                                candidate_id=candidate_id))

                # Track updates made to candidate's records
                db.session.add(CandidateEdit(
                    user_id=request.user.id,
                    candidate_id=candidate_id,
                    field_id=1801,
                    old_value=talent_pipeline.name,
                    new_value=None,  # inferring deletion
                    edit_datetime=datetime.utcnow(),
                    is_custom_field=False
                ))
            else:
                db.session.rollback()
                raise InvalidUsage("Candidate: %s is not included in pipeline: %s" % (candidate_id, talent_pipeline.id))

        db.session.commit()

        update_candidates_on_cloudsearch(request.oauth_token, candidate_ids)

        activity_data = {
            'name': talent_pipeline.name
        }
        candidates = Candidate.query.with_entities(Candidate.id, Candidate.formatted_name).filter(
            Candidate.id.in_(removed_candidate_ids)).all()

        for candidate_id, formatted_name in candidates:
            activity_data['formattedName'] = formatted_name
            add_activity(user_id=request.user.id, activity_type=Activity.MessageIds.PIPELINE_REMOVE_CANDIDATE,
                         source_table=Candidate.__tablename__, source_id=candidate_id, params=activity_data)

        return '', 204

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES)
    def get(self, **kwargs):
        """
        GET /talent-pipeline/<id>/candidates  Fetch all candidates of a talent-pipeline
        Query String: {'fields': 'id,email', 'sort_by': 'match' 'limit':10, 'page': 2}
        :return A dictionary containing list of candidates belonging to a talent-pipeline

        :rtype: dict
        """

        talent_pipeline_id = kwargs.get('id')

        talent_pipeline = TalentPipeline.query.get(talent_pipeline_id)

        if not talent_pipeline:
            raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id))

        if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id:
            raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain")

        request_params = dict()
        request_params['facets'] = request.args.get('facets', '')
        request_params['fields'] = request.args.get('fields', '')
        request_params['sort_by'] = request.args.get('sort_by', '')
        request_params['limit'] = request.args.get('limit', '')
        request_params['page'] = request.args.get('page', '')

        return get_candidates_of_talent_pipeline(talent_pipeline, request.oauth_token, request_params)
Exemple #7
0
class TalentPipelineApi(Resource):
    # Access token decorator
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_TALENT_PIPELINES)
    def get(self, **kwargs):
        """
        GET /talent-pipelines/<id>      Fetch talent-pipeline object
        GET /talent-pipelines           Fetch all talent-pipelines objects of domain of logged-in user

        :return A dictionary containing talent-pipeline's basic info or a dictionary containing all talent-pipelines of
        a domain

        :rtype: dict
        """
        talent_pipeline_id = kwargs.get('id')
        interval_in_days = request.args.get('interval', 30)
        candidate_count = request.args.get('candidate-count', False)
        email_campaign_count = request.args.get('email-campaign-count', False)

        if not is_number(candidate_count) or int(candidate_count) not in (True, False):
            raise InvalidUsage("`candidate_count` field value can be 0 or 1")

        if not is_number(email_campaign_count) or int(email_campaign_count) not in (True, False):
            raise InvalidUsage("`email_campaign_count` field value can be 0 or 1")

        if not is_number(interval_in_days) or int(interval_in_days) < 0:
            raise InvalidUsage("Value of interval should be positive integer")

        if talent_pipeline_id:
            talent_pipeline = TalentPipeline.query.get(talent_pipeline_id)

            if not talent_pipeline:
                raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id))

            if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id:
                raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain")

            if not candidate_count:
                talent_pipeline_dict = talent_pipeline.to_dict(email_campaign_count=email_campaign_count)
            else:
                talent_pipeline_dict = talent_pipeline.to_dict(email_campaign_count=email_campaign_count,
                                                               include_candidate_count=True,
                                                               get_candidate_count=get_talent_pipeline_stat_for_given_day)

            talent_pipeline_dict.update({'engagement_score': get_pipeline_engagement_score(talent_pipeline_id)})
            return {'talent_pipeline': talent_pipeline_dict}

        else:
            sort_by = request.args.get('sort_by', 'added_time')
            sort_type = request.args.get('sort_type', 'DESC')
            search_keyword = request.args.get('search', '').strip()
            owner_user_id = request.args.get('user_id', '')
            is_hidden = request.args.get('is_hidden', 0)
            from_date = request.args.get('from_date', EPOCH_TIME_STRING)
            to_date = request.args.get('to_date', datetime.utcnow().isoformat())
            page = request.args.get('page', DEFAULT_PAGE)
            per_page = request.args.get('per_page', DEFAULT_PAGE_SIZE)

            if not is_number(is_hidden) or int(is_hidden) not in (0, 1):
                raise InvalidUsage('`is_hidden` can be either 0 or 1')

            if not is_number(page) or not is_number(per_page) or int(page) < 1 or int(per_page) < 1:
                raise InvalidUsage("page and per_page should be positive integers")

            page = int(page)
            per_page = int(per_page)

            if owner_user_id and is_number(owner_user_id) and not User.query.get(int(owner_user_id)):
                raise InvalidUsage("User: (%s) doesn't exist in system")

            if sort_by not in ('added_time', 'name', 'engagement_score', 'candidate_count'):
                raise InvalidUsage('Value of sort parameter is not valid')

            try:
                from_date = parser.parse(from_date).replace(tzinfo=None)
                to_date = parser.parse(to_date).replace(tzinfo=None)
            except Exception as e:
                raise InvalidUsage("from_date or to_date is not properly formatted: %s" % e)

            talent_pipelines_query = TalentPipeline.query.join(User).filter(
                    TalentPipeline.is_hidden == is_hidden, User.domain_id == request.user.domain_id,
                    from_date <= TalentPipeline.added_time, TalentPipeline.added_time <= to_date, or_(
                            TalentPipeline.name.ilike('%' + search_keyword + '%'),
                            TalentPipeline.description.ilike('%' + search_keyword + '%')))
            if owner_user_id:
                talent_pipelines_query = talent_pipelines_query.filter(User.id == int(owner_user_id))

            total_number_of_talent_pipelines = talent_pipelines_query.count()

            if sort_by not in ("engagement_score", "candidate_count"):
                if sort_by == 'added_time':
                    sort_attribute = TalentPipeline.added_time
                else:
                    sort_attribute = TalentPipeline.name
                talent_pipelines = talent_pipelines_query.order_by(
                    sort_attribute.asc() if sort_type == 'ASC' else sort_attribute.desc()).paginate(page, per_page,
                                                                                                    False)
                talent_pipelines = talent_pipelines.items
            else:
                talent_pipelines = talent_pipelines_query.all()

            if candidate_count or sort_by == "candidate_count":
                talent_pipelines_data = [
                    talent_pipeline.to_dict(include_cached_candidate_count=True,
                                            email_campaign_count=email_campaign_count,
                                            get_candidate_count=get_talent_pipeline_stat_for_given_day)
                    for talent_pipeline in talent_pipelines]
            else:
                talent_pipelines_data = [
                    talent_pipeline.to_dict(email_campaign_count=email_campaign_count)
                    for talent_pipeline in talent_pipelines]

            for talent_pipeline_data in talent_pipelines_data:
                talent_pipeline_data['engagement_score'] = get_pipeline_engagement_score(talent_pipeline_data['id'])

            if sort_by in ("engagement_score", "candidate_count"):
                sort_by = 'total_candidates' if sort_by == "candidate_count" else "engagement_score"
                talent_pipelines_data = sorted(
                        talent_pipelines_data, key=lambda talent_pipeline_data: talent_pipeline_data[sort_by],
                        reverse=(False if sort_type == 'ASC' else True))
                talent_pipelines_data = talent_pipelines_data[(page - 1) * per_page:page * per_page]

            headers = generate_pagination_headers(total_number_of_talent_pipelines, per_page, page)

            response = dict(
                talent_pipelines=talent_pipelines_data,
                page_number=page, talent_pipelines_per_page=per_page,
                total_number_of_talent_pipelines=total_number_of_talent_pipelines
            )

            return ApiResponse(response=response, headers=headers, status=200)

    @require_all_permissions(Permission.PermissionNames.CAN_DELETE_TALENT_PIPELINES)
    def delete(self, **kwargs):
        """
        DELETE /talent-pipelines/<id>  Remove talent-pipeline from Database

        :return A dictionary containing deleted talent-pipeline's id

        :rtype: dict
        """

        talent_pipeline_id = kwargs.get('id')

        if not talent_pipeline_id:
            raise InvalidUsage("A valid talent_pipeline_id should be provided")

        talent_pipeline = TalentPipeline.query.get(talent_pipeline_id)
        if not talent_pipeline:
            raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id))

        if request.user.role.name == 'USER' and talent_pipeline.user.id != request.user.id:
            raise ForbiddenError("Logged-in user doesn't have appropriate permissions to delete this talent-pipeline")

        if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id:
            raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain")

        talent_pipeline.is_hidden = 1
        for smartlist in Smartlist.query.filter(Smartlist.talent_pipeline_id == talent_pipeline_id):
            smartlist.is_hidden = 1

        db.session.commit()

        return {
            'talent_pipeline': {'id': talent_pipeline_id}
        }

    @require_all_permissions(Permission.PermissionNames.CAN_ADD_TALENT_PIPELINES)
    def post(self, **kwargs):
        """
        POST /talent-pipelines  Add new talent-pipelines to Database
        input: {'talent_pipelines': [talent_pipeline_dict1, talent_pipeline_dict2, talent_pipeline_dict3, ... ]}

        Take a JSON dictionary containing array of TalentPipeline dictionaries
        A single talent-pipelines dict must contain pipelines's name, date_needed, talent_pool_id

        :return A dictionary containing ids of added talent-pipelines

        :rtype: dict
        """

        posted_data = request.get_json(silent=True)
        if not posted_data or 'talent_pipelines' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        # Save user object(s)
        talent_pipelines = posted_data['talent_pipelines']

        # TalentPipeline object(s) must be in a list
        if not isinstance(talent_pipelines, list):
            raise InvalidUsage("Request body is not properly formatted")

        talent_pipeline_objects = []
        for talent_pipeline in talent_pipelines:

            name = talent_pipeline.get('name', '')
            description = talent_pipeline.get('description', '')
            positions = talent_pipeline.get('positions', 1)
            date_needed = talent_pipeline.get('date_needed', '')
            talent_pool_id = talent_pipeline.get('talent_pool_id', '')
            search_params = talent_pipeline.get('search_params', dict())

            if not name or not date_needed or not talent_pool_id:
                raise InvalidUsage("A valid name, date_needed, talent_pool_id should be provided to "
                                   "create a new talent-pipeline")

            if TalentPipeline.query.join(TalentPipeline.user).filter(and_(
                            TalentPipeline.name == name, User.domain_id == request.user.domain_id)).first():
                raise InvalidUsage(
                    "Talent pipeline with name {} already exists in domain {}".format(name, request.user.domain_id))

            try:
                date_needed = parser.parse(date_needed)
            except Exception as e:
                raise InvalidUsage("Date_needed is not valid as: {}".format(e.message))

            if not is_number(positions) or not int(positions) > 0:
                raise InvalidUsage("Number of positions should be integer and greater than zero")

            if not is_number(talent_pool_id):
                raise InvalidUsage("talent_pool_id should be an integer")

            talent_pool_id = int(talent_pool_id)
            talent_pool = TalentPool.query.get(talent_pool_id)
            if not talent_pool:
                raise NotFoundError("Talent pool with id {} doesn't exist in database".format(talent_pool_id))

            if talent_pool.domain_id != request.user.domain_id:
                raise ForbiddenError("Logged-in user and given talent-pool belong to different domain")

            if search_params:
                if not isinstance(search_params, dict):
                    raise InvalidUsage("search_params is not provided in valid format")

                # Put into params dict
                for key in search_params:
                    if key not in TALENT_PIPELINE_SEARCH_PARAMS and not key.startswith('cf-'):
                        raise NotFoundError("Key[{}] is invalid".format(key))

            search_params = json.dumps(search_params) if search_params else None

            talent_pipeline = TalentPipeline(name=name, description=description, positions=positions,
                                             date_needed=date_needed, user_id=request.user.id,
                                             talent_pool_id=talent_pool_id, search_params=search_params)

            db.session.add(talent_pipeline)
            talent_pipeline_objects.append(talent_pipeline)

        db.session.commit()

        return {
            'talent_pipelines': [talent_pipeline_object.id for talent_pipeline_object in talent_pipeline_objects]
        }

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_TALENT_PIPELINES)
    def put(self, **kwargs):
        """
        PUT /talent-pipelines/<id>  Edit existing talent-pipeline

        Take a JSON dictionary containing TalentPipeline dictionary

        :return A dictionary containing id of edited talent-pipeline

        :rtype: dict
        """

        talent_pipeline_id = kwargs.get('id')

        if not talent_pipeline_id:
            raise InvalidUsage("A valid talent_pipeline_id should be provided")

        talent_pipeline = TalentPipeline.query.get(talent_pipeline_id)
        if not talent_pipeline:
            raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id))

        if request.user.role.name == 'USER' and talent_pipeline.user.id != request.user.id:
            raise ForbiddenError("Logged-in user doesn't have appropriate permissions to edit this talent-pipeline")

        if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id:
            raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain")

        posted_data = request.get_json(silent=True)
        if not posted_data or 'talent_pipeline' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")
        posted_data = posted_data['talent_pipeline']

        name = posted_data.get('name')
        description = posted_data.get('description', '')
        positions = posted_data.get('positions', '')
        date_needed = posted_data.get('date_needed', '')
        talent_pool_id = posted_data.get('talent_pool_id', '')
        is_hidden = posted_data.get('is_hidden', 0)
        search_params = posted_data.get('search_params', dict())

        if name is not None:
            if name:
                if TalentPipeline.query.join(TalentPipeline.user).filter(
                        and_(TalentPipeline.name == name, User.domain_id == request.user.domain_id)).first():
                    raise InvalidUsage("Talent pipeline with name {} already exists in domain {}".format(
                        name, request.user.domain_id))
                talent_pipeline.name = name
            else:
                raise InvalidUsage("Name cannot be an empty string")

        if date_needed:
            try:
                parser.parse(date_needed)
            except Exception as e:
                raise InvalidUsage("Date_needed is not valid as: {}".format(e.message))

            if parser.parse(date_needed) < datetime.utcnow():
                raise InvalidUsage("Date_needed {} cannot be before current date".format(date_needed))

            talent_pipeline.date_needed = date_needed

        if positions:
            if not is_number(positions) or not int(positions) > 0:
                raise InvalidUsage("Number of positions should be integer and greater than zero")

            talent_pipeline.positions = positions

        if description:
            talent_pipeline.description = description

        if talent_pool_id:

            if not is_number(talent_pool_id):
                raise InvalidUsage("talent_pool_id should be an integer")

            talent_pool_id = int(talent_pool_id)
            talent_pool = TalentPool.query.get(talent_pool_id)
            if not talent_pool:
                raise NotFoundError("Talent pool with id {} doesn't exist in database".format(talent_pool_id))

            if talent_pool.domain_id != request.user.domain_id:
                raise ForbiddenError("Logged-in user and given talent-pool belong to different domain")

            talent_pipeline.talent_pool_id = talent_pool_id

        if search_params:
            if not isinstance(search_params, dict):
                raise InvalidUsage("search_params is not provided in valid format")

            # Put into params dict
            for key in search_params:
                if key not in TALENT_PIPELINE_SEARCH_PARAMS and not key.startswith('cf-'):
                    raise NotFoundError("Key[{}] is invalid".format(key))

            talent_pipeline.search_params = json.dumps(search_params)

        if not is_number(is_hidden) or (int(is_hidden) not in (0, 1)):
            raise InvalidUsage("Possible vaues of `is_hidden` are 0 and 1")

        talent_pipeline.is_hidden = int(is_hidden)
        for smartlist in Smartlist.query.filter(Smartlist.talent_pipeline_id == talent_pipeline_id):
            smartlist.is_hidden = int(is_hidden)

        db.session.commit()

        return {
            'talent_pipeline': {'id': talent_pipeline.id}
        }
Exemple #8
0
class TalentPipelinesOfTalentPools(Resource):

    # Access token decorator
    decorators = [require_oauth()]

    # 'SELF' is for readability. It means this endpoint will be accessible to any user
    @require_all_permissions(Permission.PermissionNames.CAN_GET_TALENT_POOLS)
    def get(self, **kwargs):
        """
        GET /talent-pools/<id>/talent-pipelines  Fetch all talent-pipelines of a Talent-Pool

        :return A dictionary containing all talent-pipelines of a Talent Pool
        :rtype: dict
        """
        talent_pool_id = kwargs.get('id')
        talent_pool = TalentPool.query.get(talent_pool_id)

        if not talent_pool:
            raise NotFoundError(
                "Talent pool with id {} doesn't exist in database".format(
                    talent_pool_id))

        if request.user.role.name != 'TALENT_ADMIN' and talent_pool.domain_id != request.user.domain_id:
            raise ForbiddenError(
                "Talent pool and logged in user belong to different domains")

        talent_pool_group = TalentPoolGroup.query.filter_by(
            user_group_id=request.user.user_group_id,
            talent_pool_id=talent_pool_id).first()
        if request.user.role.name == 'USER' and not talent_pool_group:
            raise ForbiddenError(
                "User {} doesn't have appropriate permissions to "
                "get talent-pipelines".format(request.user.id))

        page = request.args.get('page', DEFAULT_PAGE)
        per_page = request.args.get('per_page', DEFAULT_PAGE_SIZE)

        if not is_number(page) or not is_number(
                per_page) or int(page) < 1 or int(per_page) < 1:
            raise InvalidUsage("page and per_page should be positive integers")

        page = int(page)
        per_page = int(per_page)

        talent_pipelines_query = TalentPipeline.query.filter_by(
            talent_pool_id=talent_pool_id)
        talent_pipelines = talent_pipelines_query.paginate(
            page, per_page, False)
        talent_pipelines = talent_pipelines.items

        headers = generate_pagination_headers(talent_pipelines_query.count(),
                                              per_page, page)

        response = {
            'talent_pipelines': [
                talent_pipeline.to_dict(True, get_stats_generic_function)
                for talent_pipeline in talent_pipelines
            ]
        }

        return ApiResponse(response=response, headers=headers, status=200)
Exemple #9
0
class TalentPoolCandidateApi(Resource):

    # Access token decorator
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES)
    def get(self, **kwargs):
        """
        GET /talent-pools/<id>/candidates  Fetch Candidate Statistics of Talent-Pool

        :return A dictionary containing Candidate Statistics of a Talent Pool
        :rtype: dict
        """
        talent_pool_id = kwargs.get('id')
        talent_pool = TalentPool.query.get(talent_pool_id)

        if not talent_pool:
            raise NotFoundError(
                "Talent pool with id {} doesn't exist in database".format(
                    talent_pool_id))

        if request.user.role.name != 'TALENT_ADMIN' and talent_pool.domain_id != request.user.domain_id:
            raise ForbiddenError(
                "Talent pool and logged in user belong to different domains")

        talent_pool_group = TalentPoolGroup.query.filter_by(
            user_group_id=request.user.user_group_id,
            talent_pool_id=talent_pool_id).first()
        if request.user.role.name == 'USER' and not talent_pool_group:
            raise ForbiddenError(
                "User {} doesn't have appropriate permissions to get "
                "candidates".format(request.user.id))

        request_params = dict()
        request_params['facets'] = request.args.get('facets', '')
        request_params['fields'] = request.args.get('fields', '')
        request_params['sort_by'] = request.args.get('sort_by', '')
        request_params['limit'] = request.args.get('limit', '')
        request_params['page'] = request.args.get('page', '')

        search_candidates_response = get_candidates_of_talent_pool(
            talent_pool, request.oauth_token, request_params)

        #  To be backwards-compatible, for now, we add talent_pool_candidates to top level dict
        search_candidates_response['talent_pool_candidates'] = {
            'name': talent_pool.name,
            'total_found': search_candidates_response.get('total_found')
        }

        return search_candidates_response

    # 'SELF' is for readability. It means this endpoint will be accessible to any user
    @require_all_permissions(Permission.PermissionNames.CAN_ADD_CANDIDATES)
    def post(self, **kwargs):
        """
        POST /talent-pools/<id>/candidates   Add input candidates in talent-pool
        input: {'talent_pool_candidates': [talent_pool_candidate_id1, talent_pool_candidate_id2, ... ]}

        :return A dictionary containing candidate_ids which have been added successfully
        :rtype: dict
        """

        talent_pool_id = kwargs.get('id')

        posted_data = request.get_json(silent=True)
        if not posted_data or 'talent_pool_candidates' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        # Save user object(s)
        talent_pool_candidate_ids = posted_data['talent_pool_candidates']

        # Talent_pool object(s) must be in a list
        if not isinstance(talent_pool_candidate_ids, list):
            raise InvalidUsage("Request body is not properly formatted")

        talent_pool = TalentPool.query.get(talent_pool_id)

        if not talent_pool:
            raise NotFoundError(
                "Talent pool with id {} doesn't exist in database".format(
                    talent_pool_id))

        if request.user.role.name != 'TALENT_ADMIN' and talent_pool.domain_id != request.user.domain_id:
            raise ForbiddenError(
                "Talent pool and logged-in user belong to different domains")

        talent_pool_group = TalentPoolGroup.query.filter_by(
            user_group_id=request.user.user_group_id,
            talent_pool_id=talent_pool_id).first()
        if request.user.role.name == 'USER' and not talent_pool_group:
            raise ForbiddenError(
                "User {} doesn't have appropriate permissions to add "
                "candidates".format(request.user.id))

        try:
            talent_pool_candidate_ids = [
                int(talent_pool_candidate_id)
                for talent_pool_candidate_id in talent_pool_candidate_ids
            ]
        except:
            raise InvalidUsage("All candidate ids should be integer")

        # Candidates which already exist in a given talent-pool
        already_existing_candidates_in_talent_pool = TalentPoolCandidate.query.filter(
            and_(
                TalentPoolCandidate.talent_pool_id == talent_pool_id,
                TalentPoolCandidate.candidate_id.in_(
                    talent_pool_candidate_ids))).all()

        # No candidate should already exist in a given talent-pool
        if len(already_existing_candidates_in_talent_pool) > 0:
            raise InvalidUsage(
                "Candidate {} already exists in talent-pool {}".format(
                    already_existing_candidates_in_talent_pool[0].id,
                    talent_pool_id))

        # Candidates with input candidate ids exist in database or not
        for talent_pool_candidate_id in talent_pool_candidate_ids:
            talent_pool_candidate = Candidate.query.get(
                talent_pool_candidate_id)
            if not talent_pool_candidate:
                raise NotFoundError(
                    "Candidate with id {} doesn't exist in database".format(
                        talent_pool_candidate_id))

            if talent_pool_candidate.user.domain_id != talent_pool.domain_id:
                raise ForbiddenError(
                    "Talent Pool {} and Candidate {} belong to different domain"
                    .format(talent_pool.id, talent_pool_candidate.id))
            db.session.add(
                TalentPoolCandidate(talent_pool_id=talent_pool_id,
                                    candidate_id=talent_pool_candidate_id))

        db.session.commit()

        try:
            # Update Candidate Documents in Amazon Cloud Search
            headers = {
                'Authorization': request.oauth_token,
                'Content-Type': 'application/json'
            }
            response = requests.post(
                CandidateApiUrl.CANDIDATES_DOCUMENTS_URI,
                headers=headers,
                data=json.dumps({'candidate_ids': talent_pool_candidate_ids}))

            if response.status_code != 204:
                raise Exception("Status Code: {} Response: {}".format(
                    response.status_code, response.json()))

        except Exception as e:
            raise InvalidUsage(
                "Couldn't update Candidate Documents in Amazon Cloud "
                "Search. Because: {}".format(e.message))

        return {
            'added_talent_pool_candidates': [
                int(talent_pool_candidate_id)
                for talent_pool_candidate_id in talent_pool_candidate_ids
            ]
        }

    @require_all_permissions(Permission.PermissionNames.CAN_DELETE_CANDIDATES)
    def delete(self, **kwargs):
        """
        DELETE /talent-pools/<id>/candidates   Remove input candidates from talent-pool
        input: {'talent_pool_candidates': [talent_pool_candidate_id1, talent_pool_candidate_id2, ... ]}

        :return A dictionary containing candidate_ids which have been removed successfully
        :rtype: dict
        """

        talent_pool_id = kwargs.get('id')

        posted_data = request.get_json(silent=True)
        if not posted_data or 'talent_pool_candidates' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        # Save user object(s)
        talent_pool_candidate_ids = posted_data['talent_pool_candidates']

        # Talent_pool object(s) must be in a list
        if not isinstance(talent_pool_candidate_ids, list):
            raise InvalidUsage("Request body is not properly formatted")

        talent_pool = TalentPool.query.get(talent_pool_id)

        if not talent_pool:
            raise NotFoundError(
                "Talent pool with id {} doesn't exist in database".format(
                    talent_pool_id))

        if request.user.role.name != 'TALENT_ADMIN' and talent_pool.domain_id != request.user.domain_id:
            raise ForbiddenError(
                "Talent pool and logged in user belong to different domains")

        talent_pool_group = TalentPoolGroup.query.filter_by(
            user_group_id=request.user.user_group_id,
            talent_pool_id=talent_pool_id).first()
        if request.user.role.name == 'USER' and not talent_pool_group:
            raise ForbiddenError(
                "User {} doesn't have appropriate permissions to "
                "remove candidates".format(request.user.id))

        for talent_pool_candidate_id in talent_pool_candidate_ids:
            if not is_number(talent_pool_candidate_id):
                raise InvalidUsage(
                    'Candidate id {} should be an integer'.format(
                        talent_pool_candidate_id))
            else:
                talent_pool_candidate_id = int(talent_pool_candidate_id)

            talent_pool_candidate = TalentPoolCandidate.query.filter_by(
                candidate_id=talent_pool_candidate_id,
                talent_pool_id=talent_pool_id).first()
            if not talent_pool_candidate:
                raise NotFoundError(
                    "Candidate {} doesn't belong to talent-pool {}".format(
                        talent_pool_candidate_id, talent_pool_id))
            else:
                db.session.delete(talent_pool_candidate)

        db.session.commit()

        try:
            # Update Candidate Documents in Amazon Cloud Search
            headers = {
                'Authorization': request.oauth_token,
                'Content-Type': 'application/json'
            }
            response = requests.post(
                CandidateApiUrl.CANDIDATES_DOCUMENTS_URI,
                headers=headers,
                data=json.dumps({'candidate_ids': talent_pool_candidate_ids}))

            if response.status_code != 204:
                raise Exception("Status Code: {} Response: {}".format(
                    response.status_code, response.json()))

        except Exception as e:
            raise InvalidUsage(
                "Couldn't update Candidate Documents in Amazon Cloud Search. Because: {}"
                .format(e.message))

        return {
            'talent_pool_candidates': [
                int(talent_pool_candidate_id)
                for talent_pool_candidate_id in talent_pool_candidate_ids
            ]
        }
Exemple #10
0
class TalentPoolGroupApi(Resource):

    # Access token decorator
    decorators = [require_oauth()]

    # 'SELF' is for readability. It means this endpoint will be accessible to any user
    @require_all_permissions(Permission.PermissionNames.CAN_GET_DOMAIN_GROUPS)
    def get(self, **kwargs):
        """
        GET /groups/<group_id>/talent_pools     Fetch all talent-pool objects of given user group

        :return A dictionary containing all talent-pools of a user group
        :rtype: dict
        """

        user_group_id = kwargs.get('group_id')
        user_group = UserGroup.query.get(user_group_id)

        if not user_group:
            raise NotFoundError(
                "User group with id {} doesn't exist".format(user_group_id))

        if request.user.role.name != 'TALENT_ADMIN' and user_group.domain_id != request.user.domain_id:
            raise ForbiddenError(
                "Logged-in user belongs to different domain as given user group"
            )

        talent_pool_ids = [
            talent_pool_group.talent_pool_id
            for talent_pool_group in TalentPoolGroup.query.filter_by(
                user_group_id=user_group_id).all()
        ]

        talent_pools = TalentPool.query.filter(
            TalentPool.id.in_(talent_pool_ids)).all()
        return {
            'talent_pools': [{
                'id': talent_pool.id,
                'name': talent_pool.name,
                'description': talent_pool.description,
                'domain_id': talent_pool.domain_id,
                'user_id': talent_pool.user_id
            } for talent_pool in talent_pools]
        }

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAIN_GROUPS)
    def delete(self, **kwargs):
        """
        DELETE /groups/<group_id>/talent_pools   Remove given input of talent-pool ids from user group
        input: {'talent_pools': [talent_pool_id1, talent_pool_id2, talent_pool_id3, ... ]}

        :return A dictionary containing talent-pool ids which have been removed successfully
        :rtype: dict
        """
        user_group_id = kwargs.get('group_id')

        posted_data = request.get_json(silent=True)
        if not posted_data or 'talent_pools' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        # Save user object(s)
        talent_pool_ids = posted_data['talent_pools']

        # Talent_pool object(s) must be in a list
        if not isinstance(talent_pool_ids, list):
            raise InvalidUsage("Request body is not properly formatted")

        user_group = UserGroup.query.get(user_group_id)

        if not user_group:
            raise NotFoundError(
                "User group with id {} doesn't exist".format(user_group_id))

        if request.user.role.name != 'TALENT_ADMIN' and request.user.domain_id != user_group.domain_id:
            raise ForbiddenError(
                "Logged-in user and given user-group belong to different domains"
            )

        for talent_pool_id in talent_pool_ids:

            if not is_number(talent_pool_id):
                raise InvalidUsage(
                    'Talent pool id {} should be an integer'.format(
                        talent_pool_id))
            else:
                talent_pool_id = int(talent_pool_id)

            talent_pool_group = TalentPoolGroup.query.filter_by(
                user_group_id=user_group_id,
                talent_pool_id=talent_pool_id).first()
            if not talent_pool_group:
                raise NotFoundError(
                    "Talent pool {} doesn't belong to group {}".format(
                        talent_pool_id, user_group_id))
            else:
                db.session.delete(talent_pool_group)

        db.session.commit()

        return {
            'talent_pools':
            [int(talent_pool_id) for talent_pool_id in talent_pool_ids]
        }

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAIN_GROUPS)
    def post(self, **kwargs):
        """
        POST /groups/<group_id>/talent_pools   Add talent-pools to user_group
        input: {'talent_pools': [talent_pool_id1, talent_pool_id2, talent_pool_id3, ... ]}

        :return A dictionary containing talent-pool ids which have been added successfully
        :rtype: dict
        """

        user_group_id = kwargs.get('group_id')

        posted_data = request.get_json(silent=True)
        if not posted_data or 'talent_pools' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        # Save user object(s)
        talent_pool_ids = posted_data['talent_pools']

        # Talent_pool object(s) must be in a list
        if not isinstance(talent_pool_ids, list):
            raise InvalidUsage("Request body is not properly formatted")

        user_group = UserGroup.query.get(user_group_id)

        if not user_group:
            raise NotFoundError(
                "User group with id {} doesn't exist".format(user_group_id))

        if request.user.role.name != 'TALENT_ADMIN' and request.user.domain_id != user_group.domain_id:
            raise ForbiddenError(
                "Logged-in user and given user-group belong to different domains"
            )

        for talent_pool_id in talent_pool_ids:

            if not is_number(talent_pool_id):
                raise InvalidUsage(
                    'Talent pool id {} should be an integer'.format(
                        talent_pool_id))
            else:
                talent_pool_id = int(talent_pool_id)

            if TalentPoolGroup.query.filter_by(
                    user_group_id=user_group_id,
                    talent_pool_id=talent_pool_id).first():
                raise InvalidUsage(
                    "Talent pool {} already belongs to user group {}".format(
                        talent_pool_id, user_group_id))

            talent_pool = TalentPool.query.get(talent_pool_id)
            if not talent_pool:
                raise NotFoundError(
                    "Talent pool {} doesn't exist in database".format(
                        talent_pool_id))

            if user_group.domain_id != talent_pool.domain_id:
                raise ForbiddenError(
                    "Talent pool {} and user_group {} belong to different "
                    "domain".format(talent_pool.name, user_group.name))

            db.session.add(
                TalentPoolGroup(talent_pool_id=talent_pool_id,
                                user_group_id=user_group_id))

        db.session.commit()
        return {
            'added_talent_pools':
            [int(talent_pool_id) for talent_pool_id in talent_pool_ids]
        }
Exemple #11
0
class TalentPoolApi(Resource):

    # Access token decorator
    decorators = [require_oauth()]

    # 'SELF' is for readability. It means this endpoint will be accessible to any user
    @require_all_permissions(Permission.PermissionNames.CAN_GET_TALENT_POOLS)
    def get(self, **kwargs):
        """
        GET /talent-pools/<id>              Fetch talent-pool object
        GET /talent-pools?domain_id=1       Fetch all talent-pool objects of domain of logged-in user

        :return A dictionary containing talent-pool basic info or a dictionary containing all talent-pools of a domain
        :rtype: dict
        """

        talent_pool_id = kwargs.get('id')

        # Getting a single talent-pool
        if talent_pool_id:
            talent_pool = TalentPool.query.get(talent_pool_id)

            if not talent_pool:
                raise NotFoundError(
                    "Talent pool with id {} doesn't exist in database".format(
                        talent_pool_id))

            if request.user.role.name != 'TALENT_ADMIN' and talent_pool.domain_id != request.user.domain_id:
                raise ForbiddenError(
                    "User {} is not authorized to get talent-pool's info".
                    format(request.user.id))

            talent_pool_group = TalentPoolGroup.query.filter_by(
                user_group_id=request.user.user_group_id,
                talent_pool_id=talent_pool_id).all()
            if request.user.role.name == 'USER' and not talent_pool_group:
                raise ForbiddenError(
                    "User {} doesn't have appropriate permissions to get "
                    "talent-pools's info".format(request.user.id))
            return {
                'talent_pool': {
                    'id': talent_pool.id,
                    'name': talent_pool.name,
                    'description': talent_pool.description,
                    'domain_id': talent_pool.domain_id,
                    'user_id': talent_pool.user_id,
                    'added_time': talent_pool.added_time.isoformat(),
                    'updated_time': talent_pool.updated_time.isoformat()
                }
            }
        # Getting all talent-pools of logged-in user's domain
        else:
            domain_id = request.user.domain_id

            # Get all Talent Pools of any domain if user is `TALENT_ADMIN`
            if request.user.role.name == 'TALENT_ADMIN' and request.args.get(
                    'domain_id'):
                domain_id = request.args.get('domain_id')
                if not is_number(domain_id) or not Domain.query.get(
                        int(domain_id)):
                    raise InvalidUsage("Invalid Domain Id is provided")

            talent_pools = TalentPool.query.filter_by(
                domain_id=int(domain_id)).all()
            return {
                'talent_pools': [{
                    'id':
                    talent_pool.id,
                    'name':
                    talent_pool.name,
                    'description':
                    talent_pool.description,
                    'user_id':
                    talent_pool.user_id,
                    'added_time':
                    talent_pool.added_time.isoformat(),
                    'updated_time':
                    talent_pool.updated_time.isoformat(),
                    'accessible_to_user_group_ids': [
                        talent_pool_group.user_group_id
                        for talent_pool_group in TalentPoolGroup.query.
                        filter_by(talent_pool_id=talent_pool.id).all()
                    ]
                } for talent_pool in talent_pools]
            }

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_TALENT_POOLS)
    def put(self, **kwargs):
        """
        PUT /talent-pools/<id>      Modify an already existing talent-pool
        input: {'name': 'facebook-recruiting', 'description': ''}

        :return {'talent_pool': {'id': talent_pool_id}}
        :rtype: dict
        """

        talent_pool_id = kwargs.get('id')
        if not talent_pool_id:
            raise InvalidUsage("A valid talent_pool_id should be provided")

        talent_pool = TalentPool.query.get(talent_pool_id)
        if not talent_pool:
            raise NotFoundError(
                "Talent pool with id {} doesn't exist in database".format(
                    talent_pool_id))

        posted_data = request.get_json(silent=True)
        if not posted_data or 'talent_pool' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        posted_data = posted_data['talent_pool']

        # posted_data must be in a dict
        if not isinstance(posted_data, dict):
            raise InvalidUsage("Request body is not properly formatted")

        if request.user.role.name != 'TALENT_ADMIN' and request.user.domain_id != talent_pool.domain_id:
            raise ForbiddenError(
                "User {} is not authorized to edit talent-pool's info".format(
                    request.user.id))

        name = posted_data.get('name')
        description = posted_data.get('description')

        if not name and not description:
            raise InvalidUsage(
                "Neither modified name nor description is provided")

        if name and not TalentPool.query.filter_by(
                name=name, domain_id=talent_pool.domain_id).all():
            talent_pool.name = name
        elif name:
            raise InvalidUsage(
                "Talent pool {} already exists in domain {}".format(
                    name, talent_pool.domain_id))

        if description:
            talent_pool.description = description

        db.session.commit()

        return {'talent_pool': {'id': talent_pool.id}}

    @require_all_permissions(Permission.PermissionNames.CAN_DELETE_TALENT_POOLS
                             )
    def delete(self, **kwargs):
        """
        DELETE /talent-pools/<id>      Delete an already existing talent-pool
        :return {'delete_talent_pool': {'id': talent_pool_id}}
        :rtype: dict
        """

        talent_pool_id = kwargs.get('id')
        if not talent_pool_id:
            raise InvalidUsage("A valid talent_pool_id should be provided")

        talent_pool = TalentPool.query.get(talent_pool_id)
        if not talent_pool:
            raise NotFoundError(
                "Talent pool with id {} doesn't exist in database".format(
                    talent_pool_id))

        if request.user.role.name != 'TALENT_ADMIN' and request.user.domain_id != talent_pool.domain_id:
            raise ForbiddenError(
                "User {} is not authorized to delete a talent-pool".format(
                    request.user.id))

        talent_pool_candidate_ids = map(
            lambda talent_pool_candidate: talent_pool_candidate[0],
            TalentPoolCandidate.query.with_entities(
                TalentPoolCandidate.candidate_id).filter_by(
                    talent_pool_id=talent_pool_id).all())
        talent_pool.delete()

        if talent_pool_candidate_ids:
            try:
                # Update Candidate Documents in Amazon Cloud Search
                headers = {
                    'Authorization': request.oauth_token,
                    'Content-Type': 'application/json'
                }
                response = requests.post(
                    CandidateApiUrl.CANDIDATES_DOCUMENTS_URI,
                    headers=headers,
                    data=json.dumps(
                        {'candidate_ids': talent_pool_candidate_ids}))

                if response.status_code != 204:
                    raise Exception("Status Code: {} Response: {}".format(
                        response.status_code, response.json()))

            except Exception as e:
                raise InvalidUsage(
                    "Couldn't update Candidate Documents in Amazon Cloud "
                    "Search. Because: {}".format(e.message))

        return {'talent_pool': {'id': talent_pool.id}}

    @require_all_permissions(Permission.PermissionNames.CAN_ADD_TALENT_POOLS)
    def post(self, **kwargs):
        """
        POST /talent-pools    Create new empty talent pools
        input: {'talent_pools': [talent_pool_dict1, talent_pool_dict2, talent_pool_dict3, ... ]}

        Take a JSON dictionary containing array of TalentPool dictionaries
        A single talent-pool dict must contain pool's name

        :return A dictionary containing talent_pools_ids
        :rtype: dict
        """

        posted_data = request.get_json(silent=True)
        if not posted_data or 'talent_pools' not in posted_data:
            raise InvalidUsage("Request body is empty or not provided")

        # Save user object(s)
        talent_pools = posted_data['talent_pools']

        # Talent_pool object(s) must be in a list
        if not isinstance(talent_pools, list):
            raise InvalidUsage("Request body is not properly formatted")

        talent_pool_objects = []
        for talent_pool in talent_pools:

            name = talent_pool.get('name', '').strip()
            description = talent_pool.get('description', '').strip()
            user_object = User.query.get(
                talent_pool.get('user_id', request.user.id))
            if not user_object:
                raise InvalidUsage(
                    "User with id {} doesn't exist in Database".format(
                        user_object.id))

            if request.user.role.name != 'TALENT_ADMIN' and user_object.domain_id != request.user.domain_id:
                raise UnauthorizedError(
                    "User {} doesn't have appropriate permission to "
                    "add TalentPool".format(request.user.id))

            if not name:
                raise InvalidUsage(
                    "A valid name should be provided to create a talent-pool")

            if name and TalentPool.query.filter_by(
                    name=name, domain_id=user_object.domain_id).all():
                raise InvalidUsage(
                    "Talent pool '{}' already exists in domain {}".format(
                        name, user_object.domain_id))

            # Add TalentPool
            talent_pool_object = TalentPool(name=name,
                                            description=description,
                                            domain_id=user_object.domain_id,
                                            user_id=user_object.id)
            talent_pool_objects.append(talent_pool_object)
            db.session.add(talent_pool_object)
            db.session.flush()

            # Add TalentPoolGroup to associate new TalentPool with existing UserGroup
            db.session.add(
                TalentPoolGroup(talent_pool_id=talent_pool_object.id,
                                user_group_id=user_object.user_group_id))

        db.session.commit()

        for talent_pool in talent_pool_objects:
            hash = hashlib.md5()
            hash.update(str(talent_pool.id))
            talent_pool.simple_hash = hash.hexdigest()[:8]
            pool_saved = False
            while not pool_saved:
                try:
                    db.session.add(talent_pool)
                    db.session.commit()
                    pool_saved = True
                except IntegrityError:
                    hash.update(random_word(8))

        return {
            'talent_pools': [
                talent_pool_object.id
                for talent_pool_object in talent_pool_objects
            ]
        }