diff --git a/public/images/navigation.png b/public/images/navigation.png deleted file mode 100644 index 250209724..000000000 Binary files a/public/images/navigation.png and /dev/null differ diff --git a/public/videos/observabilityButton.mp4 b/public/videos/observabilityButton.mp4 new file mode 100644 index 000000000..5a4095928 Binary files /dev/null and b/public/videos/observabilityButton.mp4 differ diff --git a/public/videos/observabilityPanel.mp4 b/public/videos/observabilityPanel.mp4 new file mode 100644 index 000000000..6f96d8aa4 Binary files /dev/null and b/public/videos/observabilityPanel.mp4 differ diff --git a/public/videos/runOrDebug.mp4 b/public/videos/runOrDebug.mp4 new file mode 100644 index 000000000..0d44589ea Binary files /dev/null and b/public/videos/runOrDebug.mp4 differ diff --git a/src/components/Assets/index.tsx b/src/components/Assets/index.tsx index 89f37cf8b..1d2148997 100644 --- a/src/components/Assets/index.tsx +++ b/src/components/Assets/index.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from "react"; import { dispatcher } from "../../dispatcher"; -import { getActions } from "../../utils/getActions"; +import { addPrefix } from "../../utils/addPrefix"; import { groupBy } from "../../utils/groupBy"; import { AssetList } from "./AssetList"; import { AssetTypeList } from "./AssetTypeList"; @@ -20,10 +20,10 @@ const REFRESH_INTERVAL = const ACTION_PREFIX = "ASSETS"; -const actions = getActions(ACTION_PREFIX, { - getData: "GET_DATA", - setData: "SET_DATA", - goToAsset: "GO_TO_ASSET" +const actions = addPrefix(ACTION_PREFIX, { + GET_DATA: "GET_DATA", + SET_DATA: "SET_DATA", + GO_TO_ASSET: "GO_TO_ASSET" }); const groupEntries = (data: AssetsData): GroupedAssetEntries => { @@ -61,11 +61,11 @@ export const Assets = (props: AssetsProps) => { useEffect(() => { window.sendMessageToDigma({ - action: actions.getData + action: actions.GET_DATA }); const refreshInterval = setInterval(() => { window.sendMessageToDigma({ - action: actions.getData + action: actions.GET_DATA }); }, REFRESH_INTERVAL); @@ -73,12 +73,12 @@ export const Assets = (props: AssetsProps) => { setData(groupEntries(data as AssetsData)); }; - dispatcher.addActionListener(actions.setData, handleAssetsData); + dispatcher.addActionListener(actions.SET_DATA, handleAssetsData); return () => { clearInterval(refreshInterval); - dispatcher.removeActionListener(actions.setData, handleAssetsData); + dispatcher.removeActionListener(actions.SET_DATA, handleAssetsData); }; }, []); @@ -101,7 +101,7 @@ export const Assets = (props: AssetsProps) => { const handleAssetLinkClick = (entry: AssetEntry) => { window.sendMessageToDigma({ - action: actions.goToAsset, + action: actions.GO_TO_ASSET, payload: { entry } }); }; diff --git a/src/components/InstallationWizard/Button/index.tsx b/src/components/InstallationWizard/Button/index.tsx index a8eeb9af0..919af10a7 100644 --- a/src/components/InstallationWizard/Button/index.tsx +++ b/src/components/InstallationWizard/Button/index.tsx @@ -9,13 +9,12 @@ export const Button = (props: ButtonProps) => { } }; - const disabled = - ["success", "failure"].includes(props.buttonType) || props.disabled; return ( {props.icon} diff --git a/src/components/InstallationWizard/Button/styles.ts b/src/components/InstallationWizard/Button/styles.ts index 42bb88845..c3a912e33 100644 --- a/src/components/InstallationWizard/Button/styles.ts +++ b/src/components/InstallationWizard/Button/styles.ts @@ -6,50 +6,47 @@ export const Button = styled.button` font-size: 12px; line-height: 14px; display: flex; - gap: 8px; align-items: center; justify-content: center; - padding: 6px 8px; border-radius: 2px; cursor: pointer; + color: #b9c2eb; - color: ${({ buttonType }) => { - switch (buttonType) { - case "primary": - return "#b9c2eb"; - case "secondary": - case "success": - case "failure": - return "#1e1e1e"; - } - }}; - - background: ${({ buttonType }) => { - switch (buttonType) { - case "primary": - return "#000"; - case "secondary": - return "#646363"; - case "success": - return "#c8e5c9"; - case "failure": - return "#e4c6c6"; - } - }}; + padding: ${({ buttonType }) => + buttonType === "primary" ? "4px" : "2px 4px"}; + + background: ${({ buttonType }) => + buttonType === "primary" ? "#3538cd" : "none"}; border: ${({ buttonType }) => - buttonType === "secondary" ? "1px solid #1e1e1e" : "none"}; + buttonType === "primary" ? "none" : "1px solid #3538cd"}; width: ${({ buttonType }) => buttonType === "primary" ? "100%" : "max-content"}; + &:hover { + color: #dadada; + border: ${({ buttonType }) => + buttonType === "primary" ? "none" : "1px solid #5154ec"}; + background: ${({ buttonType }) => + buttonType === "primary" ? "#5154ec" : "none"}; + } + + &:focus { + color: #dadada; + background: ${({ buttonType }) => + buttonType === "primary" ? "#5154ec" : "none"}; + } + &:disabled { - ${({ buttonType }) => (buttonType === "primary" ? "opacity: 0.2;" : "")} + background: #2e2e2e; + color: #49494d; cursor: initial; } `; -export const ContentContainer = styled.span` +export const ContentContainer = styled.div` display: flex; - gap: 8px; + gap: 2px; + align-items: center; `; diff --git a/src/components/InstallationWizard/Button/types.ts b/src/components/InstallationWizard/Button/types.ts index 198524b08..c09532888 100644 --- a/src/components/InstallationWizard/Button/types.ts +++ b/src/components/InstallationWizard/Button/types.ts @@ -1,7 +1,10 @@ +import { ReactNode } from "react"; + export interface ButtonProps { - icon?: JSX.Element; + icon?: ReactNode; onClick?: React.MouseEventHandler; disabled?: boolean; children: React.ReactNode; - buttonType: "primary" | "secondary" | "success" | "failure"; + buttonType?: "primary" | "secondary"; + className?: string; } diff --git a/src/components/InstallationWizard/CodeSnippet/index.tsx b/src/components/InstallationWizard/CodeSnippet/index.tsx index 8a78359d7..eeb7ca65e 100644 --- a/src/components/InstallationWizard/CodeSnippet/index.tsx +++ b/src/components/InstallationWizard/CodeSnippet/index.tsx @@ -12,7 +12,7 @@ export const CodeSnippet = (props: CodeSnippetProps) => { {props.text} - + ); diff --git a/src/components/InstallationWizard/CodeSnippet/styles.ts b/src/components/InstallationWizard/CodeSnippet/styles.ts index 7151b74d6..60b1389ad 100644 --- a/src/components/InstallationWizard/CodeSnippet/styles.ts +++ b/src/components/InstallationWizard/CodeSnippet/styles.ts @@ -3,10 +3,9 @@ import { getCodeFont } from "../../common/App/styles"; import { ContainerProps } from "./types"; export const Container = styled.div` - background: #0c0b0b; - padding: 8px 12px; - border-radius: 2px; - margin: 8px 0 12px; + background: #252526; + padding: 4px 4px 4px 8px; + border-radius: 4px; display: flex; gap: 27px; align-items: flex-start; @@ -27,7 +26,16 @@ export const Code = styled.code` export const CopyButton = styled.button` padding: 0; - background: transparent; - border: none; cursor: pointer; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 18px; + height: 18px; + background: #2e2e2e; + border: 1px solid #383838; + box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.25); + border-radius: 4px; `; diff --git a/src/components/InstallationWizard/FinishStep/index.tsx b/src/components/InstallationWizard/FinishStep/index.tsx new file mode 100644 index 000000000..d2a9aa7d7 --- /dev/null +++ b/src/components/InstallationWizard/FinishStep/index.tsx @@ -0,0 +1,51 @@ +import { LightBulbIcon } from "../../common/icons/LightBulbIcon"; +import { OpenTelemetryLogoIcon } from "../../common/icons/OpenTelemetryLogoIcon"; +import { PlayIcon } from "../../common/icons/PlayIcon"; +import { SectionDescription, SectionIconContainer } from "../styles"; +import * as s from "./styles"; + +export const FinishStep = () => ( + + + + + + Run / Debug your application + + + Run or debug your application and trigger some actions or APIs to collect + observability. + + + + + + + + + Observability Panel + + + You'll be able to see the results in the observability panel below, + you can open it by clicking on the "Telescope". + + + + + + + + + More and more information about your code will continue to appear as you + perform more actions. + + +); diff --git a/src/components/InstallationWizard/FinishStep/styles.ts b/src/components/InstallationWizard/FinishStep/styles.ts new file mode 100644 index 000000000..8b46bf166 --- /dev/null +++ b/src/components/InstallationWizard/FinishStep/styles.ts @@ -0,0 +1,37 @@ +import styled from "styled-components"; +import * as s from "../styles"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + padding: 12px 8px; +`; + +export const SectionTitle = styled(s.SectionTitle)` + gap: 8px; + margin-bottom: 4px; +`; + +export const IllustrationContainer = styled(s.IllustrationContainer)` + margin: 12px 0 12px; +`; + +export const ObservabilityPanelIllustration = styled.video` + bottom: 0; + position: absolute; + width: 100%; +`; + +export const RunOrDebugIllustration = styled.video` + width: 206px; +`; + +export const TipContainer = styled.div` + display: flex; + flex-shrink: 0; + gap: 4px; + font-weight: 500; + font-size: 12px; + color: #b9c2eb; + margin-top: 8px; +`; diff --git a/src/components/InstallationWizard/InstallStep/index.tsx b/src/components/InstallationWizard/InstallStep/index.tsx new file mode 100644 index 000000000..4d3b605ea --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/index.tsx @@ -0,0 +1,197 @@ +import { useState } from "react"; +import { Loader } from "../../common/Loader"; +import { CheckmarkCircleInvertedIcon } from "../../common/icons/CheckmarkCircleInvertedIcon"; +import { CodeIcon } from "../../common/icons/CodeIcon"; +import { DockerLogoIcon } from "../../common/icons/DockerLogoIcon"; +import { Button } from "../Button"; +import { CodeSnippet } from "../CodeSnippet"; +import { Tabs } from "../Tabs"; +import { Link, SectionDescription } from "../styles"; +import * as s from "./styles"; +import { InstallStepProps } from "./types"; + +const GET_DIGMA_DOCKER_COMPOSE_COMMAND_LINUX = + "curl -L https://get.digma.ai/ --output docker-compose.yml"; +const GET_DIGMA_DOCKER_COMPOSE_COMMAND_WINDOWS = + "iwr https://get.digma.ai/ -outfile docker-compose.yml"; +const RUN_DOCKER_COMPOSE_COMMAND = "docker compose up -d"; + +const DOCKER_DESKTOP_URL = "https://www.docker.com/products/docker-desktop/"; +const DOCKER_URL = "https://docs.docker.com/get-docker/"; +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"; + +export const InstallStep = (props: InstallStepProps) => { + const isConnectionCheckStarted = Boolean(props.connectionCheckStatus); + const [selectedInstallTab, setSelectedInstallTab] = useState(0); + const [selectedDockerComposeOSTab, setSelectedDockerComposeOSTab] = + useState(0); + + const handleInstallDigmaButtonClick = () => { + props.onGetDigmaDockerDesktopButtonClick(); + }; + + const handleDigmaIsInstalledButtonClick = () => { + props.onConnectionStatusCheck(); + }; + + const handleRetryButtonClick = () => { + props.onResetConnectionCheckStatus(); + }; + + const handleNextButtonClick = () => { + props.onGoToNextStep(); + }; + + const handleSelectInstallTab = (tabIndex: number) => { + setSelectedInstallTab(tabIndex); + }; + + const handleSelectDockerComposeOSTab = (tabIndex: number) => { + setSelectedDockerComposeOSTab(tabIndex); + }; + + const dockerComposeOSTabs = [ + { + title: "Linux & macOS", + disabled: isConnectionCheckStarted, + content: ( + + ) + }, + { + title: "Windows (PowerShell)", + disabled: isConnectionCheckStarted, + content: ( + + ) + } + ]; + + const installTabs = [ + { + icon: DockerLogoIcon, + disabled: isConnectionCheckStarted, + title: "Docker Desktop", + content: ( + + + + Install Digma Docker Extension + + + (You'll need{" "} + + Docker Desktop + {" "} + installed) + + + Get Digma Docker Extension + + + ) + }, + { + icon: CodeIcon, + disabled: isConnectionCheckStarted, + title: "Docker Compose", + content: ( + + + Run the following from the terminal/command line to start the Digma + backend: + + + (You'll need{" "} + + Docker + {" "} + and{" "} + + Docker Compose + {" "} + installed) + + + Then run: + + + Prefer to use a helm file? Check out{" "} + + these + {" "} + instructions instead + + + ) + } + ]; + + return ( + + + + {props.connectionCheckStatus && ( + + )} + + {!isConnectionCheckStarted && ( + + )} + {props.connectionCheckStatus === "pending" && ( + + )} + {props.connectionCheckStatus === "failure" && ( + + )} + {props.connectionCheckStatus === "success" && ( + + )} + + ); +}; diff --git a/src/components/InstallationWizard/InstallStep/styles.ts b/src/components/InstallationWizard/InstallStep/styles.ts new file mode 100644 index 000000000..18776a3d2 --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/styles.ts @@ -0,0 +1,45 @@ +import styled from "styled-components"; +import { Button } from "../Button"; +import { Tabs } from "../Tabs"; +import * as s from "../styles"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + padding: 0 8px 12px; +`; + +export const SectionTitle = styled(s.SectionTitle)` + gap: 2px; + margin-bottom: 2px; +`; + +export const DockerComposeTabs = styled(Tabs)` + margin-top: 21px; +`; + +export const GetDockerExtensionButton = styled(Button)` + margin-top: 12px; +`; + +export const InstallTabContentContainer = styled.div` + display: flex; + flex-direction: column; + padding: 12px 0 12px; +`; + +export const DockerComposeOSTabContentContainer = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px 0 12px; +`; + +export const LoaderContainer = styled.div` + padding: 80px 0 16px; + height: 172px; + display: flex; + justify-content: center; + flex-grow: 1; + box-sizing: border-box; +`; diff --git a/src/components/InstallationWizard/InstallStep/types.ts b/src/components/InstallationWizard/InstallStep/types.ts new file mode 100644 index 000000000..1a3976b44 --- /dev/null +++ b/src/components/InstallationWizard/InstallStep/types.ts @@ -0,0 +1,9 @@ +import { ConnectionCheckStatus } from "../types"; + +export interface InstallStepProps { + connectionCheckStatus: ConnectionCheckStatus; + onConnectionStatusCheck: () => void; + onResetConnectionCheckStatus: () => void; + onGetDigmaDockerDesktopButtonClick: () => void; + onGoToNextStep: () => void; +} diff --git a/src/components/InstallationWizard/ObservabilityStep/index.tsx b/src/components/InstallationWizard/ObservabilityStep/index.tsx new file mode 100644 index 000000000..8d49adf38 --- /dev/null +++ b/src/components/InstallationWizard/ObservabilityStep/index.tsx @@ -0,0 +1,119 @@ +import { useState } from "react"; +import { Loader } from "../../common/Loader"; +import { ToggleSwitch } from "../../common/ToggleSwitch"; +import { OpenTelemetryLogoIcon } from "../../common/icons/OpenTelemetryLogoIcon"; +import { Button } from "../Button"; +import { CodeSnippet } from "../CodeSnippet"; +import { Link, SectionDescription, SectionTitle } from "../styles"; +import * as s from "./styles"; +import { ObservabilityStepProps } from "./types"; + +const COLLECTOR_CONFIGURATION_SNIPPET = `otlp/digma: +endpoint: "localhost:5050" +tls: + insecure: true +service: +pipelines: +traces: + exporters: [otlp/digma, ...]`; + +export const ObservabilityStep = (props: ObservabilityStepProps) => { + const [isCollectorModified, setIsCollectorModified] = + useState(false); + + const handleNextButtonClick = () => { + props.onGoToNextStep(); + }; + + const handleAlreadyUsingOTELLinkClick = ( + e: React.MouseEvent + ) => { + e.preventDefault(); + props.onIsAlreadyUsingOtelChange(!props.isAlreadyUsingOtel); + }; + + const handleCollectorIsModifiedButtonClick = () => { + setIsCollectorModified(true); + }; + + const handleObservabilityContainerClick = () => { + props.onObservabilityChange(!props.isObservabilityEnabled); + }; + + return props.isAlreadyUsingOtel ? ( + + Add Digma to your collector + + Modify your collector configuration file to add Digma's backend as + a target. For example: + + + + {isCollectorModified ? ( + + ) : ( + + )} + + Observe your application + + + + ) : ( + + How to get started? + + + + Observe your application in one click + + + + To quickly collect data from your application in intelliJ, + + You can just toggle observability on now to get started + + + + + {props.isObservabilityEnabled && ( + + + Congratulation! + Your application is now being observed. + + )} + + + You can always expand the Digma side-panel and open the settings menu as + seen bellow + + + + + + + + Already using OpenTelemetry? + + + + ); +}; diff --git a/src/components/InstallationWizard/ObservabilityStep/styles.ts b/src/components/InstallationWizard/ObservabilityStep/styles.ts new file mode 100644 index 000000000..71e7a1854 --- /dev/null +++ b/src/components/InstallationWizard/ObservabilityStep/styles.ts @@ -0,0 +1,86 @@ +import styled from "styled-components"; +import * as s from "../styles"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + padding: 12px 8px; +`; + +export const ObservabilityContainer = styled.div` + padding: 20px; + margin: 8px 0 12px; + background: #303031; + border: 1px solid #9b9b9b; + box-sizing: border-box; + border-radius: 4px; + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; + justify-content: center; + user-select: none; + cursor: pointer; +`; + +export const ObservabilityTitle = styled.span` + font-weight: 500; + font-size: 14px; + text-transform: capitalize; + color: #fff; + text-align: center; +`; + +export const ObservabilityDescription = styled.div` + font-weight: 500; + font-size: 10px; + line-height: 12px; + color: #969798; + text-align: center; + display: flex; + flex-direction: column; +`; + +export const ObservabilityToggleSwitchContainer = styled.div` + padding-top: 12px; + display: flex; + justify-content: center; +`; + +export const CongratulationsTextContainer = styled.div` + display: flex; + align-items: center; + gap: 2px; + font-weight: 500; + font-size: 10px; + line-height: 11px; + color: #dadada; + flex-wrap: wrap; + justify-content: center; +`; + +export const CongratulationsText = styled.span` + font-weight: 700; + padding-left: 2px; + color: #67d28b; +`; + +export const IllustrationContainer = styled(s.IllustrationContainer)` + margin-top: 8px; +`; + +export const ObservabilityButtonIllustration = styled.video` + width: 100%; +`; + +export const StepFooter = styled.div` + padding-top: 12px; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +`; + +export const SectionDescription = styled(s.SectionDescription)` + padding: 8px 0; +`; diff --git a/src/components/InstallationWizard/ObservabilityStep/types.ts b/src/components/InstallationWizard/ObservabilityStep/types.ts new file mode 100644 index 000000000..29672bc36 --- /dev/null +++ b/src/components/InstallationWizard/ObservabilityStep/types.ts @@ -0,0 +1,7 @@ +export interface ObservabilityStepProps { + isAlreadyUsingOtel: boolean; + isObservabilityEnabled: boolean; + onGoToNextStep: () => void; + onIsAlreadyUsingOtelChange: (value: boolean) => void; + onObservabilityChange: (value: boolean) => void; +} diff --git a/src/components/InstallationWizard/Tabs/index.tsx b/src/components/InstallationWizard/Tabs/index.tsx new file mode 100644 index 000000000..858ed61ea --- /dev/null +++ b/src/components/InstallationWizard/Tabs/index.tsx @@ -0,0 +1,38 @@ +import * as s from "./styles"; +import { TabsProps } from "./types"; + +export const Tabs = (props: TabsProps) => { + const handleTabClick = (tabIndex: number) => { + if (!props.tabs[tabIndex].disabled) { + props.onSelect(tabIndex); + } + }; + + return ( + + + {props.tabs.map((tab, i) => { + const isSelected = props.selectedTab === i; + + return ( + handleTabClick(i)} + > + {tab.icon && ( + + )} + {tab.title} + + ); + })} + + {props.tabs[props.selectedTab].content} + + ); +}; diff --git a/src/components/InstallationWizard/Tabs/styles.ts b/src/components/InstallationWizard/Tabs/styles.ts new file mode 100644 index 000000000..ce90a4868 --- /dev/null +++ b/src/components/InstallationWizard/Tabs/styles.ts @@ -0,0 +1,32 @@ +import styled from "styled-components"; +import { TabProps } from "./types"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +export const TabList = styled.ul` + display: flex; + margin: 0; + padding: 0; + box-sizing: border-box; + border-bottom: 1px solid #2e2e2e; +`; + +export const Tab = styled.li` + box-sizing: border-box; + font-weight: 500; + font-size: 12px; + line-height: 14px; + padding: 9px 10px; + display: flex; + gap: 4px; + cursor: ${({ disabled }) => (disabled ? "initial" : "pointer")}; + + border-bottom: ${({ isSelected }) => + isSelected ? "3px solid #5154ec" : "none"}; + + color: ${({ isSelected }) => (isSelected ? "#dadada" : "#9b9b9b")}; +`; diff --git a/src/components/InstallationWizard/Tabs/types.ts b/src/components/InstallationWizard/Tabs/types.ts new file mode 100644 index 000000000..196e05ef2 --- /dev/null +++ b/src/components/InstallationWizard/Tabs/types.ts @@ -0,0 +1,19 @@ +import { ComponentType, ReactNode } from "react"; +import { IconProps } from "../../common/icons/types"; + +export interface TabsProps { + tabs: { + title: string; + icon?: ComponentType; + content: ReactNode; + disabled?: boolean; + }[]; + selectedTab: number; + onSelect: (tabIndex: number) => void; + className?: string; +} + +export interface TabProps { + isSelected: boolean; + disabled?: boolean; +} diff --git a/src/components/InstallationWizard/index.tsx b/src/components/InstallationWizard/index.tsx index 61b3fe5f5..5ff6042b1 100644 --- a/src/components/InstallationWizard/index.tsx +++ b/src/components/InstallationWizard/index.tsx @@ -1,42 +1,60 @@ import { useEffect, useState } from "react"; import { dispatcher } from "../../dispatcher"; import { usePrevious } from "../../hooks/usePrevious"; -import { getActions } from "../../utils/getActions"; +import { addPrefix } from "../../utils/addPrefix"; import { actions as globalActions } from "../common/App"; -import { Loader } from "../common/Loader"; -import { CheckmarkCircleIcon } from "../common/icons/CheckmarkCircleIcon"; import { CheckmarkCircleInvertedIcon } from "../common/icons/CheckmarkCircleInvertedIcon"; -import { CrossCircleIcon } from "../common/icons/CrossCircleIcon"; import { Button } from "./Button"; -import { CodeSnippet } from "./CodeSnippet"; +import { FinishStep } from "./FinishStep"; +import { InstallStep } from "./InstallStep"; +import { ObservabilityStep } from "./ObservabilityStep"; import * as s from "./styles"; import { ConnectionCheckResultData, ConnectionCheckStatus } from "./types"; const ACTION_PREFIX = "INSTALLATION_WIZARD"; -const actions = getActions(ACTION_PREFIX, { - finish: "FINISH", - checkConnection: "CHECK_CONNECTION", - setConnectionCheckResult: "SET_CONNECTION_CHECK_RESULT" +const actions = addPrefix(ACTION_PREFIX, { + FINISH: "FINISH", + CHECK_CONNECTION: "CHECK_CONNECTION", + SET_CONNECTION_CHECK_RESULT: "SET_CONNECTION_CHECK_RESULT", + SET_OBSERVABILITY: "SET_OBSERVABILITY" }); +const DIGMA_DOCKER_EXTENSION_URL = + "https://open.docker.com/extensions/marketplace?extensionId=digmaai/digma-docker-extension"; + +const TRACKING_PREFIX = "installation wizard"; + +const trackingEvents = addPrefix( + TRACKING_PREFIX, + { + INSTALL_STEP_PASSED: "install step passed", + INSTALL_STEP_AUTOMATICALLY_PASSED: "install step automatically passed", + GET_DIGMA_DOCKER_EXTENSION_BUTTON_CLICKED: + "get digma docker extension button clicked", + OBSERVABILITY_BUTTON_CLICKED: "set observability button clicked" + }, + " " +); + const firstStep = window.wizardSkipInstallationStep === true ? 1 : 0; export const InstallationWizard = () => { const [currentStep, setCurrentStep] = useState(firstStep); const previousStep = usePrevious(currentStep); - const [isCollectorModified, setIsCollectorModified] = - useState(false); const [isAlreadyUsingOtel, setIsAlreadyUsingOtel] = useState(false); + const [isObservabilityEnabled, setIsObservabilityEnabled] = + useState(false); + const [connectionCheckStatus, setConnectionCheckStatus] = useState(); useEffect(() => { if (previousStep === 0 && currentStep === 1) { window.sendMessageToDigma({ - action: globalActions.sendTrackingEvent, + action: globalActions.SEND_TRACKING_EVENT, payload: { - eventName: "installation wizard install step passed" + eventName: trackingEvents.INSTALL_STEP_PASSED } }); } @@ -45,9 +63,9 @@ export const InstallationWizard = () => { useEffect(() => { if (firstStep === 1) { window.sendMessageToDigma({ - action: globalActions.sendTrackingEvent, + action: globalActions.SEND_TRACKING_EVENT, payload: { - eventName: "installation wizard install step automatically passed" + eventName: trackingEvents.INSTALL_STEP_AUTOMATICALLY_PASSED } }); } @@ -58,52 +76,67 @@ export const InstallationWizard = () => { }; dispatcher.addActionListener( - actions.setConnectionCheckResult, + actions.SET_CONNECTION_CHECK_RESULT, handleConnectionCheckResultData ); return () => { dispatcher.removeActionListener( - actions.setConnectionCheckResult, + actions.SET_CONNECTION_CHECK_RESULT, handleConnectionCheckResultData ); }; }, []); - const handleDigmaIsInstalledButtonClick = () => { + const handleConnectionStatusCheck = () => { setConnectionCheckStatus("pending"); window.sendMessageToDigma({ - action: actions.checkConnection + action: actions.CHECK_CONNECTION }); }; - const handleRetryButtonClick = () => { + const handleResetConnectionCheckStatus = () => { setConnectionCheckStatus(undefined); }; - const handleInstallDigmaButtonClick = () => { + const handleGetDigmaDockerDesktopButtonClick = () => { + window.sendMessageToDigma({ + action: globalActions.OPEN_URL_IN_DEFAULT_BROWSER, + payload: { + url: DIGMA_DOCKER_EXTENSION_URL + } + }); + window.sendMessageToDigma({ + action: globalActions.SET_TRACKING_EVENT, + payload: { + eventName: trackingEvents.GET_DIGMA_DOCKER_EXTENSION_BUTTON_CLICKED + } + }); + }; + + const handleIsAlreadyUsingOtelChange = (value: boolean) => { + setIsAlreadyUsingOtel(value); + }; + + const handleObservabilityChange = (value: boolean) => { + setIsObservabilityEnabled(value); window.sendMessageToDigma({ - action: globalActions.openURLInDefaultBrowser, + action: actions.SET_OBSERVABILITY, payload: { - url: "https://open.docker.com/extensions/marketplace?extensionId=digmaai/digma-docker-extension" + isObservabilityEnabled: value } }); window.sendMessageToDigma({ - action: globalActions.sendTrackingEvent, + action: globalActions.SEND_TRACKING_EVENT, payload: { - eventName: - "installation wizard get digma docker extension button clicked" + eventName: trackingEvents.OBSERVABILITY_BUTTON_CLICKED } }); }; - const handleContinueButtonClick = () => { + const goToNextStep = () => { if (currentStep < steps.length - 1) { setCurrentStep(currentStep + 1); - } else { - window.sendMessageToDigma({ - action: actions.finish - }); } }; @@ -111,274 +144,93 @@ export const InstallationWizard = () => { e.preventDefault(); if (currentStep < steps.length - 1) { setCurrentStep(currentStep + 1); + } else { + window.sendMessageToDigma({ + action: actions.FINISH + }); } }; - const handleAlreadyUsingOTELLinkClick = ( - e: React.MouseEvent - ) => { - e.preventDefault(); - setIsAlreadyUsingOtel(!isAlreadyUsingOtel); - }; - - const handleCollectorIsModifiedButtonClick = () => { - setIsCollectorModified(true); - }; - - const renderDigmaInstallationContent = () => { - const getDigmaDockerComposeCommandLinux = - "curl -L https://get.digma.ai/ --output docker-compose.yml"; - const getDigmaDockerComposeCommandWindows = - "iwr https://get.digma.ai/ -outfile docker-compose.yml"; - const runDockerComposeCommand = "docker compose up -d"; - - return ( - <> - Install Digma Docker Extension - - (You'll need{" "} - - Docker Desktop - {" "} - installed) - - - - Get Digma Docker Extension - - - or - - Run the following from the terminal/command line to start the Digma - backend: - - - (You'll need{" "} - - Docker - {" "} - and{" "} - - Docker Compose - {" "} - installed) - - Linux & MacOS: - - - Windows (PowerShell): - - Then run: - - - Prefer to use a helm file? Check out{" "} - - these - {" "} - instructions instead - - {!connectionCheckStatus && ( - - )} - {connectionCheckStatus === "success" && ( - - )} - {connectionCheckStatus === "failure" && ( - - )} - - {connectionCheckStatus && ( - - )} - - - {connectionCheckStatus === "failure" ? ( - - ) : ( - - )} - Skip for now - - - ); - }; - - const renderObservabilityContent = (isAlreadyUsingOtel: boolean) => { - const collectorConfigurationSnippet = `otlp/digma: - endpoint: "localhost:5050" - tls: - insecure: true -service: - pipelines: - traces: - exporters: [otlp/digma, ...]`; - - return isAlreadyUsingOtel ? ( - <> - Add Digma to your collector: - - Modify your collector configuration file to add Digma's backend - as a target. For example: - - - {isCollectorModified ? ( - - ) : ( - - )} - - - - Observe your application - - - - ) : ( - <> - How to get started? - - To quickly collect data from your application in IntelliJ, expand the - Digma side-panel and open the settings menu as seen below. - - - - Click the "Observability" toggle button to automatically - collect data each time you run or debug via the IDE. - - - - - Already using OpenTelemetry? - - - - ); + const handleFinishButtonClick = () => { + window.sendMessageToDigma({ + action: actions.FINISH + }); }; const steps = [ { - shortTitle: "Install Digma", title: "Get Digma up and running", - content: renderDigmaInstallationContent(), - link: { - text: "Skip for now", - onClick: (e: React.MouseEvent) => { - handleSkipLinkClick(e); - } - } + content: ( + + ) }, { - shortTitle: isAlreadyUsingOtel - ? "If you're already using OpenTelemetry…" - : "Observe your application", title: isAlreadyUsingOtel ? "If you're already using OpenTelemetry…" : "Observe your application", - content: renderObservabilityContent(isAlreadyUsingOtel), - link: { - text: "Already using OpenTelemetry?", - onClick: (e: React.MouseEvent) => { - handleAlreadyUsingOTELLinkClick(e); - } - } + content: ( + + ) + }, + { + title: "You're done!", + content: } ]; const step = steps[currentStep]; - const previousSteps = steps.slice(0, currentStep); + const nextSteps = steps.slice(currentStep + 1); return ( - {previousSteps.length > 0 ? ( - previousSteps.map((step, i) => ( - - - Step {i + 1} - {step.shortTitle} - - )) - ) : ( - Follow the steps to configure your project - )} - + + Install Digma + + Follow the steps to configure your projects + + + {previousSteps.length > 0 && + previousSteps.map((step) => ( + + + {step.title} + + ))} - Step {currentStep + 1} - {step.title} + + {currentStep + 1} + {step.title} + Skip for now + {step.content} + {nextSteps.length > 0 && + nextSteps.map((step, i) => ( + + {currentStep + 2 + i} + {step.title} + + ))} + + {currentStep === steps.length - 1 && ( + + )} + ); }; diff --git a/src/components/InstallationWizard/styles.ts b/src/components/InstallationWizard/styles.ts index b8c98ebf8..5d282ec4b 100644 --- a/src/components/InstallationWizard/styles.ts +++ b/src/components/InstallationWizard/styles.ts @@ -1,68 +1,94 @@ import styled from "styled-components"; -import { Button } from "../common/Button"; export const Container = styled.div` - background: #5a5a5a; + background: #383838; min-height: 100vh; display: flex; flex-direction: column; `; -export const PreviousStepHeader = styled.div` +export const Header = styled.div` display: flex; - gap: 8px; - padding: 9px; - color: #919191; - background: #6a6a6a; + text-align: center; font-weight: 500; + font-size: 12px; + color: #fff; + background: #2e2e2e; + padding: 8px; +`; + +export const HeaderTitle = styled.span` + padding-right: 8px; +`; + +export const HeaderSubtitle = styled.span` + padding-left: 8px; + color: #9b9b9b; + border-left: 1px solid #7c7c94; +`; + +export const Link = styled.a` font-size: 12px; line-height: 14px; - text-transform: uppercase; + color: #b9c2eb; + text-decoration: underline; + cursor: pointer; `; -export const StepShortTitle = styled.span` +export const SkipLink = styled(Link)` + padding: 2px 4px; margin-left: auto; - text-transform: none; + font-weight: 400; `; -export const Header = styled.div` +export const StepHeader = styled.div` display: flex; - text-align: center; - justify-content: center; - // TODO: check font - /* font-family: "Nunito"; */ - font-weight: 700; - font-size: 16px; - line-height: 18px; + gap: 4px; + padding: 12px 8px; color: #fff; - background: #6a6a6a; - padding: 7px 0 5px; + font-weight: 500; + font-size: 14px; + text-transform: capitalize; + border-top: 1px solid #49494d; +`; + +export const InactiveStepHeader = styled(StepHeader)` + color: #9b9b9b; `; export const Content = styled.div` - padding: 10px 16px 15px; display: flex; flex-direction: column; - flex-grow: 1; `; -export const StepCounter = styled.div` - font-weight: 500; - font-size: 12px; - line-height: 14px; - color: #919191; - text-transform: uppercase; +export const Footer = styled.div` + background: #3d3f41; + display: flex; + flex-grow: 1; + flex-direction: column; + align-items: center; + justify-content: flex-end; + margin-top: auto; + padding: 12px; `; -export const StepTitle = styled.span` +export const StepNumber = styled.div` display: flex; - // TODO: check font - /* font-family: "Nunito"; */ - font-weight: 700; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + background: #6a6dfa; + border-radius: 50%; font-size: 14px; - line-height: 18px; - margin: 4px 0 18px; + line-height: 100%; + font-weight: 500; color: #fff; + flex-shrink: 0; +`; + +export const NextStepNumber = styled(StepNumber)` + color: #383838; `; export const StepContent = styled.div` @@ -71,86 +97,33 @@ export const StepContent = styled.div` flex-grow: 1; `; -export const SectionTitle = styled.span` - // TODO: check font - /* font-family: "Nunito"; */ - font-weight: 700; +export const SectionTitle = styled.div` + display: flex; + font-weight: 500; font-size: 14px; - line-height: 16px; - margin-bottom: 6px; color: #ededed; + align-items: center; + text-transform: capitalize; `; -export const SectionDivider = styled.span` - // TODO: check font - /* font-family: "Nunito"; */ - font-weight: 700; - font-size: 12px; - line-height: 16px; - margin: 20px 0; - color: #ededed; +export const SectionIconContainer = styled.div` + display: flex; + flex-shrink: 0; `; export const SectionDescription = styled.span` - // TODO: check font - /* font-family: "Nunito"; */ font-size: 12px; - line-height: 16px; - color: #cdcdcd; - margin-bottom: 6px; + color: #9b9b9b; `; -export const LoaderContainer = styled.div` - margin-top: 23; - align-self: center; +export const IllustrationContainer = styled.div` + height: 123px; + width: 312px; + background: #393739; + border-radius 4px; + position: relative; + overflow: hidden; display: flex; - justify-content: center; align-items: center; - flex-grow: 1; -`; - -export const Footer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - margin-top: auto; - padding-top: 8px; - gap: 8px; -`; - -export const Link = styled.a` - font-size: 12px; - line-height: 14px; - color: #dadada; - text-decoration: underline; - cursor: pointer; -`; - -// export const SectionNumber = styled.div` -// display: flex; -// align-items: center; -// justify-content: center; -// width: 17px; -// height: 17px; -// background: #5154ec; -// border-radius: 50%; -// TODO: check font -/* font-family: "Mulish"; */ -// font-size: 12px; -// line-height: 15px; -// color: #fff; -// margin: 15px 0 13px; -// `; - -export const Illustration = styled.img` - margin: 12px 0; - max-width: 500px; -`; - -export const GetDigmaButton = styled(Button)` - width: max-content; - font-size: 12px; - line-height: 14px; - padding: 6px 8px; - height: 26px; + justify-content: center; `; diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index 7522929a5..7a165be79 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from "react"; import { dispatcher } from "../../dispatcher"; import { usePrevious } from "../../hooks/usePrevious"; -import { getActions } from "../../utils/getActions"; +import { addPrefix } from "../../utils/addPrefix"; import { groupBy } from "../../utils/groupBy"; import { actions as globalActions } from "../common/App"; import { CursorFollower } from "../common/CursorFollower"; @@ -29,11 +29,11 @@ const REFRESH_INTERVAL = const ACTION_PREFIX = "RECENT_ACTIVITY"; -const actions = getActions(ACTION_PREFIX, { - getData: "GET_DATA", - setData: "SET_DATA", - goToSpan: "GO_TO_SPAN", - goToTrace: "GO_TO_TRACE" +const actions = addPrefix(ACTION_PREFIX, { + GET_DATA: "GET_DATA", + SET_DATA: "SET_DATA", + GO_TO_SPAN: "GO_TO_SPAN", + GO_TO_TRACE: "GO_TO_TRACE" }); const renderNoData = () => { @@ -73,11 +73,11 @@ export const RecentActivity = (props: RecentActivityProps) => { useEffect(() => { window.sendMessageToDigma({ - action: actions.getData + action: actions.GET_DATA }); const refreshInterval = setInterval(() => { window.sendMessageToDigma({ - action: actions.getData + action: actions.GET_DATA }); }, REFRESH_INTERVAL); @@ -89,9 +89,9 @@ export const RecentActivity = (props: RecentActivityProps) => { setIsJaegerEnabled((data as SetIsJaegerData).isJaegerEnabled); }; - dispatcher.addActionListener(actions.setData, handleRecentActivityData); + dispatcher.addActionListener(actions.SET_DATA, handleRecentActivityData); dispatcher.addActionListener( - globalActions.setIsJaegerEnabled, + globalActions.SET_IS_JAEGER_ENABLED, handleSetIsJaegerEnabledData ); @@ -99,11 +99,11 @@ export const RecentActivity = (props: RecentActivityProps) => { clearInterval(refreshInterval); dispatcher.removeActionListener( - actions.setData, + actions.SET_DATA, handleRecentActivityData ); dispatcher.removeActionListener( - globalActions.setIsJaegerEnabled, + globalActions.SET_IS_JAEGER_ENABLED, handleSetIsJaegerEnabledData ); }; @@ -129,7 +129,7 @@ export const RecentActivity = (props: RecentActivityProps) => { const handleSpanLinkClick = (span: EntrySpan, environment: string) => { window.sendMessageToDigma({ - action: actions.goToSpan, + action: actions.GO_TO_SPAN, payload: { span, environment @@ -139,7 +139,7 @@ export const RecentActivity = (props: RecentActivityProps) => { const handleTraceButtonClick = (traceId: string, span: EntrySpan) => { window.sendMessageToDigma({ - action: actions.goToTrace, + action: actions.GO_TO_TRACE, payload: { traceId, span diff --git a/src/components/common/App/index.tsx b/src/components/common/App/index.tsx index 8bbb12cdf..e96585cdb 100644 --- a/src/components/common/App/index.tsx +++ b/src/components/common/App/index.tsx @@ -4,7 +4,7 @@ import { dispatcher } from "../../../dispatcher"; import { Mode } from "../../../globals"; import { isObject } from "../../../typeGuards/isObject"; import { isString } from "../../../typeGuards/isString"; -import { getActions } from "../../../utils/getActions"; +import { addPrefix } from "../../../utils/addPrefix"; import { GlobalStyle } from "./styles"; import { AppProps } from "./types"; @@ -27,13 +27,13 @@ const getMode = (): Mode => { const ACTION_PREFIX = "GLOBAL"; -export const actions = getActions(ACTION_PREFIX, { - setColorMode: "SET_THEME", - setMainFont: "SET_MAIN_FONT", - setCodeFont: "SET_CODE_FONT", - openURLInDefaultBrowser: "OPEN_URL_IN_DEFAULT_BROWSER", - sendTrackingEvent: "SEND_TRACKING_EVENT", - setIsJaegerEnabled: "SET_IS_JAEGER_ENABLED" +export const actions = addPrefix(ACTION_PREFIX, { + SET_THEME: "SET_THEME", + SET_MAIN_FONT: "SET_MAIN_FONT", + 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" }); export const App = (props: AppProps) => { @@ -49,7 +49,7 @@ export const App = (props: AppProps) => { }, [props.theme]); useEffect(() => { - const handleSetColorMode = (data: unknown) => { + const handleSetTheme = (data: unknown) => { if (isObject(data) && isMode(data.theme)) { setMode(data.theme); } @@ -67,14 +67,14 @@ export const App = (props: AppProps) => { } }; - dispatcher.addActionListener(actions.setColorMode, handleSetColorMode); - dispatcher.addActionListener(actions.setMainFont, handleSetMainFont); - dispatcher.addActionListener(actions.setCodeFont, handleSetCodeFont); + dispatcher.addActionListener(actions.SET_THEME, handleSetTheme); + dispatcher.addActionListener(actions.SET_MAIN_FONT, handleSetMainFont); + dispatcher.addActionListener(actions.SET_CODE_FONT, handleSetCodeFont); return () => { - dispatcher.removeActionListener(actions.setColorMode, handleSetColorMode); - dispatcher.removeActionListener(actions.setMainFont, handleSetMainFont); - dispatcher.removeActionListener(actions.setCodeFont, handleSetCodeFont); + dispatcher.removeActionListener(actions.SET_THEME, handleSetTheme); + dispatcher.removeActionListener(actions.SET_MAIN_FONT, handleSetMainFont); + dispatcher.removeActionListener(actions.SET_CODE_FONT, handleSetCodeFont); }; }, []); diff --git a/src/components/common/Loader/index.tsx b/src/components/common/Loader/index.tsx index 469b4af72..702deed9a 100644 --- a/src/components/common/Loader/index.tsx +++ b/src/components/common/Loader/index.tsx @@ -229,7 +229,7 @@ const LoaderComponent = (props: LoaderProps) => { d="M112.241 75.0235C112.241 75.0235 121.052 67.0769 121.32 66.789C121.595 66.3641 121.753 65.8742 121.779 65.3687C121.804 64.8633 121.696 64.3601 121.464 63.9098C120.898 63.1133 121.349 64.4857 120.927 65.4358C120.505 66.3859 110.12 72.1539 110.12 72.1539L112.241 75.0235Z" /> { { ); diff --git a/src/components/common/ToggleSwitch/ToggleSwitch.stories.tsx b/src/components/common/ToggleSwitch/ToggleSwitch.stories.tsx new file mode 100644 index 000000000..832c03691 --- /dev/null +++ b/src/components/common/ToggleSwitch/ToggleSwitch.stories.tsx @@ -0,0 +1,23 @@ +import { ComponentMeta, ComponentStory } from "@storybook/react"; + +import { ToggleSwitch } from "."; +import { ToggleSwitchProps } from "./types"; + +export default { + title: "Common/ToggleSwitch", + component: ToggleSwitch, + parameters: { + // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +} as ComponentMeta; + +const Template: ComponentStory = ( + args: ToggleSwitchProps +) => ; + +export const Default = Template.bind({}); +Default.args = { + label: "Click here", + checked: false +}; diff --git a/src/components/common/ToggleSwitch/index.tsx b/src/components/common/ToggleSwitch/index.tsx new file mode 100644 index 000000000..323c70fdf --- /dev/null +++ b/src/components/common/ToggleSwitch/index.tsx @@ -0,0 +1,18 @@ +import * as s from "./styles"; +import { ToggleSwitchProps } from "./types"; + +export const ToggleSwitch = (props: ToggleSwitchProps) => { + const handleContainerClick = () => { + if (props.onChange) { + props.onChange(!props.checked); + } + }; + return ( + + {props.label} + + + + + ); +}; diff --git a/src/components/common/ToggleSwitch/styles.ts b/src/components/common/ToggleSwitch/styles.ts new file mode 100644 index 000000000..e237b16f3 --- /dev/null +++ b/src/components/common/ToggleSwitch/styles.ts @@ -0,0 +1,37 @@ +import styled from "styled-components"; +import { CircleProps, SwitchContainerProps } from "./types"; + +export const Container = styled.div` + display: flex; + color: #fff; + font-size: 10px; + font-weight: 500; + line-height: 12px; + gap: 10px; + padding: 8px 2px 8px 0; + align-items: center; + cursor: pointer; + user-select: none; +`; + +export const SwitchContainer = styled.div` + border-radius: 8px; + background: ${({ isChecked }) => (isChecked ? "#3538cd" : "#7c7c94")}; + width: 28px; + height: 16px; + position: relative; + transition-property: background; + transition-duration: 0.3s; +`; + +export const Circle = styled.div` + width: 8px; + height: 8px; + border-radius: 50%; + background: ${({ isChecked }) => (isChecked ? "#fbfdff" : "#b9c0d4")}; + position: absolute; + top: 4px; + left: ${({ isChecked }) => (isChecked ? "16px" : "4px")}; + transition-property: background, left; + transition-duration: 0.3s; +`; diff --git a/src/components/common/ToggleSwitch/types.ts b/src/components/common/ToggleSwitch/types.ts new file mode 100644 index 000000000..70cf47cd3 --- /dev/null +++ b/src/components/common/ToggleSwitch/types.ts @@ -0,0 +1,11 @@ +export interface ToggleSwitchProps { + label: string; + checked: boolean; + onChange?: (value: boolean) => void; +} + +export interface SwitchContainerProps { + isChecked: boolean; +} + +export type CircleProps = SwitchContainerProps; diff --git a/src/components/common/icons/CodeIcon.tsx b/src/components/common/icons/CodeIcon.tsx new file mode 100644 index 000000000..961a35bb6 --- /dev/null +++ b/src/components/common/icons/CodeIcon.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const CodeIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const CodeIcon = React.memo(CodeIconComponent); diff --git a/src/components/common/icons/DataIcon.tsx b/src/components/common/icons/DataIcon.tsx index 864b79823..51b43221b 100644 --- a/src/components/common/icons/DataIcon.tsx +++ b/src/components/common/icons/DataIcon.tsx @@ -18,12 +18,12 @@ const DataIconComponent = (props: IconProps) => { strokeLinecap="round" strokeLinejoin="round" strokeWidth=".5" - clipPath="url(#a)" + clipPath="url(#data-clip-1)" > - + diff --git a/src/components/common/icons/DigmaLogoFlatIcon.tsx b/src/components/common/icons/DigmaLogoFlatIcon.tsx index d77513c96..fe36aeef8 100644 --- a/src/components/common/icons/DigmaLogoFlatIcon.tsx +++ b/src/components/common/icons/DigmaLogoFlatIcon.tsx @@ -13,12 +13,12 @@ const DigmaLogoFlatIconComponent = (props: IconProps) => { fill="none" viewBox="0 0 22 25" > - + diff --git a/src/components/common/icons/DockerLogoIcon.tsx b/src/components/common/icons/DockerLogoIcon.tsx new file mode 100644 index 000000000..4e08e4150 --- /dev/null +++ b/src/components/common/icons/DockerLogoIcon.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const DockerLogoComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + ); +}; + +export const DockerLogoIcon = React.memo(DockerLogoComponent); diff --git a/src/components/common/icons/HTTPClientIcon.tsx b/src/components/common/icons/HTTPClientIcon.tsx index 2d76de685..261d5ec08 100644 --- a/src/components/common/icons/HTTPClientIcon.tsx +++ b/src/components/common/icons/HTTPClientIcon.tsx @@ -13,7 +13,11 @@ const HTTPClientIconComponent = (props: IconProps) => { fill="none" viewBox="0 0 16 16" > - + { /> - + diff --git a/src/components/common/icons/LightBulbIcon.tsx b/src/components/common/icons/LightBulbIcon.tsx new file mode 100644 index 000000000..eb5dbc2fc --- /dev/null +++ b/src/components/common/icons/LightBulbIcon.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const LightBulbIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + ); +}; + +export const LightBulbIcon = React.memo(LightBulbIconComponent); diff --git a/src/components/common/icons/OpenTelemetryLogoIcon.tsx b/src/components/common/icons/OpenTelemetryLogoIcon.tsx index b7bac9c80..f9da2a52e 100644 --- a/src/components/common/icons/OpenTelemetryLogoIcon.tsx +++ b/src/components/common/icons/OpenTelemetryLogoIcon.tsx @@ -3,7 +3,7 @@ import { useIconProps } from "./hooks"; import { IconProps } from "./types"; const OpenTelemetryLogoIconComponent = (props: IconProps) => { - const { size, color } = useIconProps(props); + const { size } = useIconProps(props); return ( { fill="none" viewBox="0 0 12 12" > - + - + diff --git a/src/components/common/icons/PlayIcon.tsx b/src/components/common/icons/PlayIcon.tsx new file mode 100644 index 000000000..e8a50dc0c --- /dev/null +++ b/src/components/common/icons/PlayIcon.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const PlayIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const PlayIcon = React.memo(PlayIconComponent); diff --git a/src/utils/addPrefix.ts b/src/utils/addPrefix.ts new file mode 100644 index 000000000..be200e075 --- /dev/null +++ b/src/utils/addPrefix.ts @@ -0,0 +1,13 @@ +export const addPrefix = ( + prefix: string, + actions: Record, + separator?: string +): Record => { + const res: Record = {}; + for (const [key, value] of Object.entries(actions)) { + res[key] = `${prefix}${ + typeof separator === "string" ? separator : "/" + }${value}`; + } + return res; +}; diff --git a/src/utils/getActions.ts b/src/utils/getActions.ts deleted file mode 100644 index 6f04861f6..000000000 --- a/src/utils/getActions.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const getActions = (prefix: string, actions: Record) => { - const res: Record = {}; - for (const [key, value] of Object.entries(actions)) { - res[key] = `${prefix}/${value}`; - } - return res; -};