Skip to content
Open
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
48 changes: 24 additions & 24 deletions packages/orgcheck-api/src/api/core/orgcheck-api-secretsauce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -738,191 +738,191 @@ const ALL_SCORE_RULES: ScoreRule[] = [
{
id: 100,
description: '[LFS] Inactive Flow',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('InactiveFlow') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'InactiveFlow') || false) as (data: unknown) => boolean,
errorMessage: `This flow is inactive. Consider activating it or removing it from your org.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.USELESS
}, {
id: 101,
description: '[LFS] Process Builder',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('ProcessBuilder') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'ProcessBuilder') || false) as (data: unknown) => boolean,
errorMessage: `Time to migrate this process builder to flow!`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.USELESS
}, {
id: 102,
description: '[LFS] Missing Flow Description',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('FlowDescription') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'FlowDescription') || false) as (data: unknown) => boolean,
errorMessage: `This flow does not have a description. Add documentation about its purpose and usage.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.DOCUMENTATION
}, {
id: 103,
description: '[LFS] Outdated API Version',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('APIVersion') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'APIVersion') || false) as (data: unknown) => boolean,
errorMessage: `The API version of this flow is outdated. Update it to the newest version.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.API_VERSION
}, {
id: 104,
description: '[LFS] Unsafe Running Context',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('UnsafeRunningContext') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'UnsafeRunningContext') || false) as (data: unknown) => boolean,
errorMessage: `This flow runs in System Mode without Sharing, which can lead to unsafe data access.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.SECURITY
}, {
id: 105,
description: '[LFS] SOQL Query In Loop',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('SOQLQueryInLoop') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'SOQLQueryInLoop') || false) as (data: unknown) => boolean,
errorMessage: `This flow has SOQL queries inside loops. Consolidate queries at the end of the flow to avoid governor limits.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 106,
description: '[LFS] DML Statement In Loop',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('DMLStatementInLoop') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'DMLStatementInLoop') || false) as (data: unknown) => boolean,
errorMessage: `This flow has DML operations inside loops. Consolidate DML at the end to avoid governor limits.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 107,
description: '[LFS] Action Calls In Loop',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('ActionCallsInLoop') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'ActionCallsInLoop') || false) as (data: unknown) => boolean,
errorMessage: `This flow has action calls inside loops. Bulkify apex calls using collection variables.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 108,
description: '[LFS] Hardcoded Id',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('HardcodedId') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'HardcodedId') || false) as (data: unknown) => boolean,
errorMessage: `This flow contains hardcoded IDs which are org-specific. Use variables or merge fields instead.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.HARDCODED_ID
}, {
id: 109,
description: '[LFS] Hardcoded Url',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('HardcodedUrl') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'HardcodedUrl') || false) as (data: unknown) => boolean,
errorMessage: `This flow contains hardcoded URLs. Use $API formulas or custom labels instead.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.HARDCODED_URL
}, {
id: 110,
description: '[LFS] Missing Null Handler',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('MissingNullHandler') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'MissingNullHandler') || false) as (data: unknown) => boolean,
errorMessage: `This flow has Get Records operations without null checks. Use decision elements to validate results.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 111,
description: '[LFS] Missing Fault Path',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('MissingFaultPath') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'MissingFaultPath') || false) as (data: unknown) => boolean,
errorMessage: `This flow has DML or action operations without fault handlers. Add fault paths for better error handling.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 112,
description: '[LFS] Recursive After Update',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('RecursiveAfterUpdate') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'RecursiveAfterUpdate') || false) as (data: unknown) => boolean,
errorMessage: `This after-update flow modifies the same record that triggered it, risking recursion. Use before-save flows instead.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 113,
description: '[LFS] Duplicate DML Operation',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('DuplicateDMLOperation') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'DuplicateDMLOperation') || false) as (data: unknown) => boolean,
errorMessage: `This flow allows navigation back after DML operations, which may cause duplicate changes.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 114,
description: '[LFS] Get Record All Fields',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('GetRecordAllFields') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'GetRecordAllFields') || false) as (data: unknown) => boolean,
errorMessage: `This flow uses Get Records with "all fields". Specify only needed fields for better performance.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 115,
description: '[LFS] Record ID as String',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('RecordIdAsString') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'RecordIdAsString') || false) as (data: unknown) => boolean,
errorMessage: `This flow uses a String recordId variable. Modern flows can receive the entire record object, eliminating Get Records queries.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 116,
description: '[LFS] Unconnected Element',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('UnconnectedElement') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'UnconnectedElement') || false) as (data: unknown) => boolean,
errorMessage: `This flow has unconnected elements that are not in use. Remove them to maintain clarity.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.USELESS
}, {
id: 117,
description: '[LFS] Unused Variable',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('UnusedVariable') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'UnusedVariable') || false) as (data: unknown) => boolean,
errorMessage: `This flow has unused variables. Remove them to maintain efficiency.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.USELESS
}, {
id: 118,
description: '[LFS] Copy API Name',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('CopyAPIName') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'CopyAPIName') || false) as (data: unknown) => boolean,
errorMessage: `This flow has elements with copy-paste naming patterns like "Copy_X_Of_Element". Update API names for readability.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.DOCUMENTATION
}, {
id: 120,
description: '[LFS] Same Record Field Updates',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('SameRecordFieldUpdates') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'SameRecordFieldUpdates') || false) as (data: unknown) => boolean,
errorMessage: `This before-save flow uses Update Records on $Record. Use direct assignment instead for better performance.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 122,
description: '[LFS] Missing Metadata Description',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('MissingMetadataDescription') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'MissingMetadataDescription') || false) as (data: unknown) => boolean,
errorMessage: `This flow has elements or variables without descriptions. Add documentation for better maintainability.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.DOCUMENTATION
}, {
id: 123,
description: '[LFS] Missing Filter Record Trigger',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('MissingFilterRecordTrigger') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'MissingFilterRecordTrigger') || false) as (data: unknown) => boolean,
errorMessage: `This record-triggered flow lacks filters on changed fields or entry conditions, causing unnecessary executions.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 124,
description: '[LFS] Transform Instead of Loop',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('TransformInsteadOfLoop') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'TransformInsteadOfLoop') || false) as (data: unknown) => boolean,
errorMessage: `This flow uses Loop + Assignment which could be replaced with Transform element (10x faster).`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
category: SCORE_RULE_CATEGORIES.CODE_QUALITY
}, {
id: 125,
description: '[LFS] Missing Auto Layout',
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.includes('AutoLayout') || false) as (data: unknown) => boolean,
formula: ((d: SfdcFlow) => d?.currentVersionRef?.lfsViolations?.some(v => v.name === 'AutoLayout') || false) as (data: unknown) => boolean,
errorMessage: `This flow doesn't use Auto-Layout mode. Enable it to keep your flow organized automatically.`,
badField: 'currentVersionRef.lfsViolations',
applicable: [ DataAliases.SfdcFlow ],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ export class LFSScanner {
// Scan flows
const scanResults = lfsCore.scan(lfsFlows);

// Apply warning severity threshold via the LFS core if available (v6.19+)
if (lfsCore.filterByThreshold) {
for (const result of scanResults) {
result.ruleResults = lfsCore.filterByThreshold(result.ruleResults, 'warning');
}
}

// Map results: flowVersionId -> violations
results = this.mapResults(scanResults);
}
Expand All @@ -73,15 +80,15 @@ export class LFSScanner {
/**
* @description Map LFS scan results to OrgCheck format
* @param {any[]} scanResults - LFS scan results
* @returns {Map<string, string[]>} Map of flow version ID to violations
* @returns {Map<string, {name: string, severity: string}[]>} Map of flow version ID to violations
*/
static mapResults(scanResults: Record<string, unknown>[]): Map<string, string[]> {
static mapResults(scanResults: Record<string, unknown>[]): Map<string, { name: string; severity: string }[]> {
const violationsMap = new Map();
for (const result of scanResults) {
const ruleResults = result.ruleResults as { occurs: boolean; ruleName: string }[];
const ruleResults = result.ruleResults as { occurs: boolean; ruleName: string; severity?: string }[];
const violations = ruleResults
.filter((ruleResult) => ruleResult.occurs === true)
.map((ruleResult) => ruleResult.ruleName);
.map((ruleResult) => ({ name: ruleResult.ruleName, severity: ruleResult.severity ?? 'warning' }));
if (violations?.length > 0) {
violationsMap.set((result.flow as { uri: string }).uri, violations);
}
Expand Down
14 changes: 11 additions & 3 deletions packages/orgcheck-api/src/api/data/orgcheck-api-data-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ export interface SfdcFlow extends DataWithScoreAndDependencies {
lastModifiedDate: number;
}

/**
* Represents a single LFS rule violation with its name and severity
*/
export interface LfsViolation {
name: string;
severity: string;
}

/**
* Represents a Flow Version
*/
Expand Down Expand Up @@ -237,9 +245,9 @@ export interface SfdcFlowVersion extends DataWithoutScore {
recordTriggerType: string;

/**
* @description LFS Violations (list of rule names) for this flow version
* @type {string[]}
* @description LFS Violations for this flow version, filtered to warning severity and above by the LFS core
* @type {LfsViolation[]}
* @public
*/
lfsViolations: string[];
lfsViolations: LfsViolation[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class FlowsTableDefinition implements TableDefinition {
{ label: '# DML Delete Nodes', type: ColumnType.NUM, data: { value: 'currentVersionRef.dmlDeleteNodeCount' }},
{ label: '# DML Update Nodes', type: ColumnType.NUM, data: { value: 'currentVersionRef.dmlUpdateNodeCount' }},
{ label: '# Screen Nodes', type: ColumnType.NUM, data: { value: 'currentVersionRef.screenNodeCount' }},
{ label: 'Its LFS Violations', type: ColumnType.TXTS, data: { values: 'currentVersionRef.lfsViolations', value: '.' }},
{ label: 'Its LFS Violations', type: ColumnType.TXTS, data: { values: 'currentVersionRef.lfsViolations', value: 'name' }},
{ label: 'Its created date', type: ColumnType.DTM, data: { value: 'currentVersionRef.createdDate' }},
{ label: 'Its modified date', type: ColumnType.DTM, data: { value: 'currentVersionRef.lastModifiedDate' }},
{ label: 'Its description', type: ColumnType.TXT, data: { value: 'currentVersionRef.description' }, modifier: { maximumLength: 45, valueIfEmpty: 'No description.' }},
Expand Down
Loading