Skip to content

Commit

Permalink
feat(view, vscode): implement branchSelector onChange handler (#486)
Browse files Browse the repository at this point in the history
* feat: implement branchSelector onChange handler

* fix: apply view service module, add coverage about detached head
  • Loading branch information
ss-won committed Sep 19, 2023
1 parent e31c0a6 commit ae3e28a
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 103 deletions.
10 changes: 5 additions & 5 deletions packages/view/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,24 @@ import type { IDESentEvents } from "types/IDESentEvents";
const App = () => {
const initRef = useRef<boolean>(false);

const { filteredData, fetchAnalyzedData, fetchBranchList, loading, setLoading } = useGlobalData();
const { filteredData, handleChangeAnalyzedData, handleChangeBranchList, loading, setLoading } = useGlobalData();

const ideAdapter = container.resolve<IDEPort>("IDEAdapter");

useEffect(() => {
if (initRef.current === false) {
const callbacks: IDESentEvents = {
fetchAnalyzedData,
fetchBranchList,
handleChangeAnalyzedData,
handleChangeBranchList,
};

setLoading(true);
ideAdapter.addIDESentEventListener(callbacks);
ideAdapter.sendFetchAnalyzedDataMessage();
ideAdapter.sendGetBranchListMessage();
ideAdapter.sendFetchBranchListMessage();
initRef.current = true;
}
}, [fetchBranchList, fetchAnalyzedData, ideAdapter, setLoading]);
}, [handleChangeAnalyzedData, handleChangeBranchList, ideAdapter, setLoading]);

