Salesforce Summer ’17 Platform Highlights

The Summer Salesforce release is once again upon us, sporting this time a seasonal Seagulls release logo. As someone who lives by the sea I’m no fan of the noisy gull – so this logo isn’t a favourite, I’d have voted for the Sunscreen release logo option personally. That said, the logo animation within Lightning Experience does work well.

Quick fun fact – there is actually no such bird type or species as the seagull or sea-gull – the correct term is gull. Our friends in the sunglasses are probably common gulls. Anyway I digress.

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.

The release notes are available online here.

– features are GA if not indicated otherwise

Visual Workflow – Field Rules (Pilot)
Field Rules enable conditions to be defined for a given UI input that control the setting of visible, hidden, required or optional attributes. Enabling the UI composition and interactions to be data-driven allows guided journeys to be implemented in Flow with better user experience characteristics than was previously possible. Flows are a great tool for developing custom UI in Salesforce and can remove the requirement to develop complex Lightning Component based interactions in LEX. There are additional capabilities in the Summer release for launching/embedding Flows within Lightning Pages (screenshot below shows the Flow component). It seems clear that extending the capabilities of Flow is seen as mitigation for the complexity/time/resource challenges of Lightning Components over Visualforce.

Einstein Vision
Einstein Vision is a REST API that offers image recognition capabilities such as visual search, brand detection and object identification via custom or pre-trained classifiers. An Einstein Platform Account is required to access the API (signup here), which offers 1,000 free predictions per month. Full detail of the API can be found at https://metamind.readme.io.

Einstein Data Discovery for Salesforce Wave Analytics
Einstein Data Discovery performs automated analysis of an ingested dataset to provide natural language answers, explanations and recommendations leading to predictive actionable insights. The tool enables automated, unbiased analysis of scenario patterns as below.

What happened?
Why did it happen?
What will happen?
What should I do about it?

Results and recommendations can be exported to Wave Analytics or a Salesforce object for reporting purposes. Further detail is provided in this datasheet.

Deploy and Retrieve Metadata via Apex
A new Metadata namespace enables metadata manipulation directly from Apex script. This release is limited to Custom Metadata Type records and page layout metadata types which reflects a focus on use cases around development of custom configuration UI. Hopefully this is just a starting point and consistency with the Metadata API will ultimately be achieved.

Advanced PDF Rendering (Pilot)
Advanced PDF rendering provides support for the CSS Paged Media specification which adds functionality for pagination, margins, page size and orientation, headers and footers and image orientation. To configure a Visualforce page for advanced rendering the attribute setting renderAs=”advanced_pdf” is required. Visualforce is not an ideal solution for document generation, it is nonetheless a popular approach – so improvements to the layout handling will be widely appreciated.

Subscribe Other People to Reports (LEX-only)
Reporting Subscriptions were previously configured by the end-User on a one-to-one basis. In my view this implementation approach limited the use of this capability in addressing requirements for proactive notification. In the Summer ’17 release one user can configure a report subscription for multiple users – as shown below.

Lightning Console JavaScript API (Pilot)
Programmatically control Tabs, Subtabs and the Utility Bar in the Lightning Console from JavaScript within Lightning Component controller code. This API is the direct equivalent of the Console Integration Toolkit for the Classic Console.

Omni-Channel in Lighting Experience (Beta)
Omni-Channel support has been added to the Lightning Console to allow Agents to accept/reject routed Work Items and to set their Presence Status. The Omni-Channel Supervisor view is not yet supported which is unfortunate given how much functionality this relatively new feature adds. Live Agent Chat and SOS routing is not supported as the underlying features are not yet Lightning ready. Active time tracking is also absent in this release.

Omni-Channel External Routing (Beta)
There is little information available on this one, however it appears that Queues can be configured for either standard Omni-Channel routing or to delegate routing to an external source via API. This could be incredibly powerful where routing decisions rely on off-platform data or cross system boundaries.

UI API (Developer Preview)
The previously named Lightning UI has been renamed to the UI API, which makes sense. This API provides UI centric, composite responses intended for the efficient composition of UI.

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.

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 Summer ’16 Platform Highlights

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

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

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

_16__1_

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

– features are GA if not indicated otherwise

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

Summer 16 - Sandbox Clone

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

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

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

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

https://developer.salesforce.com/blogs/developer-relations/2016/04/introducing-lockerservice-lightning-components.html

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

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

Summer 16 - Calendar

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

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

Summer 16 - User Switcher

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

Summer 16 - AccountContactRelation 2

Summer 16 - AccountContactRelation 1

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

Salesforce Omni-Channel

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

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

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

Omni-Channel Console Widget

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

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

Data Model

Omni-Channel Data Model

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

Extension Points

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

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

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

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

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

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

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

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

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

Update – 10th March

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