Skip to content

Commit 40f86a5

Browse files
authored
feat: extend new revert logic to "custom instructions" (#304)
* remove jsonforms * extract <FormButtons /> * better naming * reuse form hook for custom instructions * add pending state for form buttons * make workspace name uneditable for the default workspace * add sidenote when workspace cannot be renamed
1 parent 00bf204 commit 40f86a5

28 files changed

+288
-639
lines changed

eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,5 +238,5 @@ export default tseslint.config(
238238
},
239239
],
240240
},
241-
}
241+
},
242242
);

package-lock.json

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
"@radix-ui/react-dialog": "^1.1.4",
2929
"@radix-ui/react-separator": "^1.1.0",
3030
"@radix-ui/react-slot": "^1.1.0",
31-
"@sinclair/typebox": "^0.34.16",
3231
"@stacklok/ui-kit": "^1.0.1-1",
3332
"@tanstack/react-query": "^5.64.1",
3433
"@tanstack/react-query-devtools": "^5.66.0",

src/components/FormButtons.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { FormState } from "@/hooks/useFormState";
2+
import { Button } from "@stacklok/ui-kit";
3+
import { FlipBackward } from "@untitled-ui/icons-react";
4+
5+
type Props<T> = {
6+
canSubmit: boolean;
7+
formErrorMessage?: string;
8+
formSideNote?: string;
9+
formState: FormState<T>;
10+
children?: React.ReactNode;
11+
isPending: boolean;
12+
};
13+
export function FormButtons<T>({
14+
formErrorMessage,
15+
formState,
16+
canSubmit,
17+
isPending,
18+
children,
19+
formSideNote,
20+
}: Props<T>) {
21+
return (
22+
<div className="flex gap-2 items-center">
23+
{formSideNote && <div className="p-1 text-secondary">{formSideNote}</div>}
24+
{formErrorMessage && (
25+
<div className="p-1 text-red-700">{formErrorMessage}</div>
26+
)}
27+
{formState.isDirty && (
28+
<Button variant="tertiary" onPress={formState.resetForm}>
29+
<FlipBackward />
30+
Revert changes
31+
</Button>
32+
)}
33+
{children}
34+
<Button
35+
isPending={isPending}
36+
isDisabled={!canSubmit || !formState.isDirty || isPending}
37+
type="submit"
38+
>
39+
Save
40+
</Button>
41+
</div>
42+
);
43+
}

src/features/dashboard-messages/components/__tests__/table-messages.alerts.test.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ it("shows zero in alerts counts when no alerts", async () => {
1414
mockConversation({
1515
alertsConfig: { numAlerts: 0 },
1616
}),
17-
])
18-
)
17+
]),
18+
),
1919
);
2020
render(<TableMessages />);
2121

@@ -26,12 +26,12 @@ it("shows zero in alerts counts when no alerts", async () => {
2626
expect(
2727
screen.getByRole("button", {
2828
name: /malicious packages count/i,
29-
})
29+
}),
3030
).toHaveTextContent("0");
3131
expect(
3232
screen.getByRole("button", {
3333
name: /secrets count/i,
34-
})
34+
}),
3535
).toHaveTextContent("0");
3636
});
3737

@@ -42,8 +42,8 @@ it("shows count of malicious alerts in row", async () => {
4242
mockConversation({
4343
alertsConfig: { numAlerts: 10, type: "malicious" },
4444
}),
45-
])
46-
)
45+
]),
46+
),
4747
);
4848
render(<TableMessages />);
4949

@@ -54,7 +54,7 @@ it("shows count of malicious alerts in row", async () => {
5454
expect(
5555
screen.getByRole("button", {
5656
name: /malicious packages count/i,
57-
})
57+
}),
5858
).toHaveTextContent("10");
5959
});
6060

@@ -65,8 +65,8 @@ it("shows count of secret alerts in row", async () => {
6565
mockConversation({
6666
alertsConfig: { numAlerts: 10, type: "secret" },
6767
}),
68-
])
69-
)
68+
]),
69+
),
7070
);
7171
render(<TableMessages />);
7272

@@ -77,6 +77,6 @@ it("shows count of secret alerts in row", async () => {
7777
expect(
7878
screen.getByRole("button", {
7979
name: /secrets count/i,
80-
})
80+
}),
8181
).toHaveTextContent("10");
8282
});

src/features/dashboard-messages/components/__tests__/table-messages.pagination.test.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ it("only displays a limited number of items in the table", async () => {
1212
server.use(
1313
http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => {
1414
return HttpResponse.json(
15-
Array.from({ length: 30 }).map(() => mockConversation())
15+
Array.from({ length: 30 }).map(() => mockConversation()),
1616
);
17-
})
17+
}),
1818
);
1919

