Ejemplo n.º 1
0
def manage_projects():
    result = None
    is_active = request.args.get('is_active', None)
    if request.method == 'POST':
        model = {x: request.form[x] for x in request.form}
        project_id = model.pop('project_id')
        model['is_microsetta'] = model.get('is_microsetta', '') == 'true'
        model['bank_samples'] = model.get('bank_samples', '') == 'true'
        model = _translate_nones(model, False)

        if project_id.isdigit():
            # update (put) an existing project
            action = "update"
            status, api_output = APIRequest.put('{}/{}'.format(
                API_PROJECTS_URL, project_id),
                                                json=model)
        else:
            # create (post) a new project
            action = "create"
            status, api_output = APIRequest.post(API_PROJECTS_URL, json=model)

        # if api post or put failed
        if status >= 400:
            result = {'error_message': f'Unable to {action} project.'}
    # end if post

    # if the above work (if any) didn't produce an error message, return
    # the projects list
    if result is None:
        _, result = _get_projects(include_stats=True, is_active=is_active)

    return render_template('manage_projects.html',
                           **build_login_variables(),
                           result=result), 200
Ejemplo n.º 2
0
def submit_daklapack_order():
    error_msg_key = "error_message"

    def return_error(msg):
        return render_template('submit_daklapack_order.html',
                               **build_login_variables(),
                               error_message=msg)

    status, dak_articles_output = APIRequest.get(
        '/api/admin/daklapack_articles')
    if status >= 400:
        return return_error("Unable to load daklapack articles list.")

    status, projects_output = _get_projects(include_stats=False,
                                            is_active=True)
    if status >= 400:
        return return_error(projects_output[error_msg_key])

    return render_template(
        'submit_daklapack_order.html',
        **build_login_variables(),
        error_message=None,
        dummy_status=DUMMY_SELECT_TEXT,
        dak_articles=dak_articles_output,
        contact_phone_number=SERVER_CONFIG["order_contact_phone"],
        projects=projects_output['projects'])
Ejemplo n.º 3
0
def _fetch_barcode_metadata(sample_barcode):
    """Query the private API to obtain per-sample metadata

    Parameters
    ----------
    sample_barcode : str
        The barcode to request

    Returns
    -------
    dict
        The survey responses associated with the sample barcode
    dict or None
        Any error information associated with the retreival. If an error is
        observed, the survey responses should not be considered valid.
    """
    errors = None

    status, response = APIRequest.get(
        '/api/admin/metadata/samples/%s/surveys/' % sample_barcode)
    if status != 200:
        errors = {
            "barcode": sample_barcode,
            "error": str(status) + " from api"
        }

    return response, errors
Ejemplo n.º 4
0
def _fetch_survey_template(template_id, ids):
    """Fetch the survey structure to get full multi-choice detail

    Parameters
    ----------
    template_id : int
        The survey template ID to fetch
    ids : dict
        An account and source ID to use

    Returns
    -------
    dict
        The survey structure as returned from the private API
    dict or None
        Any error information associated with the retreival. If an error is
        observed, the survey responses should not be considered valid.
    """
    errors = None

    ids['template_id'] = template_id
    url = ("/api/accounts/%(account_id)s/sources/%(source_id)s/"
           "survey_templates/%(template_id)d?language_tag=en-US")

    status, response = APIRequest.get(url % ids)
    if status != 200:
        errors = {"ids": ids, "error": str(status) + " from api"}

    return response, errors
