Example #1
0
def set_user_task_completed(ut_id, correlation_id=None):
    utils.validate_uuid(ut_id)
    # check that user_task exists
    result = get_user_task(ut_id, correlation_id)
    if len(result) == 0:
        errorjson = {
            "user_task_id": ut_id,
            "correlation_id": str(correlation_id)
        }
        raise utils.ObjectDoesNotExistError("user task does not exist",
                                            errorjson)

    if result[0]["project_task_status"] not in ["planned", "testing"]:
        updated_rows_count = execute_non_query(
            sql_q.UPDATE_USER_TASK_STATUS,
            (
                "complete",
                str(utils.now_with_tz()),
                str(ut_id),
            ),
            correlation_id,
        )
        assert (
            updated_rows_count == 1
        ), f"Failed to update status of user task {ut_id}; updated_rows_count: {updated_rows_count}"
    def from_json(cls, ugm_json, correlation_id):
        """
        Creates new object as specified by JSON.
        Checks that attributes are present but does not check for referential integrity (ie that user and group exist)
        :param ugm_json: MUST contain: user_id, user_group_id, may OPTIONALLY include: id, created, modified
        :param correlation_id:
        :return: new ugm object
        """
        try:
            user_id = utils.validate_uuid(ugm_json["user_id"])
            user_group_id = utils.validate_uuid(ugm_json["user_group_id"])
        except utils.DetailedValueError as err:  # uuids are not valid
            err.add_correlation_id(correlation_id)
            raise err
        except KeyError as err:  # mandatory data not present
            error_json = {
                "parameter": err.args[0],
                "correlation_id": str(correlation_id),
            }
            raise utils.DetailedValueError(
                "mandatory data missing", error_json
            ) from err

        ugm = cls(user_id, user_group_id, ugm_json, correlation_id)

        return ugm
 def __init__(self, response_dict, correlation_id=None):
     self.survey_id = response_dict.pop("survey_id", None)
     self.response_id = response_dict.pop("response_id", None)
     self.project_task_id = str(
         utils.validate_uuid(response_dict.pop("project_task_id", None)))
     self.anon_project_specific_user_id = str(
         utils.validate_uuid(
             response_dict.pop("anon_project_specific_user_id", None)))
     self.anon_user_task_id = str(
         utils.validate_uuid(response_dict.pop("anon_user_task_id", None)))
     for required_parameter_name, value in [
         ("survey_id", self.survey_id),
         ("response_id", self.response_id),
     ]:
         if not value:
             raise utils.DetailedValueError(
                 f"Required parameter {required_parameter_name} not present in body of call",
                 details={
                     "response_dict": response_dict,
                     "correlation_id": correlation_id,
                 },
             )
     self.response_dict = response_dict
     self.ddb_client = Dynamodb(
         stack_name=const.STACK_NAME,
         correlation_id=correlation_id,
     )
     self.correlation_id = correlation_id
Example #4
0
def list_user_tasks_by_user(user_id, correlation_id=None):
    try:
        user_id = utils.validate_uuid(user_id)
    except utils.DetailedValueError:
        raise

    # check that user exists
    try:
        user_result = get_user_by_id(user_id, correlation_id)[0]
    except IndexError:
        errorjson = {"user_id": user_id, "correlation_id": str(correlation_id)}
        raise utils.ObjectDoesNotExistError("user does not exist", errorjson)

    result = execute_query(sql_q.LIST_USER_TASKS_SQL, (str(user_id), ),
                           correlation_id)

    # add url field to each user_task in result
    edited_result = list()
    for ut in result:
        user_task = UserTask(correlation_id=correlation_id)
        user_task.from_dict(ut_dict=ut)
        user_task.id = ut["user_task_id"]
        user_task.first_name = user_result["first_name"]
        user_task.last_name = user_result["last_name"]
        user_task.email = user_result["email"]
        ut["url"] = user_task.calculate_url()
        del ut["base_url"]
        del ut["external_task_id"]
        del ut["user_specific_url"]
        del ut["user_task_url"]
        del ut["anon_project_specific_user_id"]
        del ut["anonymise_url"]
        edited_result.append(ut)

    return edited_result
