From d35524c20a332e568f50a9f3035918cb73dba4b5 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Wed, 30 Jul 2025 09:02:50 +0200 Subject: [PATCH 01/17] Add Incident details page to IDE launcher --- .../RepositorySidebar/Header/index.tsx | 2 +- .../IncidentDetails/addChatContextFile.ts | 27 ++ .../IdeLauncher/IncidentDetails/index.tsx | 301 ++++++++++++++++++ .../IdeLauncher/ProjectOpener/index.tsx | 265 +++++++++++++++ .../IdeLauncher/ProjectOpener/styles.ts | 10 + .../common/IdeProjectSelect/constants.ts | 1 + .../common/IdeProjectSelect/index.tsx | 20 ++ .../common/IdeProjectSelect/styles.ts | 5 + .../common/IdeProjectSelect/types.ts | 6 + .../utils/getSelectedItemValue.ts | 4 + .../utils/parseSelectedItemValue.ts | 6 + src/components/IdeLauncher/index.tsx | 259 +-------------- .../IdeLauncher/scanRunningIdeProjects.ts | 40 --- .../scanRunningIntellijIdeProjects.ts | 41 +++ .../scanRunningVSCodeIdeProjects.ts | 49 +++ src/components/IdeLauncher/showIdeProject.ts | 4 +- src/components/IdeLauncher/styles.ts | 4 - src/components/IdeLauncher/tracking.ts | 4 +- src/components/IdeLauncher/types.ts | 25 +- src/containers/IdeLauncher/index.tsx | 17 +- src/containers/IdeLauncher/router.tsx | 30 ++ src/containers/IdeLauncher/store.ts | 16 + 22 files changed, 824 insertions(+), 312 deletions(-) create mode 100644 src/components/IdeLauncher/IncidentDetails/addChatContextFile.ts create mode 100644 src/components/IdeLauncher/IncidentDetails/index.tsx create mode 100644 src/components/IdeLauncher/ProjectOpener/index.tsx create mode 100644 src/components/IdeLauncher/ProjectOpener/styles.ts create mode 100644 src/components/IdeLauncher/common/IdeProjectSelect/constants.ts create mode 100644 src/components/IdeLauncher/common/IdeProjectSelect/index.tsx create mode 100644 src/components/IdeLauncher/common/IdeProjectSelect/styles.ts create mode 100644 src/components/IdeLauncher/common/IdeProjectSelect/types.ts create mode 100644 src/components/IdeLauncher/common/IdeProjectSelect/utils/getSelectedItemValue.ts create mode 100644 src/components/IdeLauncher/common/IdeProjectSelect/utils/parseSelectedItemValue.ts delete mode 100644 src/components/IdeLauncher/scanRunningIdeProjects.ts create mode 100644 src/components/IdeLauncher/scanRunningIntellijIdeProjects.ts create mode 100644 src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts create mode 100644 src/containers/IdeLauncher/router.tsx create mode 100644 src/containers/IdeLauncher/store.ts diff --git a/src/components/Admin/common/RepositorySidebarOverlay/RepositorySidebar/Header/index.tsx b/src/components/Admin/common/RepositorySidebarOverlay/RepositorySidebar/Header/index.tsx index dc52187e6..1e26c5b80 100644 --- a/src/components/Admin/common/RepositorySidebarOverlay/RepositorySidebar/Header/index.tsx +++ b/src/components/Admin/common/RepositorySidebarOverlay/RepositorySidebar/Header/index.tsx @@ -100,7 +100,7 @@ export const Header = ({ onGoHome={handleGoHome} /> => { + try { + await axios.post( + `http://localhost:${port}/api/digma/chat/context/file`, + file + ); + return { result: "success" }; + } catch (error) { + return { + result: "failure", + error: { + message: isAxiosError(error) + ? error.message + : "Failed to add file to the chat context" + } + }; + } +}; diff --git a/src/components/IdeLauncher/IncidentDetails/index.tsx b/src/components/IdeLauncher/IncidentDetails/index.tsx new file mode 100644 index 000000000..f3bdb7ab3 --- /dev/null +++ b/src/components/IdeLauncher/IncidentDetails/index.tsx @@ -0,0 +1,301 @@ +import { useCallback, useEffect, useState } from "react"; +import { useParams } from "react-router"; +import { useGetIncidentQuery } from "../../../redux/services/digma"; +import type { GetIncidentResponse } from "../../../redux/services/types"; +import { isString } from "../../../typeGuards/isString"; +import { sendTrackingEvent } from "../../../utils/actions/sendTrackingEvent"; +import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; +import { + Subtitle, + TextContainer, + Title +} from "../../common/GenericPageLayout/styles"; +import { NewButton } from "../../common/v3/NewButton"; +import type { SelectItem } from "../../common/v3/Select/types"; +import { IdeProjectSelect } from "../common/IdeProjectSelect"; +import { getSelectItemValue } from "../common/IdeProjectSelect/utils/getSelectedItemValue"; +import { parseSelectedItemValue } from "../common/IdeProjectSelect/utils/parseSelectedItemValue"; +import { scanRunningVSCodeIdeProjects } from "../scanRunningVSCodeIdeProjects"; +import { ButtonsContainer, EmphasizedText } from "../styles"; +import { trackingEvents } from "../tracking"; +import type { AddChatContextFileResult } from "../types"; +import { addChatContextFile } from "./addChatContextFile"; + +const REFRESH_INTERVAL = 10 * 1000; // in milliseconds + +export const IncidentDetails = () => { + const params = useParams(); + const incidentId = params.id; + const [isIncidentNotFound, setIsIncidentNotFound] = useState(false); + const [selectItems, setSelectItems] = useState(); + const [isIdeProjectScanningInProgress, setIsIdeProjectScanningInProgress] = + useState(false); + const [ + isAddingChatContextFileInProgress, + setAddingChatContextFileInProgress + ] = useState(false); + const [addChatContextFileResult, setAddChatContextFileResult] = + useState(); + + const { + data: incidentData, + isLoading, + error + } = useGetIncidentQuery( + { id: incidentId ?? "" }, + { + pollingInterval: isIncidentNotFound ? 0 : REFRESH_INTERVAL, + skip: !incidentId + } + ); + + const tryToAddChatContextFile = useCallback( + async ( + port: number, + incidentId: string, + incidentData: GetIncidentResponse + ) => { + setAddChatContextFileResult(undefined); + setAddingChatContextFileInProgress(true); + const result = await addChatContextFile(port, { + name: `incident-${incidentId}.json`, + content: JSON.stringify(incidentData, null, 2) + }); + sendTrackingEvent(trackingEvents.IDE_CHAT_CONTEXT_FILE_RESULT_RECEIVED, { + result + }); + setAddChatContextFileResult(result); + setAddingChatContextFileInProgress(false); + }, + [] + ); + + const tryToScanRunningIdeProjects = useCallback(async () => { + setSelectItems(undefined); + setIsIdeProjectScanningInProgress(true); + const result = await scanRunningVSCodeIdeProjects(); + setIsIdeProjectScanningInProgress(false); + + setSelectItems( + result.map((x, i) => ({ + label: `${x.response.ideName} (${x.response.workspace})`, + description: `${x.response.ideName} (${x.response.workspace})`, + value: getSelectItemValue(x.port, x.response.workspace), + enabled: true, + selected: result.length === 1 && i === 0 + })) + ); + }, []); + + useEffect(() => { + if (selectItems && selectItems.length === 1 && incidentId && incidentData) { + void tryToAddChatContextFile( + parseSelectedItemValue(selectItems[0].value).port, + incidentId, + incidentData + ); + } + }, [incidentId, incidentData, tryToAddChatContextFile, selectItems]); + + const handleSelectChange = async (value: string | string[]) => { + sendUserActionTrackingEvent(trackingEvents.IDE_PROJECT_SELECTED); + const selectedValue = isString(value) ? value : value[0]; + const { port } = parseSelectedItemValue(selectedValue); + + if (!selectItems) { + return; + } + + setSelectItems( + selectItems.map((item) => ({ + ...item, + selected: item.value === selectedValue + })) + ); + + if (!incidentId || !incidentData) { + return; + } + + await tryToAddChatContextFile(port, incidentId, incidentData); + }; + + const handleTryScanningAgainButtonClick = () => { + sendUserActionTrackingEvent( + trackingEvents.TRY_SCANNING_AGAIN_BUTTON_CLICKED + ); + window.location.reload(); + }; + + const handleTryShowIdeProjectAgainButtonClick = async () => { + sendUserActionTrackingEvent(trackingEvents.TRY_AGAIN_BUTTON_CLICKED); + const selectedItemValue = selectItems?.find((item) => item.selected)?.value; + if (!selectedItemValue) { + return; + } + + if (!incidentId || !incidentData) { + return; + } + + const { port } = parseSelectedItemValue(selectedItemValue); + await tryToAddChatContextFile(port, incidentId, incidentData); + }; + + // const handleGetDigmaButtonClick = () => { + // sendUserActionTrackingEvent(trackingEvents.GET_DIGMA_BUTTON_CLICKED); + // window.open( + // JETBRAINS_MARKETPLACE_PLUGIN_URL, + // "_blank", + // "noopener noreferrer" + // ); + // }; + + useEffect(() => { + async function initialScan() { + await tryToScanRunningIdeProjects(); + } + + void initialScan(); + }, [tryToScanRunningIdeProjects]); + + useEffect(() => { + setIsIncidentNotFound(false); + }, [incidentId]); + + useEffect(() => { + if (error && "status" in error && error.status === 404) { + setIsIncidentNotFound(true); + } + }, [error]); + + const renderContent = () => { + if (!incidentId) { + return ( + + Incident ID is not provided + + ); + } + + if (!incidentData && isLoading) { + return ( + + Getting incident details + + ); + } + + if (!incidentData && error) { + return ( + + Failed to get incident details + + ); + } + + if (isIdeProjectScanningInProgress) { + return ( + + Searching for a running IDE + + You'll need an IDE installed with Digma configured to open the + link + + + ); + } + + if (isAddingChatContextFileInProgress) { + return ( + + Adding the incident details to the IDE chat context + + ); + } + + if (addChatContextFileResult?.result === "failure") { + return ( + <> + + + There was an issue adding the incident details to the IDE chat + context + + + Please check that IDE is running and click the{" "} + Try again button below. + + + { + void handleTryShowIdeProjectAgainButtonClick(); + }} + /> + + ); + } + + if (addChatContextFileResult?.result === "success") { + return ( + + + Incident details have been added to the IDE chat context + + You can close this tab. + + ); + } + + if (!selectItems) { + return null; + } + + if (selectItems.length === 0) { + return ( + <> + + Unable to open the Digma link + + Opening this link requires a running IDE with Digma installed and + configured. Launch your IDE and install Digma as needed, then + click the Try again button. + + + + + {/* */} + + + ); + } + + // TODO: remove equal check when we have only one IDE project + if (selectItems.length >= 1) { + return ( + <> + + + Select the IDE project to add the incident details to the chat + context + + + void handleSelectChange(value)} + /> + + ); + } + }; + + return <>{renderContent()}; +}; diff --git a/src/components/IdeLauncher/ProjectOpener/index.tsx b/src/components/IdeLauncher/ProjectOpener/index.tsx new file mode 100644 index 000000000..acf95570f --- /dev/null +++ b/src/components/IdeLauncher/ProjectOpener/index.tsx @@ -0,0 +1,265 @@ +import { useCallback, useEffect, useState } from "react"; +import { JETBRAINS_MARKETPLACE_PLUGIN_URL } from "../../../constants"; +import { useStableSearchParams } from "../../../hooks/useStableSearchParams"; +import { isString } from "../../../typeGuards/isString"; +import { sendTrackingEvent } from "../../../utils/actions/sendTrackingEvent"; +import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; +import { GenericPageLayout } from "../../common/GenericPageLayout"; +import { + Subtitle, + TextContainer, + Title +} from "../../common/GenericPageLayout/styles"; +import { NewButton } from "../../common/v3/NewButton"; +import type { SelectItem } from "../../common/v3/Select/types"; +import { IdeProjectSelect } from "../common/IdeProjectSelect"; +import { getSelectItemValue } from "../common/IdeProjectSelect/utils/getSelectedItemValue"; +import { parseSelectedItemValue } from "../common/IdeProjectSelect/utils/parseSelectedItemValue"; +import { scanRunningIntellijIdeProjects } from "../scanRunningIntellijIdeProjects"; +import { showIdeProject } from "../showIdeProject"; +import { trackingEvents } from "../tracking"; +import type { ShowIntellijIdeProjectResult } from "../types"; +import * as s from "./styles"; + +export const ProjectOpener = () => { + const [searchParams] = useStableSearchParams(); + const action = searchParams.get("plugin.action"); + const [selectItems, setSelectItems] = useState(); + const isMobile = ["Android", "iPhone", "iPad"].some((x) => + window.navigator.userAgent.includes(x) + ); + const [isIdeProjectScanningInProgress, setIsIdeProjectScanningInProgress] = + useState(false); + const [isShowIdeProjectInProgress, setIsShowIdeProjectInProgress] = + useState(false); + const [showIdeProjectResult, setShowIdeProjectResult] = + useState(); + + const tryToShowIdeProject = useCallback( + async (port: number, project: string) => { + setShowIdeProjectResult(undefined); + setIsShowIdeProjectInProgress(true); + const result = await showIdeProject( + port, + project, + Object.fromEntries(searchParams) + ); + sendTrackingEvent(trackingEvents.IDE_PROJECT_OPEN_RESULT_RECEIVED, { + result + }); + setShowIdeProjectResult(result); + setIsShowIdeProjectInProgress(false); + }, + [searchParams] + ); + + const tryToScanRunningIdeProjects = useCallback(async () => { + setSelectItems(undefined); + setIsIdeProjectScanningInProgress(true); + const result = await scanRunningIntellijIdeProjects(); + setIsIdeProjectScanningInProgress(false); + + const projects = result + .filter((x) => x.response.isCentralized) + .flatMap((info) => + info.response.openProjects.map((project) => ({ + ...info, + project: project, + port: info.port + })) + ); + + setSelectItems( + projects.map((x, i) => ({ + label: `${x.response.name} (${x.project})`, + description: `${x.response.name} (${x.project})`, + value: getSelectItemValue(x.port, x.project), + enabled: true, + selected: projects.length === 1 && i === 0 + })) + ); + + if (projects.length === 1) { + await tryToShowIdeProject(projects[0].port, projects[0].project); + } + }, [tryToShowIdeProject]); + + const handleSelectChange = async (value: string | string[]) => { + sendUserActionTrackingEvent(trackingEvents.IDE_PROJECT_SELECTED); + const selectedValue = isString(value) ? value : value[0]; + const { port, project } = parseSelectedItemValue(selectedValue); + + if (!selectItems) { + return; + } + + setSelectItems( + selectItems.map((item) => ({ + ...item, + selected: item.value === selectedValue + })) + ); + + await tryToShowIdeProject(port, project); + }; + + const handleTryScanningAgainButtonClick = () => { + sendUserActionTrackingEvent( + trackingEvents.TRY_SCANNING_AGAIN_BUTTON_CLICKED + ); + window.location.reload(); + }; + + const handleTryShowIdeProjectAgainButtonClick = async () => { + sendUserActionTrackingEvent(trackingEvents.TRY_AGAIN_BUTTON_CLICKED); + const selectedItemValue = selectItems?.find((item) => item.selected)?.value; + if (!selectedItemValue) { + return; + } + + const { port, project } = parseSelectedItemValue(selectedItemValue); + await tryToShowIdeProject(port, project); + }; + + const handleGetDigmaButtonClick = () => { + sendUserActionTrackingEvent(trackingEvents.GET_DIGMA_BUTTON_CLICKED); + window.open( + JETBRAINS_MARKETPLACE_PLUGIN_URL, + "_blank", + "noopener noreferrer" + ); + }; + + useEffect(() => { + async function initialScan() { + await tryToScanRunningIdeProjects(); + } + + void initialScan(); + }, [tryToScanRunningIdeProjects]); + + const renderContent = () => { + if (!action) { + return ( + + Invalid link + Link is partial or invalid + + ); + } + + if (isMobile) { + return ( + + Can't open Digma link + Digma links can only be opened on desktop/laptop + + ); + } + + if (isIdeProjectScanningInProgress) { + return ( + + Searching for a running IDE + + You'll need an IDE installed with Digma configured to open the + link + + + ); + } + + if (isShowIdeProjectInProgress) { + return ( + + Opening the Digma link in your IDE + + ); + } + + if (showIdeProjectResult?.result === "failure") { + return ( + <> + + There was an issue opening the link in the IDE + + Please check that IDE is running and click the{" "} + Try again button below. + + + { + void handleTryShowIdeProjectAgainButtonClick(); + }} + /> + + ); + } + + if (showIdeProjectResult?.result === "success") { + return ( + + Opening the Digma link in your IDE + + Switching over to the IDE. You can close this tab. + + + ); + } + + if (!selectItems) { + return null; + } + + if (selectItems.length === 0) { + return ( + <> + + Unable to open the Digma link + + Opening this link requires a running IDE with Digma installed and + configured. Launch your IDE and install Digma as needed, then + click the Try again button. + + + + + + + + ); + } + + if (selectItems.length > 1) { + return ( + <> + + Select the IDE project to view the Digma link + + We'll automatically switch to the IDE once you make a + selection + + + void handleSelectChange(value)} + /> + + ); + } + }; + + return ( + + {renderContent()} + + ); +}; diff --git a/src/components/IdeLauncher/ProjectOpener/styles.ts b/src/components/IdeLauncher/ProjectOpener/styles.ts new file mode 100644 index 000000000..d0009cd58 --- /dev/null +++ b/src/components/IdeLauncher/ProjectOpener/styles.ts @@ -0,0 +1,10 @@ +import styled from "styled-components"; + +export const EmphasizedText = styled.span` + color: ${({ theme }) => theme.colors.v3.text.primary}; +`; + +export const ButtonsContainer = styled.div` + display: flex; + gap: 16px; +`; diff --git a/src/components/IdeLauncher/common/IdeProjectSelect/constants.ts b/src/components/IdeLauncher/common/IdeProjectSelect/constants.ts new file mode 100644 index 000000000..68fd5326b --- /dev/null +++ b/src/components/IdeLauncher/common/IdeProjectSelect/constants.ts @@ -0,0 +1 @@ +export const SELECT_VALUE_DELIMITER = ":"; diff --git a/src/components/IdeLauncher/common/IdeProjectSelect/index.tsx b/src/components/IdeLauncher/common/IdeProjectSelect/index.tsx new file mode 100644 index 000000000..3f65096cc --- /dev/null +++ b/src/components/IdeLauncher/common/IdeProjectSelect/index.tsx @@ -0,0 +1,20 @@ +import { Select } from "../../../common/v3/Select"; +import * as s from "./styles"; +import type { IdeProjectSelectProps } from "./types"; + +export const IdeProjectSelect = ({ + items, + onChange +}: IdeProjectSelectProps) => { + const selectedItem = items?.find((item) => item.selected); + + return ( + + { - void handleSelectChange(value); - }} - /> - - - ); - } + return ; }; return ( diff --git a/src/components/IdeLauncher/scanRunningIdeProjects.ts b/src/components/IdeLauncher/scanRunningIdeProjects.ts deleted file mode 100644 index d65a1bba0..000000000 --- a/src/components/IdeLauncher/scanRunningIdeProjects.ts +++ /dev/null @@ -1,40 +0,0 @@ -import axios from "axios"; -import { isString } from "../../typeGuards/isString"; -import type { IdeScanningResult, PluginInfo } from "./types"; - -const DEFAULT_PORT = 63342; -const PORT_RANGE = 20; -const ABOUT_PATH = "api/digma/about"; - -export const scanRunningIdeProjects = async (): Promise => { - const instances = Array.from( - { length: PORT_RANGE }, - (_, i) => DEFAULT_PORT + i - ).map((port) => ({ - port, - url: `http://localhost:${port}/${ABOUT_PATH}` - })); - - const responses = await Promise.allSettled( - instances.map((x) => - axios - .get(x.url) - .then((response) => ({ port: x.port, response: response.data })) - .catch((error) => ({ - port: x.port, - response: axios.isAxiosError(error) - ? `${error.message}` - : "Unknown error" - })) - ) - ); - - const successfulResponses = responses.filter( - (x) => x.status === "fulfilled" && !isString(x.value) - ) as unknown as PromiseFulfilledResult<{ - port: number; - response: PluginInfo; - }>[]; - - return successfulResponses.map((x) => x.value); -}; diff --git a/src/components/IdeLauncher/scanRunningIntellijIdeProjects.ts b/src/components/IdeLauncher/scanRunningIntellijIdeProjects.ts new file mode 100644 index 000000000..afe3e0852 --- /dev/null +++ b/src/components/IdeLauncher/scanRunningIntellijIdeProjects.ts @@ -0,0 +1,41 @@ +import axios from "axios"; +import { isString } from "../../typeGuards/isString"; +import type { IntellijIdeScanningResult, IntellijPluginInfo } from "./types"; + +const DEFAULT_PORT = 63342; +const PORT_RANGE = 20; +const ABOUT_PATH = "api/digma/about"; + +export const scanRunningIntellijIdeProjects = + async (): Promise => { + const instances = Array.from( + { length: PORT_RANGE }, + (_, i) => DEFAULT_PORT + i + ).map((port) => ({ + port, + url: `http://localhost:${port}/${ABOUT_PATH}` + })); + + const responses = await Promise.allSettled( + instances.map((x) => + axios + .get(x.url) + .then((response) => ({ port: x.port, response: response.data })) + .catch((error) => ({ + port: x.port, + response: axios.isAxiosError(error) + ? `${error.message}` + : "Unknown error" + })) + ) + ); + + const successfulResponses = responses.filter( + (x) => x.status === "fulfilled" && !isString(x.value) + ) as unknown as PromiseFulfilledResult<{ + port: number; + response: IntellijPluginInfo; + }>[]; + + return successfulResponses.map((x) => x.value); + }; diff --git a/src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts b/src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts new file mode 100644 index 000000000..e5873962c --- /dev/null +++ b/src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts @@ -0,0 +1,49 @@ +import axios from "axios"; +import { isString } from "../../typeGuards/isString"; +import type { + IntellijPluginInfo, + VSCodeExtensionInfo, + VSCodeIdeScanningResult +} from "./types"; + +const PORT_RANGES_TO_SCAN = [ + [33100, 33119], // VS Code + [33200, 33219], // Cursor + [33300, 33319], // Windsurf + [33400, 33419] // Unknown VS Code fork +]; + +const ABOUT_PATH = "api/digma/about"; + +export const scanRunningVSCodeIdeProjects = + async (): Promise => { + const instances = PORT_RANGES_TO_SCAN.flatMap(([start, end]) => + Array.from({ length: end - start + 1 }, (_, i) => start + i) + ).map((port) => ({ + port, + url: `http://localhost:${port}/${ABOUT_PATH}` + })); + + const responses = await Promise.allSettled( + instances.map((x) => + axios + .get(x.url) + .then((response) => ({ port: x.port, response: response.data })) + .catch((error) => ({ + port: x.port, + response: axios.isAxiosError(error) + ? `${error.message}` + : "Unknown error" + })) + ) + ); + + const successfulResponses = responses.filter( + (x) => x.status === "fulfilled" && !isString(x.value.response) + ) as unknown as PromiseFulfilledResult<{ + port: number; + response: VSCodeExtensionInfo; + }>[]; + + return successfulResponses.map((x) => x.value); + }; diff --git a/src/components/IdeLauncher/showIdeProject.ts b/src/components/IdeLauncher/showIdeProject.ts index 09b37f93a..9dc4911b2 100644 --- a/src/components/IdeLauncher/showIdeProject.ts +++ b/src/components/IdeLauncher/showIdeProject.ts @@ -1,11 +1,11 @@ import axios, { isAxiosError } from "axios"; -import type { ShowIdeProjectResult } from "./types"; +import type { ShowIntellijIdeProjectResult } from "./types"; export const showIdeProject = async ( port: number, project: string, params: Record -): Promise => { +): Promise => { const pluginParams = Object.entries(params).reduce( (acc, [key, value]) => { const KEY_PREFIX = "plugin."; diff --git a/src/components/IdeLauncher/styles.ts b/src/components/IdeLauncher/styles.ts index a62dc50d0..d0009cd58 100644 --- a/src/components/IdeLauncher/styles.ts +++ b/src/components/IdeLauncher/styles.ts @@ -8,7 +8,3 @@ export const ButtonsContainer = styled.div` display: flex; gap: 16px; `; - -export const SelectContainer = styled.div` - width: 560px; -`; diff --git a/src/components/IdeLauncher/tracking.ts b/src/components/IdeLauncher/tracking.ts index b1f6ab32f..f01429654 100644 --- a/src/components/IdeLauncher/tracking.ts +++ b/src/components/IdeLauncher/tracking.ts @@ -8,7 +8,9 @@ export const trackingEvents = addPrefix( TRY_SCANNING_AGAIN_BUTTON_CLICKED: "try scanning again button clicked", TRY_AGAIN_BUTTON_CLICKED: "try again button clicked", GET_DIGMA_BUTTON_CLICKED: "get digma button clicked", - IDE_PROJECT_OPEN_RESULT_RECEIVED: "ide project open result received" + IDE_PROJECT_OPEN_RESULT_RECEIVED: "ide project open result received", + IDE_CHAT_CONTEXT_FILE_RESULT_RECEIVED: + "ide chat context file result received" }, " " ); diff --git a/src/components/IdeLauncher/types.ts b/src/components/IdeLauncher/types.ts index 081acf0c3..d56d7ba5e 100644 --- a/src/components/IdeLauncher/types.ts +++ b/src/components/IdeLauncher/types.ts @@ -1,4 +1,4 @@ -export interface PluginInfo { +export interface IntellijPluginInfo { name: string; productName: string; edition: string; @@ -11,9 +11,28 @@ export interface PluginInfo { openProjects: string[]; } -export interface ShowIdeProjectResult { +export interface VSCodeExtensionInfo { + ideName: string; + ideVersion: string; + workspace: string; +} + +export interface ShowIntellijIdeProjectResult { result: "success" | "failure"; error?: { message: string }; } -export type IdeScanningResult = { port: number; response: PluginInfo }[]; +export interface AddChatContextFileResult { + result: "success" | "failure"; + error?: { message: string }; +} + +export type IntellijIdeScanningResult = { + port: number; + response: IntellijPluginInfo; +}[]; + +export type VSCodeIdeScanningResult = { + port: number; + response: VSCodeExtensionInfo; +}[]; diff --git a/src/containers/IdeLauncher/index.tsx b/src/containers/IdeLauncher/index.tsx index 7c632672f..ecac50938 100644 --- a/src/containers/IdeLauncher/index.tsx +++ b/src/containers/IdeLauncher/index.tsx @@ -1,11 +1,15 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import { Provider } from "react-redux"; +import { RouterProvider } from "react-router"; +import { sendMessage } from "../../api"; import { App } from "../../components/common/App"; import { PostHogHoC } from "../../components/common/PostHogHoC"; -import { IdeLauncher } from "../../components/IdeLauncher"; import posthog from "../../posthog"; import { handleUncaughtError } from "../../utils/handleUncaughtError"; import { APP_ID } from "./constants"; +import { router } from "./router"; +import { store } from "./store"; posthog?.register({ app: APP_ID }); @@ -13,6 +17,9 @@ window.addEventListener("error", (e) => { handleUncaughtError(APP_ID, e); }); +// TODO: make not required and remove +window.sendMessageToDigma = sendMessage; + const rootElement = document.getElementById("root"); if (rootElement) { @@ -20,9 +27,11 @@ if (rootElement) { root.render( - - - + + + + + ); diff --git a/src/containers/IdeLauncher/router.tsx b/src/containers/IdeLauncher/router.tsx new file mode 100644 index 000000000..d955010c9 --- /dev/null +++ b/src/containers/IdeLauncher/router.tsx @@ -0,0 +1,30 @@ +import type { RouteObject } from "react-router"; +import { createBrowserRouter, useRouteError } from "react-router"; +import { IdeLauncher } from "../../components/IdeLauncher"; +import { IncidentDetails } from "../../components/IdeLauncher/IncidentDetails"; +import { ProjectOpener } from "../../components/IdeLauncher/ProjectOpener"; + +export const routes: RouteObject[] = [ + { + path: "/", + element: , + ErrorBoundary: () => { + throw useRouteError(); + }, + children: [ + { + index: true, + element: + }, + { + path: "incidents/:id", + element: + } + ] + } +]; + +const basename = + document.querySelector("base")?.getAttribute("href") ?? undefined; + +export const router = createBrowserRouter(routes, { basename }); diff --git a/src/containers/IdeLauncher/store.ts b/src/containers/IdeLauncher/store.ts new file mode 100644 index 000000000..50e558be9 --- /dev/null +++ b/src/containers/IdeLauncher/store.ts @@ -0,0 +1,16 @@ +import { configureStore } from "@reduxjs/toolkit"; +import { setupListeners } from "@reduxjs/toolkit/query"; +import { digmaApi } from "../../redux/services/digma"; + +export const store = configureStore({ + reducer: { + [digmaApi.reducerPath]: digmaApi.reducer + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().concat(digmaApi.middleware) +}); + +setupListeners(store.dispatch); + +export type IdeLauncherRootState = ReturnType; +export type IdeLauncherDispatch = typeof store.dispatch; From 90a66889997fdd9dc577d9403e93ba84ed96e132 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Jul 2025 07:04:24 +0000 Subject: [PATCH 02/17] Bump version to 16.7.0-alpha.0 [skip ci] --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa96a6cc9..9b0037191 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "digma-ui", - "version": "16.6.2", + "version": "16.7.0-alpha.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "digma-ui", - "version": "16.6.2", + "version": "16.7.0-alpha.0", "license": "MIT", "dependencies": { "@codemirror/lang-json": "^6.0.2", diff --git a/package.json b/package.json index 960a3026b..cbc21293a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "digma-ui", - "version": "16.6.2", + "version": "16.7.0-alpha.0", "description": "Digma UI", "scripts": { "lint:eslint": "eslint --cache .", From 6a9732cfde868e7994626a9066bbe18082f23d67 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Wed, 30 Jul 2025 09:23:00 +0200 Subject: [PATCH 03/17] Improve file name format and remove data polling --- .../IdeLauncher/IncidentDetails/index.tsx | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/components/IdeLauncher/IncidentDetails/index.tsx b/src/components/IdeLauncher/IncidentDetails/index.tsx index f3bdb7ab3..f2637992d 100644 --- a/src/components/IdeLauncher/IncidentDetails/index.tsx +++ b/src/components/IdeLauncher/IncidentDetails/index.tsx @@ -1,3 +1,4 @@ +import { formatISO } from "date-fns"; import { useCallback, useEffect, useState } from "react"; import { useParams } from "react-router"; import { useGetIncidentQuery } from "../../../redux/services/digma"; @@ -21,12 +22,9 @@ import { trackingEvents } from "../tracking"; import type { AddChatContextFileResult } from "../types"; import { addChatContextFile } from "./addChatContextFile"; -const REFRESH_INTERVAL = 10 * 1000; // in milliseconds - export const IncidentDetails = () => { const params = useParams(); const incidentId = params.id; - const [isIncidentNotFound, setIsIncidentNotFound] = useState(false); const [selectItems, setSelectItems] = useState(); const [isIdeProjectScanningInProgress, setIsIdeProjectScanningInProgress] = useState(false); @@ -44,7 +42,6 @@ export const IncidentDetails = () => { } = useGetIncidentQuery( { id: incidentId ?? "" }, { - pollingInterval: isIncidentNotFound ? 0 : REFRESH_INTERVAL, skip: !incidentId } ); @@ -58,7 +55,7 @@ export const IncidentDetails = () => { setAddChatContextFileResult(undefined); setAddingChatContextFileInProgress(true); const result = await addChatContextFile(port, { - name: `incident-${incidentId}.json`, + name: `incident-${incidentId}-${formatISO(new Date(), { format: "basic" })}.json`, content: JSON.stringify(incidentData, null, 2) }); sendTrackingEvent(trackingEvents.IDE_CHAT_CONTEXT_FILE_RESULT_RECEIVED, { @@ -159,16 +156,6 @@ export const IncidentDetails = () => { void initialScan(); }, [tryToScanRunningIdeProjects]); - useEffect(() => { - setIsIncidentNotFound(false); - }, [incidentId]); - - useEffect(() => { - if (error && "status" in error && error.status === 404) { - setIsIncidentNotFound(true); - } - }, [error]); - const renderContent = () => { if (!incidentId) { return ( @@ -278,8 +265,7 @@ export const IncidentDetails = () => { ); } - // TODO: remove equal check when we have only one IDE project - if (selectItems.length >= 1) { + if (selectItems.length > 1) { return ( <> From 7dd14b91f00a8f63d6e121c7e154dfa96720e618 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Jul 2025 08:45:48 +0000 Subject: [PATCH 04/17] Bump version to 16.7.0-alpha.1 [skip ci] --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b0037191..b4fe9f6f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "digma-ui", - "version": "16.7.0-alpha.0", + "version": "16.7.0-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "digma-ui", - "version": "16.7.0-alpha.0", + "version": "16.7.0-alpha.1", "license": "MIT", "dependencies": { "@codemirror/lang-json": "^6.0.2", diff --git a/package.json b/package.json index cbc21293a..87ac85d16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "digma-ui", - "version": "16.7.0-alpha.0", + "version": "16.7.0-alpha.1", "description": "Digma UI", "scripts": { "lint:eslint": "eslint --cache .", From 5b64b03aab2fb29e0f7255013af86b6da1c3c607 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 1 Aug 2025 20:51:17 +0200 Subject: [PATCH 05/17] Use links with custom scheme --- .../IncidentDetails/addChatContextFile.ts | 32 +--- .../IdeLauncher/IncidentDetails/index.tsx | 156 +++++------------- .../scanRunningVSCodeIdeProjects.ts | 13 +- src/components/IdeLauncher/types.ts | 1 + webpack.dev.ts | 26 ++- 5 files changed, 61 insertions(+), 167 deletions(-) diff --git a/src/components/IdeLauncher/IncidentDetails/addChatContextFile.ts b/src/components/IdeLauncher/IncidentDetails/addChatContextFile.ts index 5f7ce926d..6810832cf 100644 --- a/src/components/IdeLauncher/IncidentDetails/addChatContextFile.ts +++ b/src/components/IdeLauncher/IncidentDetails/addChatContextFile.ts @@ -1,27 +1,7 @@ -import axios, { isAxiosError } from "axios"; -import type { AddChatContextFileResult } from "../types"; - -export const addChatContextFile = async ( - port: number, - file: { - name: string; - content: string; - } -): Promise => { - try { - await axios.post( - `http://localhost:${port}/api/digma/chat/context/file`, - file - ); - return { result: "success" }; - } catch (error) { - return { - result: "failure", - error: { - message: isAxiosError(error) - ? error.message - : "Failed to add file to the chat context" - } - }; - } +export const addChatContextIncidentFile = ( + ideUriScheme: string, + incidentId: string +): void => { + const url = `${ideUriScheme}://digma.digma/chat/context/add/file/incident/${incidentId}`; + window.open(url, "_blank", "noopener noreferrer"); }; diff --git a/src/components/IdeLauncher/IncidentDetails/index.tsx b/src/components/IdeLauncher/IncidentDetails/index.tsx index f2637992d..2944f4415 100644 --- a/src/components/IdeLauncher/IncidentDetails/index.tsx +++ b/src/components/IdeLauncher/IncidentDetails/index.tsx @@ -1,11 +1,9 @@ -import { formatISO } from "date-fns"; import { useCallback, useEffect, useState } from "react"; import { useParams } from "react-router"; -import { useGetIncidentQuery } from "../../../redux/services/digma"; -import type { GetIncidentResponse } from "../../../redux/services/types"; +import { usePrevious } from "../../../hooks/usePrevious"; import { isString } from "../../../typeGuards/isString"; -import { sendTrackingEvent } from "../../../utils/actions/sendTrackingEvent"; import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; +import { uniqueBy } from "../../../utils/uniqueBy"; import { Subtitle, TextContainer, @@ -14,13 +12,10 @@ import { import { NewButton } from "../../common/v3/NewButton"; import type { SelectItem } from "../../common/v3/Select/types"; import { IdeProjectSelect } from "../common/IdeProjectSelect"; -import { getSelectItemValue } from "../common/IdeProjectSelect/utils/getSelectedItemValue"; -import { parseSelectedItemValue } from "../common/IdeProjectSelect/utils/parseSelectedItemValue"; import { scanRunningVSCodeIdeProjects } from "../scanRunningVSCodeIdeProjects"; import { ButtonsContainer, EmphasizedText } from "../styles"; import { trackingEvents } from "../tracking"; -import type { AddChatContextFileResult } from "../types"; -import { addChatContextFile } from "./addChatContextFile"; +import { addChatContextIncidentFile } from "./addChatContextFile"; export const IncidentDetails = () => { const params = useParams(); @@ -28,41 +23,19 @@ export const IncidentDetails = () => { const [selectItems, setSelectItems] = useState(); const [isIdeProjectScanningInProgress, setIsIdeProjectScanningInProgress] = useState(false); + const previousIsProjectScanningInProgress = usePrevious( + isIdeProjectScanningInProgress + ); const [ isAddingChatContextFileInProgress, setAddingChatContextFileInProgress ] = useState(false); - const [addChatContextFileResult, setAddChatContextFileResult] = - useState(); - - const { - data: incidentData, - isLoading, - error - } = useGetIncidentQuery( - { id: incidentId ?? "" }, - { - skip: !incidentId - } - ); const tryToAddChatContextFile = useCallback( - async ( - port: number, - incidentId: string, - incidentData: GetIncidentResponse - ) => { - setAddChatContextFileResult(undefined); + (ideUriScheme: string, incidentId: string) => { setAddingChatContextFileInProgress(true); - const result = await addChatContextFile(port, { - name: `incident-${incidentId}-${formatISO(new Date(), { format: "basic" })}.json`, - content: JSON.stringify(incidentData, null, 2) - }); - sendTrackingEvent(trackingEvents.IDE_CHAT_CONTEXT_FILE_RESULT_RECEIVED, { - result - }); - setAddChatContextFileResult(result); - setAddingChatContextFileInProgress(false); + + addChatContextIncidentFile(ideUriScheme, incidentId); }, [] ); @@ -73,31 +46,43 @@ export const IncidentDetails = () => { const result = await scanRunningVSCodeIdeProjects(); setIsIdeProjectScanningInProgress(false); + const ides = uniqueBy( + result.map((x) => x.response), + "ideUriScheme" + ); + setSelectItems( - result.map((x, i) => ({ - label: `${x.response.ideName} (${x.response.workspace})`, - description: `${x.response.ideName} (${x.response.workspace})`, - value: getSelectItemValue(x.port, x.response.workspace), + ides.map((x, i) => ({ + label: x.ideName, + description: x.ideName, + value: x.ideUriScheme, enabled: true, selected: result.length === 1 && i === 0 })) ); }, []); + // Automatically select the first IDE if there is only one available useEffect(() => { - if (selectItems && selectItems.length === 1 && incidentId && incidentData) { - void tryToAddChatContextFile( - parseSelectedItemValue(selectItems[0].value).port, - incidentId, - incidentData - ); - } - }, [incidentId, incidentData, tryToAddChatContextFile, selectItems]); - - const handleSelectChange = async (value: string | string[]) => { + if ( + previousIsProjectScanningInProgress && + !isIdeProjectScanningInProgress && + selectItems?.length === 1 && + incidentId + ) { + tryToAddChatContextFile(selectItems[0].value, incidentId); + } + }, [ + incidentId, + isIdeProjectScanningInProgress, + previousIsProjectScanningInProgress, + tryToAddChatContextFile, + selectItems + ]); + + const handleSelectChange = (value: string | string[]) => { sendUserActionTrackingEvent(trackingEvents.IDE_PROJECT_SELECTED); const selectedValue = isString(value) ? value : value[0]; - const { port } = parseSelectedItemValue(selectedValue); if (!selectItems) { return; @@ -110,11 +95,11 @@ export const IncidentDetails = () => { })) ); - if (!incidentId || !incidentData) { + if (!incidentId) { return; } - await tryToAddChatContextFile(port, incidentId, incidentData); + tryToAddChatContextFile(selectedValue, incidentId); }; const handleTryScanningAgainButtonClick = () => { @@ -124,21 +109,6 @@ export const IncidentDetails = () => { window.location.reload(); }; - const handleTryShowIdeProjectAgainButtonClick = async () => { - sendUserActionTrackingEvent(trackingEvents.TRY_AGAIN_BUTTON_CLICKED); - const selectedItemValue = selectItems?.find((item) => item.selected)?.value; - if (!selectedItemValue) { - return; - } - - if (!incidentId || !incidentData) { - return; - } - - const { port } = parseSelectedItemValue(selectedItemValue); - await tryToAddChatContextFile(port, incidentId, incidentData); - }; - // const handleGetDigmaButtonClick = () => { // sendUserActionTrackingEvent(trackingEvents.GET_DIGMA_BUTTON_CLICKED); // window.open( @@ -165,22 +135,6 @@ export const IncidentDetails = () => { ); } - if (!incidentData && isLoading) { - return ( - - Getting incident details - - ); - } - - if (!incidentData && error) { - return ( - - Failed to get incident details - - ); - } - if (isIdeProjectScanningInProgress) { return ( @@ -201,40 +155,6 @@ export const IncidentDetails = () => { ); } - if (addChatContextFileResult?.result === "failure") { - return ( - <> - - - There was an issue adding the incident details to the IDE chat - context - - - Please check that IDE is running and click the{" "} - Try again button below. - - - { - void handleTryShowIdeProjectAgainButtonClick(); - }} - /> - - ); - } - - if (addChatContextFileResult?.result === "success") { - return ( - - - Incident details have been added to the IDE chat context - - You can close this tab. - - ); - } - if (!selectItems) { return null; } diff --git a/src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts b/src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts index e5873962c..dab4ce224 100644 --- a/src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts +++ b/src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts @@ -6,19 +6,16 @@ import type { VSCodeIdeScanningResult } from "./types"; -const PORT_RANGES_TO_SCAN = [ - [33100, 33119], // VS Code - [33200, 33219], // Cursor - [33300, 33319], // Windsurf - [33400, 33419] // Unknown VS Code fork -]; +const START_PORT_TO_SCAN = 33100; +const END_PORT_TO_SCAN = 33119; const ABOUT_PATH = "api/digma/about"; export const scanRunningVSCodeIdeProjects = async (): Promise => { - const instances = PORT_RANGES_TO_SCAN.flatMap(([start, end]) => - Array.from({ length: end - start + 1 }, (_, i) => start + i) + const instances = Array.from( + { length: END_PORT_TO_SCAN - START_PORT_TO_SCAN + 1 }, + (_, i) => START_PORT_TO_SCAN + i ).map((port) => ({ port, url: `http://localhost:${port}/${ABOUT_PATH}` diff --git a/src/components/IdeLauncher/types.ts b/src/components/IdeLauncher/types.ts index d56d7ba5e..d84a58864 100644 --- a/src/components/IdeLauncher/types.ts +++ b/src/components/IdeLauncher/types.ts @@ -13,6 +13,7 @@ export interface IntellijPluginInfo { export interface VSCodeExtensionInfo { ideName: string; + ideUriScheme: string; ideVersion: string; workspace: string; } diff --git a/webpack.dev.ts b/webpack.dev.ts index 3fc78ce6d..bd89608dc 100644 --- a/webpack.dev.ts +++ b/webpack.dev.ts @@ -44,26 +44,22 @@ const apiProxyClient = axios.create({ }); const login = async (credentials: Credentials) => { - const response = await apiProxyClient.post<{ - accessToken: string; - refreshToken: string; - expiration: string; - userId: string; - }>("/authentication/login", credentials); + const response = await apiProxyClient.post( + "/authentication/login", + credentials + ); return response.data; }; const refreshToken = async (session: Session) => { - const response = await apiProxyClient.post<{ - accessToken: string; - refreshToken: string; - expiration: string; - userId: string; - }>("/authentication/refresh-token", { - accessToken: session.accessToken, - refreshToken: session.refreshToken - }); + const response = await apiProxyClient.post( + "/authentication/refresh-token", + { + accessToken: session.accessToken, + refreshToken: session.refreshToken + } + ); return response.data; }; From 2d1f76e28b5fd1a885fa91cda7926d948b47addd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 1 Aug 2025 19:04:20 +0000 Subject: [PATCH 06/17] Bump version to 16.7.0-alpha.2 [skip ci] --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b4fe9f6f2..c6a2a52f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "digma-ui", - "version": "16.7.0-alpha.1", + "version": "16.7.0-alpha.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "digma-ui", - "version": "16.7.0-alpha.1", + "version": "16.7.0-alpha.2", "license": "MIT", "dependencies": { "@codemirror/lang-json": "^6.0.2", diff --git a/package.json b/package.json index 87ac85d16..3a6c2b28d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "digma-ui", - "version": "16.7.0-alpha.1", + "version": "16.7.0-alpha.2", "description": "Digma UI", "scripts": { "lint:eslint": "eslint --cache .", From a8ad0b534f315982aa10351bb9babf2a60bbad71 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 4 Aug 2025 18:59:32 +0200 Subject: [PATCH 07/17] Add IDE buttons to Incident page --- .../IdeToolbar}/addChatContextFile.ts | 0 .../IncidentMetaData/IdeToolbar/index.tsx | 80 +++++++ .../IncidentMetaData/IdeToolbar/styles.ts | 7 + .../IncidentMetaData/IdeToolbar/types.ts | 3 + .../IncidentMetaData/index.tsx | 41 ++-- .../IncidentMetaData/styles.ts | 18 +- src/components/Agentic/tracking.ts | 1 + .../IdeLauncher/IncidentDetails/index.tsx | 207 ------------------ .../common/icons/100px/VSCodeLogoIcon.tsx | 123 +++++++++++ .../common/icons/24px/CursorLogoIcon.tsx | 70 ++++++ src/containers/IdeLauncher/router.tsx | 5 - 11 files changed, 317 insertions(+), 238 deletions(-) rename src/components/{IdeLauncher/IncidentDetails => Agentic/IncidentDetails/IncidentMetaData/IdeToolbar}/addChatContextFile.ts (100%) create mode 100644 src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx create mode 100644 src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/styles.ts create mode 100644 src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/types.ts delete mode 100644 src/components/IdeLauncher/IncidentDetails/index.tsx create mode 100644 src/components/common/icons/100px/VSCodeLogoIcon.tsx create mode 100644 src/components/common/icons/24px/CursorLogoIcon.tsx diff --git a/src/components/IdeLauncher/IncidentDetails/addChatContextFile.ts b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/addChatContextFile.ts similarity index 100% rename from src/components/IdeLauncher/IncidentDetails/addChatContextFile.ts rename to src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/addChatContextFile.ts diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx new file mode 100644 index 000000000..dfcfb013a --- /dev/null +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx @@ -0,0 +1,80 @@ +import { useEffect, useState, type ComponentType } from "react"; +import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent"; +import { uniqueBy } from "../../../../../utils/uniqueBy"; +import { scanRunningVSCodeIdeProjects } from "../../../../IdeLauncher/scanRunningVSCodeIdeProjects"; +import type { VSCodeExtensionInfo } from "../../../../IdeLauncher/types"; +import { VSCodeLogoIcon } from "../../../../common/icons/100px/VSCodeLogoIcon"; +import { CursorLogoIcon } from "../../../../common/icons/24px/CursorLogoIcon"; +import type { IconProps } from "../../../../common/icons/types"; +import { NewIconButton } from "../../../../common/v3/NewIconButton"; +import { Tooltip } from "../../../../common/v3/Tooltip"; +import { trackingEvents } from "../../../tracking"; +import { addChatContextIncidentFile } from "./addChatContextFile"; +import * as s from "./styles"; +import type { IdeToolbarProps } from "./types"; + +const IDE_ICONS: Record> = { + cursor: CursorLogoIcon, + vscode: VSCodeLogoIcon +}; + +export const IdeToolbar = ({ incidentId }: IdeToolbarProps) => { + const [ides, setIdes] = useState(); + + const handleIdeButtonClick = (ide: string) => { + sendUserActionTrackingEvent(trackingEvents.INCIDENT_IDE_BUTTON_CLICKED, { + ide + }); + addChatContextIncidentFile(ide, incidentId); + }; + + useEffect(() => { + const scan = async () => { + try { + const results = await scanRunningVSCodeIdeProjects(); + const ides = uniqueBy( + results.map((x) => x.response), + "ideUriScheme" + ); + setIdes(ides); + } catch { + setIdes([]); + } + }; + + void scan(); + }, []); + + if (!ides || ides.length === 0) { + return null; + } + + return ( + + {ides + ?.map((ide) => { + const IdeIcon = IDE_ICONS[ide.ideUriScheme]; + + if (!IdeIcon) { + return null; + } + + return ( + + } + onClick={() => handleIdeButtonClick(ide.ideUriScheme)} + /> + + ); + }) + .filter(Boolean)} + + ); +}; diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/styles.ts b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/styles.ts new file mode 100644 index 000000000..8d20cee7c --- /dev/null +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/styles.ts @@ -0,0 +1,7 @@ +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/types.ts b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/types.ts new file mode 100644 index 000000000..63a7b2daa --- /dev/null +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/types.ts @@ -0,0 +1,3 @@ +export interface IdeToolbarProps { + incidentId: string; +} diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/index.tsx b/src/components/Agentic/IncidentDetails/IncidentMetaData/index.tsx index d6bff71da..5d0ff676b 100644 --- a/src/components/Agentic/IncidentDetails/IncidentMetaData/index.tsx +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/index.tsx @@ -12,10 +12,12 @@ import { import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; import { intersperse } from "../../../../utils/intersperse"; import { InfoCircleIcon } from "../../../common/icons/InfoCircleIcon"; +import { NewButton } from "../../../common/v3/NewButton"; import { NewIconButton } from "../../../common/v3/NewIconButton"; import { Tooltip } from "../../../common/v3/Tooltip"; import { trackingEvents } from "../../tracking"; import { Divider } from "./Divider"; +import { IdeToolbar } from "./IdeToolbar"; import * as s from "./styles"; const DATE_FORMAT = "dd MMM, yyyy HH:mm"; @@ -194,24 +196,27 @@ export const IncidentMetaData = () => { return ( {attributes} - {data.status === "active" && ( - - )} - {data.status === "pending" && ( - - )} - {["error", "closed", "canceled"].includes(data.status) && ( - - )} + + {incidentId && } + {data.status === "active" && ( + + )} + {data.status === "pending" && ( + + )} + {["error", "closed", "canceled"].includes(data.status) && ( + + )} + ); }; diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/styles.ts b/src/components/Agentic/IncidentDetails/IncidentMetaData/styles.ts index 40a4ef50a..253c40c18 100644 --- a/src/components/Agentic/IncidentDetails/IncidentMetaData/styles.ts +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/styles.ts @@ -1,6 +1,5 @@ import styled from "styled-components"; import { subheading1RegularTypography } from "../../../common/App/typographies"; -import { NewButton } from "../../../common/v3/NewButton"; export const Container = styled.div` display: flex; @@ -14,6 +13,7 @@ export const AttributesList = styled.div` display: flex; flex-wrap: wrap; align-items: center; + margin-right: auto; `; export const DividerContainer = styled.div` @@ -22,13 +22,6 @@ export const DividerContainer = styled.div` display: flex; `; -export const CloseIncidentButton = styled(NewButton)` - flex-shrink: 0; - margin-top: 12px; - margin-left: auto; - margin-right: 16px; -`; - export const Attribute = styled.div` ${subheading1RegularTypography} display: flex; @@ -77,3 +70,12 @@ export const HiddenServicesCountTag = styled(Tag)` border: 1px solid ${({ theme }) => theme.colors.v3.stroke.primary}; color: ${({ theme }) => theme.colors.v3.text.secondary}; `; + +export const Toolbar = styled.div` + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; + margin-top: 12px; + margin-right: 16px; +`; diff --git a/src/components/Agentic/tracking.ts b/src/components/Agentic/tracking.ts index 684262cf4..b1f046c6c 100644 --- a/src/components/Agentic/tracking.ts +++ b/src/components/Agentic/tracking.ts @@ -16,6 +16,7 @@ export const trackingEvents = addPrefix( INCIDENT_CANCEL_BUTTON_CLICKED: "incident cancel button clicked", INCIDENT_CLOSE_BUTTON_CLICKED: "incident close button clicked", INCIDENT_DELETE_BUTTON_CLICKED: "incident delete button clicked", + INCIDENT_IDE_BUTTON_CLICKED: "incident IDE button clicked", FLOW_CHART_NODE_CLICKED: "flow chart node clicked", FLOW_CHART_NODE_KEBAB_MENU_CLICKED: "flow chart node kebab menu clicked", FLOW_CHART_NODE_KEBAB_MENU_ITEM_CLICKED: diff --git a/src/components/IdeLauncher/IncidentDetails/index.tsx b/src/components/IdeLauncher/IncidentDetails/index.tsx deleted file mode 100644 index 2944f4415..000000000 --- a/src/components/IdeLauncher/IncidentDetails/index.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { useParams } from "react-router"; -import { usePrevious } from "../../../hooks/usePrevious"; -import { isString } from "../../../typeGuards/isString"; -import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; -import { uniqueBy } from "../../../utils/uniqueBy"; -import { - Subtitle, - TextContainer, - Title -} from "../../common/GenericPageLayout/styles"; -import { NewButton } from "../../common/v3/NewButton"; -import type { SelectItem } from "../../common/v3/Select/types"; -import { IdeProjectSelect } from "../common/IdeProjectSelect"; -import { scanRunningVSCodeIdeProjects } from "../scanRunningVSCodeIdeProjects"; -import { ButtonsContainer, EmphasizedText } from "../styles"; -import { trackingEvents } from "../tracking"; -import { addChatContextIncidentFile } from "./addChatContextFile"; - -export const IncidentDetails = () => { - const params = useParams(); - const incidentId = params.id; - const [selectItems, setSelectItems] = useState(); - const [isIdeProjectScanningInProgress, setIsIdeProjectScanningInProgress] = - useState(false); - const previousIsProjectScanningInProgress = usePrevious( - isIdeProjectScanningInProgress - ); - const [ - isAddingChatContextFileInProgress, - setAddingChatContextFileInProgress - ] = useState(false); - - const tryToAddChatContextFile = useCallback( - (ideUriScheme: string, incidentId: string) => { - setAddingChatContextFileInProgress(true); - - addChatContextIncidentFile(ideUriScheme, incidentId); - }, - [] - ); - - const tryToScanRunningIdeProjects = useCallback(async () => { - setSelectItems(undefined); - setIsIdeProjectScanningInProgress(true); - const result = await scanRunningVSCodeIdeProjects(); - setIsIdeProjectScanningInProgress(false); - - const ides = uniqueBy( - result.map((x) => x.response), - "ideUriScheme" - ); - - setSelectItems( - ides.map((x, i) => ({ - label: x.ideName, - description: x.ideName, - value: x.ideUriScheme, - enabled: true, - selected: result.length === 1 && i === 0 - })) - ); - }, []); - - // Automatically select the first IDE if there is only one available - useEffect(() => { - if ( - previousIsProjectScanningInProgress && - !isIdeProjectScanningInProgress && - selectItems?.length === 1 && - incidentId - ) { - tryToAddChatContextFile(selectItems[0].value, incidentId); - } - }, [ - incidentId, - isIdeProjectScanningInProgress, - previousIsProjectScanningInProgress, - tryToAddChatContextFile, - selectItems - ]); - - const handleSelectChange = (value: string | string[]) => { - sendUserActionTrackingEvent(trackingEvents.IDE_PROJECT_SELECTED); - const selectedValue = isString(value) ? value : value[0]; - - if (!selectItems) { - return; - } - - setSelectItems( - selectItems.map((item) => ({ - ...item, - selected: item.value === selectedValue - })) - ); - - if (!incidentId) { - return; - } - - tryToAddChatContextFile(selectedValue, incidentId); - }; - - const handleTryScanningAgainButtonClick = () => { - sendUserActionTrackingEvent( - trackingEvents.TRY_SCANNING_AGAIN_BUTTON_CLICKED - ); - window.location.reload(); - }; - - // const handleGetDigmaButtonClick = () => { - // sendUserActionTrackingEvent(trackingEvents.GET_DIGMA_BUTTON_CLICKED); - // window.open( - // JETBRAINS_MARKETPLACE_PLUGIN_URL, - // "_blank", - // "noopener noreferrer" - // ); - // }; - - useEffect(() => { - async function initialScan() { - await tryToScanRunningIdeProjects(); - } - - void initialScan(); - }, [tryToScanRunningIdeProjects]); - - const renderContent = () => { - if (!incidentId) { - return ( - - Incident ID is not provided - - ); - } - - if (isIdeProjectScanningInProgress) { - return ( - - Searching for a running IDE - - You'll need an IDE installed with Digma configured to open the - link - - - ); - } - - if (isAddingChatContextFileInProgress) { - return ( - - Adding the incident details to the IDE chat context - - ); - } - - if (!selectItems) { - return null; - } - - if (selectItems.length === 0) { - return ( - <> - - Unable to open the Digma link - - Opening this link requires a running IDE with Digma installed and - configured. Launch your IDE and install Digma as needed, then - click the Try again button. - - - - - {/* */} - - - ); - } - - if (selectItems.length > 1) { - return ( - <> - - - Select the IDE project to add the incident details to the chat - context - - - void handleSelectChange(value)} - /> - - ); - } - }; - - return <>{renderContent()}; -}; diff --git a/src/components/common/icons/100px/VSCodeLogoIcon.tsx b/src/components/common/icons/100px/VSCodeLogoIcon.tsx new file mode 100644 index 000000000..40f9c7365 --- /dev/null +++ b/src/components/common/icons/100px/VSCodeLogoIcon.tsx @@ -0,0 +1,123 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import type { IconProps } from "../types"; + +const VSCodeLogoIconComponent = (props: IconProps) => { + const { size } = useIconProps(props); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const VSCodeLogoIcon = React.memo(VSCodeLogoIconComponent); diff --git a/src/components/common/icons/24px/CursorLogoIcon.tsx b/src/components/common/icons/24px/CursorLogoIcon.tsx new file mode 100644 index 000000000..524c5f071 --- /dev/null +++ b/src/components/common/icons/24px/CursorLogoIcon.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import type { ThemeableIconProps } from "../types"; + +const CursorLogoIconComponent = (props: ThemeableIconProps) => { + const { size } = useIconProps(props); + + const color = props.themeKind === "light" ? "#000" : "#fff"; + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const CursorLogoIcon = React.memo(CursorLogoIconComponent); diff --git a/src/containers/IdeLauncher/router.tsx b/src/containers/IdeLauncher/router.tsx index d955010c9..d13498bbc 100644 --- a/src/containers/IdeLauncher/router.tsx +++ b/src/containers/IdeLauncher/router.tsx @@ -1,7 +1,6 @@ import type { RouteObject } from "react-router"; import { createBrowserRouter, useRouteError } from "react-router"; import { IdeLauncher } from "../../components/IdeLauncher"; -import { IncidentDetails } from "../../components/IdeLauncher/IncidentDetails"; import { ProjectOpener } from "../../components/IdeLauncher/ProjectOpener"; export const routes: RouteObject[] = [ @@ -15,10 +14,6 @@ export const routes: RouteObject[] = [ { index: true, element: - }, - { - path: "incidents/:id", - element: } ] } From 4b855f3eb9357e096ae4601d5bfea47240d3a170 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 4 Aug 2025 19:11:14 +0200 Subject: [PATCH 08/17] Revert changes in IDE launcher --- .../IncidentMetaData/IdeToolbar/index.tsx | 2 +- .../scanRunningVSCodeIdeProjects.ts | 4 +- .../IdeLauncher/ProjectOpener/index.tsx | 265 ------------------ .../IdeLauncher/ProjectOpener/styles.ts | 10 - src/components/IdeLauncher/index.tsx | 237 +++++++++++++++- src/containers/IdeLauncher/index.tsx | 13 +- src/containers/IdeLauncher/router.tsx | 25 -- src/containers/IdeLauncher/store.ts | 16 -- 8 files changed, 242 insertions(+), 330 deletions(-) rename src/components/{IdeLauncher => Agentic/IncidentDetails/IncidentMetaData/IdeToolbar}/scanRunningVSCodeIdeProjects.ts (92%) delete mode 100644 src/components/IdeLauncher/ProjectOpener/index.tsx delete mode 100644 src/components/IdeLauncher/ProjectOpener/styles.ts delete mode 100644 src/containers/IdeLauncher/router.tsx delete mode 100644 src/containers/IdeLauncher/store.ts diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx index dfcfb013a..52d658399 100644 --- a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx @@ -1,7 +1,6 @@ import { useEffect, useState, type ComponentType } from "react"; import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent"; import { uniqueBy } from "../../../../../utils/uniqueBy"; -import { scanRunningVSCodeIdeProjects } from "../../../../IdeLauncher/scanRunningVSCodeIdeProjects"; import type { VSCodeExtensionInfo } from "../../../../IdeLauncher/types"; import { VSCodeLogoIcon } from "../../../../common/icons/100px/VSCodeLogoIcon"; import { CursorLogoIcon } from "../../../../common/icons/24px/CursorLogoIcon"; @@ -10,6 +9,7 @@ import { NewIconButton } from "../../../../common/v3/NewIconButton"; import { Tooltip } from "../../../../common/v3/Tooltip"; import { trackingEvents } from "../../../tracking"; import { addChatContextIncidentFile } from "./addChatContextFile"; +import { scanRunningVSCodeIdeProjects } from "./scanRunningVSCodeIdeProjects"; import * as s from "./styles"; import type { IdeToolbarProps } from "./types"; diff --git a/src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/scanRunningVSCodeIdeProjects.ts similarity index 92% rename from src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts rename to src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/scanRunningVSCodeIdeProjects.ts index dab4ce224..b2de84ef9 100644 --- a/src/components/IdeLauncher/scanRunningVSCodeIdeProjects.ts +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/scanRunningVSCodeIdeProjects.ts @@ -1,10 +1,10 @@ import axios from "axios"; -import { isString } from "../../typeGuards/isString"; +import { isString } from "../../../../../typeGuards/isString"; import type { IntellijPluginInfo, VSCodeExtensionInfo, VSCodeIdeScanningResult -} from "./types"; +} from "../../../../IdeLauncher/types"; const START_PORT_TO_SCAN = 33100; const END_PORT_TO_SCAN = 33119; diff --git a/src/components/IdeLauncher/ProjectOpener/index.tsx b/src/components/IdeLauncher/ProjectOpener/index.tsx deleted file mode 100644 index acf95570f..000000000 --- a/src/components/IdeLauncher/ProjectOpener/index.tsx +++ /dev/null @@ -1,265 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { JETBRAINS_MARKETPLACE_PLUGIN_URL } from "../../../constants"; -import { useStableSearchParams } from "../../../hooks/useStableSearchParams"; -import { isString } from "../../../typeGuards/isString"; -import { sendTrackingEvent } from "../../../utils/actions/sendTrackingEvent"; -import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; -import { GenericPageLayout } from "../../common/GenericPageLayout"; -import { - Subtitle, - TextContainer, - Title -} from "../../common/GenericPageLayout/styles"; -import { NewButton } from "../../common/v3/NewButton"; -import type { SelectItem } from "../../common/v3/Select/types"; -import { IdeProjectSelect } from "../common/IdeProjectSelect"; -import { getSelectItemValue } from "../common/IdeProjectSelect/utils/getSelectedItemValue"; -import { parseSelectedItemValue } from "../common/IdeProjectSelect/utils/parseSelectedItemValue"; -import { scanRunningIntellijIdeProjects } from "../scanRunningIntellijIdeProjects"; -import { showIdeProject } from "../showIdeProject"; -import { trackingEvents } from "../tracking"; -import type { ShowIntellijIdeProjectResult } from "../types"; -import * as s from "./styles"; - -export const ProjectOpener = () => { - const [searchParams] = useStableSearchParams(); - const action = searchParams.get("plugin.action"); - const [selectItems, setSelectItems] = useState(); - const isMobile = ["Android", "iPhone", "iPad"].some((x) => - window.navigator.userAgent.includes(x) - ); - const [isIdeProjectScanningInProgress, setIsIdeProjectScanningInProgress] = - useState(false); - const [isShowIdeProjectInProgress, setIsShowIdeProjectInProgress] = - useState(false); - const [showIdeProjectResult, setShowIdeProjectResult] = - useState(); - - const tryToShowIdeProject = useCallback( - async (port: number, project: string) => { - setShowIdeProjectResult(undefined); - setIsShowIdeProjectInProgress(true); - const result = await showIdeProject( - port, - project, - Object.fromEntries(searchParams) - ); - sendTrackingEvent(trackingEvents.IDE_PROJECT_OPEN_RESULT_RECEIVED, { - result - }); - setShowIdeProjectResult(result); - setIsShowIdeProjectInProgress(false); - }, - [searchParams] - ); - - const tryToScanRunningIdeProjects = useCallback(async () => { - setSelectItems(undefined); - setIsIdeProjectScanningInProgress(true); - const result = await scanRunningIntellijIdeProjects(); - setIsIdeProjectScanningInProgress(false); - - const projects = result - .filter((x) => x.response.isCentralized) - .flatMap((info) => - info.response.openProjects.map((project) => ({ - ...info, - project: project, - port: info.port - })) - ); - - setSelectItems( - projects.map((x, i) => ({ - label: `${x.response.name} (${x.project})`, - description: `${x.response.name} (${x.project})`, - value: getSelectItemValue(x.port, x.project), - enabled: true, - selected: projects.length === 1 && i === 0 - })) - ); - - if (projects.length === 1) { - await tryToShowIdeProject(projects[0].port, projects[0].project); - } - }, [tryToShowIdeProject]); - - const handleSelectChange = async (value: string | string[]) => { - sendUserActionTrackingEvent(trackingEvents.IDE_PROJECT_SELECTED); - const selectedValue = isString(value) ? value : value[0]; - const { port, project } = parseSelectedItemValue(selectedValue); - - if (!selectItems) { - return; - } - - setSelectItems( - selectItems.map((item) => ({ - ...item, - selected: item.value === selectedValue - })) - ); - - await tryToShowIdeProject(port, project); - }; - - const handleTryScanningAgainButtonClick = () => { - sendUserActionTrackingEvent( - trackingEvents.TRY_SCANNING_AGAIN_BUTTON_CLICKED - ); - window.location.reload(); - }; - - const handleTryShowIdeProjectAgainButtonClick = async () => { - sendUserActionTrackingEvent(trackingEvents.TRY_AGAIN_BUTTON_CLICKED); - const selectedItemValue = selectItems?.find((item) => item.selected)?.value; - if (!selectedItemValue) { - return; - } - - const { port, project } = parseSelectedItemValue(selectedItemValue); - await tryToShowIdeProject(port, project); - }; - - const handleGetDigmaButtonClick = () => { - sendUserActionTrackingEvent(trackingEvents.GET_DIGMA_BUTTON_CLICKED); - window.open( - JETBRAINS_MARKETPLACE_PLUGIN_URL, - "_blank", - "noopener noreferrer" - ); - }; - - useEffect(() => { - async function initialScan() { - await tryToScanRunningIdeProjects(); - } - - void initialScan(); - }, [tryToScanRunningIdeProjects]); - - const renderContent = () => { - if (!action) { - return ( - - Invalid link - Link is partial or invalid - - ); - } - - if (isMobile) { - return ( - - Can't open Digma link - Digma links can only be opened on desktop/laptop - - ); - } - - if (isIdeProjectScanningInProgress) { - return ( - - Searching for a running IDE - - You'll need an IDE installed with Digma configured to open the - link - - - ); - } - - if (isShowIdeProjectInProgress) { - return ( - - Opening the Digma link in your IDE - - ); - } - - if (showIdeProjectResult?.result === "failure") { - return ( - <> - - There was an issue opening the link in the IDE - - Please check that IDE is running and click the{" "} - Try again button below. - - - { - void handleTryShowIdeProjectAgainButtonClick(); - }} - /> - - ); - } - - if (showIdeProjectResult?.result === "success") { - return ( - - Opening the Digma link in your IDE - - Switching over to the IDE. You can close this tab. - - - ); - } - - if (!selectItems) { - return null; - } - - if (selectItems.length === 0) { - return ( - <> - - Unable to open the Digma link - - Opening this link requires a running IDE with Digma installed and - configured. Launch your IDE and install Digma as needed, then - click the Try again button. - - - - - - - - ); - } - - if (selectItems.length > 1) { - return ( - <> - - Select the IDE project to view the Digma link - - We'll automatically switch to the IDE once you make a - selection - - - void handleSelectChange(value)} - /> - - ); - } - }; - - return ( - - {renderContent()} - - ); -}; diff --git a/src/components/IdeLauncher/ProjectOpener/styles.ts b/src/components/IdeLauncher/ProjectOpener/styles.ts deleted file mode 100644 index d0009cd58..000000000 --- a/src/components/IdeLauncher/ProjectOpener/styles.ts +++ /dev/null @@ -1,10 +0,0 @@ -import styled from "styled-components"; - -export const EmphasizedText = styled.span` - color: ${({ theme }) => theme.colors.v3.text.primary}; -`; - -export const ButtonsContainer = styled.div` - display: flex; - gap: 16px; -`; diff --git a/src/components/IdeLauncher/index.tsx b/src/components/IdeLauncher/index.tsx index bee14af18..862ee7332 100644 --- a/src/components/IdeLauncher/index.tsx +++ b/src/components/IdeLauncher/index.tsx @@ -1,17 +1,153 @@ -import { Outlet } from "react-router"; +import { useCallback, useEffect, useState } from "react"; +import { JETBRAINS_MARKETPLACE_PLUGIN_URL } from "../../constants"; +import { useStableSearchParams } from "../../hooks/useStableSearchParams"; +import { isString } from "../../typeGuards/isString"; +import { sendTrackingEvent } from "../../utils/actions/sendTrackingEvent"; +import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionTrackingEvent"; import { GenericPageLayout } from "../common/GenericPageLayout"; import { Subtitle, TextContainer, Title } from "../common/GenericPageLayout/styles"; +import { NewButton } from "../common/v3/NewButton"; +import type { SelectItem } from "../common/v3/Select/types"; +import { IdeProjectSelect } from "./common/IdeProjectSelect"; +import { getSelectItemValue } from "./common/IdeProjectSelect/utils/getSelectedItemValue"; +import { parseSelectedItemValue } from "./common/IdeProjectSelect/utils/parseSelectedItemValue"; +import { scanRunningIntellijIdeProjects } from "./scanRunningIntellijIdeProjects"; +import { showIdeProject } from "./showIdeProject"; +import * as s from "./styles"; +import { trackingEvents } from "./tracking"; +import type { ShowIntellijIdeProjectResult } from "./types"; export const IdeLauncher = () => { + const [searchParams] = useStableSearchParams(); + const action = searchParams.get("plugin.action"); + const [selectItems, setSelectItems] = useState(); const isMobile = ["Android", "iPhone", "iPad"].some((x) => window.navigator.userAgent.includes(x) ); + const [isIdeProjectScanningInProgress, setIsIdeProjectScanningInProgress] = + useState(false); + const [isShowIdeProjectInProgress, setIsShowIdeProjectInProgress] = + useState(false); + const [showIdeProjectResult, setShowIdeProjectResult] = + useState(); + + const tryToShowIdeProject = useCallback( + async (port: number, project: string) => { + setShowIdeProjectResult(undefined); + setIsShowIdeProjectInProgress(true); + const result = await showIdeProject( + port, + project, + Object.fromEntries(searchParams) + ); + sendTrackingEvent(trackingEvents.IDE_PROJECT_OPEN_RESULT_RECEIVED, { + result + }); + setShowIdeProjectResult(result); + setIsShowIdeProjectInProgress(false); + }, + [searchParams] + ); + + const tryToScanRunningIdeProjects = useCallback(async () => { + setSelectItems(undefined); + setIsIdeProjectScanningInProgress(true); + const result = await scanRunningIntellijIdeProjects(); + setIsIdeProjectScanningInProgress(false); + + const projects = result + .filter((x) => x.response.isCentralized) + .flatMap((info) => + info.response.openProjects.map((project) => ({ + ...info, + project: project, + port: info.port + })) + ); + + setSelectItems( + projects.map((x, i) => ({ + label: `${x.response.name} (${x.project})`, + description: `${x.response.name} (${x.project})`, + value: getSelectItemValue(x.port, x.project), + enabled: true, + selected: projects.length === 1 && i === 0 + })) + ); + + if (projects.length === 1) { + await tryToShowIdeProject(projects[0].port, projects[0].project); + } + }, [tryToShowIdeProject]); + + const handleSelectChange = async (value: string | string[]) => { + sendUserActionTrackingEvent(trackingEvents.IDE_PROJECT_SELECTED); + const selectedValue = isString(value) ? value : value[0]; + const { port, project } = parseSelectedItemValue(selectedValue); + + if (!selectItems) { + return; + } + + setSelectItems( + selectItems.map((item) => ({ + ...item, + selected: item.value === selectedValue + })) + ); + + await tryToShowIdeProject(port, project); + }; + + const handleTryScanningAgainButtonClick = () => { + sendUserActionTrackingEvent( + trackingEvents.TRY_SCANNING_AGAIN_BUTTON_CLICKED + ); + window.location.reload(); + }; + + const handleTryShowIdeProjectAgainButtonClick = async () => { + sendUserActionTrackingEvent(trackingEvents.TRY_AGAIN_BUTTON_CLICKED); + const selectedItemValue = selectItems?.find((item) => item.selected)?.value; + if (!selectedItemValue) { + return; + } + + const { port, project } = parseSelectedItemValue(selectedItemValue); + await tryToShowIdeProject(port, project); + }; + + const handleGetDigmaButtonClick = () => { + sendUserActionTrackingEvent(trackingEvents.GET_DIGMA_BUTTON_CLICKED); + window.open( + JETBRAINS_MARKETPLACE_PLUGIN_URL, + "_blank", + "noopener noreferrer" + ); + }; + + useEffect(() => { + async function initialScan() { + await tryToScanRunningIdeProjects(); + } + + void initialScan(); + }, [tryToScanRunningIdeProjects]); const renderContent = () => { + if (!action) { + return ( + + Invalid link + Link is partial or invalid + + ); + } + if (isMobile) { return ( @@ -21,7 +157,104 @@ export const IdeLauncher = () => { ); } - return ; + if (isIdeProjectScanningInProgress) { + return ( + + Searching for a running IDE + + You'll need an IDE installed with Digma configured to open the + link + + + ); + } + + if (isShowIdeProjectInProgress) { + return ( + + Opening the Digma link in your IDE + + ); + } + + if (showIdeProjectResult?.result === "failure") { + return ( + <> + + There was an issue opening the link in the IDE + + Please check that IDE is running and click the{" "} + Try again button below. + + + { + void handleTryShowIdeProjectAgainButtonClick(); + }} + /> + + ); + } + + if (showIdeProjectResult?.result === "success") { + return ( + + Opening the Digma link in your IDE + + Switching over to the IDE. You can close this tab. + + + ); + } + + if (!selectItems) { + return null; + } + + if (selectItems.length === 0) { + return ( + <> + + Unable to open the Digma link + + Opening this link requires a running IDE with Digma installed and + configured. Launch your IDE and install Digma as needed, then + click the Try again button. + + + + + + + + ); + } + + if (selectItems.length > 1) { + return ( + <> + + Select the IDE project to view the Digma link + + We'll automatically switch to the IDE once you make a + selection + + + void handleSelectChange(value)} + /> + + ); + } }; return ( diff --git a/src/containers/IdeLauncher/index.tsx b/src/containers/IdeLauncher/index.tsx index ecac50938..78daa5a49 100644 --- a/src/containers/IdeLauncher/index.tsx +++ b/src/containers/IdeLauncher/index.tsx @@ -1,15 +1,12 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { Provider } from "react-redux"; -import { RouterProvider } from "react-router"; import { sendMessage } from "../../api"; import { App } from "../../components/common/App"; import { PostHogHoC } from "../../components/common/PostHogHoC"; +import { IdeLauncher } from "../../components/IdeLauncher"; import posthog from "../../posthog"; import { handleUncaughtError } from "../../utils/handleUncaughtError"; import { APP_ID } from "./constants"; -import { router } from "./router"; -import { store } from "./store"; posthog?.register({ app: APP_ID }); @@ -27,11 +24,9 @@ if (rootElement) { root.render( - - - - - + + + ); diff --git a/src/containers/IdeLauncher/router.tsx b/src/containers/IdeLauncher/router.tsx deleted file mode 100644 index d13498bbc..000000000 --- a/src/containers/IdeLauncher/router.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { RouteObject } from "react-router"; -import { createBrowserRouter, useRouteError } from "react-router"; -import { IdeLauncher } from "../../components/IdeLauncher"; -import { ProjectOpener } from "../../components/IdeLauncher/ProjectOpener"; - -export const routes: RouteObject[] = [ - { - path: "/", - element: , - ErrorBoundary: () => { - throw useRouteError(); - }, - children: [ - { - index: true, - element: - } - ] - } -]; - -const basename = - document.querySelector("base")?.getAttribute("href") ?? undefined; - -export const router = createBrowserRouter(routes, { basename }); diff --git a/src/containers/IdeLauncher/store.ts b/src/containers/IdeLauncher/store.ts deleted file mode 100644 index 50e558be9..000000000 --- a/src/containers/IdeLauncher/store.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { configureStore } from "@reduxjs/toolkit"; -import { setupListeners } from "@reduxjs/toolkit/query"; -import { digmaApi } from "../../redux/services/digma"; - -export const store = configureStore({ - reducer: { - [digmaApi.reducerPath]: digmaApi.reducer - }, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware().concat(digmaApi.middleware) -}); - -setupListeners(store.dispatch); - -export type IdeLauncherRootState = ReturnType; -export type IdeLauncherDispatch = typeof store.dispatch; From 6848a556d51d072729bf4ae4bef1ccbbdbd80c18 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 4 Aug 2025 19:16:03 +0200 Subject: [PATCH 09/17] Revert changes in IDE launcher --- .../IdeToolbar/scanRunningVSCodeIdeProjects.ts | 8 ++------ .../IncidentMetaData/IdeToolbar/types.ts | 12 ++++++++++++ src/components/IdeLauncher/tracking.ts | 4 +--- src/components/IdeLauncher/types.ts | 17 ----------------- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/scanRunningVSCodeIdeProjects.ts b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/scanRunningVSCodeIdeProjects.ts index b2de84ef9..6f8181d63 100644 --- a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/scanRunningVSCodeIdeProjects.ts +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/scanRunningVSCodeIdeProjects.ts @@ -1,10 +1,6 @@ import axios from "axios"; import { isString } from "../../../../../typeGuards/isString"; -import type { - IntellijPluginInfo, - VSCodeExtensionInfo, - VSCodeIdeScanningResult -} from "../../../../IdeLauncher/types"; +import type { VSCodeExtensionInfo, VSCodeIdeScanningResult } from "./types"; const START_PORT_TO_SCAN = 33100; const END_PORT_TO_SCAN = 33119; @@ -24,7 +20,7 @@ export const scanRunningVSCodeIdeProjects = const responses = await Promise.allSettled( instances.map((x) => axios - .get(x.url) + .get(x.url) .then((response) => ({ port: x.port, response: response.data })) .catch((error) => ({ port: x.port, diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/types.ts b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/types.ts index 63a7b2daa..0732cfd72 100644 --- a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/types.ts +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/types.ts @@ -1,3 +1,15 @@ export interface IdeToolbarProps { incidentId: string; } + +export interface VSCodeExtensionInfo { + ideName: string; + ideUriScheme: string; + ideVersion: string; + workspace: string; +} + +export type VSCodeIdeScanningResult = { + port: number; + response: VSCodeExtensionInfo; +}[]; diff --git a/src/components/IdeLauncher/tracking.ts b/src/components/IdeLauncher/tracking.ts index f01429654..b1f6ab32f 100644 --- a/src/components/IdeLauncher/tracking.ts +++ b/src/components/IdeLauncher/tracking.ts @@ -8,9 +8,7 @@ export const trackingEvents = addPrefix( TRY_SCANNING_AGAIN_BUTTON_CLICKED: "try scanning again button clicked", TRY_AGAIN_BUTTON_CLICKED: "try again button clicked", GET_DIGMA_BUTTON_CLICKED: "get digma button clicked", - IDE_PROJECT_OPEN_RESULT_RECEIVED: "ide project open result received", - IDE_CHAT_CONTEXT_FILE_RESULT_RECEIVED: - "ide chat context file result received" + IDE_PROJECT_OPEN_RESULT_RECEIVED: "ide project open result received" }, " " ); diff --git a/src/components/IdeLauncher/types.ts b/src/components/IdeLauncher/types.ts index d84a58864..2d361071f 100644 --- a/src/components/IdeLauncher/types.ts +++ b/src/components/IdeLauncher/types.ts @@ -11,29 +11,12 @@ export interface IntellijPluginInfo { openProjects: string[]; } -export interface VSCodeExtensionInfo { - ideName: string; - ideUriScheme: string; - ideVersion: string; - workspace: string; -} - export interface ShowIntellijIdeProjectResult { result: "success" | "failure"; error?: { message: string }; } -export interface AddChatContextFileResult { - result: "success" | "failure"; - error?: { message: string }; -} - export type IntellijIdeScanningResult = { port: number; response: IntellijPluginInfo; }[]; - -export type VSCodeIdeScanningResult = { - port: number; - response: VSCodeExtensionInfo; -}[]; From 67880a3e41f92f30b3860a1e9727c66157685410 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 4 Aug 2025 19:16:45 +0200 Subject: [PATCH 10/17] Fix import --- .../IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx index 52d658399..44fd94e72 100644 --- a/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx +++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/IdeToolbar/index.tsx @@ -1,7 +1,6 @@ import { useEffect, useState, type ComponentType } from "react"; import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent"; import { uniqueBy } from "../../../../../utils/uniqueBy"; -import type { VSCodeExtensionInfo } from "../../../../IdeLauncher/types"; import { VSCodeLogoIcon } from "../../../../common/icons/100px/VSCodeLogoIcon"; import { CursorLogoIcon } from "../../../../common/icons/24px/CursorLogoIcon"; import type { IconProps } from "../../../../common/icons/types"; @@ -11,7 +10,7 @@ import { trackingEvents } from "../../../tracking"; import { addChatContextIncidentFile } from "./addChatContextFile"; import { scanRunningVSCodeIdeProjects } from "./scanRunningVSCodeIdeProjects"; import * as s from "./styles"; -import type { IdeToolbarProps } from "./types"; +import type { IdeToolbarProps, VSCodeExtensionInfo } from "./types"; const IDE_ICONS: Record> = { cursor: CursorLogoIcon, From e8b4d7fb797641925f56c0755a90e96b9f2ddb7c Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 4 Aug 2025 19:20:12 +0200 Subject: [PATCH 11/17] Revert changes in IDE launcher --- src/components/IdeLauncher/index.tsx | 6 ++++-- src/containers/IdeLauncher/index.tsx | 4 ---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/IdeLauncher/index.tsx b/src/components/IdeLauncher/index.tsx index 862ee7332..96179330c 100644 --- a/src/components/IdeLauncher/index.tsx +++ b/src/components/IdeLauncher/index.tsx @@ -135,8 +135,10 @@ export const IdeLauncher = () => { await tryToScanRunningIdeProjects(); } - void initialScan(); - }, [tryToScanRunningIdeProjects]); + if (!isMobile) { + void initialScan(); + } + }, [isMobile, tryToScanRunningIdeProjects]); const renderContent = () => { if (!action) { diff --git a/src/containers/IdeLauncher/index.tsx b/src/containers/IdeLauncher/index.tsx index 78daa5a49..7c632672f 100644 --- a/src/containers/IdeLauncher/index.tsx +++ b/src/containers/IdeLauncher/index.tsx @@ -1,6 +1,5 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { sendMessage } from "../../api"; import { App } from "../../components/common/App"; import { PostHogHoC } from "../../components/common/PostHogHoC"; import { IdeLauncher } from "../../components/IdeLauncher"; @@ -14,9 +13,6 @@ window.addEventListener("error", (e) => { handleUncaughtError(APP_ID, e); }); -// TODO: make not required and remove -window.sendMessageToDigma = sendMessage; - const rootElement = document.getElementById("root"); if (rootElement) { From aa35283547a6e638a22b357d026a1411b8553df9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 4 Aug 2025 17:23:54 +0000 Subject: [PATCH 12/17] Bump version to 16.7.0-alpha.3 [skip ci] --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c6a2a52f0..9ee6c381f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "digma-ui", - "version": "16.7.0-alpha.2", + "version": "16.7.0-alpha.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "digma-ui", - "version": "16.7.0-alpha.2", + "version": "16.7.0-alpha.3", "license": "MIT", "dependencies": { "@codemirror/lang-json": "^6.0.2", diff --git a/package.json b/package.json index 3a6c2b28d..8f5ec2809 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "digma-ui", - "version": "16.7.0-alpha.2", + "version": "16.7.0-alpha.3", "description": "Digma UI", "scripts": { "lint:eslint": "eslint --cache .", From fe258c72a861ca1e29fd0cb401ef08e2dad4852d Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 5 Aug 2025 10:46:12 +0200 Subject: [PATCH 13/17] Move env section to build job level --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c12656e72..950890cfb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,8 @@ jobs: contents: read actions: write runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.CONTENTS_WRITE_PAT }} outputs: artifact-name: ${{ steps.get-artifact-name.outputs.artifact_name }} steps: @@ -28,9 +30,7 @@ jobs: - run: npm ci - - env: - GITHUB_TOKEN: ${{ secrets.CONTENTS_WRITE_PAT }} # TODO: fix - run: npm run build:prod:${{ inputs.platform }} + - run: npm run build:prod:${{ inputs.platform }} - name: Get artifact name id: get-artifact-name From 4381a00f6e2ff76c16140e6b2f6ae1f8dbfa92cb Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 5 Aug 2025 13:01:21 +0200 Subject: [PATCH 14/17] Pass secrets to reusable workflows --- .github/workflows/push.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index be5215f43..31f0628bc 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -39,6 +39,7 @@ jobs: actions: write needs: lint-test uses: ./.github/workflows/build.yml + secrets: inherit with: platform: "jetbrains" @@ -49,6 +50,7 @@ jobs: actions: write needs: lint-test uses: ./.github/workflows/build.yml + secrets: inherit with: platform: "vs" @@ -59,6 +61,7 @@ jobs: actions: write needs: lint-test uses: ./.github/workflows/build.yml + secrets: inherit with: platform: "web" From b7a584e70357677511268a59177bfe72dd2e01b6 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 5 Aug 2025 13:26:03 +0200 Subject: [PATCH 15/17] Revert changes in Github actions to use GITHUB_TOKEN again --- .github/workflows/build.yml | 7 ++++--- .github/workflows/push.yml | 3 --- .github/workflows/release-asset.yml | 2 +- .github/workflows/update-dependencies.yml | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 950890cfb..710666bfb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,8 +16,7 @@ jobs: contents: read actions: write runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ secrets.CONTENTS_WRITE_PAT }} + outputs: artifact-name: ${{ steps.get-artifact-name.outputs.artifact_name }} steps: @@ -30,7 +29,9 @@ jobs: - run: npm ci - - run: npm run build:prod:${{ inputs.platform }} + - env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npm run build:prod:${{ inputs.platform }} - name: Get artifact name id: get-artifact-name diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 31f0628bc..be5215f43 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -39,7 +39,6 @@ jobs: actions: write needs: lint-test uses: ./.github/workflows/build.yml - secrets: inherit with: platform: "jetbrains" @@ -50,7 +49,6 @@ jobs: actions: write needs: lint-test uses: ./.github/workflows/build.yml - secrets: inherit with: platform: "vs" @@ -61,7 +59,6 @@ jobs: actions: write needs: lint-test uses: ./.github/workflows/build.yml - secrets: inherit with: platform: "web" diff --git a/.github/workflows/release-asset.yml b/.github/workflows/release-asset.yml index bce69ec27..181388bd1 100644 --- a/.github/workflows/release-asset.yml +++ b/.github/workflows/release-asset.yml @@ -27,4 +27,4 @@ jobs: with: files: dist/${{ inputs.artifact-name }}.zip env: - GITHUB_TOKEN: ${{ secrets.CONTENTS_WRITE_PAT }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml index 8cb147a4d..f866748c7 100644 --- a/.github/workflows/update-dependencies.yml +++ b/.github/workflows/update-dependencies.yml @@ -28,7 +28,7 @@ jobs: - name: Commit, push changes and create PR env: - GH_TOKEN: ${{ secrets.CONTENTS_WRITE_PAT }} + GITHUB_TOKEN: ${{ secrets.CONTENTS_WRITE_PAT }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" From eda09f6dbf89a55522732315e6ffda40359c9653 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 5 Aug 2025 16:08:49 +0200 Subject: [PATCH 16/17] Add jiti as explicit dependency for ESLint --- package-lock.json | 10 ++++++---- package.json | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ee6c381f..958c4f407 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,6 +91,7 @@ "husky": "^9.1.7", "jest": "^30.0.5", "jest-environment-jsdom": "^30.0.5", + "jiti": "^2.5.1", "knip": "^5.62.0", "lint-staged": "^16.1.2", "postcss-styled-syntax": "^0.7.1", @@ -14695,9 +14696,9 @@ } }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", "dev": true, "license": "MIT", "bin": { @@ -18729,7 +18730,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/reselect": { "version": "5.1.1", diff --git a/package.json b/package.json index 8f5ec2809..d8058e87e 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "husky": "^9.1.7", "jest": "^30.0.5", "jest-environment-jsdom": "^30.0.5", + "jiti": "^2.5.1", "knip": "^5.62.0", "lint-staged": "^16.1.2", "postcss-styled-syntax": "^0.7.1", From ce3a4766d82f397753714d5ebf0c33991d9642d4 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 8 Aug 2025 16:00:01 +0200 Subject: [PATCH 17/17] Improve styling in Directives table --- .../Agentic/IncidentDirectives/index.tsx | 15 +++++++++++++-- .../Agentic/IncidentDirectives/styles.ts | 18 ++++++++++++++++-- src/redux/posthogMiddleware.ts | 4 +--- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/components/Agentic/IncidentDirectives/index.tsx b/src/components/Agentic/IncidentDirectives/index.tsx index 4de34421e..550ca8e55 100644 --- a/src/components/Agentic/IncidentDirectives/index.tsx +++ b/src/components/Agentic/IncidentDirectives/index.tsx @@ -279,7 +279,11 @@ export const IncidentDirectives = () => { }, cell: (info) => { const value = info.getValue(); - return {value}; + return ( + + {value} + + ); }, enableSorting: true, sortingFn: (rowA, rowB) => { @@ -313,7 +317,14 @@ export const IncidentDirectives = () => { }, cell: (info) => { const value = info.getValue(); - return {value.join(", ")}; + const valueString = value.join(", "); + return ( + + + {valueString} + + + ); }, enableSorting: true, sortingFn: (rowA, rowB) => { diff --git a/src/components/Agentic/IncidentDirectives/styles.ts b/src/components/Agentic/IncidentDirectives/styles.ts index d6387aa2d..879d71877 100644 --- a/src/components/Agentic/IncidentDirectives/styles.ts +++ b/src/components/Agentic/IncidentDirectives/styles.ts @@ -139,12 +139,26 @@ export const RecordNumber = styled.span` color: ${({ theme }) => theme.colors.v3.text.tertiary}; `; -export const Condition = styled.span` +export const TruncatedTableCellContent = styled.span` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &:hover { + padding: 26px 0 16px; + overflow-y: auto; + white-space: normal; + box-sizing: border-box; + height: 100%; + } +`; + +export const Condition = styled(TruncatedTableCellContent)` ${subheading1BoldTypography} color: ${({ theme }) => theme.colors.v3.text.primary}; `; -export const Directive = styled.span` +export const Directive = styled(TruncatedTableCellContent)` ${subheading1RegularTypography} color: ${({ theme }) => theme.colors.v3.text.secondary}; `; diff --git a/src/redux/posthogMiddleware.ts b/src/redux/posthogMiddleware.ts index b4d6604fa..37f13e1c1 100644 --- a/src/redux/posthogMiddleware.ts +++ b/src/redux/posthogMiddleware.ts @@ -47,9 +47,7 @@ export const posthogMiddleware = } if ( - rejectedAction.payload.error?.includes( - "Error: TypeError: Failed to fetch" - ) + rejectedAction.payload.error?.includes("TypeError: Failed to fetch") ) { // Ignore network errors return next(action);