From 1b577d50476fef2b041b6eb4033e14d45af4721a Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 14 Jul 2023 20:44:32 +0200 Subject: [PATCH 01/20] Add Automatic installer --- .storybook/preview-body.html | 13 +- assets/assets/index.ejs | 9 +- assets/installationWizard/index.ejs | 6 +- assets/recentActivity/index.ejs | 8 +- .../InstallationWizard/FinishStep/styles.ts | 1 - .../AutomaticInstaller.stories.tsx | 20 + .../InstallStep/AutomaticInstaller/index.tsx | 347 ++++++++++++++++++ .../InstallStep/AutomaticInstaller/styles.ts | 66 ++++ .../InstallStep/AutomaticInstaller/types.ts | 11 + .../InstallationWizard/InstallStep/index.tsx | 93 +++-- .../InstallationWizard/InstallStep/types.ts | 4 +- .../ObservabilityStep/styles.ts | 1 - .../InstallationWizard/Tabs/Tab/styles.ts | 1 - .../InstallationWizard/Tabs/styles.ts | 1 - src/components/InstallationWizard/index.tsx | 20 +- src/components/InstallationWizard/types.ts | 11 +- .../RecentActivity/LiveView/styles.ts | 1 - .../common/Button/Button.stories.tsx | 33 ++ src/components/common/Button/index.tsx | 14 +- src/components/common/Button/styles.ts | 83 ++--- src/components/common/Button/types.ts | 2 +- src/components/common/Loader/index.tsx | 2 +- src/components/common/Loader/types.ts | 4 +- .../common/icons/CrossCircleIcon.tsx | 24 ++ src/components/common/icons/DigmaLogoIcon.tsx | 88 ++--- .../common/icons/OpenTelemetryLogoIcon.tsx | 8 +- .../common/icons/StopCircleIcon.tsx | 24 ++ src/globals.d.ts | 10 +- src/types.ts | 8 +- src/utils/getInsightTypeInfo.ts | 10 +- src/utils/getInsightTypeOrderPriority.ts | 2 +- 31 files changed, 738 insertions(+), 187 deletions(-) create mode 100644 src/components/InstallationWizard/InstallStep/AutomaticInstaller/AutomaticInstaller.stories.tsx create mode 100644 src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx create mode 100644 src/components/InstallationWizard/InstallStep/AutomaticInstaller/styles.ts create mode 100644 src/components/InstallationWizard/InstallStep/AutomaticInstaller/types.ts create mode 100644 src/components/common/Button/Button.stories.tsx create mode 100644 src/components/common/icons/CrossCircleIcon.tsx create mode 100644 src/components/common/icons/StopCircleIcon.tsx diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html index 7667554cd..acc13d900 100644 --- a/.storybook/preview-body.html +++ b/.storybook/preview-body.html @@ -1,16 +1,21 @@ diff --git a/assets/assets/index.ejs b/assets/assets/index.ejs index 518434c7f..0e3f165b4 100644 --- a/assets/assets/index.ejs +++ b/assets/assets/index.ejs @@ -15,10 +15,17 @@
diff --git a/assets/installationWizard/index.ejs b/assets/installationWizard/index.ejs index 82f93247a..f4d44ed87 100644 --- a/assets/installationWizard/index.ejs +++ b/assets/installationWizard/index.ejs @@ -15,13 +15,17 @@
diff --git a/assets/recentActivity/index.ejs b/assets/recentActivity/index.ejs index 665dac875..eefcc7254 100644 --- a/assets/recentActivity/index.ejs +++ b/assets/recentActivity/index.ejs @@ -15,11 +15,17 @@
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/AutomaticInstaller/AutomaticInstaller.stories.tsx b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/AutomaticInstaller.stories.tsx new file mode 100644 index 000000000..d34e4e223 --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/AutomaticInstaller.stories.tsx @@ -0,0 +1,20 @@ +import { Meta, StoryObj } from "@storybook/react"; + +import { AutomaticInstaller } from "."; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Installation Wizard/AutomaticInstaller", + component: AutomaticInstaller, + 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/AutomaticInstaller/index.tsx b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx new file mode 100644 index 000000000..fe69a1ea7 --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx @@ -0,0 +1,347 @@ +import { useEffect, useState } from "react"; +import { useTheme } from "styled-components"; +import { actions } from "../.."; +import { dispatcher } from "../../../../dispatcher"; +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 { MainButton } from "../../styles"; +import { AsyncActionResultData, AsyncActionStatus } from "../../types"; +import * as s from "./styles"; +import { AutomaticInstallerProps, Operation } 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 AutomaticInstaller = (props: AutomaticInstallerProps) => { + const [isDigmaInstalled, setIsDigmaInstalled] = useState( + window.isDigmaInstalled === true + ); + const [isDigmaRunning, setIsDigmaRunning] = useState( + window.isDigmaRunning === true + ); + const [currentOperation, setCurrentOperation] = useState<{ + operation: Operation; + status: AsyncActionStatus; + }>(); + const [failedOperation, setFailedOperation] = useState(); + const theme = useTheme(); + const themeKind = getThemeKind(theme); + + const operationsInfo: Record< + Operation, + { + action: string; + } + > = { + [Operation.INSTALL]: { + action: actions.INSTALL_DIGMA + }, + [Operation.UNINSTALL]: { + action: actions.UNINSTALL_DIGMA + }, + [Operation.START]: { + action: actions.START_DIGMA + }, + [Operation.STOP]: { action: actions.STOP_DIGMA } + }; + + useEffect(() => { + const handleInstallDigmaResultData = (data: unknown) => { + const result = (data as AsyncActionResultData).result; + setCurrentOperation({ operation: Operation.INSTALL, status: result }); + }; + + const handleUninstallDigmaResultData = (data: unknown) => { + const result = (data as AsyncActionResultData).result; + setCurrentOperation({ operation: Operation.UNINSTALL, status: result }); + }; + + const handleStartDigmaResultData = (data: unknown) => { + const result = (data as AsyncActionResultData).result; + setCurrentOperation({ operation: Operation.START, status: result }); + }; + + const handleStopDigmaResultData = (data: unknown) => { + const result = (data as AsyncActionResultData).result; + setCurrentOperation({ operation: Operation.STOP, status: result }); + }; + + dispatcher.addActionListener( + actions.SET_INSTALL_DIGMA_RESULT, + handleInstallDigmaResultData + ); + + dispatcher.addActionListener( + actions.SET_UNINSTALL_DIGMA_RESULT, + handleUninstallDigmaResultData + ); + + dispatcher.addActionListener( + actions.SET_START_DIGMA_RESULT, + handleStartDigmaResultData + ); + + dispatcher.addActionListener( + actions.SET_STOP_DIGMA_RESULT, + handleStopDigmaResultData + ); + + return () => { + dispatcher.removeActionListener( + actions.SET_INSTALL_DIGMA_RESULT, + handleInstallDigmaResultData + ); + + dispatcher.removeActionListener( + actions.SET_UNINSTALL_DIGMA_RESULT, + handleUninstallDigmaResultData + ); + + dispatcher.removeActionListener( + actions.SET_START_DIGMA_RESULT, + handleStartDigmaResultData + ); + + dispatcher.removeActionListener( + actions.SET_STOP_DIGMA_RESULT, + handleStopDigmaResultData + ); + }; + }, []); + + useEffect(() => { + if (!isDigmaInstalled) { + window.sendMessageToDigma({ + action: actions.INSTALL_DIGMA + }); + setCurrentOperation({ operation: Operation.INSTALL, status: "pending" }); + } + }, [isDigmaInstalled]); + + // Handle operation result + useEffect(() => { + if (currentOperation) { + if (currentOperation.status === "success") { + switch (currentOperation.operation) { + case Operation.INSTALL: + setIsDigmaInstalled(true); + setIsDigmaRunning(true); + break; + case Operation.UNINSTALL: + setIsDigmaInstalled(false); + props.onManualInstallSelect(); + break; + case Operation.START: + setIsDigmaRunning(true); + break; + case Operation.STOP: + setIsDigmaRunning(false); + break; + } + } + + switch (currentOperation.status) { + case "success": + setCurrentOperation(undefined); + break; + case "failure": + setFailedOperation(currentOperation.operation); + setCurrentOperation(undefined); + break; + case "pending": + setFailedOperation(undefined); + break; + } + } + }, [currentOperation, props.onManualInstallSelect]); + + const handleStartButtonClick = () => { + window.sendMessageToDigma({ + action: actions.START_DIGMA + }); + setCurrentOperation({ operation: Operation.START, status: "pending" }); + }; + + const handleStopButtonClick = () => { + window.sendMessageToDigma({ + action: actions.STOP_DIGMA + }); + setCurrentOperation({ operation: Operation.STOP, status: "pending" }); + }; + + const handleRemoveButtonClick = () => { + window.sendMessageToDigma({ + action: actions.UNINSTALL_DIGMA + }); + setCurrentOperation({ operation: Operation.UNINSTALL, status: "pending" }); + }; + + const handleRetryButtonClick = () => { + if (failedOperation) { + window.sendMessageToDigma({ + action: operationsInfo[failedOperation].action + }); + setCurrentOperation({ operation: failedOperation, status: "pending" }); + } + }; + + const handleInstallManuallyButtonClick = () => { + props.onManualInstallSelect(); + }; + + const handleNextButtonClick = () => { + props.onGoToNextStep(); + }; + + const renderContent = () => { + const loaderStatus = getLoaderStatus( + isDigmaRunning, + currentOperation?.status, + failedOperation + ); + + const icon = loaderStatus ? ( + + ) : ( + + ); + + let title = "Digma Local Analysis Engine "; + + switch (currentOperation?.operation || failedOperation) { + case Operation.START: + case Operation.INSTALL: + title += "Starting"; + break; + case Operation.UNINSTALL: + title += "Removing"; + break; + case Operation.STOP: + title += "Stopping"; + break; + case undefined: + title += isDigmaRunning ? "Running" : "Stopped"; + } + + if (failedOperation) { + title += " Failed"; + } + + const buttons = []; + + if (failedOperation) { + buttons.push( + ...[ + , + + ] + ); + } + + if (isDigmaInstalled) { + if (isDigmaRunning) { + buttons.push( + + ); + } else { + buttons.push( + + ); + } + + buttons.push( + + ); + } + + 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'} + + + You can always start / stop / remove the Digma Engine from the + Digma panel + + + )} + + {!currentOperation && buttonsWithDividers} + + + Next + + + ); + }; + + return {renderContent()}; +}; diff --git a/src/components/InstallationWizard/InstallStep/AutomaticInstaller/styles.ts b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/styles.ts new file mode 100644 index 000000000..fd4ab8d4e --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/styles.ts @@ -0,0 +1,66 @@ +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + padding: 40px 8px 0; +`; + +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; + text-align: center; + font-size: 10px; + font-style: normal; + font-weight: 500; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#828797"; + case "dark": + case "dark-jetbrains": + return "#9b9b9b"; + } + }}; +`; + +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/AutomaticInstaller/types.ts b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/types.ts new file mode 100644 index 000000000..d3368cb4a --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/types.ts @@ -0,0 +1,11 @@ +export interface AutomaticInstallerProps { + onManualInstallSelect: () => void; + onGoToNextStep: () => void; +} + +export enum Operation { + INSTALL = "install", + UNINSTALL = "uninstall", + START = "start", + STOP = "stop" +} diff --git a/src/components/InstallationWizard/InstallStep/index.tsx b/src/components/InstallationWizard/InstallStep/index.tsx index 99f72cadd..82a690708 100644 --- a/src/components/InstallationWizard/InstallStep/index.tsx +++ b/src/components/InstallationWizard/InstallStep/index.tsx @@ -12,6 +12,7 @@ import { CodeSnippet } from "../CodeSnippet"; import { Tabs } from "../Tabs"; import { Link, MainButton, SectionDescription } from "../styles"; import { trackingEvents } from "../tracking"; +import { AutomaticInstaller } from "./AutomaticInstaller"; import * as s from "./styles"; import { InstallStepProps } from "./types"; @@ -33,6 +34,7 @@ export const InstallStep = (props: InstallStepProps) => { const [selectedInstallTab, setSelectedInstallTab] = useState(0); const [selectedDockerComposeOSTab, setSelectedDockerComposeOSTab] = useState(0); + const [isAutomaticInstallation, setIsAutomaticInstallation] = useState(true); const handleInstallDigmaButtonClick = () => { props.onGetDigmaDockerDesktopButtonClick(); @@ -117,6 +119,10 @@ export const InstallStep = (props: InstallStepProps) => { props.onSlackLinkClick(); }; + const handleManualInstallSelect = () => { + setIsAutomaticInstallation(false); + }; + const installTabs = [ { icon: DockerLogoIcon, @@ -197,45 +203,54 @@ export const InstallStep = (props: InstallStepProps) => { return ( - - - - {props.connectionCheckStatus && ( - - )} - - {!isConnectionCheckStarted && ( - - OK, I've installed Digma - - )} - {props.connectionCheckStatus === "pending" && ( - Complete - )} - {props.connectionCheckStatus === "failure" && ( - Retry - )} - {props.connectionCheckStatus === "success" && ( - - Complete - - )} - + {isAutomaticInstallation ? ( + + ) : ( + <> + + + + {props.connectionCheckStatus && ( + + )} + + {!isConnectionCheckStarted && ( + + OK, I've installed Digma + + )} + {props.connectionCheckStatus === "pending" && ( + Complete + )} + {props.connectionCheckStatus === "failure" && ( + Retry + )} + {props.connectionCheckStatus === "success" && ( + + Complete + + )} + + + )} ); }; 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/Tabs/Tab/styles.ts b/src/components/InstallationWizard/Tabs/Tab/styles.ts index 6f633e6a9..e50a602b4 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; 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..6e98228a8 100644 --- a/src/components/InstallationWizard/index.tsx +++ b/src/components/InstallationWizard/index.tsx @@ -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: "INSTALL_DIGMA", + UNINSTALL_DIGMA: "UNINSTALL_DIGMA", + START_DIGMA: "START_DIGMA", + STOP_DIGMA: "STOP_DIGMA", + SET_INSTALL_DIGMA_RESULT: "SET_INSTALL_DIGMA_RESULT", + SET_UNINSTALL_DIGMA_RESULT: "SET_UNINSTALL_DIGMA_RESULT", + SET_START_DIGMA_RESULT: "SET_START_DIGMA_RESULT", + SET_STOP_DIGMA_RESULT: "SET_STOP_DIGMA_RESULT" }); const DIGMA_DOCKER_EXTENSION_URL = @@ -95,7 +103,7 @@ export const InstallationWizard = () => { preselectedIsObservabilityEnabled ); const [connectionCheckStatus, setConnectionCheckStatus] = - useState(); + useState(); const footerContentRef = useRef(null); const [installationType, setInstallationType] = useState< InstallationType | undefined @@ -142,7 +150,7 @@ export const InstallationWizard = () => { } const handleConnectionCheckResultData = (data: unknown) => { - const result = (data as ConnectionCheckResultData).result; + const result = (data as AsyncActionResultData).result; setConnectionCheckStatus(result); }; diff --git a/src/components/InstallationWizard/types.ts b/src/components/InstallationWizard/types.ts index c4864ca03..3421a1cea 100644 --- a/src/components/InstallationWizard/types.ts +++ b/src/components/InstallationWizard/types.ts @@ -1,12 +1,9 @@ -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; } export type InstallationType = "local" | "cloud"; 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/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..1724e2c4e 100644 --- a/src/components/common/Button/index.tsx +++ b/src/components/common/Button/index.tsx @@ -47,7 +47,7 @@ const getIconColor = ( return "#3538cd"; case "dark": case "dark-jetbrains": - return "#b9c2eb"; + return "#e2e7ff"; } } @@ -64,7 +64,7 @@ const getIconColor = ( if (isPressed) { switch (theme.mode) { case "light": - return "#f1f5fa"; + return "#fbfdff"; case "dark": case "dark-jetbrains": return "#dadada"; @@ -72,16 +72,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..184e6aadf 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; @@ -21,34 +22,26 @@ export const Button = styled.button` return "#3538cd"; case "dark": case "dark-jetbrains": - return "#b9c2eb"; + return "#e2e7ff"; } } - return "#b9c2eb"; + return "#e2e7ff"; }}; - background: ${({ theme, buttonType }) => { + background: ${({ buttonType }) => { + if (buttonType === "tertiary") { + return "none"; + } + if (buttonType === "secondary") { - switch (theme.mode) { - case "light": - return "none"; - 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"; @@ -67,23 +60,15 @@ export const Button = styled.button` } } - switch (theme.mode) { - case "light": - return "#e2e7ff"; - case "dark": - case "dark-jetbrains": - return "#b9c2eb"; - } + return "#e2e7ff"; }}; - 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 "#5154ec"; @@ -111,34 +96,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"; @@ -167,6 +144,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..a9e5d0c5d 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -28,14 +28,16 @@ declare global { mainFont?: unknown; codeFont?: unknown; isJaegerEnabled?: unknown; - isObservabilityEnabled?: unknown; userEmail?: unknown; - recentActivityExpirationLimit?: unknown; - recentActivityDocumentationURL?: unknown; - wizardSkipInstallationStep?: unknown; + isObservabilityEnabled?: unknown; + isDigmaInstalled?: unknown; + isDigmaRunning?: unknown; assetsRefreshInterval?: unknown; assetsSearch?: unknown; insightsRefreshInterval?: unknown; + recentActivityExpirationLimit?: unknown; + recentActivityDocumentationURL?: unknown; + wizardSkipInstallationStep?: unknown; } } 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, From 65a3ed961d42bd7a23d3447e7534cfebe6d63159 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Sat, 15 Jul 2023 01:01:00 +0200 Subject: [PATCH 02/20] Add global messages for the Digma Engine Update Button styles --- .../InstallStep/AutomaticInstaller/index.tsx | 10 +-- src/components/InstallationWizard/index.tsx | 14 ++--- src/components/RecentActivity/index.tsx | 29 ++------- src/components/common/App/ConfigContext.ts | 10 +++ src/components/common/App/index.tsx | 61 +++++++++++++++++-- src/components/common/Button/index.tsx | 40 ++++++++++++ src/components/common/Button/styles.ts | 40 ++++++++++++ src/typeGuards/isBoolean.ts | 1 + 8 files changed, 161 insertions(+), 44 deletions(-) create mode 100644 src/components/common/App/ConfigContext.ts create mode 100644 src/typeGuards/isBoolean.ts diff --git a/src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx index fe69a1ea7..76950b77d 100644 --- a/src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx +++ b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx @@ -1,7 +1,8 @@ -import { useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { useTheme } from "styled-components"; import { actions } from "../.."; import { dispatcher } from "../../../../dispatcher"; +import { ConfigContext } from "../../../common/App/ConfigContext"; import { getThemeKind } from "../../../common/App/styles"; import { Button } from "../../../common/Button"; import { Loader } from "../../../common/Loader"; @@ -33,12 +34,11 @@ const getLoaderStatus = ( }; export const AutomaticInstaller = (props: AutomaticInstallerProps) => { + const config = useContext(ConfigContext); const [isDigmaInstalled, setIsDigmaInstalled] = useState( - window.isDigmaInstalled === true - ); - const [isDigmaRunning, setIsDigmaRunning] = useState( - window.isDigmaRunning === true + config.isDigmaInstalled ); + const [isDigmaRunning, setIsDigmaRunning] = useState(config.isDigmaRunning); const [currentOperation, setCurrentOperation] = useState<{ operation: Operation; status: AsyncActionStatus; diff --git a/src/components/InstallationWizard/index.tsx b/src/components/InstallationWizard/index.tsx index 6e98228a8..9269ad20f 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"; @@ -68,11 +68,6 @@ const TRANSITION_DURATION = 300; // in milliseconds const firstStep = window.wizardSkipInstallationStep === true ? 1 : 0; -const preselectedIsObservabilityEnabled = - window.isObservabilityEnabled === true; - -const preselectedEmail = isString(window.userEmail) ? window.userEmail : ""; - // TO DO: // add environment variable for presetting the correct installation type // if Digma already installed @@ -96,11 +91,12 @@ 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(); @@ -110,7 +106,7 @@ export const InstallationWizard = () => { >(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 ); 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..08961fde1 --- /dev/null +++ b/src/components/common/App/ConfigContext.ts @@ -0,0 +1,10 @@ +import { createContext } from "react"; +import { isString } from "../../../typeGuards/isString"; + +export const ConfigContext = createContext({ + isObservabilityEnabled: window.isObservabilityEnabled === true, + isJaegerEnabled: window.isJaegerEnabled === true, + isDigmaInstalled: window.isDigmaInstalled === true, + isDigmaRunning: window.isDigmaRunning === 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..f3a451435 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,9 @@ 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_INSTALLED: "SET_IS_DIGMA_INSTALLED", + SET_IS_DIGMA_RUNNING: "SET_IS_DIGMA_RUNNING" }); const defaultMainFont = isString(window.mainFont) ? window.mainFont : ""; @@ -43,6 +47,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 +75,67 @@ export const App = (props: AppProps) => { } }; + const handleSetIsJaegerEnabled = (data: unknown) => { + if (isObject(data) && isBoolean(data.isJaegerEnabled)) { + setConfig({ ...config, isJaegerEnabled: data.isJaegerEnabled }); + } + }; + + const handleSetIsDigmaInstalled = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDigmaInstalled)) { + setConfig({ ...config, isDigmaInstalled: data.isDigmaInstalled }); + } + }; + + const handleSetIsDigmaRunning = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDigmaRunning)) { + setConfig({ ...config, isDigmaRunning: data.isDigmaRunning }); + } + }; + 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_INSTALLED, + handleSetIsDigmaInstalled + ); + dispatcher.addActionListener( + actions.SET_IS_DIGMA_RUNNING, + handleSetIsDigmaRunning + ); 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_INSTALLED, + handleSetIsDigmaInstalled + ); + dispatcher.removeActionListener( + actions.SET_IS_DIGMA_RUNNING, + handleSetIsDigmaRunning + ); }; }, []); return ( <> - - - {props.children} - + + + + {props.children} + + ); }; diff --git a/src/components/common/Button/index.tsx b/src/components/common/Button/index.tsx index 1724e2c4e..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) { diff --git a/src/components/common/Button/styles.ts b/src/components/common/Button/styles.ts index 184e6aadf..b9c8dfb3b 100644 --- a/src/components/common/Button/styles.ts +++ b/src/components/common/Button/styles.ts @@ -16,6 +16,16 @@ export const Button = styled.button` width: max-content; user-select: none; color: ${({ theme, buttonType }) => { + if (buttonType === "tertiary") { + switch (theme.mode) { + case "light": + return "#3538cd"; + case "dark": + case "dark-jetbrains": + return "#e2e7ff"; + } + } + if (buttonType === "secondary") { switch (theme.mode) { case "light": @@ -50,6 +60,16 @@ export const Button = styled.button` &:hover, &:focus { color: ${({ theme, buttonType }) => { + if (buttonType === "tertiary") { + switch (theme.mode) { + case "light": + return "#5154ec"; + case "dark": + case "dark-jetbrains": + return "#92affa"; + } + } + if (buttonType === "secondary") { switch (theme.mode) { case "light": @@ -84,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": @@ -125,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": 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"; From c32aadddfa63b346bdcf1f1d421b5247e327ef56 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 17 Jul 2023 19:08:19 +0200 Subject: [PATCH 03/20] Update Engine Manager API and flow --- .storybook/preview-body.html | 6 +- assets/assets/index.ejs | 6 +- assets/installationWizard/index.ejs | 6 +- assets/recentActivity/index.ejs | 6 +- .../InstallStep/AutomaticInstaller/index.tsx | 347 ---------------- .../InstallStep/AutomaticInstaller/types.ts | 11 - .../EngineManager.stories.tsx} | 8 +- .../InstallStep/EngineManager/index.tsx | 383 ++++++++++++++++++ .../styles.ts | 12 + .../InstallStep/EngineManager/types.ts | 38 ++ .../InstallationWizard/InstallStep/index.tsx | 281 ++++++++----- .../InstallationWizard/InstallStep/styles.ts | 4 +- src/components/InstallationWizard/index.tsx | 16 +- src/components/InstallationWizard/tracking.ts | 3 +- src/components/InstallationWizard/types.ts | 1 + src/components/common/App/ConfigContext.ts | 6 +- src/components/common/App/index.tsx | 76 +++- src/globals.d.ts | 6 +- 18 files changed, 706 insertions(+), 510 deletions(-) delete mode 100644 src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx delete mode 100644 src/components/InstallationWizard/InstallStep/AutomaticInstaller/types.ts rename src/components/InstallationWizard/InstallStep/{AutomaticInstaller/AutomaticInstaller.stories.tsx => EngineManager/EngineManager.stories.tsx} (74%) create mode 100644 src/components/InstallationWizard/InstallStep/EngineManager/index.tsx rename src/components/InstallationWizard/InstallStep/{AutomaticInstaller => EngineManager}/styles.ts (84%) create mode 100644 src/components/InstallationWizard/InstallStep/EngineManager/types.ts diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html index acc13d900..992bb5c9c 100644 --- a/.storybook/preview-body.html +++ b/.storybook/preview-body.html @@ -7,8 +7,10 @@ window.isJaegerEnabled = true; window.userEmail; window.isObservabilityEnabled; - window.isDigmaInstalled; - window.isDigmaRunning; + window.isDigmaEngineInstalled; + window.isDigmaEngineRunning; + window.isDockerInstalled; + window.isDockerComposeInstalled; window.assetsRefreshInterval; window.assetsSearch = true; diff --git a/assets/assets/index.ejs b/assets/assets/index.ejs index 0e3f165b4..f742e0bb2 100644 --- a/assets/assets/index.ejs +++ b/assets/assets/index.ejs @@ -23,8 +23,10 @@ window.isJaegerEnabled; window.userEmail; window.isObservabilityEnabled; - window.isDigmaInstalled; - window.isDigmaRunning; + window.isDigmaEngineInstalled; + window.isDigmaEngineRunning; + window.isDockerInstalled; + window.isDockerComposeInstalled; window.assetsRefreshInterval; window.assetsSearch; diff --git a/assets/installationWizard/index.ejs b/assets/installationWizard/index.ejs index f4d44ed87..2671f3fe6 100644 --- a/assets/installationWizard/index.ejs +++ b/assets/installationWizard/index.ejs @@ -23,8 +23,10 @@ window.isJaegerEnabled; window.userEmail; window.isObservabilityEnabled; - window.isDigmaInstalled; - window.isDigmaRunning; + window.isDigmaEngineInstalled; + window.isDigmaEngineRunning; + window.isDockerInstalled; + window.isDockerComposeInstalled; window.wizardSkipInstallationStep; diff --git a/assets/recentActivity/index.ejs b/assets/recentActivity/index.ejs index eefcc7254..ebde3811f 100644 --- a/assets/recentActivity/index.ejs +++ b/assets/recentActivity/index.ejs @@ -23,8 +23,10 @@ window.isJaegerEnabled; window.userEmail; window.isObservabilityEnabled; - window.isDigmaInstalled; - window.isDigmaRunning; + window.isDigmaEngineInstalled; + window.isDigmaEngineRunning; + window.isDockerInstalled; + window.isDockerComposeInstalled; window.recentActivityExpirationLimit; window.recentActivityDocumentationURL; diff --git a/src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx deleted file mode 100644 index 76950b77d..000000000 --- a/src/components/InstallationWizard/InstallStep/AutomaticInstaller/index.tsx +++ /dev/null @@ -1,347 +0,0 @@ -import { useContext, useEffect, useState } from "react"; -import { useTheme } from "styled-components"; -import { actions } from "../.."; -import { dispatcher } from "../../../../dispatcher"; -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 { MainButton } from "../../styles"; -import { AsyncActionResultData, AsyncActionStatus } from "../../types"; -import * as s from "./styles"; -import { AutomaticInstallerProps, Operation } 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 AutomaticInstaller = (props: AutomaticInstallerProps) => { - const config = useContext(ConfigContext); - const [isDigmaInstalled, setIsDigmaInstalled] = useState( - config.isDigmaInstalled - ); - const [isDigmaRunning, setIsDigmaRunning] = useState(config.isDigmaRunning); - const [currentOperation, setCurrentOperation] = useState<{ - operation: Operation; - status: AsyncActionStatus; - }>(); - const [failedOperation, setFailedOperation] = useState(); - const theme = useTheme(); - const themeKind = getThemeKind(theme); - - const operationsInfo: Record< - Operation, - { - action: string; - } - > = { - [Operation.INSTALL]: { - action: actions.INSTALL_DIGMA - }, - [Operation.UNINSTALL]: { - action: actions.UNINSTALL_DIGMA - }, - [Operation.START]: { - action: actions.START_DIGMA - }, - [Operation.STOP]: { action: actions.STOP_DIGMA } - }; - - useEffect(() => { - const handleInstallDigmaResultData = (data: unknown) => { - const result = (data as AsyncActionResultData).result; - setCurrentOperation({ operation: Operation.INSTALL, status: result }); - }; - - const handleUninstallDigmaResultData = (data: unknown) => { - const result = (data as AsyncActionResultData).result; - setCurrentOperation({ operation: Operation.UNINSTALL, status: result }); - }; - - const handleStartDigmaResultData = (data: unknown) => { - const result = (data as AsyncActionResultData).result; - setCurrentOperation({ operation: Operation.START, status: result }); - }; - - const handleStopDigmaResultData = (data: unknown) => { - const result = (data as AsyncActionResultData).result; - setCurrentOperation({ operation: Operation.STOP, status: result }); - }; - - dispatcher.addActionListener( - actions.SET_INSTALL_DIGMA_RESULT, - handleInstallDigmaResultData - ); - - dispatcher.addActionListener( - actions.SET_UNINSTALL_DIGMA_RESULT, - handleUninstallDigmaResultData - ); - - dispatcher.addActionListener( - actions.SET_START_DIGMA_RESULT, - handleStartDigmaResultData - ); - - dispatcher.addActionListener( - actions.SET_STOP_DIGMA_RESULT, - handleStopDigmaResultData - ); - - return () => { - dispatcher.removeActionListener( - actions.SET_INSTALL_DIGMA_RESULT, - handleInstallDigmaResultData - ); - - dispatcher.removeActionListener( - actions.SET_UNINSTALL_DIGMA_RESULT, - handleUninstallDigmaResultData - ); - - dispatcher.removeActionListener( - actions.SET_START_DIGMA_RESULT, - handleStartDigmaResultData - ); - - dispatcher.removeActionListener( - actions.SET_STOP_DIGMA_RESULT, - handleStopDigmaResultData - ); - }; - }, []); - - useEffect(() => { - if (!isDigmaInstalled) { - window.sendMessageToDigma({ - action: actions.INSTALL_DIGMA - }); - setCurrentOperation({ operation: Operation.INSTALL, status: "pending" }); - } - }, [isDigmaInstalled]); - - // Handle operation result - useEffect(() => { - if (currentOperation) { - if (currentOperation.status === "success") { - switch (currentOperation.operation) { - case Operation.INSTALL: - setIsDigmaInstalled(true); - setIsDigmaRunning(true); - break; - case Operation.UNINSTALL: - setIsDigmaInstalled(false); - props.onManualInstallSelect(); - break; - case Operation.START: - setIsDigmaRunning(true); - break; - case Operation.STOP: - setIsDigmaRunning(false); - break; - } - } - - switch (currentOperation.status) { - case "success": - setCurrentOperation(undefined); - break; - case "failure": - setFailedOperation(currentOperation.operation); - setCurrentOperation(undefined); - break; - case "pending": - setFailedOperation(undefined); - break; - } - } - }, [currentOperation, props.onManualInstallSelect]); - - const handleStartButtonClick = () => { - window.sendMessageToDigma({ - action: actions.START_DIGMA - }); - setCurrentOperation({ operation: Operation.START, status: "pending" }); - }; - - const handleStopButtonClick = () => { - window.sendMessageToDigma({ - action: actions.STOP_DIGMA - }); - setCurrentOperation({ operation: Operation.STOP, status: "pending" }); - }; - - const handleRemoveButtonClick = () => { - window.sendMessageToDigma({ - action: actions.UNINSTALL_DIGMA - }); - setCurrentOperation({ operation: Operation.UNINSTALL, status: "pending" }); - }; - - const handleRetryButtonClick = () => { - if (failedOperation) { - window.sendMessageToDigma({ - action: operationsInfo[failedOperation].action - }); - setCurrentOperation({ operation: failedOperation, status: "pending" }); - } - }; - - const handleInstallManuallyButtonClick = () => { - props.onManualInstallSelect(); - }; - - const handleNextButtonClick = () => { - props.onGoToNextStep(); - }; - - const renderContent = () => { - const loaderStatus = getLoaderStatus( - isDigmaRunning, - currentOperation?.status, - failedOperation - ); - - const icon = loaderStatus ? ( - - ) : ( - - ); - - let title = "Digma Local Analysis Engine "; - - switch (currentOperation?.operation || failedOperation) { - case Operation.START: - case Operation.INSTALL: - title += "Starting"; - break; - case Operation.UNINSTALL: - title += "Removing"; - break; - case Operation.STOP: - title += "Stopping"; - break; - case undefined: - title += isDigmaRunning ? "Running" : "Stopped"; - } - - if (failedOperation) { - title += " Failed"; - } - - const buttons = []; - - if (failedOperation) { - buttons.push( - ...[ - , - - ] - ); - } - - if (isDigmaInstalled) { - if (isDigmaRunning) { - buttons.push( - - ); - } else { - buttons.push( - - ); - } - - buttons.push( - - ); - } - - 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'} - - - You can always start / stop / remove the Digma Engine from the - Digma panel - - - )} - - {!currentOperation && buttonsWithDividers} - - - Next - - - ); - }; - - return {renderContent()}; -}; diff --git a/src/components/InstallationWizard/InstallStep/AutomaticInstaller/types.ts b/src/components/InstallationWizard/InstallStep/AutomaticInstaller/types.ts deleted file mode 100644 index d3368cb4a..000000000 --- a/src/components/InstallationWizard/InstallStep/AutomaticInstaller/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface AutomaticInstallerProps { - onManualInstallSelect: () => void; - onGoToNextStep: () => void; -} - -export enum Operation { - INSTALL = "install", - UNINSTALL = "uninstall", - START = "start", - STOP = "stop" -} diff --git a/src/components/InstallationWizard/InstallStep/AutomaticInstaller/AutomaticInstaller.stories.tsx b/src/components/InstallationWizard/InstallStep/EngineManager/EngineManager.stories.tsx similarity index 74% rename from src/components/InstallationWizard/InstallStep/AutomaticInstaller/AutomaticInstaller.stories.tsx rename to src/components/InstallationWizard/InstallStep/EngineManager/EngineManager.stories.tsx index d34e4e223..9407e9582 100644 --- a/src/components/InstallationWizard/InstallStep/AutomaticInstaller/AutomaticInstaller.stories.tsx +++ b/src/components/InstallationWizard/InstallStep/EngineManager/EngineManager.stories.tsx @@ -1,11 +1,11 @@ import { Meta, StoryObj } from "@storybook/react"; -import { AutomaticInstaller } from "."; +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/AutomaticInstaller", - component: AutomaticInstaller, +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" diff --git a/src/components/InstallationWizard/InstallStep/EngineManager/index.tsx b/src/components/InstallationWizard/InstallStep/EngineManager/index.tsx new file mode 100644 index 000000000..05a52a462 --- /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 [isDigmaEngineInstalled, setIsDigmaEngineInstalled] = useState( + config.isDigmaEngineInstalled + ); + const [isDigmaEngineRunning, setIsDigmaEngineRunning] = useState( + config.isDigmaEngineRunning + ); + 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 handleInstallDigmaResultData = (data: unknown) => { + const payload = data as AsyncActionResultData; + setCurrentOperation({ + operation: Operation.INSTALL, + status: payload.result, + error: payload.error + }); + }; + + const handleUninstallDigmaResultData = (data: unknown) => { + const payload = data as AsyncActionResultData; + setCurrentOperation({ + operation: Operation.UNINSTALL, + status: payload.result, + error: payload.error + }); + }; + + const handleStartDigmaResultData = (data: unknown) => { + const payload = data as AsyncActionResultData; + setCurrentOperation({ + operation: Operation.START, + status: payload.result, + error: payload.error + }); + }; + + const handleStopDigmaResultData = (data: unknown) => { + const payload = data as AsyncActionResultData; + setCurrentOperation({ + operation: Operation.STOP, + status: payload.result, + error: payload.error + }); + }; + + 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: + setIsDigmaEngineInstalled(true); + setIsDigmaEngineRunning(true); + break; + case Operation.UNINSTALL: + setIsDigmaEngineInstalled(false); + props.onManualInstallSelect(); + break; + case Operation.START: + setIsDigmaEngineRunning(true); + break; + case Operation.STOP: + setIsDigmaEngineRunning(false); + break; + } + } + + if (currentOperation.status === "failure") { + switch (currentOperation.operation) { + case Operation.INSTALL: + if (props.autoInstall) { + props.onAutoInstallFinish(); + } + break; + case Operation.UNINSTALL: + if (props.autoInstall) { + props.onManualInstallSelect(); + } + break; + case Operation.START: + case Operation.STOP: + break; + } + } + + switch (currentOperation.status) { + case "success": + setCurrentOperation(undefined); + break; + case "failure": + setFailedOperation({ + operation: currentOperation.operation, + error: currentOperation.error + }); + setCurrentOperation(undefined); + break; + case "pending": + setFailedOperation(undefined); + break; + } + } + }, [currentOperation, props.onManualInstallSelect]); + + 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(); + }; + + 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( + 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 += isDigmaEngineInstalled + ? isDigmaEngineRunning + ? "Running" + : "Stopped" + : "Not installed"; + } + + if (failedOperation) { + title += " Failed"; + } + + const buttons = []; + + if (failedOperation) { + buttons.push( + + ); + + if (props.autoInstall) { + buttons.push( + + ); + } + } else { + if (isDigmaEngineInstalled) { + if (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/AutomaticInstaller/styles.ts b/src/components/InstallationWizard/InstallStep/EngineManager/styles.ts similarity index 84% rename from src/components/InstallationWizard/InstallStep/AutomaticInstaller/styles.ts rename to src/components/InstallationWizard/InstallStep/EngineManager/styles.ts index fd4ab8d4e..7d8f9cd8b 100644 --- a/src/components/InstallationWizard/InstallStep/AutomaticInstaller/styles.ts +++ b/src/components/InstallationWizard/InstallStep/EngineManager/styles.ts @@ -44,6 +44,18 @@ export const ContentContainer = styled.div` }}; `; +export const ErrorMessage = styled.span` + 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; diff --git a/src/components/InstallationWizard/InstallStep/EngineManager/types.ts b/src/components/InstallationWizard/InstallStep/EngineManager/types.ts new file mode 100644 index 000000000..df9de2a8f --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/EngineManager/types.ts @@ -0,0 +1,38 @@ +import { MemoExoticComponent } from "react"; +import { IconProps } from "../../../common/icons/types"; +import { AsyncActionStatus } from "../../types"; + +export interface EngineManagerProps { + onManualInstallSelect: () => void; + onAutoInstallFinish: () => void; + // onGoToNextStep: () => void; + // onToggleNextStep: (isEnabled: boolean) => 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 82a690708..4d3ee8140 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,7 +13,7 @@ import { CodeSnippet } from "../CodeSnippet"; import { Tabs } from "../Tabs"; import { Link, MainButton, SectionDescription } from "../styles"; import { trackingEvents } from "../tracking"; -import { AutomaticInstaller } from "./AutomaticInstaller"; +import { EngineManager } from "./EngineManager"; import * as s from "./styles"; import { InstallStepProps } from "./types"; @@ -34,7 +35,19 @@ export const InstallStep = (props: InstallStepProps) => { const [selectedInstallTab, setSelectedInstallTab] = useState(0); const [selectedDockerComposeOSTab, setSelectedDockerComposeOSTab] = useState(0); - const [isAutomaticInstallation, setIsAutomaticInstallation] = useState(true); + const config = useContext(ConfigContext); + const [isAutomaticInstallation, setIsAutomaticInstallation] = useState( + !config.isDigmaEngineInstalled && + config.isDockerInstalled && + config.isDockerComposeInstalled + ); + const [isAutoInstallationFinished, setIsAutoInstallationFinished] = + useState(false); + + const isAutoInstallTabVisible = + !config.isDigmaEngineInstalled && + config.isDockerInstalled && + config.isDockerComposeInstalled; const handleInstallDigmaButtonClick = () => { props.onGetDigmaDockerDesktopButtonClick(); @@ -70,6 +83,72 @@ 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 handleAutoInstallFinish = () => { + setIsAutoInstallationFinished(true); + }; + + const handleManualInstallSelect = () => { + setIsAutomaticInstallation(false); + }; + + const renderLoader = () => ( + + {props.connectionCheckStatus && ( + + )} + + ); + + const renderMainButton = () => { + if ( + isAutoInstallationFinished || + (isAutoInstallTabVisible && selectedInstallTab === 0) + ) { + return Next; + } + + if (!isAutoInstallTabVisible && !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: @@ -109,94 +188,109 @@ 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 handleManualInstallSelect = () => { - setIsAutomaticInstallation(false); - }; - const installTabs = [ + ...(isAutoInstallTabVisible + ? [ + { + title: "Auto install", + content: ( + + + + ) + } + ] + : []), { 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()} + ) }, { 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()} + ) }, { 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()} + ) } ]; @@ -204,9 +298,10 @@ export const InstallStep = (props: InstallStepProps) => { return ( {isAutomaticInstallation ? ( - ) : ( <> @@ -217,37 +312,7 @@ export const InstallStep = (props: InstallStepProps) => { fullWidth={true} /> - - {props.connectionCheckStatus && ( - - )} - - {!isConnectionCheckStarted && ( - - OK, I've installed Digma - - )} - {props.connectionCheckStatus === "pending" && ( - Complete - )} - {props.connectionCheckStatus === "failure" && ( - Retry - )} - {props.connectionCheckStatus === "success" && ( - - Complete - - )} + {renderMainButton()} )} 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/index.tsx b/src/components/InstallationWizard/index.tsx index 9269ad20f..d7051cb70 100644 --- a/src/components/InstallationWizard/index.tsx +++ b/src/components/InstallationWizard/index.tsx @@ -39,14 +39,14 @@ export const actions = addPrefix(ACTION_PREFIX, { CHECK_CONNECTION: "CHECK_CONNECTION", SET_CONNECTION_CHECK_RESULT: "SET_CONNECTION_CHECK_RESULT", SET_OBSERVABILITY: "SET_OBSERVABILITY", - INSTALL_DIGMA: "INSTALL_DIGMA", - UNINSTALL_DIGMA: "UNINSTALL_DIGMA", - START_DIGMA: "START_DIGMA", - STOP_DIGMA: "STOP_DIGMA", - SET_INSTALL_DIGMA_RESULT: "SET_INSTALL_DIGMA_RESULT", - SET_UNINSTALL_DIGMA_RESULT: "SET_UNINSTALL_DIGMA_RESULT", - SET_START_DIGMA_RESULT: "SET_START_DIGMA_RESULT", - SET_STOP_DIGMA_RESULT: "SET_STOP_DIGMA_RESULT" + 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 = 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 3421a1cea..8439ed103 100644 --- a/src/components/InstallationWizard/types.ts +++ b/src/components/InstallationWizard/types.ts @@ -4,6 +4,7 @@ export type AsyncActionStatus = AsyncActionResult | "pending" | undefined; export interface AsyncActionResultData { result: AsyncActionResult; + error?: string; } export type InstallationType = "local" | "cloud"; diff --git a/src/components/common/App/ConfigContext.ts b/src/components/common/App/ConfigContext.ts index 08961fde1..d201abff5 100644 --- a/src/components/common/App/ConfigContext.ts +++ b/src/components/common/App/ConfigContext.ts @@ -4,7 +4,9 @@ import { isString } from "../../../typeGuards/isString"; export const ConfigContext = createContext({ isObservabilityEnabled: window.isObservabilityEnabled === true, isJaegerEnabled: window.isJaegerEnabled === true, - isDigmaInstalled: window.isDigmaInstalled === true, - isDigmaRunning: window.isDigmaRunning === 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 f3a451435..84b3c210a 100644 --- a/src/components/common/App/index.tsx +++ b/src/components/common/App/index.tsx @@ -36,8 +36,10 @@ export const actions = addPrefix(ACTION_PREFIX, { 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_DIGMA_INSTALLED: "SET_IS_DIGMA_INSTALLED", - SET_IS_DIGMA_RUNNING: "SET_IS_DIGMA_RUNNING" + 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 : ""; @@ -81,15 +83,39 @@ export const App = (props: AppProps) => { } }; - const handleSetIsDigmaInstalled = (data: unknown) => { - if (isObject(data) && isBoolean(data.isDigmaInstalled)) { - setConfig({ ...config, isDigmaInstalled: data.isDigmaInstalled }); + const handleSetIsDigmaEngineInstalled = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDigmaEngineInstalled)) { + setConfig({ + ...config, + isDigmaEngineInstalled: data.isDigmaEngineInstalled + }); } }; - const handleSetIsDigmaRunning = (data: unknown) => { - if (isObject(data) && isBoolean(data.isDigmaRunning)) { - setConfig({ ...config, isDigmaRunning: data.isDigmaRunning }); + const handleSetIsDigmaEngineRunning = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDigmaEngineRunning)) { + setConfig({ + ...config, + isDigmaEngineRunning: data.isDigmaEngineRunning + }); + } + }; + + const handleSetIsDockerInstalled = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDockerInstalled)) { + setConfig({ + ...config, + isDockerInstalled: data.isDockerInstalled + }); + } + }; + + const handleSetIsDockerComposeInstalled = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDockerComposeInstalled)) { + setConfig({ + ...config, + isDockerComposeInstalled: data.isDockerComposeInstalled + }); } }; @@ -101,12 +127,20 @@ export const App = (props: AppProps) => { handleSetIsJaegerEnabled ); dispatcher.addActionListener( - actions.SET_IS_DIGMA_INSTALLED, - handleSetIsDigmaInstalled + 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_DIGMA_RUNNING, - handleSetIsDigmaRunning + actions.SET_IS_DOCKER_COMPOSE_INSTALLED, + handleSetIsDockerComposeInstalled ); return () => { @@ -118,15 +152,23 @@ export const App = (props: AppProps) => { handleSetIsJaegerEnabled ); dispatcher.removeActionListener( - actions.SET_IS_DIGMA_INSTALLED, - handleSetIsDigmaInstalled + 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_DIGMA_RUNNING, - handleSetIsDigmaRunning + actions.SET_IS_DOCKER_COMPOSE_INSTALLED, + handleSetIsDockerComposeInstalled ); }; - }, []); + }, [config]); return ( <> diff --git a/src/globals.d.ts b/src/globals.d.ts index a9e5d0c5d..4cedbbca2 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -30,8 +30,10 @@ declare global { isJaegerEnabled?: unknown; userEmail?: unknown; isObservabilityEnabled?: unknown; - isDigmaInstalled?: unknown; - isDigmaRunning?: unknown; + isDigmaEngineInstalled?: unknown; + isDigmaEngineRunning?: unknown; + isDockerInstalled?: unknown; + isDockerComposeInstalled?: unknown; assetsRefreshInterval?: unknown; assetsSearch?: unknown; insightsRefreshInterval?: unknown; From f09af6849946948a35a25475a80fe29d35c33ce3 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 18 Jul 2023 00:59:01 +0200 Subject: [PATCH 04/20] Add firstLaunch env variable --- .../InstallStep/EngineManager/index.tsx | 98 +++++++-------- .../InstallStep/EngineManager/types.ts | 11 +- .../InstallationWizard/InstallStep/index.tsx | 117 ++++++++++++------ src/components/common/App/index.tsx | 29 +++-- src/globals.d.ts | 1 + 5 files changed, 150 insertions(+), 106 deletions(-) diff --git a/src/components/InstallationWizard/InstallStep/EngineManager/index.tsx b/src/components/InstallationWizard/InstallStep/EngineManager/index.tsx index 05a52a462..fd0856c9f 100644 --- a/src/components/InstallationWizard/InstallStep/EngineManager/index.tsx +++ b/src/components/InstallationWizard/InstallStep/EngineManager/index.tsx @@ -42,12 +42,6 @@ const getLoaderStatus = ( export const EngineManager = (props: EngineManagerProps) => { const config = useContext(ConfigContext); - const [isDigmaEngineInstalled, setIsDigmaEngineInstalled] = useState( - config.isDigmaEngineInstalled - ); - const [isDigmaEngineRunning, setIsDigmaEngineRunning] = useState( - config.isDigmaEngineRunning - ); const [currentOperation, setCurrentOperation] = useState(); const [failedOperation, setFailedOperation] = useState(); const theme = useTheme(); @@ -77,40 +71,36 @@ export const EngineManager = (props: EngineManagerProps) => { }; useEffect(() => { - const handleInstallDigmaResultData = (data: unknown) => { - const payload = data as AsyncActionResultData; + const handleOperationResultData = ( + data: AsyncActionResultData, + operation: Operation + ) => { setCurrentOperation({ - operation: Operation.INSTALL, - status: payload.result, - error: payload.error + operation: operation, + status: data.result, + error: data.error }); }; + const handleInstallDigmaResultData = (data: unknown) => { + handleOperationResultData( + data as AsyncActionResultData, + Operation.INSTALL + ); + }; const handleUninstallDigmaResultData = (data: unknown) => { - const payload = data as AsyncActionResultData; - setCurrentOperation({ - operation: Operation.UNINSTALL, - status: payload.result, - error: payload.error - }); + handleOperationResultData( + data as AsyncActionResultData, + Operation.UNINSTALL + ); }; const handleStartDigmaResultData = (data: unknown) => { - const payload = data as AsyncActionResultData; - setCurrentOperation({ - operation: Operation.START, - status: payload.result, - error: payload.error - }); + handleOperationResultData(data as AsyncActionResultData, Operation.START); }; const handleStopDigmaResultData = (data: unknown) => { - const payload = data as AsyncActionResultData; - setCurrentOperation({ - operation: Operation.STOP, - status: payload.result, - error: payload.error - }); + handleOperationResultData(data as AsyncActionResultData, Operation.STOP); }; dispatcher.addActionListener( @@ -170,18 +160,23 @@ export const EngineManager = (props: EngineManagerProps) => { if (currentOperation.status === "success") { switch (currentOperation.operation) { case Operation.INSTALL: - setIsDigmaEngineInstalled(true); - setIsDigmaEngineRunning(true); + if (props.autoInstall && props.onAutoInstallFinish) { + props.onAutoInstallFinish(); + } break; case Operation.UNINSTALL: - setIsDigmaEngineInstalled(false); - props.onManualInstallSelect(); + if (props.autoInstall) { + if (props.onManualInstallSelect) { + props.onManualInstallSelect(); + } + } else { + if (props.onRemoveFinish) { + props.onRemoveFinish(); + } + } break; case Operation.START: - setIsDigmaEngineRunning(true); - break; case Operation.STOP: - setIsDigmaEngineRunning(false); break; } } @@ -189,12 +184,8 @@ export const EngineManager = (props: EngineManagerProps) => { if (currentOperation.status === "failure") { switch (currentOperation.operation) { case Operation.INSTALL: - if (props.autoInstall) { - props.onAutoInstallFinish(); - } - break; case Operation.UNINSTALL: - if (props.autoInstall) { + if (props.autoInstall && props.onManualInstallSelect) { props.onManualInstallSelect(); } break; @@ -207,6 +198,9 @@ export const EngineManager = (props: EngineManagerProps) => { switch (currentOperation.status) { case "success": setCurrentOperation(undefined); + if (props.onOperationFinish) { + props.onOperationFinish(); + } break; case "failure": setFailedOperation({ @@ -214,13 +208,19 @@ export const EngineManager = (props: EngineManagerProps) => { error: currentOperation.error }); setCurrentOperation(undefined); + if (props.onOperationFinish) { + props.onOperationFinish(); + } break; case "pending": setFailedOperation(undefined); + if (props.onOperationStart) { + props.onOperationStart(); + } break; } } - }, [currentOperation, props.onManualInstallSelect]); + }, [currentOperation, props]); const sendActionButtonTrackingEvent = (buttonName: string) => { window.sendMessageToDigma({ @@ -245,7 +245,7 @@ export const EngineManager = (props: EngineManagerProps) => { }; const handleInstallManuallyButtonClick = () => { - props.onManualInstallSelect(); + props.onManualInstallSelect && props.onManualInstallSelect(); }; const renderOperationButton = (operation: Operation) => { @@ -273,7 +273,7 @@ export const EngineManager = (props: EngineManagerProps) => { const renderContent = () => { const loaderStatus = getLoaderStatus( - isDigmaEngineRunning, + config.isDigmaEngineRunning, currentOperation?.status, failedOperation?.operation ); @@ -292,8 +292,8 @@ export const EngineManager = (props: EngineManagerProps) => { if (lastOperation) { title += operationsInfo[lastOperation].titleSuffix; } else { - title += isDigmaEngineInstalled - ? isDigmaEngineRunning + title += config.isDigmaEngineInstalled + ? config.isDigmaEngineRunning ? "Running" : "Stopped" : "Not installed"; @@ -316,7 +316,7 @@ export const EngineManager = (props: EngineManagerProps) => { ); - if (props.autoInstall) { + if (props.onManualInstallSelect) { buttons.push(