2020
render(<TableMessages />);
2121

2222
await waitFor(() => {
2323
expect(
24-
within(screen.getByTestId("messages-table")).getAllByRole("row")
24+
within(screen.getByTestId("messages-table")).getAllByRole("row"),
2525
).toHaveLength(16);
2626
});
2727
});
@@ -30,9 +30,9 @@ it("allows pagination", async () => {
3030
server.use(
3131
http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => {
3232
return HttpResponse.json(
33-
Array.from({ length: 35 }).map(() => mockConversation())
33+
Array.from({ length: 35 }).map(() => mockConversation()),
3434
);
35-
})
35+
}),
3636
);
3737

3838
render(<TableMessages />);
@@ -42,10 +42,10 @@ it("allows pagination", async () => {
4242
await userEvent.click(screen.getByRole("button", { name: /next/i }));
4343

4444
expect(
45-
within(screen.getByTestId("messages-table")).getAllByRole("row").length
45+
within(screen.getByTestId("messages-table")).getAllByRole("row").length,
4646
).toBeLessThan(16);
4747
},
48-
{ timeout: 5000 }
48+
{ timeout: 5000 },
4949
);
5050

5151
// on the last page, we cannot go further
@@ -63,7 +63,7 @@ it("allows pagination", async () => {
6363
expect(screen.getByRole("button", { name: /next/i })).toBeEnabled();
6464

6565
expect(
66-
within(screen.getByTestId("messages-table")).getAllByRole("row").length
66+
within(screen.getByTestId("messages-table")).getAllByRole("row").length,
6767
).toEqual(16);
6868
});
6969
});

src/features/dashboard-messages/components/__tests__/table-messages.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ it.each(TABLE_MESSAGES_COLUMNS)("contains $children header", async (column) => {
1212
expect(
1313
screen.getByRole("columnheader", {
1414
name: column.children as string,
15-
})
15+
}),
1616
).toBeVisible();
1717
});
1818
});

src/features/dashboard-messages/components/__tests__/tabs-messages.test.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,24 @@ test("shows correct count of all packages", async () => {
1515
type: "secret",
1616
numAlerts: 1,
1717
},
18-
})
18+
}),
1919
),
2020
...Array.from({ length: 13 }).map(() =>
2121
mockConversation({
2222
alertsConfig: {
2323
type: "malicious",
2424
numAlerts: 1,
2525
},
26-
})
26+
}),
2727
),
2828
]);
29-
})
29+
}),
3030
);
3131

3232
const { getByRole } = render(
3333
<TabsMessages>
3434
<div>foo</div>
35-
</TabsMessages>
35+
</TabsMessages>,
3636
);
3737

3838
await waitFor(() => {
@@ -50,16 +50,16 @@ test("shows correct count of malicious packages", async () => {
5050
type: "malicious",
5151
numAlerts: 1,
5252
},
53-
})
54-
)
53+
}),
54+
),
5555
);
56-
})
56+
}),
5757
);
5858

5959
const { getByRole } = render(
6060
<TabsMessages>
6161
<div>foo</div>
62-
</TabsMessages>
62+
</TabsMessages>,
6363
);
6464

6565
await waitFor(() => {
@@ -77,16 +77,16 @@ test("shows correct count of secret packages", async () => {
7777
type: "secret",
7878
numAlerts: 1,
7979
},
80-
})
81-
)
80+
}),
81+
),
8282
);
83-
})
83+
}),
8484
);
8585

8686
const { getByRole } = render(
8787
<TabsMessages>
8888
<div>foo</div>
89-
</TabsMessages>
89+
</TabsMessages>,
9090
);
9191

9292
await waitFor(() => {

src/features/dashboard-messages/lib/filter-messages-by-substring.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Conversation } from "@/api/generated";
22

33
export function filterMessagesBySubstring(
44
conversation: Conversation,
5-
substring: string | null
5+
substring: string | null,
66
): boolean {
77
if (conversation == null) return false;
88
if (substring === null) return true;
@@ -14,10 +14,10 @@ export function filterMessagesBySubstring(
1414
if (curr.answer) acc.push(curr.answer.message);
1515
return acc;
1616
},
17-
[] as string[]
17+
[] as string[],
1818
);
1919

2020
return [...messages].some((i) =>
21-
i?.toLowerCase().includes(substring.toLowerCase())
21+
i?.toLowerCase().includes(substring.toLowerCase()),
2222
);
2323
}

0 commit comments

Comments
 (0)