Skip to content

Commit 480dcb2

Browse files
authored
[GEN-1787]: descriptive titles for drawer "details card" (#1872)
1 parent 21b5af8 commit 480dcb2

File tree

7 files changed

+128
-108
lines changed

7 files changed

+128
-108
lines changed

frontend/webapp/containers/main/actions/action-drawer/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export const ActionDrawer: React.FC<Props> = () => {
119119
/>
120120
</FormContainer>
121121
) : (
122-
<CardDetails data={cardData} />
122+
<CardDetails title='Action Details' data={cardData} />
123123
)}
124124
</OverviewDrawer>
125125
);

frontend/webapp/containers/main/destinations/destination-drawer/index.tsx

Lines changed: 46 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import React, { useMemo, useState } from 'react';
2-
import { ACTION } from '@/utils';
3-
import buildCard from './build-card';
42
import styled from 'styled-components';
3+
import { safeJsonParse } from '@/utils';
54
import { useDrawerStore } from '@/store';
65
import { CardDetails } from '@/components';
7-
import buildDrawerItem from './build-drawer-item';
8-
import { ConditionDetails } from '@/reuseable-components';
6+
import type { ActualDestination } from '@/types';
97
import OverviewDrawer from '../../overview/overview-drawer';
108
import { DestinationFormBody } from '../destination-form-body';
11-
import { OVERVIEW_ENTITY_TYPES, type ActualDestination } from '@/types';
129
import { useDestinationCRUD, useDestinationFormData, useDestinationTypes } from '@/hooks';
1310

1411
interface Props {}
@@ -21,94 +18,96 @@ const FormContainer = styled.div`
2118
overflow-y: auto;
2219
`;
2320

