Skip to content

Commit d9fcf1d

Browse files
add: change tracking, closes #57
1 parent 871c987 commit d9fcf1d

File tree

10 files changed

+202
-24
lines changed

10 files changed

+202
-24
lines changed

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ savedQuery | String | `retrieveRequest` | A String representing the GUID value o
255255
select | Array | `retrieveRequest`, `retrieveMultipleRequest`, `retrieveAllRequest`, `updateRequest`, `upsertRequest` | An Array (of Strings) representing the $select OData System Query Option to control which attributes will be returned.
256256
token | String | All | Authorization Token. If set, onTokenRefresh will not be called.
257257
top | Number | `retrieveMultipleRequest`, `retrieveAllRequest` | Limit the number of results returned by using the $top system query option. Do not use $top with $count!
258+
trackChanges | Boolean | `retrieveMultipleRequest` | `v.1.5.11+` Sets Prefer header with value 'odata.track-changes' to request that a delta link be returned which can subsequently be used to retrieve entity changes. __Important!__ Change Tracking must be enabled for the entity. [More Info](https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/use-change-tracking-synchronize-data-external-systems#enable-change-tracking-for-an-entity)
258259
userQuery | String | `retrieveRequest` | A String representing the GUID value of the user query.
259260

260261
Basic and Advanced functions also have differences in `expand` parameters. For Basic ones this parameter is a type of String
@@ -644,7 +645,6 @@ var request = {
644645

645646
//perform a multiple records retrieve operation
646647
dynamicsWebApi.retrieveMultipleRequest(request).then(function (response) {
647-
/// <param name="response" type="DWA.Types.MultipleResponse">Response object</param>
648648

649649
var count = response.oDataCount;
650650
var nextLink = response.oDataNextLink;
@@ -656,6 +656,34 @@ dynamicsWebApi.retrieveMultipleRequest(request).then(function (response) {
656656
});
657657
```
658658

659+
#### Change Tracking
660+
661+
```js
662+
//set the request parameters
663+
var request = {
664+
collection: "leads",
665+
select: ["fullname", "subject"],
666+
trackChanges: true
667+
};
668+
669+
//perform a multiple records retrieve operation (1)
670+
dynamicsWebApi.retrieveMultipleRequest(request).then(function (response) {
671+
672+
var deltaLink = response.oDataDeltaLink;
673+
//make other requests to Web API
674+
//...
675+
676+
//(2) only retrieve changes:
677+
return dynamicsWebApi.retrieveMultipleRequest(request, response.oDataDeltaLink);
678+
})
679+
.then(function (response) {
680+
//here you will get changes between the first retrieveMultipleRequest (1) and the second one (2)
681+
})
682+
.catch(function (error){
683+
//catch an error
684+
});
685+
```
686+
659687
#### Retrieve All records
660688

661689
The following function retrieves records and goes through all pages automatically.

lib/dynamics-web-api-callbacks.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -674,15 +674,15 @@ function DynamicsWebApi(config) {
674674
* @param {Function} errorCallback - The function that will be passed through and be called by a failed response.
675675
* @param {Array} [select] - Use the $select system query option to limit the properties returned.
676676
* @param {string} [filter] - Use the $filter system query option to set criteria for which entities will be returned.
677-
* @param {string} [nextPageLink] - Use the value of the @odata.nextLink property with a new GET request to return the next page of data. Pass null to retrieveMultipleOptions.
677+
* @param {string} [oDataLink] - Use this parameter to pass @odata.nextLink or @odata.deltaLink to return a necessary response. Pass null to retrieveMultipleOptions.
678678
*/
679-
this.retrieveMultiple = function (collection, successCallback, errorCallback, select, filter, nextPageLink) {
679+
this.retrieveMultiple = function (collection, successCallback, errorCallback, select, filter, oDataLink) {
680680

681681
this.retrieveMultipleRequest({
682682
collection: collection,
683683
select: select,
684684
filter: filter
685-
}, successCallback, errorCallback, nextPageLink);
685+
}, successCallback, errorCallback, oDataLink);
686686
};
687687

688688
/**
@@ -704,16 +704,16 @@ function DynamicsWebApi(config) {
704704
}, successCallback, errorCallback);
705705
};
706706

707-
var retrieveMultipleRequest = function (request, successCallback, errorCallback, nextPageLink) {
707+
var retrieveMultipleRequest = function (request, successCallback, errorCallback, oDataLink) {
708708

709709
if (!_isBatch) {
710710
ErrorHelper.callbackParameterCheck(successCallback, "DynamicsWebApi.retrieveMultiple", "successCallback");
711711
ErrorHelper.callbackParameterCheck(errorCallback, "DynamicsWebApi.retrieveMultiple", "errorCallback");
712712
}
713713

714-
if (nextPageLink) {
715-
ErrorHelper.stringParameterCheck(nextPageLink, 'DynamicsWebApi.retrieveMultiple', 'nextPageLink');
716-
request.url = nextPageLink;
714+
if (oDataLink) {
715+
ErrorHelper.stringParameterCheck(oDataLink, 'DynamicsWebApi.retrieveMultiple', 'oDataLink');
716+
request.url = oDataLink;
717717
}
718718

719719
var onSuccess = function (response) {
@@ -729,11 +729,11 @@ function DynamicsWebApi(config) {
729729
* @param {DWARequest} request - An object that represents all possible options for a current request.
730730
* @param {Function} successCallback - The function that will be passed through and be called by a successful response.
731731
* @param {Function} errorCallback - The function that will be passed through and be called by a failed response.
732-
* @param {string} [nextPageLink] - Use the value of the @odata.nextLink property with a new GET request to return the next page of data. Pass null to retrieveMultipleOptions.
732+
* @param {string} [oDataLink] - Use this parameter to pass @odata.nextLink or @odata.deltaLink to return a necessary response. Pass null to retrieveMultipleOptions.
733733
*/
734734
this.retrieveMultipleRequest = retrieveMultipleRequest;
735735

736-
var _retrieveAllRequest = function (request, successCallback, errorCallback, nextPageLink, records) {
736+
var _retrieveAllRequest = function (request, successCallback, errorCallback, oDataLink, records) {
737737

738738
records = records || [];
739739

@@ -748,7 +748,7 @@ function DynamicsWebApi(config) {
748748
}
749749
};
750750

751-
retrieveMultipleRequest(request, internalSuccessCallback, errorCallback, nextPageLink);
751+
retrieveMultipleRequest(request, internalSuccessCallback, errorCallback, oDataLink);
752752
};
753753

754754
/**

lib/dynamics-web-api.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ var dwaRequest = function () {
8989
* @property {boolean} mergeLabels - If set to 'true', DynamicsWebApi adds a request header 'MSCRM.MergeLabels: true'. Default value is 'false'.
9090
* @property {boolean} isBatch - If set to 'true', DynamicsWebApi treats a request as a part of a batch request. Call ExecuteBatch to execute all requests in a batch. Default value is 'false'.
9191
* @property {string} contentId - BATCH REQUESTS ONLY! Sets Content-ID header or references request in a Change Set.
92+
* @property {boolean} trackChanges - Preference header 'odata.track-changes' is used to request that a delta link be returned which can subsequently be used to retrieve entity changes.
93+
* @property {string} deltaLink - Delta link can be used to retrieve entity changes. Important! Change Tracking must be enabled for the entity.
9294
*/
9395

9496
/**

lib/requests/helpers/parseResponse.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ function getFormattedKeyValue(keyName, value) {
2424
case 'odata.nextLink':
2525
newKey = 'oDataNextLink';
2626
break;
27+
case 'odata.deltaLink':
28+
newKey = 'oDataDeltaLink';
29+
break;
2730
case DWA.Prefer.Annotations.FormattedValue:
2831
newKey = format[0] + '_Formatted';
2932
break;

lib/utilities/buildPreferHeader.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module.exports = function buildPreferHeader(request, functionName, config) {
1212
var returnRepresentation = request.returnRepresentation;
1313
var includeAnnotations = request.includeAnnotations;
1414
var maxPageSize = request.maxPageSize;
15+
var trackChanges = request.trackChanges;
1516

1617
var prefer;
1718

@@ -26,12 +27,15 @@ module.exports = function buildPreferHeader(request, functionName, config) {
2627
if (item === DWA.Prefer.ReturnRepresentation) {
2728
returnRepresentation = true;
2829
}
29-
else if (item.startsWith("odata.include-annotations=")) {
30-
includeAnnotations = item.replace('odata.include-annotations=', '').replace(/"/g,'');
30+
else if (item.indexOf("odata.include-annotations=") > -1) {
31+
includeAnnotations = item.replace('odata.include-annotations=', '').replace(/"/g, '');
3132
}
3233
else if (item.startsWith("odata.maxpagesize=")) {
3334
maxPageSize = item.replace('odata.maxpagesize=', '').replace(/"/g, '');
3435
}
36+
else if (item.indexOf("odata.track-changes") > -1) {
37+
trackChanges = true;
38+
}
3539
}
3640
}
3741

@@ -60,5 +64,10 @@ module.exports = function buildPreferHeader(request, functionName, config) {
6064
prefer.push('odata.maxpagesize=' + maxPageSize);
6165
}
6266

67+
if (trackChanges) {
68+
ErrorHelper.boolParameterCheck(trackChanges, "DynamicsWebApi." + functionName, "request.trackChanges");
69+
prefer.push('odata.track-changes');
70+
}
71+
6372
return prefer.join(',');
6473
}

tests/common-tests.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,45 @@ describe("RequestConverter.convertRequestOptions -", function () {
12051205
});
12061206
});
12071207

1208+
it("prefer - trackChanges & maxPageSize", function () {
1209+
var dwaRequest = {
1210+
prefer: 'odata.track-changes,odata.maxpagesize=20'
1211+
};
1212+
1213+
var result = RequestConverter.convertRequestOptions(dwaRequest, "", stubUrl);
1214+
expect(result).to.deep.equal({
1215+
url: stubUrl, query: "", headers: {
1216+
Prefer: 'odata.maxpagesize=20,odata.track-changes'
1217+
}
1218+
});
1219+
});
1220+
1221+
it("prefer - trackChanges & includeAnnotations", function () {
1222+
var dwaRequest = {
1223+
prefer: 'odata.track-changes,odata.include-annotations="' + DWA.Prefer.Annotations.AssociatedNavigationProperty + '"'
1224+
};
1225+
1226+
var result = RequestConverter.convertRequestOptions(dwaRequest, "", stubUrl);
1227+
expect(result).to.deep.equal({
1228+
url: stubUrl, query: "", headers: {
1229+
Prefer: 'odata.include-annotations="' + DWA.Prefer.Annotations.AssociatedNavigationProperty + '",odata.track-changes'
1230+
}
1231+
});
1232+
});
1233+
1234+
it("prefer - includeAnnotations & returnRepresentation & maxPageSize & trackChanges", function () {
1235+
var dwaRequest = {
1236+
prefer: 'return=representation,odata.include-annotations="*",odata.track-changes,odata.maxpagesize=20'
1237+
};
1238+
1239+
var result = RequestConverter.convertRequestOptions(dwaRequest, "", stubUrl);
1240+
expect(result).to.deep.equal({
1241+
url: stubUrl, query: "", headers: {
1242+
Prefer: DWA.Prefer.ReturnRepresentation + ',odata.include-annotations="*",odata.maxpagesize=20,odata.track-changes'
1243+
}
1244+
});
1245+
});
1246+
12081247
it("returnRepresentation: false & config.returnRepresentation: true", function () {
12091248
var config = {
12101249
returnRepresentation: true

tests/main-tests.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,6 +2660,83 @@ describe("promises -", function () {
26602660
expect(scope.isDone()).to.be.true;
26612661
});
26622662
});
2663+
2664+
describe("retrieves the delta link (@odata.deltaLink)", function () {
2665+
var scope;
2666+
before(function () {
2667+
var response = mocks.responses.multipleWithDeltaLinkResponse;
2668+
scope = nock(mocks.responses.collectionUrl, {
2669+
reqheaders: {
2670+
Prefer: 'odata.track-changes'
2671+
}
2672+
})
2673+
.get("?$select=name")
2674+
.reply(response.status, response.responseText, response.responseHeaders);
2675+
});
2676+
2677+
after(function () {
2678+
nock.cleanAll();
2679+
});
2680+
2681+
it("returns a correct response", function (done) {
2682+
var dwaRequest = {
2683+
collection: "tests",
2684+
select: ["name"],
2685+
trackChanges: true
2686+
};
2687+
2688+
dynamicsWebApiTest.retrieveMultipleRequest(dwaRequest)
2689+
.then(function (object) {
2690+
expect(object).to.deep.equal(mocks.responses.multipleWithDeltaLink());
2691+
done();
2692+
}).catch(function (object) {
2693+
done(object);
2694+
});
2695+
});
2696+
2697+
it("all requests have been made", function () {
2698+
expect(scope.isDone()).to.be.true;
2699+
});
2700+
});
2701+
2702+
describe("when goes by delta link (@odata.deltaLink)", function () {
2703+
var scope;
2704+
before(function () {
2705+
var response = mocks.responses.multipleResponse;
2706+
var link = mocks.responses.multipleWithDeltaLink().oDataDeltaLink.split('?');
2707+
scope = nock(link[0], {
2708+
reqheaders: {
2709+
Prefer: 'odata.track-changes'
2710+
}
2711+
})
2712+
.get("?" + link[1])
2713+
.reply(response.status, response.responseText, response.responseHeaders);
2714+
});
2715+
2716+
after(function () {
2717+
nock.cleanAll();
2718+
});
2719+
2720+
it("returns a correct response", function (done) {
2721+
var dwaRequest = {
2722+
collection: "tests",
2723+
select: ["name"],
2724+
trackChanges: true
2725+
};
2726+
2727+
dynamicsWebApiTest.retrieveMultipleRequest(dwaRequest, mocks.responses.multipleWithDeltaLink().oDataDeltaLink)
2728+
.then(function (object) {
2729+
expect(object).to.deep.equal(mocks.responses.multiple());
2730+
done();
2731+
}).catch(function (object) {
2732+
done(object);
2733+
});
2734+
});
2735+
2736+
it("all requests have been made", function () {
2737+
expect(scope.isDone()).to.be.true;
2738+
});
2739+
});
26632740
});
26642741

26652742
describe("dynamicsWebApi.retrieveAllRequest -", function () {

tests/stubs.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ var dataStubs = {
132132
{ name: "name2", subject: "subject2" }
133133
]
134134
},
135+
multipleWithDeltaLink: {
136+
"@odata.context": "context",
137+
"@odata.deltaLink": webApiUrl + "tests?$select=name&$deltatoken=919042%2108%2f22%2f2017%2008%3a10%3a44",
138+
value: [
139+
{ name: "name1", subject: "subject1" },
140+
{ name: "name2", subject: "subject2" }
141+
]
142+
},
135143
batch:
136144
'--dwa_batch_XXX\n' +
137145
'Content-Type: application/http\n' +
@@ -543,6 +551,10 @@ var responseStubs = {
543551
status: 200,
544552
responseText: JSON.stringify(dataStubs.multipleWithLink)
545553
},
554+
multipleWithDeltaLinkResponse: {
555+
status: 200,
556+
responseText: JSON.stringify(dataStubs.multipleWithDeltaLink)
557+
},
546558
batch: {
547559
status: 200,
548560
responseText:
@@ -861,6 +873,12 @@ var responseStubs = {
861873
stub.oDataNextLink = stub["@odata.nextLink"];
862874
return stub;
863875
},
876+
multipleWithDeltaLink: function () {
877+
var stub = dataStubs.multipleWithDeltaLink;
878+
stub.oDataContext = stub["@odata.context"];
879+
stub.oDataDeltaLink = stub["@odata.deltaLink"];
880+
return stub;
881+
},
864882
multiple: function () {
865883
var stub = dataStubs.multiple;
866884
stub.oDataContext = stub["@odata.context"];

types/dynamics-web-api-callbacks.d.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Type definitions for dynamics-web-api-callbacks v1.5.9
1+
// Type definitions for dynamics-web-api-callbacks v1.5.11
22
// Project: https://github.com/AleksandrRogov/DynamicsWebApi
33
// Definitions by: Aleksandr Rogov https://github.com/AleksandrRogov/
44

@@ -177,11 +177,11 @@ declare class DynamicsWebApi {
177177
* @param collection - The name of the Entity Collection or Entity Logical name.
178178
* @param successCallback - The function that will be passed through and be called by a successful response.
179179
* @param errorCallback - The function that will be passed through and be called by a failed response.
180-
* @param select] - Use the $select system query option to limit the properties returned.
180+
* @param select - Use the $select system query option to limit the properties returned.
181181
* @param filter - Use the $filter system query option to set criteria for which entities will be returned.
182-
* @param nextPageLink - Use the value of the @odata.nextLink property with a new GET request to return the next page of data. Pass null to retrieveMultipleOptions.
182+
* @param oDataLink - Use this parameter to pass @odata.nextLink or @odata.deltaLink to return a necessary response. Pass null to retrieveMultipleOptions.
183183
*/
184-
retrieveMultiple(collection: string, successCallback: Function, errorCallback: Function, select?: string[], filter?: string, nextPageLink?: string): void;
184+
retrieveMultiple(collection: string, successCallback: Function, errorCallback: Function, select?: string[], filter?: string, oDataLink?: string): void;
185185
/**
186186
* Sends an asynchronous request to retrieve all records.
187187
*
@@ -198,7 +198,7 @@ declare class DynamicsWebApi {
198198
* @param request - An object that represents all possible options for a current request.
199199
* @param successCallback - The function that will be passed through and be called by a successful response.
200200
* @param errorCallback - The function that will be passed through and be called by a failed response.
201-
* @param nextPageLink - Use the value of the @odata.nextLink property with a new GET request to return the next page of data. Pass null to retrieveMultipleOptions.
201+
* @param oDataLink - Use this parameter to pass @odata.nextLink or @odata.deltaLink to return a necessary response. Pass null to retrieveMultipleOptions.
202202
*/
203203
retrieveMultipleRequest(request: DynamicsWebApi.RetrieveMultipleRequest, successCallback: Function, errorCallback: Function): void;
204204
/**

0 commit comments

Comments
 (0)