Ejemplo n.º 5
0
def new_kits():
    _, result = _get_projects(include_stats=False, is_active=True)
    projects = result.get('projects')

    if request.method == 'GET':
        return render_template('create_kits.html',
                               error_message=result.get('error_message'),
                               projects=projects,
                               **build_login_variables())

    elif request.method == 'POST':
        num_kits = int(request.form['num_kits'])
        num_samples = int(request.form['num_samples'])
        prefix = request.form['prefix']
        selected_project_ids = request.form.getlist('project_ids')
        payload = {
            'number_of_kits': num_kits,
            'number_of_samples': num_samples,
            'project_ids': selected_project_ids
        }
        if prefix:
            payload['kit_id_prefix'] = prefix

        status, result = APIRequest.post('/api/admin/create/kits',
                                         json=payload)

        if status != 201:
            return render_template('create_kits.html',
                                   error_message='Failed to create kits',
                                   projects=projects,
                                   **build_login_variables())

        # StringIO/BytesIO based off https://stackoverflow.com/a/45111660
        buf = io.StringIO()
        payload = io.BytesIO()

        # explicitly expand out the barcode detail
        kits = pd.DataFrame(result['created'])
        for i in range(num_samples):
            kits['barcode_%d' % (i + 1)] = [
                r['sample_barcodes'][i] for _, r in kits.iterrows()
            ]
        kits.drop(columns='sample_barcodes', inplace=True)

        kits.to_csv(buf, sep=',', index=False, header=True)
        payload.write(buf.getvalue().encode('utf-8'))
        payload.seek(0)
        buf.close()

        stamp = datetime.now().strftime('%d%b%Y-%H%M')
        fname = f'kits-{stamp}.csv'

        return send_file(payload,
                         as_attachment=True,
                         attachment_filename=fname,
                         mimetype='text/csv')
Ejemplo n.º 6
0
def search_result():
    query = request.form['search_term']

    barcode_result, barcode_status = APIRequest.get('/.../scan/%s' % query)
    name_result, name_status = APIRequest.get('/.../name/%s' % query)
    kitid_result, kitid_status = APIRequest.get('/.../kitid/%s' % query)

    error = False
    status = 200
    if barcode_status == 200:
        result = barcode_result
    elif name_status == 200:
        result = name_result
    elif kitid_status == 200:
        result = kitid_result
    else:
        error = True
        status = 404
        result = {'message': 'Nothing was found.'}

    return render_template('search_result.html',
                           **build_login_variables(),
                           result=result,
                           error=error), status
Ejemplo n.º 7
0
def _get_projects(include_stats, is_active):
    projects_uri = API_PROJECTS_URL + f"?include_stats={include_stats}"
    if is_active is not None:
        projects_uri += f"&is_active={is_active}"
    status, projects_output = APIRequest.get(projects_uri)

    if status >= 400:
        result = {
            'error_message': f"Unable to load project list: "
            f"{projects_uri}"
        }
    else:
        cleaned_projects = [_translate_nones(x, True) for x in projects_output]
        # if we're not using full project stats, sort
        # alphabetically by project name
        if not include_stats:
            cleaned_projects = sorted(cleaned_projects,
                                      key=lambda k: k['project_name'])
        result = {'projects': cleaned_projects}

    return status, result
Ejemplo n.º 8
0
def _search(resource=None):
    if request.method == 'GET':
        return render_template('search.html', **build_login_variables())
    elif request.method == 'POST':
        query = request.form['search_%s' % resource]

        status, result = APIRequest.get('/api/admin/search/%s/%s' %
                                        (resource, query))

        if status == 404:
            result = {'error_message': "Query not found"}
            return render_template('search_result.html',
                                   **build_login_variables(),
                                   result=result), 200
        elif status == 200:
            return render_template('search_result.html',
                                   **build_login_variables(),
                                   resource=resource,
                                   result=result), 200
        else:
            return result
