From 74b73e128ec40c52319bd7a3018667f2e51b5ccf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 11:22:51 +0000 Subject: [PATCH 1/5] Initial plan From cf91adb21feac3aa41ba26f7b8514bedade5cd3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 11:42:01 +0000 Subject: [PATCH 2/5] Implement solution overview with Venn diagram visualization Co-authored-by: magesoe <8904582+magesoe@users.noreply.github.com> --- Generator/DTO/Solution.cs | 23 ++ Generator/DataverseService.cs | 132 +++++++++ Generator/Program.cs | 3 +- Generator/WebsiteBuilder.cs | 10 +- Website/app/solution-overview/page.tsx | 15 + Website/components/shared/SidebarNavRail.tsx | 10 +- .../ComponentDetailsPane.tsx | 130 ++++++++ .../SolutionOverviewView.tsx | 72 +++++ .../SolutionVennDiagram.tsx | 279 ++++++++++++++++++ Website/lib/Types.ts | 26 ++ Website/stubs/Data.ts | 120 +++++++- 11 files changed, 813 insertions(+), 7 deletions(-) create mode 100644 Generator/DTO/Solution.cs create mode 100644 Website/app/solution-overview/page.tsx create mode 100644 Website/components/solutionoverviewview/ComponentDetailsPane.tsx create mode 100644 Website/components/solutionoverviewview/SolutionOverviewView.tsx create mode 100644 Website/components/solutionoverviewview/SolutionVennDiagram.tsx diff --git a/Generator/DTO/Solution.cs b/Generator/DTO/Solution.cs new file mode 100644 index 0000000..cc1212a --- /dev/null +++ b/Generator/DTO/Solution.cs @@ -0,0 +1,23 @@ +namespace Generator.DTO; + +internal record Solution( + Guid SolutionId, + string UniqueName, + string DisplayName, + List Components); + +internal record SolutionComponent( + Guid ObjectId, + int ComponentType, + int RootComponentBehavior, + string? ComponentTypeName, + string? ComponentDisplayName); + +internal record SolutionOverview( + List Solutions, + List Overlaps); + +internal record ComponentOverlap( + List SolutionNames, + List SharedComponents, + int ComponentCount); \ No newline at end of file diff --git a/Generator/DataverseService.cs b/Generator/DataverseService.cs index ccdeca3..962ee0f 100644 --- a/Generator/DataverseService.cs +++ b/Generator/DataverseService.cs @@ -369,6 +369,138 @@ await Parallel.ForEachAsync( .ToList(); } + public async Task GetSolutionOverview() + { + var solutionNameArg = configuration["DataverseSolutionNames"]; + if (solutionNameArg == null) + { + throw new Exception("Specify one or more solutions"); + } + var solutionNames = solutionNameArg.Split(",").Select(x => x.Trim()).ToList(); + + // Get solution details with display names + var solutionQuery = new QueryExpression("solution") + { + ColumnSet = new ColumnSet("solutionid", "uniquename", "friendlyname"), + Criteria = new FilterExpression(LogicalOperator.And) + { + Conditions = + { + new ConditionExpression("uniquename", ConditionOperator.In, solutionNames.Select(x => x.ToLower()).ToList()) + } + } + }; + + var solutionEntities = (await client.RetrieveMultipleAsync(solutionQuery)).Entities; + var solutions = new List(); + + foreach (var solutionEntity in solutionEntities) + { + var solutionId = solutionEntity.GetAttributeValue("solutionid"); + var uniqueName = solutionEntity.GetAttributeValue("uniquename"); + var displayName = solutionEntity.GetAttributeValue("friendlyname"); + + // Get components for this specific solution + var componentQuery = new QueryExpression("solutioncomponent") + { + ColumnSet = new ColumnSet("objectid", "componenttype", "rootcomponentbehavior"), + Criteria = new FilterExpression(LogicalOperator.And) + { + Conditions = + { + new ConditionExpression("componenttype", ConditionOperator.In, new List() { 1, 2, 20, 92 }), + new ConditionExpression("solutionid", ConditionOperator.Equal, solutionId) + } + } + }; + + var componentEntities = (await client.RetrieveMultipleAsync(componentQuery)).Entities; + var components = componentEntities.Select(e => new DTO.SolutionComponent( + e.GetAttributeValue("objectid"), + e.GetAttributeValue("componenttype").Value, + e.Contains("rootcomponentbehavior") ? e.GetAttributeValue("rootcomponentbehavior").Value : -1, + GetComponentTypeName(e.GetAttributeValue("componenttype").Value), + null // Component display name will be filled later if needed + )).ToList(); + + solutions.Add(new DTO.Solution(solutionId, uniqueName, displayName, components)); + } + + // Calculate overlaps + var overlaps = CalculateComponentOverlaps(solutions); + + return new DTO.SolutionOverview(solutions, overlaps); + } + + private static string GetComponentTypeName(int componentType) + { + return componentType switch + { + 1 => "Entity", + 2 => "Attribute", + 20 => "Security Role", + 92 => "Plugin Step", + _ => "Unknown" + }; + } + + private static List CalculateComponentOverlaps(List solutions) + { + var overlaps = new List(); + + // Generate all possible combinations of solutions + for (int i = 1; i <= solutions.Count; i++) + { + var combinations = GetCombinations(solutions, i); + foreach (var combination in combinations) + { + var solutionNames = combination.Select(s => s.UniqueName).ToList(); + var sharedComponents = GetSharedComponents(combination); + + if (sharedComponents.Any()) + { + overlaps.Add(new DTO.ComponentOverlap( + solutionNames, + sharedComponents, + sharedComponents.Count + )); + } + } + } + + return overlaps; + } + + private static IEnumerable> GetCombinations(List list, int length) + { + if (length == 1) return list.Select(t => new List { t }); + + return GetCombinations(list, length - 1) + .SelectMany(t => list.Where(e => list.IndexOf(e) > list.IndexOf(t.Last())), + (t1, t2) => t1.Concat(new List { t2 }).ToList()); + } + + private static List GetSharedComponents(List solutions) + { + if (!solutions.Any()) return new List(); + + var firstSolutionComponents = solutions.First().Components; + var sharedComponents = new List(); + + foreach (var component in firstSolutionComponents) + { + var isSharedAcrossAll = solutions.Skip(1).All(s => + s.Components.Any(c => c.ObjectId == component.ObjectId && c.ComponentType == component.ComponentType)); + + if (isSharedAcrossAll) + { + sharedComponents.Add(component); + } + } + + return sharedComponents; + } + private async Task>> GetSecurityRoles(List rolesInSolution, Dictionary priviledges) { if (rolesInSolution.Count == 0) return []; diff --git a/Generator/Program.cs b/Generator/Program.cs index 59b8316..ed17b44 100644 --- a/Generator/Program.cs +++ b/Generator/Program.cs @@ -19,7 +19,8 @@ var dataverseService = new DataverseService(configuration, logger); var entities = (await dataverseService.GetFilteredMetadata()).ToList(); +var solutionOverview = await dataverseService.GetSolutionOverview(); -var websiteBuilder = new WebsiteBuilder(configuration, entities); +var websiteBuilder = new WebsiteBuilder(configuration, entities, solutionOverview); websiteBuilder.AddData(); diff --git a/Generator/WebsiteBuilder.cs b/Generator/WebsiteBuilder.cs index 6a612b5..10c128d 100644 --- a/Generator/WebsiteBuilder.cs +++ b/Generator/WebsiteBuilder.cs @@ -9,12 +9,14 @@ internal class WebsiteBuilder { private readonly IConfiguration configuration; private readonly List records; + private readonly DTO.SolutionOverview solutionOverview; private readonly string OutputFolder; - public WebsiteBuilder(IConfiguration configuration, List records) + public WebsiteBuilder(IConfiguration configuration, List records, DTO.SolutionOverview solutionOverview) { this.configuration = configuration; this.records = records; + this.solutionOverview = solutionOverview; // Assuming execution in bin/xxx/net8.0 OutputFolder = configuration["OutputFolder"] ?? Path.Combine(System.Reflection.Assembly.GetExecutingAssembly().Location, "../../../../../Website/generated"); @@ -23,7 +25,7 @@ public WebsiteBuilder(IConfiguration configuration, List records) internal void AddData() { var sb = new StringBuilder(); - sb.AppendLine("import { GroupType } from \"@/lib/Types\";"); + sb.AppendLine("import { GroupType, SolutionOverviewType } from \"@/lib/Types\";"); sb.AppendLine(""); sb.AppendLine($"export const LastSynched: Date = new Date('{DateTimeOffset.UtcNow:yyyy-MM-ddTHH:mm:ss.fffZ}');"); var logoUrl = configuration.GetValue("Logo", defaultValue: null); @@ -48,7 +50,9 @@ internal void AddData() sb.AppendLine(" },"); } - sb.AppendLine("]"); + sb.AppendLine("];"); + sb.AppendLine(""); + sb.AppendLine($"export const SolutionOverview: SolutionOverviewType = {JsonConvert.SerializeObject(solutionOverview)};"); File.WriteAllText(Path.Combine(OutputFolder, "Data.ts"), sb.ToString()); } diff --git a/Website/app/solution-overview/page.tsx b/Website/app/solution-overview/page.tsx new file mode 100644 index 0000000..4a6eb98 --- /dev/null +++ b/Website/app/solution-overview/page.tsx @@ -0,0 +1,15 @@ +import { SolutionOverviewView } from "@/components/solutionoverviewview/SolutionOverviewView"; +import { TouchProvider } from "@/components/shared/ui/hybridtooltop"; +import { Loading } from "@/components/shared/ui/loading"; +import { TooltipProvider } from "@/components/shared/ui/tooltip"; +import { Suspense } from "react"; + +export default function SolutionOverview() { + return }> + + + + + + +} \ No newline at end of file diff --git a/Website/components/shared/SidebarNavRail.tsx b/Website/components/shared/SidebarNavRail.tsx index c6125a2..5993bb9 100644 --- a/Website/components/shared/SidebarNavRail.tsx +++ b/Website/components/shared/SidebarNavRail.tsx @@ -1,6 +1,6 @@ import React from "react"; import { useRouter, usePathname } from "next/navigation"; -import { LogOut, Info, Database, PencilRuler, PlugZap, Sparkles, Home, ChartPie } from "lucide-react"; +import { LogOut, Info, Database, PencilRuler, PlugZap, Sparkles, Home, ChartPie, Target } from "lucide-react"; import { Button } from "@/components/shared/ui/button"; import { useSidebarDispatch } from "@/contexts/SidebarContext"; import { Tooltip, TooltipContent } from "./ui/tooltip"; @@ -15,6 +15,14 @@ const navItems = [ disabled: false, new: true, }, + { + label: "Solution Overview", + icon: , + href: "/solution-overview", + active: false, + disabled: false, + new: true, + }, { label: "Insight viewer", icon: , diff --git a/Website/components/solutionoverviewview/ComponentDetailsPane.tsx b/Website/components/solutionoverviewview/ComponentDetailsPane.tsx new file mode 100644 index 0000000..723b3aa --- /dev/null +++ b/Website/components/solutionoverviewview/ComponentDetailsPane.tsx @@ -0,0 +1,130 @@ +'use client' + +import React from 'react'; +import { SolutionComponentType } from '@/lib/Types'; + +interface IComponentDetailsPaneProps { + solutionNames: string[]; + components: SolutionComponentType[]; +} + +export const ComponentDetailsPane = ({ solutionNames, components }: IComponentDetailsPaneProps) => { + const groupedComponents = components.reduce((acc, component) => { + const type = component.ComponentTypeName || 'Unknown'; + if (!acc[type]) { + acc[type] = []; + } + acc[type].push(component); + return acc; + }, {} as Record); + + const getComponentTypeIcon = (componentType: string) => { + switch (componentType) { + case 'Entity': + return '🗂️'; + case 'Attribute': + return '📝'; + case 'Security Role': + return '🔐'; + case 'Plugin Step': + return '⚙️'; + default: + return '❓'; + } + }; + + const getComponentTypeColor = (componentType: string) => { + switch (componentType) { + case 'Entity': + return 'bg-blue-100 text-blue-800 border-blue-300'; + case 'Attribute': + return 'bg-green-100 text-green-800 border-green-300'; + case 'Security Role': + return 'bg-purple-100 text-purple-800 border-purple-300'; + case 'Plugin Step': + return 'bg-orange-100 text-orange-800 border-orange-300'; + default: + return 'bg-gray-100 text-gray-800 border-gray-300'; + } + }; + + return ( +
+ {/* Header with solution names */} +
+

+ {solutionNames.length === 1 ? 'Solution' : 'Solutions'} +

+
+ {solutionNames.map((name, index) => ( + + {name} + + ))} +
+
+ Total: {components.length} component{components.length !== 1 ? 's' : ''} +
+
+ + {/* Component types summary */} +
+ {Object.entries(groupedComponents).map(([type, comps]) => ( +
+
+ {getComponentTypeIcon(type)} + {type} +
+
{comps.length}
+
+ ))} +
+ + {/* Detailed component list */} +
+ {Object.entries(groupedComponents).map(([type, comps]) => ( +
+
+
+ {getComponentTypeIcon(type)} + {type} ({comps.length}) +
+
+
+ {comps.map((component, index) => ( +
+
+
+
+ {component.ComponentDisplayName || 'Unnamed Component'} +
+
+ ID: {component.ObjectId} +
+
+
+
+ Type: {component.ComponentType} +
+ {component.RootComponentBehavior !== -1 && ( +
+ Behavior: {component.RootComponentBehavior} +
+ )} +
+
+
+ ))} +
+
+ ))} +
+
+ ); +}; \ No newline at end of file diff --git a/Website/components/solutionoverviewview/SolutionOverviewView.tsx b/Website/components/solutionoverviewview/SolutionOverviewView.tsx new file mode 100644 index 0000000..a3d29c9 --- /dev/null +++ b/Website/components/solutionoverviewview/SolutionOverviewView.tsx @@ -0,0 +1,72 @@ +'use client' + +import React, { useEffect, useState } from 'react'; +import { AppSidebar } from '../shared/AppSidebar' +import { useSidebarDispatch } from '@/contexts/SidebarContext' +import { SolutionOverview } from '@/generated/Data' +import { SolutionComponentType } from '@/lib/Types' +import { SolutionVennDiagram } from './SolutionVennDiagram' +import { ComponentDetailsPane } from './ComponentDetailsPane' + +interface ISolutionOverviewViewProps { } + +export const SolutionOverviewView = ({}: ISolutionOverviewViewProps) => { + const dispatch = useSidebarDispatch(); + const [selectedOverlap, setSelectedOverlap] = useState<{ + solutionNames: string[]; + components: SolutionComponentType[]; + } | null>(null); + + useEffect(() => { + dispatch({ type: 'SET_ELEMENT', payload: <> }); + dispatch({ type: 'SET_SHOW_ELEMENT', payload: false }); + }, [dispatch]); + + const handleOverlapClick = (solutionNames: string[], components: SolutionComponentType[]) => { + setSelectedOverlap({ solutionNames, components }); + }; + + return ( +
+ + +
+
+

Solution Overview

+
+ +
+ {/* Venn Diagram Section */} +
+

Solution Component Overlaps

+ {SolutionOverview && SolutionOverview.Solutions.length > 0 ? ( + + ) : ( +
+ No solution data available. Please run the generator with multiple solutions configured. +
+ )} +
+ + {/* Component Details Section */} +
+

Component Details

+ {selectedOverlap ? ( + + ) : ( +
+ Click on a section in the diagram to view component details. +
+ )} +
+
+
+
+ ) +} \ No newline at end of file diff --git a/Website/components/solutionoverviewview/SolutionVennDiagram.tsx b/Website/components/solutionoverviewview/SolutionVennDiagram.tsx new file mode 100644 index 0000000..def498f --- /dev/null +++ b/Website/components/solutionoverviewview/SolutionVennDiagram.tsx @@ -0,0 +1,279 @@ +'use client' + +import React from 'react'; +import { SolutionOverviewType, SolutionComponentType } from '@/lib/Types'; + +interface ISolutionVennDiagramProps { + solutionOverview: SolutionOverviewType; + onOverlapClick: (solutionNames: string[], components: SolutionComponentType[]) => void; +} + +export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolutionVennDiagramProps) => { + const solutions = solutionOverview.Solutions; + const overlaps = solutionOverview.Overlaps; + + // Color palette for different sections + const colors = [ + '#3B82F6', // blue + '#EF4444', // red + '#10B981', // green + '#F59E0B', // yellow + '#8B5CF6', // purple + '#F97316', // orange + '#06B6D4', // cyan + '#EC4899', // pink + ]; + + // For simplicity, we'll render based on the number of solutions + if (solutions.length === 1) { + return renderSingleSolution(); + } else if (solutions.length === 2) { + return renderTwoSolutions(); + } else if (solutions.length === 3) { + return renderThreeSolutions(); + } else { + return renderMultipleSolutions(); + } + + function renderSingleSolution() { + const solution = solutions[0]; + const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution.UniqueName); + + return ( +
+ + overlap && onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} + /> + + {solution.DisplayName} + + + {overlap?.ComponentCount || 0} components + + +
+ ); + } + + function renderTwoSolutions() { + const [solution1, solution2] = solutions; + const overlap1 = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution1.UniqueName); + const overlap2 = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution2.UniqueName); + const overlapBoth = overlaps.find(o => o.SolutionNames.length === 2); + + return ( +
+ + {/* Solution 1 Circle */} + overlap1 && onOverlapClick(overlap1.SolutionNames, overlap1.SharedComponents)} + /> + + {/* Solution 2 Circle */} + overlap2 && onOverlapClick(overlap2.SolutionNames, overlap2.SharedComponents)} + /> + + {/* Overlap area - invisible clickable region */} + overlapBoth && onOverlapClick(overlapBoth.SolutionNames, overlapBoth.SharedComponents)} + /> + + {/* Labels */} + + {solution1.DisplayName} + + + {overlap1?.ComponentCount || 0} + + + + {solution2.DisplayName} + + + {overlap2?.ComponentCount || 0} + + + + {overlapBoth?.ComponentCount || 0} + + + shared + + +
+ ); + } + + function renderThreeSolutions() { + const [solution1, solution2, solution3] = solutions; + + return ( +
+ + {/* Solution 1 Circle (top) */} + { + const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution1.UniqueName); + if (overlap) onOverlapClick(overlap.SolutionNames, overlap.SharedComponents); + }} + /> + + {/* Solution 2 Circle (bottom left) */} + { + const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution2.UniqueName); + if (overlap) onOverlapClick(overlap.SolutionNames, overlap.SharedComponents); + }} + /> + + {/* Solution 3 Circle (bottom right) */} + { + const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution3.UniqueName); + if (overlap) onOverlapClick(overlap.SolutionNames, overlap.SharedComponents); + }} + /> + + {/* Clickable regions for overlaps */} + {overlaps.map((overlap, index) => { + if (overlap.SolutionNames.length > 1) { + const centerX = overlap.SolutionNames.length === 2 ? + (overlap.SolutionNames.includes(solution1.UniqueName) && overlap.SolutionNames.includes(solution2.UniqueName) ? 175 : + overlap.SolutionNames.includes(solution1.UniqueName) && overlap.SolutionNames.includes(solution3.UniqueName) ? 225 : 200) : + 200; + const centerY = overlap.SolutionNames.length === 2 ? + (overlap.SolutionNames.includes(solution2.UniqueName) && overlap.SolutionNames.includes(solution3.UniqueName) ? 200 : 150) : + 150; + + return ( + onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} + /> + ); + } + return null; + })} + + {/* Labels */} + + {solution1.DisplayName} + + + {solution2.DisplayName} + + + {solution3.DisplayName} + + +
+ ); + } + + function renderMultipleSolutions() { + return ( +
+
+

Multiple Solutions Overview

+
+ {solutions.map((solution, index) => { + const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution.UniqueName); + return ( +
overlap && onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} + > +
{solution.DisplayName}
+
{overlap?.ComponentCount || 0} components
+
+ ); + })} +
+ + {overlaps.filter(o => o.SolutionNames.length > 1).length > 0 && ( +
+

Shared Components

+
+ {overlaps.filter(o => o.SolutionNames.length > 1).map((overlap, index) => ( +
onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} + > +
{overlap.SolutionNames.join(' + ')}
+
{overlap.ComponentCount} shared components
+
+ ))} +
+
+ )} +
+
+ ); + } + + return null; +}; \ No newline at end of file diff --git a/Website/lib/Types.ts b/Website/lib/Types.ts index 2b27869..82975dc 100644 --- a/Website/lib/Types.ts +++ b/Website/lib/Types.ts @@ -199,4 +199,30 @@ export type Key = { Name: string, LogicalName: string, KeyAttributes: string[] +} + +export type SolutionType = { + SolutionId: string, + UniqueName: string, + DisplayName: string, + Components: SolutionComponentType[] +} + +export type SolutionComponentType = { + ObjectId: string, + ComponentType: number, + RootComponentBehavior: number, + ComponentTypeName: string | null, + ComponentDisplayName: string | null +} + +export type ComponentOverlapType = { + SolutionNames: string[], + SharedComponents: SolutionComponentType[], + ComponentCount: number +} + +export type SolutionOverviewType = { + Solutions: SolutionType[], + Overlaps: ComponentOverlapType[] } \ No newline at end of file diff --git a/Website/stubs/Data.ts b/Website/stubs/Data.ts index 23226f5..91ce962 100644 --- a/Website/stubs/Data.ts +++ b/Website/stubs/Data.ts @@ -1,7 +1,7 @@ /// Used in github workflow to generate stubs for data /// This file is a stub and should not be modified directly. -import { GroupType } from "@/lib/Types"; +import { GroupType, SolutionOverviewType } from "@/lib/Types"; export const LastSynched: Date = new Date(); export const Logo: string | null = null; @@ -83,4 +83,120 @@ export let Groups: GroupType[] = [ } ] } -]; \ No newline at end of file +]; + +export const SolutionOverview: SolutionOverviewType = { + "Solutions": [ + { + "SolutionId": "11111111-1111-1111-1111-111111111111", + "UniqueName": "SampleSolution1", + "DisplayName": "Sample Solution 1", + "Components": [ + { + "ObjectId": "22222222-2222-2222-2222-222222222222", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Account" + }, + { + "ObjectId": "33333333-3333-3333-3333-333333333333", + "ComponentType": 2, + "RootComponentBehavior": 0, + "ComponentTypeName": "Attribute", + "ComponentDisplayName": "Account Name" + }, + { + "ObjectId": "44444444-4444-4444-4444-444444444444", + "ComponentType": 20, + "RootComponentBehavior": 0, + "ComponentTypeName": "Security Role", + "ComponentDisplayName": "Sales Manager" + } + ] + }, + { + "SolutionId": "55555555-5555-5555-5555-555555555555", + "UniqueName": "SampleSolution2", + "DisplayName": "Sample Solution 2", + "Components": [ + { + "ObjectId": "22222222-2222-2222-2222-222222222222", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Account" + }, + { + "ObjectId": "66666666-6666-6666-6666-666666666666", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Contact" + }, + { + "ObjectId": "77777777-7777-7777-7777-777777777777", + "ComponentType": 92, + "RootComponentBehavior": 0, + "ComponentTypeName": "Plugin Step", + "ComponentDisplayName": "Validate Contact" + } + ] + } + ], + "Overlaps": [ + { + "SolutionNames": ["SampleSolution1"], + "SharedComponents": [ + { + "ObjectId": "33333333-3333-3333-3333-333333333333", + "ComponentType": 2, + "RootComponentBehavior": 0, + "ComponentTypeName": "Attribute", + "ComponentDisplayName": "Account Name" + }, + { + "ObjectId": "44444444-4444-4444-4444-444444444444", + "ComponentType": 20, + "RootComponentBehavior": 0, + "ComponentTypeName": "Security Role", + "ComponentDisplayName": "Sales Manager" + } + ], + "ComponentCount": 2 + }, + { + "SolutionNames": ["SampleSolution2"], + "SharedComponents": [ + { + "ObjectId": "66666666-6666-6666-6666-666666666666", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Contact" + }, + { + "ObjectId": "77777777-7777-7777-7777-777777777777", + "ComponentType": 92, + "RootComponentBehavior": 0, + "ComponentTypeName": "Plugin Step", + "ComponentDisplayName": "Validate Contact" + } + ], + "ComponentCount": 2 + }, + { + "SolutionNames": ["SampleSolution1", "SampleSolution2"], + "SharedComponents": [ + { + "ObjectId": "22222222-2222-2222-2222-222222222222", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Account" + } + ], + "ComponentCount": 1 + } + ] +}; \ No newline at end of file From baf8e4a8b5d1bfa96ca81dfbd25ea1baa308902f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:01:05 +0000 Subject: [PATCH 3/5] Convert Component Details to flyout panel for better readability Co-authored-by: magesoe <8904582+magesoe@users.noreply.github.com> --- .../SolutionOverviewView.tsx | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/Website/components/solutionoverviewview/SolutionOverviewView.tsx b/Website/components/solutionoverviewview/SolutionOverviewView.tsx index a3d29c9..f25e517 100644 --- a/Website/components/solutionoverviewview/SolutionOverviewView.tsx +++ b/Website/components/solutionoverviewview/SolutionOverviewView.tsx @@ -7,6 +7,7 @@ import { SolutionOverview } from '@/generated/Data' import { SolutionComponentType } from '@/lib/Types' import { SolutionVennDiagram } from './SolutionVennDiagram' import { ComponentDetailsPane } from './ComponentDetailsPane' +import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/shared/ui/sheet' interface ISolutionOverviewViewProps { } @@ -16,6 +17,7 @@ export const SolutionOverviewView = ({}: ISolutionOverviewViewProps) => { solutionNames: string[]; components: SolutionComponentType[]; } | null>(null); + const [isDetailsPaneOpen, setIsDetailsPaneOpen] = useState(false); useEffect(() => { dispatch({ type: 'SET_ELEMENT', payload: <> }); @@ -24,6 +26,7 @@ export const SolutionOverviewView = ({}: ISolutionOverviewViewProps) => { const handleOverlapClick = (solutionNames: string[], components: SolutionComponentType[]) => { setSelectedOverlap({ solutionNames, components }); + setIsDetailsPaneOpen(true); }; return ( @@ -35,37 +38,44 @@ export const SolutionOverviewView = ({}: ISolutionOverviewViewProps) => {

