diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html index 7667554cd..41d7ac3c4 100644 --- a/.storybook/preview-body.html +++ b/.storybook/preview-body.html @@ -1,16 +1,24 @@ diff --git a/assets/assets/index.ejs b/assets/assets/index.ejs index 518434c7f..f742e0bb2 100644 --- a/assets/assets/index.ejs +++ b/assets/assets/index.ejs @@ -15,10 +15,19 @@
diff --git a/assets/installationWizard/index.ejs b/assets/installationWizard/index.ejs index 82f93247a..79a41edad 100644 --- a/assets/installationWizard/index.ejs +++ b/assets/installationWizard/index.ejs @@ -15,13 +15,20 @@
diff --git a/assets/recentActivity/index.ejs b/assets/recentActivity/index.ejs index 665dac875..ebde3811f 100644 --- a/assets/recentActivity/index.ejs +++ b/assets/recentActivity/index.ejs @@ -15,11 +15,19 @@
diff --git a/src/components/InstallationWizard/FinishStep/styles.ts b/src/components/InstallationWizard/FinishStep/styles.ts index 891c99674..559e3295c 100644 --- a/src/components/InstallationWizard/FinishStep/styles.ts +++ b/src/components/InstallationWizard/FinishStep/styles.ts @@ -68,7 +68,6 @@ export const EmailField = styled.div` `; export const EmailInput = styled.input` - box-sizing: border-box; font-size: 12px; line-height: 14px; padding: 8px 10px; diff --git a/src/components/InstallationWizard/InstallStep/EngineManager/EngineManager.stories.tsx b/src/components/InstallationWizard/InstallStep/EngineManager/EngineManager.stories.tsx new file mode 100644 index 000000000..9407e9582 --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/EngineManager/EngineManager.stories.tsx @@ -0,0 +1,20 @@ +import { Meta, StoryObj } from "@storybook/react"; + +import { EngineManager } from "."; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Installation Wizard/EngineManager", + component: EngineManager, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Default: Story = {}; diff --git a/src/components/InstallationWizard/InstallStep/EngineManager/index.tsx b/src/components/InstallationWizard/InstallStep/EngineManager/index.tsx new file mode 100644 index 000000000..fd0856c9f --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/EngineManager/index.tsx @@ -0,0 +1,383 @@ +import { useContext, useEffect, useState } from "react"; +import { useTheme } from "styled-components"; +import { actions } from "../.."; +import { dispatcher } from "../../../../dispatcher"; +import { actions as globalActions } from "../../../common/App"; +import { ConfigContext } from "../../../common/App/ConfigContext"; +import { getThemeKind } from "../../../common/App/styles"; +import { Button } from "../../../common/Button"; +import { Loader } from "../../../common/Loader"; +import { CrossCircleIcon } from "../../../common/icons/CrossCircleIcon"; +import { DigmaLogoIcon } from "../../../common/icons/DigmaLogoIcon"; +import { PlayCircleIcon } from "../../../common/icons/PlayCircleIcon"; +import { StopCircleIcon } from "../../../common/icons/StopCircleIcon"; +import { trackingEvents } from "../../tracking"; +import { AsyncActionResultData, AsyncActionStatus } from "../../types"; +import * as s from "./styles"; +import { + CurrentOperation, + EngineManagerProps, + FailedOperation, + Operation, + OperationInfo +} from "./types"; + +const getLoaderStatus = ( + isDigmaRunning: boolean, + currentOperationStatus: AsyncActionStatus, + failedOperation: Operation | undefined +) => { + if (currentOperationStatus) { + return currentOperationStatus; + } + + if (failedOperation) { + return "failure"; + } + + if (isDigmaRunning) { + return "success"; + } +}; + +export const EngineManager = (props: EngineManagerProps) => { + const config = useContext(ConfigContext); + const [currentOperation, setCurrentOperation] = useState(); + const [failedOperation, setFailedOperation] = useState(); + const theme = useTheme(); + const themeKind = getThemeKind(theme); + + const operationsInfo: Record = { + [Operation.INSTALL]: { + action: actions.INSTALL_DIGMA_ENGINE, + button: { label: "Start", icon: PlayCircleIcon }, + titleSuffix: "Starting" + }, + [Operation.UNINSTALL]: { + action: actions.UNINSTALL_DIGMA_ENGINE, + button: { label: "Remove", icon: CrossCircleIcon }, + titleSuffix: "Removing" + }, + [Operation.START]: { + action: actions.START_DIGMA_ENGINE, + button: { label: "Start", icon: PlayCircleIcon }, + titleSuffix: "Starting" + }, + [Operation.STOP]: { + action: actions.STOP_DIGMA_ENGINE, + button: { label: "Stop", icon: StopCircleIcon }, + titleSuffix: "Stopping" + } + }; + + useEffect(() => { + const handleOperationResultData = ( + data: AsyncActionResultData, + operation: Operation + ) => { + setCurrentOperation({ + operation: operation, + status: data.result, + error: data.error + }); + }; + const handleInstallDigmaResultData = (data: unknown) => { + handleOperationResultData( + data as AsyncActionResultData, + Operation.INSTALL + ); + }; + + const handleUninstallDigmaResultData = (data: unknown) => { + handleOperationResultData( + data as AsyncActionResultData, + Operation.UNINSTALL + ); + }; + + const handleStartDigmaResultData = (data: unknown) => { + handleOperationResultData(data as AsyncActionResultData, Operation.START); + }; + + const handleStopDigmaResultData = (data: unknown) => { + handleOperationResultData(data as AsyncActionResultData, Operation.STOP); + }; + + dispatcher.addActionListener( + actions.SET_INSTALL_DIGMA_ENGINE_RESULT, + handleInstallDigmaResultData + ); + + dispatcher.addActionListener( + actions.SET_UNINSTALL_DIGMA_ENGINE_RESULT, + handleUninstallDigmaResultData + ); + + dispatcher.addActionListener( + actions.SET_START_DIGMA_ENGINE_RESULT, + handleStartDigmaResultData + ); + + dispatcher.addActionListener( + actions.SET_STOP_DIGMA_ENGINE_RESULT, + handleStopDigmaResultData + ); + + return () => { + dispatcher.removeActionListener( + actions.SET_INSTALL_DIGMA_ENGINE_RESULT, + handleInstallDigmaResultData + ); + + dispatcher.removeActionListener( + actions.SET_UNINSTALL_DIGMA_ENGINE_RESULT, + handleUninstallDigmaResultData + ); + + dispatcher.removeActionListener( + actions.SET_START_DIGMA_ENGINE_RESULT, + handleStartDigmaResultData + ); + + dispatcher.removeActionListener( + actions.SET_STOP_DIGMA_ENGINE_RESULT, + handleStopDigmaResultData + ); + }; + }, []); + + useEffect(() => { + if (props.autoInstall) { + window.sendMessageToDigma({ + action: actions.INSTALL_DIGMA_ENGINE + }); + setCurrentOperation({ operation: Operation.INSTALL, status: "pending" }); + } + }, [props.autoInstall]); + + useEffect(() => { + if (currentOperation) { + if (currentOperation.status === "success") { + switch (currentOperation.operation) { + case Operation.INSTALL: + if (props.autoInstall && props.onAutoInstallFinish) { + props.onAutoInstallFinish(); + } + break; + case Operation.UNINSTALL: + if (props.autoInstall) { + if (props.onManualInstallSelect) { + props.onManualInstallSelect(); + } + } else { + if (props.onRemoveFinish) { + props.onRemoveFinish(); + } + } + break; + case Operation.START: + case Operation.STOP: + break; + } + } + + if (currentOperation.status === "failure") { + switch (currentOperation.operation) { + case Operation.INSTALL: + case Operation.UNINSTALL: + if (props.autoInstall && props.onManualInstallSelect) { + props.onManualInstallSelect(); + } + break; + case Operation.START: + case Operation.STOP: + break; + } + } + + switch (currentOperation.status) { + case "success": + setCurrentOperation(undefined); + if (props.onOperationFinish) { + props.onOperationFinish(); + } + break; + case "failure": + setFailedOperation({ + operation: currentOperation.operation, + error: currentOperation.error + }); + setCurrentOperation(undefined); + if (props.onOperationFinish) { + props.onOperationFinish(); + } + break; + case "pending": + setFailedOperation(undefined); + if (props.onOperationStart) { + props.onOperationStart(); + } + break; + } + } + }, [currentOperation, props]); + + const sendActionButtonTrackingEvent = (buttonName: string) => { + window.sendMessageToDigma({ + action: globalActions.SEND_TRACKING_EVENT, + payload: { + eventName: trackingEvents.ENGINE_ACTION_BUTTON_CLICKED, + buttonName + } + }); + }; + + const handleRetryButtonClick = () => { + if (failedOperation) { + window.sendMessageToDigma({ + action: operationsInfo[failedOperation.operation].action + }); + setCurrentOperation({ + operation: failedOperation.operation, + status: "pending" + }); + } + }; + + const handleInstallManuallyButtonClick = () => { + props.onManualInstallSelect && props.onManualInstallSelect(); + }; + + const renderOperationButton = (operation: Operation) => { + const handleOperationButtonClick = () => { + const operationInfo = operationsInfo[operation]; + window.sendMessageToDigma({ + action: operationInfo.action + }); + sendActionButtonTrackingEvent(operationInfo.button.label); + setCurrentOperation({ operation, status: "pending" }); + }; + + const operationInfo = operationsInfo[operation]; + return ( + + ); + }; + + const renderContent = () => { + const loaderStatus = getLoaderStatus( + config.isDigmaEngineRunning, + currentOperation?.status, + failedOperation?.operation + ); + + const icon = loaderStatus ? ( + + ) : ( + + ); + + let title = "Digma Local Analysis Engine "; + + const lastOperation = + currentOperation?.operation || failedOperation?.operation; + + if (lastOperation) { + title += operationsInfo[lastOperation].titleSuffix; + } else { + title += config.isDigmaEngineInstalled + ? config.isDigmaEngineRunning + ? "Running" + : "Stopped" + : "Not installed"; + } + + if (failedOperation) { + title += " Failed"; + } + + const buttons = []; + + if (failedOperation) { + buttons.push( + + ); + + if (props.onManualInstallSelect) { + buttons.push( + + ); + } + } else { + if (config.isDigmaEngineInstalled) { + if (config.isDigmaEngineRunning) { + if (!props.autoInstall) { + buttons.push(renderOperationButton(Operation.STOP)); + } + } else { + buttons.push(renderOperationButton(Operation.START)); + } + + if (!props.autoInstall) { + buttons.push(renderOperationButton(Operation.UNINSTALL)); + } + } else { + buttons.push(renderOperationButton(Operation.INSTALL)); + } + } + + const buttonsWithDividers = buttons.flatMap((button, i, arr) => + i !== arr.length - 1 + ? [button, ] + : button + ); + + return ( + <> + {icon} + {title} + + {!failedOperation && ( + + {currentOperation?.status === "pending" + ? "This may take a few minutes..." + : 'Click "Next" to continue setup'} + + )} + {failedOperation ? ( + {failedOperation.error} + ) : ( + + You can always start / stop / remove the Digma Engine from the + Digma panel + + )} + + + {!currentOperation && buttonsWithDividers} + + + ); + }; + + return {renderContent()}; +}; diff --git a/src/components/InstallationWizard/InstallStep/EngineManager/styles.ts b/src/components/InstallationWizard/InstallStep/EngineManager/styles.ts new file mode 100644 index 000000000..eee19b603 --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/EngineManager/styles.ts @@ -0,0 +1,81 @@ +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + padding: 40px 8px 0; + text-align: center; +`; + +export const Title = styled.span` + font-size: 14px; + font-weight: 500; + line-height: normal; + text-transform: capitalize; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#788ca9"; + case "dark": + case "dark-jetbrains": + return "#dadada"; + } + }}; +`; + +export const ContentContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + font-size: 12px; + font-style: normal; + font-weight: 500; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#828797"; + case "dark": + case "dark-jetbrains": + return "#9b9b9b"; + } + }}; +`; + +export const ErrorMessage = styled.span` + word-break: break-word; + max-width: 300px; + white-space: pre-line; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#e00036"; + case "dark": + case "dark-jetbrains": + return "#f93967"; + } + }}; +`; + +export const ButtonsContainer = styled.div` + display: flex; + align-items: center; + height: 75px; +`; + +export const ButtonDivider = styled.div` + height: 22px; + width: 1px; + margin: 4px; + background: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#b9c0d4"; + case "dark": + case "dark-jetbrains": + return "#49494d"; + } + }}; +`; diff --git a/src/components/InstallationWizard/InstallStep/EngineManager/types.ts b/src/components/InstallationWizard/InstallStep/EngineManager/types.ts new file mode 100644 index 000000000..b2fda6be8 --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/EngineManager/types.ts @@ -0,0 +1,39 @@ +import { MemoExoticComponent } from "react"; +import { IconProps } from "../../../common/icons/types"; +import { AsyncActionStatus } from "../../types"; + +export interface EngineManagerProps { + onManualInstallSelect?: () => void; + onAutoInstallFinish?: () => void; + onRemoveFinish?: () => void; + onOperationStart?: () => void; + onOperationFinish?: () => void; + autoInstall?: boolean; +} + +export enum Operation { + INSTALL = "install", + UNINSTALL = "uninstall", + START = "start", + STOP = "stop" +} + +export interface CurrentOperation { + operation: Operation; + status: AsyncActionStatus; + error?: string; +} + +export interface FailedOperation { + operation: Operation; + error?: string; +} + +export interface OperationInfo { + action: string; + button: { + label: string; + icon: MemoExoticComponent<(props: IconProps) => JSX.Element>; + }; + titleSuffix: string; +} diff --git a/src/components/InstallationWizard/InstallStep/index.tsx b/src/components/InstallationWizard/InstallStep/index.tsx index 99f72cadd..1b95454b5 100644 --- a/src/components/InstallationWizard/InstallStep/index.tsx +++ b/src/components/InstallationWizard/InstallStep/index.tsx @@ -1,6 +1,7 @@ -import { useState } from "react"; +import { useContext, useState } from "react"; import { useTheme } from "styled-components"; import { actions as globalActions } from "../../common/App"; +import { ConfigContext } from "../../common/App/ConfigContext"; import { getThemeKind } from "../../common/App/styles"; import { Loader } from "../../common/Loader"; import { ChatFillIcon } from "../../common/icons/ChatIFillIcon"; @@ -12,6 +13,7 @@ import { CodeSnippet } from "../CodeSnippet"; import { Tabs } from "../Tabs"; import { Link, MainButton, SectionDescription } from "../styles"; import { trackingEvents } from "../tracking"; +import { EngineManager } from "./EngineManager"; import * as s from "./styles"; import { InstallStepProps } from "./types"; @@ -27,12 +29,29 @@ const DOCKER_COMPOSE_URL = "https://docs.docker.com/compose/install/"; const DIGMA_HELM_CHART_URL = "https://github.com/digma-ai/helm-chart/tree/gh-pages"; +const isFirstLaunch = window.wizardFirstLaunch === true; + export const InstallStep = (props: InstallStepProps) => { const theme = useTheme(); const isConnectionCheckStarted = Boolean(props.connectionCheckStatus); const [selectedInstallTab, setSelectedInstallTab] = useState(0); const [selectedDockerComposeOSTab, setSelectedDockerComposeOSTab] = useState(0); + const config = useContext(ConfigContext); + const [isAutoInstallationFlow] = useState( + isFirstLaunch && + !config.isDigmaEngineInstalled && + config.isDockerInstalled && + config.isDockerComposeInstalled + ); + const [isAutoInstallationFinished, setIsAutoInstallationFinished] = + useState(false); + const [areTabsVisible, setAreTabsVisible] = useState( + !config.isDockerInstalled || !config.isDockerComposeInstalled + ); + const [isAutoInstallTabVisible, setIsAutoInstallTabVisible] = useState(false); + const [isEngineOperationInProgress, setIsEngineOperationInProgress] = + useState(false); const handleInstallDigmaButtonClick = () => { props.onGetDigmaDockerDesktopButtonClick(); @@ -68,6 +87,80 @@ export const InstallStep = (props: InstallStepProps) => { }); }; + const handleSlackLinkClick = () => { + window.sendMessageToDigma({ + action: globalActions.SEND_TRACKING_EVENT, + payload: { + eventName: trackingEvents.NO_DOCKER_SLACK_LINK_CLICKED + } + }); + props.onSlackLinkClick(); + }; + + const handleEngineAutoInstallationFinish = () => { + setIsAutoInstallationFinished(true); + }; + + const handleEngineRemovalFinish = () => { + setAreTabsVisible(true); + setIsAutoInstallTabVisible(true); + }; + + const handleEngineManualInstallSelect = () => { + setIsAutoInstallationFinished(true); + setAreTabsVisible(true); + setIsAutoInstallTabVisible(false); + }; + + const handleEngineOperationStart = () => { + setIsEngineOperationInProgress(true); + }; + + const handleEngineOperationFinish = () => { + setIsEngineOperationInProgress(false); + }; + + const renderLoader = () => ( + + {props.connectionCheckStatus && ( + + )} + + ); + + const renderMainButton = () => { + if (!isConnectionCheckStarted) { + return ( + + OK, I've installed Digma + + ); + } + + switch (props.connectionCheckStatus) { + case "pending": + return Complete; + case "failure": + return Retry; + case "success": + return ( + + Complete + + ); + } + }; + const renderDockerComposeInstructions = () => ( <> Then run: @@ -107,135 +200,159 @@ export const InstallStep = (props: InstallStepProps) => { } ]; - const handleSlackLinkClick = () => { - window.sendMessageToDigma({ - action: globalActions.SEND_TRACKING_EVENT, - payload: { - eventName: trackingEvents.NO_DOCKER_SLACK_LINK_CLICKED - } - }); - props.onSlackLinkClick(); - }; - const installTabs = [ + ...(isAutoInstallTabVisible + ? [ + { + title: "Auto install", + content: ( + <> + + + + { + + {!isEngineOperationInProgress && ( + + Next + + )} + + } + + ) + } + ] + : []), { icon: DockerLogoIcon, title: "Docker Desktop", content: ( - - - - Install Digma Docker extension - - - (You'll need{" "} - openLinkInDefaultBrowser(DOCKER_DESKTOP_URL)}> - Docker Desktop - {" "} - 4.10.0 or higher installed) - - - Get Digma Docker Extension - - + <> + + + + Install Digma Docker extension + + + (You'll need{" "} + openLinkInDefaultBrowser(DOCKER_DESKTOP_URL)} + > + Docker Desktop + {" "} + 4.10.0 or higher installed) + + + Get Digma Docker Extension + + + + {renderLoader()} + {renderMainButton()} + + ) }, { icon: CodeIcon, title: "Docker Compose", content: ( - - - Run the following from the terminal/command line to start the Digma - backend: - - - (You'll need{" "} - openLinkInDefaultBrowser(DOCKER_URL)}> - Docker - {" "} - and{" "} - openLinkInDefaultBrowser(DOCKER_COMPOSE_URL)}> - Docker Compose - {" "} - installed) - - - + <> + + + Run the following from the terminal/command line to start the + Digma backend: + + + (You'll need{" "} + openLinkInDefaultBrowser(DOCKER_URL)}> + Docker + {" "} + and{" "} + openLinkInDefaultBrowser(DOCKER_COMPOSE_URL)} + > + Docker Compose + {" "} + installed) + + + + + {renderLoader()} + {renderMainButton()} + + ) }, { title: "I don't have Docker", content: ( - - - - - - We'll be adding more options soon - but please reach out via Slack and we'll see - if we can still get your Digma up and running - - - - Slack group - - + <> + + + + + + We'll be adding more options soon + but please reach out via Slack and we'll see + if we can still get your Digma up and running + + + + Slack group + + + + {renderLoader()} + {renderMainButton()} + + ) } ]; return ( - - - - {props.connectionCheckStatus && ( - - )} - - {!isConnectionCheckStarted && ( - - OK, I've installed Digma - - )} - {props.connectionCheckStatus === "pending" && ( - Complete - )} - {props.connectionCheckStatus === "failure" && ( - Retry - )} - {props.connectionCheckStatus === "success" && ( - - Complete - - )} - + {areTabsVisible ? ( + + ) : ( + <> + + + {(isAutoInstallationFinished || + (!isFirstLaunch && !isEngineOperationInProgress)) && ( + Next + )} + + + )} ); }; diff --git a/src/components/InstallationWizard/InstallStep/styles.ts b/src/components/InstallationWizard/InstallStep/styles.ts index 8b22e9569..a183b3c65 100644 --- a/src/components/InstallationWizard/InstallStep/styles.ts +++ b/src/components/InstallationWizard/InstallStep/styles.ts @@ -36,8 +36,8 @@ export const DockerComposeOSTabContentContainer = styled.div` `; export const LoaderContainer = styled.div` - padding: 80px 0 16px; - height: 172px; + padding: 80px 0 10px; + height: 174px; display: flex; justify-content: center; flex-grow: 1; diff --git a/src/components/InstallationWizard/InstallStep/types.ts b/src/components/InstallationWizard/InstallStep/types.ts index b835024a4..bbae2e1b3 100644 --- a/src/components/InstallationWizard/InstallStep/types.ts +++ b/src/components/InstallationWizard/InstallStep/types.ts @@ -1,7 +1,7 @@ -import { ConnectionCheckStatus } from "../types"; +import { AsyncActionStatus } from "../types"; export interface InstallStepProps { - connectionCheckStatus: ConnectionCheckStatus; + connectionCheckStatus: AsyncActionStatus; onConnectionStatusCheck: () => void; onResetConnectionCheckStatus: () => void; onGetDigmaDockerDesktopButtonClick: () => void; diff --git a/src/components/InstallationWizard/ObservabilityStep/styles.ts b/src/components/InstallationWizard/ObservabilityStep/styles.ts index 09faf1f67..35391fb80 100644 --- a/src/components/InstallationWizard/ObservabilityStep/styles.ts +++ b/src/components/InstallationWizard/ObservabilityStep/styles.ts @@ -11,7 +11,6 @@ export const Container = styled.div` export const ObservabilityContainer = styled.div` padding: 20px; margin: 8px 0 12px; - box-sizing: border-box; border-radius: 4px; display: flex; flex-direction: column; diff --git a/src/components/InstallationWizard/Step/types.ts b/src/components/InstallationWizard/Step/types.ts index c07444c45..dbd325e83 100644 --- a/src/components/InstallationWizard/Step/types.ts +++ b/src/components/InstallationWizard/Step/types.ts @@ -8,6 +8,7 @@ export interface TransitionProps { } export interface StepData { + key: string; title: string; content: ReactNode; } diff --git a/src/components/InstallationWizard/Tabs/Tab/index.tsx b/src/components/InstallationWizard/Tabs/Tab/index.tsx index 1fd8f9ab1..5127e3dd9 100644 --- a/src/components/InstallationWizard/Tabs/Tab/index.tsx +++ b/src/components/InstallationWizard/Tabs/Tab/index.tsx @@ -67,18 +67,20 @@ export const Tab = (props: TabProps) => { onBlur={handleBlur} fullWidth={props.fullWidth} > - {props.icon && ( - - )} + + {props.icon && ( + + )} + {props.children} ); diff --git a/src/components/InstallationWizard/Tabs/Tab/styles.ts b/src/components/InstallationWizard/Tabs/Tab/styles.ts index 6f633e6a9..1a2398948 100644 --- a/src/components/InstallationWizard/Tabs/Tab/styles.ts +++ b/src/components/InstallationWizard/Tabs/Tab/styles.ts @@ -2,7 +2,6 @@ import styled from "styled-components"; import { TabProps } from "./types"; export const Container = styled.li` - box-sizing: border-box; font-weight: 500; font-size: 12px; line-height: 14px; @@ -10,6 +9,8 @@ export const Container = styled.li` display: flex; gap: 4px; user-select: none; + text-align: center; + align-items: center; cursor: ${({ isDisabled }) => (isDisabled ? "initial" : "pointer")}; border-bottom: ${({ isSelected }) => isSelected ? "3px solid #5154ec" : "none"}; @@ -44,3 +45,8 @@ export const Container = styled.li` }}; } `; + +export const IconContainer = styled.div` + display: flex; + flex-shrink: 0; +`; diff --git a/src/components/InstallationWizard/Tabs/styles.ts b/src/components/InstallationWizard/Tabs/styles.ts index 851f052b7..21d963da9 100644 --- a/src/components/InstallationWizard/Tabs/styles.ts +++ b/src/components/InstallationWizard/Tabs/styles.ts @@ -10,7 +10,6 @@ export const TabList = styled.ul` display: flex; margin: 0; padding: 0; - box-sizing: border-box; border-bottom: 1px solid ${({ theme }) => { switch (theme.mode) { diff --git a/src/components/InstallationWizard/index.tsx b/src/components/InstallationWizard/index.tsx index 2f9192840..3ad3a2f6e 100644 --- a/src/components/InstallationWizard/index.tsx +++ b/src/components/InstallationWizard/index.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, useEffect, useRef, useState } from "react"; +import { ChangeEvent, useContext, useEffect, useRef, useState } from "react"; import { CSSTransition } from "react-transition-group"; import { useTheme } from "styled-components"; import { SLACK_WORKSPACE_URL } from "../../constants"; @@ -7,9 +7,9 @@ import { IDE } from "../../globals"; import { useDebounce } from "../../hooks/useDebounce"; import { usePrevious } from "../../hooks/usePrevious"; import { ide } from "../../platform"; -import { isString } from "../../typeGuards/isString"; import { addPrefix } from "../../utils/addPrefix"; import { actions as globalActions } from "../common/App"; +import { ConfigContext } from "../common/App/ConfigContext"; import { getThemeKind } from "../common/App/styles"; import { CloudDownloadIcon } from "../common/icons/CloudDownloadIcon"; import { DigmaGreetingIcon } from "../common/icons/DigmaGreetingIcon"; @@ -24,8 +24,8 @@ import { StepData, StepStatus } from "./Step/types"; import * as s from "./styles"; import { trackingEvents } from "./tracking"; import { - ConnectionCheckResultData, - ConnectionCheckStatus, + AsyncActionResultData, + AsyncActionStatus, InstallationType } from "./types"; @@ -34,11 +34,19 @@ const EMAIL_ADDRESS_REGEX = const ACTION_PREFIX = "INSTALLATION_WIZARD"; -const actions = addPrefix(ACTION_PREFIX, { +export const actions = addPrefix(ACTION_PREFIX, { FINISH: "FINISH", CHECK_CONNECTION: "CHECK_CONNECTION", SET_CONNECTION_CHECK_RESULT: "SET_CONNECTION_CHECK_RESULT", - SET_OBSERVABILITY: "SET_OBSERVABILITY" + SET_OBSERVABILITY: "SET_OBSERVABILITY", + INSTALL_DIGMA_ENGINE: "INSTALL_DIGMA_ENGINE", + UNINSTALL_DIGMA_ENGINE: "UNINSTALL_DIGMA_ENGINE", + START_DIGMA_ENGINE: "START_DIGMA_ENGINE", + STOP_DIGMA_ENGINE: "STOP_DIGMA_ENGINE", + SET_INSTALL_DIGMA_ENGINE_RESULT: "SET_INSTALL_DIGMA_ENGINE_RESULT", + SET_UNINSTALL_DIGMA_ENGINE_RESULT: "SET_UNINSTALL_DIGMA_ENGINE_RESULT", + SET_START_DIGMA_ENGINE_RESULT: "SET_START_DIGMA_ENGINE_RESULT", + SET_STOP_DIGMA_ENGINE_RESULT: "SET_STOP_DIGMA_ENGINE_RESULT" }); const DIGMA_DOCKER_EXTENSION_URL = @@ -58,18 +66,9 @@ const quickstartURL = getQuickstartURL(ide); const footerTransitionClassName = "footer"; const TRANSITION_DURATION = 300; // in milliseconds -const firstStep = window.wizardSkipInstallationStep === true ? 1 : 0; - -const preselectedIsObservabilityEnabled = - window.isObservabilityEnabled === true; +const isFirstLaunch = window.wizardFirstLaunch === true; -const preselectedEmail = isString(window.userEmail) ? window.userEmail : ""; - -// TO DO: -// add environment variable for presetting the correct installation type -// if Digma already installed -const preselectedInstallationType = - window.wizardSkipInstallationStep === true ? "local" : undefined; +const firstStep = window.wizardSkipInstallationStep === true ? 1 : 0; const getStepStatus = (index: number, currentStep: number): StepStatus => { if (index < currentStep) { @@ -88,21 +87,31 @@ const validateEmailFormat = (email: string): boolean => { }; export const InstallationWizard = () => { + const config = useContext(ConfigContext); const [currentStep, setCurrentStep] = useState(firstStep); const previousStep = usePrevious(currentStep); const [isAlreadyUsingOtel, setIsAlreadyUsingOtel] = useState(false); const [isObservabilityEnabled, setIsObservabilityEnabled] = useState( - preselectedIsObservabilityEnabled + config.isObservabilityEnabled ); const [connectionCheckStatus, setConnectionCheckStatus] = - useState(); + useState(); const footerContentRef = useRef(null); + + // TO DO: + // add environment variable for presetting the correct installation type + // if Digma already installed + const preselectedInstallationType = + window.wizardSkipInstallationStep === true || config.isDigmaEngineInstalled + ? "local" + : undefined; const [installationType, setInstallationType] = useState< InstallationType | undefined >(preselectedInstallationType); + const theme = useTheme(); const themeKind = getThemeKind(theme); - const [email, setEmail] = useState(preselectedEmail); + const [email, setEmail] = useState(config.userEmail); const [isEmailValid, setIsEmailValid] = useState( email.length > 0 ? validateEmailFormat(email) : undefined ); @@ -129,7 +138,22 @@ export const InstallationWizard = () => { } }); } - }, [previousStep, currentStep]); + + if ( + previousStep === 1 && + currentStep === 2 && + isFirstLaunch && + !isObservabilityEnabled + ) { + setIsObservabilityEnabled(true); + window.sendMessageToDigma({ + action: actions.SET_OBSERVABILITY, + payload: { + isObservabilityEnabled: true + } + }); + } + }, [previousStep, currentStep, isObservabilityEnabled]); useEffect(() => { if (firstStep === 1) { @@ -142,7 +166,7 @@ export const InstallationWizard = () => { } const handleConnectionCheckResultData = (data: unknown) => { - const result = (data as ConnectionCheckResultData).result; + const result = (data as AsyncActionResultData).result; setConnectionCheckStatus(result); }; @@ -263,6 +287,7 @@ export const InstallationWizard = () => { const steps: StepData[] = [ { + key: "install", title: "Get Digma up and running", content: ( { ...(ide === "IDEA" ? [ { + key: "observability", title: isAlreadyUsingOtel ? "If you're already using OpenTelemetry…" : "Observe your application", @@ -297,6 +323,7 @@ export const InstallationWizard = () => { ] : []), { + key: "finish", title: "You're done!", content: ( { )} - {steps.map((step, i) => ( - - ))} + {installationType && + steps.map((step, i) => ( + + ))} {installationType && ( diff --git a/src/components/InstallationWizard/tracking.ts b/src/components/InstallationWizard/tracking.ts index b2e9a5f9c..49fb6cb9a 100644 --- a/src/components/InstallationWizard/tracking.ts +++ b/src/components/InstallationWizard/tracking.ts @@ -11,7 +11,8 @@ export const trackingEvents = addPrefix( "get digma docker extension button clicked", OBSERVABILITY_BUTTON_CLICKED: "set observability button clicked", TAB_CLICKED: "tab clicked", - NO_DOCKER_SLACK_LINK_CLICKED: "no docker slack link clicked" + NO_DOCKER_SLACK_LINK_CLICKED: "no docker slack link clicked", + ENGINE_ACTION_BUTTON_CLICKED: "engine action button clicked" }, " " ); diff --git a/src/components/InstallationWizard/types.ts b/src/components/InstallationWizard/types.ts index c4864ca03..7dfd1fad8 100644 --- a/src/components/InstallationWizard/types.ts +++ b/src/components/InstallationWizard/types.ts @@ -1,12 +1,14 @@ -export type ConnectionCheckResult = "success" | "failure"; +export type AsyncActionResult = "success" | "failure"; -export type ConnectionCheckStatus = - | ConnectionCheckResult - | "pending" - | undefined; +export type AsyncActionStatus = AsyncActionResult | "pending" | undefined; -export interface ConnectionCheckResultData { - result: ConnectionCheckResult; +export interface AsyncActionResultData { + result: AsyncActionResult; + error?: string; } export type InstallationType = "local" | "cloud"; + +export interface SetCurrentStepData { + currentStep: string; +} diff --git a/src/components/RecentActivity/LiveView/styles.ts b/src/components/RecentActivity/LiveView/styles.ts index 1bd8a4a53..7066ae780 100644 --- a/src/components/RecentActivity/LiveView/styles.ts +++ b/src/components/RecentActivity/LiveView/styles.ts @@ -152,7 +152,6 @@ export const ZoomButtonsContainer = styled.div` `; export const ZoomButton = styled.button` - box-sizing: border-box; display: flex; padding: 4px; border-radius: 4px; diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index 6922d4264..a6a78ce1a 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -1,11 +1,10 @@ import { Allotment } from "allotment"; import "allotment/dist/style.css"; -import { useEffect, useMemo, useState } from "react"; +import { useContext, useEffect, useMemo, useState } from "react"; import { dispatcher } from "../../dispatcher"; import { usePrevious } from "../../hooks/usePrevious"; import { addPrefix } from "../../utils/addPrefix"; import { groupBy } from "../../utils/groupBy"; -import { actions as globalActions } from "../common/App"; import { CursorFollower } from "../common/CursorFollower"; import { DigmaLogoFlatIcon } from "../common/icons/DigmaLogoFlatIcon"; import { EnvironmentPanel } from "./EnvironmentPanel"; @@ -16,12 +15,8 @@ import { RecentActivityTable, isRecent } from "./RecentActivityTable"; import * as s from "./styles"; import { isString } from "../../typeGuards/isString"; -import { - EntrySpan, - RecentActivityData, - RecentActivityProps, - SetIsJaegerData -} from "./types"; +import { ConfigContext } from "../common/App/ConfigContext"; +import { EntrySpan, RecentActivityData, RecentActivityProps } from "./types"; const documentationURL = isString(window.recentActivityDocumentationURL) ? window.recentActivityDocumentationURL @@ -69,10 +64,8 @@ export const RecentActivity = (props: RecentActivityProps) => { const [data, setData] = useState(); const previousSelectedEnvironment = usePrevious(selectedEnvironment); const [viewMode, setViewMode] = useState("table"); - const [isJaegerEnabled, setIsJaegerEnabled] = useState( - window.isJaegerEnabled === true - ); const [liveData, setLiveData] = useState(); + const config = useContext(ConfigContext); useEffect(() => { window.sendMessageToDigma({ @@ -83,19 +76,11 @@ export const RecentActivity = (props: RecentActivityProps) => { setData(data as RecentActivityData); }; - const handleSetIsJaegerEnabledData = (data: unknown) => { - setIsJaegerEnabled((data as SetIsJaegerData).isJaegerEnabled); - }; - const handleLiveData = (data: unknown) => { setLiveData(data as LiveData); }; dispatcher.addActionListener(actions.SET_DATA, handleRecentActivityData); - dispatcher.addActionListener( - globalActions.SET_IS_JAEGER_ENABLED, - handleSetIsJaegerEnabledData - ); dispatcher.addActionListener(actions.SET_LIVE_DATA, handleLiveData); return () => { @@ -103,10 +88,6 @@ export const RecentActivity = (props: RecentActivityProps) => { actions.SET_DATA, handleRecentActivityData ); - dispatcher.removeActionListener( - globalActions.SET_IS_JAEGER_ENABLED, - handleSetIsJaegerEnabledData - ); }; }, []); @@ -212,7 +193,7 @@ export const RecentActivity = (props: RecentActivityProps) => { data={environmentActivities[selectedEnvironment]} onSpanLinkClick={handleSpanLinkClick} onTraceButtonClick={handleTraceButtonClick} - isTraceButtonVisible={isJaegerEnabled} + isTraceButtonVisible={config.isJaegerEnabled} /> )} diff --git a/src/components/common/App/ConfigContext.ts b/src/components/common/App/ConfigContext.ts new file mode 100644 index 000000000..d201abff5 --- /dev/null +++ b/src/components/common/App/ConfigContext.ts @@ -0,0 +1,12 @@ +import { createContext } from "react"; +import { isString } from "../../../typeGuards/isString"; + +export const ConfigContext = createContext({ + isObservabilityEnabled: window.isObservabilityEnabled === true, + isJaegerEnabled: window.isJaegerEnabled === true, + isDigmaEngineInstalled: window.isDigmaEngineInstalled === true, + isDigmaEngineRunning: window.isDigmaEngineRunning === true, + isDockerInstalled: window.isDockerInstalled === true, + isDockerComposeInstalled: window.isDockerComposeInstalled === true, + userEmail: isString(window.userEmail) ? window.userEmail : "" +}); diff --git a/src/components/common/App/index.tsx b/src/components/common/App/index.tsx index cf6e5d32e..8a0c3cb4f 100644 --- a/src/components/common/App/index.tsx +++ b/src/components/common/App/index.tsx @@ -1,10 +1,12 @@ -import { useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { ThemeProvider } from "styled-components"; import { dispatcher } from "../../../dispatcher"; import { Mode } from "../../../globals"; +import { isBoolean } from "../../../typeGuards/isBoolean"; import { isObject } from "../../../typeGuards/isObject"; import { isString } from "../../../typeGuards/isString"; import { addPrefix } from "../../../utils/addPrefix"; +import { ConfigContext } from "./ConfigContext"; import { GlobalStyle } from "./styles"; import { AppProps } from "./types"; @@ -33,7 +35,11 @@ export const actions = addPrefix(ACTION_PREFIX, { SET_CODE_FONT: "SET_CODE_FONT", OPEN_URL_IN_DEFAULT_BROWSER: "OPEN_URL_IN_DEFAULT_BROWSER", SEND_TRACKING_EVENT: "SEND_TRACKING_EVENT", - SET_IS_JAEGER_ENABLED: "SET_IS_JAEGER_ENABLED" + SET_IS_JAEGER_ENABLED: "SET_IS_JAEGER_ENABLED", + SET_IS_DIGMA_ENGINE_INSTALLED: "SET_IS_DIGMA_ENGINE_INSTALLED", + SET_IS_DIGMA_ENGINE_RUNNING: "SET_IS_DIGMA_ENGINE_RUNNING", + SET_IS_DOCKER_INSTALLED: "SET_IS_DOCKER_INSTALLED", + SET_IS_DOCKER_COMPOSE_INSTALLED: "SET_IS_DOCKER_COMPOSE_INSTALLED" }); const defaultMainFont = isString(window.mainFont) ? window.mainFont : ""; @@ -43,6 +49,7 @@ export const App = (props: AppProps) => { const [mode, setMode] = useState(getMode()); const [mainFont, setMainFont] = useState(defaultMainFont); const [codeFont, setCodeFont] = useState(defaultCodeFont); + const [config, setConfig] = useState(useContext(ConfigContext)); useEffect(() => { if (!props.theme) { @@ -70,23 +77,110 @@ export const App = (props: AppProps) => { } }; + const handleSetIsJaegerEnabled = (data: unknown) => { + if (isObject(data) && isBoolean(data.isJaegerEnabled)) { + setConfig((config) => ({ + ...config, + isJaegerEnabled: data.isJaegerEnabled as boolean + })); + } + }; + + const handleSetIsDigmaEngineInstalled = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDigmaEngineInstalled)) { + setConfig((config) => ({ + ...config, + isDigmaEngineInstalled: data.isDigmaEngineInstalled as boolean + })); + } + }; + + const handleSetIsDigmaEngineRunning = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDigmaEngineRunning)) { + setConfig((config) => ({ + ...config, + isDigmaEngineRunning: data.isDigmaEngineRunning as boolean + })); + } + }; + + const handleSetIsDockerInstalled = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDockerInstalled)) { + setConfig((config) => ({ + ...config, + isDockerInstalled: data.isDockerInstalled as boolean + })); + } + }; + + const handleSetIsDockerComposeInstalled = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDockerComposeInstalled)) { + setConfig((config) => ({ + ...config, + isDockerComposeInstalled: data.isDockerComposeInstalled as boolean + })); + } + }; + dispatcher.addActionListener(actions.SET_THEME, handleSetTheme); dispatcher.addActionListener(actions.SET_MAIN_FONT, handleSetMainFont); dispatcher.addActionListener(actions.SET_CODE_FONT, handleSetCodeFont); + dispatcher.addActionListener( + actions.SET_IS_JAEGER_ENABLED, + handleSetIsJaegerEnabled + ); + dispatcher.addActionListener( + actions.SET_IS_DIGMA_ENGINE_INSTALLED, + handleSetIsDigmaEngineInstalled + ); + dispatcher.addActionListener( + actions.SET_IS_DIGMA_ENGINE_RUNNING, + handleSetIsDigmaEngineRunning + ); + dispatcher.addActionListener( + actions.SET_IS_DOCKER_INSTALLED, + handleSetIsDockerInstalled + ); + dispatcher.addActionListener( + actions.SET_IS_DOCKER_COMPOSE_INSTALLED, + handleSetIsDockerComposeInstalled + ); return () => { dispatcher.removeActionListener(actions.SET_THEME, handleSetTheme); dispatcher.removeActionListener(actions.SET_MAIN_FONT, handleSetMainFont); dispatcher.removeActionListener(actions.SET_CODE_FONT, handleSetCodeFont); + dispatcher.removeActionListener( + actions.SET_IS_JAEGER_ENABLED, + handleSetIsJaegerEnabled + ); + dispatcher.removeActionListener( + actions.SET_IS_DIGMA_ENGINE_INSTALLED, + handleSetIsDigmaEngineInstalled + ); + dispatcher.removeActionListener( + actions.SET_IS_DIGMA_ENGINE_RUNNING, + handleSetIsDigmaEngineRunning + ); + dispatcher.removeActionListener( + actions.SET_IS_DOCKER_INSTALLED, + handleSetIsDockerInstalled + ); + dispatcher.removeActionListener( + actions.SET_IS_DOCKER_COMPOSE_INSTALLED, + handleSetIsDockerComposeInstalled + ); }; }, []); return ( <> - - - {props.children} - + + + + {props.children} + + ); }; diff --git a/src/components/common/Button/Button.stories.tsx b/src/components/common/Button/Button.stories.tsx new file mode 100644 index 000000000..dbe803585 --- /dev/null +++ b/src/components/common/Button/Button.stories.tsx @@ -0,0 +1,33 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { Button } from "."; +import { UserIcon } from "../icons/UserIcon"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Common/Button", + component: Button, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + children: "Click me", + // onClick?: MouseEventHandler; + disabled: false + } +}; + +export const WithIcon: Story = { + args: { + children: "Click me", + icon: { component: UserIcon }, + disabled: false + } +}; diff --git a/src/components/common/Button/index.tsx b/src/components/common/Button/index.tsx index 5fb2bc50c..d7614c4d0 100644 --- a/src/components/common/Button/index.tsx +++ b/src/components/common/Button/index.tsx @@ -11,6 +11,46 @@ const getIconColor = ( isPressed: boolean, buttonType: ButtonType ): string => { + if (buttonType === "tertiary") { + if (isDisabled) { + switch (theme.mode) { + case "light": + return "#b9c0d4"; + case "dark": + case "dark-jetbrains": + return "#49494d"; + } + } + + if (isPressed) { + switch (theme.mode) { + case "light": + return "#5154ec"; + case "dark": + case "dark-jetbrains": + return "#7891d0"; + } + } + + if (isHovered || isFocused) { + switch (theme.mode) { + case "light": + return "#5154ec"; + case "dark": + case "dark-jetbrains": + return "#92affa"; + } + } + + switch (theme.mode) { + case "light": + return "#3538cd"; + case "dark": + case "dark-jetbrains": + return "#e2e7ff"; + } + } + if (buttonType === "secondary") { if (isDisabled) { switch (theme.mode) { @@ -47,7 +87,7 @@ const getIconColor = ( return "#3538cd"; case "dark": case "dark-jetbrains": - return "#b9c2eb"; + return "#e2e7ff"; } } @@ -64,7 +104,7 @@ const getIconColor = ( if (isPressed) { switch (theme.mode) { case "light": - return "#f1f5fa"; + return "#fbfdff"; case "dark": case "dark-jetbrains": return "#dadada"; @@ -72,16 +112,10 @@ const getIconColor = ( } if (isFocused || isHovered) { - switch (theme.mode) { - case "light": - return "#e2e7ff"; - case "dark": - case "dark-jetbrains": - return "#b9c2eb"; - } + return "#e2e7ff"; } - return "#b9c2eb"; + return "#e2e7ff"; }; export const Button = (props: ButtonProps) => { diff --git a/src/components/common/Button/styles.ts b/src/components/common/Button/styles.ts index b39b6d26f..b9c8dfb3b 100644 --- a/src/components/common/Button/styles.ts +++ b/src/components/common/Button/styles.ts @@ -5,8 +5,9 @@ export const Button = styled.button` font-family: inherit; font-weight: 500; font-size: 12px; - line-height: 14px; + line-height: normal; padding: 4px 8px; + height: 22px; border-radius: 2px; cursor: pointer; display: flex; @@ -15,40 +16,42 @@ export const Button = styled.button` width: max-content; user-select: none; color: ${({ theme, buttonType }) => { - if (buttonType === "secondary") { + if (buttonType === "tertiary") { switch (theme.mode) { case "light": return "#3538cd"; case "dark": case "dark-jetbrains": - return "#b9c2eb"; + return "#e2e7ff"; } } - return "#b9c2eb"; - }}; - background: ${({ theme, buttonType }) => { if (buttonType === "secondary") { switch (theme.mode) { case "light": - return "none"; + return "#3538cd"; case "dark": case "dark-jetbrains": - return "#414363"; + return "#e2e7ff"; } } + return "#e2e7ff"; + }}; + background: ${({ buttonType }) => { + if (buttonType === "tertiary") { + return "none"; + } + + if (buttonType === "secondary") { + return "none"; + } + return "#3538cd"; }}; - border: ${({ theme, buttonType }) => { + border: ${({ buttonType }) => { if (buttonType === "secondary") { - switch (theme.mode) { - case "light": - return "1px solid #3538cd"; - case "dark": - case "dark-jetbrains": - return "1px solid #5154ec"; - } + return "1px solid #3538cd"; } return "none"; @@ -57,35 +60,37 @@ export const Button = styled.button` &:hover, &:focus { color: ${({ theme, buttonType }) => { - if (buttonType === "secondary") { + if (buttonType === "tertiary") { switch (theme.mode) { case "light": return "#5154ec"; case "dark": case "dark-jetbrains": - return "#e2e7ff"; + return "#92affa"; } } - switch (theme.mode) { - case "light": - return "#e2e7ff"; - case "dark": - case "dark-jetbrains": - return "#b9c2eb"; - } - }}; - background: ${({ theme, buttonType }) => { if (buttonType === "secondary") { switch (theme.mode) { case "light": - return "#eeeefd"; + return "#5154ec"; case "dark": case "dark-jetbrains": - return "#414363"; + return "#e2e7ff"; } } + return "#e2e7ff"; + }}; + background: ${({ buttonType }) => { + if (buttonType === "tertiary") { + return "none"; + } + + if (buttonType === "secondary") { + return "none"; + } + return "#5154ec"; }}; border: ${({ buttonType }) => { @@ -99,6 +104,16 @@ export const Button = styled.button` &:active { color: ${({ theme, buttonType }) => { + if (buttonType === "tertiary") { + switch (theme.mode) { + case "light": + return "#5154ec"; + case "dark": + case "dark-jetbrains": + return "#7891d0"; + } + } + if (buttonType === "secondary") { switch (theme.mode) { case "light": @@ -111,34 +126,26 @@ export const Button = styled.button` switch (theme.mode) { case "light": - return "#f1f5fa"; + return "#fbfdff"; case "dark": case "dark-jetbrains": return "#dadada"; } }}; - background: ${({ theme, buttonType }) => { + background: ${({ buttonType }) => { + if (buttonType === "tertiary") { + return "none"; + } + if (buttonType === "secondary") { - switch (theme.mode) { - case "light": - return "#eeeefd"; - case "dark": - case "dark-jetbrains": - return "#414363"; - } + return "none"; } return "#3538cd"; }}; - border: ${({ theme, buttonType }) => { + border: ${({ buttonType }) => { if (buttonType === "secondary") { - switch (theme.mode) { - case "light": - return "1px solid #3538cd"; - case "dark": - case "dark-jetbrains": - return "1px solid #5154ec"; - } + return "1px solid #3538cd"; } return "none"; @@ -148,6 +155,16 @@ export const Button = styled.button` &:disabled { cursor: initial; color: ${({ theme, buttonType }) => { + if (buttonType === "tertiary") { + switch (theme.mode) { + case "light": + return "#b9c0d4"; + case "dark": + case "dark-jetbrains": + return "#49494d"; + } + } + if (buttonType === "secondary") { switch (theme.mode) { case "light": @@ -167,6 +184,10 @@ export const Button = styled.button` } }}; background: ${({ theme, buttonType }) => { + if (buttonType === "tertiary") { + return "none"; + } + if (buttonType === "secondary") { return "none"; } diff --git a/src/components/common/Button/types.ts b/src/components/common/Button/types.ts index 6a6983a35..d5b43c98d 100644 --- a/src/components/common/Button/types.ts +++ b/src/components/common/Button/types.ts @@ -1,7 +1,7 @@ import { ComponentType, MouseEventHandler, ReactNode } from "react"; import { IconProps } from "../icons/types"; -export type ButtonType = "primary" | "secondary"; +export type ButtonType = "primary" | "secondary" | "tertiary"; export interface ButtonProps { icon?: { diff --git a/src/components/common/Loader/index.tsx b/src/components/common/Loader/index.tsx index 06c76c3bd..35c3cab4a 100644 --- a/src/components/common/Loader/index.tsx +++ b/src/components/common/Loader/index.tsx @@ -355,7 +355,7 @@ const LoaderComponent = (props: LoaderProps) => { { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const CrossCircleIcon = React.memo(CrossCircleIconComponent); diff --git a/src/components/common/icons/DigmaLogoIcon.tsx b/src/components/common/icons/DigmaLogoIcon.tsx index e6624cc9c..205dec86d 100644 --- a/src/components/common/icons/DigmaLogoIcon.tsx +++ b/src/components/common/icons/DigmaLogoIcon.tsx @@ -1,8 +1,8 @@ import React from "react"; import { useIconProps } from "./hooks"; -import { IconProps } from "./types"; +import { ThemeableIconProps } from "./types"; -const DigmaLogoIconComponent = (props: IconProps) => { +const DigmaLogoIconComponent = (props: ThemeableIconProps) => { const { size } = useIconProps(props); return ( @@ -11,91 +11,91 @@ const DigmaLogoIconComponent = (props: IconProps) => { width={size} height={size} fill="none" - viewBox="0 0 22 23" + viewBox="0 0 101 100" > ); diff --git a/src/components/common/icons/OpenTelemetryLogoIcon.tsx b/src/components/common/icons/OpenTelemetryLogoIcon.tsx index f9da2a52e..fe9546aa7 100644 --- a/src/components/common/icons/OpenTelemetryLogoIcon.tsx +++ b/src/components/common/icons/OpenTelemetryLogoIcon.tsx @@ -15,19 +15,19 @@ const OpenTelemetryLogoIconComponent = (props: IconProps) => { > diff --git a/src/components/common/icons/StopCircleIcon.tsx b/src/components/common/icons/StopCircleIcon.tsx new file mode 100644 index 000000000..804e36a1f --- /dev/null +++ b/src/components/common/icons/StopCircleIcon.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const StopCircleIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const StopCircleIcon = React.memo(StopCircleIconComponent); diff --git a/src/globals.d.ts b/src/globals.d.ts index 84ffeed7b..b8aca3e90 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -28,14 +28,19 @@ declare global { mainFont?: unknown; codeFont?: unknown; isJaegerEnabled?: unknown; - isObservabilityEnabled?: unknown; userEmail?: unknown; - recentActivityExpirationLimit?: unknown; - recentActivityDocumentationURL?: unknown; - wizardSkipInstallationStep?: unknown; + isObservabilityEnabled?: unknown; + isDigmaEngineInstalled?: unknown; + isDigmaEngineRunning?: unknown; + isDockerInstalled?: unknown; + isDockerComposeInstalled?: unknown; assetsRefreshInterval?: unknown; assetsSearch?: unknown; insightsRefreshInterval?: unknown; + recentActivityExpirationLimit?: unknown; + recentActivityDocumentationURL?: unknown; + wizardSkipInstallationStep?: unknown; + wizardFirstLaunch?: unknown; } } diff --git a/src/typeGuards/isBoolean.ts b/src/typeGuards/isBoolean.ts new file mode 100644 index 000000000..46342346f --- /dev/null +++ b/src/typeGuards/isBoolean.ts @@ -0,0 +1 @@ +export const isBoolean = (x: unknown): x is boolean => typeof x === "boolean"; diff --git a/src/types.ts b/src/types.ts index 28ef51026..cab19d70d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,11 +16,13 @@ export enum InsightType { SpanNPlusOne = "SpaNPlusOne", SpanEndpointBottleneck = "SpanEndpointBottleneck", SpanDurations = "SpanDurations", - SpanScaling = "SpanScaling", + SpanScalingBadly = "SpanScaling", SpanScalingRootCause = "SpanScalingRootCause", SpanDurationBreakdown = "SpanDurationBreakdown", EndpointDurationSlowdown = "EndpointDurationSlowdown", - EndpointBreakdown = "EndpointBreakdown" + EndpointBreakdown = "EndpointBreakdown", + SpanScalingWell = "SpanScalingWell", + SpanScalingInsufficientData = "SpanScalingInsufficientData" } export enum InsightImportance { @@ -41,7 +43,7 @@ export interface SpanInfo { instrumentationLibrary: string; spanCodeObjectId: string; methodCodeObjectId: string | null; - kind: string; + kind: string | null; /** * @deprecated diff --git a/src/utils/getInsightTypeInfo.ts b/src/utils/getInsightTypeInfo.ts index ba589a2ff..14d39ba23 100644 --- a/src/utils/getInsightTypeInfo.ts +++ b/src/utils/getInsightTypeInfo.ts @@ -65,7 +65,7 @@ export const getInsightTypeInfo = ( icon: BottleneckIcon, label: "Bottleneck" }, - [InsightType.SpanScaling]: { + [InsightType.SpanScalingBadly]: { icon: ScalesIcon, label: "Scaling Issue Found" }, @@ -92,6 +92,14 @@ export const getInsightTypeInfo = ( [InsightType.EndpointBreakdown]: { icon: PieChartIcon, label: "Request Breakdown" + }, + [InsightType.SpanScalingWell]: { + icon: ScalesIcon, + label: "No Scaling Issue Detected" + }, + [InsightType.SpanScalingInsufficientData]: { + icon: ScalesIcon, + label: "Performance at Scale" } }; diff --git a/src/utils/getInsightTypeOrderPriority.ts b/src/utils/getInsightTypeOrderPriority.ts index f8a03cfa8..e259bcca7 100644 --- a/src/utils/getInsightTypeOrderPriority.ts +++ b/src/utils/getInsightTypeOrderPriority.ts @@ -8,7 +8,7 @@ export const getInsightTypeOrderPriority = (type: string): number => { [InsightType.SpanDurations]: 60, [InsightType.SpanUsages]: 61, - [InsightType.SpanScaling]: 63, + [InsightType.SpanScalingBadly]: 63, [InsightType.SpanNPlusOne]: 65, [InsightType.SpanDurationChange]: 66, [InsightType.SpanEndpointBottleneck]: 67,