Note: This release is in preview. Features described here don’t become generally available until the latest general availability date that Salesforce announces for this release. Before then, and where features are noted as beta, pilot, or developer preview, we can’t guarantee general availability within any particular time frame or at all. Make your purchase decisions only on the basis of generally available products and features.
GitHub Custom Adapter for Salesforce Connect
This example illustrates how to support indirect lookup relationships. An indirect
lookup relationship links a child external object to a parent standard or custom
object.
For this example to work, create a custom field on the Contact standard object. Name the custom field github_username, make it a text field of length 39, and select the External ID and Unique attributes. Also, add https://api.github.com to your remote site settings.
GitHubDataSourceConnection Class
1/**
2 * Defines the connection to GitHub REST API v3 to support
3 * querying of GitHub profiles.
4 * Extends the DataSource.Connection class to enable
5 * Salesforce to sync the external system’s schema
6 * and to handle queries and searches of the external data.
7 **/
8global class GitHubDataSourceConnection extends
9 DataSource.Connection {
10 private DataSource.ConnectionParams connectionInfo;
11
12 /**
13 * Constructor for GitHubDataSourceConnection
14 **/
15 global GitHubDataSourceConnection(
16 DataSource.ConnectionParams connectionInfo) {
17 this.connectionInfo = connectionInfo;
18 }
19
20 /**
21 * Called to query and get results from the external
22 * system for SOQL queries, list views, and detail pages
23 * for an external object that’s associated with the
24 * external data source.
25 *
26 * The queryContext argument represents the query to run
27 * against a table in the external system.
28 *
29 * Returns a list of rows as the query results.
30 **/
31 override global DataSource.TableResult query(
32 DataSource.QueryContext context) {
33 DataSource.Filter filter = context.tableSelection.filter;
34 String url;
35 if (filter != null) {
36 String thisColumnName = filter.columnName;
37 if (thisColumnName != null &&
38 (thisColumnName.equals('ExternalId') ||
39 thisColumnName.equals('login')))
40 url = 'https://api.github.com/users/'
41 + filter.columnValue;
42 else
43 url = 'https://api.github.com/users';
44 } else {
45 url = 'https://api.github.com/users';
46 }
47
48 /**
49 * Filters, sorts, and applies limit and offset clauses.
50 **/
51 List<Map<String, Object>> rows =
52 DataSource.QueryUtils.process(context, getData(url));
53 return DataSource.TableResult.get(true, null,
54 context.tableSelection.tableSelected, rows);
55 }
56
57 /**
58 * Defines the schema for the external system.
59 * Called when the administrator clicks “Validate and Sync”
60 * in the user interface for the external data source.
61 **/
62 override global List<DataSource.Table> sync() {
63 List<DataSource.Table> tables =
64 new List<DataSource.Table>();
65 List<DataSource.Column> columns;
66 columns = new List<DataSource.Column>();
67
68 // Defines the indirect lookup field. (For this to work,
69 // make sure your Contact standard object has a
70 // custom unique, external ID field called github_username.)
71 columns.add(DataSource.Column.indirectLookup(
72 'login', 'Contact', 'github_username__c'));
73
74 columns.add(DataSource.Column.text('id', 255));
75 columns.add(DataSource.Column.text('name',255));
76 columns.add(DataSource.Column.text('company',255));
77 columns.add(DataSource.Column.text('bio',255));
78 columns.add(DataSource.Column.text('followers',255));
79 columns.add(DataSource.Column.text('following',255));
80 columns.add(DataSource.Column.url('html_url'));
81 columns.add(DataSource.Column.url('DisplayUrl'));
82 columns.add(DataSource.Column.text('ExternalId',255));
83 tables.add(DataSource.Table.get('githubProfile','login',
84 columns));
85 return tables;
86 }
87
88 /**
89 * Called to do a full text search and get results from
90 * the external system for SOSL queries and Salesforce
91 * global searches.
92 *
93 * The SearchContext argument represents the query to run
94 * against a table in the external system.
95 *
96 * Returns results for each table that the SearchContext
97 * requested to be searched.
98 **/
99 override global List<DataSource.TableResult> search(
100 DataSource.SearchContext context) {
101 List<DataSource.TableResult> results =
102 new List<DataSource.TableResult>();
103
104 for (Integer i =0;i< context.tableSelections.size();i++) {
105 String entity = context.tableSelections[i].tableSelected;
106
107 // Search usernames
108 String url = 'https://api.github.com/users/'
109 + context.searchPhrase;
110 results.add(DataSource.TableResult.get(
111 true, null, entity, getData(url)));
112 }
113
114 return results;
115 }
116
117 /**
118 * Helper method to parse the data.
119 * The url argument is the URL of the external system.
120 * Returns a list of rows from the external system.
121 **/
122 public List<Map<String, Object>> getData(String url) {
123 String response = getResponse(url);
124
125 // Standardize response string
126 if (!response.contains('"items":')) {
127 if (response.substring(0,1).equals('{')) {
128 response = '[' + response + ']';
129 }
130 response = '{"items": ' + response + '}';
131 }
132
133 List<Map<String, Object>> rows =
134 new List<Map<String, Object>>();
135
136 Map<String, Object> responseBodyMap = (Map<String, Object>)
137 JSON.deserializeUntyped(response);
138
139 /**
140 * Checks errors.
141 **/
142 Map<String, Object> error =
143 (Map<String, Object>)responseBodyMap.get('error');
144 if (error!=null) {
145 List<Object> errorsList =
146 (List<Object>)error.get('errors');
147 Map<String, Object> errors =
148 (Map<String, Object>)errorsList[0];
149 String errorMessage = (String)errors.get('message');
150 throw new
151 DataSource.OAuthTokenExpiredException(errorMessage);
152 }
153
154 List<Object> fileItems =
155 (List<Object>)responseBodyMap.get('items');
156 if (fileItems != null) {
157 for (Integer i=0; i < fileItems.size(); i++) {
158 Map<String, Object> item =
159 (Map<String, Object>)fileItems[i];
160 rows.add(createRow(item));
161 }
162 } else {
163 rows.add(createRow(responseBodyMap));
164 }
165
166 return rows;
167 }
168
169 /**
170 * Helper method to populate the External ID and Display
171 * URL fields on external object records based on the 'id'
172 * value that’s sent by the external system.
173 *
174 * The Map<String, Object> item parameter maps to the data
175 * that represents a row.
176 *
177 * Returns an updated map with the External ID and
178 * Display URL values.
179 **/
180 public Map<String, Object> createRow(
181 Map<String, Object> item){
182 Map<String, Object> row = new Map<String, Object>();
183 for ( String key : item.keySet() ) {
184 if (key == 'login') {
185 row.put('ExternalId', item.get(key));
186 } else if (key=='html_url') {
187 row.put('DisplayUrl', item.get(key));
188 }
189
190 row.put(key, item.get(key));
191 }
192 return row;
193 }
194
195 /**
196 * Helper method to make the HTTP GET call.
197 * The url argument is the URL of the external system.
198 * Returns the response from the external system.
199 **/
200 public String getResponse(String url) {
201 // Perform callouts for production (non-test) results.
202 Http httpProtocol = new Http();
203 HttpRequest request = new HttpRequest();
204 request.setEndPoint(url);
205 request.setMethod('GET');
206 HttpResponse response = httpProtocol.send(request);
207 return response.getBody();
208 }
209}GitHubDataSourceProvider Class
1/**
2 * Extends the DataSource.Provider base class to create a
3 * custom adapter for Salesforce Connect. The class informs
4 * Salesforce of the functional and authentication
5 * capabilities that are supported by or required to connect
6 * to an external system.
7 **/
8global class GitHubDataSourceProvider
9 extends DataSource.Provider {
10
11 /**
12 * For simplicity, this example declares that the external
13 * system doesn’t require authentication by returning
14 * AuthenticationCapability.ANONYMOUS as the sole entry
15 * in the list of authentication capabilities.
16 **/
17 override global List<DataSource.AuthenticationCapability>
18 getAuthenticationCapabilities() {
19 List<DataSource.AuthenticationCapability> capabilities =
20 new List<DataSource.AuthenticationCapability>();
21 capabilities.add(
22 DataSource.AuthenticationCapability.ANONYMOUS);
23 return capabilities;
24 }
25
26 /**
27 * Declares the functional capabilities that the
28 * external system supports, in this case
29 * only SOQL queries.
30 **/
31 override global List<DataSource.Capability>
32 getCapabilities() {
33 List<DataSource.Capability> capabilities =
34 new List<DataSource.Capability>();
35 capabilities.add(DataSource.Capability.ROW_QUERY);
36 return capabilities;
37 }
38
39 /**
40 * Declares the associated DataSource.Connection class.
41 **/
42 override global DataSource.Connection getConnection(
43 DataSource.ConnectionParams connectionParams) {
44 return new GitHubDataSourceConnection(connectionParams);
45 }
46}