Salesforce Marketing Cloud January 2017 Release

icon-cloud-marketing

This post marks the first Salesforce Marketing Cloud related post on this blog, an event reflective of the increasing number of Salesforce implementations that span both the Salesforce and Marketing Cloud platforms (or cross-cloud, a term I can’t seem to stop using). Architects working on such implementations require a solid understanding of Marketing processes and both the functional and technical composition of the Marketing Cloud platform – not to mention the various APIs, connectors and 3rd party solutions offered via the HubExchange. Such a grounding is necessary to allow business processes (that are incidentally cross-cloud) to be understood and optimally implemented. This point is key; in ideal terms marketing processes should be integral parts of wider/deeper business processes that touch upon multiple areas of the business in pursuit of better customer experience or engagement. This type of thinking is key to realising current industry trends such as “Continuous Experience” where classic organisational structures (sales, service and marketing operations) are abandoned, or diminished, in favour of delivering unified customer journeys across all touch-points. For architects tasked with delivery of such solutions, the challenge starts with marketing domain knowledge and Marketing Cloud practitioner insight. In my own recent experience both stated aspects can benefit greatly from the combination of website/blog trawling, Trailhead and certification (Salesforce Certified Marketing Cloud Consultant). I completed the two required exams for this certification recently and found the experience challenging and time consuming but ultimately rewarding and definitely something I’d recommend to all Salesforce architects.

And so, on to the actual topic for this post – a review of the key features within the first of five major releases for the Salesforce Marketing Cloud scheduled for 2017.

The release notes are available here. The release is due to occur on the 27th January – this of course is subject to change.

– features are GA if not indicated otherwise

Marketing Cloud Connect – Sales & Service Cloud Activities
The ability to create Salesforce Activity records within a Journey Builder definition is now more intuitive via a new Lightning UI that abstracts the complexity of the WhatID/Who Id model for relating Activity records to Leads, Contacts and related records. Salesforce record interactions from within Journey Builder are key to blending the power of the two cloud platforms.

Content Builder – Themed Templates
Email messages can now be created from Themed Templates that encapsulate best practice for content creation. The templates provided cover Financial Services, Retail, Restaurant and Newsletter scenarios. The Themed Templates options can be found in the Define Properties step of the content creation flow.

Journey Builder – History Tab
The Contacts tab in the main Journey Builder navigation has been replaced with a History tab that displays the status of running journeys along with failure reasons to aid troubleshooting.

Marketing Cloud Mobile App
The January release brings an Android version of the mobile app and a new home dashboard for the iOS version. The Android version exhibits the same Lightning Experience UI and can be downloaded from the Google Play Store. The new app version will be released 2 weeks approximately after the main release date subject to Google/Apple app review. The new home page for the iOS version of the mobile app supports daily or weekly reminders and the delivers key performance statistics plus the current status of marketing automations. Note, the reminders work as push notifications and appear as badges on the app icon (as per email, SMS messages etc.). The new version of the iOS app also supports SMS campaigns via the SMS button in the primary navigation.

Social Studio – Emoji Support
Emojis come to life in the January 2017 release. The Publish component now supports the use of social network specific emojis within social content creation via the Emoji Picker (Composer and Inspector). Emojis are also correctly rendered by Engage and can be used to infer sentiment within Analyze thereby increasing accuracy.

Social Studio – Facebook Reviews
Social Studio now provides features to manage the reputation for a local Facebook page. Facebook reviews can be organised to filter promoters from detractors, automated actions can also be triggered based on review score. For example low scores could invoke a Journey Builder journey or Service Cloud case. Powerful stuff.

Social Studio – Analyze Dashboards
The Analyze component of Social Studio receives significant enhancement in respect to dashboards. The new release supports mixed dashboards showing content across multiple social accounts or topic profiles and expanded real time date options. Advanced card configurations enable filters, custom names and custom dimensions to be applied to individual cards within a dashboard.

Web Studio – Smart Capture to Lists
Previously Smart Capture Blocks defined within Content Editor were limited to Data Extensions for data push, with the January release this is extended to Lists.

Salesforce Spring ’17 Platform Highlights

Accompanied by a huge (472-page) release notes document, the Spring ’17 release rolls out in early February. For perhaps the first time (to my recognition) a Lightning Experience (LEX) only theme is identifiable across the newly introduced set of features. This isn’t exhaustive but interesting nonetheless as historically any functional disparity typically favoured Salesforce Classic. Adoption rates for LEX will certainly be one-factor behind this approach, it’s also not surprising to see investment going into the future platform. What this does underscore is the growing status of Salesforce Classic as a legacy platform despite the high proportion of implementations yet to transition.

This post briefly outlines selected highlights related to the Force.com platform (in no order of significance).

The new release is generally available now for pre-release preview.

spring17logo

– features are GA if not indicated otherwise

External Services (Beta / LEX-only)
This new LEX-only feature provides a non-programmatic method for invoking external web service operations. The external service is registered (via JSON Schema) which generates proxy/wrapper Apex classes that can then be introduced into Flows which encapsulate the required business process automation. It will be interesting to see the code generated by the registration process, this could be a useful convenience irrespective of whether Flow is being. As a huge advocate of Flow (or Visual Workflow) it’s great to see investment that enables additional use cases. I’m slightly sceptical about non-technical treatment of API consumption scenarios, but that said anything that reduces the requirement for code should be a good thing.

external-service

Data Integration Rules
Data.com Clean Rules will be replaced with a generic framework for the definition of Data Integration Rules provided by external parties. Some rules will incur a cost, others such as the Data.com Geocode Address rules are free-of-charge. The term integration in this context relates to data enrichment i.e. a rule updates mapped fields on a given record from an external data source. The update can occur on-demand per-record or en-masse via data refresh (note, the Geocode address rule does not support data refresh). The ISV side of things isn’t clear at this stage in respect to publication of new Integration Rules.

External Data – Cross-org Adapter for Salesforce Connect
The REST API based Cross-org adapter for Salesforce Connect now supports write operations. Given the cost implications of this approach it will be interesting to see how much traction this gains – Salesforce-to-Salesforce whilst technically very different (data synchronisation etc.) does address the same set of use cases without the additional licence cost.

Lightning Console Apps (Beta)
Spring ’17 delivers a beta version of Lightning Console Apps. As one would expect Sales and Service standard console apps are provided plus the ability to define a custom console app. New Lightning Components (Related List, Related Record) and a Lightning Page Template (3 columns) are provided to enable implementation of console record detail pages comparable with Salesforce Classic. Given the beta status, lack of migration path and the number of omitted features it’s unlikely that customers will view this as a viable option right now, however now is the time to start thinking about a transition later in the year. Clearly compatibility with Omni-Channel, telephony solutions etc. will be fundamental to such planning.

lightning-console

Lightning App Builder
The Lightning App Builder gets a serious overhaul in Spring ’17 with a new UI structure, additional templates, page cloning and the ability to assign record pages by app, record type and profile being the feature highlights. The latter point being key to delivering a customised experience across users and business processes.

lightning-app-builder

Lightning API (Developer Preview)
The Lightning API is a REST API targeted at custom development use cases (mostly but not exclusively mobile) where the response payload can include data, metadata and layout information. Clearly the value-add here is efficiency – one API call to retrieve all the information that should be required to build the requisite UI/interaction. It should be noted that the API is rate-limited and will return a 503 if the limit is reached; careful client-side cache control logic should reduce the risk of this happening.

Lightning Experience – Filter Reports via URL
At long last it is now possible to set field filter values for reports via URL in LEX. This simple capability has historically played a significant role in most implementations; custom links on report detail pages, bookmarked reports etc. Good to see another LEX obstacle removed.

ISV – Subscriber Org Debugging
ISV partners now have access to the ISV Customer Debugger, meaning for each LMO a single Apex Debugger session can be run at a time against a customer sandbox. The Apex Debugger runs in the Force.com IDE (i.e. eclipse) and provides enhanced support for code debugging; breakpoints, step into/over, call stack inspection etc. This powerful tool is normally a paid-for service – providing a free license to ISV partners will be well appreciated.

ISV – List Lightning Bolt Solutions
At some point after the initial rollout it will be possible for Salesforce partners to distribute Bolt solutions as managed packages listed on the AppExchange. A Bolt solution is prefabricated Community template typically targeting a vertical solution or providing a foundation for horizontal solutions such as booking/event management, membership etc. It will be interesting to see the traction for listed Bolt solutions over the coming months, I think the opportunity here could be significant for both ISV and consulting partners. Anyway it’s great to see the AppExchange now supported low-level Lightning Components and high-level Bolt solutions in addition to the standards Apps and Consultancy services.

Skype for Salesforce (Beta)
Check online status, chat and make audio/video calls directly within Salesforce. Chat transcripts can be saved as Notes. All useful and convenient functionality. A “Skype for Business” component can be added to LEX Pages via the Lightning App Builder. The component provides the same functionality for records with at least one email address field.

Kanban Views
List views now support easy switching between the default grid layout and the Kanban style visual layout. Custom Kanban settings and record types are also supported, the latter appear as tabs above the board.