Example #5
0
 def _create_user_task_validate_mandatory_data(self):
     for param in ["user_id", "project_task_id", "consented"]:
         if self.__dict__[param] is None:
             errorjson = {
                 "parameter": param,
                 "correlation_id": str(self._correlation_id),
             }
             raise utils.DetailedValueError("mandatory data missing",
                                            details=errorjson)
     try:
         utils.validate_uuid(self.user_id)
         utils.validate_uuid(self.project_task_id)
         utils.validate_utc_datetime(self.consented)
     except utils.DetailedValueError as err:
         err.add_correlation_id(self._correlation_id)
         raise err
Example #6
0
 def main(self, project_task_id=None):
     self.project_task_id = project_task_id
     if self.project_task_id is None:
         self.project_task_id = utils.validate_uuid(
             input(
                 "Please enter the project_task_id of the completed user tasks we will be creating:"
             ).strip())
     for anon_psu_id in self.anon_ids:
         self.create_user_task_(anon_psu_id)
Example #7
0
def get_user_by_any_anon_id(anon_id, correlation_id=None):
    try:
        anon_id = utils.validate_uuid(anon_id)
    except utils.DetailedValueError as err:
        err.add_correlation_id(correlation_id)
        raise err

    user_json = execute_query(sql_q.GET_USER_BY_ANY_ANON_ID_SQL,
                              ((str(anon_id), ), ) * 2, correlation_id)

    return append_calculated_properties_to_list(user_json)
Example #8
0
def get_user_by_id(user_id, correlation_id=None):

    try:
        user_id = utils.validate_uuid(user_id)
    except utils.DetailedValueError as err:
        err.add_correlation_id(correlation_id)
        raise err

    sql_where_clause = " WHERE id = %s"

    user_json = execute_query(sql_q.BASE_USER_SELECT_SQL + sql_where_clause,
                              (str(user_id), ), correlation_id)

    return append_calculated_properties_to_list(user_json)
Example #9
0
def main(response_dataset, account, survey_id):
    logger = utils.get_logger()
    with open(response_dataset) as csvfile:
        reader = csv.DictReader(csvfile)
        row_counter = 0
        for row in reader:
            row_counter += 1
            logger.info(f"Working on row {row_counter}")
            try:
                response_id = row.pop("ResponseId")
                event_time = row.pop("RecordedDate")
                anon_project_specific_user_id = row.pop(
                    "anon_project_specific_user_id")
                anon_user_task_id = row.pop("anon_user_task_id")
            except KeyError as exc:
                raise utils.DetailedValueError(
                    f"Mandatory {exc} data not found in source event",
                    details={
                        "row": row,
                    },
                )
            try:
                utils.validate_uuid(anon_project_specific_user_id)
            except utils.DetailedValueError:
                logger.warning(
                    f"Could not process row because anon_project_specific_user_id is not valid: {anon_project_specific_user_id}"
                )
                continue
            emulated_event = user_task_completed_event_template.copy()
            emulated_event["time"] = event_time
            emulated_event["detail"] = {
                "anon_project_specific_user_id": anon_project_specific_user_id,
                "anon_user_task_id": anon_user_task_id,
                "response_id": f"{survey_id}-{response_id}",
                "account": account,
            }
            resp.put_task_response(emulated_event, {})
Example #10
0
def list_user_projects(user_id, correlation_id):

    try:
        user_id = utils.validate_uuid(user_id)
    except utils.DetailedValueError:
        raise

    # check that user exists
    result = get_user_by_id(user_id, correlation_id)
    if len(result) == 0:
        errorjson = {"user_id": user_id, "correlation_id": str(correlation_id)}
        raise utils.ObjectDoesNotExistError("user does not exist", errorjson)

    return execute_query(LIST_USER_PROJECTS_SQL, (str(user_id), ),
                         correlation_id)
