amtrack / force-dev-tool

[DEPRECATED] Command line tool supporting the Force.com development lifecycle
MIT License
108 stars 37 forks source link

Custom Object with Compact Layout #226

Closed froucher closed 4 years ago

froucher commented 4 years ago

Custom Object with Compact Layout

When a Compact Layout is added to a Custom Object the change set can't be deployed, and it returns the following error:

TYPE   FILE                                      NAME                 PROBLEM
─────  ────────────────────────────────────────  ───────────────────  ──────────────────────────────────────────────────────────
Error  delta/objects/CustomObjectTest__c.object  CustomObjectTest__c  Must specify a non-empty label for the CustomObject

Steps to reproduce the error

  1. Create a new repository

    mkdir force-dev-tool-test
    cd force-dev-tool-test
    git init
  2. Create the simplest Custom Object and commit

    Init the folders

    mkdir src
    mkdir src/objects

    Add a src/package.xml file:

    <?xml version="1.0" encoding="UTF-8"?>
    <Package xmlns="http://soap.sforce.com/2006/04/metadata">
      <types>
        <name>CustomObject</name>
        <members>CustomObjectTest__c</members>
      </types>
      <version>46.0</version>
    </Package>

    Then, create a new file src/objects/CustomObjectTest__c.object with the following content:

    <?xml version="1.0" encoding="UTF-8"?>
    <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
        <actionOverrides>
            <actionName>Accept</actionName>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Accept</actionName>
            <formFactor>Large</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Accept</actionName>
            <formFactor>Small</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>CancelEdit</actionName>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>CancelEdit</actionName>
            <formFactor>Large</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>CancelEdit</actionName>
            <formFactor>Small</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Clone</actionName>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Clone</actionName>
            <formFactor>Large</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Clone</actionName>
            <formFactor>Small</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Delete</actionName>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Delete</actionName>
            <formFactor>Large</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Delete</actionName>
            <formFactor>Small</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Edit</actionName>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Edit</actionName>
            <formFactor>Large</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Edit</actionName>
            <formFactor>Small</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>List</actionName>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>List</actionName>
            <formFactor>Large</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>List</actionName>
            <formFactor>Small</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>New</actionName>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>New</actionName>
            <formFactor>Large</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>New</actionName>
            <formFactor>Small</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>SaveEdit</actionName>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>SaveEdit</actionName>
            <formFactor>Large</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>SaveEdit</actionName>
            <formFactor>Small</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Tab</actionName>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Tab</actionName>
            <formFactor>Large</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>Tab</actionName>
            <formFactor>Small</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>View</actionName>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>View</actionName>
            <formFactor>Large</formFactor>
            <type>Default</type>
        </actionOverrides>
        <actionOverrides>
            <actionName>View</actionName>
            <formFactor>Small</formFactor>
            <type>Default</type>
        </actionOverrides>
        <allowInChatterGroups>false</allowInChatterGroups>
        <compactLayoutAssignment>SYSTEM</compactLayoutAssignment>
        <deploymentStatus>Deployed</deploymentStatus>
        <enableActivities>false</enableActivities>
        <enableBulkApi>true</enableBulkApi>
        <enableFeeds>false</enableFeeds>
        <enableHistory>false</enableHistory>
        <enableLicensing>false</enableLicensing>
        <enableReports>false</enableReports>
        <enableSearch>true</enableSearch>
        <enableSharing>true</enableSharing>
        <enableStreamingApi>true</enableStreamingApi>
        <gender>Feminine</gender>
        <label>CustomObjectTest</label>
        <listViews>
            <fullName>All</fullName>
            <filterScope>Everything</filterScope>
            <label>All</label>
        </listViews>
        <nameField>
            <label>CustomObjectTest Name</label>
            <type>Text</type>
        </nameField>
        <pluralLabel>CustomObjectTest</pluralLabel>
        <searchLayouts/>
        <sharingModel>ReadWrite</sharingModel>
        <visibility>Public</visibility>
    </CustomObject>

    Commit this change

    git add .
    git commit -m "init basic custom object"
    git tag FIRST-COMMIT

    And deploy these changes in a new Salesforce Sandbox called testSandbox

    sfdx force:mdapi:deploy -u testSandbox -d src -w 10
  3. Add a Compact Layout and commit the change

    Add a Compact Layout to src/objects/CustomObjectTest__c.object:

    ...
    <allowInChatterGroups>false</allowInChatterGroups>
    <compactLayoutAssignment>SYSTEM</compactLayoutAssignment>
    <compactLayouts>
        <fullName>compactLayoutTest</fullName>
        <fields>CreatedById</fields>
        <fields>CurrencyIsoCode</fields>
        <fields>Name</fields>
        <fields>LastModifiedById</fields>
        <fields>OwnerId</fields>
        <label>compactLayoutTest</label>
    </compactLayouts>
    <deploymentStatus>Deployed</deploymentStatus>
    ...

    Or if you want to create the Compact Layout directly in the Salesforce Setup then remember to retrieve the changes:

    sfdx force:mdapi:retrieve -u testSandbox -r mdAPIZip -k src/package.xml
    unzip -p mdAPIZip/unpackaged.zip unpackaged/objects/CustomObjectTest__c.object > src/objects/CustomObjectTest__c.object

    Commit this change:

    git add .
    git commit -m "adding compact layout"
    git tag COMPACT-LAYOUT

    And deploy these changes in a new Salesforce Sandbox called testSandbox

    sfdx force:mdapi:deploy -u testSandbox -d src -w 10
  4. Create a change set

    Check differences between two last commits:

    git diff --no-renames FIRST-COMMIT COMPACT-LAYOUT -- src/objects/CustomObjectTest__c.object

    Will return:

    diff --git a/src/objects/CustomObjectTest__c.object b/src/objects/CustomObjectTest__c.object
    index 967b38e..ea324ea 100644
    --- a/src/objects/CustomObjectTest__c.object
    +++ b/src/objects/CustomObjectTest__c.object
    @@ -142,6 +142,15 @@
        </actionOverrides>
        <allowInChatterGroups>false</allowInChatterGroups>
        <compactLayoutAssignment>SYSTEM</compactLayoutAssignment>
    +    <compactLayouts>
    +        <fullName>compactLayoutTest</fullName>
    +        <fields>CreatedById</fields>
    +        <fields>CurrencyIsoCode</fields>
    +        <fields>Name</fields>
    +        <fields>LastModifiedById</fields>
    +        <fields>OwnerId</fields>
    +        <label>compactLayoutTest</label>
    +    </compactLayouts>
        <deploymentStatus>Deployed</deploymentStatus>
        <enableActivities>false</enableActivities>
        <enableBulkApi>true</enableBulkApi>
    (END)

    So create a change set

    git diff --no-renames FIRST-COMMIT COMPACT-LAYOUT -- src/objects/CustomObjectTest__c.object | force-dev-tool changeset create -f delta --apiVersion 46.0 -d ./publish

    The delta generates the following file publish/delta/objects/CustomObjectTest__c.object:

    <?xml version="1.0" encoding="UTF-8"?>
    <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
        <compactLayouts>
            <fullName>compactLayoutTest</fullName>
            <fields>CreatedById</fields>
            <fields>CurrencyIsoCode</fields>
            <fields>Name</fields>
            <fields>LastModifiedById</fields>
            <fields>OwnerId</fields>
            <label>compactLayoutTest</label>
        </compactLayouts>
    </CustomObject>

    If you try to deploy:

    sfdx force:mdapi:deploy -u testSandbox -c -d publish/delta -w 10

    It will return the following in sequence errors:

    TYPE   FILE                                      NAME                 PROBLEM
    ─────  ────────────────────────────────────────  ───────────────────  ──────────────────────────────────────────────────────────
    Error  delta/objects/CustomObjectTest__c.object  CustomObjectTest__c  Must specify a non-empty label for the CustomObject
    Error  delta/objects/CustomObjectTest__c.object  CustomObjectTest__c  Must specify a non-empty plural label for the CustomObject
    Error  delta/objects/CustomObjectTest__c.object  CustomObjectTest__c  Must specify a nameField of type Text or AutoNumber
    Error  delta/objects/CustomObjectTest__c.object  CustomObjectTest__c  Must specify a deployment status for the Custom Object
    Error  delta/objects/CustomObjectTest__c.object  CustomObjectTest__c  Must specify a sharing model value for the Custom Object

    Fixing the missing metadata then it will work:

    <?xml version="1.0" encoding="UTF-8"?>
    <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
        <compactLayouts>
            <fullName>compactLayoutTest</fullName>
            <fields>CreatedById</fields>
            <fields>CurrencyIsoCode</fields>
            <fields>Name</fields>
            <fields>LastModifiedById</fields>
            <fields>OwnerId</fields>
            <label>compactLayoutTest</label>
        </compactLayouts>
        <deploymentStatus>Deployed</deploymentStatus>
        <label>CustomObjectTest</label>
        <nameField>
            <label>CustomObjectTest Name</label>
            <type>Text</type>
        </nameField>
        <pluralLabel>CustomObjectTest</pluralLabel>
        <sharingModel>ReadWrite</sharingModel>
    </CustomObject>

