Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor UI bugs #2063

Merged
merged 15 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const ACTION_OPTIONS: ActionOption[] = [
},
{
id: 'latency-action',
label: 'Latency Action',
label: 'Latency Sampler',
description: 'Add latency to your traces.',
type: ActionsType.LATENCY_SAMPLER,
icon: getActionIcon('sampler'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ export function AddDestinationContainer() {
await createSources(configuredSources, configuredFutureApps);
await Promise.all(configuredDestinations.map(async ({ form }) => await createDestination(form)));

resetState();
router.push(ROUTES.OVERVIEW);
// Delay redirect by 3 seconds to allow the sources to be created on the backend 1st,
// otherwise we would have to apply polling on the overview page on every mount.
setTimeout(() => {
resetState();
router.push(ROUTES.OVERVIEW);
}, 3000);
};

const isSourcesListEmpty = () => !Object.values(configuredSources).some((sources) => !!sources.length);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
import theme from '@/styles/theme';
import styled from 'styled-components';
import { EditIcon, SVG, XIcon } from '@/assets';
import { EditIcon, SVG, TrashIcon, XIcon } from '@/assets';
import { Button, IconWrapped, Input, Text, Tooltip } from '@/reuseable-components';

const HeaderContainer = styled.section`
Expand Down Expand Up @@ -60,9 +61,11 @@ interface DrawerHeaderProps {
isEdit?: boolean;
onEdit?: () => void;
onClose: () => void;
onDelete?: () => void;
deleteLabel?: string;
}

const DrawerHeader = forwardRef<DrawerHeaderRef, DrawerHeaderProps>(({ title, titleTooltip, icon, iconSrc, isEdit, onEdit, onClose }, ref) => {
const DrawerHeader = forwardRef<DrawerHeaderRef, DrawerHeaderProps>(({ title, titleTooltip, icon, iconSrc, isEdit, onEdit, onClose, onDelete, deleteLabel = 'Delete' }, ref) => {
const [inputValue, setInputValue] = useState(title);

useEffect(() => {
Expand Down Expand Up @@ -93,14 +96,23 @@ const DrawerHeader = forwardRef<DrawerHeaderRef, DrawerHeaderProps>(({ title, ti
</InputWrapper>
)}

<SectionItemsWrapper $gap={8}>
{!isEdit && !!onEdit && (
<SectionItemsWrapper $gap={2}>
{!!onEdit && !isEdit && (
<EditButton data-id='drawer-edit' variant='tertiary' onClick={onEdit}>
<EditIcon />
<ButtonText>Edit</ButtonText>
</EditButton>
)}

{!!onDelete && !isEdit && (
<EditButton data-id='drawer-delete' variant='tertiary' onClick={onDelete}>
<TrashIcon />
<Text color={theme.text.error} size={14} family='secondary'>
{deleteLabel}
</Text>
</EditButton>
)}

<CloseButton data-id='drawer-close' variant='secondary' onClick={onClose}>
<XIcon size={12} />
</CloseButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({ children, title,
isEdit={isEdit}
onEdit={onEdit ? () => onEdit(true) : undefined}
onClose={isEdit ? clickCancel : closeDrawer}
onDelete={onEdit ? clickDelete : undefined}
deleteLabel={isSource ? 'Uninstrument' : undefined}
/>
<ContentArea>{children}</ContentArea>
<DrawerFooter isOpen={isEdit} onSave={clickSave} onCancel={clickCancel} onDelete={clickDelete} deleteLabel={isSource ? 'Uninstrument' : undefined} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const SourceDrawer: React.FC<Props> = () => {
if (!selectedItem) return [];

const { item } = selectedItem as { item: K8sActualSource };
if (!item?.instrumentedApplicationDetails?.conditions) return [];

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'),
Expand Down
21 changes: 17 additions & 4 deletions frontend/webapp/hooks/actions/useActionFormData.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DrawerItem, useNotificationStore } from '@/store';
import { FORM_ALERTS } from '@/utils';
import { useGenericForm } from '@/hooks';
import { NOTIFICATION_TYPE, type ActionDataParsed, type ActionInput } from '@/types';
import { FORM_ALERTS, safeJsonParse } from '@/utils';
import { DrawerItem, useNotificationStore } from '@/store';
import { ActionsType, LatencySamplerSpec, NOTIFICATION_TYPE, type ActionDataParsed, type ActionInput } from '@/types';

const INITIAL: ActionInput = {
// @ts-ignore (TS complains about empty string because we expect an "ActionsType", but it's fine)
Expand All @@ -25,18 +25,31 @@ export function useActionFormData() {
switch (k) {
case 'type':
case 'signals':
if (Array.isArray(v) ? !v.length : !v) {
errors[k] = FORM_ALERTS.FIELD_IS_REQUIRED;
}
break;

case 'details':
if (Array.isArray(v) ? !v.length : !v) {
ok = false;
errors[k] = FORM_ALERTS.FIELD_IS_REQUIRED;
}
if (formData.type === ActionsType.LATENCY_SAMPLER) {
(safeJsonParse(v as string, { endpoints_filters: [] }) as LatencySamplerSpec).endpoints_filters.forEach((endpoint) => {
if (endpoint.http_route.charAt(0) !== '/') {
errors[k] = FORM_ALERTS.LATENCY_HTTP_ROUTE;
}
});
}
break;

default:
break;
}
});

ok = !Object.values(errors).length;

if (!ok && params?.withAlert) {
addNotification({
type: NOTIFICATION_TYPE.WARNING,
Expand Down
2 changes: 1 addition & 1 deletion frontend/webapp/hooks/sources/useSourceCRUD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const useSourceCRUD = (params?: Params) => {
} else {
const id = { kind, name, namespace };
if (!selected) removeNotifications(getSseTargetFromId(id, OVERVIEW_ENTITY_TYPES.SOURCE));
if (!selected) setConfiguredSources({ ...configuredSources, [namespace]: configuredSources[namespace].filter((source) => source.name !== name) });
if (!selected) setConfiguredSources({ ...configuredSources, [namespace]: configuredSources[namespace]?.filter((source) => source.name !== name) || [] });
handleComplete(action, `source "${name}" was ${action.toLowerCase()}d ${fromOrIn} "${namespace}"`, selected ? id : undefined);
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const AutocompleteInput: FC<Props> = ({ placeholder = 'Type to search...'
};

const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
e.stopPropagation();
if (!['Enter'].includes(e.key)) e.stopPropagation();

// Flatten the options to handle keyboard navigation - TODO: Refactor this
return;
Expand Down
15 changes: 1 addition & 14 deletions frontend/webapp/reuseable-components/input-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,6 @@ const InputList: React.FC<InputListProps> = ({ initialValues = [], value, onChan
});
};

const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
e.stopPropagation();
};

// Check if any input field is empty
const isAddButtonDisabled = rows.some((input) => input.trim() === '');
const isDelButtonDisabled = rows.length <= 1;
Expand All @@ -118,16 +114,7 @@ const InputList: React.FC<InputListProps> = ({ initialValues = [], value, onChan
<ListContainer>
{rows.map((val, idx) => (
<RowWrapper key={`input-list-${idx}`}>
<Input
value={val}
onChange={(e) => {
e.stopPropagation();
handleInputChange(e.target.value, idx);
}}
onKeyDown={handleKeyDown}
hasError={!!errorMessage}
autoFocus={!val && rows.length > 1 && idx === rows.length - 1}
/>
<Input value={val} onChange={(e) => handleInputChange(e.target.value, idx)} hasError={!!errorMessage} autoFocus={!val && rows.length > 1 && idx === rows.length - 1} />
<DeleteButton disabled={isDelButtonDisabled} onClick={() => handleDeleteInput(idx)}>
<TrashIcon />
</DeleteButton>
Expand Down
10 changes: 1 addition & 9 deletions frontend/webapp/reuseable-components/input-table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,6 @@ export const InputTable: React.FC<Props> = ({ columns, initialValues = [], value
});
};

const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
e.stopPropagation();
};

// Check if any key or value field is empty
const isAddButtonDisabled = rows.some((row) => !!Object.values(row).filter((val) => !val).length);
const isDelButtonDisabled = rows.length <= 1;
Expand Down Expand Up @@ -143,11 +139,7 @@ export const InputTable: React.FC<Props> = ({ columns, initialValues = [], value
type={type}
placeholder={placeholder}
value={value}
onChange={({ stopPropagation, target: { value: val } }) => {
stopPropagation();
handleChange(keyName, type === 'number' ? Number(val) : val, idx);
}}
onKeyDown={handleKeyDown}
onChange={({ target: { value: val } }) => handleChange(keyName, type === 'number' ? Number(val) : val, idx)}
autoFocus={!value && rows.length > 1 && idx === rows.length - 1 && innerIdx === 0}
style={{ maxWidth, paddingLeft: 10 }}
hasError={!!errorMessage && (!required || (required && !value))}
Expand Down
105 changes: 48 additions & 57 deletions frontend/webapp/reuseable-components/input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,61 +115,52 @@ const Button = styled.button`
`;

// Wrap Input with forwardRef to handle the ref prop
const Input = forwardRef<HTMLInputElement, InputProps>(
({ 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<string>(v?.toString() || initialValue || '');

const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();
setValue(e.target.value);
onChange?.(e);
};

const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
e.stopPropagation();
};

return (
<Container>
<FieldLabel title={title} required={required} tooltip={tooltip} />

<InputWrapper $disabled={props.disabled} $hasError={hasError || !!errorMessage} $isActive={!!props.autoFocus}>
{isSecret ? (
<IconWrapperClickable onClick={() => setRevealSecret((prev) => !prev)}>
{revealSecret ? <EyeClosedIcon size={14} fill={theme.text.grey} /> : <EyeOpenIcon size={14} fill={theme.text.grey} />}
</IconWrapperClickable>
) : Icon ? (
<IconWrapper>
<Icon size={14} fill={theme.text.grey} />
</IconWrapper>
) : null}

<StyledInput
ref={ref}
data-id={name}
type={revealSecret ? 'text' : type}
$hasIcon={!!Icon || isSecret}
name={name}
value={value}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
{...props}
/>

{buttonLabel && onButtonClick && (
<Button onClick={onButtonClick} disabled={props.disabled}>
{buttonLabel}
</Button>
)}
</InputWrapper>

{!!errorMessage && <FieldError>{errorMessage}</FieldError>}
</Container>
);
},
);

Input.displayName = 'Input'; // Set a display name for easier debugging
const Input = forwardRef<HTMLInputElement, InputProps>(({ icon: Icon, buttonLabel, onButtonClick, hasError, errorMessage, title, tooltip, required, onChange, type = 'text', name, ...props }, ref) => {
const isSecret = type === 'password';
const [revealSecret, setRevealSecret] = useState(false);

const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();

const v = e.target.value;
const actualValue = type === 'number' ? v.replace(/[^\d]/g, '') : v;
e.target.value = actualValue;

onChange?.(e);
};

const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
if (!['Enter'].includes(e.key)) e.stopPropagation();
};

return (
<Container>
<FieldLabel title={title} required={required} tooltip={tooltip} />

<InputWrapper $disabled={props.disabled} $hasError={hasError || !!errorMessage} $isActive={!!props.autoFocus}>
{isSecret ? (
<IconWrapperClickable onClick={() => setRevealSecret((prev) => !prev)}>
{revealSecret ? <EyeClosedIcon size={14} fill={theme.text.grey} /> : <EyeOpenIcon size={14} fill={theme.text.grey} />}
</IconWrapperClickable>
) : Icon ? (
<IconWrapper>
<Icon size={14} fill={theme.text.grey} />
</IconWrapper>
) : null}

<StyledInput ref={ref} data-id={name} type={revealSecret ? 'text' : type} $hasIcon={!!Icon || isSecret} name={name} onChange={handleInputChange} onKeyDown={handleKeyDown} {...props} />

{buttonLabel && onButtonClick && (
<Button onClick={onButtonClick} disabled={props.disabled}>
{buttonLabel}
</Button>
)}
</InputWrapper>

{!!errorMessage && <FieldError>{errorMessage}</FieldError>}
</Container>
);
});

Input.displayName = 'Input';
export { Input };
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,6 @@ export const KeyValueInputsList: React.FC<KeyValueInputsListProps> = ({ initialK
});
};

const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
e.stopPropagation();
};

// Check if any key or value field is empty
const isAddButtonDisabled = rows.some(({ key, value }) => key.trim() === '' || value.trim() === '');
const isDelButtonDisabled = rows.length <= 1;
Expand All @@ -129,11 +125,7 @@ export const KeyValueInputsList: React.FC<KeyValueInputsListProps> = ({ initialK
<Input
placeholder='Attribute name'
value={key}
onChange={(e) => {
e.stopPropagation();
handleChange('key', e.target.value, idx);
}}
onKeyDown={handleKeyDown}
onChange={(e) => handleChange('key', e.target.value, idx)}
hasError={!!errorMessage && (!required || (required && !key))}
autoFocus={!value && rows.length > 1 && idx === rows.length - 1}
/>
Expand All @@ -143,11 +135,7 @@ export const KeyValueInputsList: React.FC<KeyValueInputsListProps> = ({ initialK
<Input
placeholder='Attribute value'
value={value}
onChange={(e) => {
e.stopPropagation();
handleChange('value', e.target.value, idx);
}}
onKeyDown={handleKeyDown}
onChange={(e) => handleChange('value', e.target.value, idx)}
hasError={!!errorMessage && (!required || (required && !value))}
autoFocus={false}
/>
Expand Down
Loading
Loading