Example #11
0
def redirect_to_user_interview_task(event, context):
    """
    Updates user task url in response to
    user_interview_task events posted by Qualtrics
    """
    detail_type = event["detail-type"]
    assert (detail_type == "user_interview_task"
            ), f"Unexpected detail-type: {detail_type}"
    event_detail = event["detail"]
    correlation_id = event["id"]
    try:
        anon_user_task_id = utils.validate_uuid(
            event_detail.pop("anon_user_task_id"))
    except KeyError as exc:
        raise utils.DetailedValueError(
            f"Mandatory {exc} data not found in source event",
            details={
                "event": event,
            },
        )

    ssm_client = SsmClient()
    vcs_param = ssm_client.get_parameter(name="video-call-system")
    ut_base_url = vcs_param["base-url"]
    user_task_url = f"{ut_base_url}?response_id={event_detail['response_id']}"

    ut_id = anon_user_task_id_2_user_task_id(anon_user_task_id,
                                             correlation_id=correlation_id)
    updated_rows_count = execute_non_query(
        sql_q.UPDATE_USER_TASK_URL,
        (
            user_task_url,
            str(utils.now_with_tz()),
            str(ut_id),
        ),
        correlation_id,
    )
    assert (
        updated_rows_count == 1
    ), f"Failed to update url of user task {ut_id}; updated_rows_count: {updated_rows_count}"

    body = {
        "user_task_id": ut_id,
        "user_task_url": user_task_url,
    }
    return {"statusCode": HTTPStatus.OK, "body": json.dumps(body)}
Example #12
0
    def get_by_id(cls, user_group_id, correlation_id):

        try:
            user_group_id = validate_uuid(user_group_id)
        except DetailedValueError as err:
            err.add_correlation_id(correlation_id)
            raise err

        sql_where_clause = " WHERE id = %s"

        ug_json = execute_query(
            sql_q.USER_GROUP_BASE_SELECT_SQL + sql_where_clause,
            (str(user_group_id), ),
            correlation_id,
        )
        if len(ug_json) > 0:
            ug_json = ug_json[0]
            return cls.from_json(ug_json, correlation_id)
        else:
            return None
Example #13
0
def get_personal_link_api(event, context):
    valid_accounts = ["cambridge", "thisinstitute"]
    logger = event["logger"]
    correlation_id = event["correlation_id"]
    params = event["queryStringParameters"]
    project_task_id = params.get('project_task_id')

    # validate params
    try:
        survey_id = params["survey_id"]
        anon_project_specific_user_id = str(
            utils.validate_uuid(params["anon_project_specific_user_id"]))
        if (account := params["account"]) not in valid_accounts:
            raise utils.DetailedValueError(
                f'Account {account} is not supported. Valid values are {",".join(valid_accounts)}',
                details={"params": params},
            )
    except KeyError as err:
        raise utils.DetailedValueError(f"Mandatory {err} data not provided",
                                       details={"params": params})

    logger.info(
        "API call",
        extra={
            "anon_project_specific_user_id": anon_project_specific_user_id,
            "correlation_id": correlation_id,
            "survey_id": survey_id,
            "project_task_id": project_task_id,
        },
    )
    plm = PersonalLinkManager(
        survey_id=survey_id,
        anon_project_specific_user_id=anon_project_specific_user_id,
        account=account,
        project_task_id=project_task_id,
    )
    return {
        "statusCode": HTTPStatus.OK,
        "body": json.dumps({"personal_link": plm.get_personal_link()}),
    }