kanban-settings

AccountContactRelation Enhancements
The AccountContactRelation standard object which allows Contacts to be related to multiple Accounts receives further enhancement in Spring ’17. Relationships can now be defined between Person Accounts and Contacts, the object supports process automation (workflow, Process Builder etc.) and custom buttons, links and actions. I’m a big fan of this seemingly innocuous feature; great to see the additional capabilities being added each release.

Apex Stub API
The Apex Stub API provides a mocking framework for use in test code development. Typically mocks are used to isolate code for specific logical test cases or to allow streamlining of unit tests. Mocking frameworks are common to other programming languages, as such it’s great to have a standard approach for Apex.

Goodbye Force365 Hello Audit9

Regular visitors to this blog may notice that the title has changed recently from Force365 to Audit9.blog. I’ve done this firstly to remove the term “Force” which proved problematic in establishing an ISV contract with Salesforce for the AppExchange listed clearMDM product (something for other bloggers to consider perhaps) and secondly to consolidate all my activities (both personal and otherwise) against a single identity. I hope this makes sense – the content on this blog moving forward will be the same as ever, thanks for reading.

Salesforce ISV Environment Strategy

salesforce-isv-environment-strategy-all

Please note – This content was previously posted on the audit9.com site directly and is re-published here as part of the consolidation of 2 blogs.

This post provides an overview of the various environments involved in delivering ISV solutions to the Salesforce AppExchange. As will be obvious to architects working outside of the ISV domain, the ISV environment strategy has some commonality to the non-ISV approach in respect to the development and testing activities but also introduces a further level of complexity and environment roles unique to the AppExchange distribution model.

Primary Org Definitions

Packaging Org

For small-scale, low complexity managed package developments the packaging and development orgs may be combined.

1. Package/Patch Upload
The Packaging Org is where the Managed Package is created and its namespace prefix defined. Package uploads in either Managed Beta or Managed Release state are initiated from this environment.

2. Patch Development Org – PDO
PDO are short-lived orgs created specifically for Patch development. Only certain components can be modified in a PDO. Once ready for distribution a Patch can be uploaded and pushed to Subscriber Orgs.

3. Push Upgrades
Uploaded Major Releases and Patches can be scheduled for push distribution to selected (or all) Subscriber Orgs. Package subscribers have no control over whether to accept pushed changes and as such it is not advisable (unless with tacit agreement) to push changes that impact the end user experience in any material way.

4. Localisation
Translation Workbench based localisation of internationalised resources.

Partner Business Org

A Partner Business Org can be provisioned upon partnership agreement with Salesforce or the same environment roles can be established within an existing production org.

1. Environment Hub
There Environment Hub enables partner orgs to be created for Development, Test and Trialforce usage. Single Sign-on can be implemented across connected orgs. A hub member org can only be connected to one hub at any given point in time. Where Trialforce is being implemented the TMO and Packaging Org must be connected to the same Environment Hub. The Environment Hub can create TSO directly, although bypassing a TMO prevents implementation of custom branding.

2. AppExchange Publishing Org – APO
This org role is for AppExchange management and is where Private and Public Listings and the Publisher Profile are stored.

3. Licence Management Org – LMO
This org role relates to the License Management App, a custom app installed by Salesforce which enables management of Packages, Package Versions, Licenses and Subscriber data resulting from AppExchange interactions. The LMO is also the entry point for Subscriber Org Login Access.

4. Channel Orders Application – COA
This org role relates to the Channel Orders app (also termed the Partner Order app), a custom app installed by Salesforce which supports automation of Service Order submissions (relating to AppExchange sales) to Salesforce.

5. Usage Metrics Reporting
Usage Metrics provides a daily update of statistics relating to Custom Object record counts (snapshot) and Visualforce Page access and performance (summaries) across all production Subscriber Orgs. There is no opt-in required for Package subscribers. The usage data is written to objects that are API accessible only; a visualisation app is available on the AppExchange to provide convenient access. Usage Metrics provide the basis for monitoring usage patterns and proactive customer retention process.

Trialforce Orgs

AppExchange listings can offer customers the ability to trial an app within a new, pre-configured Trial Org (package, data and users). This highly valuable capability is implemented with Trialforce. Trial organisations can also be created directly from Signup forms hosted on an external website or via the API. In addition to offering free trials the Trialforce solution provides a convenient option for the quick creation of pre-configured orgs for QA or demonstration purposes.

1. Trialforce Management Org – TMO
An optional environment used for centralised management of TSO for different packages. TSO can be created directly from the Environment Hub also, however such this direct approach does not support custom branding. The TMO approach does enable custom branding for Login Pages and Email Templates.

2. Trialforce Source Org – TSO
The TSO is where the package is installed (or upgraded) and configured to reflect the trial experience (user profiles, sample data etc.) A snapshot is taken of the Source org state to create a template. Note, TSO expire after a 12 month period unless a partner case is raised to request an extension. TSO are linked to the AppExchange Publishing Console manually, once a link is in place templates are listed on the Trial Templates tab.

3. Trialforce Template.
Templates listed in the Publishing Console can be submitted for Security Review. This reviews takes less time than the Package Security Review. Once approved a template can be connected to an AppExchange listing at which point the link to start a free trial appears on the AppExchange listing.

Note, each time a package is updated the TSO must be upgraded and a new template created, reviewed and connected to the AppExchange listing.

The ISVforce Guide provides further detail on the environments listed in the preceding sections.

InvocableMethod Invocations via Process Builder

As I’ve previously stated the combination of Process Builder and Apex Actions is incredibly powerful.

This short post adds the answer to a frequently asked question; how many times is the InvocableMethod called when records are modified in bulk?

To answer this with an example. With default batch settings (200 records) and 201 modified records the InvocableMethod is called twice (2 times – 1 invocation per-batch), first call with 200 records then a second call with 1 record. Not 201 individual calls!

If the Process applies entry criteria met by only 10 of the first batch – plus the single record in the second batch – there would again be 2 calls (10, then 1). So the assertion holds true; one invocation per batch.

This bulk behaviour is why the @InvocableMethod input parameter is a list of course; however it isn’t obvious in the related documentation.

Salesforce Winter ’17 Platform Highlights

Once again regretfully it’s time to start talking about Winter; the Winter ’17 (v38.0) release that is. This (pre-Dreamforce ’16) release sees the return of a snowman logo (last seen in Winter ’10) – albeit rather glum looking on this occasion – despite the animated wink ;-); perhaps Dreamforce ’16 will cheer him/her up.

The new release is generally available now for pre-release preview. The preview release notes are available here.

The Winter ’17 sandbox preview starts early September 9th, with production orgs being upgraded late September/early October. Full details of the release schedule can be found on the trust.salesforce.com site.

images

This post briefly outlines selected highlights related to the Force.com platform (in no order of significance).

– features are GA if not indicated otherwise

App Launcher
Winter ’17 re-introduces Application as a primary navigation concept; with previous LEX versions Applications are essentially logical groupings of functions within the App Switcher but had no further relevance – Navigation Menus are defined at the Profile Level not per-Application. With Winter ’17 Custom Applications are defined as Lightning Applications, tabs are added as-per Salesforce Classic – so really the only difference is the position of the App Launcher (far-left of the tab bar) and its ability to open a tab directly. The new App Launcher can also be set as the default page.

Winter 17 - App Launcher

AccountContactRelation Person Account Compatibility
The Summer ’16 release introduced a standard object version of the junction object typically implemented between Account and Contact to support indirect relationships (where more detail is required than ContactRoles allows). Winter ’17 provides Person Account compatibility and also support for Apex Triggers and Validation Rules. This is great functionality; Person Accounts (in a primarily B2C context perhaps) can now reflect any Business Account relationships and vice-versa using standard features.

Lightning Component Actions
Lightning Components that implement the new force:LightningQuickAction or
force:LightningQuickActionWithoutHeader interfaces can be invoked from a Custom Action added to a Page Layout.

Winter 17 - Lightning Component Action

Winter 17 - Lightning Component Action 2

Spanning Relationships Limit Increase
The Spanning Relationships limit applies to declarative features such as formulas, workflow rules and validation rules and constrains the number of unique object references. Previously this limit was 10 but could be lifted to 15 via Salesforce Support. Hopefully the new value of 15 can also be increased as this particular limit can be serious scalability constraint particularly where the org customisation has made no effort in respect to conservation.

Invoke a Process from a Process
Invocable Processes allow one Process to call another Process; this allows a modular design approach to be taken to support reuse of processes as units of logical encapsulation.

Winter 17 - Invocable Process

Flows for Lightning Experience (Beta)
With this beta feature enabled URL-based flows can execute with the Lightning runtime rather than the Salesforce Classic runtime. It’s great to see Force.com Flow (or Visual Workflow) entering the Lightning era – automated conversion of existing Flows to Lightning Experience is a real benefit for implementations that have taken advantage of this incredibly powerful and much overlooked capability. Winter ’17 also provides the ability to embed flows within Lighting Pages (App, Record and Home pages).

