Esempio n. 1
0
def handle_webhook(treatment):
    """
    request.args:
        - job_id: job's id
        - worker_id: worker's id
        - synchron: Directly process data without puting in a queue for another thread

    """
    app.logger.debug("handle_webhook")
    sync_process = False
    auto_finalize = False
    sync_process = request.args.get("synchron", False)
    form = request.form.to_dict()
    if "signal" in form:
        signal = form['signal']
        if signal in {'unit_complete', 'new_judgments'}:
            payload_raw = form['payload']
            signature = form['signature']
            payload = json.loads(payload_raw)
            job_id = payload['job_id']

            job_config = get_job_config(get_db("DB"), job_id)
            payload_ext = payload_raw + job_config["api_key"]
            verif_signature = hashlib.sha1(payload_ext.encode()).hexdigest()
            if signature == verif_signature:
                args = (signal, payload, job_id, job_config, treatment,
                        auto_finalize)
                if sync_process:
                    _process_judgments(*args)
                else:
                    app.config["THREADS_POOL"].starmap_async(
                        _process_judgments, [args])
    else:
        job_id = request.args.get("job_id")
        worker_id = request.args.get("worker_id")
        job_config = get_job_config(get_db("DB"), job_id)
        auto_finalize = True
        payload = {
            "judgments_count": 1,
            "job_id": job_id,
            "results": {
                "judgments": [{
                    "job_id": job_id,
                    "worker_id": worker_id
                }]
            }
        }
        args = ("new_judgments", payload, job_id, job_config, treatment,
                auto_finalize)
        if sync_process:
            _process_judgments(*args)
        else:
            app.config["THREADS_POOL"].starmap_async(_process_judgments,
                                                     [args])
        # flash("You may close this tab now and continue with the survey.")
        # return render_template("info.html", job_id=job_id, webhook=True)
    return Response(status=200)
Esempio n. 2
0
def test_index(client):
    path = f"/admin"
    job_id = "test_index_job"
    api_key = "api_key"
    secret = app.config["ADMIN_SECRET"]
    base_code = "base_code"
    expected_judgments = 16
    with app.test_request_context(path):
        # if clear_session:
        #     with client:
        #         with client.session_transaction() as sess:
        #             sess.clear()
        client.get(path, follow_redirects=True)
        return client.post(path,
                           data={
                               "job_id": job_id,
                               api_key: "api_key",
                               "secret": secret,
                               "base_code": base_code,
                               "expected_judgments": expected_judgments
                           },
                           follow_redirects=True)
    job_config = get_job_config(get_db(), job_id)
    assert job_config["job_id"] == job_id
    assert job_config["base_code"] == base_code
    assert job_config["api_key"] == api_key
    assert job_config["expected_judgments"] == expected_judgments

    expected_judgments = 32
    base_code = "super_base_code"
    job_config["expected_judgments"] = expected_judgments
    job_config["base_code"] = base_code
    with app.test_request_context(path):
        client.get(path, follow_redirects=True)
        return client.post(path, data=job_config, follow_redirects=True)
Esempio n. 3
0
def test_get_job_config():
    job_id = "--------"
    with app.app_context():
        job_config = get_job_config(get_db(), job_id=job_id)
        assert job_config["job_id"] == job_id
        assert "api_key" in job_config
        assert "nb_rows" in job_config
        assert "unique_worker" in job_config
        assert "base_code" in job_config
        assert "expected_judgments" in job_config
        assert "payment_max_cents" in job_config
        assert len(job_config) == 7
Esempio n. 4
0
def generate_completion_code(base, job_id):
    """
    :param base:
    :param job_id:
    """
    app.logger.debug("generate_completion_code")
    job_config = get_job_config(get_db("DB"), job_id)
    base_completion_code = job_config["base_code"]
    part1 = f"{base}:"
    part2 = base_completion_code
    part3 = "".join(random.choices(string.ascii_letters + string.digits, k=5))
    return "".join([part1, part2, part3])
