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:
Igor Carvalho 2024-07-29 16:04:58 -03:00 committed by GitHub
commit 6fa3c7b17d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 140 additions and 107 deletions

View file

@ -1 +1,3 @@
export * from "./use-delete-api-key";
export * from "./use-get-api-keys";
export * from "./use-post-add-api-key";

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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;

View file

@ -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;

View file

@ -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 { useContext, useEffect, useRef, useState } from "react";
import TableComponent from "../../../../components/tableComponent";
@ -5,8 +16,6 @@ import { AuthContext } from "../../../../contexts/authContext";
import useAlertStore from "../../../../stores/alertStore";
import ApiKeyHeaderComponent from "./components/ApiKeyHeader";
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() {
const [loadingKeys, setLoadingKeys] = useState(true);
@ -15,29 +24,61 @@ export default function ApiKeysPage() {
const setErrorData = useAlertStore((state) => state.setErrorData);
const { userData } = useContext(AuthContext);
const [userId, setUserId] = useState("");
const keysList = useRef([]);
const [keysList, setKeysList] = useState<IApiKeysDataArray[]>([]);
const { refetch } = useGetApiKeysQuery();
useEffect(() => {
fetchApiKeys();
}, [userData]);
const { fetchApiKeys } = useApiKeys(
userData,
setLoadingKeys,
keysList,
setUserId,
);
function resetFilter() {
fetchApiKeys();
async function getApiKeysQuery() {
const { data } = await refetch();
if (data !== undefined) {
const updatedKeysList = data["api_keys"].map((apikey) => ({
...apikey,
name: apikey.name && apikey.name !== "" ? apikey.name : "Untitled",
last_used_at: apikey.last_used_at ?? "Never",
}));
setKeysList(updatedKeysList);
setUserId(data["user_id"]);
}
}
const { handleDeleteKey } = useDeleteApiKeys(
selectedRows,
resetFilter,
setSuccessData,
setErrorData,
);
useEffect(() => {
if (userData) {
getApiKeysQuery();
}
}, [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();
@ -45,14 +86,14 @@ export default function ApiKeysPage() {
<div className="flex h-full w-full flex-col justify-between gap-6">
<ApiKeyHeaderComponent
selectedRows={selectedRows}
fetchApiKeys={fetchApiKeys}
fetchApiKeys={getApiKeysQuery}
userId={userId}
/>
<div className="flex h-full w-full flex-col justify-between">
<TableComponent
key={"apiKeys"}
onDelete={handleDeleteKey}
onDelete={handleDeleteApi}
overlayNoRowsTemplate="No data available"
onSelectionChanged={(event: SelectionChangedEvent) => {
setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
@ -61,7 +102,7 @@ export default function ApiKeysPage() {
suppressRowClickSelection={true}
pagination={true}
columnDefs={columnDefs}
rowData={keysList.current}
rowData={keysList}
/>
</div>
</div>

View file

@ -110,9 +110,8 @@ test("PromptTemplateComponent", async ({ page }) => {
}
value =
(await page
.locator('//*[@id="textarea_str_edit_prompt1"]')
.inputValue()) ?? "";
(await page.locator('//*[@id="textarea_str_edit_prompt1"]').inputValue()) ??
"";
if (value != "prompt_name_test_123123!@#!@#") {
expect(false).toBeTruthy();
@ -126,14 +125,14 @@ test("PromptTemplateComponent", async ({ page }) => {
expect(false).toBeTruthy();
}
await page.getByTestId('textarea_str_edit_prompt1-ExternalLink').click();
await page.getByTestId("textarea_str_edit_prompt1-ExternalLink").click();
await page
.getByTestId("text-area-modal")
.fill("prompt_edit_test_12312312321!@#$");
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
.getByTestId("text-area-modal")
.fill("prompt_edit_test_44444444444!@#$");
@ -194,9 +193,8 @@ test("PromptTemplateComponent", async ({ page }) => {
}
value =
(await page
.locator('//*[@id="textarea_str_edit_prompt1"]')
.inputValue()) ?? "";
(await page.locator('//*[@id="textarea_str_edit_prompt1"]').inputValue()) ??
"";
if (value != "prompt_edit_test_12312312321!@#$") {
expect(false).toBeTruthy();