Use SLDS in Lightning Apps
Lightning App definitions can now automatically reference the latest Salesforce Lightning Design System (SLDS) styles and design tokens. This is achieved by defining the application as extends=”force:slds”. This approach is recommended over the Static Resource approach where a particular version of the SLDS is statically referenced.

Automated Package Installation via API
ISVs are now able to automate managed package push upgrades via the API. This capability provides support for use cases such as customers accepting an upgrade offer sent via email or submitting a web form to request an upgrade. The Tooling API now supports automated upload of packages, upload status monitoring and the retrieval of installation Urls for distribution to subscribers – via the PackageUploadRequest object (and related).

Omni-Channel Routing for Chats (Beta)
A new Routing Type “Omni” enables Omni-Channel to route and prioritise Live Agent chats alongside other types of Work Item. Previously Live Agent chats were routed using skills and Agent availability only. As with Omni-Channel generally, this feature is Salesforce Classic only at this time.

Lightning Login
Passwords can be frustrating for users and a system administration nightmare. With Lightning Login user authentication can be achieved through a simple mobile-based approval. The user enters their username and clicks the Lightning Bolt, a notification is sent to the Salesforce Authenticator app (iOS and Android), user completes authentication in the app via Fingerprint or PIN. The feature works in both LEX and Salesforce Classic and is assigned to users via Permission Set.

Visualforce for Lightning Experience by Example

This post provides a code example for a multi-purpose Visualforce page that dynamically switches between Salesforce Classic and Lightning Experience (LEX) views based on the detected user setting.

At some future stage (perhaps Dreamforce ’16 will enlighten us here) the platform will likely provide support for auto-conversion of legacy Visualforce assets to the new Lightning UI. For those that can’t wait for this capability and require the means to build custom UI that blends with both Salesforce Classic and Lightning Experience the following example hopefully provides a useful reference. Please note, this is provided for information purposes only.

In conjunction with the example Visualforce markup, the following points should be considered:

1. For each interaction (page or sub-page) a decision should be taken as to the development approach:

CSS Switching: UI structure is consistent across views (and no page block components), therefore style class switching may work.

Conditional Visualforce: UI structure is partially consistent; perhaps the LEX view introduces a new activity timeline component for example but is otherwise closely aligned to the Salesforce Classic view. A lack of Lightning Component Framework expertise or availability may also lead to this approach.

Lightning Components: The LEX view is developed with the Lightning Component Framework; components are added to the Visualforce page via apex:includeLightning and conditionally rendered.

2. The Salesforce Lightning Design System is a CSS framework, not a JavaScript framework. In order to build UI with the SLDS requires working knowledge of a JavaScript library such as JQuery to add interactivity such as tab-switching, drop-down menu animation and tooltip display.

3. The ideal of a completely dynamic solution where certain users have the LEX view and others the Salesforce Classic view is somewhat spoiled by the constraint that application and tab icons must be shared between the two views. Predictably, icons that work well in the LEX Navigation Menu do not work at all in Salesforce Classic. Equally the square application icon style preferred in LEX looks visually jarring when rendered in the Salesforce Classic header.

4. LEX compatible pages should be responsive, Salesforce Classic pages rarely have this requirement.

5. Key Lightning Ready requirements include correct handling of the Navigation Menu expand/collqpse events, use of the Salesforce font and visual consistency with the SLDS guidelines.

The screenshots below show how the example page renders in both Salesforce Classic and LEX views.

Settings - Salesforce Classic View

Settings - LEX View

Example Visualforce page; note this example takes the “Conditional Visualforce” approach introduced above.


<!-- 
Name: Settings.page (abbreviated)
Copyright © 2016  Force365
======================================================
======================================================
Purpose:
-------
Example Settings management page.

Multiple purpose page shared between LEX and Classic themes.
> Theme3—Salesforce Classic 2010 user interface theme
> Theme4d—Modern “Lightning Experience” Salesforce theme
> Theme4t—Salesforce1 mobile Salesforce theme

Functions :
View and maintain Application Settings, Target Objects and Data Sources.
======================================================
======================================================
History
------- 
redacted.
 -->