Example #14
0
def get_project_status_for_user_api(event, context):
    logger = event["logger"]
    correlation_id = event["correlation_id"]

    params = event["queryStringParameters"]
    user_id = str(utils.validate_uuid(params["user_id"]))
    try:
        demo = json.loads(params.get("demo", "false"))
        include_non_visible_data = json.loads(
            params.get("include_non_visible_data", "false"))
    except json.decoder.JSONDecodeError:
        raise utils.DetailedValueError(
            "The parameters 'demo' and 'include_non_visible_data', if present, must be either 'true' or 'false' "
            "(lowercase)",
            {},
        )

    logger.info(
        "API call",
        extra={
            "user_id": user_id,
            "correlation_id": correlation_id,
            "event": event
        },
    )
    if user_id == "760f4e4d-4a3b-4671-8ceb-129d81f9d9ca":
        raise ValueError("Deliberate error raised to test error handling")
    return {
        "statusCode":
        HTTPStatus.OK,
        "body":
        json.dumps(
            get_project_status_for_user(
                user_id=user_id,
                demo=demo,
                correlation_id=correlation_id,
                include_non_visible_data=include_non_visible_data,
            )),
    }
Example #15
0
    def check_no_testers_in_data(self):
        users_in_both_groups = list()
        users_in_test_group = list()
        users_in_user_group = list()
        user_ids_in_input_file = list()
        unique_user_ids_in_input_file = set()
        this_users = list()
        with open(self.input_filename) as csv_f:
            reader = csv.DictReader(csv_f)
            for row in reader:
                user_id = row.get(self.user_id_column)

                if user_id is None:
                    anon_project_specific_user_id = row.get(
                        self.anon_project_specific_user_id_column)
                    try:
                        utils.validate_uuid(anon_project_specific_user_id)
                    except utils.DetailedValueError:
                        self.logger.warning(
                            f'anon_project_specific_user_id "{anon_project_specific_user_id}" not valid; row skipped'
                        )
                        continue
                    user = pg_utils.execute_query(
                        sql_q.GET_USER_BY_ANON_PROJECT_SPECIFIC_USER_ID_SQL,
                        [str(anon_project_specific_user_id)],
                    )[0]
                    user_id = user["id"]
                else:
                    try:
                        utils.validate_uuid(user_id)
                    except utils.DetailedValueError:
                        self.logger.warning(
                            f'User id "{user_id}" not valid; row skipped')
                        continue
                    user = pg_utils.execute_query(
                        f"{sql_q.BASE_USER_SELECT_SQL} WHERE id = %s",
                        [str(user_id)],
                    )[0]

                self.user_table.add_row([
                    user["first_name"],
                    user["last_name"],
                    user["email"],
                ])
                user_ids_in_input_file.append(user_id)
                unique_user_ids_in_input_file.add(user_id)
                if (user_id
                        in self.test_group_ids) and (user_id
                                                     in self.user_group_ids):
                    self.logger.warning(
                        f"User {user_id} is both in user group and in test group for this project task"
                    )
                    users_in_both_groups.append(user_id)
                elif user_id in self.test_group_ids:
                    self.logger.error(
                        f"User {user_id} is a tester; they are not in user this project task"
                    )
                    users_in_test_group.append(user_id)
                elif user_id in self.user_group_ids:
                    users_in_user_group.append(user_id)
                    user = u.get_user_by_id(user_id)[0]
                    user_email = user["email"]
                    if user_email:
                        if "@thisinstitute.cam.ac.uk" in user_email:
                            self.logger.error(
                                f"User {user_id} is a member of THIS")
                            this_users.append(user_id)
                else:
                    print("user_group_ids:")
                    pprint(self.user_group_ids)
                    print("\ntest_group_ids:")
                    pprint(self.test_group_ids)

                    err_message = (
                        f"User id {user_id} (anon_project_specific_user_id: {anon_project_specific_user_id}) could not be "
                        f"found on user group or test group of project task {self.project_task_id}. Are "
                        f"you sure you entered the correct project task id?")
                    raise ValueError(err_message)

            number_of_rows_with_duplicate_user_ids = len(
                user_ids_in_input_file) - len(unique_user_ids_in_input_file)

            return (
                users_in_both_groups,
                users_in_test_group,
                users_in_user_group,
                user_ids_in_input_file,
                this_users,
                number_of_rows_with_duplicate_user_ids,
            )
