From 5841993ba22de0c3b1e26c4bf1550be1bcdcaae7 Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Sun, 15 Dec 2024 16:39:33 +0200 Subject: [PATCH 1/8] feat: "other agent" for new UI --- .../configured-destinations-list/index.tsx | 137 +++--------------- .../sources/source-drawer-container/index.tsx | 8 +- .../condition-details/index.tsx | 2 +- .../data-card/data-card-fields/index.tsx | 23 ++- .../reuseable-components/data-tab/index.tsx | 114 +++++++++------ .../extend-icon/index.tsx | 6 +- .../monitors-icons/index.tsx | 9 +- 7 files changed, 129 insertions(+), 170 deletions(-) diff --git a/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx b/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx index 89360955a4..0991e17616 100644 --- a/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx +++ b/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; import Image from 'next/image'; import styled from 'styled-components'; +import { extractMonitors } from '@/utils'; import { DeleteWarning } from '@/components'; import { IAppState, useAppStore } from '@/store'; import { OVERVIEW_ENTITY_TYPES, type ConfiguredDestination } from '@/types'; -import { Button, DataCardFields, Divider, ExtendIcon, Text } from '@/reuseable-components'; +import { DataCardFields, DataTab, IconButton, Text } from '@/reuseable-components'; const Container = styled.div` display: flex; @@ -18,128 +19,26 @@ const Container = styled.div` overflow-y: scroll; `; -const ListItem = styled.div` - width: 100%; - border-radius: 16px; - background: ${({ theme }) => theme.colors.translucent_bg}; -`; - -const ListItemBody = styled.div` - width: 100%; - padding: 16px; -`; - -const ListItemHeader = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 16px 0px; -`; - -const ListItemContent = styled.div` - display: flex; - gap: 12px; - margin-left: 16px; -`; - -const DestinationIconWrapper = styled.div` - display: flex; - width: 36px; - height: 36px; - justify-content: center; - align-items: center; - gap: 8px; - border-radius: 8px; - background: linear-gradient(180deg, rgba(249, 249, 249, 0.06) 0%, rgba(249, 249, 249, 0.02) 100%); -`; - -const SignalsWrapper = styled.div` - display: flex; - align-items: center; - gap: 4px; -`; - -const SignalText = styled(Text)` - color: rgba(249, 249, 249, 0.8); - font-size: 10px; - text-transform: capitalize; -`; - -const TextWrapper = styled.div` - display: flex; - flex-direction: column; - height: 36px; - justify-content: space-between; -`; - -const IconsContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; - margin-right: 16px; -`; - -const IconButton = styled(Button)<{ $expand?: boolean }>` - transition: background 0.3s ease 0s, transform 0.3s ease 0s; - transform: ${({ $expand }) => ($expand ? 'rotate(-180deg)' : 'rotate(0deg)')}; -`; - -const ConfiguredDestinationsListItem: React.FC<{ item: ConfiguredDestination; isLastItem: boolean }> = ({ item, isLastItem }) => { - const [expand, setExpand] = useState(false); - const [deleteWarning, setDeleteWarning] = useState(false); +const ListItem: React.FC<{ item: ConfiguredDestination; isLastItem: boolean }> = ({ item, isLastItem }) => { const { removeConfiguredDestination } = useAppStore((state) => state); - - function renderSupportedSignals(item: ConfiguredDestination) { - const supportedSignals = item.exportedSignals; - const signals = Object.keys(supportedSignals); - const supportedSignalsList = signals.filter((signal) => supportedSignals[signal].supported); - - return Object.keys(supportedSignals).map( - (signal, index) => - supportedSignals[signal] && ( - - monitor - - {signal} - {index < supportedSignalsList.length - 1 && ·} - - ), - ); - } + const [deleteWarning, setDeleteWarning] = useState(false); return ( <> - - - - - destination - - - {item.displayName} - {renderSupportedSignals(item)} - - - - - setDeleteWarning(true)}> - delete - - - setExpand(!expand)}> - - - - - - {expand && ( - - - - + } + renderActions={() => ( + setDeleteWarning(true)}> + delete + )} - + /> {data.map(({ stored }) => ( - + ))} ); diff --git a/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx b/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx index b1feeeb42c..5a86fbf4e9 100644 --- a/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx +++ b/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx @@ -8,7 +8,7 @@ import { useDescribeSource, useSourceCRUD } from '@/hooks'; import OverviewDrawer from '../../overview/overview-drawer'; import { OVERVIEW_ENTITY_TYPES, type WorkloadId, type K8sActualSource } from '@/types'; import { ConditionDetails, DataCard, DataCardRow, DataCardFieldTypes } from '@/reuseable-components'; -import { ACTION, DATA_CARDS, getMainContainerLanguage, getProgrammingLanguageIcon, safeJsonStringify } from '@/utils'; +import { ACTION, BACKEND_BOOLEAN, DATA_CARDS, getMainContainerLanguage, getProgrammingLanguageIcon, safeJsonStringify } from '@/utils'; interface Props {} @@ -79,13 +79,17 @@ export const SourceDrawer: React.FC = () => { const { item } = selectedItem as { item: K8sActualSource }; + const hasPresenceOfOtherAgent = item.instrumentedApplicationDetails.conditions.some( + (condition) => condition.status === BACKEND_BOOLEAN.FALSE && condition.message.includes('device not added to any container due to the presence of another agent'), + ); + return ( item.instrumentedApplicationDetails.containers.map( (container) => ({ type: DataCardFieldTypes.SOURCE_CONTAINER, width: '100%', - value: JSON.stringify(container), + value: JSON.stringify({ ...container, hasPresenceOfOtherAgent }), } as DataCardRow), ) || [] ); diff --git a/frontend/webapp/reuseable-components/condition-details/index.tsx b/frontend/webapp/reuseable-components/condition-details/index.tsx index 392a66c061..3c91cb8006 100644 --- a/frontend/webapp/reuseable-components/condition-details/index.tsx +++ b/frontend/webapp/reuseable-components/condition-details/index.tsx @@ -60,7 +60,7 @@ export const ConditionDetails: React.FC = ({ conditions }) => { ({hasErrors ? errors.length : conditions.length}/{conditions.length}) - + {extend && ( diff --git a/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx b/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx index c09ce002e6..7527d9928d 100644 --- a/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx +++ b/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx @@ -1,7 +1,8 @@ import React, { useId } from 'react'; import styled from 'styled-components'; -import { ActiveStatus, Code, DataTab, Divider, InstrumentStatus, MonitorsIcons, Text, Tooltip } from '@/reuseable-components'; +import { ActiveStatus, Code, DataTab, Divider, InstrumentStatus, MonitorsIcons, NotificationNote, Text, Tooltip } from '@/reuseable-components'; import { capitalizeFirstLetter, getProgrammingLanguageIcon, parseJsonStringToPrettyString, safeJsonParse, WORKLOAD_PROGRAMMING_LANGUAGES } from '@/utils'; +import { NOTIFICATION_TYPE } from '@/types'; export enum DataCardFieldTypes { DIVIDER = 'divider', @@ -81,17 +82,35 @@ const renderValue = (type: DataCardRow['type'], value: DataCardRow['value']) => return ; case DataCardFieldTypes.SOURCE_CONTAINER: { - const { containerName, language, runtimeVersion } = safeJsonParse(value, { + const { containerName, language, runtimeVersion, otherAgent, hasPresenceOfOtherAgent } = safeJsonParse(value, { containerName: '-', language: WORKLOAD_PROGRAMMING_LANGUAGES.UNKNOWN, runtimeVersion: '-', + otherAgent: null, + hasPresenceOfOtherAgent: false, }); + // Determine if running concurrently is possible based on language and other_agent + const canRunInParallel = !hasPresenceOfOtherAgent && (language === WORKLOAD_PROGRAMMING_LANGUAGES.PYTHON || language === WORKLOAD_PROGRAMMING_LANGUAGES.JAVA); + return ( ( + + )} > diff --git a/frontend/webapp/reuseable-components/data-tab/index.tsx b/frontend/webapp/reuseable-components/data-tab/index.tsx index 71575cc7ed..b9d79a6b7b 100644 --- a/frontend/webapp/reuseable-components/data-tab/index.tsx +++ b/frontend/webapp/reuseable-components/data-tab/index.tsx @@ -1,24 +1,28 @@ -import React, { PropsWithChildren, useCallback } from 'react'; +import React, { Fragment, PropsWithChildren, useCallback, useState } from 'react'; import Image from 'next/image'; -import { FlexColumn } from '@/styles'; +import { FlexColumn, FlexRow } from '@/styles'; import styled, { css } from 'styled-components'; -import { ActiveStatus, MonitorsIcons, Text } from '@/reuseable-components'; +import { ActiveStatus, Divider, ExtendIcon, IconButton, MonitorsIcons, Text } from '@/reuseable-components'; interface Props extends PropsWithChildren { title: string; subTitle: string; logo: string; monitors?: string[]; + monitorsWithLabels?: boolean; isActive?: boolean; isError?: boolean; + withExtend?: boolean; + isExtended?: boolean; + renderExtended?: () => JSX.Element; + renderActions?: () => JSX.Element; onClick?: () => void; } const Container = styled.div<{ $withClick: boolean; $isError: Props['isError'] }>` display: flex; - align-items: center; + flex-direction: column; align-self: stretch; - gap: 8px; padding: 16px; width: calc(100% - 32px); border-radius: 16px; @@ -51,6 +55,7 @@ const Title = styled(Text)` overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + font-size: 14px; `; const SubTitleWrapper = styled.div` @@ -71,45 +76,72 @@ const ActionsWrapper = styled.div` margin-left: auto; `; -export const DataTab: React.FC = ({ title, subTitle, logo, monitors, isActive, isError, onClick, children }) => { - const renderMonitors = useCallback(() => { - if (!monitors) return null; - - return ( - <> - {'•'} - - - ); - }, [monitors]); - - const renderActiveStatus = useCallback(() => { - if (typeof isActive !== 'boolean') return null; - - return ( - <> - {'•'} - - - ); - }, [isActive]); +export const DataTab: React.FC = ({ title, subTitle, logo, monitors, monitorsWithLabels, isActive, isError, withExtend, isExtended, renderExtended, renderActions, onClick }) => { + const [extend, setExtend] = useState(isExtended || false); + + const renderMonitors = useCallback( + (withSeperator: boolean) => { + if (!monitors || !monitors.length) return null; + + return ( + <> + {withSeperator && {'•'}} + + + ); + }, + [monitors], + ); + + const renderActiveStatus = useCallback( + (withSeperator: boolean) => { + if (typeof isActive !== 'boolean') return null; + + return ( + <> + {withSeperator && {'•'}} + + + ); + }, + [isActive], + ); return ( - - - - - - {title} - - {subTitle} - {renderMonitors()} - {renderActiveStatus()} - - - - {children} + + + + + + + {title} + + {subTitle && {subTitle}} + {renderMonitors(!!subTitle)} + {renderActiveStatus(!!monitors?.length)} + + + + + {renderActions && renderActions()} + {withExtend && ( + + + setExtend((prev) => !prev)}> + + + + )} + + + + {extend && renderExtended && ( + + + {renderExtended()} + + )} ); }; diff --git a/frontend/webapp/reuseable-components/extend-icon/index.tsx b/frontend/webapp/reuseable-components/extend-icon/index.tsx index c36caae5ec..62b93d8e43 100644 --- a/frontend/webapp/reuseable-components/extend-icon/index.tsx +++ b/frontend/webapp/reuseable-components/extend-icon/index.tsx @@ -5,10 +5,11 @@ import styled from 'styled-components'; interface Props { extend: boolean; size?: number; - align?: 'left' | 'right'; + align?: 'left' | 'right' | 'center'; } const Icon = styled(Image)<{ $align?: Props['align'] }>` + margin: ${({ $align }) => ($align === 'right' ? 'auto 0 auto auto' : $align === 'left' ? 'auto auto auto 0' : 'auto')}; &.open { transform: rotate(180deg); } @@ -16,9 +17,8 @@ const Icon = styled(Image)<{ $align?: Props['align'] }>` transform: rotate(0deg); } transition: transform 0.3s; - margin-${({ $align }) => ($align === 'right' ? 'left' : 'right')}: auto; `; -export const ExtendIcon: React.FC = ({ extend, size = 14, align = 'right' }) => { +export const ExtendIcon: React.FC = ({ extend, size = 14, align = 'center' }) => { return ; }; diff --git a/frontend/webapp/reuseable-components/monitors-icons/index.tsx b/frontend/webapp/reuseable-components/monitors-icons/index.tsx index 9c580d4514..ccd8ae1faf 100644 --- a/frontend/webapp/reuseable-components/monitors-icons/index.tsx +++ b/frontend/webapp/reuseable-components/monitors-icons/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import Image from 'next/image'; +import theme from '@/styles/theme'; import { FlexRow } from '@/styles'; import { capitalizeFirstLetter } from '@/utils'; import { Text, Tooltip } from '@/reuseable-components'; @@ -15,14 +16,18 @@ export const MonitorsIcons: React.FC = ({ monitors, withTooltips, withLab return ( {monitors.map((str) => { - const signal = str.toLocaleLowerCase(); + const signal = str.toLowerCase(); const signalDisplayName = capitalizeFirstLetter(signal); return ( {signal} - {withLabels && {signalDisplayName}} + {withLabels && ( + + {signalDisplayName} + + )} ); From 894e04afe881a33c0f8838191822e983b3672a2f Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Fri, 20 Dec 2024 13:06:27 +0200 Subject: [PATCH 2/8] refactor: enhance tooltip positioning logic with useRef for better accuracy --- .../reuseable-components/tooltip/index.tsx | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/frontend/webapp/reuseable-components/tooltip/index.tsx b/frontend/webapp/reuseable-components/tooltip/index.tsx index 7e77424f43..77a66e8a25 100644 --- a/frontend/webapp/reuseable-components/tooltip/index.tsx +++ b/frontend/webapp/reuseable-components/tooltip/index.tsx @@ -1,9 +1,8 @@ -import React, { useState, PropsWithChildren } from 'react'; -import Image from 'next/image'; +import React, { useState, PropsWithChildren, useRef, MouseEvent, forwardRef } from 'react'; import ReactDOM from 'react-dom'; -import { Text } from '../text'; -import styled from 'styled-components'; +import { Text } from '..'; import { InfoIcon } from '@/assets'; +import styled from 'styled-components'; interface Position { top: number; @@ -27,17 +26,17 @@ const TooltipContainer = styled.div` export const Tooltip: React.FC = ({ text, withIcon, children }) => { const [isHovered, setIsHovered] = useState(false); const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0 }); + const popupRef = useRef(null); - const handleMouseEvent = (e: React.MouseEvent) => { + const handleMouseEvent = (e: MouseEvent) => { const { type, clientX, clientY } = e; const { innerWidth, innerHeight } = window; let top = clientY; let left = clientX; - const textLen = text?.length || 0; - if (top >= innerHeight / 2) top += -40; - if (left >= innerWidth / 2) left += -(textLen * 8); + if (top >= innerHeight / 2) top += -(popupRef.current?.clientHeight || 40); + if (left >= innerWidth / 2) left += -(popupRef.current?.clientWidth || Math.min((text?.length || 0) * 7.5, 300)); setPopupPosition({ top, left }); setIsHovered(type !== 'mouseleave'); @@ -49,7 +48,11 @@ export const Tooltip: React.FC = ({ text, withIcon, children }) => {children} {withIcon && } - {isHovered && {text}} + {isHovered && ( + + {text} + + )} ); }; @@ -70,11 +73,11 @@ const PopupContainer = styled.div<{ $top: number; $left: number }>` pointer-events: none; `; -const Popup: React.FC = ({ top, left, children }) => { +const Popup = forwardRef(({ top, left, children }, ref) => { return ReactDOM.createPortal( - + {children} , document.body, ); -}; +}); From 872f5153793d2659b6749f5310f533eecc1965bd Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Fri, 20 Dec 2024 14:39:45 +0200 Subject: [PATCH 3/8] refactor: enhance useConnectDestinationForm to support additional component properties for checkboxes --- .../hooks/destinations/useConnectDestinationForm.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts b/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts index 5d4cff9b82..2e03fa4920 100644 --- a/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts +++ b/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts @@ -58,7 +58,16 @@ export function useConnectDestinationForm() { }; case INPUT_TYPES.KEY_VALUE_PAIR: + return { + name, + componentType, + title: displayName, + ...componentPropertiesJson, + }; + case INPUT_TYPES.CHECKBOX: + componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); + return { name, componentType, From 81a8131ad1a4797193d2f44488b65515ca7732bc Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Fri, 20 Dec 2024 14:48:33 +0200 Subject: [PATCH 4/8] feat: enhance AutocompleteInput with autoFocus --- .../containers/main/actions/action-modal/index.tsx | 2 +- .../auto-complete-input/index.tsx | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/webapp/containers/main/actions/action-modal/index.tsx b/frontend/webapp/containers/main/actions/action-modal/index.tsx index 6d97d5ea76..aa33fde30c 100644 --- a/frontend/webapp/containers/main/actions/action-modal/index.tsx +++ b/frontend/webapp/containers/main/actions/action-modal/index.tsx @@ -60,7 +60,7 @@ export const ActionModal: React.FC = ({ isOpen, onClose }) => { > - + {!!selectedItem?.type ? (
diff --git a/frontend/webapp/reuseable-components/auto-complete-input/index.tsx b/frontend/webapp/reuseable-components/auto-complete-input/index.tsx index 84c21ca582..42ad312661 100644 --- a/frontend/webapp/reuseable-components/auto-complete-input/index.tsx +++ b/frontend/webapp/reuseable-components/auto-complete-input/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, type ChangeEvent, type KeyboardEvent, type FC } from 'react'; +import React, { useState, type ChangeEvent, type KeyboardEvent, type FC, type InputHTMLAttributes } from 'react'; import { SVG } from '@/assets'; import { Text } from '../text'; import styled from 'styled-components'; @@ -11,7 +11,7 @@ export interface Option { items?: Option[]; // For handling a list of items } -interface Props { +interface Props extends InputHTMLAttributes { options: Option[]; placeholder?: string; selectedOption?: Option; @@ -34,7 +34,7 @@ const filterOptions = (optionsList: Option[], input: string): Option[] => { }, []); }; -export const AutocompleteInput: FC = ({ placeholder = 'Type to search...', options, selectedOption, onOptionSelect, style, disabled }) => { +export const AutocompleteInput: FC = ({ placeholder = 'Type to search...', options, selectedOption, onOptionSelect, style, disabled, ...props }) => { const [query, setQuery] = useState(selectedOption?.label || ''); const [filteredOptions, setFilteredOptions] = useState(filterOptions(options, '')); const [showOptions, setShowOptions] = useState(false); @@ -99,6 +99,7 @@ export const AutocompleteInput: FC = ({ placeholder = 'Type to search...' disabled={disabled} onBlur={() => !disabled && setShowOptions(false)} onFocus={() => !disabled && setShowOptions(true)} + {...props} /> @@ -199,10 +200,10 @@ const StyledInput = styled.input` const OptionsList = styled.ul` position: absolute; - max-height: 348px; + max-height: 500px; top: 32px; border-radius: 24px; - width: calc(100% - 24px); + width: calc(100% - 32px); overflow-y: auto; background-color: ${({ theme }) => theme.colors.dropdown_bg}; border: 1px solid ${({ theme }) => theme.colors.border}; From da952a53b0c9dffde360dfa16dcb90cb1ffa04e5 Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Fri, 20 Dec 2024 16:32:52 +0200 Subject: [PATCH 5/8] refactor: streamline form field handling by consolidating field properties and enhancing initial value management --- .../destinations/useConnectDestinationForm.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts b/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts index 2e03fa4920..87dbccee91 100644 --- a/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts +++ b/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts @@ -5,10 +5,11 @@ export function useConnectDestinationForm() { function buildFormDynamicFields(fields: DestinationDetailsField[]): DynamicField[] { return fields .map((field) => { - const { name, componentType, displayName, componentProperties, initialValue } = field; + const { componentType, displayName, componentProperties, initialValue, ...restOfField } = field; let componentPropertiesJson; let initialValuesJson; + switch (componentType) { case INPUT_TYPES.DROPDOWN: componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); @@ -24,12 +25,12 @@ export function useConnectDestinationForm() { })); return { - name, componentType, title: displayName, - onSelect: () => {}, options, - placeholder: 'Select an option', + onSelect: () => {}, + placeholder: componentPropertiesJson.placeholder || 'Select an option', + ...restOfField, ...componentPropertiesJson, }; @@ -38,9 +39,11 @@ export function useConnectDestinationForm() { componentPropertiesJson = safeJsonParse(componentProperties, []); return { - name, componentType, title: displayName, + initialValue, + value: initialValue, + ...restOfField, ...componentPropertiesJson, }; @@ -49,29 +52,33 @@ export function useConnectDestinationForm() { initialValuesJson = safeJsonParse(initialValue, []); return { - name, componentType, title: displayName, initialValues: initialValuesJson, value: initialValuesJson, + ...restOfField, ...componentPropertiesJson, }; case INPUT_TYPES.KEY_VALUE_PAIR: return { - name, componentType, title: displayName, - ...componentPropertiesJson, + initialValue, + value: initialValue, + componentProperties, + ...restOfField, }; case INPUT_TYPES.CHECKBOX: componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); return { - name, componentType, title: displayName, + initialValue, + value: initialValue, + ...restOfField, ...componentPropertiesJson, }; From e26d04054e719d8d251a9b1aea157f4e60278561 Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Fri, 20 Dec 2024 16:56:22 +0200 Subject: [PATCH 6/8] refactor: improve form field handling and input component to support dynamic initial values --- .../destinations/useConnectDestinationForm.ts | 60 +++++++------------ .../reuseable-components/input/index.tsx | 8 +-- 2 files changed, 24 insertions(+), 44 deletions(-) diff --git a/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts b/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts index 87dbccee91..4dd8827b31 100644 --- a/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts +++ b/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts @@ -5,79 +5,59 @@ export function useConnectDestinationForm() { function buildFormDynamicFields(fields: DestinationDetailsField[]): DynamicField[] { return fields .map((field) => { - const { componentType, displayName, componentProperties, initialValue, ...restOfField } = field; + const { componentType, displayName, initialValue, componentProperties, ...restOfField } = field; let componentPropertiesJson; let initialValuesJson; switch (componentType) { - case INPUT_TYPES.DROPDOWN: - componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); - - const options = Array.isArray(componentPropertiesJson.values) - ? componentPropertiesJson.values.map((value) => ({ - id: value, - value, - })) - : Object.entries(componentPropertiesJson.values).map(([key, value]) => ({ - id: key, - value, - })); - - return { - componentType, - title: displayName, - options, - onSelect: () => {}, - placeholder: componentPropertiesJson.placeholder || 'Select an option', - ...restOfField, - ...componentPropertiesJson, - }; - case INPUT_TYPES.INPUT: case INPUT_TYPES.TEXTAREA: - componentPropertiesJson = safeJsonParse(componentProperties, []); + case INPUT_TYPES.CHECKBOX: + case INPUT_TYPES.KEY_VALUE_PAIR: + componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); return { componentType, title: displayName, - initialValue, value: initialValue, ...restOfField, ...componentPropertiesJson, }; case INPUT_TYPES.MULTI_INPUT: - componentPropertiesJson = safeJsonParse(componentProperties, []); + componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); initialValuesJson = safeJsonParse(initialValue, []); return { componentType, title: displayName, - initialValues: initialValuesJson, value: initialValuesJson, + initialValues: initialValuesJson, ...restOfField, ...componentPropertiesJson, }; - case INPUT_TYPES.KEY_VALUE_PAIR: - return { - componentType, - title: displayName, - initialValue, - value: initialValue, - componentProperties, - ...restOfField, - }; - - case INPUT_TYPES.CHECKBOX: + case INPUT_TYPES.DROPDOWN: componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); + const options = Array.isArray(componentPropertiesJson.values) + ? componentPropertiesJson.values.map((value) => ({ + id: value, + value, + })) + : Object.entries(componentPropertiesJson.values).map(([key, value]) => ({ + id: key, + value, + })); + return { componentType, title: displayName, - initialValue, value: initialValue, + placeholder: componentPropertiesJson.placeholder || 'Select an option', + options, + onSelect: () => {}, ...restOfField, ...componentPropertiesJson, }; diff --git a/frontend/webapp/reuseable-components/input/index.tsx b/frontend/webapp/reuseable-components/input/index.tsx index 28f94ce102..04daaf1306 100644 --- a/frontend/webapp/reuseable-components/input/index.tsx +++ b/frontend/webapp/reuseable-components/input/index.tsx @@ -1,10 +1,10 @@ -import React, { useState, forwardRef, type ChangeEvent, type KeyboardEventHandler } from 'react'; +import React, { useState, forwardRef, type ChangeEvent, type KeyboardEventHandler, type InputHTMLAttributes } from 'react'; import theme from '@/styles/theme'; import styled, { css } from 'styled-components'; import { EyeClosedIcon, EyeOpenIcon, SVG } from '@/assets'; import { FieldError, FieldLabel } from '@/reuseable-components'; -interface InputProps extends React.InputHTMLAttributes { +interface InputProps extends InputHTMLAttributes { title?: string; icon?: SVG; tooltip?: string; @@ -116,10 +116,10 @@ const Button = styled.button` // Wrap Input with forwardRef to handle the ref prop const Input = forwardRef( - ({ icon: Icon, buttonLabel, onButtonClick, hasError, errorMessage, title, tooltip, required, initialValue, onChange, type = 'text', name, ...props }, ref) => { + ({ icon: Icon, buttonLabel, onButtonClick, hasError, errorMessage, title, tooltip, required, initialValue, value: v, onChange, type = 'text', name, ...props }, ref) => { const isSecret = type === 'password'; const [revealSecret, setRevealSecret] = useState(false); - const [value, setValue] = useState(initialValue || ''); + const [value, setValue] = useState(v?.toString() || initialValue || ''); const handleInputChange = (e: ChangeEvent) => { e.stopPropagation(); From 038bb48bea801c177c75161da15da995ef1776f9 Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Fri, 20 Dec 2024 16:58:43 +0200 Subject: [PATCH 7/8] refactor: update Checkbox component to use 'value' prop instead of 'initialValue' for better state management --- .../action-form-body/custom-fields/pii-masking.tsx | 2 +- .../destination-form-body/dynamic-fields/index.tsx | 2 +- .../rule-form-body/custom-fields/payload-collection.tsx | 2 +- .../choose-sources-body-fast/source-controls/index.tsx | 2 +- .../choose-sources-body-fast/sources-list/index.tsx | 4 ++-- .../choose-sources-body-simple/source-controls/index.tsx | 7 +------ .../choose-sources-body-simple/sources-list/index.tsx | 2 +- frontend/webapp/reuseable-components/checkbox/index.tsx | 8 ++++---- frontend/webapp/reuseable-components/dropdown/index.tsx | 2 +- .../reuseable-components/monitoring-checkboxes/index.tsx | 4 +--- .../nodes-data-flow/nodes/base-node.tsx | 2 +- .../nodes-data-flow/nodes/header-node.tsx | 2 +- 12 files changed, 16 insertions(+), 23 deletions(-) diff --git a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/pii-masking.tsx b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/pii-masking.tsx index 3a719af67e..e386c8941f 100644 --- a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/pii-masking.tsx +++ b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/pii-masking.tsx @@ -66,7 +66,7 @@ const PiiMasking: React.FC = ({ value, setValue, errorMessage }) => { {strictPicklist.map(({ id, label }) => ( - handleChange(id, bool)} /> + handleChange(id, bool)} /> ))} {!!errorMessage && {errorMessage}} diff --git a/frontend/webapp/containers/main/destinations/destination-form-body/dynamic-fields/index.tsx b/frontend/webapp/containers/main/destinations/destination-form-body/dynamic-fields/index.tsx index d7e73213d1..e3c3ddff50 100644 --- a/frontend/webapp/containers/main/destinations/destination-form-body/dynamic-fields/index.tsx +++ b/frontend/webapp/containers/main/destinations/destination-form-body/dynamic-fields/index.tsx @@ -24,7 +24,7 @@ export const DestinationDynamicFields: React.FC = ({ fields, onChange, fo case INPUT_TYPES.TEXTAREA: return