From 1b05deb31f80dad08cb2c65fe50773ca1995d4e7 Mon Sep 17 00:00:00 2001 From: "ytaek.kim" Date: Sat, 30 Sep 2023 01:47:44 +0900 Subject: [PATCH 1/4] feat(vscode): add feature for resetting github auth by deleting saved auth --- packages/vscode/package.json | 5 +++++ packages/vscode/src/commands.ts | 1 + packages/vscode/src/extension.ts | 11 ++++++++--- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/vscode/package.json b/packages/vscode/package.json index ae8ee6ff..ab58cd1f 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -52,6 +52,11 @@ "command": "githru.command.login.github", "title": "Login with Github", "category": "Githru" + }, + { + "command": "githru.command.reset.github_auth", + "title": "Reset GitHub Authentication saved previously", + "category": "Githru" } ], "configuration": { diff --git a/packages/vscode/src/commands.ts b/packages/vscode/src/commands.ts index 377a94c1..597cfa6f 100644 --- a/packages/vscode/src/commands.ts +++ b/packages/vscode/src/commands.ts @@ -2,3 +2,4 @@ export const COMMAND_PREFIX = "githru.command"; export const COMMAND_LAUNCH = `${COMMAND_PREFIX}.launch`; export const COMMAND_RELOAD = `${COMMAND_PREFIX}.reload`; export const COMMAND_LOGIN_WITH_GITHUB = `${COMMAND_PREFIX}.login.github`; +export const COMMAND_RESET_GITHUB_AUTH = `${COMMAND_PREFIX}.reset.github_auth`; diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index e569263b..aa88d797 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -1,10 +1,10 @@ import { AnalysisEngine } from "@githru-vscode-ext/analysis-engine"; import * as vscode from "vscode"; -import { COMMAND_LAUNCH, COMMAND_LOGIN_WITH_GITHUB } from "./commands"; +import { COMMAND_LAUNCH, COMMAND_LOGIN_WITH_GITHUB, COMMAND_RESET_GITHUB_AUTH } from "./commands"; import { Credentials } from "./credentials"; import { GithubTokenUndefinedError, WorkspacePathUndefinedError } from "./errors/ExtensionError"; -import { getGithubToken, setGithubToken, } from "./setting-repository"; +import { deleteGithubToken, getGithubToken, setGithubToken, } from "./setting-repository"; import { mapClusterNodesFrom } from "./utils/csm.mapper"; import { findGit, @@ -106,7 +106,12 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.executeCommand(COMMAND_LAUNCH); }); - subscriptions.concat([disposable, loginWithGithub]); + const resetGithubAuth = vscode.commands.registerCommand(COMMAND_RESET_GITHUB_AUTH, async () => { + await deleteGithubToken(secrets); + vscode.window.showInformationMessage(`Github Authentication reset.`); + }); + + subscriptions.concat([disposable, loginWithGithub, resetGithubAuth]); myStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, -10); myStatusBarItem.text = "githru"; From 1831b7d42a6d1a96ea367a737f6b3c0c8375bd9d Mon Sep 17 00:00:00 2001 From: "ytaek.kim" Date: Sat, 30 Sep 2023 02:55:24 +0900 Subject: [PATCH 2/4] feat(all): v0.6.0 release --- package.json | 2 +- packages/analysis-engine/package.json | 2 +- packages/view/package.json | 4 ++-- packages/vscode/README.md | 6 ++++++ packages/vscode/package.json | 4 ++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 153e0810..815a5cb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "githru-vscode-ext", - "version": "0.5.0", + "version": "0.6.0", "description": "githru-vscode-ext root package.json", "scripts": { "build:all": "npm run build --workspaces", diff --git a/packages/analysis-engine/package.json b/packages/analysis-engine/package.json index 1ac9d672..654fcb24 100644 --- a/packages/analysis-engine/package.json +++ b/packages/analysis-engine/package.json @@ -1,6 +1,6 @@ { "name": "@githru-vscode-ext/analysis-engine", - "version": "0.5.0", + "version": "0.6.0", "description": "analysis-engine module for githru", "main": "dist/index.js", "module": "dist/index.es.js", diff --git a/packages/view/package.json b/packages/view/package.json index 40501450..1f1bcbd8 100644 --- a/packages/view/package.json +++ b/packages/view/package.json @@ -1,6 +1,6 @@ { "name": "@githru-vscode-ext/view", - "version": "0.5.0", + "version": "0.6.0", "description": "view module for githru", "engines": { "node": ">=16", @@ -47,7 +47,7 @@ "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.22.15", "@babel/runtime": "^7.18.9", - "@githru-vscode-ext/analysis-engine": "^0.5.0", + "@githru-vscode-ext/analysis-engine": "^0.6.0", "@playwright/test": "^1.37.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@svgr/webpack": "^8.0.1", diff --git a/packages/vscode/README.md b/packages/vscode/README.md index 2f6a9c61..7d6708c8 100644 --- a/packages/vscode/README.md +++ b/packages/vscode/README.md @@ -33,6 +33,12 @@ Calling out known issues can help limit users opening duplicate issues against y ## Release Notes +### 0.6.0 +- branch selector +- reset github auth +- error handling for pr/auth +- e2e test + ### 0.5.0 - login by github auth - fix minor ui issues diff --git a/packages/vscode/package.json b/packages/vscode/package.json index ab58cd1f..bfb33085 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/githru/githru-vscode-ext", "type": "git" }, - "version": "0.5.0", + "version": "0.6.0", "engines": { "vscode": "^1.67.0" }, @@ -89,7 +89,7 @@ "test": "node ./out/test/runTest.js" }, "dependencies": { - "@githru-vscode-ext/analysis-engine": "^0.5.0", + "@githru-vscode-ext/analysis-engine": "^0.6.0", "@octokit/rest": "^20.0.1", "node-fetch": "^3.3.2" }, From 1ab90ff3d8e180d9af3f5ca134a70b65b53607ea Mon Sep 17 00:00:00 2001 From: Jeon Date: Wed, 27 Sep 2023 15:54:25 +0900 Subject: [PATCH 3/4] fix: Prevent multiple webview panel instances --- packages/vscode/src/extension.ts | 8 ++++++++ packages/vscode/src/webview-loader.ts | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index aa88d797..7fe0c631 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -26,12 +26,19 @@ function normalizeFsPath(fsPath: string) { export async function activate(context: vscode.ExtensionContext) { const { subscriptions, extensionPath, secrets } = context; const credentials = new Credentials(); + let currentPanel: vscode.WebviewPanel | undefined = undefined; + await credentials.initialize(context); console.log('Congratulations, your extension "githru" is now active!'); const disposable = vscode.commands.registerCommand(COMMAND_LAUNCH, async () => { try { + console.debug(currentPanel); + if (currentPanel) { + currentPanel.reveal(); + return; + } const gitPath = (await findGit()).path; const currentWorkspaceUri = vscode.workspace.workspaceFolders?.[0].uri; @@ -81,6 +88,7 @@ export async function activate(context: vscode.ExtensionContext) { fetchBranches, fetchCurrentBranch, }); + currentPanel = webLoader.getPanel(); subscriptions.push(webLoader); vscode.window.showInformationMessage("Hello Githru"); diff --git a/packages/vscode/src/webview-loader.ts b/packages/vscode/src/webview-loader.ts index 53ae8133..3715fae3 100644 --- a/packages/vscode/src/webview-loader.ts +++ b/packages/vscode/src/webview-loader.ts @@ -69,6 +69,10 @@ export default class WebviewLoader implements vscode.Disposable { this._panel?.dispose(); } + getPanel() { + return this._panel; + } + private async respondToMessage(message: { command: string; payload: unknown }) { this._panel?.webview.postMessage({ command: message.command, From 764a56db29c2882cf8c2bb565485f1d60f67e14b Mon Sep 17 00:00:00 2001 From: Taesung Lee <66891085+2taesung@users.noreply.github.com> Date: Wed, 4 Oct 2023 04:16:07 +0900 Subject: [PATCH 4/4] fix(view): revert scss module usage due to rendering error --- .../ClusterGraph/ClusterGraph.hook.tsx | 17 +++-- .../ClusterGraph/ClusterGraph.scss | 4 + .../ClusterGraph/ClusterGraph.tsx | 1 - .../ClusterGraph/ClusterGraph.util.ts | 11 ++- .../ClusterGraph/Draws/drawSubGraph.ts | 73 +++++++++++++++++++ .../ClusterGraph/Draws/drawTotalLine.ts | 3 +- .../ClusterGraph/Draws/index.ts | 1 + packages/view/src/ide/FakeIDEAdapter.ts | 5 +- packages/view/tests/home.spec.ts | 33 +++++++++ packages/view/tsconfig.json | 4 +- 10 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/drawSubGraph.ts diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.hook.tsx b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.hook.tsx index 4cbcc96a..2dc10476 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.hook.tsx +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.hook.tsx @@ -9,7 +9,7 @@ import { selectedDataUpdater } from "../VerticalClusterList.util"; import { CLUSTER_HEIGHT, DETAIL_HEIGHT, GRAPH_WIDTH, NODE_GAP, SVG_MARGIN } from "./ClusterGraph.const"; import type { ClusterGraphElement } from "./ClusterGraph.type"; -import { destroyClusterGraph, drawClusterBox, drawCommitAmountCluster, drawTotalLine } from "./Draws"; +import { destroyClusterGraph, drawClusterBox, drawCommitAmountCluster, drawSubGraph, drawTotalLine } from "./Draws"; import { getTranslateAfterSelect } from "./ClusterGraph.util"; const drawClusterGraph = ( @@ -34,18 +34,17 @@ const drawClusterGraph = ( .duration(0) .attr("transform", (d, i) => getTranslateAfterSelect(d, i, detailElementHeight)); - drawTotalLine(svgRef, data, detailElementHeight, SVG_MARGIN, CLUSTER_HEIGHT, NODE_GAP, GRAPH_WIDTH); drawClusterBox(group, GRAPH_WIDTH, CLUSTER_HEIGHT); drawCommitAmountCluster(group, GRAPH_WIDTH, CLUSTER_HEIGHT); + drawSubGraph(svgRef, data, detailElementHeight); + drawTotalLine(svgRef, data, detailElementHeight, SVG_MARGIN, CLUSTER_HEIGHT, NODE_GAP, GRAPH_WIDTH); }; export const useHandleClusterGraph = ({ data, - clusterSizes, selectedIndex, setSelectedData, }: { - clusterSizes: number[]; selectedIndex: number[]; data: ClusterNode[]; setSelectedData: Dispatch>; @@ -53,9 +52,9 @@ export const useHandleClusterGraph = ({ const svgRef = useRef(null); const prevSelected = useRef([-1]); - const clusterGraphElements = data.map((cluster, i) => ({ + const clusterGraphElements = data.map((cluster) => ({ cluster, - clusterSize: clusterSizes[i], + clusterSize: cluster.commitNodeList.length, selected: { prev: prevSelected.current, current: selectedIndex, @@ -63,8 +62,10 @@ export const useHandleClusterGraph = ({ })); const handleClickCluster = useCallback( - (_: PointerEvent, d: ClusterGraphElement) => - setSelectedData(selectedDataUpdater(d.cluster, d.cluster.commitNodeList[0].clusterId)), + (_: PointerEvent, d: ClusterGraphElement) => { + const targetIndex = d.cluster.commitNodeList[0].clusterId; + setSelectedData(selectedDataUpdater(d.cluster, targetIndex)); + }, [setSelectedData] ); useEffect(() => { diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.scss b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.scss index 1b144bde..8e791a77 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.scss +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.scss @@ -24,3 +24,7 @@ stroke: var(--primary-color); stroke-width: 1; } + +.circle-group { + fill: var(--primary-color); +} diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.tsx b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.tsx index 5437fa72..96aa51f0 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.tsx +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.tsx @@ -14,7 +14,6 @@ const ClusterGraph = () => { const svgRef = useHandleClusterGraph({ data, - clusterSizes, selectedIndex, setSelectedData, }); diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.util.ts b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.util.ts index 643bfef3..718d3b39 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.util.ts +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.util.ts @@ -11,11 +11,20 @@ export function getGraphHeight(clusterSizes: number[]) { return clusterSizes.length * CLUSTER_HEIGHT + clusterSizes.length * NODE_GAP + NODE_GAP; } +export function getStartYEndY(d: ClusterGraphElement, a: number, detailElementHeight: number) { + const selected = d.selected.current; + const selectedLength = selected.filter((selectedIdx) => selectedIdx < a).length; + const selectedLongerHeight = selectedLength * detailElementHeight; + const startY = SVG_MARGIN.top + 20 + (a + 1) * (CLUSTER_HEIGHT + NODE_GAP) + selectedLongerHeight; + const endY = startY + detailElementHeight - 50; + return { startY, endY }; +} + export function getTranslateAfterSelect( d: ClusterGraphElement, i: number, detailElementHeight: number, - isPrev = false + isPrev = false // TODO - this param can be removed ) { const selected = isPrev ? d.selected.prev : d.selected.current; const selectedLength = selected.filter((selectedIdx) => selectedIdx < i).length; diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/drawSubGraph.ts b/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/drawSubGraph.ts new file mode 100644 index 00000000..f9778571 --- /dev/null +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/drawSubGraph.ts @@ -0,0 +1,73 @@ +import * as d3 from "d3"; +import type { RefObject } from "react"; + +import type { ClusterGraphElement } from "../ClusterGraph.type"; +import { getStartYEndY } from "../ClusterGraph.util"; +import { GRAPH_WIDTH } from "../ClusterGraph.const"; + +// create tootip (HTML) +const tooltip = d3 + .select("body") + .append("div") + .attr("class", "tooltip") + .style("position", "absolute") + .style("z-index", "10") + .style("visibility", "hidden") + .text("Tooltip"); + +const calculateCirclePositions = (numOfCircles: number, startY: number, endY: number, gap: number) => { + const positionStrategies = new Map number[]>([ + [1, (start, end) => [(start + end) / 2]], + [2, (start, end) => [(3 * start + end) / 4, (start + 3 * end) / 4]], + ]); + + const strategy = positionStrategies.get(numOfCircles); + + return strategy ? strategy(startY, endY) : Array.from({ length: numOfCircles }, (_, i) => startY + i * gap); +}; + +export const drawSubGraph = ( + svgRef: RefObject, + data: ClusterGraphElement[], + detailElementHeight: number +) => { + const allCirclePositions = data.reduce( + (acc, clusterData, index) => { + if (clusterData.selected.current.includes(index)) { + const { startY, endY } = getStartYEndY(clusterData, index, detailElementHeight); + const numOfCircles = clusterData.cluster.commitNodeList.length; + const gap = (endY - startY) / (numOfCircles - 1); + const circlePositions = calculateCirclePositions(numOfCircles, startY, endY, gap); + + const enrichedPositions = circlePositions.map((y, circleIndex) => ({ y, clusterData, circleIndex })); + return acc.concat(enrichedPositions); + } + return acc; + }, + [] as Array<{ y: number; clusterData: ClusterGraphElement; circleIndex: number }> + ); + + const circleRadius = 5; + + d3.select(svgRef.current) + .selectAll(".circle-group") + .data(allCirclePositions) + .join("circle") + .attr("class", "circle-group") + .attr("cx", GRAPH_WIDTH / 2 + 2) + .attr("cy", (d) => d.y) + .attr("r", circleRadius) + .on("mouseover", (_, { clusterData, circleIndex }) => { + const { commitNodeList } = clusterData.cluster; + const targetIndex = commitNodeList.length - 1 - circleIndex; + const info = commitNodeList[targetIndex].commit.message; + tooltip.text(info); + return tooltip.style("visibility", "visible"); + }) + .on("mousemove", (event) => { + return tooltip.style("top", `${event.pageY - 10}px`).style("left", `${event.pageX + 10}px`); + }) + .on("mouseout", () => { + return tooltip.style("visibility", "hidden"); + }); +}; diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/drawTotalLine.ts b/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/drawTotalLine.ts index ea8870e1..71545af5 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/drawTotalLine.ts +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/drawTotalLine.ts @@ -33,5 +33,6 @@ export const drawTotalLine = ( .attr("x2", svgMargin.left + graphWidth / 2) .attr("y2", (d) => d.end + d.selected.prev.length * detailElementHeight) .transition() - .attr("y2", (d) => d.end + d.selected.current.length * detailElementHeight); + .attr("y2", (d) => d.end + d.selected.current.length * detailElementHeight) + .attr("pointer-events", "none"); }; diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/index.ts b/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/index.ts index cd6a89ff..37e96e56 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/index.ts +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/Draws/index.ts @@ -2,3 +2,4 @@ export * from "./drawClusterBox"; export * from "./drawCommitAmountCluster"; export * from "./drawTotalLine"; export * from "./destroyClusterGraph"; +export * from "./drawSubGraph"; diff --git a/packages/view/src/ide/FakeIDEAdapter.ts b/packages/view/src/ide/FakeIDEAdapter.ts index 0527fbc1..290bd347 100644 --- a/packages/view/src/ide/FakeIDEAdapter.ts +++ b/packages/view/src/ide/FakeIDEAdapter.ts @@ -14,13 +14,12 @@ export default class FakeIDEAdapter implements IDEPort { const onReceiveMessage = (e: IDEMessageEvent): void => { const responseMessage = e.data; const { command, payload } = responseMessage; - const payloadData = payload ? JSON.parse(payload) : undefined; switch (command) { case "fetchAnalyzedData": - return events.handleChangeAnalyzedData(payloadData); + return events.handleChangeAnalyzedData(payload ? JSON.parse(payload) : undefined); case "fetchBranchList": - return events.handleChangeBranchList(payloadData); + return events.handleChangeBranchList(payload ? JSON.parse(payload) : undefined); default: console.log("Unknown Message"); } diff --git a/packages/view/tests/home.spec.ts b/packages/view/tests/home.spec.ts index 2eb4d76b..cec3199d 100644 --- a/packages/view/tests/home.spec.ts +++ b/packages/view/tests/home.spec.ts @@ -1,6 +1,7 @@ import { test, expect } from "@playwright/test"; test.describe("home", () => { + const CLICK_INDEX = 0; test.beforeEach(async ({ page }) => { await page.goto("/"); }); @@ -8,4 +9,36 @@ test.describe("home", () => { test("has title", async ({ page }) => { await expect(page).toHaveTitle(/Githru/); }); + + test("when click cluster", async ({ page }) => { + await page.waitForSelector("[data-testid=cluster-graph__container]", { state: "attached" }); + + const childContainers = await page.$$("[data-testid=cluster-graph__container]"); + + if (childContainers.length > CLICK_INDEX) { + await childContainers[CLICK_INDEX].scrollIntoViewIfNeeded(); + await childContainers[CLICK_INDEX].click(); + } else { + throw new Error("No child containers found"); + } + + // waiting for changing + await page.waitForTimeout(1000); + + const newChildContainers = await page.$$("[data-testid=cluster-graph__container]"); + + const targetIndexForCheck = CLICK_INDEX + 1; + const transformPositionForCheck = 10 + targetIndexForCheck * 50 + 220; + if (newChildContainers.length > targetIndexForCheck) { + const transformValue = await newChildContainers[targetIndexForCheck].getAttribute("transform"); + + if (transformValue !== null) { + expect(transformValue).toBe(`translate(2, ${transformPositionForCheck})`); + } else { + throw new Error("Transform attribute not found"); + } + } else { + throw new Error("Not enough child containers found"); + } + }); }); diff --git a/packages/view/tsconfig.json b/packages/view/tsconfig.json index d5670d57..92bbc6d2 100644 --- a/packages/view/tsconfig.json +++ b/packages/view/tsconfig.json @@ -39,7 +39,9 @@ } /* Specify a set of entries that re-map imports to additional lookup locations. */, // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + "types": [ + "@playwright/test" + ] /* Specify type package names to be included without being referenced in a source file. */, // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ "resolveJsonModule": true /* Enable importing .json files. */,