Skip to content

Commit 844f74c

Browse files
fix: validate name for saved searches (#964)
1 parent 7a14dea commit 844f74c

File tree

4 files changed

+70
-6
lines changed

4 files changed

+70
-6
lines changed

.changeset/purple-keys-fry.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@hyperdx/api": patch
3+
"@hyperdx/app": patch
4+
---
5+
6+
fix: validate name for saved searches

packages/api/src/routers/api/__tests__/savedSearch.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ describe('savedSearch router', () => {
4040
expect(savedSearch.body.source).toBe(MOCK_SAVED_SEARCH.source);
4141
});
4242

43+
it('cannot create a saved search with empty name', async () => {
44+
const { agent } = await getLoggedInAgent(server);
45+
await agent
46+
.post('/saved-search')
47+
.send({ ...MOCK_SAVED_SEARCH, name: ' ' }) // Trimmed string will be empty and invalid
48+
.expect(400);
49+
});
50+
4351
it('can update a saved search', async () => {
4452
const { agent } = await getLoggedInAgent(server);
4553
const savedSearch = await agent
@@ -53,6 +61,31 @@ describe('savedSearch router', () => {
5361
expect(updatedSavedSearch.body.name).toBe('warning');
5462
});
5563

64+
it('cannot update a saved search with empty name', async () => {
65+
const { agent } = await getLoggedInAgent(server);
66+
const savedSearch = await agent
67+
.post('/saved-search')
68+
.send(MOCK_SAVED_SEARCH)
69+
.expect(200);
70+
await agent
71+
.patch(`/saved-search/${savedSearch.body._id}`)
72+
.send({ name: ' ' }) // Trimmed string will be empty and invalid
73+
.expect(400);
74+
});
75+
76+
it('can update a saved search with undefined name', async () => {
77+
const { agent } = await getLoggedInAgent(server);
78+
const savedSearch = await agent
79+
.post('/saved-search')
80+
.send(MOCK_SAVED_SEARCH)
81+
.expect(200);
82+
const updatedSavedSearch = await agent
83+
.patch(`/saved-search/${savedSearch.body._id}`)
84+
.send({ name: undefined, select: 'SELECT 1' }) // Name is optional
85+
.expect(200);
86+
expect(updatedSavedSearch.body.select).toBe('SELECT 1');
87+
});
88+
5689
it('can get saved searches', async () => {
5790
const { agent } = await getLoggedInAgent(server);
5891
await agent.post('/saved-search').send(MOCK_SAVED_SEARCH).expect(200);

packages/api/src/routers/api/savedSearch.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ router.get('/', async (req, res, next) => {
3131
router.post(
3232
'/',
3333
validateRequest({
34-
body: SavedSearchSchema.omit({ id: true }),
34+
body: SavedSearchSchema.omit({ id: true }).extend({
35+
name: z.string().trim().min(1),
36+
}),
3537
}),
3638
async (req, res, next) => {
3739
try {
@@ -49,7 +51,9 @@ router.post(
4951
router.patch(
5052
'/:id',
5153
validateRequest({
52-
body: SavedSearchSchema.partial(),
54+
body: SavedSearchSchema.partial().extend({
55+
name: z.string().trim().min(1).optional(),
56+
}),
5357
params: z.object({
5458
id: objectIdSchema,
5559
}),

packages/app/src/DBSearchPage.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,12 @@ function SaveSearchModal({
182182
},
183183
);
184184

185-
const { control, handleSubmit } = useForm({
185+
const {
186+
control,
187+
handleSubmit,
188+
formState,
189+
reset: resetForm,
190+
} = useForm({
186191
...(isUpdate
187192
? {
188193
values: {
@@ -196,6 +201,13 @@ function SaveSearchModal({
196201
},
197202
});
198203

204+
const closeAndReset = () => {
205+
resetForm();
206+
onClose();
207+
};
208+
209+
const isValidName = (name?: string): boolean =>
210+
Boolean(name && name.trim().length > 0);
199211
const [tags, setTags] = useState<string[]>(savedSearch?.tags || []);
200212

201213
// Update tags when savedSearch changes
@@ -260,7 +272,7 @@ function SaveSearchModal({
260272
return (
261273
<Modal
262274
opened={opened}
263-
onClose={onClose}
275+
onClose={closeAndReset}
264276
title="Save Search"
265277
centered
266278
size="lg"
@@ -309,7 +321,11 @@ function SaveSearchModal({
309321
<Text c="gray.4" size="xs" mb="xs">
310322
Name
311323
</Text>
312-
<InputControlled control={control} name="name" />
324+
<InputControlled
325+
control={control}
326+
name="name"
327+
rules={{ required: true, validate: isValidName }}
328+
/>
313329
</Box>
314330
<Box mb="sm">
315331
<Text c="gray.4" size="xs" mb="xs">
@@ -347,7 +363,12 @@ function SaveSearchModal({
347363
</Tags>
348364
</Group>
349365
</Box>
350-
<Button variant="outline" color="green" type="submit">
366+
<Button
367+
variant="outline"
368+
color="green"
369+
type="submit"
370+
disabled={!formState.isValid}
371+
>
351372
{isUpdate ? 'Update' : 'Save'}
352373
</Button>
353374
</Stack>

0 commit comments

Comments
 (0)