Skip to content

Commit e17301f

Browse files
feat: add tooltip to secrets (#673)
Co-authored-by: Cahllagerfeld <[email protected]>
1 parent 2f7584d commit e17301f

File tree

15 files changed

+276
-209
lines changed

15 files changed

+276
-209
lines changed

src/app/settings/secrets/AddSecretDialog.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,16 @@ export function AddSecret({
166166
/>
167167
)}
168168
/>
169-
<div
169+
<button
170+
type="button"
170171
onClick={() => {
171172
const showPassword = watch(`keysValues.${index}.showPassword`);
172173
setValue(`keysValues.${index}.showPassword`, !showPassword);
173174
}}
174-
className="absolute inset-y-1 right-0 flex cursor-pointer items-center pb-1 pr-3"
175+
className="absolute inset-y-1 right-0 flex items-center pb-1 pr-3"
175176
>
176-
<EyeIcon className="h-4 w-4 flex-shrink-0 cursor-pointer" />
177-
</div>
177+
<EyeIcon className="h-4 w-4 flex-shrink-0" />
178+
</button>
178179
</div>
179180
</div>
180181
<div className="flex items-center">

src/app/settings/secrets/EditSecretDialog.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,13 @@ export function EditSecret({ secretId, isSecretNameEditable }: EditSecretProps)
200200
/>
201201
)}
202202
/>
203-
<div
203+
<button
204+
type="button"
204205
onClick={() => togglePasswordVisibility(index)}
205-
className="absolute inset-y-1 right-0 flex cursor-pointer items-center pb-1 pr-3"
206+
className="absolute inset-y-1 right-0 flex items-center pb-1 pr-3"
206207
>
207-
<EyeIcon className="h-4 w-4 flex-shrink-0 cursor-pointer" />
208-
</div>
208+
<EyeIcon className="h-4 w-4 flex-shrink-0" />
209+
</button>
209210
</div>
210211
</div>
211212
<div className="flex items-center">
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {
2+
Tooltip,
3+
TooltipContent,
4+
TooltipProvider,
5+
TooltipTrigger
6+
} from "@zenml-io/react-component-library";
7+
import { Codesnippet } from "@/components/CodeSnippet";
8+
import Info from "@/assets/icons/info.svg?react";
9+
10+
export function SecretTooltip({ code }: { code: string }) {
11+
return (
12+
<TooltipProvider>
13+
<Tooltip>
14+
<TooltipTrigger>
15+
<Info className="h-4 w-4 shrink-0 fill-theme-text-tertiary" />
16+
</TooltipTrigger>
17+
<TooltipContent className="z-50 flex max-w-[480px] flex-col gap-2 bg-theme-surface-primary p-5 text-text-sm text-theme-text-primary">
18+
<p className="text-md text-theme-text-primary">
19+
To use your secret in a step, you can use the following code:
20+
</p>
21+
<Codesnippet highlightCode wrap codeClasses="break-words" code={code} />
22+
<a
23+
className="link w-fit text-primary-400"
24+
target="_blank"
25+
href="https://docs.zenml.io/how-to/interact-with-secrets#accessing-registered-secrets"
26+
>
27+
Learn More
28+
</a>
29+
</TooltipContent>
30+
</Tooltip>
31+
</TooltipProvider>
32+
);
33+
}

src/app/settings/secrets/SecretsTable.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ import { secretQueries } from "@/data/secrets";
44
import { useCurrentUser } from "@/data/users/current-user-query";
55
import { useQuery } from "@tanstack/react-query";
66
import { DataTable, Skeleton } from "@zenml-io/react-component-library";
7-
import { useNavigate } from "react-router-dom";
87
import { workspaceQueries } from "../../../data/workspaces";
98
import { AddSecretDialog } from "./AddSecretDialog";
10-
import { getSecretColumns } from "./columns";
9+
import { secretsColumns } from "./columns";
1110
import { useSecretOverviewSearchParams } from "./service";
1211