<apex:page tabStyle="Settings__tab" 
            controller="SettingsController" 
            readOnly="false" 
            title="{!$Label.UI_Text_MDM} {!$Label.UI_Text_Settings}"
            docType="html-5.0">

    <apex:form >

        <!--  Lightning Experience Theme [Theme4d] + Theme4t—Salesforce1 mobile Salesforce theme -->
        <apex:outputPanel id="lexPanel" rendered="{!OR( $User.UIThemeDisplayed == 'Theme4d', $User.UIThemeDisplayed == 'Theme4t') }">
            
            <apex:includeScript value="{!URLFOR($Resource.PrimaryResources, '/js/jquery-1.11.3.min.js')}" />
            <apex:stylesheet value="{!URLFOR($Resource.SLDS202, 'assets/styles/salesforce-lightning-design-system-vf.css')}" />     
    
            <script type='text/javascript'>
                var $j = jQuery.noConflict();
        
                $j(document).ready(function(){              
                    init();
                });
                
                function init(){            
                    // tab hide/show content.
                    $j('.slds-tabs--scoped__item').off('click');
                    
                    $j('.slds-tabs--scoped__item').on('click', function(){
                        $j(this).addClass('slds-active');
                        $j(this).find('a').attr('aria-selected', true);
                        var contentToShow = $j('#'+$j(this).find('a').attr('aria-controls'));
                        
                        $j(contentToShow).removeClass('slds-hide');
                        $j(contentToShow).addClass('slds-show');                        
                    
                        $j(this).siblings().removeClass('slds-active');
                        $j(this).siblings().find('a').attr('aria-selected', false);
                        $j(contentToShow).siblings('.slds-tabs--scoped__content').removeClass('slds-show');
                        $j(contentToShow).siblings('.slds-tabs--scoped__content').addClass('slds-hide');
                    }); 
                
                    // data table menu drop downs.
                    $j('.slds-dropdown-trigger .slds-button').off('click');
                    
                    $j('.slds-dropdown-trigger .slds-button').on('click', function(){
                    
                        event.stopPropagation();
                        
                        if ($j(this).parent().hasClass('slds-is-open')){
                            $j(this).parent().removeClass('slds-is-open');
                            $j(this).parent().attr('aria-expanded', false); 
                        } else {                        
                            $j('.slds-dropdown-trigger').removeClass('slds-is-open');
                            $j('.slds-dropdown-trigger').attr('aria-expanded', false);                      
                                                
                            $j(this).parent().addClass('slds-is-open');
                            $j(this).parent().attr('aria-expanded', true);
                        }
                    });
                                        
                    $j(document).click( function(){
                        $j('.slds-dropdown-trigger').removeClass('slds-is-open');
                        $j('.slds-dropdown-trigger').attr('aria-expanded', false);                      
                    });
                            
                    // tooltip hide/show on hover over information icons related to form elements.
                    $j( '.slds-form-element__icon' ).hover(
                        function() {
                        
                            var tooltip = $j(this).find('.slds-popover');
                            tooltip.css( { position:'absolute',left:'0px',margin:'-1.5rem',width:'20rem' } );
                            
                            var t = $j(tooltip).outerHeight()-15;                           
                            t*=-1;
                            tooltip.css( { top:t } );
                            
                            tooltip.removeClass('slds-hide');
                            tooltip.addClass('slds-show');                      
                        }, function() {
                            
                            var tooltip = $j(this).find('.slds-popover');
                            tooltip.removeClass('slds-show');
                            tooltip.addClass('slds-hide');
                        }
                    );                      
                }
                
                function showSpinner(){
                    $j('.slds-spinner_container').removeClass('slds-hide');
                    $j('.slds-spinner_container').addClass('slds-show');    

                    var winWidth = $j(document).width();
                    var winHeight = $j(document).height();
                    
                    $j('.slds-spinner_container').css({ 'width': winWidth,'height': winHeight });                   
                }
                
                function hideSpinner(){                 
                    $j('.slds-spinner_container').removeClass('slds-show');
                    $j('.slds-spinner_container').addClass('slds-hide');    
                }   
                            
            </script>
                        
            <style>
                .myapp-icon-container-background-color {
                    background-color: #73BFB3;
                }
                .myapp-column--overflow-visible {
                    overflow: visible !important;
                }
                html body.sfdcBody {
                    padding: 0 !important;
                }               
            </style>
                        
            <!-- custom scope wrapper -->           
            <div class="myapp">
            
                <div class="slds-spinner_container slds-hide">
                    <div class="slds-spinner--brand slds-spinner slds-spinner--medium" aria-hidden="false" role="alert">
                        <div class="slds-spinner__dot-a"></div>
                        <div class="slds-spinner__dot-b"></div>
                    </div>
                </div>      

                <!-- header -->
                <div class="slds-page-header" role="banner">
                    <div class="slds-media slds-media--center">                     
                        <div class="slds-media__figure">
                            <span class="slds-icon_container myapp-icon-container-background-color">
                                <img class="slds-icon" src="{!URLFOR($Resource.myapp__IconResources, 'Settings-icons_white-01.png')}" alt="portrait" />                          
                            </span>
                        </div>
                        <div class="slds-media__body">
                            <p class="slds-text-body--small page-header__info">{!$Label.UI_Text_MDM}</p>
                            <p class="slds-page-header__title slds-truncate slds-align-middle" title="{!$Label.UI_Text_Settings}">{!$Label.UI_Text_Settings}</p>                            
                        </div>
                    </div>
                </div>                  
                <!-- / header -->

                <!-- content -->
                <apex:outputPanel id="missingStandardPermissionsPanelLEX" rendered="{!NOT(hasStandardPermissions)}">
                    <div class="slds-notify slds-notify--alert slds-theme--error" role="alert">
                        <span class="slds-assistive-text">Info</span>
                        <h2>{!$Label.UI_Error_No_Customize_Application_Permissions}</h2>
                    </div>
                </apex:outputPanel>
            
                <apex:outputPanel id="noCustomPermissionPanelLEX" rendered="{!AND(hasStandardPermissions,NOT($Permission.Manage_Settings))}">
                    <div class="slds-notify slds-notify--alert slds-theme--error" role="alert">
                        <span class="slds-assistive-text">Info</span>
                        <h2>{!$Label.UI_Error_No_Manage_Settings_Permission}</h2>
                    </div>
                </apex:outputPanel>
    
                <apex:outputPanel id="msgsRefreshPanelLEX">
                    <span class="slds-assistive-text">Info</span>
                    <apex:Messages id="msgsLEX" styleclass="slds-notify slds-notify--alert slds-theme--error" html-role="alert"/>
                </apex:outputPanel>
            
                <apex:outputPanel id="mainPanelLEX" rendered="{!AND(showMainPanel,$Permission.Manage_Settings,hasStandardPermissions)}">                
                    
                    <div class="slds-tabs--scoped">
                        <ul class="slds-tabs--scoped__nav" role="tablist">
                            <li class="slds-tabs--scoped__item slds-text-heading--label {!IF(selectedTab=='applicationSettings', 'slds-active', '')}" title="{!$Label.UI_Text_Application_Settings}" role="presentation">
                                <a class="slds-tabs--scoped__link" href="javascript:void(0);" role="tab" tabindex="0" aria-selected="true" aria-controls="tab-scoped-1" id="tab-scoped-1__item">{!$Label.UI_Text_Application_Settings}</a>
                            </li>                       
                            <li class="slds-tabs--scoped__item slds-text-heading--label {!IF(selectedTab=='targetObjects', 'slds-active', '')} {!IF(appSettings.IsActive__c, 'slds-show', 'slds-hide')}" title="{!$Label.UI_Text_Target_Objects}" role="presentation">
                                <a class="slds-tabs--scoped__link" href="javascript:void(0);" role="tab" tabindex="-1" aria-selected="false" aria-controls="tab-scoped-2" id="tab-scoped-2__item">{!$Label.UI_Text_Target_Objects}</a>
                            </li>
                            <li class="slds-tabs--scoped__item slds-text-heading--label {!IF(selectedTab=='dataSources', 'slds-active', '')} {!IF(appSettings.IsActive__c, 'slds-show', 'slds-hide')}" title="{!$Label.UI_Text_Data_Sources}" role="presentation">
                                <a class="slds-tabs--scoped__link" href="javascript:void(0);" role="tab" tabindex="-1" aria-selected="false" aria-controls="tab-scoped-3" id="tab-scoped-3__item">{!$Label.UI_Text_Data_Sources}</a>
                            </li>
                            <li class="slds-tabs--scoped__item slds-text-heading--label {!IF(selectedTab=='dynamicHierarchies', 'slds-active', '')} {!IF(appSettings.IsActive__c, 'slds-show', 'slds-hide')}" title="{!$Label.UI_Text_Dynamic_Hierarchies}" role="presentation">
                                <a class="slds-tabs--scoped__link" href="javascript:void(0);" role="tab" tabindex="-1" aria-selected="false" aria-controls="tab-scoped-4" id="tab-scoped-4__item">{!$Label.UI_Text_Dynamic_Hierarchies}</a>
                            </li>
                            <li class="slds-tabs--scoped__item slds-text-heading--label {!IF(selectedTab=='customRollups', 'slds-active', '')} {!IF(appSettings.IsActive__c, 'slds-show', 'slds-hide')}" title="{!$Label.UI_Text_Custom_Rollups}" role="presentation">
                                <a class="slds-tabs--scoped__link" href="javascript:void(0);" role="tab" tabindex="-1" aria-selected="false" aria-controls="tab-scoped-5" id="tab-scoped-5__item">{!$Label.UI_Text_Custom_Rollups}</a>
                            </li>
                        </ul>
                        <div id="tab-scoped-1" class="slds-tabs--scoped__content {!IF(selectedTab=='applicationSettings', 'slds-show', 'slds-hide')}" role="tabpanel" aria-labelledby="tab-scoped-1__item">
                            
                            <apex:outputPanel layout="block" id="applicationSettingsRefreshPanelLEX">
                            
                                <h3 class="slds-section-title--divider">{!$Label.UI_Text_Application_Settings}</h3>
                                <div class="slds-m-vertical--small"></div>
                                <fieldset class="slds-form--compound">
                                    <div class="form-element__group">
                                        <div class="slds-form-element__row">
                                            <div class="slds-form-element slds-size--1-of-2">
                                            
                                                <label class="slds-checkbox">
                                                    <apex:inputCheckbox value="{!appSettings.IsActive__c}" />
                                                    <span class="slds-checkbox--faux"></span>
                                                    <span class="slds-form-element__label slds-truncate">{!$ObjectType.ApplicationSettings__c.fields.IsActive__c.Label}</span>
                                                </label>
                                                <div class="slds-form-element__icon">
                                                    <a href="javascript:void(0);">
                                                        <svg aria-hidden="true" class="slds-icon slds-icon--x-small slds-icon-text-default">
                                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Resource.myapp__SLDS202, '/assets/icons/utility-sprite/svg/symbols.svg#info')}" />
                                                        </svg>
                                                        <span class="slds-assistive-text">Help</span>
                                                    </a>
                                                    <div id="help-IsActive" class="slds-popover slds-popover--tooltip slds-nubbin--bottom-left slds-hide" role="tooltip" aria-live="polite">
                                                        <div class="slds-popover__body slds-text-longform">
                                                            <p>{!$ObjectType.ApplicationSettings__c.fields.IsActive__c.inlineHelpText}</p>
                                                        </div>
                                                    </div>
                                                </div>                                      
                                            </div>
                                            <div class="slds-form-element slds-size--1-of-2">
        
                                                <label class="slds-checkbox">
                                                    <apex:inputCheckbox value="{!appSettings.TriggersActive__c}" />
                                                    <span class="slds-checkbox--faux"></span>
                                                    <span class="slds-form-element__label slds-truncate">{!$ObjectType.ApplicationSettings__c.fields.TriggersActive__c.Label}</span>
                                                </label>    
                                                <div class="slds-form-element__icon">
                                                    <a href="javascript:void(0);">
                                                        <svg aria-hidden="true" class="slds-icon slds-icon--x-small slds-icon-text-default">
                                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Resource.myapp__SLDS202, '/assets/icons/utility-sprite/svg/symbols.svg#info')}" />
                                                        </svg>
                                                        <span class="slds-assistive-text">Help</span>
                                                    </a>
                                                    <div id="help-TriggersActive" class="slds-popover slds-popover--tooltip slds-nubbin--bottom-left slds-hide" role="tooltip" aria-live="polite">
                                                        <div class="slds-popover__body slds-text-longform">
                                                            <p>{!$ObjectType.ApplicationSettings__c.fields.TriggersActive__c.inlineHelpText}</p>
                                                        </div>
                                                    </div>                              
                                                </div>                                      
                                            </div>
                                        </div>
                                        <div class="slds-form-element__row">
                                            <div class="slds-form-element slds-size--1-of-2">
        
                                                <label class="slds-checkbox">
                                                    <apex:inputCheckbox value="{!appSettings.IsHierarchiesActive__c}" />
                                                    <span class="slds-checkbox--faux"></span>
                                                    <span class="slds-form-element__label slds-truncate">{!$ObjectType.ApplicationSettings__c.fields.IsHierarchiesActive__c.Label}</span>
                                                </label>
                                                <div class="slds-form-element__icon">
                                                    <a href="javascript:void(0);">
                                                        <svg aria-hidden="true" class="slds-icon slds-icon--x-small slds-icon-text-default">
                                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Resource.myapp__SLDS202, '/assets/icons/utility-sprite/svg/symbols.svg#info')}" />
                                                        </svg>
                                                        <span class="slds-assistive-text">Help</span>
                                                    </a>
                                                    <div id="help-IsHierarchiesActive" class="slds-popover slds-popover--tooltip slds-nubbin--bottom-left slds-hide" role="tooltip" aria-live="polite">
                                                        <div class="slds-popover__body slds-text-longform">
                                                            <p>{!$ObjectType.ApplicationSettings__c.fields.IsHierarchiesActive__c.inlineHelpText}</p>
                                                        </div>
                                                    </div>
                                                </div>                                          
                                            </div>
                                            <div class="slds-form-element slds-size--1-of-2">
        
                                                <label class="slds-checkbox">
                                                    <apex:inputCheckbox value="{!appSettings.PersistNoMatches__c}" />
                                                    <span class="slds-checkbox--faux"></span>
                                                    <span class="slds-form-element__label slds-truncate">{!$ObjectType.ApplicationSettings__c.fields.PersistNoMatches__c.Label}</span>
                                                </label>
                                                <div class="slds-form-element__icon">
                                                    <a href="javascript:void(0);">
                                                        <svg aria-hidden="true" class="slds-icon slds-icon--x-small slds-icon-text-default">
                                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Resource.myapp__SLDS202, '/assets/icons/utility-sprite/svg/symbols.svg#info')}" />
                                                        </svg>
                                                        <span class="slds-assistive-text">Help</span>
                                                    </a>
                                                    <div id="help-PersistNoMatches" class="slds-popover slds-popover--tooltip slds-nubbin--bottom-left slds-hide" role="tooltip" aria-live="polite">
                                                        <div class="slds-popover__body slds-text-longform">
                                                            <p>{!$ObjectType.ApplicationSettings__c.fields.PersistNoMatches__c.inlineHelpText}</p>
                                                        </div>
                                                    </div>
                                                </div>                                          
                                            </div>
                                        </div>
                                        <div class="slds-form-element__row">
                                            <div class="slds-form-element slds-size--1-of-2">
        
                                                <label class="slds-checkbox">
                                                    <apex:inputCheckbox value="{!appSettings.ShowHelpCaptions__c}" />
                                                    <span class="slds-checkbox--faux"></span>
                                                    <span class="slds-form-element__label slds-truncate">{!$ObjectType.ApplicationSettings__c.fields.ShowHelpCaptions__c.Label}</span>
                                                </label>
                                                <div class="slds-form-element__icon">
                                                    <a href="javascript:void(0);">
                                                        <svg aria-hidden="true" class="slds-icon slds-icon--x-small slds-icon-text-default">
                                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Resource.myapp__SLDS202, '/assets/icons/utility-sprite/svg/symbols.svg#info')}" />
                                                        </svg>
                                                        <span class="slds-assistive-text">Help</span>
                                                    </a>
                                                    <div id="help-ShowHelpCaptions" class="slds-popover slds-popover--tooltip slds-nubbin--bottom-left slds-hide" role="tooltip" aria-live="polite">
                                                        <div class="slds-popover__body slds-text-longform">
                                                            <p>{!$ObjectType.ApplicationSettings__c.fields.ShowHelpCaptions__c.inlineHelpText}</p>
                                                        </div>
                                                    </div>
                                                </div>                                                                
                                            </div>
                                            <div class="slds-form-element slds-size--1-of-2">
        
                                                <label class="slds-checkbox">
                                                    <apex:inputCheckbox value="{!sysSettings.DisplayOnSettingsPage__c}" />
                                                    <span class="slds-checkbox--faux"></span>
                                                    <span class="slds-form-element__label slds-truncate">{!$ObjectType.SystemSettings__c.fields.DisplayOnSettingsPage__c.Label}</span>
                                                </label>
                                                <div class="slds-form-element__icon">
                                                    <a href="javascript:void(0);">
                                                        <svg aria-hidden="true" class="slds-icon slds-icon--x-small slds-icon-text-default">
                                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Resource.myapp__SLDS202, '/assets/icons/utility-sprite/svg/symbols.svg#info')}" />
                                                        </svg>
                                                        <span class="slds-assistive-text">Help</span>
                                                    </a>
                                                    <div id="help-DisplayOnSettingsPage" class="slds-popover slds-popover--tooltip slds-nubbin--bottom-left slds-hide" role="tooltip" aria-live="polite">
                                                        <div class="slds-popover__body slds-text-longform">
                                                            <p>{!$ObjectType.SystemSettings__c.fields.DisplayOnSettingsPage__c.inlineHelpText}</p>
                                                        </div>
                                                    </div>
                                                </div>                                                                
                                            </div>
                                        </div>
                                        <div class="slds-form-element__row">
                                            <div class="slds-form-element slds-size--1-of-2">
                                  
                                                <label class="slds-form-element__label slds-truncate" for="select-AuditLogLevel">{!$ObjectType.ApplicationSettings__c.fields.AuditLogLevel__c.Label}</label>
                                                <div class="slds-form-element__icon">
                                                    <a href="javascript:void(0);">
                                                        <svg aria-hidden="true" class="slds-icon slds-icon--x-small slds-icon-text-default">
                                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Resource.myapp__SLDS202, '/assets/icons/utility-sprite/svg/symbols.svg#info')}" />
                                                        </svg>
                                                        <span class="slds-assistive-text">Help</span>
                                                    </a>
                                                    <div id="help-AuditLogLevel" class="slds-popover slds-popover--tooltip slds-nubbin--bottom-left slds-hide" role="tooltip" aria-live="polite">
                                                        <div class="slds-popover__body slds-text-longform">
                                                            <p>{!$ObjectType.ApplicationSettings__c.fields.AuditLogLevel__c.inlineHelpText}</p>
                                                        </div>
                                                    </div>                              
                                                </div>                                                                              
                                                <div class="slds-form-element__control slds-size--1-of-1 slds-small-size--1-of-1 slds-medium-size--2-of-3 slds-large-size--1-of-2">
                                                    <div class="slds-select_container">
                                                        <apex:selectList id="select-AuditLogLevel"
                                                                            styleClass="slds-select" 
                                                                            value="{!selectedAuditLogLevel}" 
                                                                            multiselect="false" 
                                                                            size="1"                                                                    
                                                                            title="{!$ObjectType.ApplicationSettings__c.fields.AuditLogLevel__c.inlineHelpText}">
                                                            <apex:selectOptions value="{!auditLogLevelOptions}"/>
                                                        </apex:selectList>                                          
                                                    </div>
                                                </div>                                                
                                            </div>
                                            <div class="slds-form-element slds-size--1-of-2"></div>
                                        </div>
                                        <div class="slds-form-element__row">
                                            <div class="slds-form-element slds-size--1-of-2">
                                            
                                                <label class="slds-form-element__label slds-truncate" for="text-MaxJobsDisplayDays">{!$ObjectType.ApplicationSettings__c.fields.MaxJobsDisplayDays__c.Label}</label>
                                                <div class="slds-form-element__icon">
                                                    <a href="javascript:void(0);">
                                                        <svg aria-hidden="true" class="slds-icon slds-icon--x-small slds-icon-text-default">
                                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Resource.myapp__SLDS202, '/assets/icons/utility-sprite/svg/symbols.svg#info')}" />
                                                        </svg>
                                                        <span class="slds-assistive-text">Help</span>
                                                    </a>
                                                    <div id="help-MaxJobsDisplayDays" class="slds-popover slds-popover--tooltip slds-nubbin--bottom-left slds-hide" role="tooltip" aria-live="polite">
                                                        <div class="slds-popover__body slds-text-longform">
                                                            <p>{!$ObjectType.ApplicationSettings__c.fields.MaxJobsDisplayDays__c.inlineHelpText}</p>
                                                        </div>
                                                    </div>                                          
                                                </div>                                      
                                                <div class="slds-form-element__control slds-size--1-of-1 slds-small-size--1-of-1 slds-medium-size--2-of-3 slds-large-size--1-of-2">
                                                    <apex:inputText id="text-MaxJobsDisplayDays" styleClass="slds-input" value="{!appSettings.MaxJobsDisplayDays__c}" html-type="number" />
                                                </div>
                                            </div>
                                            <div class="slds-form-element slds-size--1-of-2"></div>
                                        </div>
                                    </div>
                                </fieldset>
                                
                                <div class="slds-m-vertical--small"></div>
                            </apex:outputPanel> <!--  application settings refresh panel -->
                            
                            <apex:commandButton id="saveButtonLEX" 
                                                action="{!saveApplicationSettingsAction}" 
                                                value="{!$Label.UI_Button_Label_Save}" 
                                                rerender="mainPanelLEX,msgsRefreshPanelLEX" 
                                                onclick="showSpinner();"
                                                oncomplete="init();hideSpinner();"
                                                styleclass="slds-button slds-button--neutral" />
                                                
                            <div class="slds-m-vertical--large"></div>                                                                                                                  
                        </div>
                        <div id="tab-scoped-2" class="slds-tabs--scoped__content {!IF(selectedTab=='targetObjects', 'slds-show', 'slds-hide')}" role="tabpanel" aria-labelledby="tab-scoped-2__item">                   
                            
                            <apex:outputPanel id="targetObjectsRefreshPanelLEX">
                                <apex:outputPanel rendered="{!$Setup.ApplicationSettings__c.ShowHelpCaptions__c}">
                                    <div class="slds-notify slds-notify--alert slds-theme--alert-texture" role="alert">
                                        <span class="slds-assistive-text">Info</span>
                                        <h2>{!$Label.UI_Help_Caption_Target_Object_Settings}</h2>
                                    </div>
                                </apex:outputPanel>
                            
                                <apex:dataTable value="{!targetObjects}" var="to" headerClass="slds-text-heading--label slds-truncate" 
                                                styleClass="slds-table slds-table--bordered slds-max-medium-table--stacked-horizontal slds-no-row-hover slds-table--striped" 
                                                columnClasses="slds-cell-wrap slds-truncate" >
                                    <apex:column value="{!to.targetObjectLabel}" headerValue="{!$Label.UI_Column_Header_Setting_Name}" html-data-label="{!$Label.UI_Column_Header_Setting_Name}"/>  
                                    
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Normalisation}" html-data-label="{!$Label.UI_Column_Header_Active_For_Normalisation}">
                                        <apex:inputCheckbox value="{!to.isActiveForNormalisation}" disabled="true" />
                                    </apex:column>
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Matching}" html-data-label="{!$Label.UI_Column_Header_Active_For_Matching}">
                                        <apex:inputCheckbox value="{!to.isActiveForMatching}" disabled="true" />
                                    </apex:column>
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Merge}" html-data-label="{!$Label.UI_Column_Header_Active_For_Merge}">
                                        <apex:inputCheckbox value="{!to.isActiveForMerge}" disabled="true" />
                                    </apex:column>
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Conversion}" html-data-label="{!$Label.UI_Column_Header_Active_For_Conversion}">
                                        <apex:inputCheckbox value="{!to.isActiveForConversion}" disabled="true" />
                                    </apex:column>
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Reparenting}" html-data-label="{!$Label.UI_Column_Header_Active_For_Reparenting}">
                                        <apex:inputCheckbox value="{!to.isActiveForReparenting}" disabled="true" />
                                    </apex:column>
                                    <apex:column html-data-label="{!$Label.UI_Text_Action}" styleClass="myapp-column--overflow-visible">
                                    
                                        <div class="slds-dropdown-trigger slds-dropdown-trigger--click" aria-expanded="false">
                                            <button type="button" class="slds-button slds-button--icon-border-filled" aria-haspopup="true">
                                                <svg aria-hidden="true" class="slds-button__icon">
                                                    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Resource.myapp__SLDS202, '/assets/icons/utility-sprite/svg/symbols.svg#down')}" />
                                                </svg>
                                                <span class="slds-assistive-text">{!$Label.UI_Text_Actions}</span>
                                            </button>
                                            <div class="slds-dropdown slds-dropdown--right">
                                                <ul class="slds-dropdown__list" role="menu">
                                                
                                                    <apex:outputPanel rendered="{!hasTargetObjectPageAccess}">
                                                        <li class="slds-dropdown__item">                                                                                    
                                                            <apex:commandLink style="color:#0070d2;" action="{!editTargetObjectAction}" value="{!$Label.UI_Text_Edit}" id="targetObjectEditCommandLinkLEX" styleclass="slds-truncate" html-role="menuitem">
                                                                <apex:param name="s" value="{!to.targetObjectName}"/>
                                                            </apex:commandLink>
                                                        </li>
                                                    </apex:outputPanel>                                             
                                                
                                                    <li class="slds-dropdown__item">
                                                        <apex:commandLink style="color:#0070d2;" 
                                                                        action="{!deleteTargetObjectAction}" 
                                                                        rerender="targetObjectsRefreshPanelLEX,msgsRefreshPanelLEX" 
                                                                        value="{!$Label.UI_Text_Del}" 
                                                                        id="targetObjectDeleteCommandLinkLEX" 
                                                                        onclick="if(!confirm('{!$Label.UI_Text_Confirmation_Proceed_To_Delete}')){return};"
                                                                        oncomplete="init();" >
                                                            <apex:param name="s" value="{!to.targetObjectName}"/>
                                                        </apex:commandLink>                                       
                                                    </li>
                                                        
                                                </ul>
                                            </div>
                                        </div>                                  
                                    </apex:column>
                                                      
                                </apex:dataTable>
    
                                <!-- if no target objects show columns headers and line "No results" beneath -->    
                                <apex:outputPanel rendered="{!IF(NOT(hasTargetObjects),true,false)}">
                                    <div class="slds-text-heading--label-normal slds-text-align--center slds-m-vertical--xx-large">{!$Label.UI_Text_No_Items_To_Display}</div>
                                </apex:outputPanel> 
                                
                                <div class="slds-m-vertical--small"></div>
                                <apex:actionFunction name="addTargetObject" action="{!addTargetObjectAction}" />
                                <button class="slds-button slds-button--neutral" onclick="addTargetObject()">{!$Label.UI_Button_Label_Add}</button>
                                <div class="slds-m-vertical--large"></div>
                                
                            </apex:outputPanel> <!-- targetObjectsRefreshPanelLEX -->               
                        </div>
                        <div id="tab-scoped-3" class="slds-tabs--scoped__content {!IF(selectedTab=='dataSources', 'slds-show', 'slds-hide')}" role="tabpanel" aria-labelledby="tab-scoped-3__item">
                        </div>
                        <div id="tab-scoped-4" class="slds-tabs--scoped__content {!IF(selectedTab=='dynamicHierarchies', 'slds-show', 'slds-hide')}" role="tabpanel" aria-labelledby="tab-scoped-4__item">
                        </div>
                        <div id="tab-scoped-5" class="slds-tabs--scoped__content {!IF(selectedTab=='customRollups', 'slds-show', 'slds-hide')}" role="tabpanel" aria-labelledby="tab-scoped-5__item">
                        </div>                        
                    </div>  
                    <div class="slds-m-vertical--large"></div>
                    
                </apex:outputPanel>                     
                <!-- / content -->
        
                <!-- footer -->
                <!-- / footer -->
                
            </div> <!-- custom scope wrapper -->            
        
        
        </apex:outputPanel> <!-- Theme4d/Theme4t -->

        <!--  Salesforce Classic Theme [Theme3] -->
        <apex:outputPanel rendered="{!AND( $User.UIThemeDisplayed <> 'Theme4d', $User.UIThemeDisplayed <> 'Theme4t') }">
        
            <style>
              .activeTab { background-color: #F1F1F1; color:black; background-image:none; height:22px; font-family:Arial,​Helvetica,​sans-serif; font-size:12px }
              .inactiveTab { background-color: #C0C0C0; color:black; background-image:none; height:22px; font-family:Arial,​Helvetica,​sans-serif; font-size:12px }
            </style>        
            
            <apex:sectionHeader subtitle="{!$Label.UI_Text_Settings}" title="{!$Label.UI_Text_MDM}" />
    
            <apex:outputPanel id="missingStandardPermissionsPanel" rendered="{!NOT(hasStandardPermissions)}">
                <apex:pageMessage summary="{!$Label.UI_Error_No_Customize_Application_Permissions}" severity="warning" strength="1" />    
            </apex:outputPanel>
            
            <apex:outputPanel id="noCustomPermissionPanel" rendered="{!AND(hasStandardPermissions,NOT($Permission.Manage_Settings))}">
                <apex:pageMessage summary="{!$Label.UI_Error_No_Manage_Settings_Permission}" severity="warning" strength="1" />    
            </apex:outputPanel>
    
            <apex:outputPanel id="msgsPanel">
                <apex:pageMessages id="msgs" />
            </apex:outputPanel>
            
            <apex:outputPanel id="mainPanel" rendered="{!AND(showMainPanel,$Permission.Manage_Settings,hasStandardPermissions)}">       
                <apex:tabPanel switchType="client" value="{!selectedTab}" id="mainTabPanel" tabClass="activeTab" inactiveTabClass="inactiveTab">
                    <apex:tab label="{!$Label.UI_Text_Application_Settings}" name="applicationSettings" id="applicationSettingsTab">
                    
                    
                        <div style="position: relative; min-height:400px; height:auto !important; height:400px;">               
                            <apex:actionstatus id="pageActionStatus">
                                <apex:facet name="start">
                                    <div class="waitingSearchDiv" id="loaderDiv" style="background-color: #fbfbfb; height: 100%;opacity:0.65;width:100%;"> 
                                    <div class="waitingHolder" style="top: 74.2px; width: 91px;">
                                        <img class="waitingImage" src="/img/loading.gif" title="Please Wait..." />
                                            <span class="waitingDescription">{!$Label.UI_Text_Saving}</span>
                                        </div>
                                    </div>
                                </apex:facet>                       
                                <apex:facet name="stop"></apex:facet>                                               
                            </apex:actionstatus>                  
                    
                            <apex:pageBlock id="applicationSettingsPageBlock" title="{!$Label.UI_Text_Application_Settings}" mode="edit" >              
                                
                                <apex:pageBlockButtons >
                                    <apex:commandButton id="saveButton" 
                                                        action="{!saveApplicationSettingsAction}" 
                                                        value="{!$Label.UI_Button_Label_Save}" 
                                                        rerender="mainPanel" 
                                                        status="pageActionStatus"/>
                                </apex:pageBlockButtons>
                            
                                <apex:pageMessage summary="{!$Label.UI_Help_Caption_Application_Settings}" severity="info" strength="1" rendered="{!$Setup.ApplicationSettings__c.ShowHelpCaptions__c}" />                  
                                
                                <apex:pageBlockSection showHeader="true" columns="2" collapsible="false" title="{!$Label.UI_Text_Application_Settings}">
                                    <apex:inputField value="{!appSettings.IsActive__c}" />
                                    <apex:inputField value="{!appSettings.TriggersActive__c}" />
                                    <apex:inputField value="{!appSettings.IsHierarchiesActive__c}" />
                                    <apex:inputField value="{!appSettings.PersistNoMatches__c}" />
                                    <apex:inputField value="{!appSettings.ShowHelpCaptions__c}" />  
                                    <apex:pageBlockSectionItem /> 
                                    
                                    <apex:pageBlockSectionItem >
                                        <apex:outputLabel value="{!$Label.UI_Text_Audit_Log_Level}" for="auditLogLevelSelectList" />
                                        <apex:selectList id="auditLogLevelSelectList" 
                                                        value="{!selectedAuditLogLevel}" 
                                                        multiselect="false" 
                                                        size="1" 
                                                        style="width:100px"
                                                        title="{!$ObjectType.ApplicationSettings__c.fields.AuditLogLevel__c.inlineHelpText}">
                                            <apex:selectOptions value="{!auditLogLevelOptions}"/>
                                        </apex:selectList>
                                    </apex:pageBlockSectionItem>
                                    <apex:pageBlockSectionItem />                                     
                                    
                                    <apex:inputField value="{!appSettings.MaxJobsDisplayDays__c}" />
                                    <apex:inputField value="{!appSettings.MaxSearchResultsPerDataSource__c}" />                                                     
                                    <apex:inputField value="{!appSettings.SearchTermMinimumCharacterCount__c}" />                           
                                    <apex:inputField value="{!appSettings.DefaultFuzzyMatchThresholdPercentage__c}" />
                                    <apex:pageBlockSectionItem />
                                    <apex:pageBlockSectionItem />
                                
                                    <apex:inputField value="{!appSettings.MaxRecordsPerDataExport__c}" />                               
                                    <apex:pageBlockSectionItem />                                                               
                                    <apex:inputField value="{!appSettings.MaxChildRecordSearchResults__c}" />                               
                                    <apex:pageBlockSectionItem />
                                                                                                
                                    <apex:outputField value="{!appSettings.NamespaceApexPrefix__c}" />
                                    <apex:outputField value="{!appSettings.NamespaceComponentPrefix__c}" />
                                    <apex:outputField value="{!appSettings.NamespacePrefix__c}" />
                                </apex:pageBlockSection>
                                                                
                            </apex:pageBlock>
                        </div>
                    </apex:tab>
    
                    <apex:tab label="{!$Label.UI_Text_Target_Objects}" name="targetObjects" id="targetObjectsTab" rendered="{!$Setup.ApplicationSettings__c.IsActive__c}">
                        <apex:outputPanel id="targetObjectsRefreshPanel">
                            <apex:pageBlock id="targetObjectsPageBlock" title="{!$Label.UI_Text_Target_Object_Settings}" mode="edit" >                    
                                <apex:pageBlockButtons >
                                    <apex:commandButton id="addTargetObjectButton" action="{!addTargetObjectAction}" value="{!$Label.UI_Button_Label_Add}" rendered="{!hasTargetObjectPageAccess}" />
                                </apex:pageBlockButtons>
                                            
                                <apex:pageMessage summary="{!$Label.UI_Help_Caption_Target_Object_Settings}" severity="info" strength="1" rendered="{!$Setup.ApplicationSettings__c.ShowHelpCaptions__c}" />
                            
                                <apex:pageBlockTable value="{!targetObjects}" var="to" columnsWidth="15px,20%,15%,15%,15%,15%,15%" >
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Action}">
                                        <apex:commandLink action="{!editTargetObjectAction}" value="{!$Label.UI_Text_Edit}" id="targetObjectEditCommandLink" style="color:#015BA7;" rendered="{!hasTargetObjectPageAccess}">
                                            <apex:param name="s" value="{!to.targetObjectName}"/>
                                        </apex:commandLink>
                                        <apex:commandLink action="{!deleteTargetObjectAction}" rerender="targetObjectsRefreshPanel,msgsPanel" value="{!$Label.UI_Text_Del}" id="targetObjectDeleteCommandLink" style="padding-left: 10px; color:#015BA7;" onclick="if(!confirm('{!$Label.UI_Text_Confirmation_Proceed_To_Delete}')){return};" >
                                            <apex:param name="s" value="{!to.targetObjectName}"/>
                                        </apex:commandLink>                                
                                    </apex:column>                  
                                    <apex:column value="{!to.targetObjectLabel}" headerValue="{!$Label.UI_Column_Header_Setting_Name}" />    
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Normalisation}">
                                        <apex:inputCheckbox value="{!to.isActiveForNormalisation}" disabled="true" />
                                    </apex:column>
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Matching}">
                                        <apex:inputCheckbox value="{!to.isActiveForMatching}" disabled="true" />
                                    </apex:column>
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Merge}">
                                        <apex:inputCheckbox value="{!to.isActiveForMerge}" disabled="true" />
                                    </apex:column>
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Conversion}">
                                        <apex:inputCheckbox value="{!to.isActiveForConversion}" disabled="true" />
                                    </apex:column>
                                    <apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Reparenting}">
                                        <apex:inputCheckbox value="{!to.isActiveForReparenting}" disabled="true" />
                                    </apex:column>                                                                                    
                                                                                                                                                                    
                                </apex:pageBlockTable>
        
                                <!-- if no target objects show columns headers and line "No results" beneath -->    
                                <apex:outputPanel rendered="{!IF(NOT(hasTargetObjects),true,false)}">
                                    <div style="padding: 5px; color: #808080; font: 11px Arial,Helvetica,sans-serif;">{!$Label.UI_Text_No_Search_Results}</div>
                                </apex:outputPanel> 
                                            
                            </apex:pageBlock>
                        </apex:outputPanel>
                    </apex:tab>
                    
                    <apex:tab label="{!$Label.UI_Text_Data_Sources}" name="dataSources" id="datasourcesTab" rendered="{!$Setup.ApplicationSettings__c.IsActive__c}">                           
                    </apex:tab>                
                    
                    <apex:tab label="{!$Label.UI_Text_Dynamic_Hierarchies}" name="dynamicHierarchies" id="dynamicHierachiesTab" rendered="{!AND($Setup.ApplicationSettings__c.IsActive__c, $Setup.ApplicationSettings__c.IsHierarchiesActive__c)}">
                    </apex:tab>  
                    
                    <apex:tab label="{!$Label.UI_Text_Custom_Rollups}" name="customRollups" id="customRollupsTab" rendered="{!AND($Setup.ApplicationSettings__c.IsActive__c)}">                        
                    </apex:tab>                                
                </apex:tabPanel>        
            </apex:outputPanel> <!-- mainPanel -->      
    
        </apex:outputPanel> <!-- NOT Theme4d/Theme4t = Salesforce Classic -->        
    
    </apex:form>
    