Solution

I think the change set in Custom Object must always contains the following xml properties:

<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
  ...
  change set
  ...
    <deploymentStatus>Deployed</deploymentStatus>
    <label>CustomObjectTest</label>
    <nameField>
        <label>CustomObjectTest Name</label>
        <type>Text</type>
    </nameField>
    <pluralLabel>CustomObjectTest</pluralLabel>
    <sharingModel>ReadWrite</sharingModel>
</CustomObject>

Do you agree with this solution? If you do then please let me know if I can help developing the required changes...

amtrack commented 4 years ago

Hey @froucher, thank you very much for your very detailed report! I really appreciate that! 🙏

Yes, I agree that the changeset create command needs to include required properties. Additionally to CustomObject this should also be the case for PermissionSet, Profiles and probably more Metadata Types.

Do you already have an idea on how to implement this? As you know, I've deprecated this project long time ago and didn't want to start new development but I'm open to collaborating on this. How about a web meeting to brainstorm and discuss a rough plan?

froucher commented 4 years ago

Hi,

Yes I would really appreciate your help with a web meeting.

During these days I have reviewed the code, but I am not sure where is the best point to add it.

Maybe the end of the 'changeset' process:

changeset.js:

    es.merge(stdin, metadataContainer.getStream())
        .pipe(MetadataContainer.completeMetadataStream())
        .pipe(MetadataContainer.outputStream({
            apiVersion: apiVersion
        }))
        .pipe(vinylFs.dest(deploymentPath))
        .on('end', function() {
            callback(null, "exported metadata container to " + path.relative(proc.cwd, deploymentPath));
        });

