Skip to content

Commit

Permalink
Some more refactors
Browse files Browse the repository at this point in the history
Since this is the refactor PR
  • Loading branch information
BryceStevenWilley committed Jul 21, 2022
1 parent 568b214 commit 0fb5f96
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
modules:
- .github_issue
- .feedback_on_server
---
objects:
- red: DARedis
Expand All @@ -24,7 +24,7 @@ subquestion: |
${ action_button_html(url_action('open_session'), label='Open Session', color='secondary') }
---
metadata:
title: Browse Feedback Sessions
title: Browse Interview Feedback
short title: Browse Feedback
temporary session: True
required privileges:
Expand All @@ -33,7 +33,7 @@ metadata:
if: ids
id: main browse screen
question: |
Select a feedback session to browse
Select feedback to browse
fields:
- Feedback Session: feedback_id
datatype: dropdown
Expand All @@ -58,7 +58,7 @@ content: |
---
if: not ids
question: |
No feedback sessions to view
No feedback to view
event: feedback_id
help:
label: |
Expand All @@ -67,10 +67,11 @@ help:
${ view_panelists }
---
code: |
id_map = get_all_session_info()
id_map = get_all_feedback_info()
---
code: |
ids = {row_id: info['html_url'] if info.get('html_url') else info.get('session_id') for row_id, info in id_map.items()}
ids = {row_id: info['html_url'] if info.get('html_url') else info.get('session_id')
for row_id, info in id_map.items() if info.get('session_id')}
---
event: open_session
code: |
Expand Down
8 changes: 4 additions & 4 deletions docassemble/GithubFeedbackForm/data/questions/feedback.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
modules:
- .github_issue
- .feedback_on_server
---
include:
- docassemble.ALToolbox:collapse_template.yml
---
---
metadata:
title: User Feedback
exit url: |
Expand All @@ -15,7 +15,7 @@ features:
labels above fields: True
question back button: True
---
code: al_feedback_form_title = "Court Forms Online"
code: al_feedback_form_title = "Court Forms Online"
---
code: |
# This email will be used ONLY if there is no valid GitHub config
Expand Down Expand Up @@ -295,7 +295,7 @@ code: |
if github_user in allowed_github_users:
issue_url
if actually_share_answers and issue_url and saved_uuid:
set_session_github_url(saved_uuid, issue_url)
set_feedback_github_url(saved_uuid, issue_url)
if not issue_url and al_error_email:
log(f"Unable to create issue on repo {github_repo}, falling back to emailing {al_error_email}")
send_email(to=al_error_email, subject=f"{github_repo} - {issue_template.subject_as_html(trim=True)}", template=issue_template)
Expand All @@ -309,7 +309,7 @@ code: |
send_to_github = True
---
code: |
saved_uuid = save_session_info(interview=filename, session_id=orig_session_id, template=issue_template)
saved_uuid = save_feedback_info(interview=filename, session_id=orig_session_id, template=issue_template)
---
code: |
issue_url = make_github_issue(github_user, github_repo, template=issue_template, label=al_github_label)
Expand Down
88 changes: 88 additions & 0 deletions docassemble/GithubFeedbackForm/feedback_on_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import importlib

from typing import Optional, Iterable, Tuple
from datetime import datetime
from sqlalchemy import insert, update, select, Text, DateTime, Table, Column,\
String, Integer, MetaData, create_engine, func
from docassemble.base.util import DARedis, log
from docassemble.base.sql import alchemy_url

__all__ = ['save_feedback_info', 'set_feedback_github_url', 'redis_panel_emails_key', 'add_panel_participant',
'potential_panelists', 'get_all_feedback_info']

redis_panel_emails_key = 'docassemble-GithubFeedbackForm:panel_emails'

############################################
## Panel particitpants are managed through a Redis ZSet (a sorted/scored set).
## The score is the timestamp of when they responded, so we can fetch newer
## respondents (more likely to accept a follow up interview).
## https://redis.io/docs/manual/data-types/#sorted-sets

def add_panel_participant(email:str):
"""Adds this email to a list of potential participants for a qualitative research panel.
Adds the email and when they responded, as we shouldn't link their feedback to their identity at all.
"""
red = DARedis()
red.zadd(redis_panel_emails_key, {email: datetime.now().timestamp()})

def potential_panelists() -> Iterable[Tuple[str, datetime]]:
red = DARedis()
return [(item, datetime.fromtimestamp(score)) for item, score in red.zrange(redis_panel_emails_key, 0, -1, withscores=True)]

###################################
## Using SQLAlchemy to save / retrieve session information that is linked
## to specific feedback issues

db_url = alchemy_url('db')
engine = create_engine(db_url)

metadata_obj = MetaData()

## This table functions as both storage for bug report-session links,
## and for more general on-server feedback, prompted for at the end of interviews
feedback_session_table = Table(
"feedback_session",
metadata_obj,
Column('id', Integer, primary_key=True),
Column('interview', String),
Column('session_id', String),
Column('body', Text),
Column('html_url', String)
)

metadata_obj.create_all(engine)

def save_feedback_info(interview, *, session_id=None, template=None, body=None) -> Optional[str]:
"""Saves feedback along with optional session information in a SQL DB"""
if template:
body = template.content

