@@ -153,6 +153,17 @@ class SfdcApi {
153153 } , 5000 ) ;
154154 } ) ;
155155 }
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+ }
156167
157168 describeMetadata ( ) {
158169 const self = this ;
@@ -192,6 +203,7 @@ class SfdcApi {
192203 self . getMetadataList ( type )
193204 . then ( function ( metadata ) {
194205 for ( let meta of metadata ) {
206+ if ( meta . manageableState !== 'unmanaged' && meta . manageableState !== undefined ) continue ;
195207 folders . push ( meta ) ;
196208 }
197209 return completion ( null ) ;
@@ -201,12 +213,26 @@ class SfdcApi {
201213 } ) ;
202214 } , function ( err ) {
203215 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+ } ) ;
205231 } ) ; // .async.eachSeries
206232 } ) ;
207233 }
208234
209- getMetadataDetailList ( metadataList , folders ) {
235+ getMetadataDetailList ( metadataList , folders , objLabelMap ) {
210236 const self = this ;
211237 //const excepts = ['LightningComponentBundle'];
212238 return new Promise ( function ( resolve , reject ) {
@@ -227,32 +253,43 @@ class SfdcApi {
227253 let xmlName = ( meta . xmlName == 'EmailTemplate' ) ? 'EmailFolder' : meta . xmlName + 'Folder' ;
228254 for ( let fd of folders ) {
229255 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 } ) ;
231258 }
232259 }
233260 let metadataDetailMap = { } ;
234261 // console.log('>>>> queues', queues);
235262 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 )
238266 . 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)
240278 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' ) ) {
245283 continue ;
246284 }
247- customObjects . push ( meta ) ;
285+ customObjs . push ( meta ) ;
248286 }
249- metadataDetailMap [ queue . type ] = customObjects ;
250- } else {
251- metadataDetailMap [ queue . type ] = metadata ;
287+ metadataDetailMap [ queue . type ] = customObjs ;
252288 }
289+
253290 if ( queue . type == 'CustomObject' || queue . type == 'Workflow' ) {
254291 //TODO SharingRules
255- return self . readChildMetadata ( metadata ) ;
292+ return self . readChildMetadata ( metadata , objLabelMap ) ;
256293 } else {
257294 return true ;
258295 }
@@ -277,7 +314,149 @@ class SfdcApi {
277314 } ) ;
278315 }
279316
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 ) {
281460 const self = this ;
282461 return new Promise ( function ( resolve , reject ) {
283462 if ( metadata . length == 0 ) {
@@ -345,8 +524,15 @@ class SfdcApi {
345524 // Filter standard field
346525 continue ;
347526 }
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+
350536 metadataMap [ child . typeName ] . push ( cMeta ) ;
351537 }
352538 }
@@ -423,42 +609,35 @@ class SfdcApi {
423609 types : pipeline . targetTypes
424610 }
425611 } ) ;
426- const Raven = require ( 'raven' ) ;
427612 retrieveResult . complete ( function ( err , result ) {
428613 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' ) {
438615 return reject ( new Error ( 'Retrieve metadata failed' ) ) ;
439616 }
440617 } ) ;
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+ } ) ;
441631 retrieveResult . stream ( ) . pipe ( zipstream ) ;
442632 } ) ;
443633 }
444634
445635 // 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 ) {
449637 const fileinfo = fs . statSync ( sourceZipPath ) ;
450638 if ( fileinfo . size == 0 ) {
451639 // 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' ) ) ;
462641 }
463642 // Ready to extract
464643 const unzipper = new DecompressZip ( sourceZipPath )
0 commit comments