mirror of
https://github.com/20kaushik02/spotify-manager-web.git
synced 2025-12-06 08:14:07 +00:00
link backfill and prune
This commit is contained in:
parent
c9938aca35
commit
4baedc3e60
@ -1,9 +1,11 @@
|
||||
import { AxiosResponse } from "axios";
|
||||
import { apiRespBaseType, axiosInstance } from "./axiosInstance";
|
||||
import {
|
||||
opBackfillLinkURL,
|
||||
opCreateLinkURL,
|
||||
opDeleteLinkURL,
|
||||
opFetchGraphURL,
|
||||
opPruneLinkURL,
|
||||
opUpdateUserDataURL,
|
||||
} from "./paths";
|
||||
|
||||
@ -27,8 +29,26 @@ type createLinkBodyType = {
|
||||
to: string; // playlistID
|
||||
};
|
||||
|
||||
interface createLinkDataType extends apiRespBaseType {}
|
||||
|
||||
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<
|
||||
AxiosResponse<fetchGraphDataType, any>
|
||||
> => {
|
||||
@ -53,7 +73,7 @@ export const apiUpdateUserData = async (): Promise<
|
||||
|
||||
export const apiCreateLink = async (
|
||||
data: createLinkBodyType
|
||||
): Promise<AxiosResponse<apiRespBaseType, any>> => {
|
||||
): Promise<AxiosResponse<createLinkDataType, any>> => {
|
||||
try {
|
||||
const response = await axiosInstance.post(opCreateLinkURL, data);
|
||||
return response;
|
||||
@ -64,9 +84,31 @@ export const apiCreateLink = async (
|
||||
|
||||
export const apiDeleteLink = async (
|
||||
data: deleteLinkBodyType
|
||||
): Promise<AxiosResponse<apiRespBaseType, any>> => {
|
||||
): Promise<AxiosResponse<deleteLinkDataType, any>> => {
|
||||
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;
|
||||
} catch (error: any) {
|
||||
return error.response;
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
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 authLogoutURL = backendDomain + "api/auth/logout"
|
||||
export const authLoginURL = backendDomain + "api/auth/login";
|
||||
export const authLogoutURL = backendDomain + "api/auth/logout";
|
||||
|
||||
export const authHealthCheckURL = "auth-health";
|
||||
export const authRefreshURL = "api/auth/refresh";
|
||||
@ -10,3 +11,5 @@ export const opFetchGraphURL = "api/operations/fetch";
|
||||
export const opUpdateUserDataURL = "api/operations/update";
|
||||
export const opCreateLinkURL = "api/operations/link";
|
||||
export const opDeleteLinkURL = opCreateLinkURL;
|
||||
export const opBackfillLinkURL = "api/operations/populate/link";
|
||||
export const opPruneLinkURL = "api/operations/prune/link";
|
||||
|
||||
@ -3,10 +3,10 @@ import styles from "./Button.module.css";
|
||||
|
||||
type ButtonProps = {
|
||||
children: React.ReactNode;
|
||||
onClickMethod: () => void;
|
||||
}
|
||||
onClickMethod?: () => void;
|
||||
};
|
||||
|
||||
const Button = ({ children, onClickMethod }: ButtonProps) => {
|
||||
const Button = ({ children, onClickMethod = () => {} }: ButtonProps) => {
|
||||
const clickHandler = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
onClickMethod();
|
||||
|
||||
@ -13,9 +13,9 @@ import {
|
||||
BackgroundVariant,
|
||||
type DefaultEdgeOptions,
|
||||
type ProOptions,
|
||||
type ReactFlowInstance,
|
||||
type Node,
|
||||
type Edge,
|
||||
type OnInit,
|
||||
type OnNodesChange,
|
||||
type OnEdgesChange,
|
||||
type OnSelectionChangeFunc,
|
||||
@ -31,6 +31,7 @@ import { IoIosGitNetwork } from "react-icons/io";
|
||||
import { WiCloudRefresh } from "react-icons/wi";
|
||||
import { MdOutlineLock, MdOutlineLockOpen } from "react-icons/md";
|
||||
import { AiFillSpotify } from "react-icons/ai";
|
||||
import { PiSupersetOf, PiSubsetOf } from "react-icons/pi";
|
||||
|
||||
import {
|
||||
showErrorToastNotification,
|
||||
@ -39,10 +40,13 @@ import {
|
||||
showWarnToastNotification,
|
||||
} from "../../components/ToastNotification";
|
||||
|
||||
import { spotifyPlaylistLinkPrefix } from "../../api/paths";
|
||||
import {
|
||||
apiBackfillLink,
|
||||
apiCreateLink,
|
||||
apiDeleteLink,
|
||||
apiFetchGraph,
|
||||
apiPruneLink,
|
||||
apiUpdateUserData,
|
||||
} from "../../api/operations";
|
||||
|
||||
@ -127,10 +131,11 @@ const Graph = () => {
|
||||
const flowInstance = useReactFlow();
|
||||
const [playlistNodes, setPlaylistNodes] = useState<Node[]>(initialNodes);
|
||||
const [linkEdges, setLinkEdges] = useState<Edge[]>(initialEdges);
|
||||
const [selectedEdgeID, setSelectedEdgeID] = useState<Edge["id"]>("");
|
||||
const [interactive, setInteractive] =
|
||||
useState<Interactive>(initialInteractive);
|
||||
|
||||
const onFlowInit = (_instance: ReactFlowInstance) => {
|
||||
const onFlowInit: OnInit = (_instance) => {
|
||||
console.debug("flow loaded");
|
||||
};
|
||||
|
||||
@ -146,10 +151,11 @@ const Graph = () => {
|
||||
|
||||
const onFlowSelectionChange: OnSelectionChangeFunc = useCallback(
|
||||
({ nodes, edges }) => {
|
||||
const newSelectedID = edges[0]?.id ?? "";
|
||||
const selectedID = edges[0]?.id ?? "";
|
||||
setSelectedEdgeID(selectedID);
|
||||
setLinkEdges((eds) =>
|
||||
eds.map((ed) =>
|
||||
ed.id === newSelectedID
|
||||
ed.id === selectedID
|
||||
? { ...ed, ...selectedEdgeOptions }
|
||||
: { ...ed, ...edgeOptions }
|
||||
)
|
||||
@ -197,7 +203,6 @@ const Graph = () => {
|
||||
`deleted connection: ${edges[0].source} -> ${edges[0].target}`
|
||||
);
|
||||
// call API to delete link
|
||||
const spotifyPlaylistLinkPrefix = "https://open.spotify.com/playlist/";
|
||||
const resp = await APIWrapper({
|
||||
apiFn: apiDeleteLink,
|
||||
data: {
|
||||
@ -215,6 +220,54 @@ const Graph = () => {
|
||||
[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 = {
|
||||
direction: rankdirType;
|
||||
};
|
||||
@ -405,7 +458,7 @@ const Graph = () => {
|
||||
nodesDraggable={interactive.ndDrag}
|
||||
nodesConnectable={interactive.ndConn}
|
||||
elementsSelectable={interactive.elsSel}
|
||||
deleteKeyCode={["Delete", "Backspace"]}
|
||||
deleteKeyCode={["Delete"]}
|
||||
multiSelectionKeyCode={null}
|
||||
onInit={onFlowInit}
|
||||
onBeforeDelete={onFlowBeforeDelete}
|
||||
@ -418,9 +471,13 @@ const Graph = () => {
|
||||
<Background variant={BackgroundVariant.Dots} gap={36} size={3} />
|
||||
</ReactFlow>
|
||||
<div className={`${styles.operations_wrapper} custom_scrollbar`}>
|
||||
<Button onClickMethod={refreshGraph}>
|
||||
<WiCloudRefresh size={36} />
|
||||
Refresh Graph
|
||||
<Button onClickMethod={backfillLink}>
|
||||
<PiSupersetOf size={36} />
|
||||
Backfill Link
|
||||
</Button>
|
||||
<Button onClickMethod={pruneLink}>
|
||||
<PiSubsetOf size={36} />
|
||||
Prune Link
|
||||
</Button>
|
||||
<Button onClickMethod={() => arrangeLayout("TB")}>
|
||||
<IoIosGitNetwork size={36} />
|
||||
@ -442,6 +499,10 @@ const Graph = () => {
|
||||
</span>
|
||||
Sync Spotify
|
||||
</Button>
|
||||
<Button onClickMethod={refreshGraph}>
|
||||
<WiCloudRefresh size={36} />
|
||||
Refresh Graph
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user