Solution Overview

-
- {/* Venn Diagram Section */} -
-

Solution Component Overlaps

- {SolutionOverview && SolutionOverview.Solutions.length > 0 ? ( - - ) : ( -
- No solution data available. Please run the generator with multiple solutions configured. -
- )} -
+ {/* Full width Venn Diagram Section */} +
+

Solution Component Overlaps

+ {SolutionOverview && SolutionOverview.Solutions.length > 0 ? ( + + ) : ( +
+ No solution data available. Please run the generator with multiple solutions configured. +
+ )} + + {SolutionOverview && SolutionOverview.Solutions.length > 0 && ( +
+ Click on any section of the diagram to view detailed component information +
+ )} +
- {/* Component Details Section */} -
-

Component Details

- {selectedOverlap ? ( - - ) : ( -
- Click on a section in the diagram to view component details. + {/* Component Details Flyout */} + + + + Component Details + + + {selectedOverlap && ( +
+
)} -
-
+ +
) From 7c1ffa88f11a65952492caa30ebff67ca3b71721 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:16:16 +0000 Subject: [PATCH 4/5] Improve Venn diagram readability with larger circles and component type details Co-authored-by: magesoe <8904582+magesoe@users.noreply.github.com> --- .../SolutionVennDiagram.tsx | 235 ++++++++++++------ 1 file changed, 164 insertions(+), 71 deletions(-) diff --git a/Website/components/solutionoverviewview/SolutionVennDiagram.tsx b/Website/components/solutionoverviewview/SolutionVennDiagram.tsx index def498f..cb30321 100644 --- a/Website/components/solutionoverviewview/SolutionVennDiagram.tsx +++ b/Website/components/solutionoverviewview/SolutionVennDiagram.tsx @@ -24,6 +24,19 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut '#EC4899', // pink ]; + // Helper function to get component type summary + const getComponentTypeSummary = (components: SolutionComponentType[]) => { + const typeCounts = components.reduce((acc, component) => { + const type = component.ComponentTypeName || 'Unknown'; + acc[type] = (acc[type] || 0) + 1; + return acc; + }, {} as Record); + + return Object.entries(typeCounts) + .map(([type, count]) => `${count} ${type}${count > 1 ? 's' : ''}`) + .join(', '); + }; + // For simplicity, we'll render based on the number of solutions if (solutions.length === 1) { return renderSingleSolution(); @@ -38,27 +51,33 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut function renderSingleSolution() { const solution = solutions[0]; const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution.UniqueName); + const typeSummary = overlap ? getComponentTypeSummary(overlap.SharedComponents) : ''; return ( -
- +
+ overlap && onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} /> - + {solution.DisplayName} - + {overlap?.ComponentCount || 0} components + {typeSummary && ( + + {typeSummary} + + )}
); @@ -70,67 +89,88 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut const overlap2 = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution2.UniqueName); const overlapBoth = overlaps.find(o => o.SolutionNames.length === 2); + const typeSummary1 = overlap1 ? getComponentTypeSummary(overlap1.SharedComponents) : ''; + const typeSummary2 = overlap2 ? getComponentTypeSummary(overlap2.SharedComponents) : ''; + const typeSummaryBoth = overlapBoth ? getComponentTypeSummary(overlapBoth.SharedComponents) : ''; + return ( -
- +
+ {/* Solution 1 Circle */} overlap1 && onOverlapClick(overlap1.SolutionNames, overlap1.SharedComponents)} /> {/* Solution 2 Circle */} overlap2 && onOverlapClick(overlap2.SolutionNames, overlap2.SharedComponents)} /> {/* Overlap area - invisible clickable region */} overlapBoth && onOverlapClick(overlapBoth.SolutionNames, overlapBoth.SharedComponents)} /> - {/* Labels */} - + {/* Solution 1 Labels */} + {solution1.DisplayName} - - {overlap1?.ComponentCount || 0} + + {overlap1?.ComponentCount || 0} components + {typeSummary1 && ( + + {typeSummary1} + + )} - + {/* Solution 2 Labels */} + {solution2.DisplayName} - - {overlap2?.ComponentCount || 0} + + {overlap2?.ComponentCount || 0} components + {typeSummary2 && ( + + {typeSummary2} + + )} - + {/* Overlap Labels */} + {overlapBoth?.ComponentCount || 0} - + shared + {typeSummaryBoth && ( + + {typeSummaryBoth} + + )}
); @@ -140,17 +180,17 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut const [solution1, solution2, solution3] = solutions; return ( -
- +
+ {/* Solution 1 Circle (top) */} { const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution1.UniqueName); @@ -160,13 +200,13 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut {/* Solution 2 Circle (bottom left) */} { const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution2.UniqueName); @@ -176,13 +216,13 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut {/* Solution 3 Circle (bottom right) */} { const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution3.UniqueName); @@ -194,19 +234,19 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut {overlaps.map((overlap, index) => { if (overlap.SolutionNames.length > 1) { const centerX = overlap.SolutionNames.length === 2 ? - (overlap.SolutionNames.includes(solution1.UniqueName) && overlap.SolutionNames.includes(solution2.UniqueName) ? 175 : - overlap.SolutionNames.includes(solution1.UniqueName) && overlap.SolutionNames.includes(solution3.UniqueName) ? 225 : 200) : - 200; + (overlap.SolutionNames.includes(solution1.UniqueName) && overlap.SolutionNames.includes(solution2.UniqueName) ? 215 : + overlap.SolutionNames.includes(solution1.UniqueName) && overlap.SolutionNames.includes(solution3.UniqueName) ? 285 : 250) : + 250; const centerY = overlap.SolutionNames.length === 2 ? - (overlap.SolutionNames.includes(solution2.UniqueName) && overlap.SolutionNames.includes(solution3.UniqueName) ? 200 : 150) : - 150; + (overlap.SolutionNames.includes(solution2.UniqueName) && overlap.SolutionNames.includes(solution3.UniqueName) ? 250 : 190) : + 200; return ( onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} @@ -217,15 +257,58 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut })} {/* Labels */} - + {solution1.DisplayName} - + + {overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution1.UniqueName)?.ComponentCount || 0} components + + + {solution2.DisplayName} - + + {overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution2.UniqueName)?.ComponentCount || 0} components + + + {solution3.DisplayName} + + {overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution3.UniqueName)?.ComponentCount || 0} components + + + {/* Overlap count labels */} + {overlaps.map((overlap, index) => { + if (overlap.SolutionNames.length > 1) { + const centerX = overlap.SolutionNames.length === 2 ? + (overlap.SolutionNames.includes(solution1.UniqueName) && overlap.SolutionNames.includes(solution2.UniqueName) ? 215 : + overlap.SolutionNames.includes(solution1.UniqueName) && overlap.SolutionNames.includes(solution3.UniqueName) ? 285 : 250) : + 250; + const centerY = overlap.SolutionNames.length === 2 ? + (overlap.SolutionNames.includes(solution2.UniqueName) && overlap.SolutionNames.includes(solution3.UniqueName) ? 250 : 190) : + 200; + + const typeSummary = getComponentTypeSummary(overlap.SharedComponents); + + return ( + + + {overlap.ComponentCount} + + + shared + + {typeSummary && ( + + {typeSummary} + + )} + + ); + } + return null; + })}
); @@ -233,12 +316,13 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut function renderMultipleSolutions() { return ( -
+
-

Multiple Solutions Overview

+

Multiple Solutions Overview

{solutions.map((solution, index) => { const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution.UniqueName); + const typeSummary = overlap ? getComponentTypeSummary(overlap.SharedComponents) : ''; return (
overlap && onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} > -
{solution.DisplayName}
-
{overlap?.ComponentCount || 0} components
+
{solution.DisplayName}
+
{overlap?.ComponentCount || 0} components
+ {typeSummary && ( +
{typeSummary}
+ )}
); })}
{overlaps.filter(o => o.SolutionNames.length > 1).length > 0 && ( -
-

Shared Components

-
- {overlaps.filter(o => o.SolutionNames.length > 1).map((overlap, index) => ( -
onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} - > -
{overlap.SolutionNames.join(' + ')}
-
{overlap.ComponentCount} shared components
-
- ))} +
+

Shared Components

+
+ {overlaps.filter(o => o.SolutionNames.length > 1).map((overlap, index) => { + const typeSummary = getComponentTypeSummary(overlap.SharedComponents); + return ( +
onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} + > +
{overlap.SolutionNames.join(' + ')}
+
{overlap.ComponentCount} shared components
+ {typeSummary && ( +
{typeSummary}
+ )} +
+ ); + })}
)} From 54add8bec5f700afa7cdab6e4c0e393d5f7b1c93 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:21:31 +0000 Subject: [PATCH 5/5] Improve Venn diagram readability: vertical component types, remove 'shared' text, add 4-solution test Co-authored-by: magesoe <8904582+magesoe@users.noreply.github.com> --- .../SolutionVennDiagram.tsx | 186 ++++++++---- Website/stubs/Data.ts | 264 +++++++++++++++++- 2 files changed, 386 insertions(+), 64 deletions(-) diff --git a/Website/components/solutionoverviewview/SolutionVennDiagram.tsx b/Website/components/solutionoverviewview/SolutionVennDiagram.tsx index cb30321..47dca20 100644 --- a/Website/components/solutionoverviewview/SolutionVennDiagram.tsx +++ b/Website/components/solutionoverviewview/SolutionVennDiagram.tsx @@ -24,7 +24,7 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut '#EC4899', // pink ]; - // Helper function to get component type summary + // Helper function to get component type summary as array for vertical display const getComponentTypeSummary = (components: SolutionComponentType[]) => { const typeCounts = components.reduce((acc, component) => { const type = component.ComponentTypeName || 'Unknown'; @@ -33,8 +33,7 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut }, {} as Record); return Object.entries(typeCounts) - .map(([type, count]) => `${count} ${type}${count > 1 ? 's' : ''}`) - .join(', '); + .map(([type, count]) => `${count} ${type}${count > 1 ? 's' : ''}`); }; // For simplicity, we'll render based on the number of solutions @@ -51,7 +50,7 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut function renderSingleSolution() { const solution = solutions[0]; const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution.UniqueName); - const typeSummary = overlap ? getComponentTypeSummary(overlap.SharedComponents) : ''; + const typeSummary = overlap ? getComponentTypeSummary(overlap.SharedComponents) : []; return (
@@ -67,17 +66,24 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut className="cursor-pointer hover:fillOpacity-0.8" onClick={() => overlap && onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} /> - + {solution.DisplayName} - + {overlap?.ComponentCount || 0} components - {typeSummary && ( - - {typeSummary} + {typeSummary.map((typeText, index) => ( + + {typeText} - )} + ))}
); @@ -89,9 +95,9 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut const overlap2 = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution2.UniqueName); const overlapBoth = overlaps.find(o => o.SolutionNames.length === 2); - const typeSummary1 = overlap1 ? getComponentTypeSummary(overlap1.SharedComponents) : ''; - const typeSummary2 = overlap2 ? getComponentTypeSummary(overlap2.SharedComponents) : ''; - const typeSummaryBoth = overlapBoth ? getComponentTypeSummary(overlapBoth.SharedComponents) : ''; + const typeSummary1 = overlap1 ? getComponentTypeSummary(overlap1.SharedComponents) : []; + const typeSummary2 = overlap2 ? getComponentTypeSummary(overlap2.SharedComponents) : []; + const typeSummaryBoth = overlapBoth ? getComponentTypeSummary(overlapBoth.SharedComponents) : []; return (
@@ -134,43 +140,61 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut /> {/* Solution 1 Labels */} - + {solution1.DisplayName} - + {overlap1?.ComponentCount || 0} components - {typeSummary1 && ( - - {typeSummary1} + {typeSummary1.map((typeText, index) => ( + + {typeText} - )} + ))} {/* Solution 2 Labels */} - + {solution2.DisplayName} - + {overlap2?.ComponentCount || 0} components - {typeSummary2 && ( - - {typeSummary2} + {typeSummary2.map((typeText, index) => ( + + {typeText} - )} + ))} {/* Overlap Labels */} - + {overlapBoth?.ComponentCount || 0} - - shared - - {typeSummaryBoth && ( - - {typeSummaryBoth} + {typeSummaryBoth.map((typeText, index) => ( + + {typeText} - )} + ))}
); @@ -257,26 +281,74 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut })} {/* Labels */} - + {solution1.DisplayName} - + {overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution1.UniqueName)?.ComponentCount || 0} components + {(() => { + const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution1.UniqueName); + const typeSummary = overlap ? getComponentTypeSummary(overlap.SharedComponents) : []; + return typeSummary.map((typeText, index) => ( + + {typeText} + + )); + })()} - + {solution2.DisplayName} - + {overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution2.UniqueName)?.ComponentCount || 0} components + {(() => { + const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution2.UniqueName); + const typeSummary = overlap ? getComponentTypeSummary(overlap.SharedComponents) : []; + return typeSummary.map((typeText, index) => ( + + {typeText} + + )); + })()} - + {solution3.DisplayName} - + {overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution3.UniqueName)?.ComponentCount || 0} components + {(() => { + const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution3.UniqueName); + const typeSummary = overlap ? getComponentTypeSummary(overlap.SharedComponents) : []; + return typeSummary.map((typeText, index) => ( + + {typeText} + + )); + })()} {/* Overlap count labels */} {overlaps.map((overlap, index) => { @@ -293,17 +365,21 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut return ( - + {overlap.ComponentCount} - - shared - - {typeSummary && ( - - {typeSummary} + {typeSummary.map((typeText, typeIndex) => ( + + {typeText} - )} + ))} ); } @@ -322,7 +398,7 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut
{solutions.map((solution, index) => { const overlap = overlaps.find(o => o.SolutionNames.length === 1 && o.SolutionNames[0] === solution.UniqueName); - const typeSummary = overlap ? getComponentTypeSummary(overlap.SharedComponents) : ''; + const typeSummary = overlap ? getComponentTypeSummary(overlap.SharedComponents) : []; return (
{solution.DisplayName}
{overlap?.ComponentCount || 0} components
- {typeSummary && ( -
{typeSummary}
- )} + {typeSummary.map((typeText, typeIndex) => ( +
{typeText}
+ ))}
); })} @@ -353,10 +429,10 @@ export const SolutionVennDiagram = ({ solutionOverview, onOverlapClick }: ISolut onClick={() => onOverlapClick(overlap.SolutionNames, overlap.SharedComponents)} >
{overlap.SolutionNames.join(' + ')}
-
{overlap.ComponentCount} shared components
- {typeSummary && ( -
{typeSummary}
- )} +
{overlap.ComponentCount} components
+ {typeSummary.map((typeText, typeIndex) => ( +
{typeText}
+ ))}
); })} diff --git a/Website/stubs/Data.ts b/Website/stubs/Data.ts index 91ce962..a522bbf 100644 --- a/Website/stubs/Data.ts +++ b/Website/stubs/Data.ts @@ -90,7 +90,7 @@ export const SolutionOverview: SolutionOverviewType = { { "SolutionId": "11111111-1111-1111-1111-111111111111", "UniqueName": "SampleSolution1", - "DisplayName": "Sample Solution 1", + "DisplayName": "Core Platform", "Components": [ { "ObjectId": "22222222-2222-2222-2222-222222222222", @@ -112,13 +112,27 @@ export const SolutionOverview: SolutionOverviewType = { "RootComponentBehavior": 0, "ComponentTypeName": "Security Role", "ComponentDisplayName": "Sales Manager" + }, + { + "ObjectId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Opportunity" + }, + { + "ObjectId": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + "ComponentType": 2, + "RootComponentBehavior": 0, + "ComponentTypeName": "Attribute", + "ComponentDisplayName": "Opportunity Name" } ] }, { "SolutionId": "55555555-5555-5555-5555-555555555555", "UniqueName": "SampleSolution2", - "DisplayName": "Sample Solution 2", + "DisplayName": "Sales Module", "Components": [ { "ObjectId": "22222222-2222-2222-2222-222222222222", @@ -140,6 +154,97 @@ export const SolutionOverview: SolutionOverviewType = { "RootComponentBehavior": 0, "ComponentTypeName": "Plugin Step", "ComponentDisplayName": "Validate Contact" + }, + { + "ObjectId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Opportunity" + }, + { + "ObjectId": "cccccccc-cccc-cccc-cccc-cccccccccccc", + "ComponentType": 20, + "RootComponentBehavior": 0, + "ComponentTypeName": "Security Role", + "ComponentDisplayName": "Sales Rep" + }, + { + "ObjectId": "dddddddd-dddd-dddd-dddd-dddddddddddd", + "ComponentType": 92, + "RootComponentBehavior": 0, + "ComponentTypeName": "Plugin Step", + "ComponentDisplayName": "Calculate Sales Commission" + } + ] + }, + { + "SolutionId": "88888888-8888-8888-8888-888888888888", + "UniqueName": "SampleSolution3", + "DisplayName": "Customer Service", + "Components": [ + { + "ObjectId": "22222222-2222-2222-2222-222222222222", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Account" + }, + { + "ObjectId": "66666666-6666-6666-6666-666666666666", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Contact" + }, + { + "ObjectId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Opportunity" + }, + { + "ObjectId": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Case" + }, + { + "ObjectId": "ffffffff-ffff-ffff-ffff-ffffffffffff", + "ComponentType": 20, + "RootComponentBehavior": 0, + "ComponentTypeName": "Security Role", + "ComponentDisplayName": "Support Agent" + } + ] + }, + { + "SolutionId": "99999999-9999-9999-9999-999999999999", + "UniqueName": "SampleSolution4", + "DisplayName": "Reporting Module", + "Components": [ + { + "ObjectId": "66666666-6666-6666-6666-666666666666", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Contact" + }, + { + "ObjectId": "10101010-1010-1010-1010-101010101010", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Report" + }, + { + "ObjectId": "11111110-1111-1111-1111-111111111111", + "ComponentType": 2, + "RootComponentBehavior": 0, + "ComponentTypeName": "Attribute", + "ComponentDisplayName": "Report Title" } ] } @@ -161,26 +266,80 @@ export const SolutionOverview: SolutionOverviewType = { "RootComponentBehavior": 0, "ComponentTypeName": "Security Role", "ComponentDisplayName": "Sales Manager" + }, + { + "ObjectId": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + "ComponentType": 2, + "RootComponentBehavior": 0, + "ComponentTypeName": "Attribute", + "ComponentDisplayName": "Opportunity Name" } ], - "ComponentCount": 2 + "ComponentCount": 3 }, { "SolutionNames": ["SampleSolution2"], "SharedComponents": [ { - "ObjectId": "66666666-6666-6666-6666-666666666666", - "ComponentType": 1, + "ObjectId": "77777777-7777-7777-7777-777777777777", + "ComponentType": 92, "RootComponentBehavior": 0, - "ComponentTypeName": "Entity", - "ComponentDisplayName": "Contact" + "ComponentTypeName": "Plugin Step", + "ComponentDisplayName": "Validate Contact" }, { - "ObjectId": "77777777-7777-7777-7777-777777777777", + "ObjectId": "cccccccc-cccc-cccc-cccc-cccccccccccc", + "ComponentType": 20, + "RootComponentBehavior": 0, + "ComponentTypeName": "Security Role", + "ComponentDisplayName": "Sales Rep" + }, + { + "ObjectId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "ComponentType": 92, "RootComponentBehavior": 0, "ComponentTypeName": "Plugin Step", - "ComponentDisplayName": "Validate Contact" + "ComponentDisplayName": "Calculate Sales Commission" + } + ], + "ComponentCount": 3 + }, + { + "SolutionNames": ["SampleSolution3"], + "SharedComponents": [ + { + "ObjectId": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Case" + }, + { + "ObjectId": "ffffffff-ffff-ffff-ffff-ffffffffffff", + "ComponentType": 20, + "RootComponentBehavior": 0, + "ComponentTypeName": "Security Role", + "ComponentDisplayName": "Support Agent" + } + ], + "ComponentCount": 2 + }, + { + "SolutionNames": ["SampleSolution4"], + "SharedComponents": [ + { + "ObjectId": "10101010-1010-1010-1010-101010101010", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Report" + }, + { + "ObjectId": "11111110-1111-1111-1111-111111111111", + "ComponentType": 2, + "RootComponentBehavior": 0, + "ComponentTypeName": "Attribute", + "ComponentDisplayName": "Report Title" } ], "ComponentCount": 2 @@ -194,9 +353,96 @@ export const SolutionOverview: SolutionOverviewType = { "RootComponentBehavior": 0, "ComponentTypeName": "Entity", "ComponentDisplayName": "Account" + }, + { + "ObjectId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Opportunity" + } + ], + "ComponentCount": 2 + }, + { + "SolutionNames": ["SampleSolution1", "SampleSolution3"], + "SharedComponents": [ + { + "ObjectId": "22222222-2222-2222-2222-222222222222", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Account" + }, + { + "ObjectId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Opportunity" + } + ], + "ComponentCount": 2 + }, + { + "SolutionNames": ["SampleSolution2", "SampleSolution3"], + "SharedComponents": [ + { + "ObjectId": "22222222-2222-2222-2222-222222222222", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Account" + }, + { + "ObjectId": "66666666-6666-6666-6666-666666666666", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Contact" + }, + { + "ObjectId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Opportunity" + } + ], + "ComponentCount": 3 + }, + { + "SolutionNames": ["SampleSolution2", "SampleSolution4"], + "SharedComponents": [ + { + "ObjectId": "66666666-6666-6666-6666-666666666666", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Contact" } ], "ComponentCount": 1 + }, + { + "SolutionNames": ["SampleSolution1", "SampleSolution2", "SampleSolution3"], + "SharedComponents": [ + { + "ObjectId": "22222222-2222-2222-2222-222222222222", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Account" + }, + { + "ObjectId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "ComponentType": 1, + "RootComponentBehavior": 0, + "ComponentTypeName": "Entity", + "ComponentDisplayName": "Opportunity" + } + ], + "ComponentCount": 2 } ] }; \ No newline at end of file