Skip to content

Commit 28e6bad

Browse files
authored
Merge pull request #80 from delegateas/features/globalsearch-changes
Globalsearch changes
2 parents 937afd3 + e08b03b commit 28e6bad

19 files changed

+997
-225
lines changed

Website/components/datamodelview/Attributes.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { highlightMatch } from "../datamodelview/List";
1818
import { Box, Button, FormControl, InputAdornment, InputLabel, MenuItem, Select, Table, TableBody, TableCell, TableHead, TableRow, TextField, Tooltip, Typography, useTheme } from "@mui/material"
1919
import { ClearRounded, SearchRounded, Visibility, VisibilityOff, ArrowUpwardRounded, ArrowDownwardRounded } from "@mui/icons-material"
2020
import { useEntityFiltersDispatch } from "@/contexts/EntityFiltersContext"
21+
import { useDatamodelData } from "@/contexts/DatamodelDataContext"
2122

2223
type SortDirection = 'asc' | 'desc' | null
2324
type SortColumn = 'displayName' | 'schemaName' | 'type' | 'description' | null
@@ -37,6 +38,7 @@ export const Attributes = ({ entity, search = "", onVisibleCountChange }: IAttri
3738

3839
const theme = useTheme();
3940
const entityFiltersDispatch = useEntityFiltersDispatch();
41+
const { searchScope } = useDatamodelData();
4042

4143
// Report filter state changes to context
4244
useEffect(() => {
@@ -144,7 +146,10 @@ export const Attributes = ({ entity, search = "", onVisibleCountChange }: IAttri
144146
}
145147

146148
const sortedAttributes = getSortedAttributes();
147-
const highlightTerm = searchQuery || search; // Use internal search or parent search for highlighting
149+
// Only highlight if search scope includes columns
150+
// Use internal search query first, or parent search if column scopes are enabled
151+
const highlightTerm = searchQuery ||
152+
(search && (searchScope.columnNames || searchScope.columnDescriptions || searchScope.columnDataTypes) ? search : "");
148153

149154
// Notify parent of visible count changes
150155
useEffect(() => {
@@ -432,21 +437,21 @@ function getAttributeComponent(entity: EntityType, attribute: AttributeType, hig
432437
case 'ChoiceAttribute':
433438
return <ChoiceAttribute key={key} attribute={attribute} highlightMatch={highlightMatch} highlightTerm={highlightTerm} />;
434439
case 'DateTimeAttribute':
435-
return <DateTimeAttribute key={key} attribute={attribute} />;
440+
return <DateTimeAttribute key={key} attribute={attribute} highlightMatch={highlightMatch} highlightTerm={highlightTerm} />;
436441
case 'GenericAttribute':
437-
return <GenericAttribute key={key} attribute={attribute} />;
442+
return <GenericAttribute key={key} attribute={attribute} highlightMatch={highlightMatch} highlightTerm={highlightTerm} />;
438443
case 'IntegerAttribute':
439444
return <IntegerAttribute key={key} attribute={attribute} />;
440445
case 'LookupAttribute':
441446
return <LookupAttribute key={key} attribute={attribute} />;
442447
case 'DecimalAttribute':
443448
return <DecimalAttribute key={key} attribute={attribute} />;
444449
case 'StatusAttribute':
445-
return <StatusAttribute key={key} attribute={attribute} />;
450+
return <StatusAttribute key={key} attribute={attribute} highlightMatch={highlightMatch} highlightTerm={highlightTerm} />;
446451
case 'StringAttribute':
447-
return <StringAttribute key={key} attribute={attribute} />;
452+
return <StringAttribute key={key} attribute={attribute} highlightMatch={highlightMatch} highlightTerm={highlightTerm} />;
448453
case 'BooleanAttribute':
449-
return <BooleanAttribute key={key} attribute={attribute} />;
454+
return <BooleanAttribute key={key} attribute={attribute} highlightMatch={highlightMatch} highlightTerm={highlightTerm} />;
450455
case 'FileAttribute':
451456
return <FileAttribute key={key} attribute={attribute} />;
452457
default:

Website/components/datamodelview/DatamodelView.tsx

Lines changed: 244 additions & 90 deletions
Large diffs are not rendered by default.

Website/components/datamodelview/List.tsx

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import { AttributeType, EntityType, GroupType } from "@/lib/Types";
88
import { updateURL } from "@/lib/url-utils";
99
import { copyToClipboard, generateGroupLink } from "@/lib/clipboard-utils";
1010
import { useSnackbar } from "@/contexts/SnackbarContext";
11+
import { useEntityFilters } from "@/contexts/EntityFiltersContext";
1112
import { Box, CircularProgress, debounce, Tooltip } from '@mui/material';
1213

1314
interface IListProps {
1415
setCurrentIndex: (index: number) => void;
16+
entityActiveTabs: Map<string, number>;
1517
}
1618

1719
// Helper to highlight search matches
@@ -22,15 +24,26 @@ export function highlightMatch(text: string, search: string) {
2224
return <>{text.slice(0, idx)}<mark className="bg-yellow-200 text-black px-0.5 rounded">{text.slice(idx, idx + search.length)}</mark>{text.slice(idx + search.length)}</>;
2325
}
2426

25-
export const List = ({ setCurrentIndex }: IListProps) => {
27+
export const List = ({ setCurrentIndex, entityActiveTabs }: IListProps) => {
2628
const dispatch = useDatamodelViewDispatch();
2729
const { currentSection, loadingSection } = useDatamodelView();
2830
const { groups, filtered, search } = useDatamodelData();
31+
const { selectedSecurityRoles } = useEntityFilters();
2932
const { showSnackbar } = useSnackbar();
3033
const parentRef = useRef<HTMLDivElement | null>(null);
3134
// used to relocate section after search/filter
3235
const [sectionVirtualItem, setSectionVirtualItem] = useState<string | null>(null);
3336

37+
// Helper function to check if entity has access from selected security roles
38+
const hasSecurityRoleAccess = useCallback((entity: EntityType): boolean => {
39+
if (selectedSecurityRoles.length === 0) return false;
40+
41+
return entity.SecurityRoles.some(role =>
42+
selectedSecurityRoles.includes(role.Name) &&
43+
(role.Read !== null && role.Read >= 0) // Has any read access
44+
);
45+
}, [selectedSecurityRoles]);
46+
3447
const handleCopyGroupLink = useCallback(async (groupName: string) => {
3548
const link = generateGroupLink(groupName);
3649
const success = await copyToClipboard(link);
@@ -43,7 +56,7 @@ export const List = ({ setCurrentIndex }: IListProps) => {
4356

4457
// Only recalculate items when filtered or search changes
4558
const flatItems = useMemo(() => {
46-
if (filtered && filtered.length > 0) return filtered.filter(item => item.type !== 'attribute');
59+
if (filtered && filtered.length > 0) return filtered.filter(item => item.type !== 'attribute' && item.type !== 'relationship');
4760

4861
const lowerSearch = search.trim().toLowerCase();
4962
const items: Array<
@@ -54,6 +67,13 @@ export const List = ({ setCurrentIndex }: IListProps) => {
5467
// Filter entities in this group
5568
const filteredEntities = group.Entities.filter((entity: EntityType) => {
5669
const typedEntity = entity;
70+
71+
// If security roles are selected, only show entities with access
72+
if (selectedSecurityRoles.length > 0) {
73+
const hasAccess = hasSecurityRoleAccess(typedEntity);
74+
if (!hasAccess) return false;
75+
}
76+
5777
if (!lowerSearch) return true;
5878
// Match entity schema or display name
5979
const entityMatch = typedEntity.SchemaName.toLowerCase().includes(lowerSearch) ||
@@ -73,7 +93,7 @@ export const List = ({ setCurrentIndex }: IListProps) => {
7393
}
7494
}
7595
return items;
76-
}, [filtered, search, groups]);
96+
}, [filtered, search, groups, selectedSecurityRoles, hasSecurityRoleAccess]);
7797

7898
const debouncedOnChange = debounce((instance, sync) => {
7999
if (!sync) {
@@ -192,6 +212,29 @@ export const List = ({ setCurrentIndex }: IListProps) => {
192212
}
193213
}, [scrollToSection]);
194214

215+
const scrollToRelationship = useCallback((sectionId: string, relSchema: string) => {
216+
const relId = `rel-${sectionId}-${relSchema}`;
217+
218+
// Helper function to attempt scrolling to relationship with retries
219+
const attemptScroll = (attemptsLeft: number) => {
220+
const relationshipLocation = document.getElementById(relId);
221+
222+
if (relationshipLocation) {
223+
// Relationship found, scroll to it
224+
relationshipLocation.scrollIntoView({ behavior: 'smooth', block: 'center' });
225+
} else if (attemptsLeft > 0) {
226+
// Relationship not rendered yet, retry after delay
227+
setTimeout(() => attemptScroll(attemptsLeft - 1), 100);
228+
} else {
229+
// Give up after all retries, just scroll to section
230+
scrollToSection(sectionId);
231+
}
232+
};
233+
234+
// Start attempting to scroll with 5 retries (total 500ms wait time)
235+
attemptScroll(5);
236+
}, [scrollToSection]);
237+
195238
const scrollToGroup = useCallback((groupName: string) => {
196239
const groupIndex = flatItems.findIndex(item =>
197240
item.type === 'group' && item.group.Name === groupName
@@ -214,9 +257,10 @@ export const List = ({ setCurrentIndex }: IListProps) => {
214257
useEffect(() => {
215258
dispatch({ type: 'SET_SCROLL_TO_SECTION', payload: scrollToSection });
216259
dispatch({ type: 'SET_SCROLL_TO_ATTRIBUTE', payload: scrollToAttribute });
260+
dispatch({ type: 'SET_SCROLL_TO_RELATIONSHIP', payload: scrollToRelationship });
217261
dispatch({ type: 'SET_SCROLL_TO_GROUP', payload: scrollToGroup });
218262
dispatch({ type: 'SET_RESTORE_SECTION', payload: restoreSection });
219-
}, [dispatch, scrollToSection, scrollToAttribute, scrollToGroup]);
263+
}, [dispatch, scrollToSection, scrollToAttribute, scrollToRelationship, scrollToGroup, restoreSection]);
220264

221265
const smartScrollToIndex = useCallback((index: number) => {
222266
rowVirtualizer.scrollToIndex(index, { align: 'start' });
@@ -301,6 +345,8 @@ export const List = ({ setCurrentIndex }: IListProps) => {
301345
entity={item.entity}
302346
group={item.group}
303347
search={search}
348+
activeTab={entityActiveTabs.get(item.entity.SchemaName)}
349+
highlightSecurityRole={selectedSecurityRoles.length > 0 && hasSecurityRoleAccess(item.entity)}
304350
/>
305351
</div>
306352
)}

Website/components/datamodelview/Relationships.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const Relationships = ({ entity, search = "", onVisibleCountChange }: IRe
3131

3232
const dispatch = useDatamodelViewDispatch();
3333
const { scrollToSection } = useDatamodelView();
34-
const { groups } = useDatamodelData();
34+
const { groups, searchScope } = useDatamodelData();
3535

3636
// Helper function to check if an entity is in the solution
3737
const isEntityInSolution = (entitySchemaName: string): boolean => {
@@ -147,7 +147,10 @@ export const Relationships = ({ entity, search = "", onVisibleCountChange }: IRe
147147
]
148148

149149
const sortedRelationships = getSortedRelationships();
150-
const highlightTerm = searchQuery || search; // Use internal search or parent search for highlighting
150+
// Only highlight if search scope includes relationships
151+
// Use internal search query first, or parent search if relationships scope is enabled
152+
const highlightTerm = searchQuery ||
153+
(search && searchScope.relationships ? search : "");
151154

152155
// Notify parent of visible count changes
153156
useEffect(() => {
@@ -393,6 +396,7 @@ export const Relationships = ({ entity, search = "", onVisibleCountChange }: IRe
393396
{sortedRelationships.map((relationship, index) =>
394397
<TableRow
395398
key={relationship.RelationshipSchema}
399+
id={`rel-${entity.SchemaName}-${relationship.RelationshipSchema}`}
396400
className="transition-colors duration-150 border-b"
397401
sx={{
398402
'&:hover': { backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.04)' : 'rgba(0, 0, 0, 0.04)' },
@@ -405,7 +409,7 @@ export const Relationships = ({ entity, search = "", onVisibleCountChange }: IRe
405409
}}
406410
>
407411
<TableCell className="break-words py-1 md:py-1.5 text-xs md:text-sm">
408-
{highlightMatch(relationship.Name, highlightTerm)}
412+
{relationship.Name}
409413
</TableCell>
410414
<TableCell className="py-1 md:py-1.5">
411415
{isEntityInSolution(relationship.TableSchema) ? (
@@ -430,13 +434,13 @@ export const Relationships = ({ entity, search = "", onVisibleCountChange }: IRe
430434
}
431435
}}
432436
>
433-
{highlightMatch(relationship.TableSchema, highlightTerm)}
437+
{relationship.TableSchema}
434438
</Button>
435439
) : (
436440
<Chip
437441
key={relationship.TableSchema}
438442
icon={<ContentPasteOffRounded className="w-2 h-2 md:w-3 md:h-3" />}
439-
label={highlightMatch(relationship.TableSchema, highlightTerm)}
443+
label={relationship.TableSchema}
440444
size="small"
441445
disabled
442446
sx={{
@@ -452,7 +456,7 @@ export const Relationships = ({ entity, search = "", onVisibleCountChange }: IRe
452456
)}
453457
</TableCell>
454458
<TableCell className="break-words py-1 md:py-1.5 text-xs md:text-sm">
455-
{relationship.LookupDisplayName}
459+
{highlightMatch(relationship.LookupDisplayName, highlightTerm)}
456460
</TableCell>
457461
<TableCell className="py-1 md:py-1.5 text-xs md:text-sm">
458462
{relationship.RelationshipType}
@@ -461,7 +465,7 @@ export const Relationships = ({ entity, search = "", onVisibleCountChange }: IRe
461465
<CascadeConfiguration config={relationship.CascadeConfiguration} />
462466
</TableCell>
463467
<TableCell className="break-words py-1 md:py-1.5 text-xs md:text-sm">
464-
{relationship.RelationshipSchema}
468+
{highlightMatch(relationship.RelationshipSchema, highlightTerm)}
465469
{relationship.IntersectEntitySchemaName &&
466470
(<Typography variant="body2" className="text-xs md:text-sm text-secondary"><b>Intersecting table:</b> {relationship.IntersectEntitySchemaName}</Typography>)}
467471
</TableCell>

Website/components/datamodelview/Section.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SecurityRoles } from "./entity/SecurityRoles"
66
import Keys from "./Keys"
77
import { Attributes } from "./Attributes"
88
import { Relationships } from "./Relationships"
9+
import { highlightMatch } from "./List"
910
import React from "react"
1011
import { Box, Paper, Tab, Tabs } from "@mui/material"
1112
import CustomTabPanel from "../shared/elements/TabPanel"
@@ -15,11 +16,20 @@ interface ISectionProps {
1516
entity: EntityType;
1617
group: GroupType;
1718
search?: string;
19+
activeTab?: number; // External tab control for search navigation
20+
highlightSecurityRole?: boolean; // Highlight when entity matches selected security roles
1821
}
1922

2023
export const Section = React.memo(
21-
({ entity, group, search }: ISectionProps) => {
24+
({ entity, group, search, activeTab, highlightSecurityRole }: ISectionProps) => {
2225
const [tab, setTab] = React.useState(0);
26+
27+
// Update local tab state when external activeTab prop changes
28+
React.useEffect(() => {
29+
if (activeTab !== undefined) {
30+
setTab(activeTab);
31+
}
32+
}, [activeTab]);
2333
const [visibleAttributeCount, setVisibleAttributeCount] = React.useState(entity.Attributes.length);
2434
const [visibleRelationshipCount, setVisibleRelationshipCount] = React.useState(entity.Relationships.length);
2535

@@ -37,12 +47,18 @@ export const Section = React.memo(
3747

3848
return (
3949
<Box data-entity-schema={entity.SchemaName} data-group={group.Name} className="mb-10">
40-
<Paper className="rounded-lg" sx={{ backgroundColor: 'background.paper' }} variant="outlined">
50+
<Paper
51+
className="rounded-lg"
52+
sx={{
53+
backgroundColor: 'background.paper',
54+
}}
55+
variant="outlined"
56+
>
4157
<Box className="flex flex-col xl:flex-row min-w-0 p-6">
4258
<EntityHeader entity={entity} />
4359
{entity.SecurityRoles.length > 0 && (
4460
<div className="md:w-full xl:w-2/3 md:border-t xl:border-t-0 mt-6 xl:mt-0 xl:pt-0">
45-
<SecurityRoles roles={entity.SecurityRoles} />
61+
<SecurityRoles roles={entity.SecurityRoles} highlightMatch={highlightMatch} highlightTerm={search || ''} highlightSecurityRole={highlightSecurityRole} />
4662
</div>
4763
)}
4864
</Box>
@@ -114,10 +130,12 @@ export const Section = React.memo(
114130
},
115131
// Custom comparison function to prevent unnecessary re-renders
116132
(prevProps, nextProps) => {
117-
// Only re-render if entity, search or group changes
133+
// Only re-render if entity, search, group, activeTab, or highlightSecurityRole changes
118134
return prevProps.entity.SchemaName === nextProps.entity.SchemaName &&
119135
prevProps.search === nextProps.search &&
120-
prevProps.group.Name === nextProps.group.Name;
136+
prevProps.group.Name === nextProps.group.Name &&
137+
prevProps.activeTab === nextProps.activeTab &&
138+
prevProps.highlightSecurityRole === nextProps.highlightSecurityRole;
121139
}
122140
);
123141

0 commit comments

Comments
 (0)