Skip to content

Commit e752192

Browse files
authored
Merge pull request #41 from kristo-baricevic/feedback-form2
feat: FeedbackForm POST request to Jira backend
2 parents 9d383e3 + 39c998b commit e752192

11 files changed

Lines changed: 514 additions & 53 deletions

File tree

backend/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ in order to launch a development container with interactive shell.
2424
### Teardown project
2525
1. Run
2626
```make teardown-project```
27-
in order to tear down any exisiting development containers.
27+
in order to tear down any existing development containers.

backend/balancer/controllers/chatgpt.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@
1010
# XXX: remove csrf_exempt usage before production
1111
from django.views.decorators.csrf import csrf_exempt
1212

13+
@csrf_exempt
14+
def chatgpt(request: str) -> JsonResponse:
15+
"""
16+
Takes a diagnosis and returns a table of the most commonly prescribed medications for that diagnosis.
17+
"""
18+
openai.api_key = os.environ.get("OPENAI_API_KEY")
19+
data: dict[str, str] = json.loads(request.body)
20+
21+
if data is not None:
22+
diagnosis: str = data["prompt"]
23+
ai_response = openai.ChatCompletion.create(
24+
model="gpt-4",
25+
messages= [
26+
{"role": "system", "content": f"Balancer is a powerful tool for selecting bipolar medication for patients. We are open-source and available for free use. Converstation: {diagnosis}."}
27+
]
28+
)
29+
return JsonResponse({"message": ai_response})
30+
31+
return JsonResponse({"error": "Failed to retrieve results from ChatGPT."})
32+
1333