Ejemplo n.º 9
0
def _get_by_sample_barcode(sample_barcodes, strip_sampleid, projects):
    payload = {'sample_barcodes': sample_barcodes}
    status, result = APIRequest.post('/api/admin/account_barcode_summary?'
                                     'strip_sampleid=%s' % str(strip_sampleid),
                                     json=payload)
    if status == 200:
        if result['partial_result'] is True:
            unprocessed_barcodes = result['unprocessed_barcodes']
        else:
            unprocessed_barcodes = None

        resource = pd.DataFrame(result['samples'])
        order = [
            'sampleid', 'project', 'account-email', 'source-email',
            'source-type', 'site-sampled', 'sample-status', 'sample-received',
            'ffq-taken', 'ffq-complete', 'vioscreen_username'
        ]
        order.extend(sorted(set(resource.columns) - set(order)))
        resource = resource[order]

        if unprocessed_barcodes:
            return render_template('per_sample_summary.html',
                                   resource=resource,
                                   projects=projects,
                                   error_message="Too many barcodes. S"
                                   "erver processed only"
                                   " the first 1000.",
                                   **build_login_variables())
        else:
            return render_template('per_sample_summary.html',
                                   resource=resource,
                                   projects=projects,
                                   **build_login_variables())
    else:
        return render_template('per_sample_summary.html',
                               resource=None,
                               projects=projects,
                               error_message=result,
                               **build_login_variables())
Ejemplo n.º 10
0
def post_submit_daklapack_order():
    def return_error(msg):
        return render_template('submit_daklapack_order.html',
                               **build_login_variables(),
                               error_message=msg)

    error_message = success_submissions = failure_submissions = headers = None
    expected_headers = [
        "firstName", "lastName", "address1", "insertion", "address2",
        "postalCode", "city", "state", "country", "countryCode"
    ]

    # get required fields; cast where expected by api
    phone_number = request.form['contact_phone_number']
    project_ids_list = list(map(int, request.form.getlist('projects')))
    dak_article_code = request.form['dak_article_code']
    article_quantity = int(request.form['quantity'])
    file = request.files['addresses_file']

    # get optional fields or defaults
    planned_send_str = request.form.get('planned_send_date')
    planned_send_date = planned_send_str if planned_send_str else None

    description = request.form.get('description')
    fedex_ref_1 = request.form.get('fedex_ref_1')
    fedex_ref_2 = request.form.get('fedex_ref_2')
    fedex_ref_3 = request.form.get('fedex_ref_3')

    try:
        # NB: import everything as a string so that zip codes beginning with
        # zero (e.g., 06710) don't get silently cast to numbers
        if file.filename.endswith('xls'):
            addresses_df = pd.read_excel(file, dtype=str)
        elif file.filename.endswith('xlsx'):
            addresses_df = pd.read_excel(file, engine='openpyxl', dtype=str)
        else:
            raise ValueError(f"Unrecognized extension on putative excel "
                             f"filename: {file.filename}")

        headers = list(addresses_df.columns)
    except Exception as e:  # noqa
        return return_error('Could not parse addresses file')

    if headers != expected_headers:
        return return_error(f"Received column names {headers} do "
                            f"not match expected column names"
                            f" {expected_headers}")

    # add (same) contact phone number to every address
    addresses_df['phone'] = phone_number

    addresses_df = addresses_df.fillna("")
    temp_dict = addresses_df.to_dict(orient='index')
    addresses_list = [temp_dict[n] for n in range(len(temp_dict))]

    status, post_output = APIRequest.post('/api/admin/daklapack_orders',
                                          json={
                                              "project_ids": project_ids_list,
                                              "article_code": dak_article_code,
                                              "quantity": article_quantity,
                                              "addresses": addresses_list,
                                              "planned_send_date":
                                              planned_send_date,
                                              "description": description,
                                              "fedex_ref_1": fedex_ref_1,
                                              "fedex_ref_2": fedex_ref_2,
                                              "fedex_ref_3": fedex_ref_3
                                          })

    # if the post failed, keep track of the error so it can be displayed
    if status != 200:
        error_message = post_output
    else:
        order_submissions = post_output["order_submissions"]
        success_submissions = [
            x for x in order_submissions if x["order_success"]
        ]
        failure_submissions = [
            x for x in order_submissions if not x["order_success"]
        ]

    return render_template('submit_daklapack_order.html',
                           **build_login_variables(),
                           error_message=error_message,
                           success_submissions=success_submissions,
                           failure_submissions=failure_submissions)
