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 containerprojects.getFolders(containerId)gets subfolders- Build full path for each document during traversal
Document Filtering:
- Filter documents by
$Typeproperty - 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
maxUnitToLoadparameter for large modules - Predicate function filters which microflows to load
Analyzing Microflow Contents:
microflow.objectCollection.objectscontains all flow objects- Filter by
$Type === 'Microflows$ActionActivity'to count activities microflow.flowscontains connections between objects- Access metadata like
qualifiedNamefor 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