if (loading) {
return (
Expand Down
11 changes: 7 additions & 4 deletions packages/view/src/components/BranchSelector/BranchSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { type ChangeEventHandler } from "react";
import "./BranchSelector.scss";

import { useGlobalData } from "hooks";
import { sendFetchAnalyzedDataCommand } from "services";

const BranchSelector = () => {
const { branchList, selectedBranch } = useGlobalData();
const { branchList, selectedBranch, setSelectedBranch, setLoading } = useGlobalData();

const handleChangeSelect: ChangeEventHandler<HTMLSelectElement> = (e) => {
// TODO - webview로 선택된 branch을 payload에 실어 sendFetchAnalyzedDataCommand 호출
console.log(e.target.value);
setSelectedBranch(e.target.value);
setLoading(true);
sendFetchAnalyzedDataCommand(e.target.value);
};

return (
Expand All @@ -18,7 +21,7 @@ const BranchSelector = () => {
onChange={handleChangeSelect}
value={selectedBranch}
>
{branchList.map((option) => (
{branchList?.map((option) => (
<option
key={option}
value={option}
Expand Down
9 changes: 3 additions & 6 deletions packages/view/src/components/RefreshButton/RefreshButton.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import "reflect-metadata";
import cn from "classnames";
import { container } from "tsyringe";
import { FiRefreshCcw } from "react-icons/fi";

import { throttle } from "utils";
import type IDEPort from "ide/IDEPort";
import { useGlobalData } from "hooks";

import "./RefreshButton.scss";
import { sendRefreshDataCommand } from "services";

const RefreshButton = () => {
const { loading, setLoading } = useGlobalData();
const { loading, setLoading, selectedBranch } = useGlobalData();

const refreshHandler = throttle(() => {
setLoading(true);

const ideAdapter = container.resolve<IDEPort>("IDEAdapter");
ideAdapter.sendFetchAnalyzedDataMessage();
sendRefreshDataCommand(selectedBranch);
}, 3000);

return (
Expand Down
14 changes: 7 additions & 7 deletions packages/view/src/context/GlobalDataProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ export const GlobalDataProvider = ({ children }: PropsWithChildren) => {
const [filteredRange, setFilteredRange] = useState<DateFilterRange>(undefined);

const [branchList, setBranchList] = useState<string[]>([]);
// TODO 초기에 base branch를 fetch해서 적용
const [selectedBranch, setSelectedBranch] = useState<string>("main");
const [selectedBranch, setSelectedBranch] = useState<string>(branchList?.[0]);

const fetchBranchList = (branchList: string[]) => {
setBranchList(branchList);
const handleChangeBranchList = (branches: { branchList: string[]; head: string | null }) => {
setSelectedBranch((prev) => (!prev && branches.head ? branches.head : prev));
setBranchList(branches.branchList);
};

const fetchAnalyzedData = (analyzedData: ClusterNode[]) => {
const handleChangeAnalyzedData = (analyzedData: ClusterNode[]) => {
setData(analyzedData);
setFilteredData([...analyzedData.reverse()]);
setSelectedData([]);
Expand All @@ -42,8 +42,8 @@ export const GlobalDataProvider = ({ children }: PropsWithChildren) => {
setBranchList,
selectedBranch,
setSelectedBranch,
fetchAnalyzedData,
fetchBranchList,
handleChangeAnalyzedData,
handleChangeBranchList,
}),
[data, filteredRange, filteredData, selectedData, branchList, selectedBranch, loading]
);
Expand Down
12 changes: 6 additions & 6 deletions packages/view/src/ide/FakeIDEAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ export default class FakeIDEAdapter implements IDEPort {

switch (command) {
case "fetchAnalyzedData":
return events.fetchAnalyzedData(payloadData);
case "getBranchList":
return events.fetchBranchList(payloadData);
return events.handleChangeAnalyzedData(payloadData);
case "fetchBranchList":
return events.handleChangeBranchList(payloadData);
default:
console.log("Unknown Message");
}
Expand All @@ -47,9 +47,9 @@ export default class FakeIDEAdapter implements IDEPort {
}, 3000);
}

public sendGetBranchListMessage() {
public sendFetchBranchListMessage() {
const message: IDEMessage = {
command: "getBranchList",
command: "fetchBranchList",
};
this.sendMessageToMe(message);
}
Expand All @@ -71,7 +71,7 @@ export default class FakeIDEAdapter implements IDEPort {
command,
payload: JSON.stringify(fakeData),
};
case "getBranchList":
case "fetchBranchList":
return {
command,
payload: JSON.stringify(fakeBranchList),
Expand Down
2 changes: 1 addition & 1 deletion packages/view/src/ide/IDEPort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export default interface IDEPort {
addIDESentEventListener: (apiCallbacks: IDESentEvents) => void;
sendRefreshDataMessage: (payload?: string) => void;
sendFetchAnalyzedDataMessage: (payload?: string) => void;
sendGetBranchListMessage: () => void;
sendFetchBranchListMessage: () => void;
setPrimaryColor: (color: string) => void;
}
18 changes: 9 additions & 9 deletions packages/view/src/ide/VSCodeIDEAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,35 @@ export default class VSCodeIDEAdapter implements IDEPort {

switch (command) {
case "fetchAnalyzedData":
return events.fetchAnalyzedData(payloadData);
case "getBranchList":
return events.fetchBranchList(payloadData);
return events.handleChangeAnalyzedData(payloadData);
case "fetchBranchList":
return events.handleChangeBranchList(payloadData);
default:
console.log("Unknown Message");
}
};
window.addEventListener("message", onReceiveMessage);
}

public sendRefreshDataMessage(payload?: string) {
public sendRefreshDataMessage(baseBranch?: string) {
const message: IDEMessage = {
command: "refresh",
payload,
payload: JSON.stringify(baseBranch),
};
this.sendMessageToIDE(message);
}

public sendFetchAnalyzedDataMessage(payload?: string) {
public sendFetchAnalyzedDataMessage(baseBranch?: string) {
const message: IDEMessage = {
command: "fetchAnalyzedData",
payload,
payload: JSON.stringify(baseBranch),
};
this.sendMessageToIDE(message);
}

public sendGetBranchListMessage() {
public sendFetchBranchListMessage() {
const message: IDEMessage = {
command: "getBranchList",
command: "fetchBranchList",
};
this.sendMessageToIDE(message);
}
Expand Down
14 changes: 10 additions & 4 deletions packages/view/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ export const setPrimaryColor = (color: string) => {
ideAdapter.setPrimaryColor(color);
};

export const sendFetchAnalyzedDataCommand = () => {
export const sendFetchAnalyzedDataCommand = (selectedBranch?: string) => {
const ideAdapter = container.resolve<IDEPort>("IDEAdapter");
ideAdapter.sendFetchAnalyzedDataMessage();
ideAdapter.sendFetchAnalyzedDataMessage(selectedBranch);
};

export const sendRefreshDataCommand = () => {
export const sendRefreshDataCommand = (selectedBranch?: string) => {
const ideAdapter = container.resolve<IDEPort>("IDEAdapter");
ideAdapter.sendRefreshDataMessage();
ideAdapter.sendRefreshDataMessage(selectedBranch);
ideAdapter.sendFetchBranchListMessage();
};

export const sendFetchBranchListCommand = () => {
const ideAdapter = container.resolve<IDEPort>("IDEAdapter");
ideAdapter.sendFetchBranchListMessage();
};
7 changes: 6 additions & 1 deletion packages/view/src/types/IDEMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@ export interface IDEMessageEvent extends MessageEvent {
data: IDEMessage;
}

export type IDEMessageCommandNames = "refresh" | "fetchAnalyzedData" | "getBranchList" | "updatePrimaryColor";
export type IDEMessageCommandNames =
| "refresh"
| "fetchAnalyzedData"
| "fetchBranchList"
| "fetchCurrentBranch"
| "updatePrimaryColor";
4 changes: 2 additions & 2 deletions packages/view/src/types/IDESentEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import type { ClusterNode } from "types";

// triggered by ide response
export type IDESentEvents = {
fetchAnalyzedData: (analyzedData: ClusterNode[]) => void;
fetchBranchList: (branchList: string[]) => void;
handleChangeAnalyzedData: (analyzedData: ClusterNode[]) => void;
handleChangeBranchList: (branches: { branchList: string[]; head: string | null }) => void;
};
35 changes: 23 additions & 12 deletions packages/vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ import { Credentials } from "./credentials";
import { GithubTokenUndefinedError, WorkspacePathUndefinedError } from "./errors/ExtensionError";
import { getGithubToken, setGithubToken } from "./setting-repository";
import { mapClusterNodesFrom } from "./utils/csm.mapper";
import { findGit, getBaseBranchName, getBranchNames, getGitConfig, getGitLog, getRepo } from "./utils/git.util";
import {
findGit,
getBranches,
getCurrentBranchName,
getDefaultBranchName,
getGitConfig,
getGitLog,
getRepo,
} from "./utils/git.util";
import WebviewLoader from "./webview-loader";

let myStatusBarItem: vscode.StatusBarItem;
Expand Down Expand Up @@ -38,18 +46,17 @@ export async function activate(context: vscode.ExtensionContext) {
throw new GithubTokenUndefinedError("Cannot find your GitHub token. Retrying github authentication...");
}

const fetchBranchList = async () => {
const storedBranchList = context.workspaceState.get<string[]>("branchList") ?? [];
context.workspaceState.update(
"branchList",
(await getBranchNames(gitPath, currentWorkspacePath)) ?? storedBranchList
);
return context.workspaceState.get<string[]>("branchList") ?? storedBranchList;
const fetchBranches = async () => await getBranches(gitPath, currentWorkspacePath);
const fetchCurrentBranch = async () => {
let branchName = await getCurrentBranchName(gitPath, currentWorkspacePath);
if (!branchName) {
const branchList = (await fetchBranches()).branchList;
branchName = getDefaultBranchName(branchList);
}
return branchName;
};

const branchNames = await fetchBranchList();
const initialBaseBranchName = getBaseBranchName(branchNames);

const initialBaseBranchName = await fetchCurrentBranch();
const fetchClusterNodes = async (baseBranchName = initialBaseBranchName) => {
const gitLog = await getGitLog(gitPath, currentWorkspacePath);
const gitConfig = await getGitConfig(gitPath, currentWorkspacePath, "origin");
Expand All @@ -67,7 +74,11 @@ export async function activate(context: vscode.ExtensionContext) {
return clusterNodes;
};

const webLoader = new WebviewLoader(extensionPath, context, fetchClusterNodes, fetchBranchList);
const webLoader = new WebviewLoader(extensionPath, context, {
fetchClusterNodes,
fetchBranches,
fetchCurrentBranch,
});

subscriptions.push(webLoader);
vscode.window.showInformationMessage("Hello Githru");
Expand Down
85 changes: 50 additions & 35 deletions packages/vscode/src/utils/git.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,42 +222,57 @@ export const getRepo = (gitRemoteConfig: string) => {
return { owner, repo };
};

export async function getBranchNames(path: string, repo: string): Promise<string[]> {
return new Promise((resolve, reject) => {
resolveSpawnOutput(
cp.spawn(path, ["branch", "-a"], {
cwd: repo,
env: Object.assign({}, process.env),
})
).then((values) => {
const [status, stdout, stderr] = values;
if (status.code === 0) {
const branches = stdout
.toString()
.split("\n")
.map((name) =>
name
.replace("*", "") // delete * prefix
.replace("remotes/", "") // delete remotes/ prifix
.replace(/(.*) -> (?:.*)/g, "$1") // remote HEAD parsing
.trim()
)
.filter((name) => !!name);
resolve(branches);
} else {
reject(stderr);
}
});
});
}
export async function getBranches(
path: string,
repo: string
): Promise<{
branchList: string[];
head: string | null;
}> {
let head = null;
const branchList = [];

const [status, stdout, stderr] = await resolveSpawnOutput(
cp.spawn(path, ["branch", "-a"], {
cwd: repo,
env: Object.assign({}, process.env),
})
);

export function getBaseBranchName(branchNames: string[]): string {
for (const name of branchNames) {
if (name === "main") {
return "main";
} else if (name === "master") {
return "master";
if (status.code !== 0) throw stderr;

const branches = stdout.toString().split(/\r\n|\r|\n/g);
for (let branch of branches) {
branch = branch
.trim()
.replace(/(.*) -> (?:.*)/g, "$1")
.replace("remotes/", "");
if (branch.startsWith("* ")) {
if (branch.includes("HEAD detached")) continue;
branch = branch.replace("* ", "");
head = branch;
}
branchList.push(branch);
}
return branchNames[0];

if (!head) head = getDefaultBranchName(branchList);

return { branchList, head };
}

export function getDefaultBranchName(branchList: string[]): string {
const branchSet = new Set(branchList);
return branchSet.has("main") ? "main" : branchSet.has("master") ? "master" : branchList?.[0];
}

export async function getCurrentBranchName(path: string, repo: string): Promise<string> {
const [status, stdout, stderr] = await resolveSpawnOutput(
cp.spawn(path, ["branch", "--show-current"], {
cwd: repo,
env: Object.assign({}, process.env),
})
);

if (status.code !== 0) throw stderr;
return stdout.toString().trim();
}

0 comments on commit ae3e28a

Please sign in to comment.