link backfill and prune

This commit is contained in:
Kaushik Narayan R 2025-01-08 02:04:50 -07:00
parent c9938aca35
commit 4baedc3e60
4 changed files with 123 additions and 17 deletions

View File

@ -1,9 +1,11 @@
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { apiRespBaseType, axiosInstance } from "./axiosInstance"; import { apiRespBaseType, axiosInstance } from "./axiosInstance";
import { import {
opBackfillLinkURL,
opCreateLinkURL, opCreateLinkURL,
opDeleteLinkURL, opDeleteLinkURL,
opFetchGraphURL, opFetchGraphURL,
opPruneLinkURL,
opUpdateUserDataURL, opUpdateUserDataURL,
} from "./paths"; } from "./paths";
@ -27,8 +29,26 @@ type createLinkBodyType = {
to: string; // playlistID to: string; // playlistID
}; };
interface createLinkDataType extends apiRespBaseType {}
type deleteLinkBodyType = createLinkBodyType; type deleteLinkBodyType = createLinkBodyType;
interface deleteLinkDataType extends apiRespBaseType {}
type backfillLinkBodyType = createLinkBodyType;
interface backfillLinkDataType extends apiRespBaseType {
added?: number;
local?: number;
}
type pruneLinkBodyType = createLinkBodyType;
interface pruneLinkDataType extends apiRespBaseType {
added?: number;
local?: number;
}
export const apiFetchGraph = async (): Promise< export const apiFetchGraph = async (): Promise<
AxiosResponse<fetchGraphDataType, any> AxiosResponse<fetchGraphDataType, any>
> => { > => {
@ -53,7 +73,7 @@ export const apiUpdateUserData = async (): Promise<
export const apiCreateLink = async ( export const apiCreateLink = async (
data: createLinkBodyType data: createLinkBodyType
): Promise<AxiosResponse<apiRespBaseType, any>> => { ): Promise<AxiosResponse<createLinkDataType, any>> => {
try { try {
const response = await axiosInstance.post(opCreateLinkURL, data); const response = await axiosInstance.post(opCreateLinkURL, data);
return response; return response;
@ -64,9 +84,31 @@ export const apiCreateLink = async (
export const apiDeleteLink = async ( export const apiDeleteLink = async (
data: deleteLinkBodyType data: deleteLinkBodyType
): Promise<AxiosResponse<apiRespBaseType, any>> => { ): Promise<AxiosResponse<deleteLinkDataType, any>> => {
try { try {
const response = await axiosInstance.delete(opDeleteLinkURL, { data }); const response = await axiosInstance.delete(opDeleteLinkURL, { data }); // axios delete method doesn't take body
return response;
} catch (error: any) {
return error.response;
}
};
export const apiBackfillLink = async (
data: backfillLinkBodyType
): Promise<AxiosResponse<backfillLinkDataType, any>> => {
try {
const response = await axiosInstance.put(opBackfillLinkURL, data);
return response;
} catch (error: any) {
return error.response;
}
};
export const apiPruneLink = async (
data: pruneLinkBodyType
): Promise<AxiosResponse<pruneLinkDataType, any>> => {
try {
const response = await axiosInstance.put(opPruneLinkURL, data);
return response; return response;
} catch (error: any) { } catch (error: any) {
return error.response; return error.response;

View File

@ -1,7 +1,8 @@
export const backendDomain = process.env.REACT_APP_API_BASE_URL + "/"; export const backendDomain = process.env.REACT_APP_API_BASE_URL + "/";
export const spotifyPlaylistLinkPrefix = "https://open.spotify.com/playlist/";
export const authLoginURL = backendDomain + "api/auth/login" export const authLoginURL = backendDomain + "api/auth/login";
export const authLogoutURL = backendDomain + "api/auth/logout" export const authLogoutURL = backendDomain + "api/auth/logout";
export const authHealthCheckURL = "auth-health"; export const authHealthCheckURL = "auth-health";
export const authRefreshURL = "api/auth/refresh"; export const authRefreshURL = "api/auth/refresh";
@ -10,3 +11,5 @@ export const opFetchGraphURL = "api/operations/fetch";
export const opUpdateUserDataURL = "api/operations/update"; export const opUpdateUserDataURL = "api/operations/update";
export const opCreateLinkURL = "api/operations/link"; export const opCreateLinkURL = "api/operations/link";
export const opDeleteLinkURL = opCreateLinkURL; export const opDeleteLinkURL = opCreateLinkURL;
export const opBackfillLinkURL = "api/operations/populate/link";
export const opPruneLinkURL = "api/operations/prune/link";

View File

@ -3,10 +3,10 @@ import styles from "./Button.module.css";
type ButtonProps = { type ButtonProps = {
children: React.ReactNode; children: React.ReactNode;
onClickMethod: () => void; onClickMethod?: () => void;
} };
const Button = ({ children, onClickMethod }: ButtonProps) => { const Button = ({ children, onClickMethod = () => {} }: ButtonProps) => {
const clickHandler = (e: React.MouseEvent) => { const clickHandler = (e: React.MouseEvent) => {
e.preventDefault(); e.preventDefault();
onClickMethod(); onClickMethod();

View File

@ -13,9 +13,9 @@ import {
BackgroundVariant, BackgroundVariant,
type DefaultEdgeOptions, type DefaultEdgeOptions,
type ProOptions, type ProOptions,
type ReactFlowInstance,
type Node, type Node,
type Edge, type Edge,
type OnInit,
type OnNodesChange, type OnNodesChange,
type OnEdgesChange, type OnEdgesChange,
type OnSelectionChangeFunc, type OnSelectionChangeFunc,
@ -31,6 +31,7 @@ import { IoIosGitNetwork } from "react-icons/io";
import { WiCloudRefresh } from "react-icons/wi"; import { WiCloudRefresh } from "react-icons/wi";
import { MdOutlineLock, MdOutlineLockOpen } from "react-icons/md"; import { MdOutlineLock, MdOutlineLockOpen } from "react-icons/md";
import { AiFillSpotify } from "react-icons/ai"; import { AiFillSpotify } from "react-icons/ai";
import { PiSupersetOf, PiSubsetOf } from "react-icons/pi";
import { import {
showErrorToastNotification, showErrorToastNotification,
@ -39,10 +40,13 @@ import {
showWarnToastNotification, showWarnToastNotification,
} from "../../components/ToastNotification"; } from "../../components/ToastNotification";
import { spotifyPlaylistLinkPrefix } from "../../api/paths";
import { import {
apiBackfillLink,
apiCreateLink, apiCreateLink,
apiDeleteLink, apiDeleteLink,
apiFetchGraph, apiFetchGraph,
apiPruneLink,
apiUpdateUserData, apiUpdateUserData,
} from "../../api/operations"; } from "../../api/operations";
@ -127,10 +131,11 @@ const Graph = () => {
const flowInstance = useReactFlow(); const flowInstance = useReactFlow();
const [playlistNodes, setPlaylistNodes] = useState<Node[]>(initialNodes); const [playlistNodes, setPlaylistNodes] = useState<Node[]>(initialNodes);
const [linkEdges, setLinkEdges] = useState<Edge[]>(initialEdges); const [linkEdges, setLinkEdges] = useState<Edge[]>(initialEdges);
const [selectedEdgeID, setSelectedEdgeID] = useState<Edge["id"]>("");
const [interactive, setInteractive] = const [interactive, setInteractive] =
useState<Interactive>(initialInteractive); useState<Interactive>(initialInteractive);
const onFlowInit = (_instance: ReactFlowInstance) => { const onFlowInit: OnInit = (_instance) => {
console.debug("flow loaded"); console.debug("flow loaded");
}; };
@ -146,10 +151,11 @@ const Graph = () => {
const onFlowSelectionChange: OnSelectionChangeFunc = useCallback( const onFlowSelectionChange: OnSelectionChangeFunc = useCallback(
({ nodes, edges }) => { ({ nodes, edges }) => {
const newSelectedID = edges[0]?.id ?? ""; const selectedID = edges[0]?.id ?? "";
setSelectedEdgeID(selectedID);
setLinkEdges((eds) => setLinkEdges((eds) =>
eds.map((ed) => eds.map((ed) =>
ed.id === newSelectedID ed.id === selectedID
? { ...ed, ...selectedEdgeOptions } ? { ...ed, ...selectedEdgeOptions }
: { ...ed, ...edgeOptions } : { ...ed, ...edgeOptions }
) )
@ -197,7 +203,6 @@ const Graph = () => {
`deleted connection: ${edges[0].source} -> ${edges[0].target}` `deleted connection: ${edges[0].source} -> ${edges[0].target}`
); );
// call API to delete link // call API to delete link
const spotifyPlaylistLinkPrefix = "https://open.spotify.com/playlist/";
const resp = await APIWrapper({ const resp = await APIWrapper({
apiFn: apiDeleteLink, apiFn: apiDeleteLink,
data: { data: {
@ -215,6 +220,54 @@ const Graph = () => {
[refreshAuth] [refreshAuth]
); );
// backfill link
const backfillLink = async () => {
if (selectedEdgeID === "") {
showWarnToastNotification("Select an edge!");
return;
}
const selectedEdge = linkEdges.filter((ed) => ed.id === selectedEdgeID)[0];
const resp = await APIWrapper({
apiFn: apiBackfillLink,
data: {
from: spotifyPlaylistLinkPrefix + selectedEdge.source,
to: spotifyPlaylistLinkPrefix + selectedEdge.target,
},
refreshAuth,
});
if (resp?.status === 200) {
showSuccessToastNotification(resp?.data.message);
return;
}
return;
};
// prune link
const pruneLink = async () => {
if (selectedEdgeID === "") {
showWarnToastNotification("Select an edge!");
return;
}
const selectedEdge = linkEdges.filter((ed) => ed.id === selectedEdgeID)[0];
const resp = await APIWrapper({
apiFn: apiPruneLink,
data: {
from: spotifyPlaylistLinkPrefix + selectedEdge.source,
to: spotifyPlaylistLinkPrefix + selectedEdge.target,
},
refreshAuth,
});
if (resp?.status === 200) {
showSuccessToastNotification(resp?.data.message);
return;
}
return;
};
type getLayoutedElementsOpts = { type getLayoutedElementsOpts = {
direction: rankdirType; direction: rankdirType;
}; };
@ -405,7 +458,7 @@ const Graph = () => {
nodesDraggable={interactive.ndDrag} nodesDraggable={interactive.ndDrag}
nodesConnectable={interactive.ndConn} nodesConnectable={interactive.ndConn}
elementsSelectable={interactive.elsSel} elementsSelectable={interactive.elsSel}
deleteKeyCode={["Delete", "Backspace"]} deleteKeyCode={["Delete"]}
multiSelectionKeyCode={null} multiSelectionKeyCode={null}
onInit={onFlowInit} onInit={onFlowInit}
onBeforeDelete={onFlowBeforeDelete} onBeforeDelete={onFlowBeforeDelete}
@ -418,9 +471,13 @@ const Graph = () => {
<Background variant={BackgroundVariant.Dots} gap={36} size={3} /> <Background variant={BackgroundVariant.Dots} gap={36} size={3} />
</ReactFlow> </ReactFlow>
<div className={`${styles.operations_wrapper} custom_scrollbar`}> <div className={`${styles.operations_wrapper} custom_scrollbar`}>
<Button onClickMethod={refreshGraph}> <Button onClickMethod={backfillLink}>
<WiCloudRefresh size={36} /> <PiSupersetOf size={36} />
Refresh Graph Backfill Link
</Button>
<Button onClickMethod={pruneLink}>
<PiSubsetOf size={36} />
Prune Link
</Button> </Button>
<Button onClickMethod={() => arrangeLayout("TB")}> <Button onClickMethod={() => arrangeLayout("TB")}>
<IoIosGitNetwork size={36} /> <IoIosGitNetwork size={36} />
@ -442,6 +499,10 @@ const Graph = () => {
</span> </span>
Sync Spotify Sync Spotify
</Button> </Button>
<Button onClickMethod={refreshGraph}>
<WiCloudRefresh size={36} />
Refresh Graph
</Button>
</div> </div>
</div> </div>
); );