</apex:page>

Salesforce TLS 1.0 Support

As of June 25th 2016 sandbox orgs no longer support TLS 1.0 encryption. The previously optional critical update “Require TLS 1.1 or higher for HTTPS connections” was auto-activated on this date. Production orgs will be auto-activated on the 4th March 2017.

Inbound API connections requesting TLS 1.0 will receive the error message “TLS 1.0 has been disabled in this organization. Please use TLS 1.1 or higher when connecting to Salesforce using https.”.

Eclipse IDE Error

For Force.com IDE (Eclipse) users on Java 7 the eclipse.ini file must be modified to add the line below. This is not required for Java 8.

-Dhttps.protocols=TLSv1.1,TLSv1.2

Further information here.

Salesforce Summer ’16 Platform Highlights

Thankfully it’s time to start thinking Summer; the Summer ’16 (v37.0) release that is. Sporting a cheery (but perhaps more Autumnal than Summer) fireworks logo, the new release is available now for partner preview. The release notes are generally available here.

The Summer ’16 sandbox preview starts early May (7th/8th), with production orgs being upgraded early June. Full details of the release schedule are listed in this official blog post.

As expected Summer ’16 is an evolutionary release not revolutionary and continues the trend of Lighting Experience consolidation. As well as closing the gap on Classic functionality, a number of interesting net-new LEX features have been added.

