Salesforce Selective Queries

A basic understanding of the selective query concept is fundamental to scalability on the Salesforce platform. Non-selective queries have a significant performance impact on List Views, Reports and SOQL and can often result in Apex Trigger runtime exceptions as below – as the data volume increases over time.

System.QueryException Cause: null Message: Non-selective query against large object type (more than 200000 rows)

SOQL queries executed from Apex Triggers have to be selective where the record count in the queried object is 200,000 or more. The determination of a selective query is a function of volume plus field-level selectivity. It is therefore the case that the selectivity state of a given query is volatile meaning in practical terms that the initiating Apex Trigger may work one day and not the next.

Selective Query Definition

Selectivity is determined by the index state of the query filter conditions and the number of records the filter returns (selectivity) versus the object total records. The thresholds below show the difference between the selectivity calculation for a standard index versus a custom index.

Selectivity Thresholds:
Standard Index – 30% (first 1M) then 15%
Custom Index – 10% (first 1M) then 5%

Unary filter:

e.g. select Name from Account where IndexedField__c=’ABC’

With a custom index on IndexedField__c the filter must return <10% of the total records in the object to be considered selective – up to the first 1 million records from that point the threshold drops to 5%.

Multiple filters AND (exclusively):

e.g. select Name from Account where IndexedField__c=’ABC’ and SecondIndexedField__c=’123′

The Query Optimiser will set the leading operation on the basis of lowest cost. If no filters are selective a table scan is performed.

If all filters have an index then a Composite Index Join optimisation can be applied.

In this case each filter must be less than 2x (two-times) the selectivity threshold.
All filters combined must be less than selectivity threshold.

If all filter fields are standard then use the standard index selectivity threshold – otherwise use custom index selectivity threshold.

Multiple filters OR (at least one):

e.g. select Name from Account where IndexedField__c=’ABC’ or SecondIndexedField__c=’123′

Selective AND filter indexes could be set as the Leading Operation – if none exist, then a table scan occurs unless all filters have an index then a Composite Index Union optimisation becomes possible.

In this case each filter must be less than selectivity threshold.
All filters combined must be less than selectivity threshold.

If all fields are standard then use the standard index selectivity threshold – otherwise use custom index selectivity threshold.

Parent Field Filter:

e.g. select Name from Contact where IndexedField__c=’ABC’ and Account.IndexedField__c=’ABC’

Where parent object fields are referenced in a filter, each filter index is individually and the lowest cost option selected as the leading operation.

Note, the parent field is not indexed on the queried object, so Account.Id can incur a table scan on Opportunity whereas AccountId may allow the standard index to become the leading operation.

The notes above provide a basic outline of the concepts but should be sufficient to convey the key concepts.

 

Implementation Approach

As data volumes grow query behaviour can change dramatically, to mitigate this database queries originating in Apex code, list view and report definitions must consider the future peak data volume and field-level data characteristics (primarily selectivity). This considered approach can help identify an appropriate indexing strategy that maintains query performance by ensuring query selectivity. So, forward planning is absolutely key; queries should be designed to be selective up to the projected peak data volumes. Thinking ahead to this extent is very seldom applied in my experience, particularly where the Salesforce implementation evolves project-by-project and the longer term picture is not a priority.

In order to evaluate the selectivity of a given query – the following 2 approaches can be applied.

REST API Query Resource Feedback Parameter

The Force.com REST API exposes a Query resource that accepts an explain parameter which can set with a SOQL query, List View Id or Report Id. The results show the options considered by the Query Optimiser and the lowest cost option (leading operation) taken. A relative cost value of less than 1 indicates a selective query, anything higher indicates non-selective. The example below shows the output for a report where a report filter hits an indexed field.

selective-queries-api

Developer Console Query Plan

From Summer ’14 on the Developer Console can be enabled [Help>Preferences>Enable Query Plan] to display a Query Plan option on the Query Editor tab. The construct of the output is the same as the API approach. Note, this appproach is limited to SOQL queries.

selective-queries-developer-console

The references section below provides link to the technical detail of the 2 approaches introduced above.

 

References

Query & Search Optimisation Cheat Sheet

Developing Selective Force.com Queries through the Query Resource Feedback Parameter Beta

Developer Console Query Editor

Improve performance with Custom indexes using Selective SOQL Queries

Custom Index Request Checklist

Salesforce Analytics Cloud Overview

analytics-cloud

This post provides an overview of the features and functionality of the Salesforce Analytics Cloud.

The Analytics Cloud, or Wave Analytics as it is also referred to, is a cloud-based business intelligence platform that enables the connection of disparate data sources to form interactive data views (or visualisations) that can be distributed via dashboards. A key concept here is empowerment of business users to deliver their own insights, in fact usability is often described as the defining feature – perhaps in addition to mobile access.

From a history perspective, during 2013 Salesforce acquired a cutting edge BI platform via the EdgeSpring acquisition. The EdgeSpring platform included the EdgeMart data storage service plus the Lens dynamic visualisation engine; both of which (one could safely assume) feature strongly in the Analytics Cloud architecture.