Ejemplo n.º 11
0
def _scan_post_update_info(sample_barcode, technician_notes, sample_status,
                           action, issue_type, template, received_type,
                           recorded_type):

    ###
    # Bugfix Part 1 for duplicate emails being sent.  Theory is that client is
    # out of sync due to hitting back button after a scan has changed
    # state.
    # Can't test if client is up to date without ETags, so for right now,
    # we just validate whether or not they should send an email, duplicating
    # the client log.  (This can still break with multiple admin clients,
    # but that is unlikely at the moment.)
    latest_status = None
    # TODO:  Replace this with ETags!
    status, result = APIRequest.get('/api/admin/search/samples/%s' %
                                    sample_barcode)

    if result['latest_scan']:
        latest_status = result['latest_scan']['sample_status']
    ###

    # Do the actual update
    status, response = APIRequest.post('/api/admin/scan/%s' % sample_barcode,
                                       json={
                                           "sample_status": sample_status,
                                           "technician_notes": technician_notes
                                       })

    # if the update failed, keep track of the error so it can be displayed
    if status != 201:
        update_error = response
        return _scan_get(sample_barcode, update_error)
    else:
        update_error = None

    # If we're not supposed to send an email, go back to GET
    if action != "send_email":
        return _scan_get(sample_barcode, update_error)

    ###
    # Bugfix Part 2 for duplicate emails being sent.
    if sample_status == latest_status:
        # This is what we'll hit if javascript thinks it's updating status
        # but is out of sync with the database.
        update_error = "Ignoring Send Email, sample_status would " \
                       "not have been updated (Displayed page was out of " \
                       "sync)"
        return _scan_get(sample_barcode, update_error)
    ###

    # This is what we'll hit if there are no email templates to send for
    # the new sample status (or if we screw up javascript side :D )
    if template is None:
        update_error = "Cannot Send Email: No Issue Type Specified " \
                       "(or no issue types available)"
        return _scan_get(sample_barcode, update_error)

    # Otherwise, send out an email to the end user
    status, response = APIRequest.post('/api/admin/email',
                                       json={
                                           "issue_type": issue_type,
                                           "template": template,
                                           "template_args": {
                                               "sample_barcode":
                                               sample_barcode,
                                               "recorded_type": recorded_type,
                                               "received_type": received_type
                                           }
                                       })

    # if the email failed to send, keep track of the error
    # so it can be displayed
    if status != 200:
        update_error = response
    else:
        update_error = None

    return _scan_get(sample_barcode, update_error)
Ejemplo n.º 12
0
def _scan_get(sample_barcode, update_error):
    # If there is no sample_barcode in the GET
    # they still need to enter one in the box, so show empty page
    if sample_barcode is None:
        return render_template('scan.html', **build_login_variables())

    # Assuming there is a sample barcode, grab that sample's information
    status, result = APIRequest.get('/api/admin/search/samples/%s' %
                                    sample_barcode)

    # If we successfully grab it, show the page to the user
    if status == 200:
        # Process result in python because its easier than jinja2.
        status_warning = _check_sample_status(result)

        # check the latest scan to find the default sample_status for form
        latest_status = DUMMY_SELECT_TEXT
        if result['latest_scan']:
            latest_status = result['latest_scan']['sample_status']

        account = result.get('account')
        events = []
        if account:
            event_status, event_result = APIRequest.get(
                '/api/admin/events/accounts/%s' % account['id'])
            if event_status != 200:
                raise Exception("Couldn't pull event history")

            events = event_result

        return render_template('scan.html',
                               **build_login_variables(),
                               barcode_info=result["barcode_info"],
                               projects_info=result['projects_info'],
                               scans_info=result['scans_info'],
                               latest_status=latest_status,
                               dummy_status=DUMMY_SELECT_TEXT,
                               status_options=STATUS_OPTIONS,
                               send_email=session.get(
                                   SEND_EMAIL_CHECKBOX_DEFAULT_NAME, True),
                               sample_info=result['sample'],
                               extended_info=result,
                               status_warning=status_warning,
                               update_error=update_error,
                               received_type_dropdown=RECEIVED_TYPE_DROPDOWN,
                               source=result['source'],
                               events=events)
    elif status == 401:
        # If we fail due to unauthorized, need the user to log in again
        return redirect('/logout')
    elif status == 404:
        # If we fail due to not found, need to tell the user to pick a diff
        # barcode
        return render_template('scan.html',
                               **build_login_variables(),
                               search_error="Barcode %s Not Found" %
                               sample_barcode,
                               update_error=update_error,
                               received_type_dropdown=RECEIVED_TYPE_DROPDOWN)
    else:
        raise BadRequest()
