Skip to content

Commit 22c47ae

Browse files
authored
feat: add providers page and enable muxing (#253)
* feat: get preferred model by workspace * enable muxing * chore: update openapi * chore: add @tanstack/react-query-devtools * feat: providers endpoint crud * feat: update provider and auth * refactor: move add provider button on top * fix: msw handler * feat: add providers menu item * test: add settings menu and providers items * leftover * feat: add query config on refetch for providers list endpoint * fix: provider id type * fix: spelling * refadctor: providers table * fix: dialog content overflow * refactor: remove modal state
1 parent 3b0900c commit 22c47ae

33 files changed

+909
-94
lines changed

src/App.test.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ describe("App", () => {
88
it("should render header", async () => {
99
render(<App />);
1010
expect(screen.getByText(/toggle sidebar/i)).toBeVisible();
11-
expect(screen.getByText("Certificates")).toBeVisible();
11+
expect(screen.getByText("Settings")).toBeVisible();
1212
expect(screen.getByText("Help")).toBeVisible();
1313
expect(screen.getByRole("banner", { name: "App header" })).toBeVisible();
1414
expect(screen.getByRole("heading", { name: /codeGate/i })).toBeVisible();
1515

16-
await userEvent.click(screen.getByText("Certificates"));
17-
16+
await userEvent.click(screen.getByText("Settings"));
17+
expect(
18+
screen.getByRole("menuitem", {
19+
name: /providers/i,
20+
}),
21+
).toBeVisible();
1822
expect(
1923
screen.getByRole("menuitem", {
2024
name: /certificate security/i,
@@ -26,7 +30,7 @@ describe("App", () => {
2630
}),
2731
).toBeVisible();
2832

29-
await userEvent.click(screen.getByText("Certificates"));
33+
await userEvent.click(screen.getByText("Settings"));
3034
await userEvent.click(screen.getByText("Help"));
3135

3236
expect(

src/Page.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { RouteDashboard } from "./routes/route-dashboard";
88
import { RouteCertificateSecurity } from "./routes/route-certificate-security";
99
import { RouteWorkspaceCreation } from "./routes/route-workspace-creation";
1010
import { RouteNotFound } from "./routes/route-not-found";
11+
import { RouteProvider } from "./routes/route-providers";
12+
import { RouteProviderCreate } from "./routes/route-provider-create";
13+
import { RouteProviderUpdate } from "./routes/route-provider-update";
1114

1215
export default function Page() {
1316
return (
@@ -22,6 +25,15 @@ export default function Page() {
2225
path="/certificates/security"
2326
element={<RouteCertificateSecurity />}
2427
/>
28+
29+
<Route path="providers">
30+
<Route index element={<RouteProvider />} />
31+
<Route element={<RouteProvider />}>
32+
<Route path=":id" element={<RouteProviderUpdate />} />
33+
<Route path="new" element={<RouteProviderCreate />} />
34+
</Route>
35+
</Route>
36+
2537
<Route path="*" element={<RouteNotFound />} />
2638
</Routes>
2739
);

src/api/generated/types.gen.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ export type ActiveWorkspace = {
1010
last_updated: unknown;
1111
};
1212

13+
/**
14+
* Represents a request to add a provider endpoint.
15+
*/
16+
export type AddProviderEndpointRequest = {
17+
id?: string | null;
18+
name: string;
19+
description?: string;
20+
provider_type: ProviderType;
21+
endpoint?: string;
22+
auth_type?: ProviderAuthType | null;
23+
api_key?: string | null;
24+
};
25+
1326
/**
1427
* Represents an alert with it's respective conversation.
1528
*/
@@ -100,7 +113,6 @@ export type ModelByProvider = {
100113
* Represents the different types of matchers we support.
101114
*/
102115
export enum MuxMatcherType {
103-
FILE_REGEX = "file_regex",
104116
CATCH_ALL = "catch_all",
105117
}
106118

@@ -133,7 +145,7 @@ export type ProviderEndpoint = {
133145
name: string;
134146
description?: string;
135147
provider_type: ProviderType;
136-
endpoint: string;
148+
endpoint?: string;
137149
auth_type?: ProviderAuthType | null;
138150
};
139151

@@ -219,7 +231,7 @@ export type V1ListProviderEndpointsResponse = Array<ProviderEndpoint>;
219231
export type V1ListProviderEndpointsError = HTTPValidationError;
220232

221233
export type V1AddProviderEndpointData = {
222-
body: ProviderEndpoint;
234+
body: AddProviderEndpointRequest;
223235
};
224236

225237
export type V1AddProviderEndpointResponse = ProviderEndpoint;

src/api/openapi.json

+66-5
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
"content": {
9393
"application/json": {
9494
"schema": {
95-
"$ref": "#/components/schemas/ProviderEndpoint"
95+
"$ref": "#/components/schemas/AddProviderEndpointRequest"
9696
}
9797
}
9898
}
@@ -1113,6 +1113,68 @@
11131113
],
11141114
"title": "ActiveWorkspace"
11151115
},
1116+
"AddProviderEndpointRequest": {
1117+
"properties": {
1118+
"id": {
1119+
"anyOf": [
1120+
{
1121+
"type": "string"
1122+
},
1123+
{
1124+
"type": "null"
1125+
}
1126+
],
1127+
"title": "Id",
1128+
"default": ""
1129+
},
1130+
"name": {
1131+
"type": "string",
1132+
"title": "Name"
1133+
},
1134+
"description": {
1135+
"type": "string",
1136+
"title": "Description",
1137+
"default": ""
1138+
},
1139+
"provider_type": {
1140+
"$ref": "#/components/schemas/ProviderType"
1141+
},
1142+
"endpoint": {
1143+
"type": "string",
1144+
"title": "Endpoint",
1145+
"default": ""
1146+
},
1147+
"auth_type": {
1148+
"anyOf": [
1149+
{
1150+
"$ref": "#/components/schemas/ProviderAuthType"
1151+
},
1152+
{
1153+
"type": "null"
1154+
}
1155+
],
1156+
"default": "none"
1157+
},
1158+
"api_key": {
1159+
"anyOf": [
1160+
{
1161+
"type": "string"
1162+
},
1163+
{
1164+
"type": "null"
1165+
}
1166+
],
1167+
"title": "Api Key"
1168+
}
1169+
},
1170+
"type": "object",
1171+
"required": [
1172+
"name",
1173+
"provider_type"
1174+
],
1175+
"title": "AddProviderEndpointRequest",
1176+
"description": "Represents a request to add a provider endpoint."
1177+
},
11161178
"AlertConversation": {
11171179
"properties": {
11181180
"conversation": {
@@ -1437,7 +1499,6 @@
14371499
"MuxMatcherType": {
14381500
"type": "string",
14391501
"enum": [
1440-
"file_regex",
14411502
"catch_all"
14421503
],
14431504
"title": "MuxMatcherType",
@@ -1515,7 +1576,8 @@
15151576
},
15161577
"endpoint": {
15171578
"type": "string",
1518-
"title": "Endpoint"
1579+
"title": "Endpoint",
1580+
"default": ""
15191581
},
15201582
"auth_type": {
15211583
"anyOf": [
@@ -1532,8 +1594,7 @@
15321594
"type": "object",
15331595
"required": [
15341596
"name",
1535-
"provider_type",
1536-
"endpoint"
1597+
"provider_type"
15371598
],
15381599
"title": "ProviderEndpoint",
15391600
"description": "Represents a provider's endpoint configuration. This\nallows us to persist the configuration for each provider,\nso we can use this for muxing messages."

src/features/header/components/header.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { SidebarTrigger } from "../../../components/ui/sidebar";
33
import { DropdownMenu } from "../../../components/HoverPopover";
44
import { Separator, ButtonDarkMode } from "@stacklok/ui-kit";
55
import { WorkspacesSelection } from "@/features/workspace/components/workspaces-selection";
6-
import { CERTIFICATE_MENU_ITEMS } from "../constants/certificate-menu-items";
76
import { HELP_MENU_ITEMS } from "../constants/help-menu-items";
87
import { HeaderStatusMenu } from "./header-status-menu";
8+
import { SETTINGS_MENU_ITEMS } from "../constants/settings-menu-items";
99

1010
function HomeLink() {
1111
return (
@@ -39,8 +39,8 @@ export function Header({ hasError }: { hasError?: boolean }) {
3939
</div>
4040
<div className="flex items-center gap-1 mr-2">
4141
<HeaderStatusMenu />
42-
<DropdownMenu title="Certificates" items={CERTIFICATE_MENU_ITEMS} />
4342
<DropdownMenu title="Help" items={HELP_MENU_ITEMS} />
43+
<DropdownMenu title="Settings" items={SETTINGS_MENU_ITEMS} />
4444

4545
<ButtonDarkMode />
4646
</div>

src/features/header/constants/certificate-menu-items.tsx

-17
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { OptionsSchema } from "@stacklok/ui-kit";
2+
import {
3+
Download01,
4+
LayersThree01,
5+
ShieldTick,
6+
} from "@untitled-ui/icons-react";
7+
8+
export const SETTINGS_MENU_ITEMS = [
9+
{
10+
textValue: "Providers",
11+
id: "providers",
12+
items: [
13+
{
14+
icon: <LayersThree01 />,
15+
id: "providers",
16+
href: "/providers",
17+
textValue: "Providers",
18+
},
19+
],
20+
},
21+
{
22+
textValue: "Certificates",
23+
id: "certificates",
24+
items: [
25+
{
26+
icon: <ShieldTick />,
27+
id: "about-certificate-security",
28+
href: "/certificates/security",
29+
textValue: "About certificate security",
30+
},
31+
{
32+
icon: <Download01 />,
33+
id: "download-certificates",
34+
href: "/certificates",
35+
textValue: "Download certificates",
36+
},
37+
],
38+
},
39+
] as const satisfies OptionsSchema<"menu">[];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Button, DialogFooter } from "@stacklok/ui-kit";
2+
import { useNavigate } from "react-router-dom";
3+
4+
export function ProviderDialogFooter() {
5+
const navigate = useNavigate();
6+
7+
return (
8+
<DialogFooter className="justify-end">
9+
<Button variant="secondary" onPress={() => navigate("/providers")}>
10+
Discard
11+
</Button>
12+
<Button type="submit" variant="primary">
13+
Save
14+
</Button>
15+
</DialogFooter>
16+
);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
DialogModalOverlay,
3+
DialogModal,
4+
Dialog,
5+
DialogHeader,
6+
DialogTitle,
7+
DialogCloseButton,
8+
} from "@stacklok/ui-kit";
9+
import { useNavigate } from "react-router-dom";
10+
11+
export function ProviderDialog({
12+
title,
13+
children,
14+
}: {
15+
title: string;
16+
children: React.ReactNode;
17+
}) {
18+
const navigate = useNavigate();
19+
20+
return (
21+
<DialogModalOverlay
22+
isDismissable={false}
23+
isOpen
24+
onOpenChange={() => {
25+
navigate("/providers");
26+
}}
27+
>
28+
<DialogModal>
29+
<Dialog width="md" className="">
30+
<DialogHeader>
31+
<DialogTitle>{title}</DialogTitle>
32+
<DialogCloseButton slot="close" />
33+
</DialogHeader>
34+
{children}
35+
</Dialog>
36+
</DialogModal>
37+
</DialogModalOverlay>
38+
);
39+
}

0 commit comments

Comments
 (0)