Example #16
0
def create_user_project(up_json, correlation_id, do_nothing_if_exists=False):
    """
    Inserts new UserProject row in thiscovery db

    Args:
        up_json: must contain user_id and project_id; may optionally include id, created, status, anon_project_specific_user_id
        correlation_id:
        do_nothing_if_exists:

    Returns:
    """
    # extract mandatory data from json
    try:
        user_id = utils.validate_uuid(
            up_json["user_id"])  # all public id are uuids
        project_id = utils.validate_uuid(up_json["project_id"])
    except utils.DetailedValueError as err:
        err.add_correlation_id(correlation_id)
        raise err
    except KeyError as err:
        errorjson = {
            "parameter": err.args[0],
            "correlation_id": str(correlation_id)
        }
        raise utils.DetailedValueError("mandatory data missing",
                                       errorjson) from err

    # now process optional json data
    optional_fields_name_default_and_validator = [
        ("anon_project_specific_user_id", str(uuid.uuid4()),
         utils.validate_uuid),
        ("created", str(utils.now_with_tz()), utils.validate_utc_datetime),
        ("status", DEFAULT_STATUS, validate_status),
    ]
    for (
            variable_name,
            default_value,
            validating_func,
    ) in optional_fields_name_default_and_validator:
        if variable_name in up_json:
            try:
                globals()[variable_name] = validating_func(
                    up_json[variable_name]
                )  # https://stackoverflow.com/a/4687672
            except utils.DetailedValueError as err:
                err.add_correlation_id(correlation_id)
                raise err
        else:
            globals()[variable_name] = default_value

    # id shadows builtin function, so treat if separately (using globals() approach above would overwrite that function)
    if "id" in up_json:
        try:
            id = utils.validate_uuid(up_json["id"])
        except utils.DetailedValueError as err:
            err.add_correlation_id(correlation_id)
            raise err
    else:
        id = str(uuid.uuid4())

    # check external account does not already exist
    existing = get_existing_user_project_id(user_id, project_id,
                                            correlation_id)
    if len(existing) > 0:
        if do_nothing_if_exists:
            return existing[0]
        else:
            errorjson = {
                "user_id": user_id,
                "project_id": project_id,
                "correlation_id": str(correlation_id),
            }
            raise utils.DuplicateInsertError("user_project already exists",
                                             errorjson)

    # lookup user id (needed for insert) for user uuid (supplied in json)
    result = get_user_by_id(user_id, correlation_id)
    if len(result) == 0:
        errorjson = {"user_id": user_id, "correlation_id": str(correlation_id)}
        raise utils.ObjectDoesNotExistError("user does not exist", errorjson)

    execute_non_query(
        CREATE_USER_PROJECT_SQL,
        (
            id,
            created,
            created,
            user_id,
            project_id,
            status,
            anon_project_specific_user_id,
        ),
        correlation_id,
    )

    new_user_project = {
        "id": id,
        "created": created,
        "modified": created,
        "user_id": user_id,
        "project_id": project_id,
        "status": status,
        "anon_project_specific_user_id": anon_project_specific_user_id,
    }

    return new_user_project