if interview and (session_id or body):
stmt = insert(feedback_session_table).values(interview=interview, session_id=session_id, body=body)
with engine.begin() as conn:
result = conn.execute(stmt)
id_for_feedback = result.inserted_primary_key[0]

return id_for_feedback
else: # can happen if the forwarding interview didn't pass session info
return None

def set_feedback_github_url(id_for_feedback:str, github_url:str) -> bool:
"""Returns true if save was successful"""
stmt = update(feedback_session_table).where(feedback_session_table.c.id == id_for_feedback).values(html_url=github_url)
with engine.begin() as conn:
result = conn.execute(stmt)
if result.rowcount == 0:
log(f'Cannot find {id_for_feedback} in redis DB')
return False
return True

def get_all_feedback_info(interview=None) -> Iterable:
if interview:
stmt = select(feedback_session_table).where(feedback_session_table.c.interview == interview)
else:
stmt = select(feedback_session_table)
with engine.begin() as conn:
results = conn.execute(stmt)
# Turn into literal dict because DA is too eager to save / load SQLAlchemy objects into the interview SQL
return {str(row['id']): dict(row) for row in results.mappings()}
89 changes: 4 additions & 85 deletions docassemble/GithubFeedbackForm/github_issue.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
import importlib
import json
import requests
import uuid
from typing import Optional, Iterable, Tuple
from datetime import datetime
from sqlalchemy import insert, update, select, text, Table, Column, String, Integer, MetaData, create_engine
from docassemble.base.util import log, get_config, interview_url, DARedis
from docassemble.base.sql import alchemy_url
from typing import Optional
from docassemble.base.util import log, get_config, interview_url

# reference: https://gist.github.com/JeffPaine/3145490
# https://docs.github.com/en/free-pro-team@latest/rest/reference/issues#create-an-issue

# Authentication for user filing issue (must have read/write access to
# repository to add issue to)
__all__ = ['valid_github_issue_config', 'make_github_issue', 'save_session_info',
'set_session_github_url', 'feedback_link', 'redis_feedback_key', 'add_panel_participant',
'potential_panelists', 'get_all_session_info']
__all__ = ['valid_github_issue_config', 'make_github_issue',
'feedback_link']
USERNAME = get_config('github issues',{}).get('username')
TOKEN = get_config('github issues',{}).get('token')

redis_feedback_key = 'docassemble-GithubFeedbackForm:feedback_info'
redis_panel_emails_key = 'docassemble-GithubFeedbackForm:panel_emails'

def valid_github_issue_config():
return bool(TOKEN)

Expand Down Expand Up @@ -91,79 +83,6 @@ def feedback_link(user_info_object=None,
session_id=_session_id,
local=False,reset=1)

############################################
## Panel particitpants are managed through a Redis ZSet (a sorted/scored set).
## The score is the timestamp of when they responded, so we can fetch newer
## respondents (more likely to accept a follow up interview).
## https://redis.io/docs/manual/data-types/#sorted-sets

def add_panel_participant(email:str):
"""Adds this email to a list of potential participants for a qualitative research panel.
Adds the email and when they responded, as we shouldn't link their feedback to their identity at all.
"""
red = DARedis()
red.zadd(redis_panel_emails_key, {email: datetime.now().timestamp()})

def potential_panelists() -> Iterable[Tuple[str, datetime]]:
red = DARedis()
return [(item, datetime.fromtimestamp(score)) for item, score in red.zrange(redis_panel_emails_key, 0, -1, withscores=True)]

###################################
## Using SQLAlchemy to save / retrieve session information that is linked
## to specific feedback issues

db_url = alchemy_url('db')
engine = create_engine(db_url)

metadata_obj = MetaData()

feedback_session_table = Table(
"feedback_session",
metadata_obj,
Column('id', Integer, primary_key=True),
Column('interview', String, nullable=False),
Column('session_id', String, nullable=False),
Column('body', String),
Column('html_url', String)
)

metadata_obj.create_all(engine)

def save_session_info(interview=None, session_id=None, template=None, body=None) -> Optional[str]:
"""Saves session information along with the feedback in a redis DB"""
if template:
body = template.content

if interview and session_id:
stmt = insert(feedback_session_table).values(interview=interview, session_id=session_id, body=body)
with engine.begin() as conn:
result = conn.execute(stmt)
id_for_session = result.inserted_primary_key[0]

return id_for_session
else: # can happen if the forwarding interview didn't pass session info
return None

def set_session_github_url(id_for_session:str, github_url:str) -> bool:
"""Returns true if save was successful"""
stmt = update(feedback_session_table).where(feedback_session_table.c.id == id_for_session).values(html_url=github_url)
with engine.begin() as conn:
result = conn.execute(stmt)
if result.rowcount == 0:
log(f'Cannot find {id_for_session} in redis DB')
return False
return True

def get_all_session_info(interview=None) -> Iterable:
if interview:
stmt = select(feedback_session_table).where(feedback_session_table.c.interview == interview)
else:
stmt = select(feedback_session_table)
with engine.begin() as conn:
results = conn.execute(stmt)
# Turn into literal dict because DA is too eager to save / load SQLAlchemy objects into the interview SQL
return {str(row['id']): dict(row) for row in results.mappings()}

def make_github_issue(repo_owner, repo_name, template=None, title=None, body=None, label=None) -> Optional[str]:
"""
Create a new Github issue and return the URL.
Expand Down

0 comments on commit 0fb5f96

Please sign in to comment.