Ejemplo n.º 13
0
def per_sample_summary():
    # get a list of all projects in the system
    _, result = _get_projects(include_stats=False, is_active=True)
    projects = result.get('projects')

    # filter out any projects that don't belong to Microsetta
    projects = [x for x in projects if x['is_microsetta'] is True]

    # build a list of dictionaries with just the project id and the project
    # name.
    projects = [{
        'project_name': x['project_name'],
        'project_id': x['project_id']
    } for x in projects]

    # determine if user wants sample ids stripped
    strip_sampleid = request.form.get('strip_sampleid', 'off')
    strip_sampleid = strip_sampleid.lower() == 'on'

    if request.method == 'GET':
        # If user arrived via GET then they are either here w/out
        # querying and they simply need the default webpage, or they are
        # querying with either a list of barcodes, or with a project id.

        # look for both parameters to determine which state we are in.
        sample_barcode = request.args.get('sample_barcode')
        project_id = request.args.get('project_id')

        if sample_barcode is None and project_id is None:
            # user just wants the default page.
            return render_template('per_sample_summary.html',
                                   resource=None,
                                   projects=projects,
                                   **build_login_variables())

        if project_id is not None:
            # user wants to get summaries on all samples in a project.
            payload = {'project_id': project_id}
            status, result = APIRequest.post(
                '/api/admin/account_barcode_summa'
                'ry?strip_sampleid=False',
                json=payload)

            if status == 200:
                if result['partial_result'] is True:
                    unprocessed_barcodes = result['unprocessed_barcodes']
                else:
                    unprocessed_barcodes = None

                resource = pd.DataFrame(result['samples'])
                order = [
                    'sampleid', 'project', 'account-email', 'source-email',
                    'source-type', 'site-sampled', 'sample-status',
                    'sample-received', 'ffq-taken', 'ffq-complete',
                    'vioscreen_username'
                ]
                order.extend(sorted(set(resource.columns) - set(order)))
                resource = resource[order]
                if unprocessed_barcodes:
                    return render_template('per_sample_summary.html',
                                           resource=resource,
                                           projects=projects,
                                           error_message="Too many barcodes. S"
                                           "erver processed only"
                                           " the first 1000.",
                                           **build_login_variables())
                else:
                    return render_template('per_sample_summary.html',
                                           resource=resource,
                                           projects=projects,
                                           **build_login_variables())

            else:
                return render_template('per_sample_summary.html',
                                       resource=None,
                                       projects=projects,
                                       error_message=result,
                                       **build_login_variables())

        # if we are here then the user is querying using barcodes and we
        # simply need to set up the query below to perform.
        sample_barcodes = [
            sample_barcode,
        ]
    else:
        # assume POST, since there are only two methods defined in route.
        # if we are here, it is because the user is querying using an uploaded
        # file containing sample names.
        sample_barcodes, err = upload_util.parse_request_csv_col(
            request, 'file', 'sample_name')
        if err is not None:
            # there was an error. abort early.
            return render_template('per_sample_summary.html',
                                   resource=None,
                                   projects=projects,
                                   **build_login_variables(),
                                   search_error=[{
                                       'error': err
                                   }])

    # perform the main query.
    payload = {'sample_barcodes': sample_barcodes}
    status, result = APIRequest.post('/api/admin/account_barcode_summary?stri'
                                     'p_sampleid=%s' % str(strip_sampleid),
                                     json=payload)

    if status == 200:
        if result['partial_result'] is True:
            unprocessed_barcodes = result['unprocessed_barcodes']
        else:
            unprocessed_barcodes = None
        resource = pd.DataFrame(result['samples'])
        order = [
            'sampleid', 'project', 'account-email', 'source-email',
            'source-type', 'site-sampled', 'sample-status', 'sample-received',
            'ffq-taken', 'ffq-complete', 'vioscreen_username'
        ]
        order.extend(sorted(set(resource.columns) - set(order)))
        resource = resource[order]

        if unprocessed_barcodes:
            return render_template('per_sample_summary.html',
                                   resource=resource,
                                   projects=projects,
                                   error_message="Too many barcodes. S"
                                   "erver processed only"
                                   " the first 1000.",
                                   **build_login_variables())
        else:
            return render_template('per_sample_summary.html',
                                   resource=resource,
                                   projects=projects,
                                   **build_login_variables())
    else:
        return render_template('per_sample_summary.html',
                               resource=None,
                               projects=projects,
                               error_message=result,
                               **build_login_variables())