1434
@csrf_exempt
1535
def extract_text(request: str) -> JsonResponse:
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
from django.http import JsonResponse
2+
from django import forms
3+
import requests
4+
import json
5+
import os
6+
7+
# XXX: remove csrf_exempt usage before production
8+
from django.views.decorators.csrf import csrf_exempt
9+
10+
11+
@csrf_exempt
12+
def create_new_feedback(request: str) -> JsonResponse:
13+
"""
14+
Create a new feedback request in Jira Service Desk.
15+
"""
16+
token: str = os.environ.get("JIRA_API_KEY")
17+
18+
data: dict[str, str] = json.loads(request.body)
19+
name: str = data["name"]
20+
email: str = data["email"]
21+
message: str = data["message"]
22+
23+
url: str = "https://balancer.atlassian.net/rest/servicedeskapi/request"
24+
25+
headers: dict[str, str] = {
26+
"Accept": "application/json",
27+
"Content-Type": "application/json",
28+
"Authorization": f"Basic {token}",
29+
}
30+
31+
payload: str = json.dumps(
32+
{
33+
"requestFieldValues": {
34+
"summary": f"{name} - Feedback",
35+
"customfield_10061": email,
36+
"description": message,
37+
},
38+
"requestTypeId": "33",
39+
"serviceDeskId": "2",
40+
}
41+
)
42+
43+
response: requests.Response = requests.request(
44+
"POST", url, data=payload, headers=headers
45+
)
46+
match response.status_code:
47+
case 201:
48+
response_body: dict[str, str] = json.loads(response.text)
49+
issue_key: str = response_body["issueKey"]
50+
return JsonResponse(
51+
{"status": 201, "message": "Feedback submitted", "issueKey": issue_key}
52+
)
53+
case 400:
54+
return JsonResponse({"status": 400, "message": "Invalid request"})
55+
case 401 | 403:
56+
return JsonResponse({"status": 401, "message": "Unauthorized request"})
57+
case _:
58+
return JsonResponse({"status": 500, "message": "Internal server error"})
59+
60+
61+
class UploadAttachmentForm(forms.Form):
62+
issueKey: forms.CharField = forms.CharField(max_length=50)
63+
attachment = forms.FileField()
64+
65+
66+
@csrf_exempt
67+
def upload_servicedesk_attachment(request: str) -> JsonResponse:
68+
"""
69+
Upload file to temporary files in Jira Service Desk.
70+
"""
71+
token: str = os.environ.get("JIRA_API_KEY")
72+
form: UploadAttachmentForm = UploadAttachmentForm(request.POST, request.FILES)
73+
if form.is_valid():
74+
url: str = f"https://balancer.atlassian.net/rest/servicedeskapi/servicedesk/2/attachTemporaryFile"
75+
76+
headers: dict[str, str] = {
77+
"Accept": "application/json",
78+
"X-Atlassian-Token": "no-check",
79+
"Authorization": f"Basic {token}",
80+
}
81+
82+
response: requests.Response = requests.request(
83+
"POST", url, files={"file": request.FILES["attachment"]}, headers=headers
84+
)
85+
match response.status_code:
86+
case 201:
87+
response_body: dict[str, str] = json.loads(response.text)
88+
temp_attachment_id: str = response_body["temporaryAttachments"][0][
89+
"temporaryAttachmentId"
90+
]
91+
issue_key: str = request.POST.get("issueKey")
92+
return JsonResponse(
93+
{
94+
"status": 200,
95+
"message": "Attachment uploaded to temporary files",
96+
"tempAttachmentId": temp_attachment_id,
97+
"issueKey": issue_key,
98+
}
99+
)
100+
case 400:
101+
return JsonResponse({"status": 400, "message": "Invalid request"})
102+
case 401 | 403:
103+
return JsonResponse({"status": 401, "message": "Unauthorized request"})
104+
case _:
105+
return JsonResponse({"status": 500, "message": "Internal server error"})
106+
return JsonResponse({"status": 400, "message": "Invalid form object"})
107+
108+
109+
@csrf_exempt
110+
def attach_feedback_attachment(request: str) -> JsonResponse:
111+
"""
112+
Attach a temporary file to a Jira Service Desk issue.
113+
"""
114+
token: str = os.environ.get("JIRA_API_KEY")
115+
data: dict[str, str] = json.loads(request.body)
116+
issue_key: str = data["issueKey"]
117+
temp_attachment_id: str = data["tempAttachmentId"]
118+
print(issue_key)
119+
120+
url: str = f"https://balancer.atlassian.net/rest/servicedeskapi/request/{issue_key}/attachment"
121+
122+
headers = {
123+
"Accept": "application/json",
124+
"Content-Type": "application/json",
125+
"Authorization": f"Basic {token}",
126+
}
127+
128+
payload: str = json.dumps(
129+
{"public": True, "temporaryAttachmentIds": [temp_attachment_id]}
130+
)
131+
132+
response: requests.Response = requests.request(
133+
"POST", url, data=payload, headers=headers
134+
)
135+
match response.status_code:
136+
case 201:
137+
return JsonResponse({"status": 201, "message": f"File attached to issue {issue_key}"})
138+
case 400:
139+
return JsonResponse({"status": 400, "message": "Invalid request"})
140+
case 401 | 403:
141+
return JsonResponse({"status": 401, "message": "Unauthorized request"})
142+
case _:
143+
return JsonResponse({"status": 500, "message": "Internal server error"})
144+
145+
146+
# These functions are used to get Jira data, but shouldn't be enabled in production.
147+
# Keep these commented out unless in use.
148+
149+
# @csrf_exempt
150+
# def get_jira_servicedesk_list(request: str) -> JsonResponse:
151+
# """Get jira service desk list."""
152+
# url = "https://balancer.atlassian.net/rest/servicedeskapi/servicedesk"
153+
# headers = {
154+
# "Accept": "application/json",
155+
# "Authorization": "Basic ",
156+
# }
157+
158+
# response = requests.request("GET", url, headers=headers)
159+
# print(
160+
# json.dumps(
161+
# json.loads(response.text), sort_keys=True, indent=4, separators=(",", ": ")
162+
# )
163+
# )
164+
# return JsonResponse({"message": "complete"})
165+
166+
167+
# @csrf_exempt
168+
# def get_jira_request_types(request: str) -> JsonResponse:
169+
# url = "https://balancer.atlassian.net/rest/servicedeskapi/servicedesk/2/requesttype"
170+
171+
# headers = {"Accept": "application/json", "Authorization": "Basic "}
172+
173+
# response = requests.request("GET", url, headers=headers)
174+
175+
# print(
176+
# json.dumps(
177+
# json.loads(response.text), sort_keys=True, indent=4, separators=(",", ": ")
178+
# )
179+
# )
180+
# return JsonResponse({"message": "complete"})
181+
182+
# @csrf_exempt
183+
# def get_required_request_type_fields(request: str) -> JsonResponse:
184+
# url = "https://balancer.atlassian.net/rest/servicedeskapi/servicedesk/2/requesttype/33/field"
185+
186+
# headers = {
187+
# "Accept": "application/json",
188+
# "Content-Type": "application/json",
189+
# "Authorization": "Basic "
190+
# }
191+
192+
# response = requests.request("GET", url, headers=headers)
193+
# print(json.dumps(json.loads(response.text), sort_keys=True, indent=4, separators=(",", ": ")))
194+
# return JsonResponse({"message": "complete"})
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from django.http import JsonResponse
2+
import os
3+
import openai
4+
import json
5+
6+
# XXX: remove csrf_exempt usage before production
7+
from django.views.decorators.csrf import csrf_exempt
8+
9+
10+
@csrf_exempt
11+
def medication(request):
12+
openai.api_key = os.environ.get("OPENAI_API_KEY")
13+
data = json.loads(request.body)
14+
15+
if data is not None:
16+
diagnosis = data["diagnosis"]
17+
else:
18+
return JsonResponse(
19+
{"error": "Diagnosis not found. Request must include diagnosis."}
20+
)
21+
22+
ai_response = openai.ChatCompletion.create(
23+
model="gpt-3.5-turbo",
24+
messages=[
25+
{
26+
"role": "system",
27+
"content": f"Please provide the most commonly prescribed medications for {diagnosis}. I want only the drug names seperated by a comma and noting else"
28+
}
29+
],
30+
)
31+
32+
drug_list = ai_response["choices"][0]["message"]["content"].split(",")
33+
# Return the JSON response with drugs list
34+
return JsonResponse({"drugs": drug_list})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from django.http import JsonResponse
2+
import os
3+
import openai
4+
import json
5+
6+
# XXX: remove csrf_exempt usage before production
7+
from django.views.decorators.csrf import csrf_exempt
8+
9+
@csrf_exempt
10+
def medication(request):
11+
openai.api_key = os.environ.get("OPENAI_API_KEY")
12+
data = json.loads(request.body)
13+
14+
if data is not None:
15+
diagnosis = data["diagnosis"]
16+
else:
17+
return JsonResponse({"error": "Diagnosis not found. Request must include diagnosis."})
18+
19+
ai_response = openai.ChatCompletion.create(
20+
model = "gpt-3.5-turbo",
21+
messages = [
22+
{
23+
"role": "system",
24+
"content": f"You are to provide a concise list of 5 key benefits and 5 key risks for the medication suggested when taking it for Bipolar. Each point should be short, clear and be kept under 10 words. Begin the benefits section with !!!benefits!!! and the risks section with !!!risk!!!. Please provide this information for the medication: {diagnosis}."
25+
}
26+
]
27+
)
28+
29+
content = ai_response['choices'][0]['message']['content']
30+
31+
if '!!!benefits!!!' not in content or '!!!risks!!!' not in content:
32+
return JsonResponse({"error": "Unexpected format in the response content."})
33+
34+
# Split the content into benefits and risks sections
35+
benefits_selection = content.split('!!!risks!!!')[0].replace('!!!benefits!!!', '').strip()
36+
risks_selection = content.split('!!!risks!!!')[1].strip()
37+
38+
# Split the sections into individiual points
39+
# Taking every second item as the benefits and risks are on alternate lines
40+
benefits = benefits_selection.split('\n')
41+
risks = risks_selection.split('\n')
42+
content = content
43+
44+
return JsonResponse({
45+
'benefits': benefits,
46+
'risks': risks
47+
})

