refactor: api keys API (#2652)
* feat: create useDeleteApiKey hook to handle api keys delete * refactor: use mutation to handle delete api key * [autofix.ci] apply automated fixes * feat: create useGetApiKeys hook * refactor: use useGetApiKeysQuery hook to get api keys data * [autofix.ci] apply automated fixes * refactor: change interface name * refactor: remove unnused loading state and use react state instead of react ref to ensure component render * fix: multiple refreshs when auto_login=false --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
cffed2c9be
commit
6fa3c7b17d
7 changed files with 140 additions and 107 deletions
|
|
@ -1 +1,3 @@
|
||||||
|
export * from "./use-delete-api-key";
|
||||||
|
export * from "./use-get-api-keys";
|
||||||
export * from "./use-post-add-api-key";
|
export * from "./use-post-add-api-key";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { useMutationFunctionType } from "@/types/api";
|
||||||
|
import { api } from "../../api";
|
||||||
|
import { getURL } from "../../helpers/constants";
|
||||||
|
import { UseRequestProcessor } from "../../services/request-processor";
|
||||||
|
|
||||||
|
interface IDeleteApiKey {
|
||||||
|
keyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add types for error handling and success
|
||||||
|
export const useDeleteApiKey: useMutationFunctionType<IDeleteApiKey> = (
|
||||||
|
options,
|
||||||
|
) => {
|
||||||
|
const { mutate } = UseRequestProcessor();
|
||||||
|
|
||||||
|
const deleteApiKeyFn = async (payload: IDeleteApiKey): Promise<any> => {
|
||||||
|
const res = await api.delete(`${getURL("API_KEY")}/${payload.keyId}`);
|
||||||
|
return res.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mutation = mutate(["useDeleteApiKey"], deleteApiKeyFn, options);
|
||||||
|
|
||||||
|
return mutation;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { useDarkStore } from "@/stores/darkStore";
|
||||||
|
import { useQueryFunctionType } from "@/types/api";
|
||||||
|
import { api } from "../../api";
|
||||||
|
import { getURL } from "../../helpers/constants";
|
||||||
|
import { UseRequestProcessor } from "../../services/request-processor";
|
||||||
|
|
||||||
|
export interface IApiKeysDataArray {
|
||||||
|
name: string;
|
||||||
|
last_used_at: string | null;
|
||||||
|
total_uses: number;
|
||||||
|
is_active: boolean;
|
||||||
|
id: string;
|
||||||
|
api_key: string;
|
||||||
|
user_id: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IApiQueryResponse {
|
||||||
|
total_count: number;
|
||||||
|
user_id: string;
|
||||||
|
api_keys: Array<IApiKeysDataArray>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGetApiKeysQuery: useQueryFunctionType<
|
||||||
|
undefined,
|
||||||
|
IApiQueryResponse
|
||||||
|
> = (_, options) => {
|
||||||
|
const { query } = UseRequestProcessor();
|
||||||
|
|
||||||
|
const getApiKeysFn = async () => {
|
||||||
|
return await api.get<IApiQueryResponse>(`${getURL("API_KEY")}/`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseFn = async () => {
|
||||||
|
const { data } = await getApiKeysFn();
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryResult = query(["useGetApiKeysQuery"], responseFn, { ...options });
|
||||||
|
|
||||||
|
return queryResult;
|
||||||
|
};
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { getApiKey } from "../../../../../controllers/API";
|
|
||||||
import { Users } from "../../../../../types/api";
|
|
||||||
|
|
||||||
const useApiKeys = (
|
|
||||||
userData: Users | null,
|
|
||||||
setLoadingKeys: (load: boolean) => void,
|
|
||||||
keysList: React.MutableRefObject<never[]>,
|
|
||||||
setUserId: (userId: string) => void,
|
|
||||||
) => {
|
|
||||||
const fetchApiKeys = () => {
|
|
||||||
setLoadingKeys(true);
|
|
||||||
getApiKey()
|
|
||||||
.then((keys) => {
|
|
||||||
keysList.current = keys["api_keys"].map((apikey) => ({
|
|
||||||
...apikey,
|
|
||||||
name: apikey.name && apikey.name !== "" ? apikey.name : "Untitled",
|
|
||||||
last_used_at: apikey.last_used_at ?? "Never",
|
|
||||||
}));
|
|
||||||
setUserId(keys["user_id"]);
|
|
||||||
setLoadingKeys(false);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setLoadingKeys(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
fetchApiKeys,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useApiKeys;
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import {
|
|
||||||
DEL_KEY_ERROR_ALERT,
|
|
||||||
DEL_KEY_ERROR_ALERT_PLURAL,
|
|
||||||
DEL_KEY_SUCCESS_ALERT,
|
|
||||||
DEL_KEY_SUCCESS_ALERT_PLURAL,
|
|
||||||
} from "../../../../../constants/alerts_constants";
|
|
||||||
import { deleteApiKey } from "../../../../../controllers/API";
|
|
||||||
|
|
||||||
const useDeleteApiKeys = (
|
|
||||||
selectedRows: string[],
|
|
||||||
resetFilter: () => void,
|
|
||||||
setSuccessData: (data: { title: string }) => void,
|
|
||||||
setErrorData: (data: { title: string; list: string[] }) => void,
|
|
||||||
) => {
|
|
||||||
const handleDeleteKey = () => {
|
|
||||||
Promise.all(selectedRows.map((selectedRow) => deleteApiKey(selectedRow)))
|
|
||||||
.then(() => {
|
|
||||||
resetFilter();
|
|
||||||
setSuccessData({
|
|
||||||
title:
|
|
||||||
selectedRows.length === 1
|
|
||||||
? DEL_KEY_SUCCESS_ALERT
|
|
||||||
: DEL_KEY_SUCCESS_ALERT_PLURAL,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setErrorData({
|
|
||||||
title:
|
|
||||||
selectedRows.length === 1
|
|
||||||
? DEL_KEY_ERROR_ALERT
|
|
||||||
: DEL_KEY_ERROR_ALERT_PLURAL,
|
|
||||||
list: [error?.response?.data?.detail],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleDeleteKey,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useDeleteApiKeys;
|
|
||||||
|
|
@ -1,3 +1,14 @@
|
||||||
|
import {
|
||||||
|
DEL_KEY_ERROR_ALERT,
|
||||||
|
DEL_KEY_ERROR_ALERT_PLURAL,
|
||||||
|
DEL_KEY_SUCCESS_ALERT,
|
||||||
|
DEL_KEY_SUCCESS_ALERT_PLURAL,
|
||||||
|
} from "@/constants/alerts_constants";
|
||||||
|
import {
|
||||||
|
IApiKeysDataArray,
|
||||||
|
useDeleteApiKey,
|
||||||
|
useGetApiKeysQuery,
|
||||||
|
} from "@/controllers/API/queries/api-keys";
|
||||||
import { SelectionChangedEvent } from "ag-grid-community";
|
import { SelectionChangedEvent } from "ag-grid-community";
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import TableComponent from "../../../../components/tableComponent";
|
import TableComponent from "../../../../components/tableComponent";
|
||||||
|
|
@ -5,8 +16,6 @@ import { AuthContext } from "../../../../contexts/authContext";
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import ApiKeyHeaderComponent from "./components/ApiKeyHeader";
|
import ApiKeyHeaderComponent from "./components/ApiKeyHeader";
|
||||||
import { getColumnDefs } from "./helpers/column-defs";
|
import { getColumnDefs } from "./helpers/column-defs";
|
||||||
import useApiKeys from "./hooks/use-api-keys";
|
|
||||||
import useDeleteApiKeys from "./hooks/use-handle-delete-key";
|
|
||||||
|
|
||||||
export default function ApiKeysPage() {
|
export default function ApiKeysPage() {
|
||||||
const [loadingKeys, setLoadingKeys] = useState(true);
|
const [loadingKeys, setLoadingKeys] = useState(true);
|
||||||
|
|
@ -15,29 +24,61 @@ export default function ApiKeysPage() {
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const { userData } = useContext(AuthContext);
|
const { userData } = useContext(AuthContext);
|
||||||
const [userId, setUserId] = useState("");
|
const [userId, setUserId] = useState("");
|
||||||
const keysList = useRef([]);
|
const [keysList, setKeysList] = useState<IApiKeysDataArray[]>([]);
|
||||||
|
const { refetch } = useGetApiKeysQuery();
|
||||||
|
|
||||||
useEffect(() => {
|
async function getApiKeysQuery() {
|
||||||
fetchApiKeys();
|
const { data } = await refetch();
|
||||||
}, [userData]);
|
if (data !== undefined) {
|
||||||
|
const updatedKeysList = data["api_keys"].map((apikey) => ({
|
||||||
const { fetchApiKeys } = useApiKeys(
|
...apikey,
|
||||||
userData,
|
name: apikey.name && apikey.name !== "" ? apikey.name : "Untitled",
|
||||||
setLoadingKeys,
|
last_used_at: apikey.last_used_at ?? "Never",
|
||||||
keysList,
|
}));
|
||||||
setUserId,
|
setKeysList(updatedKeysList);
|
||||||
);
|
setUserId(data["user_id"]);
|
||||||
|
}
|
||||||
function resetFilter() {
|
|
||||||
fetchApiKeys();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { handleDeleteKey } = useDeleteApiKeys(
|
useEffect(() => {
|
||||||
selectedRows,
|
if (userData) {
|
||||||
resetFilter,
|
getApiKeysQuery();
|
||||||
setSuccessData,
|
}
|
||||||
setErrorData,
|
}, [userData]);
|
||||||
);
|
|
||||||
|
function resetFilter() {
|
||||||
|
getApiKeysQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mutate } = useDeleteApiKey();
|
||||||
|
|
||||||
|
function handleDeleteApi() {
|
||||||
|
for (let i = 0; i < selectedRows.length; i++) {
|
||||||
|
mutate(
|
||||||
|
{ keyId: selectedRows[i] },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
resetFilter();
|
||||||
|
setSuccessData({
|
||||||
|
title:
|
||||||
|
selectedRows.length === 1
|
||||||
|
? DEL_KEY_SUCCESS_ALERT
|
||||||
|
: DEL_KEY_SUCCESS_ALERT_PLURAL,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setErrorData({
|
||||||
|
title:
|
||||||
|
selectedRows.length === 1
|
||||||
|
? DEL_KEY_ERROR_ALERT
|
||||||
|
: DEL_KEY_ERROR_ALERT_PLURAL,
|
||||||
|
list: [error?.response?.data?.detail],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const columnDefs = getColumnDefs();
|
const columnDefs = getColumnDefs();
|
||||||
|
|
||||||
|
|
@ -45,14 +86,14 @@ export default function ApiKeysPage() {
|
||||||
<div className="flex h-full w-full flex-col justify-between gap-6">
|
<div className="flex h-full w-full flex-col justify-between gap-6">
|
||||||
<ApiKeyHeaderComponent
|
<ApiKeyHeaderComponent
|
||||||
selectedRows={selectedRows}
|
selectedRows={selectedRows}
|
||||||
fetchApiKeys={fetchApiKeys}
|
fetchApiKeys={getApiKeysQuery}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex h-full w-full flex-col justify-between">
|
<div className="flex h-full w-full flex-col justify-between">
|
||||||
<TableComponent
|
<TableComponent
|
||||||
key={"apiKeys"}
|
key={"apiKeys"}
|
||||||
onDelete={handleDeleteKey}
|
onDelete={handleDeleteApi}
|
||||||
overlayNoRowsTemplate="No data available"
|
overlayNoRowsTemplate="No data available"
|
||||||
onSelectionChanged={(event: SelectionChangedEvent) => {
|
onSelectionChanged={(event: SelectionChangedEvent) => {
|
||||||
setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
|
setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
|
||||||
|
|
@ -61,7 +102,7 @@ export default function ApiKeysPage() {
|
||||||
suppressRowClickSelection={true}
|
suppressRowClickSelection={true}
|
||||||
pagination={true}
|
pagination={true}
|
||||||
columnDefs={columnDefs}
|
columnDefs={columnDefs}
|
||||||
rowData={keysList.current}
|
rowData={keysList}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -110,9 +110,8 @@ test("PromptTemplateComponent", async ({ page }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
value =
|
value =
|
||||||
(await page
|
(await page.locator('//*[@id="textarea_str_edit_prompt1"]').inputValue()) ??
|
||||||
.locator('//*[@id="textarea_str_edit_prompt1"]')
|
"";
|
||||||
.inputValue()) ?? "";
|
|
||||||
|
|
||||||
if (value != "prompt_name_test_123123!@#!@#") {
|
if (value != "prompt_name_test_123123!@#!@#") {
|
||||||
expect(false).toBeTruthy();
|
expect(false).toBeTruthy();
|
||||||
|
|
@ -126,14 +125,14 @@ test("PromptTemplateComponent", async ({ page }) => {
|
||||||
expect(false).toBeTruthy();
|
expect(false).toBeTruthy();
|
||||||
}
|
}
|
||||||
|
|
||||||
await page.getByTestId('textarea_str_edit_prompt1-ExternalLink').click();
|
await page.getByTestId("textarea_str_edit_prompt1-ExternalLink").click();
|
||||||
await page
|
await page
|
||||||
.getByTestId("text-area-modal")
|
.getByTestId("text-area-modal")
|
||||||
.fill("prompt_edit_test_12312312321!@#$");
|
.fill("prompt_edit_test_12312312321!@#$");
|
||||||
|
|
||||||
await page.getByText("Finish Editing", { exact: true }).click();
|
await page.getByText("Finish Editing", { exact: true }).click();
|
||||||
|
|
||||||
await page.getByTestId('textarea_str_edit_prompt-ExternalLink').click();
|
await page.getByTestId("textarea_str_edit_prompt-ExternalLink").click();
|
||||||
await page
|
await page
|
||||||
.getByTestId("text-area-modal")
|
.getByTestId("text-area-modal")
|
||||||
.fill("prompt_edit_test_44444444444!@#$");
|
.fill("prompt_edit_test_44444444444!@#$");
|
||||||
|
|
@ -194,9 +193,8 @@ test("PromptTemplateComponent", async ({ page }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
value =
|
value =
|
||||||
(await page
|
(await page.locator('//*[@id="textarea_str_edit_prompt1"]').inputValue()) ??
|
||||||
.locator('//*[@id="textarea_str_edit_prompt1"]')
|
"";
|
||||||
.inputValue()) ?? "";
|
|
||||||
|
|
||||||
if (value != "prompt_edit_test_12312312321!@#$") {
|
if (value != "prompt_edit_test_12312312321!@#$") {
|
||||||
expect(false).toBeTruthy();
|
expect(false).toBeTruthy();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue