@@ -153,6 +153,17 @@ class SfdcApi {
153
153
} , 5000 ) ;
154
154
} ) ;
155
155
}
156
+
157
+ describeGlobal ( ) {
158
+ const self = this ;
159
+ return new Promise ( function ( resolve , reject ) {
160
+ self . conn . describeGlobal ( function ( err , res ) {
161
+ if ( err ) { return reject ( err ) ; }
162
+ return resolve ( res . sobjects ) ;
163
+ } ) ;
164
+ } ) ;
165
+
166
+ }
156
167
157
168
describeMetadata ( ) {
158
169
const self = this ;
@@ -192,6 +203,7 @@ class SfdcApi {
192
203
self . getMetadataList ( type )
193
204
. then ( function ( metadata ) {
194
205
for ( let meta of metadata ) {
206
+ if ( meta . manageableState !== 'unmanaged' && meta . manageableState !== undefined ) continue ;
195
207
folders . push ( meta ) ;
196
208
}
197
209
return completion ( null ) ;
@@ -201,12 +213,26 @@ class SfdcApi {
201
213
} ) ;
202
214
} , function ( err ) {
203
215
if ( err ) return reject ( err ) ;
204
- return resolve ( folders ) ;
216
+ // Set Folder Name
217
+ self . conn . query ( 'SELECT Id, Name FROM Folder' , function ( err , res ) {
218
+ if ( err ) return reject ( err ) ;
219
+ let labelMap = { } ;
220
+ for ( let i = 0 ; i < res . records . length ; i ++ ) {
221
+ const record = res . records [ i ] ;
222
+ labelMap [ record . Id ] = record . Name ;
223
+ }
224
+ for ( let i = 0 ; i < folders . length ; i ++ ) {
225
+ if ( labelMap . hasOwnProperty ( folders [ i ] . id ) ) {
226
+ folders [ i ] [ 'label' ] = labelMap [ folders [ i ] . id ] ;
227
+ }
228
+ }
229
+ return resolve ( folders ) ;
230
+ } ) ;
205
231
} ) ; // .async.eachSeries
206
232
} ) ;
207
233
}
208
234
209
- getMetadataDetailList ( metadataList , folders ) {
235
+ getMetadataDetailList ( metadataList , folders , objLabelMap ) {
210
236
const self = this ;
211
237
//const excepts = ['LightningComponentBundle'];
212
238
return new Promise ( function ( resolve , reject ) {
@@ -227,32 +253,43 @@ class SfdcApi {
227
253
let xmlName = ( meta . xmlName == 'EmailTemplate' ) ? 'EmailFolder' : meta . xmlName + 'Folder' ;
228
254
for ( let fd of folders ) {
229
255
if ( fd . type !== xmlName ) continue ;
230
- queues . push ( { type : meta . xmlName , folder : fd . fullName } ) ;
256
+ // attribute folderLabel is used for set folder name to metadata in filterMetadata()
257
+ queues . push ( { type : meta . xmlName , folder : fd . fullName , folderLabel : fd . label } ) ;
231
258
}
232
259
}
233
260
let metadataDetailMap = { } ;
234
261
// console.log('>>>> queues', queues);
235
262
async . eachLimit ( queues , 5 , function ( queue , completion ) {
236
- //console.log('>>>> queue', queue);
237
- self . getMetadataList ( queue )
263
+ // Layout metadata type does not accept folder attribute
264
+ const param = ( queue . type == 'Layout' ) ? { type : queue . type } : { type : queue . type , folder : queue . folder } ;
265
+ self . getMetadataList ( param )
238
266
. then ( function ( metadata ) {
239
- //console.log('>>>> queue : ' + queue.type, metadata.length);
267
+ return self . filterMetadata ( queue , metadata , objLabelMap ) ;
268
+ } )
269
+ . then ( function ( metadata ) {
270
+ if ( metadataDetailMap [ queue . type ] && metadataDetailMap [ queue . type ] . length > 0 ) {
271
+ // e.g. multiple dashboard folders
272
+ metadataDetailMap [ queue . type ] . push ( ...metadata ) ;
273
+ } else if ( queue . type !== 'Workflow' ) {
274
+ // Workflow needs be filtered
275
+ metadataDetailMap [ queue . type ] = metadata ;
276
+ }
277
+ // clear standard object (custom field of standard object need to be pulled)
240
278
if ( queue . type == 'CustomObject' ) {
241
- let customObjects = [ ] ;
242
- for ( let meta of metadata ) {
243
- if ( ! meta . fullName . endsWith ( '__c' ) ) {
244
- // Filter standard object
279
+ let customObjs = [ ] ;
280
+ for ( let i = 0 ; i < metadata . length ; i ++ ) {
281
+ const meta = metadata [ i ] ;
282
+ if ( ! meta . fullName . endsWith ( '__c' ) && ! meta . fullName . endsWith ( '__mdt' ) && ! meta . fullName . endsWith ( '__kav' ) ) {
245
283
continue ;
246
284
}
247
- customObjects . push ( meta ) ;
285
+ customObjs . push ( meta ) ;
248
286
}
249
- metadataDetailMap [ queue . type ] = customObjects ;
250
- } else {
251
- metadataDetailMap [ queue . type ] = metadata ;
287
+ metadataDetailMap [ queue . type ] = customObjs ;
252
288
}
289
+
253
290
if ( queue . type == 'CustomObject' || queue . type == 'Workflow' ) {
254
291
//TODO SharingRules
255
- return self . readChildMetadata ( metadata ) ;
292
+ return self . readChildMetadata ( metadata , objLabelMap ) ;
256
293
} else {
257
294
return true ;
258
295
}
@@ -277,7 +314,149 @@ class SfdcApi {
277
314
} ) ;
278
315
}
279
316
280
- readChildMetadata ( metadata ) {
317
+ // Filter unnecessary metadata, e.g : standard application
318
+ // Set metadata label with SOAP api query
319
+ filterMetadata ( queue , metadata , objLabelMap ) {
320
+ const self = this ;
321
+ const type = queue . type ;
322
+ return new Promise ( function ( resolve , reject ) {
323
+ let filterFunction = function ( meta ) {
324
+ // escape standard and package metadata
325
+ return meta . manageableState !== 'unmanaged' && meta . manageableState !== undefined ;
326
+ } ;
327
+ let resetFunction ;
328
+ switch ( type ) {
329
+ case 'ApexClass' :
330
+ case 'ApexComponent' :
331
+ case 'ApexPage' :
332
+ case 'ApexTrigger' :
333
+ case 'CustomApplication' :
334
+ case 'CustomLabel' :
335
+ case 'CustomField' :
336
+ case 'CustomTab' :
337
+ case 'Dashboard' :
338
+ case 'Document' :
339
+ case 'EmailTemplate' :
340
+ case 'ListView' :
341
+ case 'ReportType' :
342
+ case 'Report' :
343
+ filterFunction = function ( meta ) {
344
+ // escape standard and package metadata
345
+ return ( utils . isNotBlank ( meta . namespacePrefix ) ) ;
346
+ } ;
347
+ if ( type == 'CustomTab' ) {
348
+ resetFunction = function ( meta ) {
349
+ meta [ 'label' ] = ( objLabelMap . hasOwnProperty ( meta . fullName ) ) ? objLabelMap [ meta . fullName ] : meta . fullName ;
350
+ return meta ;
351
+ } ;
352
+ }
353
+ if ( type == 'Dashboard' || type == 'Document' || type == 'EmailTemplate' || type == 'Report' ) {
354
+ resetFunction = function ( meta ) {
355
+ const names = meta . fullName . split ( '/' ) ;
356
+ meta [ 'customName' ] = names [ ( names . length - 1 ) ] ;
357
+ meta [ 'folder' ] = queue . folder ;
358
+ meta [ 'folderLabel' ] = queue . folderLabel ;
359
+ return meta ;
360
+ } ;
361
+ }
362
+ break ;
363
+ case 'CustomMetadata' :
364
+ case 'QuickAction' :
365
+ // Case.Reply → Reply , Case
366
+ resetFunction = function ( meta ) {
367
+ const names = meta . fullName . split ( '.' ) ;
368
+ if ( names . length != 2 ) return meta ;
369
+ const objName = ( type == 'CustomMetadata' ) ? ( names [ 0 ] + '__mdt' ) : names [ 0 ] ;
370
+ meta [ 'object' ] = objName ;
371
+ meta [ 'objectLabel' ] = ( objLabelMap . hasOwnProperty ( objName ) ) ? objLabelMap [ objName ] : objName ;
372
+ meta [ 'customName' ] = names [ 1 ] ; // Case.Reply → Reply
373
+ meta [ 'label' ] = names [ 1 ] ;
374
+ return meta ;
375
+ } ;
376
+ break ;
377
+ case 'CustomObject' :
378
+ case 'MatchingRules' :
379
+ /*filterFunction = function(meta) {
380
+ return (!meta.fullName.endsWith('__c') && !meta.fullName.endsWith('__mdt') && !meta.fullName.endsWith('__kav'));
381
+ }*/
382
+ resetFunction = function ( meta ) {
383
+ meta [ 'label' ] = ( objLabelMap . hasOwnProperty ( meta . fullName ) ) ? objLabelMap [ meta . fullName ] : meta . fullName ;
384
+ return meta ;
385
+ } ;
386
+ break ;
387
+ case 'Layout' :
388
+ // e.g. OpportunityLineItem-商談商品 ページレイアウト → 商談商品 ページレイアウト
389
+ resetFunction = function ( meta ) {
390
+ const names = meta . fullName . split ( '-' ) ;
391
+ if ( names . length > 1 ) {
392
+ const objName = names [ 0 ] ;
393
+ meta [ 'object' ] = objName ;
394
+ meta [ 'objectLabel' ] = ( objLabelMap . hasOwnProperty ( objName ) ) ? objLabelMap [ objName ] : objName ;
395
+ names . shift ( ) ; // remove object name
396
+ meta [ 'customName' ] = names . join ( '-' ) ;
397
+ }
398
+ return meta ;
399
+ } ;
400
+ break ;
401
+ case 'Group' :
402
+ case 'RecordType' :
403
+ case 'Role' :
404
+ // Needs requestMetaLabel
405
+ break ;
406
+ default :
407
+ break ;
408
+ }
409
+
410
+ let targets = [ ] ;
411
+ for ( let meta of metadata ) {
412
+ if ( filterFunction && filterFunction ( meta ) ) {
413
+ // Filter standard object
414
+ continue ;
415
+ }
416
+ if ( resetFunction ) {
417
+ meta = resetFunction ( meta ) ;
418
+ }
419
+ targets . push ( meta ) ;
420
+ }
421
+ self . requestMetaLabel ( type , targets , resolve ) ;
422
+ } ) ;
423
+ }
424
+
425
+ // Request metadata label, e.g. : dashboard
426
+ requestMetaLabel ( type , targets , callback ) {
427
+ const self = this ;
428
+ const labelQueryMap = {
429
+ 'Group' : { 'field' : 'Name' , 'query' : 'SELECT Id, Name FROM Group' } ,
430
+ 'Document' : { 'field' : 'Name' , 'query' : 'SELECT Id, Name FROM Document' } ,
431
+ 'Dashboard' : { 'field' : 'Title' , 'query' : 'SELECT Id, Title FROM Dashboard' } ,
432
+ 'EmailTemplate' : { 'field' : 'Name' , 'query' : 'SELECT Id, Name FROM EmailTemplate' } ,
433
+ 'ListView' : { 'field' : 'Name' , 'query' : 'SELECT Id, Name FROM ListView' } ,
434
+ 'RecordType' : { 'field' : 'Name' , 'query' : 'SELECT Id, Name FROM RecordType' } ,
435
+ 'Report' : { 'field' : 'Name' , 'query' : 'SELECT Id, Name FROM Report' } ,
436
+ 'Role' : { 'field' : 'Name' , 'query' : 'SELECT Id, Name FROM UserRole' }
437
+ }
438
+ if ( labelQueryMap . hasOwnProperty ( type ) ) {
439
+ var labelQuery = labelQueryMap [ type ] ;
440
+ self . conn . query ( labelQuery . query , function ( err , res ) {
441
+ if ( err ) return reject ( err ) ;
442
+ let labelMap = { } ;
443
+ for ( let i = 0 ; i < res . records . length ; i ++ ) {
444
+ const record = res . records [ i ] ;
445
+ labelMap [ record . Id ] = record [ labelQuery . field ] ;
446
+ }
447
+ for ( let i = 0 ; i < targets . length ; i ++ ) {
448
+ if ( labelMap . hasOwnProperty ( targets [ i ] . id ) ) {
449
+ targets [ i ] [ 'label' ] = labelMap [ targets [ i ] . id ] ;
450
+ }
451
+ }
452
+ return callback ( targets ) ;
453
+ } ) ;
454
+ } else {
455
+ return callback ( targets ) ;
456
+ }
457
+ }
458
+
459
+ readChildMetadata ( metadata , objLabelMap ) {
281
460
const self = this ;
282
461
return new Promise ( function ( resolve , reject ) {
283
462
if ( metadata . length == 0 ) {
@@ -345,8 +524,15 @@ class SfdcApi {
345
524
// Filter standard field
346
525
continue ;
347
526
}
348
- if ( meta . fullName ) cMeta [ 'Object' ] = meta . fullName ;
349
- if ( meta . label ) cMeta [ 'ObjectLabel' ] = meta . label ;
527
+ if ( meta . fullName ) cMeta [ 'object' ] = meta . fullName ;
528
+ if ( meta . label ) cMeta [ 'objectLabel' ] = meta . label ;
529
+ if ( utils . isBlank ( cMeta . ObjectLabel ) && objLabelMap . hasOwnProperty ( meta . fullName ) ) {
530
+ // Set Object Label for Workflow Alert
531
+ cMeta [ 'objectLabel' ] = objLabelMap [ meta . fullName ] ;
532
+ }
533
+ if ( child . typeName == 'WorkflowAlert' && cMeta . description ) cMeta [ 'label' ] = cMeta . description ;
534
+ if ( child . typeName == 'WorkflowFieldUpdate' && cMeta . name ) cMeta [ 'customName' ] = cMeta . name ;
535
+
350
536
metadataMap [ child . typeName ] . push ( cMeta ) ;
351
537
}
352
538
}
@@ -423,42 +609,35 @@ class SfdcApi {
423
609
types : pipeline . targetTypes
424
610
}
425
611
} ) ;
426
- const Raven = require ( 'raven' ) ;
427
612
retrieveResult . complete ( function ( err , result ) {
428
613
if ( err ) return reject ( err ) ;
429
- if ( result . success == 'true' ) {
430
- self . logger ( '[SFDC] Retrieve metadata Done.' ) ;
431
- // self.logger('[SFDC] packagePath DecompressZip .' + packagePath + ' > ' + metaPath + ' > ' + (fs.statSync(packagePath).size));
432
- if ( ! fs . existsSync ( packagePath ) ) return reject ( new Error ( 'Metadata zip not found' ) ) ;
433
- self . extractMetaZip ( packagePath , metaPath , 0 , function ( err , success ) {
434
- if ( err ) return reject ( err ) ;
435
- return resolve ( success ) ;
436
- } ) ;
437
- } else {
614
+ if ( result . success != 'true' ) {
438
615
return reject ( new Error ( 'Retrieve metadata failed' ) ) ;
439
616
}
440
617
} ) ;
618
+ zipstream . on ( 'error' , function ( err ) {
619
+ return reject ( err ) ;
620
+ } ) ;
621
+ // Retrieve and zip done
622
+ zipstream . on ( 'close' , function ( ) {
623
+ self . logger ( '[SFDC] Retrieve metadata Done.' ) ;
624
+ // self.logger('[SFDC] packagePath DecompressZip .' + packagePath + ' > ' + metaPath + ' > ' + (fs.statSync(packagePath).size));
625
+ if ( ! fs . existsSync ( packagePath ) ) return reject ( new Error ( 'Metadata zip not found' ) ) ;
626
+ self . extractMetaZip ( packagePath , metaPath , function ( err , success ) {
627
+ if ( err ) return reject ( err ) ;
628
+ return resolve ( success ) ;
629
+ } ) ;
630
+ } ) ;
441
631
retrieveResult . stream ( ) . pipe ( zipstream ) ;
442
632
} ) ;
443
633
}
444
634
445
635
// Extract metadata zip file to src folder
446
- // Metadata api may zip file cost sevaral ms
447
- extractMetaZip ( sourceZipPath , targetPath , presize , callback ) {
448
- const self = this ;
636
+ extractMetaZip ( sourceZipPath , targetPath , callback ) {
449
637
const fileinfo = fs . statSync ( sourceZipPath ) ;
450
638
if ( fileinfo . size == 0 ) {
451
639
// Is written
452
- return setTimeout ( function ( ) {
453
- self . extractMetaZip ( sourceZipPath , targetPath , presize , callback )
454
- } , 500 ) ;
455
- }
456
- if ( presize !== fileinfo . size ) {
457
- // Maybe written
458
- presize = fileinfo . size ;
459
- return setTimeout ( function ( ) {
460
- self . extractMetaZip ( sourceZipPath , targetPath , presize , callback )
461
- } , 500 ) ;
640
+ return callback ( new Error ( 'Metadata zip not found' ) ) ;
462
641
}
463
642
// Ready to extract
464
643
const unzipper = new DecompressZip ( sourceZipPath )
0 commit comments