24-
const DataContainer = styled.div`
25-
display: flex;
26-
flex-direction: column;
27-
gap: 12px;
28-
`;
29-
3021
export const DestinationDrawer: React.FC<Props> = () => {
31-
const { selectedItem, setSelectedItem } = useDrawerStore();
32-
const { destinations: destinationTypes } = useDestinationTypes();
22+
const selectedItem = useDrawerStore(({ selectedItem }) => selectedItem);
23+
const [isEditing, setIsEditing] = useState(false);
24+
const [isFormDirty, setIsFormDirty] = useState(false);
3325

26+
const { updateDestination, deleteDestination } = useDestinationCRUD();
3427
const { formData, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem, destinationTypeDetails, dynamicFields, setDynamicFields } = useDestinationFormData({
3528
destinationType: (selectedItem?.item as ActualDestination)?.destinationType?.type,
3629
preLoadedFields: (selectedItem?.item as ActualDestination)?.fields,
3730
// TODO: supportedSignals: thisDestination?.supportedSignals,
3831
// currently, the real "supportedSignals" is being used by "destination" passed as prop to "DestinationFormBody"
3932
});
4033

41-
const { updateDestination, deleteDestination } = useDestinationCRUD({
42-
onSuccess: (type) => {
43-
setIsEditing(false);
44-
setIsFormDirty(false);
45-
46-
if (type === ACTION.DELETE) {
47-
setSelectedItem(null);
48-
} else {
49-
const { item } = selectedItem as { item: ActualDestination };
50-
const { id } = item;
51-
setSelectedItem({ id, type: OVERVIEW_ENTITY_TYPES.DESTINATION, item: buildDrawerItem(id, formData, item) });
52-
}
53-
},
54-
});
34+
const cardData = useMemo(() => {
35+
if (!selectedItem) return [];
5536

56-
const [isEditing, setIsEditing] = useState(false);
57-
const [isFormDirty, setIsFormDirty] = useState(false);
37+
const buildMonitorsList = (exportedSignals: ActualDestination['exportedSignals']): string =>
38+
Object.keys(exportedSignals)
39+
.filter((key) => exportedSignals[key])
40+
.join(', ') || 'N/A';
5841

59-
const cardData = useMemo(() => {
60-
if (!selectedItem || !destinationTypeDetails) return [];
42+
const buildDestinationFieldData = (parsedFields: Record<string, string>) =>
43+
Object.entries(parsedFields).map(([key, value]) => {
44+
const found = destinationTypeDetails?.fields?.find((field) => field.name === key);
6145

62-
const { item } = selectedItem as { item: ActualDestination };
63-
const arr = buildCard(item, destinationTypeDetails);
46+
const { type } = safeJsonParse(found?.componentProperties, { type: '' });
47+
const secret = type === 'password' ? new Array(value.length).fill('•').join('') : '';
48+
49+
return {
50+
title: found?.displayName || key,
51+
value: secret || value || 'N/A',
52+
};
53+
});
6454

65-
return arr;
55+
const { exportedSignals, destinationType, fields } = selectedItem.item as ActualDestination;
56+
const parsedFields = safeJsonParse<Record<string, string>>(fields, {});
57+
const fieldsData = buildDestinationFieldData(parsedFields);
58+
59+
return [{ title: 'Destination', value: destinationType.displayName || 'N/A' }, { title: 'Monitors', value: buildMonitorsList(exportedSignals) }, ...fieldsData];
6660
}, [selectedItem, destinationTypeDetails]);
6761

62+
const { destinations } = useDestinationTypes();
6863
const thisDestination = useMemo(() => {
69-
if (!destinationTypes.length || !selectedItem || !isEditing) {
64+
if (!destinations.length || !selectedItem || !isEditing) {
7065
resetFormData();
7166
return undefined;
7267
}
7368

7469
const { item } = selectedItem as { item: ActualDestination };
75-
const found = destinationTypes.map(({ items }) => items.filter(({ type }) => type === item.destinationType.type)).filter((arr) => !!arr.length)[0][0];
70+
const found = destinations.map(({ items }) => items.filter(({ type }) => type === item.destinationType.type)).filter((arr) => !!arr.length)[0][0];
7671

7772
if (!found) return undefined;
7873

7974
loadFormWithDrawerItem(selectedItem);
8075

8176
return found;
82-
}, [destinationTypes, selectedItem, isEditing]);
77+
}, [destinations, selectedItem, isEditing]);
8378

8479
if (!selectedItem?.item) return null;
85-
const { id, item } = selectedItem as { id: string; item: ActualDestination };
80+
const { id, item } = selectedItem;
8681

8782
const handleEdit = (bool?: boolean) => {
88-
setIsEditing(typeof bool === 'boolean' ? bool : true);
83+
if (typeof bool === 'boolean') {
84+
setIsEditing(bool);
85+
} else {
86+
setIsEditing(true);
87+
}
8988
};
9089

9190
const handleCancel = () => {
91+
resetFormData();
9292
setIsEditing(false);
93-
setIsFormDirty(false);
9493
};
9594

9695
const handleDelete = async () => {
97-
await deleteDestination(id);
96+
await deleteDestination(id as string);
9897
};
9998

10099
const handleSave = async (newTitle: string) => {
101100
if (validateForm({ withAlert: true })) {
102-
const title = newTitle !== item.destinationType.displayName ? newTitle : '';
103-
handleFormChange('name', title);
104-
await updateDestination(id, { ...formData, name: title });
101+
const title = newTitle !== (item as ActualDestination).destinationType.displayName ? newTitle : '';
102+
103+
await updateDestination(id as string, { ...formData, name: title });
105104
}
106105
};
107106

108107
return (
109108
<OverviewDrawer
110-
title={item.name || item.destinationType.displayName}
111-
imageUri={item.destinationType.imageUrl}
109+
title={(item as ActualDestination).name || (item as ActualDestination).destinationType.displayName}
110+
imageUri={(item as ActualDestination).destinationType.imageUrl}
112111
isEdit={isEditing}
113112
isFormDirty={isFormDirty}
114113
onEdit={handleEdit}
@@ -135,10 +134,7 @@ export const DestinationDrawer: React.FC<Props> = () => {
135134
/>
136135
</FormContainer>
137136
) : (
138-
<DataContainer>
139-
<ConditionDetails conditions={item.conditions} />
140-
<CardDetails data={cardData} />
141-
</DataContainer>
137+
<CardDetails title='Destination Details' data={cardData} />
142138
)}
143139
</OverviewDrawer>
144140
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React, { Dispatch, SetStateAction, useState } from 'react';
2+
import styled from 'styled-components';
3+
import { SignalUppercase } from '@/utils';
4+
import type { DropdownOption } from '@/types';
5+
import { Dropdown, Input, MonitoringCheckboxes } from '@/reuseable-components';
6+
7+
interface Props {
8+
selectedTag: DropdownOption | undefined;
9+
onTagSelect: (option: DropdownOption) => void;
10+
onSearch: (value: string) => void;
11+
selectedMonitors: SignalUppercase[];
12+
setSelectedMonitors: Dispatch<SetStateAction<SignalUppercase[]>>;
13+
}
14+
15+
const Container = styled.div`
16+
display: flex;
17+
align-items: center;
18+
gap: 12px;
19+
`;
20+
21+
const WidthConstraint = styled.div`
22+
width: 160px;
23+
margin-right: 8px;
24+
`;
25+
26+
const DROPDOWN_OPTIONS = [
27+
{ value: 'All types', id: 'all' },
28+
{ value: 'Managed', id: 'managed' },
29+
{ value: 'Self-hosted', id: 'self hosted' },
30+
];
31+
32+
export const ChooseDestinationFilters: React.FC<Props> = ({ selectedTag, onTagSelect, onSearch, selectedMonitors, setSelectedMonitors }) => {
33+
const [searchTerm, setSearchTerm] = useState('');
34+
35+
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
36+
const value = e.target.value;
37+
setSearchTerm(value);
38+
onSearch(value);
39+
};
40+
41+
return (
42+
<Container>
43+
<WidthConstraint>
44+
<Input placeholder='Search...' icon='/icons/common/search.svg' value={searchTerm} onChange={handleSearchChange} />
45+
</WidthConstraint>
46+
<WidthConstraint>
47+
<Dropdown options={DROPDOWN_OPTIONS} value={selectedTag} onSelect={onTagSelect} onDeselect={() => {}} />
48+
</WidthConstraint>
49+
<MonitoringCheckboxes title='' selectedSignals={selectedMonitors} setSelectedSignals={setSelectedMonitors} />
50+
</Container>
51+
);
52+
};

frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/index.tsx

Lines changed: 24 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,82 +2,58 @@ import React, { useMemo, useState } from 'react';
22
import styled from 'styled-components';
33
import { SignalUppercase } from '@/utils';
44
import { useDestinationTypes } from '@/hooks';
5-
import type { DestinationTypeItem } from '@/types';
65
import { DestinationsList } from './destinations-list';
7-
import { Divider, Dropdown, Input, MonitoringCheckboxes, SectionTitle } from '@/reuseable-components';
6+
import { Divider, SectionTitle } from '@/reuseable-components';
7+
import type { DropdownOption, DestinationTypeItem } from '@/types';
8+
import { ChooseDestinationFilters } from './choose-destination-filters';
89

910
interface Props {
1011
onSelect: (item: DestinationTypeItem) => void;
11-
hidden?: boolean;
1212
}
1313

14+
const DEFAULT_MONITORS: SignalUppercase[] = ['LOGS', 'METRICS', 'TRACES'];
15+
const DEFAULT_DROPDOWN_VALUE = { id: 'all', value: 'All types' };
16+
1417
const Container = styled.div`
1518
display: flex;
1619
flex-direction: column;
1720
gap: 24px;
1821
`;
1922

20-
const Filters = styled.div`
21-
display: flex;
22-
align-items: center;
23-
gap: 12px;
24-
`;
25-
26-
const WidthConstraint = styled.div`
27-
width: 160px;
28-
margin-right: 8px;
29-
`;
30-
31-
const DROPDOWN_OPTIONS = [
32-
{ value: 'All types', id: 'all' },
33-
{ value: 'Managed', id: 'managed' },
34-
{ value: 'Self-hosted', id: 'self hosted' },
35-
];
36-
37-
const DEFAULT_CATEGORY = DROPDOWN_OPTIONS[0];
38-
const DEFAULT_MONITORS: SignalUppercase[] = ['LOGS', 'METRICS', 'TRACES'];
39-
40-
export const ChooseDestinationBody: React.FC<Props> = ({ onSelect, hidden }) => {
41-
const [search, setSearch] = useState('');
42-
const [selectedCategory, setSelectedCategory] = useState(DEFAULT_CATEGORY);
23+
export const ChooseDestinationBody: React.FC<Props> = ({ onSelect }) => {
24+
const [searchValue, setSearchValue] = useState('');
4325
const [selectedMonitors, setSelectedMonitors] = useState<SignalUppercase[]>(DEFAULT_MONITORS);
26+
const [dropdownValue, setDropdownValue] = useState<DropdownOption>(DEFAULT_DROPDOWN_VALUE);
4427

45-
const { destinations: destinationTypes } = useDestinationTypes();
28+
const { destinations } = useDestinationTypes();
4629

4730
const filteredDestinations = useMemo(() => {
48-
return destinationTypes
31+
return destinations
4932
.map((category) => {
5033
const filteredItems = category.items.filter((item) => {
51-
const matchesSearch = !search || item.displayName.toLowerCase().includes(search.toLowerCase());
52-
const matchesCategory = selectedCategory.id === 'all' || selectedCategory.id === category.name;
53-
const matchesMonitor = selectedMonitors.some((monitor) => item.supportedSignals[monitor.toLowerCase()]?.supported);
34+
const matchesSearch = searchValue ? item.displayName.toLowerCase().includes(searchValue.toLowerCase()) : true;
35+
const matchesDropdown = dropdownValue.id !== 'all' ? category.name === dropdownValue.id : true;
36+
const matchesMonitor = selectedMonitors.length ? selectedMonitors.some((monitor) => item.supportedSignals[monitor.toLowerCase()]?.supported) : true;
5437

55-
return matchesSearch && matchesCategory && matchesMonitor;
38+
return matchesSearch && matchesDropdown && matchesMonitor;
5639
});
5740

5841
return { ...category, items: filteredItems };
5942
})
60-
.filter(({ items }) => !!items.length); // Filter out empty categories
61-
}, [destinationTypes, search, selectedCategory, selectedMonitors]);
62-
63-
if (hidden) return null;
43+
.filter((category) => category.items.length > 0); // Filter out empty categories
44+
}, [destinations, searchValue, dropdownValue, selectedMonitors]);
6445

6546
return (
6647
<Container>
6748
<SectionTitle title='Choose destination' description='Add backend destination you want to connect with Odigos.' />
68-
69-
<Filters>
70-
<WidthConstraint>
71-
<Input placeholder='Search...' icon='/icons/common/search.svg' value={search} onChange={({ target: { value } }) => setSearch(value)} />
72-
</WidthConstraint>
73-
<WidthConstraint>
74-
<Dropdown options={DROPDOWN_OPTIONS} value={selectedCategory} onSelect={(opt) => setSelectedCategory(opt)} onDeselect={() => {}} />
75-
</WidthConstraint>
76-
<MonitoringCheckboxes title='' selectedSignals={selectedMonitors} setSelectedSignals={setSelectedMonitors} />
77-
</Filters>
78-
49+
<ChooseDestinationFilters
50+
selectedTag={dropdownValue}
51+
onTagSelect={(opt) => setDropdownValue(opt)}
52+
onSearch={setSearchValue}
53+
selectedMonitors={selectedMonitors}
54+
setSelectedMonitors={setSelectedMonitors}
55+
/>
7956
<Divider />
80-
8157
<DestinationsList items={filteredDestinations} setSelectedItems={onSelect} />
8258
</Container>
8359
);

frontend/webapp/containers/main/destinations/destination-modal/index.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,7 @@ export const DestinationModal: React.FC<AddDestinationModalProps> = ({ isOnboard
123123
</SideMenuWrapper>
124124

125125
<ModalBody style={{ margin: '32px 24px 0 24px' }}>
126-
{/*
127-
in other modals we would render this out, but for this case we will use "hidden" instead,
128-
this is to preserve the filters-state when going back-and-forth between selections
129-
*/}
130-
<ChooseDestinationBody onSelect={handleSelect} hidden={!!selectedItem} />
131-
132-
{!!selectedItem && (
126+
{!!selectedItem ? (
133127
<DestinationFormBody
134128
destination={selectedItem}
135129
isFormOk={isFormOk}
@@ -138,6 +132,8 @@ export const DestinationModal: React.FC<AddDestinationModalProps> = ({ isOnboard
138132
dynamicFields={dynamicFields}
139133
setDynamicFields={setDynamicFields}
140134
/>
135+
) : (
136+
<ChooseDestinationBody onSelect={handleSelect} />
141137
)}
142138
</ModalBody>
143139
</Container>

frontend/webapp/containers/main/instrumentation-rules/rule-drawer/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export const RuleDrawer: React.FC<Props> = () => {
116116
/>
117117
</FormContainer>
118118
) : (
119-
<CardDetails data={cardData} />
119+
<CardDetails title='Instrumentation Rule Details' data={cardData} />
120120
)}
121121
</OverviewDrawer>
122122
);

frontend/webapp/containers/main/sources/source-drawer-container/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export const SourceDrawer: React.FC<Props> = () => {
123123
) : (
124124
<DataContainer>
125125
<ConditionDetails conditions={item.instrumentedApplicationDetails.conditions} />
126-
<CardDetails data={cardData} />
126+
<CardDetails title='Source Details' data={cardData} />
127127
</DataContainer>
128128
)}
129129
</OverviewDrawer>

0 commit comments

Comments
 (0)