Esempio n. 5
0
def handle_survey_feedback(treatment=None,
                           template=None,
                           code_prefixes=None,
                           form_class=None,
                           overview_url=None):
    app.logger.info("handle_survey_feedback")
    if code_prefixes is None:
        code_prefixes = {
            "code_cpc": "cpc:",
            "code_exp": "exp:",
            "code_risk": "risk:",
            "code_cc": "cc:",
            "code_ras": "ras:"
        }
    if form_class is None:
        form_class = MainFormFeeback
    if template is None:
        template = "txx/survey_feedback.html"
    if overview_url is None:
        if treatment and treatment.upper() in TREATMENTS_AUTO_DSS:
            overview_url = url_for("overview_auto_prop_feedback")
        else:
            overview_url = url_for("overview_feedback")
    cookie_obj = get_cookie_obj(BASE)

    adapter_cookie = get_adapter_from_dict(cookie_obj.get("adapter", {}))
    adapter_args = get_adapter_from_dict(request.args)
    adapter_referrer = get_adapter()

    if adapter_referrer.job_id not in {"", "na", None}:
        adapter = adapter_referrer
    elif adapter_cookie.job_id not in {"", "na", None}:
        adapter = adapter_cookie
    else:
        adapter = adapter_args

    app.logger.debug(f"adapter: {adapter.to_dict()}")
    worker_code_key = f"{BASE}_worker_code"

    arg_job_id = adapter.get_job_id()
    arg_worker_id = adapter.get_worker_id()
    job_id = arg_job_id or f"na"
    worker_id = arg_worker_id
    if worker_id in {"", "na", None}:
        worker_id = str(uuid.uuid4())
        adapter.worker_id = worker_id

    max_judgments = 0  #not set
    if adapter.has_api():
        api = adapter.get_api()
        max_judgments = api.get_max_judgments()
    else:
        try:
            max_judgments = int(
                request.args.get("max_judgments", max_judgments))
        except:
            pass
    job_config = get_job_config(get_db(), job_id)
    max_judgments = max(max_judgments, job_config.expected_judgments)
    next_player = check_is_proposer_next(job_id,
                                         worker_id,
                                         treatment,
                                         max_judgments=max_judgments)

    table_all = get_table("txx", "all", schema=None)
    # The task was already completed, so we skip to the completion code display
    if cookie_obj.get(BASE) and cookie_obj.get(worker_code_key):
        req_response = make_response(
            redirect(url_for(f"{treatment}.survey.survey_done",
                             **request.args)))
        set_cookie_obj(req_response, BASE, cookie_obj)
        return req_response
    if treatment is None:
        treatment = get_latest_treatment()
    cookie_obj["job_id"] = job_id
    cookie_obj["worker_id"] = worker_id
    cookie_obj["assignment_id"] = adapter.get_assignment_id()
    cookie_obj["treatment"] = treatment
    cookie_obj["adapter"] = adapter.to_dict()
    cookie_obj[BASE] = True
    form = form_class(request.form)
    drop = request.form.get("drop")
    con = get_db()
    # if table_exists(con, table_all):
    #     with con:
    #         res = con.execute(f"SELECT * from {table_all} WHERE worker_id=?", (worker_id,)).fetchone()
    #         if res:
    #             flash(f"You already took part on this survey. You can just ignore this HIT for the assignment to be RETURNED later to another worker or you can submit right now for a REJECTION using the survey code provided.")
    #             req_response = make_response(render_template("error.html", worker_code=WORKER_CODE_DROPPED))
    #             return req_response

    #The next player should be a proposer but some responders may still be processing data
    ## Should not matter in feedback surveys
    if next_player == NEXT_IS_WAITING:
        resp_table = get_table(base="resp",
                               job_id=job_id,
                               schema="result",
                               treatment=treatment)
        prop_table = get_table(base="prop",
                               job_id=job_id,
                               schema="result",
                               treatment=treatment)
        # We make sure the user didn't accidentaly refreshed the page after processing the main task
        if (not is_worker_available(worker_id, resp_table)
                and not is_worker_available(worker_id, prop_table)):
            flash(
                "Unfortunately there is no task available right now. Please check again in 15 minutes. Otherwise you can just ignore this HIT for the assignment to be RETURNED later to another worker or you can submit right now for a REJECTION using the survey code provided."
            )
            return render_template("error.html",
                                   worker_code=WORKER_CODE_DROPPED)

    if adapter.is_preview() or job_id == "na":
        flash(
            "Please do note that you are currently in the preview mode of the survey. You SHOULD NOT FILL NEITHER SUBMIT the survey in this mode. Please go back to Mturk and read the instructions about how to correctly start this survey."
        )

    if request.method == "POST" and (drop == "1" or form.validate_on_submit()):
        form = form_class(request.form)
        response = request.form.to_dict()
        response["timestamp"] = str(datetime.datetime.now())
        cookie_obj["response"] = response
        is_codes_valid = True
        # Responders have to fill and submit tasks
        if is_codes_valid and job_id != "na" and worker_id != "na":
            cookie_obj["response"] = response
            #NOTE: the url should be pointing to handle_survey_feedback_done
            req_response = make_response(
                redirect(
                    url_for(f"{treatment}.survey.survey_done",
                            **request.args)))
            set_cookie_obj(req_response, BASE, cookie_obj)
            return req_response
        elif is_codes_valid:
            flash(
                "Your data was submitted and validated but not save as you are currently in the preview mode of the survey. Please go back to Mturk and read the instructions about how to correctly start this survey."
            )

    req_response = make_response(
        render_template(template,
                        job_id=job_id,
                        worker_id=worker_id,
                        treatment=treatment,
                        form=form,
                        max_judgments=max_judgments,
                        max_gain=MAX_GAIN,
                        maximum_control_mistakes=MAXIMUM_CONTROL_MISTAKES,
                        overview_url=overview_url,
                        tasks=app.config["TASKS"]))
    set_cookie_obj(req_response, BASE, cookie_obj)
    return req_response