Ejemplo n.º 14
0
def email_stats():
    _, result = _get_projects(include_stats=False, is_active=True)
    projects = result.get('projects')

    if request.method == 'GET':
        project = request.args.get('project', None)
        email = request.args.get('email')
        if email is None:
            # They want to search for emails, show them the search dialog
            return render_template("email_stats_pulldown.html",
                                   **build_login_variables(),
                                   resource=None,
                                   search_error=None,
                                   projects=projects)
        emails = [
            email,
        ]
    elif request.method == 'POST':
        project = request.form.get('project', None)
        emails, upload_err = upload_util.parse_request_csv_col(
            request, 'file', 'email')
        if upload_err is not None:
            return render_template('email_stats_pulldown.html',
                                   **build_login_variables(),
                                   resource=None,
                                   search_error=[{
                                       'error': upload_err
                                   }],
                                   projects=projects)
    else:
        raise BadRequest()

    if project == "":
        project = None

    # de-duplicate
    emails = list({e.lower() for e in emails})

    status, result = APIRequest.post('/api/admin/account_email_summary',
                                     json={
                                         "emails": emails,
                                         "project": project
                                     })

    if status != 200:
        return render_template('email_stats_pulldown.html',
                               search_error=[{
                                   'error': result
                               }],
                               resource=None,
                               **build_login_variables(),
                               projects=projects)

    # At a minimum, our table will display these columns.
    # We may show additional info depending on what comes back from the request
    base_data_template = {
        'email': 'XXX',
        'summary': 'XXX',
        'account_id': 'XXX',
        'creation_time': 'XXX',
        'kit_name': 'XXX',
        'project': 'XXX',
        'unclaimed-samples-in-kit': 0,
        'never-scanned': 0,
        'sample-is-valid': 0,
        'no-associated-source': 0,
        'no-registered-account': 0,
        'no-collection-info': 0,
        'sample-has-inconsistencies': 0,
        'received-unknown-validity': 0
    }

    df = pd.DataFrame([base_data_template] + result)
    df = df.drop(0)  # remove the template row
    numeric_cols = [
        "unclaimed-samples-in-kit", "never-scanned", "sample-is-valid",
        "no-associated-source", "no-registered-account", "no-collection-info",
        "sample-has-inconsistencies", "received-unknown-validity"
    ]
    df[numeric_cols] = df[numeric_cols].apply(pd.to_numeric)
    df[numeric_cols] = df[numeric_cols].fillna(0)

    def urlify_account_id(id_):
        if pd.isnull(id_):
            return "No associated account"
        else:
            ui_endpoint = SERVER_CONFIG['ui_endpoint']
            account_url = f"{ui_endpoint}/accounts/{id_}"
            return f'<a target="_blank" href="{account_url}">{id_}</a>'

    # see https://stackoverflow.com/questions/20035518/insert-a-link-inside-a-pandas-table  # noqa
    df['account_id'] = df["account_id"].apply(urlify_account_id)
    return render_template("email_stats_pulldown.html",
                           search_error=None,
                           resource=df,
                           **build_login_variables(),
                           projects=projects)