backend/balancer/settings.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"django.contrib.sessions",
4242
"django.contrib.messages",
4343
"django.contrib.staticfiles",
44+
"corsheaders",
4445
]
4546

4647
MIDDLEWARE = [
@@ -51,6 +52,7 @@
5152
"django.contrib.auth.middleware.AuthenticationMiddleware",
5253
"django.contrib.messages.middleware.MessageMiddleware",
5354
"django.middleware.clickjacking.XFrameOptionsMiddleware",
55+
"corsheaders.middleware.CorsMiddleware",
5456
]
5557

5658
ROOT_URLCONF = "balancer.urls"
@@ -125,3 +127,18 @@
125127
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
126128

127129
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
130+
131+
CORS_ALLOW_ALL_ORIGINS = False
132+
133+
CORS_ALLOWED_ORIGINS = [
134+
"http://localhost:3000",
135+
]
136+
137+
CORS_ALLOW_METHODS = [
138+
"GET",
139+
"POST",
140+
"PUT",
141+
"PATCH",
142+
"DELETE",
143+
"OPTIONS",
144+
]

backend/balancer/urls.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@
1616
"""
1717
from django.contrib import admin
1818
from django.urls import path
19-
from balancer.controllers import chatgpt
19+
from balancer.controllers import chatgpt, jira, listDrugs, risk
2020

2121
urlpatterns = [
2222
path("admin/", admin.site.urls),
23-
path("extract_text/", chatgpt.extract_text, name="post_web_text"),
24-
path("diagnosis/", chatgpt.diagnosis, name="post_diagnosis"),
23+
path("api/chatgpt/extract_text/", chatgpt.extract_text, name="post_web_text"),
24+
path("api/chatgpt/diagnosis/", chatgpt.diagnosis, name="post_diagnosis"),
25+
path("api/chatgpt/chat", chatgpt.chatgpt, name="chatgpt"),
26+
path("api/chatgpt/list_drugs", listDrugs.medication, name="listDrugs"),
27+
path("api/chatgpt/risk", risk.medication, name="risk"),
28+
path("api/jira/create_new_feedback/", jira.create_new_feedback, name="create_new_feedback"),
29+
path("api/jira/upload_servicedesk_attachment/", jira.upload_servicedesk_attachment, name="upload_servicedesk_attachment"),
30+
path("api/jira/attach_feedback_attachment/", jira.attach_feedback_attachment, name="attach_feedback_attachment"),
2531
]
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
OPENAI_API_KEY=
1+
OPENAI_API_KEY=
2+
JIRA_API_TOKEN=
35.2 KB
Loading

frontend/src/pages/Feedback/Feedback.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import FeedbackForm from "./FeedbackForm.tsx";
2-
import Welcome from "../../components/Welcome/Welcome.tsx";
32
import Layout from "../Layout/Layout";
43

54
function Feedback() {
65
return (
76
<Layout>
87
<div className="mt-20 flex w-full max-w-6xl flex-col items-center justify-center md:mt-28">
9-
<Welcome
10-
subHeader="Feedback"
11-
descriptionText="Leave feedback for the Balancer Team."
12-
/>
13-
<FeedbackForm />
8+
<div className="mt-10">
9+
<h1 className="head_text"></h1>
10+
<h2 className="desc">Feedback</h2>
11+
<p className="mx-auto mt-5 hidden
12+
max-w-[100%] text-center font-satoshi text-log text-gray-400 sm:text-x; md:block">
13+
Leave Feedback for the Balancer Team.
14+
</p>
15+
<FeedbackForm />
16+
</div>
1417
</div>
1518
</Layout>
1619
);

0 commit comments

Comments
 (0)