Esempio n. 6
0
def check_is_proposer_next(job_id,
                           worker_id,
                           treatment,
                           max_judgments=None,
                           resp_only=None,
                           prop_only=None):
    app.logger.debug("check_is_proposer_next")
    resp_table = get_table(resp_BASE,
                           job_id=job_id,
                           schema="result",
                           treatment=treatment)
    prop_table = get_table(prop_BASE,
                           job_id=job_id,
                           schema="result",
                           treatment=treatment)
    prop_table_data = get_table(prop_BASE,
                                job_id=job_id,
                                schema="data",
                                treatment=treatment)
    job_config = get_job_config(get_db("DB"), job_id)

    con = get_db("DATA")
    nb_resp = 0
    nb_prop = 0
    nb_prop_open = 0
    if table_exists(con, resp_table):
        with con:
            tmp = con.execute(
                f"SELECT COUNT(*) as count from {resp_table} where job_id=?",
                (job_id, )).fetchone()
            if tmp:
                nb_resp = tmp["count"]
    if table_exists(con, prop_table_data):
        with con:
            judging_timeout = time.time() - JUDGING_TIMEOUT_SEC
            tmp = con.execute(
                f"SELECT COUNT(*) as count from {prop_table_data} where (job_id=? OR job_id like 'REF%') and ({STATUS_KEY}=? OR ({STATUS_KEY}=? and {LAST_MODIFIED_KEY}<?) OR ({WORKER_KEY}=?))",
                (job_id, RowState.JUDGEABLE, RowState.JUDGING, judging_timeout,
                 worker_id)).fetchone()
            if tmp:
                nb_prop_open = tmp["count"]
    if table_exists(con, prop_table):
        with con:
            tmp = con.execute(
                f"SELECT COUNT(*) as count from {prop_table} where (job_id=? OR job_id like 'REF%')",
                (job_id, )).fetchone()
            if tmp:
                nb_prop = tmp["count"]

    #TODO: if nb_resp >= expected row/2, should only take props
    if max_judgments is None or max_judgments == 0:
        max_judgments = job_config["expected_judgments"]

    max_resp = (max_judgments // 2)
    max_prop = (max_judgments // 2)

    if resp_only:
        max_resp = max_judgments
    elif prop_only:
        max_prop = max_judgments

    if max_judgments > 0:
        #if (max_judgments // 2) <= nb_resp and (max_judgments // 2) > nb_prop:
        if max_resp <= nb_resp and max_prop > nb_prop:
            if nb_prop_open > 0:
                is_proposer = NEXT_IS_PROPOSER
            else:
                is_proposer = NEXT_IS_WAITING
        elif nb_prop_open > 0:
            is_proposer = NEXT_IS_PROPOSER
        else:
            if resp_only or prop_only:
                is_proposer = NEXT_IS_WAITING
            else:
                is_proposer = NEXT_IS_RESPONDER

        if resp_only:
            if max_judgments > nb_resp:
                is_proposer = NEXT_IS_RESPONDER
            else:
                is_proposer = NEXT_IS_WAITING
        elif prop_only:
            if max_judgments > nb_prop:
                is_proposer = NEXT_IS_PROPOSER
            else:
                is_proposer = NEXT_IS_WAITING
    elif nb_prop_open > 0:
        is_proposer = NEXT_IS_PROPOSER
    else:
        is_proposer = NEXT_IS_RESPONDER

    app.logger.debug(
        f"max_judgments: {max_judgments}, nb_prop: {nb_prop}, nb_resp: {nb_resp}, nb_prop_open: {nb_prop_open}, is_proposer: {is_proposer}"
    )
    return is_proposer
Esempio n. 7
0
def emit_webhook(client,
                 url,
                 job_id="test",
                 worker_id=None,
                 signal="new_judgments",
                 unit_state="finalized",
                 treatment="t10",
                 by_get=True):
    """
    :param client:
    :param url: (str) relative path to target api
    :param job_id: (str)
    :param worker_id: (str)
    :param signal: (new_judgments|unit_complete)
    :param unit_state: (finalized|new|judging|judgeable?)
    :param by_get: (True|False) simulate a setup were each user triggers the webhook using a get request
    """
    app.logger.debug(
        f"emit_webhook: job_id: {job_id}, worker_id: {worker_id}, treatment: {treatment}"
    )
    proceed = False
    max_retries = 5
    with app.app_context():
        while not proceed and max_retries > 0:
            table_resp = get_table("resp",
                                   job_id,
                                   "result",
                                   treatment=treatment)
            table_prop = get_table("prop",
                                   job_id,
                                   "result",
                                   treatment=treatment)
            app.logger.debug("Waiting for the db...")

            with get_db() as con:
                if con.execute(f"SELECT * FROM {table_resp} where worker_id=?",
                               (worker_id, )).fetchone():
                    proceed = True
                elif con.execute(
                        f"SELECT * FROM {table_prop} where worker_id=?",
                    (worker_id, )).fetchone():
                    proceed = True
            con = None
            time.sleep(0.01)
            max_retries -= 1
        data_dict = dict(webhook_data_template)
        data_dict["signal"] = signal
        data_dict["payload"]["job_id"] = job_id
        data_dict["payload"]["results"]["judgments"][0]["job_id"] = job_id
        data_dict["payload"]["results"]["judgments"][0][
            "worker_id"] = worker_id
        data_dict["payload"]["results"]["judgments"][0][
            "unit_state"] = unit_state
        with get_db() as con:
            job_config = get_job_config(con, job_id)
        payload = json.dumps(data_dict["payload"])
        payload_ext = payload + str(job_config["api_key"])
        signature = hashlib.sha1(payload_ext.encode()).hexdigest()
        data = {"signal": signal, "payload": payload, "signature": signature}
        if by_get:
            # An empty form is submitted when triggering the webhook by click
            data = {}
            if "?" in url:
                url += f"&job_id={job_id}&worker_id={worker_id}"
            else:
                url += f"?job_id={job_id}&worker_id={worker_id}"
            res = client.get(url, follow_redirects=True).status
        else:
            res = client.post(url, data=data, follow_redirects=True).status
        return res
Esempio n. 8
0
def pay_worker_bonus(job_id,
                     worker_id,
                     api,
                     con=None,
                     assignment_id=None,
                     send_notification=False):
    """
    :param job_id:
    :param worker_id:
    :param bonus_cents:
    :param api:
    :param overwite:
    :param con:
    :param assignment_id:
    :returns True if payment was done, False otherwise
    """
    app.logger.debug("pay_worker_bonus")
    if con is None:
        con = get_db("DB")
    table = get_table("jobs", job_id=job_id, schema=None, category="payment")
    job_config = get_job_config(con, job_id)

    should_pay = False
    bonus_cents = 0
    new_paid_bonus_cents = 0
    if table_exists(con, table):
        with con:
            row = con.execute(
                f'select bonus_cents, paid_bonus_cents, rowid from {table} WHERE job_id==? and worker_id==?',
                (job_id, worker_id)).fetchone()
            if row:
                bonus_cents = row["bonus_cents"]
                should_pay = bonus_cents > 0
                new_paid_bonus_cents = row["bonus_cents"] + row[
                    "paid_bonus_cents"]
            else:
                app.logger.warning(
                    f"pay_worker_bonus: worker not found! job_id: {job_id}, worker_id: {worker_id}"
                )

    if should_pay:
        #TODO: CHECK LATER FOR PAYMENT
        app.logger.info(f"SHOULD BE PAYING: {bonus_cents} cents")
        if job_config["payment_max_cents"] > 0 and job_config[
                "payment_max_cents"] > bonus_cents:
            app.logger.warning(
                f"Attempted payment over max allowed payment to worker {worker_id} on job {job_id}"
            )
            return False
        success = api.contributor_pay(worker_id, bonus_cents, assignment_id)
        if not success:
            app.logger.info(
                f"Impossible to pay: {bonus_cents} cents to contributor {worker_id}"
            )
            return False
        else:
            with con:
                update(
                    f"UPDATE {table} SET bonus_cents=?, paid_bonus_cents=? WHERE rowid=?",
                    (0, new_paid_bonus_cents, row["rowid"]),
                    con=con)
        if send_notification:
            api.contributor_notify(
                worker_id,
                f"Thank you for your participation. You just received your total bonus of {cents_repr(bonus_cents)} ^_^"
            )
        return True
    else:
        if send_notification:
            api.contributor_notify(
                worker_id,
                f"Thank you for your participation. Either you have already been paid or your bonus amount to 0.0 USD. ^_^"
            )
    return False