def create_user_external_account(uea_json, correlation_id):
    # json MUST contain: external_system_id, user_id, external_user_id
    # json may OPTIONALLY include: id, created, status

    # extract mandatory data from json
    try:
        external_system_id = utils.validate_uuid(
            uea_json["external_system_id"])
        user_id = utils.validate_uuid(uea_json["user_id"])
        external_user_id = uea_json["external_user_id"]
    except utils.DetailedValueError as err:
        err.add_correlation_id(correlation_id)
        raise err
    except KeyError as err:
        errorjson = {
            "parameter": err.args[0],
            "correlation_id": str(correlation_id)
        }
        raise utils.DetailedValueError("mandatory data missing",
                                       errorjson) from err

    # now process optional json data
    if "id" in uea_json:
        try:
            id = utils.validate_uuid(uea_json["id"])
        except utils.DetailedValueError as err:
            err.add_correlation_id(correlation_id)
            raise err
    else:
        id = str(uuid.uuid4())

    if "created" in uea_json:
        try:
            created = utils.validate_utc_datetime(uea_json["created"])
        except utils.DetailedValueError as err:
            err.add_correlation_id(correlation_id)
            raise err
    else:
        created = str(utils.now_with_tz())

    if "status" in uea_json:
        try:
            status = validate_status(uea_json["status"])
        except utils.DetailedValueError as err:
            err.add_correlation_id(correlation_id)
            raise err
    else:
        status = DEFAULT_STATUS

    # check external account does not already exist
    existing = check_user_id_and_external_account(user_id, external_system_id,
                                                  correlation_id)
    if len(existing) > 0:
        errorjson = {
            "user_id": user_id,
            "external_system_id": external_system_id,
            "correlation_id": str(correlation_id),
        }
        raise utils.DuplicateInsertError(
            "user_external_account already exists", errorjson)

    # lookup user id (needed for insert) for user uuid (supplied in json)
    existing_user = get_user_by_id(user_id, correlation_id)
    if len(existing_user) == 0:
        errorjson = {"user_id": user_id, "correlation_id": str(correlation_id)}
        raise utils.ObjectDoesNotExistError("user does not exist", errorjson)

    execute_non_query(
        CREATE_USER_EXTERNAL_ACCOUNT_SQL,
        (id, created, created, external_system_id, user_id, external_user_id,
         status),
        correlation_id,
    )

    new_user_external_account = {
        "id": id,
        "created": created,
        "modified": created,
        "external_system_id": external_system_id,
        "user_id": user_id,
        "external_user_id": external_user_id,
        "status": status,
    }

    return new_user_external_account
Example #18
0
def create_user(user_json, correlation_id):
    # json MUST contain: email, first_name, last_name, status
    # json may OPTIONALLY include: id, title, created, auth0_id
    # note that users will always be created with email_address_verified = false

    # extract mandatory data from json
    try:
        email = user_json["email"].lower().strip()
        first_name = user_json["first_name"].strip()
        last_name = user_json["last_name"].strip()
        status = validate_status(user_json["status"])
        country_code = user_json["country_code"].strip()
        country_utils.get_country_name(country_code)
        # looking up the name is a way of validating the code - an invalid code will raise an error
    except utils.DetailedValueError as err:
        err.add_correlation_id(correlation_id)
        raise err

    # now process optional json data
    if "id" in user_json:
        try:
            id = utils.validate_uuid(user_json["id"])
        except utils.DetailedValueError as err:
            err.add_correlation_id(correlation_id)
            raise err
    else:
        id = str(uuid.uuid4())

    if "created" in user_json:
        try:
            created = utils.validate_utc_datetime(user_json["created"])
        except utils.DetailedValueError as err:
            err.add_correlation_id(correlation_id)
            raise err
    else:
        created = str(utils.now_with_tz())

    if "auth0_id" in user_json:
        auth0_id = user_json["auth0_id"]
    else:
        auth0_id = None

    if "title" in user_json:
        title = user_json["title"]
    else:
        title = None

    existing_user = get_user_by_id(id, correlation_id)
    if len(existing_user) > 0:
        errorjson = {"id": id, "correlation_id": str(correlation_id)}
        raise utils.DuplicateInsertError("user already exists", errorjson)

    params = (
        id,
        created,
        created,
        email,
        title,
        first_name,
        last_name,
        country_code,
        auth0_id,
        status,
    )
    execute_non_query(sql_q.CREATE_USER_SQL, params, correlation_id)

    new_user = {
        "id": id,
        "created": created,
        "modified": created,
        "email": email,
        "title": title,
        "first_name": first_name,
        "last_name": last_name,
        "auth0_id": auth0_id,
        "crm_id": None,
        "country_code": country_code,
        "status": status,
    }

    new_user = append_calculated_properties(new_user)

    return new_user