Skip to content

Commit ec13871

Browse files
committed
course prompt syncing mayhem & expanded test
1 parent ea46b30 commit ec13871

File tree

6 files changed

+98
-35
lines changed

6 files changed

+98
-35
lines changed

e2e/prompts.spec.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,45 @@ import { test } from './fixtures'
22
import { expect } from '@playwright/test'
33
import { acceptDisclaimer } from './utils/test-helpers'
44

5-
test.describe('Prompt creation', () => {
6-
test('test', async ({ page }) => {
5+
test.describe('Prompts', () => {
6+
test('Prompt creation, chat link with prompt, and deletion', async ({ page }) => {
77
await page.goto('/courses/test-course/prompts')
88

99
const newPromptName = `testausprompti-${test.info().workerIndex}`
1010

1111
await page.getByRole('textbox', { name: 'Prompt name' }).fill(newPromptName)
1212
await page.getByRole('textbox', { name: 'e.g. You are a helpful' }).fill('sanot aina "testi onnistui"')
1313
await page.getByRole('button', { name: 'Save' }).click()
14+
15+
// Prompt is created and link is visible
1416
await page.getByText(`Link to chat with the prompt '${newPromptName}' active`).click()
17+
18+
// Now in chat view
1519
await acceptDisclaimer(page)
1620
await page.getByRole('button', { name: 'Chat settings' }).click()
1721

22+
// The prompt is active.
1823
expect(page.getByText(newPromptName)).toBeVisible()
24+
25+
// When prompt selector is opened, it is also visible in the list, so 2 times:
26+
await page.locator('#prompt-selector-button').click()
27+
expect(await page.getByText(newPromptName).count()).toBe(2)
28+
29+
// Back to course page, delete the prompt
30+
await page.goto('/courses/test-course/prompts')
31+
32+
page.on('dialog', (dialog) => dialog.accept())
33+
await page.getByTestId(`delete-prompt-${newPromptName}`).click()
34+
35+
// Prompt is not visible anymore
36+
expect(page.getByText(newPromptName)).not.toBeVisible()
37+
38+
// Go to student view from link
39+
await page.getByText('To student view').click()
40+
await page.getByRole('button', { name: 'Chat settings' }).click()
41+
await page.locator('#prompt-selector-button').click()
42+
43+
// Prompt is not visible anymore in student view.
44+
expect(page.getByText(newPromptName)).not.toBeVisible()
1945
})
2046
})

src/client/components/ChatV2/SettingsModal.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useSearchParams } from 'react-router-dom'
1313
import { BlueButton, OutlineButtonBlack } from './general/Buttons'
1414
import { useAnalyticsDispatch } from '../../stores/analytics'
1515
import useLocalStorageState from '../../hooks/useLocalStorageState'
16+
import { isAxiosError } from 'axios'
1617

1718
const useUrlPromptId = () => {
1819
const [searchParams] = useSearchParams()
@@ -72,9 +73,10 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
7273
refetch()
7374
},
7475
})
75-
//local storage prompt prevents the annoying case where the user refreshes the page and the chosen prompt is reset to default, and the textbox displays incorrect prompt information
76-
const [localStoragePrompt, setLocalStoragePrompt] = useLocalStorageState<Prompt | undefined>('prompt', undefined)
77-
const [activePrompt, setActivePrompt] = useState<Prompt | undefined>(localStoragePrompt)
76+
77+
// local storage prompt prevents the annoying case where the user refreshes the page and the chosen prompt is reset to default, and the textbox displays incorrect prompt information
78+
const [activePrompt, setActivePrompt] = useLocalStorageState<Prompt | undefined>('prompt', undefined)
79+
7880
const [myPromptModalOpen, setMyPromptModalOpen] = useState<boolean>(false)
7981
const mandatoryPrompt = course?.prompts.find((p) => p.mandatory)
8082
const urlPrompt = course?.prompts.find((p) => p.id === urlPromptId)
@@ -89,7 +91,29 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
8991
promptName: activePrompt?.name,
9092
},
9193
})
92-
}, [activePrompt?.id, dispatchAnalytics])
94+
}, [activePrompt?.id])
95+
96+
// Time for a quick sync :D --- really this is what you get when using the local storage to hold some data that is also on the server. basically having to build our own sync engine lol.
97+
useEffect(() => {
98+
// Dont sync personal prompts for now...
99+
if (!isPromptEditable && activePrompt) {
100+
const sync = async () => {
101+
try {
102+
const serverActivePrompt = await apiClient.get<Prompt>(`/prompts/${activePrompt?.id}`)
103+
setActivePrompt(serverActivePrompt.data)
104+
} catch (error) {
105+
if (isAxiosError(error)) {
106+
if (error.status === 404) {
107+
setActivePrompt(undefined) // The prompt has been deleted on the server.
108+
}
109+
} else {
110+
console.error('Unexpected error syncing prompt:', error)
111+
}
112+
}
113+
}
114+
sync()
115+
}
116+
}, [])
93117

