import type { Integration } from "@executor-js/sdk/shared"; // --------------------------------------------------------------------------- // Integration grid grouping — a provider whose plugin fans out into several // per-service integrations (Google → Calendar, Gmail, Drive; Microsoft → Mail, // Calendar, Teams) can leave the catalog with 6-12 flat sibling rows. Those // families collapse under a single provider umbrella; every other integration // stays exactly where it was. // --------------------------------------------------------------------------- /** Plugin kinds whose integrations are collapsed under a provider umbrella. * These are the multi-service provider plugins: one plugin owns many * per-service integrations that share a provider identity. */ export const MULTI_SERVICE_FAMILIES: ReadonlySet = new Set(["google", "microsoft"]); /** Human label for a family umbrella, keyed by plugin kind. Falls back to a * title-cased kind so a new family is never unlabeled. */ const FAMILY_LABELS: Record = { google: "Google", microsoft: "Microsoft", }; export const familyLabel = (kind: string): string => FAMILY_LABELS[kind] ?? kind.charAt(0).toUpperCase() + kind.slice(1); export const isMultiServiceFamily = (kind: string): boolean => MULTI_SERVICE_FAMILIES.has(kind); /** A run of integrations sharing a provider family, rendered under one umbrella * header. Only families with more than one member group; a lone family member * renders flat (see `groupIntegrations`). */ export interface IntegrationFamilyGroup { readonly type: "group"; readonly kind: string; readonly label: string; readonly members: readonly Integration[]; } export interface IntegrationSingle { readonly type: "single"; readonly integration: Integration; } export type IntegrationGridItem = IntegrationFamilyGroup | IntegrationSingle; /** * Partition the flat integration list into an ordered mix of family groups and * standalone integrations. Ordering is stable: a group appears where its FIRST * member would have appeared in the original list, and non-family integrations * keep their original position relative to it. A family with a single member is * emitted as a standalone integration (a one-item umbrella adds chrome without * grouping anything). */ export function groupIntegrations( integrations: readonly Integration[], ): readonly IntegrationGridItem[] { // First pass: count members per family so singletons can be rendered flat. const familyCounts = new Map(); for (const integration of integrations) { if (isMultiServiceFamily(integration.kind)) { familyCounts.set(integration.kind, (familyCounts.get(integration.kind) ?? 0) + 1); } } const items: IntegrationGridItem[] = []; const groupIndexByKind = new Map(); for (const integration of integrations) { const grouped = isMultiServiceFamily(integration.kind) && (familyCounts.get(integration.kind) ?? 0) > 1; if (!grouped) { items.push({ type: "single", integration }); continue; } const existing = groupIndexByKind.get(integration.kind); if (existing === undefined) { groupIndexByKind.set(integration.kind, items.length); items.push({ type: "group", kind: integration.kind, label: familyLabel(integration.kind), members: [integration], }); } else { const group = items[existing] as IntegrationFamilyGroup; items[existing] = { ...group, members: [...group.members, integration] }; } } return items; }