From 1205f016933b396501da593d6c5a19ea3b36fb83 Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Thu, 28 Nov 2024 11:43:07 +0200 Subject: [PATCH] UI bugs party (#1873) This pull request includes several updates to the frontend web application, focusing on adding new components, refactoring existing ones, and updating styles. The most important changes include the addition of new dropdown components, updates to modals and notifications, and various refactoring tasks to improve code readability and maintainability. ### New Components and Features: * Added `LanguageDropdown` component to the dropdowns directory. (`frontend/webapp/components/common/dropdowns/language-dropdown/index.tsx`) * Exported the new `LanguageDropdown` from the dropdowns index. (`frontend/webapp/components/common/dropdowns/index.ts`) ### Refactoring and Code Simplification: * Renamed and refactored `build-card-from-action-spec.ts` to `build-card.ts` and updated its usage. (`frontend/webapp/containers/main/actions/action-drawer/build-card.ts`, `frontend/webapp/containers/main/actions/action-drawer/index.tsx`) [[1]](diffhunk://#diff-dc7aa9e456bfd94740ecdfc9843bd6440126ab0d2c3cea06ccb0c825796d0ba7L3-R3) [[2]](diffhunk://#diff-5f56695cd2d0ca6bcd28f372653c71d8c4dab572b08715c1f36b7acc5cf50f60R2-R50) * Moved `AllDrawers` and `AllModals` components from containers to components directory and updated imports accordingly. (`frontend/webapp/components/overview/all-drawers/index.tsx`, `frontend/webapp/components/overview/all-modals/index.tsx`) [[1]](diffhunk://#diff-bf25245ffa5cb1c7ea54b941a97fc9f53caf28b7154cb4eda9b88c7b6f0944d1L3-R4) [[2]](diffhunk://#diff-e669f68767681c3105913e5f1a53f358e0f77a9f8c866cef2dbe5c507188380bL3-R4) ### Style Updates: * Updated background color for `HeaderContainer` in both main and setup headers to use `darker_grey`. (`frontend/webapp/components/main/header/index.tsx`, `frontend/webapp/components/setup/header/index.tsx`) [[1]](diffhunk://#diff-2c96f91ec30d2116981a9c0a562820ff9fd87c8292cb5dca11a45d6fb2ac6c04L19-R19) [[2]](diffhunk://#diff-b797fa218a1303de084fa2eed814d4512fb9cb215a914c0adfaee658d7558db9L21-R21) ### Modals and Notifications: * Updated `CancelWarning` and `DeleteWarning` modal button texts to be more descriptive. (`frontend/webapp/components/modals/cancel-warning/index.tsx`, `frontend/webapp/components/modals/delete-warning/index.tsx`) [[1]](diffhunk://#diff-c197a79280b54ef188b35a5e804a91ff95db0b8137b0f2c7f8dacd545a4f4650L18-R20) [[2]](diffhunk://#diff-4f8fac3da993d379d6710a6607b6f25708649cb2da5b4a5f433f8e62dde1d03fL27-R32) * Refactored `ToastList` component and updated its export method. (`frontend/webapp/components/notification/toast-list.tsx`, `frontend/webapp/components/notification/index.ts`) [[1]](diffhunk://#diff-c6ead7587d1e5c52295c921dde2a1a5b9b06977ab3900c7dbf475c47cc1c664cL20-R20) [[2]](diffhunk://#diff-b0bf2162cde911ad526b7496ce073fa39b64f7f7e030241c3a59610d7dea2081R1-R4) ### Utility and Hook Updates: * Added `BACKEND_BOOLEAN` utility to the `ErrorDropdown` component for better readability and consistency. (`frontend/webapp/components/common/dropdowns/error-dropdown/index.tsx`) [[1]](diffhunk://#diff-587e573502656d446fed8f02b5190cd43c67a9261dcb74cd077f37cbe8b93839R4) [[2]](diffhunk://#diff-587e573502656d446fed8f02b5190cd43c67a9261dcb74cd077f37cbe8b93839L24-R25) These changes collectively improve the structure, readability, and functionality of the frontend web application. --------- Co-authored-by: Alon Braymok <138359965+alonkeyval@users.noreply.github.com> --- .../webapp/app/(overview)/overview/page.tsx | 10 +- .../common/dropdowns/error-dropdown/index.tsx | 3 +- .../components/common/dropdowns/index.ts | 1 + .../dropdowns/language-dropdown/index.tsx | 34 +++++ .../webapp/components/main/header/index.tsx | 2 +- .../modals/cancel-warning/index.tsx | 4 +- .../modals/delete-warning/index.tsx | 4 +- .../webapp/components/notification/index.ts | 4 + .../webapp/components/notification/index.tsx | 2 - .../components/notification/toast-list.tsx | 4 +- .../overview/all-drawers/index.tsx | 5 +- .../overview/all-modals/index.tsx | 5 +- frontend/webapp/components/overview/index.ts | 6 + frontend/webapp/components/overview/index.tsx | 2 - .../webapp/components/setup/header/index.tsx | 2 +- ...card-from-action-spec.ts => build-card.ts} | 10 +- .../action-drawer/build-drawer-item.ts | 21 +++ .../main/actions/action-drawer/index.tsx | 78 +++++----- .../custom-fields/add-cluster-info.tsx | 5 +- .../custom-fields/delete-attributes.tsx | 5 +- .../custom-fields/error-sampler.tsx | 15 +- .../action-form-body/custom-fields/index.tsx | 16 +- .../custom-fields/latency-sampler.tsx | 10 +- .../custom-fields/pii-masking.tsx | 21 ++- .../custom-fields/probabilistic-sampler.tsx | 15 +- .../custom-fields/rename-attributes.tsx | 12 +- .../main/actions/action-form-body/index.tsx | 28 +++- .../main/actions/action-modal/index.tsx | 24 +-- .../configured-destinations-list/index.tsx | 8 +- .../destination-drawer/build-card.ts | 32 ++++ .../destination-drawer/build-drawer-item.ts | 21 +++ .../destinations/destination-drawer/index.tsx | 102 +++++++------ .../dynamic-fields/index.tsx | 13 +- .../destination-form-body/index.tsx | 59 +++++--- .../choose-destination-filters/index.tsx | 52 ------- .../choose-destination-body/index.tsx | 74 +++++---- .../destinations/destination-modal/index.tsx | 30 ++-- ...d-card-from-rule-spec.ts => build-card.ts} | 16 +- .../rule-drawer/build-drawer-item.ts | 25 ++++ .../rule-drawer/index.tsx | 58 ++++--- .../rule-form-body/custom-fields/index.tsx | 16 +- .../custom-fields/payload-collection.tsx | 23 ++- .../rule-form-body/index.tsx | 17 ++- .../rule-modal/index.tsx | 26 ++-- .../overview/multi-source-control/index.tsx | 14 +- .../overview-actions-menu/filters/index.tsx | 20 ++- .../search/search-results/index.tsx | 7 +- .../overview/overview-data-flow/index.tsx | 49 ++---- .../sources-list/index.tsx | 4 +- .../webapp/containers/main/sources/index.ts | 3 +- .../source-drawer-container/build-card.ts | 18 +++ .../build-drawer-item.ts | 19 +++ .../sources/source-drawer-container/index.tsx | 141 ++++++++---------- .../webapp/hooks/actions/useActionCRUD.ts | 23 +-- .../webapp/hooks/actions/useActionFormData.ts | 17 ++- .../compute-platform/useComputePlatform.ts | 63 +++++--- .../hooks/destinations/useDestinationCRUD.ts | 12 +- .../destinations/useDestinationFormData.ts | 17 ++- .../useInstrumentationRuleCRUD.ts | 19 +-- .../useInstrumentationRuleFormData.ts | 53 +++---- .../hooks/overview/useNodeDataFlowHandlers.ts | 47 ++++-- .../webapp/hooks/sources/useSourceCRUD.ts | 12 +- .../reuseable-components/button/index.tsx | 49 +++--- .../checkbox-list/index.tsx | 62 -------- .../condition-details/index.tsx | 80 ++++++++++ .../reuseable-components/dropdown/index.tsx | 39 ++--- .../extend-icon/index.tsx | 24 +++ .../field-error/index.tsx | 20 +++ .../field-label/index.tsx | 17 ++- frontend/webapp/reuseable-components/index.ts | 4 +- .../reuseable-components/input-list/index.tsx | 41 ++--- .../input-table/index.tsx | 51 ++++--- .../reuseable-components/input/index.tsx | 129 +++++++--------- .../key-value-input-list/index.tsx | 63 +++++--- .../monitoring-checkboxes/index.tsx | 42 ++++-- .../nodes-data-flow/builder.ts | 81 ++++------ .../nodes-data-flow/index.tsx | 16 +- .../skeleton-loader/index.tsx | 4 +- .../reuseable-components/textarea/index.tsx | 24 +-- frontend/webapp/store/useFilterStore.ts | 20 ++- frontend/webapp/styles/styled.tsx | 4 +- frontend/webapp/styles/theme.ts | 6 +- frontend/webapp/types/actions.ts | 2 +- frontend/webapp/types/compute-platform.ts | 21 ++- frontend/webapp/types/destinations.ts | 3 + .../webapp/types/instrumentation-rules.ts | 6 +- frontend/webapp/utils/constants/string.tsx | 8 +- .../webapp/utils/functions/derive-types.ts | 4 +- .../utils/functions/get-health-status.ts | 3 +- 89 files changed, 1321 insertions(+), 960 deletions(-) create mode 100644 frontend/webapp/components/common/dropdowns/language-dropdown/index.tsx create mode 100644 frontend/webapp/components/notification/index.ts delete mode 100644 frontend/webapp/components/notification/index.tsx rename frontend/webapp/{containers/main => components}/overview/all-drawers/index.tsx (75%) rename frontend/webapp/{containers/main => components}/overview/all-modals/index.tsx (78%) create mode 100644 frontend/webapp/components/overview/index.ts delete mode 100644 frontend/webapp/components/overview/index.tsx rename frontend/webapp/containers/main/actions/action-drawer/{build-card-from-action-spec.ts => build-card.ts} (93%) create mode 100644 frontend/webapp/containers/main/actions/action-drawer/build-drawer-item.ts create mode 100644 frontend/webapp/containers/main/destinations/destination-drawer/build-card.ts create mode 100644 frontend/webapp/containers/main/destinations/destination-drawer/build-drawer-item.ts delete mode 100644 frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/choose-destination-filters/index.tsx rename frontend/webapp/containers/main/instrumentation-rules/rule-drawer/{build-card-from-rule-spec.ts => build-card.ts} (54%) create mode 100644 frontend/webapp/containers/main/instrumentation-rules/rule-drawer/build-drawer-item.ts create mode 100644 frontend/webapp/containers/main/sources/source-drawer-container/build-card.ts create mode 100644 frontend/webapp/containers/main/sources/source-drawer-container/build-drawer-item.ts delete mode 100644 frontend/webapp/reuseable-components/checkbox-list/index.tsx create mode 100644 frontend/webapp/reuseable-components/condition-details/index.tsx create mode 100644 frontend/webapp/reuseable-components/extend-icon/index.tsx create mode 100644 frontend/webapp/reuseable-components/field-error/index.tsx diff --git a/frontend/webapp/app/(overview)/overview/page.tsx b/frontend/webapp/app/(overview)/overview/page.tsx index 678d41714..c24075f34 100644 --- a/frontend/webapp/app/(overview)/overview/page.tsx +++ b/frontend/webapp/app/(overview)/overview/page.tsx @@ -2,13 +2,17 @@ import React from 'react'; import dynamic from 'next/dynamic'; -const OverviewDataFlowContainer = dynamic(() => import('@/containers/main/overview/overview-data-flow'), { - ssr: false, -}); +const ToastList = dynamic(() => import('@/components/notification/toast-list'), { ssr: false }); +const AllDrawers = dynamic(() => import('@/components/overview/all-drawers'), { ssr: false }); +const AllModals = dynamic(() => import('@/components/overview/all-modals'), { ssr: false }); +const OverviewDataFlowContainer = dynamic(() => import('@/containers/main/overview/overview-data-flow'), { ssr: false }); export default function MainPage() { return ( <> + + + ); diff --git a/frontend/webapp/components/common/dropdowns/error-dropdown/index.tsx b/frontend/webapp/components/common/dropdowns/error-dropdown/index.tsx index 30410af3a..571fc3bcd 100644 --- a/frontend/webapp/components/common/dropdowns/error-dropdown/index.tsx +++ b/frontend/webapp/components/common/dropdowns/error-dropdown/index.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; import { useSourceCRUD } from '@/hooks'; import { DropdownOption } from '@/types'; +import { BACKEND_BOOLEAN } from '@/utils'; import { Dropdown } from '@/reuseable-components'; interface Props { @@ -21,7 +22,7 @@ export const ErrorDropdown: React.FC = ({ title = 'Error Message', value, sources.forEach(({ instrumentedApplicationDetails: { conditions } }) => { conditions.forEach(({ type, status, message }) => { - if (status === 'False' && !payload.find((opt) => opt.value === message)) { + if (status === BACKEND_BOOLEAN.FALSE && !payload.find((opt) => opt.id === message)) { payload.push({ id: message, value: message }); } }); diff --git a/frontend/webapp/components/common/dropdowns/index.ts b/frontend/webapp/components/common/dropdowns/index.ts index 096f148b8..13265f2a1 100644 --- a/frontend/webapp/components/common/dropdowns/index.ts +++ b/frontend/webapp/components/common/dropdowns/index.ts @@ -1,4 +1,5 @@ export * from './error-dropdown'; +export * from './language-dropdown'; export * from './monitor-dropdown'; export * from './namespace-dropdown'; export * from './type-dropdown'; diff --git a/frontend/webapp/components/common/dropdowns/language-dropdown/index.tsx b/frontend/webapp/components/common/dropdowns/language-dropdown/index.tsx new file mode 100644 index 000000000..31c53ac9a --- /dev/null +++ b/frontend/webapp/components/common/dropdowns/language-dropdown/index.tsx @@ -0,0 +1,34 @@ +import React, { useMemo } from 'react'; +import { useSourceCRUD } from '@/hooks'; +import { DropdownOption } from '@/types'; +import { Dropdown } from '@/reuseable-components'; + +interface Props { + title?: string; + value?: DropdownOption[]; + onSelect: (val: DropdownOption) => void; + onDeselect: (val: DropdownOption) => void; + isMulti?: boolean; + required?: boolean; + showSearch?: boolean; +} + +export const LanguageDropdown: React.FC = ({ title = 'Programming Languages', value, onSelect, onDeselect, ...props }) => { + const { sources } = useSourceCRUD(); + + const options = useMemo(() => { + const payload: DropdownOption[] = []; + + sources.forEach(({ instrumentedApplicationDetails: { containers } }) => { + containers.forEach(({ language }) => { + if (!payload.find((opt) => opt.id === language)) { + payload.push({ id: language, value: language }); + } + }); + }); + + return payload.sort((a, b) => a.id.localeCompare(b.id)); + }, [sources]); + + return ; +}; diff --git a/frontend/webapp/components/main/header/index.tsx b/frontend/webapp/components/main/header/index.tsx index 345ca679b..f74f9aeb4 100644 --- a/frontend/webapp/components/main/header/index.tsx +++ b/frontend/webapp/components/main/header/index.tsx @@ -16,7 +16,7 @@ const Flex = styled.div` const HeaderContainer = styled(Flex)` width: 100%; padding: 12px 0; - background-color: ${({ theme }) => theme.colors.dark_grey}; + background-color: ${({ theme }) => theme.colors.darker_grey}; border-bottom: 1px solid rgba(249, 249, 249, 0.16); `; diff --git a/frontend/webapp/components/modals/cancel-warning/index.tsx b/frontend/webapp/components/modals/cancel-warning/index.tsx index a59ea05e1..82221bc9f 100644 --- a/frontend/webapp/components/modals/cancel-warning/index.tsx +++ b/frontend/webapp/components/modals/cancel-warning/index.tsx @@ -15,9 +15,9 @@ const CancelWarning: React.FC = ({ isOpen, noOverlay, name, onApprove, on isOpen={isOpen} noOverlay={noOverlay} title={`Cancel${name ? ` ${name}` : ''}`} - description='Are you sure you want to discard your changes?' + description='Are you sure you want to cancel?' approveButton={{ - text: 'Cancel', + text: 'Confirm', variant: 'warning', onClick: onApprove, }} diff --git a/frontend/webapp/components/modals/delete-warning/index.tsx b/frontend/webapp/components/modals/delete-warning/index.tsx index a95c1bb0b..efb1db297 100644 --- a/frontend/webapp/components/modals/delete-warning/index.tsx +++ b/frontend/webapp/components/modals/delete-warning/index.tsx @@ -24,12 +24,12 @@ const DeleteWarning: React.FC = ({ isOpen, noOverlay, name, note, onAppro description='Are you sure you want to delete?' note={note} approveButton={{ - text: 'Delete', + text: 'Confirm', variant: 'danger', onClick: onApprove, }} denyButton={{ - text: 'Cancel', + text: 'Go Back', onClick: onDeny, }} /> diff --git a/frontend/webapp/components/notification/index.ts b/frontend/webapp/components/notification/index.ts new file mode 100644 index 000000000..f3993ea5b --- /dev/null +++ b/frontend/webapp/components/notification/index.ts @@ -0,0 +1,4 @@ +export * from './notification-manager'; +import ToastList from './toast-list'; + +export { ToastList }; diff --git a/frontend/webapp/components/notification/index.tsx b/frontend/webapp/components/notification/index.tsx deleted file mode 100644 index fe2628507..000000000 --- a/frontend/webapp/components/notification/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from './notification-manager'; -export * from './toast-list'; diff --git a/frontend/webapp/components/notification/toast-list.tsx b/frontend/webapp/components/notification/toast-list.tsx index ee3bedf99..e7b5f0540 100644 --- a/frontend/webapp/components/notification/toast-list.tsx +++ b/frontend/webapp/components/notification/toast-list.tsx @@ -17,7 +17,7 @@ const Container = styled.div` min-width: 600px; `; -export const ToastList: React.FC = () => { +const ToastList: React.FC = () => { const { notifications } = useNotificationStore(); return ( @@ -59,3 +59,5 @@ const Toast: React.FC = (props) => { /> ); }; + +export default ToastList; diff --git a/frontend/webapp/containers/main/overview/all-drawers/index.tsx b/frontend/webapp/components/overview/all-drawers/index.tsx similarity index 75% rename from frontend/webapp/containers/main/overview/all-drawers/index.tsx rename to frontend/webapp/components/overview/all-drawers/index.tsx index 65b2bba52..65cbb3b59 100644 --- a/frontend/webapp/containers/main/overview/all-drawers/index.tsx +++ b/frontend/webapp/components/overview/all-drawers/index.tsx @@ -1,10 +1,7 @@ import React from 'react'; import { useDrawerStore } from '@/store'; -import { SourceDrawer } from '../../sources'; -import { ActionDrawer } from '../../actions'; import { OVERVIEW_ENTITY_TYPES } from '@/types'; -import { DestinationDrawer } from '../../destinations'; -import { RuleDrawer } from '../../instrumentation-rules'; +import { ActionDrawer, DestinationDrawer, RuleDrawer, SourceDrawer } from '@/containers'; const AllDrawers = () => { const selected = useDrawerStore(({ selectedItem }) => selectedItem); diff --git a/frontend/webapp/containers/main/overview/all-modals/index.tsx b/frontend/webapp/components/overview/all-modals/index.tsx similarity index 78% rename from frontend/webapp/containers/main/overview/all-modals/index.tsx rename to frontend/webapp/components/overview/all-modals/index.tsx index 96039338a..50793a9c9 100644 --- a/frontend/webapp/containers/main/overview/all-modals/index.tsx +++ b/frontend/webapp/components/overview/all-modals/index.tsx @@ -1,10 +1,7 @@ import React from 'react'; import { useModalStore } from '@/store'; -import { ActionModal } from '../../actions'; import { OVERVIEW_ENTITY_TYPES } from '@/types'; -import { DestinationModal } from '../../destinations'; -import { RuleModal } from '../../instrumentation-rules'; -import { AddSourceModal } from '../../sources/choose-sources/choose-source-modal'; +import { ActionModal, AddSourceModal, DestinationModal, RuleModal } from '@/containers'; const AllModals = () => { const selected = useModalStore(({ currentModal }) => currentModal); diff --git a/frontend/webapp/components/overview/index.ts b/frontend/webapp/components/overview/index.ts new file mode 100644 index 000000000..ac94bf260 --- /dev/null +++ b/frontend/webapp/components/overview/index.ts @@ -0,0 +1,6 @@ +export * from './add-entity'; +import AllDrawers from './all-drawers'; +import AllModals from './all-modals'; +export * from './monitors-legend'; + +export { AllDrawers, AllModals }; diff --git a/frontend/webapp/components/overview/index.tsx b/frontend/webapp/components/overview/index.tsx deleted file mode 100644 index a84120e29..000000000 --- a/frontend/webapp/components/overview/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from './add-entity'; -export * from './monitors-legend'; diff --git a/frontend/webapp/components/setup/header/index.tsx b/frontend/webapp/components/setup/header/index.tsx index 0c22d70ba..eaf1b3bc4 100644 --- a/frontend/webapp/components/setup/header/index.tsx +++ b/frontend/webapp/components/setup/header/index.tsx @@ -18,7 +18,7 @@ const HeaderContainer = styled.div` justify-content: space-between; padding: 0 24px 0 32px; align-items: center; - background-color: ${({ theme }) => theme.colors.dark_grey}; + background-color: ${({ theme }) => theme.colors.darker_grey}; border-bottom: 1px solid rgba(249, 249, 249, 0.16); height: 80px; `; diff --git a/frontend/webapp/containers/main/actions/action-drawer/build-card-from-action-spec.ts b/frontend/webapp/containers/main/actions/action-drawer/build-card.ts similarity index 93% rename from frontend/webapp/containers/main/actions/action-drawer/build-card-from-action-spec.ts rename to frontend/webapp/containers/main/actions/action-drawer/build-card.ts index 7b872ebf2..50953951c 100644 --- a/frontend/webapp/containers/main/actions/action-drawer/build-card-from-action-spec.ts +++ b/frontend/webapp/containers/main/actions/action-drawer/build-card.ts @@ -1,6 +1,6 @@ import type { ActionDataParsed } from '@/types'; -const buildCardFromActionSpec = (action: ActionDataParsed) => { +const buildCard = (action: ActionDataParsed) => { const { type, spec: { @@ -17,14 +17,14 @@ const buildCardFromActionSpec = (action: ActionDataParsed) => { sampling_percentage, endpoints_filters, }, - } = action as ActionDataParsed; + } = action; const arr = [ - { title: 'Type', value: type || 'N/A' }, + { title: 'Type', value: type }, { title: 'Status', value: String(!disabled) }, + { title: 'Monitors', value: signals.map((str) => str.toLowerCase()).join(', ') }, { title: 'Name', value: actionName || 'N/A' }, { title: 'Notes', value: notes || 'N/A' }, - { title: 'Monitors', value: signals.map((str) => str.toLowerCase()).join(', ') }, ]; if (clusterAttributes) { @@ -91,4 +91,4 @@ const buildCardFromActionSpec = (action: ActionDataParsed) => { return arr; }; -export default buildCardFromActionSpec; +export default buildCard; diff --git a/frontend/webapp/containers/main/actions/action-drawer/build-drawer-item.ts b/frontend/webapp/containers/main/actions/action-drawer/build-drawer-item.ts new file mode 100644 index 000000000..d3c03810b --- /dev/null +++ b/frontend/webapp/containers/main/actions/action-drawer/build-drawer-item.ts @@ -0,0 +1,21 @@ +import { safeJsonParse } from '@/utils'; +import type { ActionDataParsed, ActionInput } from '@/types'; + +const buildDrawerItem = (id: string, formData: ActionInput, drawerItem: ActionDataParsed): ActionDataParsed => { + const { type, name, notes, signals, disable, details } = formData; + const {} = drawerItem; + + return { + id, + type, + spec: { + actionName: name, + notes: notes, + signals: signals, + disabled: disable, + ...safeJsonParse(details, {}), + }, + }; +}; + +export default buildDrawerItem; diff --git a/frontend/webapp/containers/main/actions/action-drawer/index.tsx b/frontend/webapp/containers/main/actions/action-drawer/index.tsx index e64aefc18..9204193a2 100644 --- a/frontend/webapp/containers/main/actions/action-drawer/index.tsx +++ b/frontend/webapp/containers/main/actions/action-drawer/index.tsx @@ -1,30 +1,53 @@ import React, { useMemo, useState } from 'react'; +import buildCard from './build-card'; import { ActionFormBody } from '../'; import styled from 'styled-components'; -import { getActionIcon } from '@/utils'; import { useDrawerStore } from '@/store'; import { CardDetails } from '@/components'; -import type { ActionDataParsed } from '@/types'; +import { ACTION, getActionIcon } from '@/utils'; +import buildDrawerItem from './build-drawer-item'; import { useActionCRUD, useActionFormData } from '@/hooks'; import OverviewDrawer from '../../overview/overview-drawer'; import { ACTION_OPTIONS } from '../action-modal/action-options'; -import buildCardFromActionSpec from './build-card-from-action-spec'; +import { OVERVIEW_ENTITY_TYPES, type ActionDataParsed } from '@/types'; interface Props {} -const ActionDrawer: React.FC = () => { - const selectedItem = useDrawerStore(({ selectedItem }) => selectedItem); +const FormContainer = styled.div` + width: 100%; + height: 100%; + max-height: calc(100vh - 220px); + overflow: overlay; + overflow-y: auto; +`; + +export const ActionDrawer: React.FC = () => { + const { selectedItem, setSelectedItem } = useDrawerStore(); + const { formData, formErrors, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem } = useActionFormData(); + + const { updateAction, deleteAction } = useActionCRUD({ + onSuccess: (type) => { + setIsEditing(false); + setIsFormDirty(false); + + if (type === ACTION.DELETE) { + setSelectedItem(null); + } else { + const { item } = selectedItem as { item: ActionDataParsed }; + const { id } = item; + setSelectedItem({ id, type: OVERVIEW_ENTITY_TYPES.ACTION, item: buildDrawerItem(id, formData, item) }); + } + }, + }); + const [isEditing, setIsEditing] = useState(false); const [isFormDirty, setIsFormDirty] = useState(false); - const { formData, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem } = useActionFormData(); - const { updateAction, deleteAction } = useActionCRUD(); - const cardData = useMemo(() => { if (!selectedItem) return []; const { item } = selectedItem as { item: ActionDataParsed }; - const arr = buildCardFromActionSpec(item); + const arr = buildCard(item); return arr; }, [selectedItem]); @@ -49,37 +72,33 @@ const ActionDrawer: React.FC = () => { }, [selectedItem, isEditing]); if (!selectedItem?.item) return null; - const { id, item } = selectedItem; + const { id, item } = selectedItem as { id: string; item: ActionDataParsed }; const handleEdit = (bool?: boolean) => { - if (typeof bool === 'boolean') { - setIsEditing(bool); - } else { - setIsEditing(true); - } + setIsEditing(typeof bool === 'boolean' ? bool : true); }; const handleCancel = () => { - resetFormData(); setIsEditing(false); + setIsFormDirty(false); }; const handleDelete = async () => { - await deleteAction(id as string, (item as ActionDataParsed).type); + await deleteAction(id, item.type); }; const handleSave = async (newTitle: string) => { - if (validateForm({ withAlert: true })) { - const title = newTitle !== (item as ActionDataParsed).type ? newTitle : ''; - - await updateAction(id as string, { ...formData, name: title }); + if (validateForm({ withAlert: true, alertTitle: ACTION.UPDATE })) { + const title = newTitle !== item.type ? newTitle : ''; + handleFormChange('name', title); + await updateAction(id, { ...formData, name: title }); } }; return ( = () => { isUpdate action={thisAction} formData={formData} + formErrors={formErrors} handleFormChange={(...params) => { setIsFormDirty(true); handleFormChange(...params); @@ -100,18 +120,8 @@ const ActionDrawer: React.FC = () => { /> ) : ( - + )} ); }; - -export { ActionDrawer }; - -const FormContainer = styled.div` - width: 100%; - height: 100%; - max-height: calc(100vh - 220px); - overflow: overlay; - overflow-y: auto; -`; diff --git a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/add-cluster-info.tsx b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/add-cluster-info.tsx index 43206bc02..9249aebba 100644 --- a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/add-cluster-info.tsx +++ b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/add-cluster-info.tsx @@ -6,11 +6,12 @@ import { KeyValueInputsList } from '@/reuseable-components'; type Props = { value: string; setValue: (value: string) => void; + errorMessage?: string; }; type Parsed = AddClusterInfoSpec; -const AddClusterInfo: React.FC = ({ value, setValue }) => { +const AddClusterInfo: React.FC = ({ value, setValue, errorMessage }) => { const mappedValue = useMemo( () => safeJsonParse(value, { clusterAttributes: [] }).clusterAttributes.map((obj) => ({ @@ -33,7 +34,7 @@ const AddClusterInfo: React.FC = ({ value, setValue }) => { setValue(str); }; - return ; + return ; }; export default AddClusterInfo; diff --git a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/delete-attributes.tsx b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/delete-attributes.tsx index cc99960ec..9b98161c2 100644 --- a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/delete-attributes.tsx +++ b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/delete-attributes.tsx @@ -6,11 +6,12 @@ import type { DeleteAttributesSpec } from '@/types'; type Props = { value: string; setValue: (value: string) => void; + errorMessage?: string; }; type Parsed = DeleteAttributesSpec; -const DeleteAttributes: React.FC = ({ value, setValue }) => { +const DeleteAttributes: React.FC = ({ value, setValue, errorMessage }) => { const mappedValue = useMemo(() => safeJsonParse(value, { attributeNamesToDelete: [] }).attributeNamesToDelete, [value]); const handleChange = (arr: string[]) => { @@ -23,7 +24,7 @@ const DeleteAttributes: React.FC = ({ value, setValue }) => { setValue(str); }; - return ; + return ; }; export default DeleteAttributes; diff --git a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/error-sampler.tsx b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/error-sampler.tsx index e089dcf90..bc78c4a7b 100644 --- a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/error-sampler.tsx +++ b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/error-sampler.tsx @@ -6,6 +6,7 @@ import type { ErrorSamplerSpec } from '@/types'; type Props = { value: string; setValue: (value: string) => void; + errorMessage?: string; }; type Parsed = ErrorSamplerSpec; @@ -13,7 +14,7 @@ type Parsed = ErrorSamplerSpec; const MIN = 0, MAX = 100; -const ErrorSampler: React.FC = ({ value, setValue }) => { +const ErrorSampler: React.FC = ({ value, setValue, errorMessage }) => { const mappedValue = useMemo(() => safeJsonParse(value, { fallback_sampling_ratio: 0 }).fallback_sampling_ratio, [value]); const handleChange = (val: string) => { @@ -28,17 +29,7 @@ const ErrorSampler: React.FC = ({ value, setValue }) => { setValue(str); }; - return ( - handleChange(v)} - /> - ); + return handleChange(v)} errorMessage={errorMessage} />; }; export default ErrorSampler; diff --git a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/index.tsx b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/index.tsx index d0e0f4c93..e9486614a 100644 --- a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/index.tsx +++ b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/index.tsx @@ -8,16 +8,18 @@ import DeleteAttributes from './delete-attributes'; import RenameAttributes from './rename-attributes'; import ProbabilisticSampler from './probabilistic-sampler'; -interface ActionCustomFieldsProps { +interface Props { actionType?: ActionsType; value: string; setValue: (value: string) => void; + errorMessage?: string; } -type ComponentProps = { - value: string; - setValue: (value: string) => void; -}; +interface ComponentProps { + value: Props['value']; + setValue: Props['setValue']; + errorMessage?: Props['errorMessage']; +} type ComponentType = React.FC | null; @@ -31,12 +33,12 @@ const componentsMap: Record = { [ActionsType.LATENCY_SAMPLER]: LatencySampler, }; -const ActionCustomFields: React.FC = ({ actionType, value, setValue }) => { +const ActionCustomFields: React.FC = ({ actionType, value, setValue, errorMessage }) => { if (!actionType) return null; const Component = componentsMap[actionType]; - return Component ? : null; + return Component ? : null; }; export default ActionCustomFields; diff --git a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/latency-sampler.tsx b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/latency-sampler.tsx index 809c2919e..d833071f9 100644 --- a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/latency-sampler.tsx +++ b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/latency-sampler.tsx @@ -6,11 +6,12 @@ import type { LatencySamplerSpec } from '@/types'; type Props = { value: string; setValue: (value: string) => void; + errorMessage?: string; }; type Parsed = LatencySamplerSpec; -const LatencySampler: React.FC = ({ value, setValue }) => { +const LatencySampler: React.FC = ({ value, setValue, errorMessage }) => { const mappedValue = useMemo(() => safeJsonParse(value, { endpoints_filters: [] }).endpoints_filters, [value]); const handleChange = (arr: Parsed['endpoints_filters']) => { @@ -31,8 +32,7 @@ const LatencySampler: React.FC = ({ value, setValue }) => { keyName: 'service_name', placeholder: 'Choose service', required: true, - tooltip: - 'Service name: The rule applies to a specific service name. Only traces originating from this service’s root span will be considered.', + tooltip: 'Service name: The rule applies to a specific service name. Only traces originating from this service’s root span will be considered.', }, { title: 'HTTP route', @@ -48,8 +48,7 @@ const LatencySampler: React.FC = ({ value, setValue }) => { placeholder: 'e.g. 1000', required: true, type: 'number', - tooltip: - 'Minimum latency threshold (ms): Specifies the minimum latency in milliseconds; traces with latency below this threshold are ignored.', + tooltip: 'Minimum latency threshold (ms): Specifies the minimum latency in milliseconds; traces with latency below this threshold are ignored.', }, { title: 'Fallback', @@ -63,6 +62,7 @@ const LatencySampler: React.FC = ({ value, setValue }) => { ]} value={mappedValue} onChange={handleChange} + errorMessage={errorMessage} /> ); }; 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 9343cbf97..3a719af67 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 @@ -1,20 +1,28 @@ -import styled from 'styled-components'; +import React, { useEffect, useMemo, useState } from 'react'; import { safeJsonParse } from '@/utils'; import type { PiiMaskingSpec } from '@/types'; -import React, { useEffect, useMemo, useState } from 'react'; -import { Checkbox, FieldLabel } from '@/reuseable-components'; +import styled, { css } from 'styled-components'; +import { Checkbox, FieldError, FieldLabel } from '@/reuseable-components'; type Props = { value: string; setValue: (value: string) => void; + errorMessage?: string; }; type Parsed = PiiMaskingSpec; -const ListContainer = styled.div` +const ListContainer = styled.div<{ $hasError: boolean }>` display: flex; flex-direction: row; gap: 32px; + ${({ $hasError }) => + $hasError && + css` + border: 1px solid ${({ theme }) => theme.text.error}; + border-radius: 32px; + padding: 8px; + `} `; const strictPicklist = [ @@ -24,7 +32,7 @@ const strictPicklist = [ }, ]; -const PiiMasking: React.FC = ({ value, setValue }) => { +const PiiMasking: React.FC = ({ value, setValue, errorMessage }) => { const mappedValue = useMemo(() => safeJsonParse(value, { piiCategories: [] }).piiCategories, [value]); const [isLastSelection, setIsLastSelection] = useState(mappedValue.length === 1); @@ -56,11 +64,12 @@ const PiiMasking: React.FC = ({ value, setValue }) => { return (
- + {strictPicklist.map(({ id, label }) => ( handleChange(id, bool)} /> ))} + {!!errorMessage && {errorMessage}}
); }; diff --git a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/probabilistic-sampler.tsx b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/probabilistic-sampler.tsx index 8952275cc..8e87c2cb4 100644 --- a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/probabilistic-sampler.tsx +++ b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/probabilistic-sampler.tsx @@ -6,6 +6,7 @@ import type { ProbabilisticSamplerSpec } from '@/types'; type Props = { value: string; setValue: (value: string) => void; + errorMessage?: string; }; type Parsed = ProbabilisticSamplerSpec; @@ -13,7 +14,7 @@ type Parsed = ProbabilisticSamplerSpec; const MIN = 0, MAX = 100; -const ProbabilisticSampler: React.FC = ({ value, setValue }) => { +const ProbabilisticSampler: React.FC = ({ value, setValue, errorMessage }) => { const mappedValue = useMemo(() => safeJsonParse(value, { sampling_percentage: '0' }).sampling_percentage, [value]); const handleChange = (val: string) => { @@ -28,17 +29,7 @@ const ProbabilisticSampler: React.FC = ({ value, setValue }) => { setValue(str); }; - return ( - handleChange(v)} - /> - ); + return handleChange(v)} errorMessage={errorMessage} />; }; export default ProbabilisticSampler; diff --git a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/rename-attributes.tsx b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/rename-attributes.tsx index 2e7c4b3d9..414e8f8ee 100644 --- a/frontend/webapp/containers/main/actions/action-form-body/custom-fields/rename-attributes.tsx +++ b/frontend/webapp/containers/main/actions/action-form-body/custom-fields/rename-attributes.tsx @@ -6,21 +6,19 @@ import { KeyValueInputsList } from '@/reuseable-components'; type Props = { value: string; setValue: (value: string) => void; + errorMessage?: string; }; type Parsed = RenameAttributesSpec; -const RenameAttributes: React.FC = ({ value, setValue }) => { - const mappedValue = useMemo( - () => Object.entries(safeJsonParse(value, { renames: {} }).renames).map(([k, v]) => ({ key: k, value: v })), - [value] - ); +const RenameAttributes: React.FC = ({ value, setValue, errorMessage }) => { + const mappedValue = useMemo(() => Object.entries(safeJsonParse(value, { renames: {} }).renames).map(([k, v]) => ({ key: k, value: v })), [value]); const handleChange = ( arr: { key: string; value: string; - }[] + }[], ) => { const payload: Parsed = { renames: {}, @@ -35,7 +33,7 @@ const RenameAttributes: React.FC = ({ value, setValue }) => { setValue(str); }; - return ; + return ; }; export default RenameAttributes; diff --git a/frontend/webapp/containers/main/actions/action-form-body/index.tsx b/frontend/webapp/containers/main/actions/action-form-body/index.tsx index cc55afe26..8ab6df68f 100644 --- a/frontend/webapp/containers/main/actions/action-form-body/index.tsx +++ b/frontend/webapp/containers/main/actions/action-form-body/index.tsx @@ -9,6 +9,7 @@ interface Props { isUpdate?: boolean; action: ActionOption; formData: ActionInput; + formErrors: Record; handleFormChange: (key: keyof ActionInput, val: any) => void; } @@ -23,7 +24,7 @@ const FieldTitle = styled(Text)` margin-bottom: 12px; `; -export const ActionFormBody: React.FC = ({ isUpdate, action, formData, handleFormChange }) => { +export const ActionFormBody: React.FC = ({ isUpdate, action, formData, formErrors, handleFormChange }) => { return ( {isUpdate && ( @@ -35,13 +36,28 @@ export const ActionFormBody: React.FC = ({ isUpdate, action, formData, ha {!isUpdate && } />} - handleFormChange('signals', value)} /> - - {!isUpdate && handleFormChange('name', value)} />} + handleFormChange('signals', value)} + errorMessage={formErrors['signals']} + /> + + {!isUpdate && ( + handleFormChange('name', value)} + errorMessage={formErrors['name']} + /> + )} - handleFormChange('details', val)} /> + handleFormChange('details', val)} errorMessage={formErrors['details']} /> -