94118
const resetSettings = () => {
95119
handleChangePrompt(undefined)
@@ -99,14 +123,12 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
99123
const handleChangePrompt = (newPrompt: Prompt | undefined) => {
100124
if (!newPrompt) {
101125
setActivePrompt(undefined)
102-
setLocalStoragePrompt(undefined)
103126
setAssistantInstructions(DEFAULT_ASSISTANT_INSTRUCTIONS)
104127
return
105128
}
106129

107130
setAssistantInstructions(newPrompt.systemMessage)
108131
setActivePrompt(newPrompt)
109-
setLocalStoragePrompt(newPrompt)
110132
}
111133

112134
useEffect(() => {

src/client/components/Courses/Course/Prompt.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const Prompt = ({ prompt, handleDelete, mandatoryPromptId }: { prompt: PromptTyp
9494
)}
9595
</Box>
9696
<Box>
97-
<Button onClick={() => handleDelete(prompt.id)} color="error">
97+
<Button onClick={() => handleDelete(prompt.id)} color="error" data-testId={`delete-prompt-${prompt.name}`}>
9898
{t('common:delete')}
9999
</Button>
100100
<ExpandButton expand={expand} setExpand={setExpand} />

src/client/components/Courses/Course/index.tsx

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const Course = () => {
4848

4949
if (userLoading || !user || !isCourseSuccess) return null
5050

51-
const studentLink = `${window.location.origin}${PUBLIC_URL}/${chatInstance.courseId}`
51+
const studentLink = `${window.location.origin}${PUBLIC_URL}/v2/${chatInstance.courseId}`
5252

5353
const amongResponsibles = chatInstance.responsibilities ? chatInstance.responsibilities.some((r) => r.user.id === user.id) : false
5454

@@ -114,24 +114,23 @@ const Course = () => {
114114
refetchCourse()
115115
}
116116
}
117-
const drawActionComponent = (user: User) => {
118-
const usersResponsibility: Responsebility | undefined = responsibilities.find((r: Responsebility) => {return r.user.id === user.id})
119-
const isResponsible = usersResponsibility != undefined
117+
const drawActionComponent = (user: User) => {
118+
const usersResponsibility: Responsebility | undefined = responsibilities.find((r: Responsebility) => {
119+
return r.user.id === user.id
120+
})
121+
const isResponsible = usersResponsibility != undefined
120122
return (
121123
<>
122-
{!isResponsible ?
123-
<Button onClick={() => handleAddResponsible(user)}>
124-
{t('course:add')}
125-
126-
</Button>
127-
:
128-
<AssignedResponsibilityManagement
129-
handleRemove={() => {
130-
handleRemoveResponsibility(usersResponsibility)
131-
}}
132-
responsibility={usersResponsibility}
133-
/>
134-
}
124+
{!isResponsible ? (
125+
<Button onClick={() => handleAddResponsible(user)}>{t('course:add')}</Button>
126+
) : (
127+
<AssignedResponsibilityManagement
128+
handleRemove={() => {
129+
handleRemoveResponsibility(usersResponsibility)
130+
}}
131+
responsibility={usersResponsibility}
132+
/>
133+
)}
135134
</>
136135
)
137136
}
@@ -274,10 +273,7 @@ const Course = () => {
274273
overflowY: 'scroll',
275274
}}
276275
>
277-
<ActionUserSearch
278-
actionText={t('course:add')}
279-
drawActionComponent={drawActionComponent}
280-
/>
276+
<ActionUserSearch actionText={t('course:add')} drawActionComponent={drawActionComponent} />
281277
</Box>
282278
</Modal>
283279

@@ -307,7 +303,11 @@ const Course = () => {
307303
const AssignedResponsibilityManagement = ({ responsibility, handleRemove }) => {
308304
const { t } = useTranslation()
309305
if (!responsibility.createdByUserId) {
310-
return <Stack direction={'row'} sx={{ marginLeft: 'auto', alignItems: 'center', height: '1rem' }}>sisu</Stack>
306+
return (
307+
<Stack direction={'row'} sx={{ marginLeft: 'auto', alignItems: 'center', height: '1rem' }}>
308+
sisu
309+
</Stack>
310+
)
311311
}
312312
return (
313313
<Stack direction={'row'} sx={{ marginLeft: 'auto', alignItems: 'center', height: '1rem' }}>

src/client/hooks/usePrompts.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import apiClient from '../util/apiClient'
55

66
export const queryKey = ['prompts']
77

8-
const usePrompts = (chatInstanceId: string) => {
8+
const usePrompts = (courseId: string) => {
99
const queryFn = async (): Promise<Prompt[]> => {
10-
const res = await apiClient.get(`/prompts/${chatInstanceId}`)
10+
const res = await apiClient.get(`/prompts/for-course/${courseId}`)
1111

1212
const { data } = res
1313

src/server/routes/prompt.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ promptRouter.get('/my-prompts', async (req, res) => {
2323
return
2424
})
2525

26-
promptRouter.get('/:courseId', async (req, res) => {
26+
promptRouter.get('/for-course/:courseId', async (req, res) => {
2727
const { courseId } = req.params
2828

29+
// Note: we dont have any authorization checks here. Consider?
2930
const chatInstance = await ChatInstance.findOne({
3031
where: {
3132
courseId,
@@ -234,7 +235,6 @@ promptRouter.put('/:id', async (req, res) => {
234235
const updates = PromptUpdateableParams.parse(req.body)
235236
const { systemMessage, name, hidden, mandatory } = updates
236237

237-
console.log(id)
238238
const prompt = await Prompt.findByPk(id)
239239

240240
if (!prompt) {
@@ -258,4 +258,19 @@ promptRouter.put('/:id', async (req, res) => {
258258
res.send(prompt)
259259
})
260260

261+
promptRouter.get('/:id', async (req, res) => {
262+
const { id } = req.params
263+
264+
// Note: we dont have any authorization checks here. Consider?
265+
const prompt = await Prompt.findByPk(id)
266+
267+
if (!prompt) {
268+
// We dont throw error here, since this is expected behaviour when the prompt has been deleted but someone still has it in their local storage.
269+
res.status(404).send('Prompt not found')
270+
return
271+
}
272+
273+
res.send(prompt)
274+
})
275+
261276
export default promptRouter

0 commit comments

Comments
 (0)