@@ -24,7 +24,19 @@ const messages = Messages.loadMessages('sfdx-hardis', 'org');
24
24
/* jscpd:ignore-end */
25
25
export default class LintMetadataStatus extends SfCommand < any > {
26
26
public static title = 'check inactive metadatas' ;
27
- public static description = `Check if elements (flows and validation rules) are inactive in the project
27
+ public static description = `Check if elements are inactive in the project:
28
+
29
+ - Approval Processes
30
+ - Assignment Rules
31
+ - Auto Response Rules
32
+ - Escalation Rules
33
+ - Flows
34
+ - Forecasting Types
35
+ - Record Types
36
+ - Validation Rules
37
+ - Workflow Rules
38
+
39
+ 
28
40
29
41
This command is part of [sfdx-hardis Monitoring](${ CONSTANTS . DOC_URL_ROOT } /salesforce-monitoring-inactive-metadata/) and can output Grafana, Slack and MsTeams Notifications.
30
42
` ;
@@ -62,30 +74,85 @@ This command is part of [sfdx-hardis Monitoring](${CONSTANTS.DOC_URL_ROOT}/sales
62
74
63
75
public async run ( ) : Promise < AnyJson > {
64
76
const { flags } = await this . parse ( LintMetadataStatus ) ;
77
+
78
+ const inactiveApprovalProcesses = await this . verifyApprovalProcesses ( ) ;
79
+ const inactiveAssignmentRules = await this . verifyAssignmentRules ( ) ;
80
+ const inactiveAutoResponseRules = await this . verifyAutoResponseRules ( ) ;
81
+ const inactiveEscalationRules = await this . verifyEscalationRules ( ) ;
65
82
const draftFlows = await this . verifyFlows ( ) ;
83
+ const inactiveForecastingTypes = await this . verifyForecastingTypes ( ) ;
84
+ const inactiveRecordTypes = await this . verifyRecordTypes ( ) ;
66
85
const inactiveValidationRules = await this . verifyValidationRules ( ) ;
86
+ const inactiveWorkflows = await this . verifyWorkflowRules ( ) ;
67
87
88
+ this . inactiveItems = [
89
+ ...inactiveApprovalProcesses ,
90
+ ...inactiveAssignmentRules ,
91
+ ...inactiveAutoResponseRules ,
92
+ ...draftFlows ,
93
+ ...inactiveEscalationRules ,
94
+ ...inactiveForecastingTypes ,
95
+ ...inactiveRecordTypes ,
96
+ ...inactiveValidationRules ,
97
+ ...inactiveWorkflows ,
98
+ ] ;
68
99
// Prepare notifications
69
100
const branchMd = await getBranchMarkdown ( ) ;
70
101
const notifButtons = await getNotificationButtons ( ) ;
71
102
let notifSeverity : NotifSeverity = 'log' ;
72
103
let notifText = `No inactive configuration elements has been found in ${ branchMd } ` ;
73
104
const attachments : MessageAttachment [ ] = [ ] ;
74
- if ( draftFlows . length > 0 || inactiveValidationRules . length > 0 ) {
105
+ if ( this . inactiveItems . length > 0 ) {
75
106
notifSeverity = 'warning' ;
107
+ if ( inactiveApprovalProcesses . length > 0 ) {
108
+ attachments . push ( {
109
+ text : `*Inactive Approval Processes*\n${ inactiveApprovalProcesses . map ( ( file ) => `• ${ file . name } ` ) . join ( '\n' ) } ` ,
110
+ } ) ;
111
+ }
112
+ if ( inactiveAssignmentRules . length > 0 ) {
113
+ attachments . push ( {
114
+ text : `*Inactive Assignment Rules*\n${ inactiveAssignmentRules . map ( ( file ) => `• ${ file . name } ` ) . join ( '\n' ) } ` ,
115
+ } ) ;
116
+ }
117
+ if ( inactiveAutoResponseRules . length > 0 ) {
118
+ attachments . push ( {
119
+ text : `*Inactive Auto Response Rules*\n${ inactiveAutoResponseRules . map ( ( file ) => `• ${ file . name } ` ) . join ( '\n' ) } ` ,
120
+ } ) ;
121
+ }
122
+ if ( inactiveEscalationRules . length > 0 ) {
123
+ attachments . push ( {
124
+ text : `*Inactive Escalation Rules*\n${ inactiveEscalationRules . map ( ( file ) => `• ${ file . name } ` ) . join ( '\n' ) } ` ,
125
+ } ) ;
126
+ }
76
127
if ( draftFlows . length > 0 ) {
77
128
attachments . push ( {
78
129
text : `*Inactive Flows*\n${ draftFlows . map ( ( file ) => `• ${ file . name } ` ) . join ( '\n' ) } ` ,
79
130
} ) ;
80
131
}
132
+ if ( inactiveForecastingTypes . length > 0 ) {
133
+ attachments . push ( {
134
+ text : `*Inactive Forecasting Types*\n${ inactiveForecastingTypes . map ( ( file ) => `• ${ file . name } ` ) . join ( '\n' ) } ` ,
135
+ } ) ;
136
+ }
137
+ if ( inactiveRecordTypes . length > 0 ) {
138
+ attachments . push ( {
139
+ text : `*Inactive Record Types*\n${ inactiveRecordTypes . map ( ( file ) => `• ${ file . name } ` ) . join ( '\n' ) } ` ,
140
+ } ) ;
141
+ }
81
142
if ( inactiveValidationRules . length > 0 ) {
82
143
attachments . push ( {
83
144
text : `*Inactive Validation Rules*\n${ inactiveValidationRules . map ( ( file ) => `• ${ file . name } ` ) . join ( '\n' ) } ` ,
84
145
} ) ;
85
146
}
86
- const numberInactive = draftFlows . length + inactiveValidationRules . length ;
87
- notifText = `${ numberInactive } inactive configuration elements have been found in ${ branchMd } ` ;
88
- await this . buildCsvFile ( draftFlows , inactiveValidationRules ) ;
147
+ if ( inactiveWorkflows . length > 0 ) {
148
+ attachments . push ( {
149
+ text : `*Inactive Workflow Rules*\n${ inactiveWorkflows . map ( ( file ) => `• ${ file . name } ` ) . join ( '\n' ) } ` ,
150
+ } ) ;
151
+ }
152
+
153
+ notifText = `${ this . inactiveItems . length } inactive configuration elements have been found in ${ branchMd } ` ;
154
+ // Build result file
155
+ await this . buildCsvFile ( ) ;
89
156
} else {
90
157
uxLog ( this , 'No draft flow or validation rule files detected.' ) ;
91
158
}
@@ -124,11 +191,11 @@ This command is part of [sfdx-hardis Monitoring](${CONSTANTS.DOC_URL_ROOT}/sales
124
191
const flowContent : string = await fs . readFile ( file , 'utf-8' ) ;
125
192
if ( flowContent . includes ( '<status>Draft</status>' ) ) {
126
193
const fileName = path . basename ( file , '.flow-meta.xml' ) ;
127
- draftFiles . push ( { type : 'Draft Flow' , name : fileName , severity : 'warning' , severityIcon : severityIcon } ) ;
194
+ draftFiles . push ( { type : 'Flow (draft) ' , name : fileName , severity : 'warning' , severityIcon : severityIcon } ) ;
128
195
}
129
196
}
130
197
131
- return draftFiles ;
198
+ return draftFiles . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
132
199
}
133
200
134
201
/**
@@ -152,15 +219,179 @@ This command is part of [sfdx-hardis Monitoring](${CONSTANTS.DOC_URL_ROOT}/sales
152
219
const ruleName = path . basename ( file , '.validationRule-meta.xml' ) ;
153
220
const objectName = path . basename ( path . dirname ( path . dirname ( file ) ) ) ;
154
221
inactiveRules . push ( {
155
- type : 'Inactive VR ' ,
222
+ type : 'Validation Rule (inactive) ' ,
156
223
name : `${ objectName } - ${ ruleName } ` ,
157
224
severity : 'warning' ,
158
225
severityIcon : severityIcon ,
159
226
} ) ;
160
227
}
161
228
}
162
229
163
- return inactiveRules ;
230
+ return inactiveRules . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
231
+ }
232
+
233
+ private async verifyRecordTypes ( ) : Promise < any [ ] > {
234
+ const inactiveRecordTypes : any [ ] = [ ] ;
235
+ const recordTypeFiles : string [ ] = await glob ( '**/objects/**/recordTypes/*.recordType-meta.xml' , {
236
+ ignore : this . ignorePatterns ,
237
+ } ) ;
238
+ const severityIcon = getSeverityIcon ( 'warning' ) ;
239
+ for ( const file of recordTypeFiles ) {
240
+ const recordTypeName = path . basename ( file , '.recordType-meta.xml' ) ;
241
+ const objectName = path . basename ( path . dirname ( path . dirname ( file ) ) ) ;
242
+ // Skip if record type is from a managed package
243
+ if ( path . basename ( recordTypeName ) . includes ( '__' ) ) {
244
+ continue ;
245
+ }
246
+ const recordTypeXml : string = await fs . readFile ( file , 'utf-8' ) ;
247
+ if ( recordTypeXml . includes ( '<active>false</active>' ) ) {
248
+ inactiveRecordTypes . push ( {
249
+ type : 'Record Type (inactive)' ,
250
+ name : `${ objectName } - ${ recordTypeName } ` ,
251
+ severity : 'warning' ,
252
+ severityIcon : severityIcon ,
253
+ } ) ;
254
+ }
255
+ }
256
+
257
+ return inactiveRecordTypes . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
258
+ }
259
+
260
+ private async verifyApprovalProcesses ( ) : Promise < any [ ] > {
261
+ const inactiveApprovalProcesses : any [ ] = [ ] ;
262
+ const approvalProcessFiles : string [ ] = await glob ( '**/approvalProcesses/**/*.approvalProcess-meta.xml' , {
263
+ ignore : this . ignorePatterns ,
264
+ } ) ;
265
+ const severityIcon = getSeverityIcon ( 'warning' ) ;
266
+ for ( const file of approvalProcessFiles ) {
267
+ const approvalProcessFullName = path . basename ( file , '.approvalProcess-meta.xml' ) ;
268
+ const [ objectName , approvalProcessName ] = approvalProcessFullName . split ( '.' ) ;
269
+ // Skip if approval process is from a managed package
270
+ if ( path . basename ( approvalProcessName ) . includes ( '__' ) ) {
271
+ continue ;
272
+ }
273
+ const approvalProcessXml : string = await fs . readFile ( file , 'utf-8' ) ;
274
+ if ( approvalProcessXml . includes ( '<active>false</active>' ) ) {
275
+ inactiveApprovalProcesses . push ( {
276
+ type : 'Approval Process (inactive)' ,
277
+ name : `${ objectName } - ${ approvalProcessName } ` ,
278
+ severity : 'warning' ,
279
+ severityIcon : severityIcon ,
280
+ } ) ;
281
+ }
282
+ }
283
+
284
+ return inactiveApprovalProcesses . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
285
+ }
286
+
287
+ private async verifyForecastingTypes ( ) : Promise < any [ ] > {
288
+ const inactiveForecastTypes : any [ ] = [ ] ;
289
+ const forecastTypeFiles : string [ ] = await glob ( '**/forecastingTypes/**/*.forecastingType-meta.xml' , {
290
+ ignore : this . ignorePatterns ,
291
+ } ) ;
292
+ const severityIcon = getSeverityIcon ( 'warning' ) ;
293
+ for ( const file of forecastTypeFiles ) {
294
+ const forecastingTypeName = path . basename ( file , '.forecastingType-meta.xml' ) ;
295
+ const forecastTypeXml : string = await fs . readFile ( file , 'utf-8' ) ;
296
+ if ( forecastTypeXml . includes ( '<active>false</active>' ) ) {
297
+ inactiveForecastTypes . push ( {
298
+ type : 'Forecasting Type (inactive)' ,
299
+ name : forecastingTypeName ,
300
+ severity : 'warning' ,
301
+ severityIcon : severityIcon ,
302
+ } ) ;
303
+ }
304
+ }
305
+
306
+ return inactiveForecastTypes . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
307
+ }
308
+
309
+ private async verifyWorkflowRules ( ) : Promise < any [ ] > {
310
+ const inactiveWorkflowRules : any [ ] = [ ] ;
311
+ const workflowRuleFiles : string [ ] = await glob ( '**/workflows/**/*.workflow-meta.xml' , {
312
+ ignore : this . ignorePatterns ,
313
+ } ) ;
314
+ const severityIcon = getSeverityIcon ( 'warning' ) ;
315
+ for ( const file of workflowRuleFiles ) {
316
+ const workflowRuleName = path . basename ( file , '.workflow-meta.xml' ) ;
317
+ const workflowRuleXml : string = await fs . readFile ( file , 'utf-8' ) ;
318
+ if ( workflowRuleXml . includes ( '<active>false</active>' ) ) {
319
+ inactiveWorkflowRules . push ( {
320
+ type : 'Workflow Rule (inactive)' ,
321
+ name : workflowRuleName ,
322
+ severity : 'warning' ,
323
+ severityIcon : severityIcon ,
324
+ } ) ;
325
+ }
326
+ }
327
+
328
+ return inactiveWorkflowRules . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
329
+ }
330
+
331
+ private async verifyAssignmentRules ( ) : Promise < any [ ] > {
332
+ const inactiveAssignmentRules : any [ ] = [ ] ;
333
+ const assignmentRuleFiles : string [ ] = await glob ( '**/assignmentRules/**/*.assignmentRules-meta.xml' , {
334
+ ignore : this . ignorePatterns ,
335
+ } ) ;
336
+ const severityIcon = getSeverityIcon ( 'warning' ) ;
337
+ for ( const file of assignmentRuleFiles ) {
338
+ const assignmentRuleName = path . basename ( file , '.assignmentRules-meta.xml' ) ;
339
+ const assignmentRuleXml : string = await fs . readFile ( file , 'utf-8' ) ;
340
+ if ( assignmentRuleXml . includes ( '<active>false</active>' ) ) {
341
+ inactiveAssignmentRules . push ( {
342
+ type : 'Assignment Rule (inactive)' ,
343
+ name : assignmentRuleName ,
344
+ severity : 'warning' ,
345
+ severityIcon : severityIcon ,
346
+ } ) ;
347
+ }
348
+ }
349
+
350
+ return inactiveAssignmentRules . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
351
+ }
352
+
353
+ private async verifyAutoResponseRules ( ) : Promise < any [ ] > {
354
+ const inactiveAutoResponseRules : any [ ] = [ ] ;
355
+ const autoResponseRuleFiles : string [ ] = await glob ( '**/autoResponseRules/**/*.autoResponseRules-meta.xml' , {
356
+ ignore : this . ignorePatterns ,
357
+ } ) ;
358
+ const severityIcon = getSeverityIcon ( 'warning' ) ;
359
+ for ( const file of autoResponseRuleFiles ) {
360
+ const autoResponseRuleName = path . basename ( file , '.autoResponseRules-meta.xml' ) ;
361
+ const autoResponseRuleXml : string = await fs . readFile ( file , 'utf-8' ) ;
362
+ if ( autoResponseRuleXml . includes ( '<active>false</active>' ) ) {
363
+ inactiveAutoResponseRules . push ( {
364
+ type : 'Auto Response Rule (inactive)' ,
365
+ name : autoResponseRuleName ,
366
+ severity : 'warning' ,
367
+ severityIcon : severityIcon ,
368
+ } ) ;
369
+ }
370
+ }
371
+
372
+ return inactiveAutoResponseRules . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
373
+ }
374
+
375
+ private async verifyEscalationRules ( ) : Promise < any [ ] > {
376
+ const inactiveEscalationRules : any [ ] = [ ] ;
377
+ const escalationRuleFiles : string [ ] = await glob ( '**/escalationRules/**/*.escalationRules-meta.xml' , {
378
+ ignore : this . ignorePatterns ,
379
+ } ) ;
380
+ const severityIcon = getSeverityIcon ( 'warning' ) ;
381
+ for ( const file of escalationRuleFiles ) {
382
+ const escalationRuleName = path . basename ( file , '.escalationRules-meta.xml' ) ;
383
+ const escalationRuleXml : string = await fs . readFile ( file , 'utf-8' ) ;
384
+ if ( escalationRuleXml . includes ( '<active>false</active>' ) ) {
385
+ inactiveEscalationRules . push ( {
386
+ type : 'Escalation Rule (inactive)' ,
387
+ name : escalationRuleName ,
388
+ severity : 'warning' ,
389
+ severityIcon : severityIcon ,
390
+ } ) ;
391
+ }
392
+ }
393
+
394
+ return inactiveEscalationRules . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
164
395
}
165
396
166
397
/**
@@ -169,14 +400,10 @@ This command is part of [sfdx-hardis Monitoring](${CONSTANTS.DOC_URL_ROOT}/sales
169
400
* It then maps the draft flows and inactive validation rules into an array of objects, each with a 'type' property set to either "Draft Flow" or "Inactive VR" and a 'name' property set to the file or rule name.
170
401
* Finally, it generates a CSV file from this array and writes it to the output file.
171
402
*
172
- * @param {string[] } draftFlows - An array of draft flow names.
173
- * @param {string[] } inactiveValidationRules - An array of inactive validation rule names.
174
403
* @returns {Promise<void> } - A Promise that resolves when the CSV file has been successfully generated.
175
404
*/
176
- private async buildCsvFile ( draftFlows : string [ ] , inactiveValidationRules : string [ ] ) : Promise < void > {
405
+ private async buildCsvFile ( ) : Promise < void > {
177
406
this . outputFile = await generateReportPath ( 'lint-metadatastatus' , this . outputFile ) ;
178
- this . inactiveItems = [ ...draftFlows , ...inactiveValidationRules ] ;
179
-
180
407
this . outputFilesRes = await generateCsvFile ( this . inactiveItems , this . outputFile ) ;
181
408
}
182
409
}
0 commit comments