_16__1_

This post briefly outlines selected highlights related to the Force.com platform (in no order of significance).

– features are GA if not indicated otherwise

Sandbox-to-Sandbox Cloning
Recent releases have provided some interesting enhancements in the sandbox area (post refresh scripts, increased edition allowances etc.), Summer ’16 build on such improvements with a new function that allows a sandbox to be created as a clone of another sandbox (as opposed to a production org). Superficially this sounds like a useful capability; on further thought however this could have a significant impact on development process, allowing QA sandboxes to be cloned as copies of development at the end of sprint (as just one example). Any uni-lateral sandbox-to-sandbox deployments could theoretically be replaced with a clone. Multiple development sandboxes converging into a single upstream org would be the exception. Cloning is also supported by the Tooling API, enabling full automation of environment management. I’ve been unable test this feature as sandbox copy doesn’t appear to be enabled in pre-release orgs, however it would appear that data can be included in the clone. How data copy works between the different sandbox types is yet to be seen.

Summer 16 - Sandbox Clone

Force.com Flow REST API (Pilot)
New resources have been added to describe Flows and to manipulate Flow Interviews. The intent of this will be to enable the development of fully customised user experiences for Flow. Whilst possible to some degree via CSS, the user interface for Flow is difficult to customise in respect to branding, fine-grained control over layout, responsive behaviour etc.. The new API resources allow development of a Flow user interface in any technology that can manipulate a REST API.

