Skip to Content
ProjectsExtensibility PlaygroundAnalysis & Inspection

Analysis & Inspection

Examples for analyzing and inspecting your Mendix model to gather statistics, audit model elements, and understand your application structure.

Microflow Analysis - Counting Activities

This comprehensive example demonstrates how to analyze all microflows in a module by recursively traversing folders, loading microflows, and gathering detailed statistics about their contents.

// ============================================================================ // Microflow Analysis - Count all microflows and their activities // ============================================================================ // // This example demonstrates: // 1. Recursively traversing all folders in a module // 2. Finding all microflows across nested folder structures // 3. Loading microflows to inspect their contents // 4. Counting activities in each microflow // 5. Displaying comprehensive statistics // // ============================================================================ const { projects, microflows } = studioPro.app.model; // Step 1: Get the PhoKing module log('๐Ÿ” Getting PhoKing module...'); const module = await projects.getModule('PhoKing'); if (!module) { log('โŒ PhoKing module not found'); return; } log(`โœ… Found module: ${module.name}`); // Step 2: Recursively get all documents from all folders log('\n๐Ÿ“ Scanning folders...'); // Helper function to recursively get all documents from a container and its subfolders async function getAllDocumentsRecursive(containerId, path = '') { const allDocs = []; // Get documents directly in this container const docs = await projects.getDocumentsInfo(containerId); allDocs.push(...docs.map(doc => ({ ...doc, path }))); // Get all folders in this container const folders = await projects.getFolders(containerId); // Recursively get documents from each subfolder for (const folder of folders) { const folderPath = path ? `${path}/${folder.name}` : folder.name; const subDocs = await getAllDocumentsRecursive(folder.$ID, folderPath); allDocs.push(...subDocs); } return allDocs; } // Get all documents recursively starting from the module const allDocuments = await getAllDocumentsRecursive(module.$ID); log(`\nโœ… Found ${allDocuments.length} total documents in PhoKing module`); // Step 3: Filter for microflows only const microflowDocs = allDocuments.filter(doc => doc.$Type === 'Microflows$Microflow'); log(`\n๐Ÿ”ง Found ${microflowDocs.length} microflows in total`); if (microflowDocs.length === 0) { log('โŒ No microflows found in PhoKing module'); return; } // Step 4: Load all microflows and analyze them log('\n๐Ÿ“Š Loading and analyzing microflows...\n'); log('=' .repeat(80)); // Collect all microflow IDs const microflowIds = microflowDocs.map(doc => doc.$ID); // Load all microflows at once (set maxUnitToLoad to handle large modules) const loadedMicroflows = await microflows.loadAll( (mf) => microflowIds.includes(mf.$ID), 200 // Maximum number of microflows to load (increase if needed) ); // Statistics let totalActivities = 0; const microflowStats = []; // Analyze each microflow for (const microflow of loadedMicroflows) { // Count activities in this microflow const activities = microflow.objectCollection.objects.filter( obj => obj.$Type === 'Microflows$ActionActivity' ); const activityCount = activities.length; totalActivities += activityCount; // Find the folder path for this microflow const docInfo = microflowDocs.find(doc => doc.$ID === microflow.$ID); const folderPath = docInfo?.path || '(root)'; microflowStats.push({ name: microflow.name, qualifiedName: microflow.qualifiedName || microflow.name, path: folderPath, activityCount: activityCount, totalObjects: microflow.objectCollection.objects.length, flowCount: microflow.flows.length }); const displayName = microflow.qualifiedName || microflow.name || '(unnamed)'; log(`๐Ÿ“Œ ${displayName}`); log(` Folder: ${folderPath}`); log(` Activities: ${activityCount}`); log(` Total Objects: ${microflow.objectCollection.objects.length}`); log(` Flows: ${microflow.flows.length}`); log(''); } log('=' .repeat(80)); // Step 5: Display summary statistics log('\n๐Ÿ“ˆ SUMMARY STATISTICS:'); log('=' .repeat(80)); log(`Total Microflows: ${loadedMicroflows.length}`); log(`Total Activities: ${totalActivities}`); log(`Average Activities per Microflow: ${(totalActivities / loadedMicroflows.length).toFixed(2)}`); // Find microflow with most activities const maxActivities = Math.max(...microflowStats.map(s => s.activityCount)); const busiestMicroflow = microflowStats.find(s => s.activityCount === maxActivities); log(`\nBusiest Microflow: ${busiestMicroflow.qualifiedName || busiestMicroflow.name} (${busiestMicroflow.activityCount} activities)`); // Find microflows with no activities const emptyMicroflows = microflowStats.filter(s => s.activityCount === 0); if (emptyMicroflows.length > 0) { log(`\nEmpty Microflows (0 activities): ${emptyMicroflows.length}`); emptyMicroflows.forEach(mf => log(` - ${mf.qualifiedName || mf.name}`)); } log('=' .repeat(80)); log('\nโœ… Analysis complete!');

Key Concepts

Recursive Folder Traversal:

  • Use a helper function to recursively explore all folders
  • projects.getDocumentsInfo(containerId) gets documents in a container
  • projects.getFolders(containerId) gets subfolders
  • Build full path for each document during traversal

Document Filtering:

  • Filter documents by $Type property
  • Common types: Microflows$Microflow, DomainModels$DomainModel, Pages$Page
  • Use .filter() to isolate specific document types

Loading Model Elements:

  • Use microflows.loadAll(predicate, maxUnitToLoad) to load multiple microflows
  • Set maxUnitToLoad parameter for large modules
  • Predicate function filters which microflows to load

Analyzing Microflow Contents:

  • microflow.objectCollection.objects contains all flow objects
  • Filter by $Type === 'Microflows$ActionActivity' to count activities
  • microflow.flows contains connections between objects
  • Access metadata like qualifiedName for full path

Statistics Gathering:

  • Collect metrics in arrays for aggregate analysis
  • Calculate totals, averages, min/max values
  • Identify outliers (busiest, empty, etc.)
  • Display organized summary reports

Use Cases: This pattern can be adapted for:

  • Analyzing domain models (counting entities, attributes, associations)
  • Auditing pages (widgets, data sources, security)
  • Finding unused documents
  • Generating architecture documentation
  • Quality metrics and technical debt analysis
  • Migration planning and impact analysis