1312
export default function SecretsTable() {
14-
const navigate = useNavigate();
1513
const queryParams = useSecretOverviewSearchParams();
1614
const { data: secretsData } = useQuery({
1715
...secretQueries.secretList({ ...queryParams, sort_by: "desc:created" }),
@@ -44,7 +42,7 @@ export default function SecretsTable() {
4442
<div className="flex flex-col items-center gap-5">
4543
<div className="w-full">
4644
{secretsData ? (
47-
<DataTable columns={getSecretColumns(navigate)} data={secretsData.items} />
45+
<DataTable columns={secretsColumns} data={secretsData.items} />
4846
) : (
4947
<Skeleton className="h-[250px] w-full" />
5048
)}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { secretQueries } from "@/data/secrets";
2+
import { useQuery } from "@tanstack/react-query";
3+
import {
4+
Button,
5+
DataTable,
6+
Dialog,
7+
DialogTrigger,
8+
Input,
9+
Skeleton
10+
} from "@zenml-io/react-component-library";
11+
import { useState } from "react";
12+
import { EditSecretDialog } from "../EditSecretDialog";
13+
import { getSecretDetailColumn } from "./columns";
14+
15+
export default function SecretDetailTable({ secretId }: { secretId: string }) {
16+
const [searchTerm, setSearchTerm] = useState("");
17+
18+
const secretDetail = useQuery({ ...secretQueries.secretDetail(secretId) });
19+
20+
if (secretDetail.isPending) return <Skeleton className="h-[200px] w-full" />;
21+
if (secretDetail.isError) return <div>{secretDetail.error.message}</div>;
22+
23+
const keyValues = Object.entries(
24+
(secretDetail.data.body?.values as Record<string, string>) || {}
25+
).map(([key, value]) => ({
26+
key,
27+
value
28+
}));
29+
30+
const filteredData = keyValues.filter((item) =>
31+
item.key.toLowerCase().includes(searchTerm.toLowerCase())
32+
);
33+
34+
return (
35+
<>
36+
<div className="mb-4 flex flex-wrap items-center justify-between gap-2">
37+
<Input
38+
type="text"
39+
placeholder="Search Keys..."
40+
value={searchTerm}
41+
onChange={(e) => setSearchTerm(e.target.value)}
42+
inputSize="md"
43+
/>
44+
<Dialog>
45+
<DialogTrigger asChild>
46+
<Button size="sm" intent="primary">
47+
Edit Keys
48+
</Button>
49+
</DialogTrigger>
50+
<EditSecretDialog
51+
secretId={secretId}
52+
isSecretNameEditable={false}
53+
dialogTitle="Edit keys"
54+
/>
55+
</Dialog>
56+
</div>
57+
<div className="w-full">
58+
<DataTable
59+
columns={getSecretDetailColumn(secretId, secretDetail.data.name)}
60+
data={filteredData}
61+
/>
62+
</div>
63+
</>
64+
);
65+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import EyeIcon from "@/assets/icons/eye.svg?react";
2+
import KeyIcon from "@/assets/icons/key-icon.svg?react";
3+
import { ColumnDef } from "@tanstack/react-table";
4+
import { useState } from "react"; // Import useState for local state
5+
import { SecretTooltip } from "../SecretTooltip";
6+
import SecretTableDropDown from "./SecretTableDropDown";
7+
8+
const ValueCell: React.FC<{ value: unknown }> = ({ value }) => {
9+
const [isVisible, setIsVisible] = useState(false);
10+
const valueStr = typeof value === "string" ? value : "";
11+
const dots = "•".repeat(valueStr.length);
12+
13+
return (
14+
<div className="flex items-center gap-2 space-x-2">
15+
<EyeIcon onClick={() => setIsVisible(!isVisible)} className="h-4 w-4 flex-shrink-0" />
16+
<span>{isVisible ? valueStr : dots}</span>
17+
</div>
18+
);
19+
};
20+
21+
export function getSecretDetailColumn(
22+
secretId: string,
23+
name: string
24+
): ColumnDef<{ key: string; value: string }>[] {
25+
return [
26+
{
27+
id: "key",
28+
header: "Key",
29+
accessorKey: "key",
30+
cell: ({ row }) => {
31+
const code = `from zenml.client import Client
32+
secret = Client().get_secret(${name})
33+
34+
# 'secret.secret_values' will contain a dictionary with all key-value pairs within your secret.
35+
secret.secret_values["${row.original.key}"]
36+
`;
37+
return (
38+
<div className="flex items-center space-x-2">
39+
<KeyIcon className="h-5 w-5 flex-shrink-0 fill-primary-400" />
40+
<div className="flex flex-col">
41+
<div className="flex items-center space-x-1">
42+
<div className="flex items-center space-x-1">
43+
<span className="text-text-md font-semibold text-theme-text-primary">
44+
{row.original.key}
45+
</span>
46+
<SecretTooltip code={code} />
47+
</div>
48+
</div>
49+
</div>
50+
</div>
51+
);
52+
}
53+
},
54+
{
55+
id: "value",
56+
header: "Value",
57+
accessorKey: "value",
58+
cell: ({ row }) => <ValueCell value={row.getValue("value")} />
59+
},
60+
{
61+
id: "actions",
62+
header: "",
63+
cell: ({ row }) => <SecretTableDropDown secretId={secretId} keyName={row.original.key} />
64+
}
65+
];
66+
}

src/app/settings/secrets/secretsDetail/page.tsx renamed to src/app/settings/secrets/[id]/page.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { useQuery } from "@tanstack/react-query";
33
import { Box } from "@zenml-io/react-component-library";
44
import { useEffect } from "react";
55
import { useParams } from "react-router-dom";
6+
import { CopyButton } from "../../../../components/CopyButton";
67
import { useBreadcrumbsContext } from "../../../../layouts/AuthenticatedLayout/BreadcrumbsContext";
78
import SecretDetailTable from "./SecretDetailTable";
89

910
export default function SecretDetailsPage() {
10-
const { secretId } = useParams<{ secretId: string }>();
11+
const { secretId } = useParams() as { secretId: string };
1112
const { setCurrentBreadcrumbData } = useBreadcrumbsContext();
1213
const { data: secretDetail } = useQuery({ ...secretQueries.secretDetail(secretId || "") });
1314

@@ -21,9 +22,14 @@ export default function SecretDetailsPage() {
2122

2223
return (
2324
<>
24-
<Box className="space-y-4 p-5">
25-
<h1 className="text-text-xl font-semibold">{secretDetail?.name}</h1>
26-
<span className="text-sm text-gray-500">{secretId?.slice(0, 8)}</span>
25+
<Box className="space-y-5 p-5">
26+
<div>
27+
<h1 className="text-text-xl font-semibold">{secretDetail?.name}</h1>
28+
<div className="group/copybutton flex items-center space-x-1">
29+
<div className="text-theme-text-secondary">{secretId?.slice(0, 8)}</div>
30+
<CopyButton copyText={secretId} />
31+
</div>
32+
</div>
2733
<SecretDetailTable secretId={secretId || ""} />
2834
</Box>
2935
</>

0 commit comments

Comments
 (0)