Inside of the function completeMetadataStream() and completeMetadataWith()

MetadataContainer.completeMetadataStream = function(opts) {
    opts = opts || {};
    return miss.through.obj(function(metadataContainer, enc, cb) {

        metadataContainer = metadataContainer.completeMetadataWith({
            path: opts.path || 'src'
        }).filter(metadataContainer.manifest);
        metadataContainer.manifest.rollup();
        metadataContainer.destructiveManifest.filterUnnamed();

        cb(null, metadataContainer);
    });
}

But not sure if it is the best point to add this patch.

Thanks.

froucher commented 4 years ago

I am working in the PR #228

froucher commented 4 years ago

As we have talked, we will reject the PR, because the solution must be implemented differently due to "Compact Layout" isn't a "independent child", that means can't be treated separately from the rest of the metadata file. So we are going to remove "Compact Layout" from ChildXmlName.

So the solution I understood in our talk is more o less:

Given we create a change set with force-dev-tool
When a list of independent child is added/modified
Then it will create a change set with the list of independent "component" 
 And excluding any metadata of the parent metadata

Given we create a change set with force-dev-tool
When a list property that doesn't belong to an independent child is added/modified
Then it will create a change set with the all "metadata" 
 And excluding all independent child metadata

Given we create a change set with force-dev-tool
When a list of independent child is added/modified
 And a list property that doesn't belong to an independent child is added/modified
Then it will create a independent "component" with the property value
 And it will create a change set with the all "metadata" 

Do you agree with the above behaviours?

Thank you for your support.

amtrack commented 4 years ago

@froucher Yes, I agree.

Let's continue this in #13 .

froucher commented 4 years ago

This is the new PR #232

amtrack commented 4 years ago

resolved by #232