Custom Metadata Types – Relationship Fields (Pilot)
CMT now support relationship fields that reference other CMT, custom objects or one of the core CRM standard objects (Account, Contact, Lead, Opportunity, Case). Previously pseudo-relationship fields were required of the text type that held the Name or id of the parent record; the new functionality is clearly a big improvement on this.

Apex – Populated SObject Field Map
As a programming convenience it is now possible to get a map of the fields populated in memory for an SObject instance. Previously, trial-and-error coding approaches have been required to identify whether particular fields are populated; this is prevalent in dynamic code and prone to error. Useful simplifications to the Apex language are always good news.

Lightning Components LockerService
LockerService introduces a new security architecture for Lightning Components comprised of technologies and techniques. Summer ’16 introduces LockerService in the form of two critical updates for existing orgs; one for internal components, the other for communities. New orgs will be auto-enabled by default; Winter ’17 will see all orgs auto-enabled. In short LockerService provides component namespace isolation in respect to DOM access and JS visibility, additionally only public documented Lightning JS APIs can be accessed. There’s considerably more to LockerService than this description covers, the link below provides more detail.

Introducing The LockerService For Lightning Components

Shield Platform Encryption
Shield Platform Encryption now supports Custom Date Fields and compatible fields within Managed Packages. The breadth of platform features that work with the shield data encryption-at-rest technology has also been extended; the new vertical clouds, Organisation Sync and Salesforce-to-Salesforce being interesting examples.

