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,