The first release of the Salesforce branded Wave Analytics took place at Dreamforce 2014, where the dynamic visualisation capabilities drew significant attention. The offer at this stage was expensive and complex in terms of licensing (Explorer and Builder user licenses plus platform setup fee/license), platform-led and focused on enterprise deployments. The perceived complexity and cost aspects particularly impacted negatively upon adoption. A second generation of Wave Analytics was launched at Dreamforce 2015, this time with a simplified license model and a focus on prebuilt analytics apps for Sales, Service and ultimately Marketing. The introduction of prebuilt Wave Apps offers two clear benefits; template-based, simplified deployment and tighter, cross-cloud integration (with Sales and Service Cloud predominantly). This latter point is key; end-users shouldn’t have to navigate consciously between distinct analytic and transactional views the two services should be seamlessly blended – this is the key differentiator Salesforce will be targeting to drive adoption.

The current set of prebuilt Wave Apps are listed below. There are also a growing number of 3rd party Wave Apps being developed on the Wave Analytics Platform; Financialforce (ERP Wave Apps; Accounting and Supply Chain) and Apptus (Quote-to-Cash Intelligence App) are notable examples.

Sales Wave Analytics App: A 9-step wizard captures parameters relating to segmentation, lead funnel and opportunity pipeline fields plus additional Sales Cloud related dimensions and measures; on completion an App is created with datasets populated via a number of auto-launched dataflows.

Service Wave Analytics App: As above with Service Cloud related dimensions and measures (7-step wizard).

Event Monitoring Wave App: Event log and setup audit trail datasets enable analysis of org and user behaviour.

Wave for B2B Marketing App: Consolidation of Sales Cloud and Pardot data to enable analysis of marketing impact on sales etc.

Key Concepts

Wave Assets – App: Analogous to a Folder an App contains a logical grouping of dashboards, lenses, and datasets. Apps can be Shared or Private.

wave-assets-app

Wave Assets – Dashboard: A dashboard is a composition of charts, metrics, and tables based on the data provided by one or many lenses.

wave-assets-dashboard

Wave Assets – Lens: A lens is a view on to the data provided by a dataset. The definition of a lens encapsulates both a query and visualisation for a specific analysis. A Lens can be clipped; this effect of this is to copy the Lens query to a Step within the most recently used dashboard.

wave-assets-lens

Wave Assets – Dataset: A dataset provides the source analytical data in a representation optimised for interactive visualisation. Data Sources can be Salesforce objects, uploaded files or partner connectors (Informatica, Jitterbit etc.). Fields added to a dataset are defined as date, dimension (qualitative) or measure (quantitative) type. Predicates can be added, with filter logic, that define record-level permissions which reflect Salesforce record ownership, management visibility or team/account collaboration.

wave-assets-dataset

Dataflow: A dataflow is a set of instructions (in JSON format) that specifies the data to extract and transform from Salesforce objects or datasets.

wave-dataflow

Visualisation: A single analytical representation of data (chart, tabular etc.) underpinned by a query.

Dimension: A dimension is a qualitative value such as Product or Region. Data analytics are primarily comprised of measures projected over multiple dimensions.

Measure: A measure is a quantitative value such as Price or Quantity. Mathematical operations can be applied to measures to calculate aggregates.

Architecture

The diagram below shows the main building blocks of a Wave Analytics App and the flow of data.

wave-analytics-architecture

Key Features

Cross-Dataset Faceting: Faceting enables steps to auto-filter in response to filters applied to a related step. Steps that share the same dataset facet by default, cross-dataset faceting can be defined directly in the dashboard designer.

Trend Wave Dashboards: Trended Datasets can be created from Salesforce reports. A snapshot of the report data is created each time the dataset is updated by the trend schedule. Each snapshot is limited to 100K rows.

Bulk Actions (Spring ’17): Table widgets can invoke a custom bulk action where the underlying SAQL query is passed to a designated Visualforce page. Apex page controller code can then execute the SAQL query (via the Wave Analytics API) and apply custom logic to results. This provides a flexible integration point, where analytics can be used to drive action such as campaign creation, email sending and so on – powerful stuff.

Smart Data Discovery (Spring ’17 tbc): This feature relates to the integration of BeyondCore (another Salesforce acquisition) into the Analytics Cloud UI. BeyondCore adds statistically significant insights such as unbiased answers, explanations and recommendations.

Analytics Home (Spring ’17): The new Analytics home page allows commonly used apps to be pinned, notification tracking is displayed with convenient links directly to the related dashboards. In new Wave orgs the Analytics tab can be accessed directly within the Salesforce UI, older orgs require a custom tab plus Visualforce container page.

Dashboard Annotations: Widgets within a dashboard can be enabled for collaboration via Annotations. This feature is natively integrated with Chatter; annotations will appear as Chatter posts and vice-versa. @Mentions are also supported.

Smart Notifications: Number type dashboard widgets can be configured for smart notifications; criteria is added to define the notification logic and a query scheduled defined. The notifications appear in the app, mobile app and are also sent via email.

Salesforce Integration Points

Lightning Experience: The Wave Dashboard component enables dashboards to be added to Lightning Home Pages, Record Pages and App Home Pages defined within the Lightning App Builder.

Salesforce Classic: Wave Analytics Assets appear listed in the palette of the Enhanced Page Layout editor and can be added directly to page layouts with context supplied via field mapping. The Visualforce component enables dashboards to be embedded and integrated at any entry point available to Visualforce (Custom tabs, links/buttons etc.). The component supports filtering to enable context to be set.

Communities users (Customer Community Plus and Customer Partner Community licenses) can view Wave Analytics Dashboard embedded in a Visualforce page.

References

Analyze Your Data – 700+ page PDF
Wave Analytics Platform Setup Guide
Wave Analytics Data Integration

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.