Create a Calendar from Standard/Custom Object Data
Personal Calendar views can now be created directly from data held in Standard or Custom objects. The calendar configuration requires that fields are specified for event start and end dates and also the name. There are currently limits to the number of events that can be viewed and also no means to share or subscribe to a calendar view of this type. Hopefully the next release will support public calendars of this type; the requirement for this style of data presentation is very common.

Summer 16 - Calendar

Process Builder – Execute Multiple Action Groups
Summer ’16 sees further investment in Process Builder which is great news for implementation practitioners using the process automation capabilities of the platform. Previously a process execution (or Flow interview behind the scenes) applied the logic of a single action groups and stopped. With Summer ’16 action groups can be configured with finish behaviour that continues to to evaluate the next action group in sequence. As such multiple logical conditions can be evaluated and acted upon within a single process execution. This new extension will help reduce the number of processes necessary to deliver even simple business process automations and as such is great maintainability improvement.

User Switcher
Regardless of role, most Salesforce users require access to multiple orgs at some stage; Summer ’16 introduces the convenience of a user switcher located on the drop-down menu accessed from the profile picture in Lightning Experience. Adding a user name provides one-click access from this menu to the org. Streamlined cross-org navigation has been long outstanding; beyond the obvious sandbox access use cases, in my experience the level of multiple-org access continues to increase over time.

Summer 16 - User Switcher

Associate Contacts to Multiple Accounts
The Contact to Account relationship is now extended to support one direct relationship plus multiple indirect relationships. A new junction object [AccountContactRelation] provides a standard Roles picklist, plus the ability to add Custom Fields. The inability to customise Contact Roles historically has resulted in the prevalence of custom approaches to Account to Contact relationship modelling. Note, it doesn’t appear possible to add process automation to the new object.

Summer 16 - AccountContactRelation 2

Summer 16 - AccountContactRelation 1

Enhanced Email
Emails sent from the Lightning Email Composer are now recorded as strongly-defined Email records rather than Tasks. A new Email standard object has been added which supports workflow, custom fields, layouts, triggers etc. Email records can be associated with multiple contacts, leads and person accounts and a single account, opportunity etc. Enhanced Email will be enabled by default where Email to Case is not in use. Representing emails as Tasks has never made complete sense; with Enhanced Email, Email records can be used as the basis for business process. Note, workflow on the Email object is limited to updating Case fields, this will limit the applicable use cases for this new functionality.

Salesforce Omni-Channel

This post provides a technical view on the Salesforce Omni-Channel feature-set added in the Summer ’15 (beta) and Winter ’16 (GA) releases.

In functional terms Omni-Channel enables use-cases where work items are proactively pushed to specific agents based on defined rules in relation to priority, capacity and availability. The model extends the traditional Queue approach through routing configurations that define how work items (Cases, Leads etc.) are sized, prioritised and the required routing logic (least active agent or most available). Agent capacity and availability is configurable per work item type, or service channel in Omni-Channel terminology. Queue membership controls the applicability of a given agent for a given work item. As such queues are defined to represent the work item assignment structure (skills, regions, teams, products, knowledge etc.) – as would typically be the case outside of Omni-Channel. Once a work item is assigned to an Omni-Channel enabled queue automated routing to an available queue member takes place. Where automated assignment fails, pending work-items are held on the queue and routing attempts made each time agent availability changes. Agent work load is represented by ownership of the records represented by work items.

From an agent perspective Omni-Channel interactions are handled via an Omni-Channel Widget in the Salesforce Console. The widget supports accept/decline behaviour and manual presence status setting. “Away for Lunch”, “Available for Cases” being illustrative examples. The work item display in the widget is controlled via the primary compact layout for the object the work item represents.

Omni-Channel Console Widget

Omni-Channel integrates seamlessly with Live Agent, enabling a standardised approach to routing and agent capacity across channels. Seamless in this context relates to the end-result, there are configuration changes required to transition from standalone Live Agent to an Omni-Channel integrated state. The objects supported by Omni-Channel are limited to those objects supported by Queues i.e. Cases, Chats, SOS video calls, Social posts, Orders, Leads, Custom objects.

Hopefully the preceding paragraphs sufficiently set the scene for the technical aspects covered below.

Data Model

Omni-Channel Data Model

As the model above shows Omni-Channel is underpinned by a significant number of standard objects. The majority of the objects support queryable, createable and updateable access levels which provides extensibility where standard features require augmentation. The one notable exception to this is the UserPresenceStatus object which records the current presence status for agents, this object is read-only. This limitation will prevent custom solutions (or more likely 3rd party AppExchange solutions) from programmatically manipulating the agent presence; this could be significant where solutions wish to share state with Omni-Channel at the application level.

Extension Points

Beyond the ability to extend Omni-Channel through direct data-level interactions (Apex or API) there are additional extension points to consider.

1. Custom Fields. The UserServicePresence (aka User Presence) and AgentWork objects support the addition of custom fields.

2. Apex Triggers / Validation Rules. The UserServicePresence object supports both Apex Triggers and Validation Rules. In the former case custom logic could be introduced to take action when specific presences are set. In the latter case conditional logic could be applied to prevent presence changes, although the console widget behaves erratically in response to validation rule failures.

3. Custom Console Footer Components. For each Service Channel (Lead, Case etc.) a footer component can be specified to open when a work item of the service channel type is opened. The customisation potential here is considerable.

4. Omni-Channel SOAP API Objects. Full set of objects exposed via the standard SOAP API.

5. Omni-Channel Objects for the Salesforce Console Integration Toolkit. Extensions to the standard console JavaScript API to support manipulation of Omni-Channel objects. Straightforward enough. A key point here to note is that unlike Apex or API transactions, the JavaScript API does support update of agent presence.

sforce.console.presence.setServicePresenceStatus(statusId, function(result) { ..}

6. Pre-assignment. Omni-Channel routing is applied when a work item is assigned to a Queue via record ownership. Automated routing logic then assigns the work item to a user based on priority, availability and capacity rules. Granular control of the user-assignment is not provided. Use cases that require one agent to be preferred over another based on logic such as last customer contact are not supported. Finer grained routing of this type should be addressed in the pre-assignment step, i.e. before the queue assignment. Agent presence and capacity are accessible via code and could therefore be queried as part of a custom user-level routing process. The AgentWork object is also createable suggesting that a record representing a pre-assignment could be added to ensure visibility of the work item to Omni-Channel.

References
Omni-Channel for Administrators
Omni-Channel Developer’s Guide

Update – 10th March

With Omni-Channel current agent workload is determined by work item records open in the Salesforce console not record ownership. Closing the tab for a Lead record has the effect of setting the related AgentWork record status to closed.