Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
31 changes: 19 additions & 12 deletions Generator/DataverseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public DataverseService(IConfiguration configuration, ILogger<DataverseService>
var entityRootBehaviour = solutionComponents.Where(x => x.ComponentType == 1).ToDictionary(x => x.ObjectId, x => x.RootComponentBehavior);
var attributesInSolution = solutionComponents.Where(x => x.ComponentType == 2).Select(x => x.ObjectId).ToHashSet();
var rolesInSolution = solutionComponents.Where(x => x.ComponentType == 20).Select(x => x.ObjectId).ToList();
var pluginStepsInSolution = solutionComponents.Where(x => x.ComponentType == 92).Select(x => x.ObjectId).ToList();

var entitiesInSolutionMetadata = await GetEntityMetadata(entitiesInSolution);

Expand Down Expand Up @@ -140,7 +139,7 @@ public DataverseService(IConfiguration configuration, ILogger<DataverseService>
.ToList(),
RelevantManyToMany =
x.ManyToManyRelationships
.Where(r => entityLogicalNamesInSolution.Contains(r.IntersectEntityName.ToLower()))
.Where(r => entityLogicalNamesInSolution.Contains(r.Entity1LogicalName) && entityLogicalNamesInSolution.Contains(r.Entity2LogicalName))
.ToList(),
})
.Where(x => x.EntityMetadata.DisplayName.UserLocalizedLabel?.Label != null)
Expand Down Expand Up @@ -215,16 +214,24 @@ private static Record MakeRecord(

var manyToMany = relevantManyToMany
.Where(x => logicalToSchema.ContainsKey(x.Entity1LogicalName) && logicalToSchema[x.Entity1LogicalName].IsInSolution)
.Select(x => new DTO.Relationship(
x.IsCustomRelationship ?? false,
x.Entity1AssociatedMenuConfiguration.Behavior == AssociatedMenuBehavior.UseLabel
? x.Entity1AssociatedMenuConfiguration.Label.UserLocalizedLabel?.Label ?? x.Entity1NavigationPropertyName
: x.Entity1NavigationPropertyName,
logicalToSchema[x.Entity1LogicalName].Name,
"-",
x.SchemaName,
IsManyToMany: true,
null))
.Select(x =>
{
var useEntity1 = x.Entity1LogicalName == entity.LogicalName;

var label = !useEntity1
? x.Entity1AssociatedMenuConfiguration.Label.UserLocalizedLabel?.Label ?? x.Entity1NavigationPropertyName
: x.Entity2AssociatedMenuConfiguration.Label.UserLocalizedLabel?.Label ?? x.Entity2NavigationPropertyName;

return new DTO.Relationship(
x.IsCustomRelationship ?? false,
label,
logicalToSchema[!useEntity1 ? x.Entity1LogicalName : x.Entity2LogicalName].Name,
"-",
x.SchemaName,
IsManyToMany: true,
null
);
})
.ToList();

Dictionary<string, string> tablegroups = []; // logicalname -> group
Expand Down
16 changes: 10 additions & 6 deletions Website/app/api/markdown/route.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { NextResponse } from 'next/server'
import { readFileSync } from "fs";
import { existsSync, readFileSync } from "fs";
import { join } from "path";

export async function GET() {
const generatedPath = join(process.cwd(), 'generated', 'Introduction.md');
const stubsPath = join(process.cwd(), 'stubs', 'Introduction.md');
let fileContent;

if (!existsSync(generatedPath)) {
console.error(`File not found at path: ${generatedPath}`);
return NextResponse.json({ error: 'File not found' }, { status: 404 });
}

let fileContent: string;
try {
fileContent = readFileSync(generatedPath, 'utf-8');
} catch (error) {
fileContent = readFileSync(stubsPath, 'utf-8');
console.error('Error reading generated wiki file, falling back to stubs:', error);
} catch {
return NextResponse.json({ error: 'File not found' }, { status: 404 });
}
return NextResponse.json({ fileContent })
}
19 changes: 13 additions & 6 deletions Website/components/aboutview/AboutView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@ export const AboutView = ({}: IAboutViewProps) => {
<Box className="max-w-6xl mx-auto py-10 px-4 sm:px-6 lg:px-12">
{/* Logo */}
<Box className="flex justify-center mb-10">
<Box
component="img"
className="h-20 sm:h-28 md:h-32 object-contain"
src="/DMVLOGOHORZ.svg"
alt="Data Model Viewer logo"
/>
<Box className="flex items-center">
<Box
component="img"
className="h-20 sm:h-28 md:h-32 object-contain"
src="/DMVLOGO.svg"
alt="Data Model Viewer logo"
/>
<Box className="flex flex-col ml-4 mt-4 justify-center h-full">
<Typography variant='h4' className='m-0 p-0 leading-8'>DATA MODEL</Typography>
<Typography variant='h2' className='m-0 p-0 font-semibold leading-14'>VIEWER</Typography>
<Typography variant='caption' color='text.secondary'>@ DELEGATE | CONTEXT&</Typography>
</Box>
</Box>
</Box>

{/* What is DMV */}
Expand Down
2 changes: 1 addition & 1 deletion Website/components/datamodelview/Attributes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ export const Attributes = ({ entity, onVisibleCountChange, search = "" }: IAttri
{highlightMatch(attribute.SchemaName, highlightTerm)}
</TableCell>
<TableCell className="break-words py-1 md:py-1.5">{getAttributeComponent(entity, attribute, highlightMatch, highlightTerm)}</TableCell>
<TableCell className="py-1 md:py-1.5"><AttributeDetails attribute={attribute} /></TableCell>
<TableCell className="py-1 md:py-1.5"><AttributeDetails entityName={entity.SchemaName} attribute={attribute} /></TableCell>
<TableCell className="break-words py-1 md:py-1.5 text-xs md:text-sm">
{highlightMatch(attribute.Description ?? "", highlightTerm)}
</TableCell>
Expand Down
167 changes: 81 additions & 86 deletions Website/components/datamodelview/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { AttributeType, EntityType, GroupType } from "@/lib/Types";
import { updateURL } from "@/lib/url-utils";
import { copyToClipboard, generateGroupLink } from "@/lib/clipboard-utils";
import { useSnackbar } from "@/contexts/SnackbarContext";
import { debounce, Tooltip } from '@mui/material';
import { Box, CircularProgress, debounce, Tooltip } from '@mui/material';

interface IListProps {
setCurrentIndex: (index: number) => void;
Expand All @@ -24,16 +24,13 @@ export function highlightMatch(text: string, search: string) {

export const List = ({ setCurrentIndex }: IListProps) => {
const dispatch = useDatamodelViewDispatch();
const { currentSection } = useDatamodelView();
const { currentSection, loadingSection } = useDatamodelView();
const { groups, filtered, search } = useDatamodelData();
const { showSnackbar } = useSnackbar();
const parentRef = useRef<HTMLDivElement | null>(null);
// used to relocate section after search/filter
const [sectionVirtualItem, setSectionVirtualItem] = useState<string | null>(null);

// Track position before search for restoration
const isTabSwitching = useRef(false);


const handleCopyGroupLink = useCallback(async (groupName: string) => {
const link = generateGroupLink(groupName);
const success = await copyToClipboard(link);
Expand Down Expand Up @@ -163,8 +160,7 @@ export const List = ({ setCurrentIndex }: IListProps) => {
}

rowVirtualizer.scrollToIndex(sectionIndex, {
align: 'start',
behavior: 'smooth'
align: 'start'
});

}, [flatItems]);
Expand All @@ -180,8 +176,7 @@ export const List = ({ setCurrentIndex }: IListProps) => {
}

rowVirtualizer.scrollToIndex(groupIndex, {
align: 'start',
behavior: 'smooth'
align: 'start'
});
}, [flatItems]);

Expand All @@ -208,85 +203,85 @@ export const List = ({ setCurrentIndex }: IListProps) => {
}, [rowVirtualizer]);

return (
<div ref={parentRef} style={{ height: 'calc(100vh - var(--layout-header-desktop-height))', overflow: 'auto' }} className="p-6 relative no-scrollbar">
<>
<Box className={`absolute w-full h-full flex items-center justify-center z-[100] transition-opacity duration-300 ${loadingSection ? 'opacity-100' : 'opacity-0 pointer-events-none'}`}>
<CircularProgress />
</Box>
<div ref={parentRef} style={{ height: 'calc(100vh - var(--layout-header-desktop-height))', overflow: 'auto' }} className="relative no-scrollbar">

{/* Show no results message when searching but no items found */}
{flatItems.length === 0 && search && search.length >= 3 && (
<div className="flex flex-col items-center justify-center h-64 text-gray-500">
<div className="text-lg font-medium mb-2">No tables found</div>
<div className="text-sm text-center">
No attributes match your search for &quot;{search}&quot;
{/* Show no results message when searching but no items found */}
{flatItems.length === 0 && search && search.length >= 3 && (
<div className="flex flex-col items-center justify-center h-64 text-gray-500">
<div className="text-lg font-medium mb-2">No tables found</div>
<div className="text-sm text-center">
No attributes match your search for &quot;{search}&quot;
</div>
</div>
</div>
)}

{/* Virtualized list */}
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
visibility: flatItems.length === 0 ? 'hidden' : 'visible'
}}
>
{rowVirtualizer.getVirtualItems().map((virtualItem) => {
const item = flatItems[virtualItem.index];
)}
{/* Virtualized list */}
<div
className={`m-6 transition-opacity duration-300 ${loadingSection ? 'opacity-0 pointer-events-none' : 'opacity-100'}`}
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
visibility: flatItems.length === 0 ? 'hidden' : 'visible'
}}
>
{rowVirtualizer.getVirtualItems().map((virtualItem) => {
const item = flatItems[virtualItem.index];

return (
<div
key={virtualItem.key}
data-index={virtualItem.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualItem.start}px)`,
}}
ref={(el) => {
if (el) {
// trigger remeasurement when content changes and load
requestAnimationFrame(() => {
handleSectionResize(virtualItem.index);
});
}
}}
>
{item.type === 'group' ? (
<div className="flex items-center py-6 my-4">
<div className="flex-1 h-0.5 bg-gray-200" />
<Tooltip title="Copy link to this group">
<div
className="px-4 text-md font-semibold text-gray-700 uppercase tracking-wide whitespace-nowrap cursor-pointer hover:text-blue-600 transition-colors"
onClick={() => handleCopyGroupLink(item.group.Name)}
>
{item.group.Name}
</div>
</Tooltip>
<div className="flex-1 h-0.5 bg-gray-200" />
</div>
) : (
<div className="text-sm">
<Section
entity={item.entity}
group={item.group}
onTabChange={(isChanging: boolean) => {
isTabSwitching.current = isChanging;
if (isChanging) {
// Reset after a short delay to allow for the content change
setTimeout(() => {
isTabSwitching.current = false;
}, 100);
}
}}
search={search}
/>
</div>
)}
</div>
);
})}
return (
<div
key={virtualItem.key}
data-index={virtualItem.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualItem.start}px)`,
}}
ref={(el) => {
if (el) {
// trigger remeasurement when content changes and load
requestAnimationFrame(() => {
handleSectionResize(virtualItem.index);
});
}
}}
>
{item.type === 'group' ? (
<div className="flex items-center py-6 my-4">
<div className="flex-1 h-0.5 bg-gray-200" />
<Tooltip title="Copy link to this group">
<div
className="px-4 text-md font-semibold text-gray-700 uppercase tracking-wide whitespace-nowrap cursor-pointer hover:text-blue-600 transition-colors"
onClick={() => handleCopyGroupLink(item.group.Name)}
>
{item.group.Name}
</div>
</Tooltip>
<div className="flex-1 h-0.5 bg-gray-200" />
</div>
) : (
<div className="text-sm">
<Section
entity={item.entity}
group={item.group}
onTabChange={() => {

}}
search={search}
/>
</div>
)}
</div>
);
})}
</div>
</div>
</div>
</>
);
};
3 changes: 2 additions & 1 deletion Website/components/datamodelview/Relationships.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ export const Relationships = ({ entity, onVisibleCountChange, search = "" }: IRe
color: 'primary.main'
}}
onClick={() => {
dispatch({ type: "SET_CURRENT_SECTION", payload: relationship.TableSchema })
dispatch({ type: 'SET_LOADING_SECTION', payload: relationship.TableSchema });
dispatch({ type: "SET_CURRENT_SECTION", payload: relationship.TableSchema });
scrollToSection(relationship.TableSchema);
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useIsMobile } from "@/hooks/use-mobile"
import { ChoiceAttributeType } from "@/lib/Types"
import { formatNumberSeperator } from "@/lib/utils"
import { Box, Typography, Chip } from "@mui/material"
import { CheckBoxOutlineBlankRounded, CheckBoxRounded, CheckRounded } from "@mui/icons-material"
import { CheckBoxOutlineBlankRounded, CheckBoxRounded, CheckRounded, RadioButtonCheckedRounded, RadioButtonUncheckedRounded } from "@mui/icons-material"

export default function ChoiceAttribute({ attribute, highlightMatch, highlightTerm }: { attribute: ChoiceAttributeType, highlightMatch: (text: string, term: string) => string | React.JSX.Element, highlightTerm: string }) {

Expand Down Expand Up @@ -38,9 +38,9 @@ export default function ChoiceAttribute({ attribute, highlightMatch, highlightTe
) : (
// For single-select, show radio buttons
option.Value === attribute.DefaultValue ? (
<CheckBoxRounded className="w-2 h-2 md:w-3 md:h-3" sx={{ color: 'success.main' }} />
<RadioButtonCheckedRounded className="w-2 h-2 md:w-3 md:h-3" sx={{ color: 'success.main' }} />
) : (
<CheckBoxOutlineBlankRounded className="w-2 h-2 md:w-3 md:h-3" sx={{ color: 'text.disabled' }} />
<RadioButtonUncheckedRounded className="w-2 h-2 md:w-3 md:h-3" sx={{ color: 'text.disabled' }} />
)
)}
<Typography className="text-xs md:text-sm">{highlightMatch(option.Name, highlightTerm)}</Typography>
Expand Down
Loading
Loading