Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/odatav4/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ OData is a standard protocol for creating and consuming data using simple HTTP a

We start with an initial app that simply retrieves data from an OData V4 service and displays it as a plain list, and progressively grow it across 11 steps.

> 💡
> 💡
> You don't have to do all tutorial steps sequentially, you can also jump directly to any step you want. Just download the code from the previous step, and start there.
>
> You can view and download the files for all steps in the Demo Kit at [OData V4](https://sdk.openui5.org/#/entity/sap.ui.core.tutorial.odatav4).
Expand Down
1 change: 0 additions & 1 deletion packages/odatav4/steps/01/webapp/index-cdn.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
<script
id="sap-ui-bootstrap"
src="https://sdk.openui5.org/1.148.6/resources/sap-ui-core.js"
data-sap-ui-theme="sap_horizon"
data-sap-ui-compat-version="edge"
data-sap-ui-async="true"
data-sap-ui-on-init="module:ui5/tutorial/odatav4/initMockServer"
Expand Down
1 change: 0 additions & 1 deletion packages/odatav4/steps/01/webapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-theme="sap_horizon"
data-sap-ui-compat-version="edge"
data-sap-ui-async="true"
data-sap-ui-on-init="module:ui5/tutorial/odatav4/initMockServer"
Expand Down
40 changes: 20 additions & 20 deletions packages/odatav4/steps/01/webapp/localService/mockdata/people.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"@odata.context": "https://services.odata.org/TripPinRESTierService/(S(id))/$metadata#People(Age,FirstName,LastName,UserName,Friends,BestFriend,HomeAddress)",
"value": [
{
"Age": 23,
"Age": "23",
"FirstName": "Angel",
"LastName": "Huffman",
"UserName": "angelhuffman",
Expand All @@ -22,7 +22,7 @@
}
},
{
"Age": 44,
"Age": "44",
"FirstName": "Clyde",
"LastName": "Guess",
"UserName": "clydeguess",
Expand All @@ -42,7 +42,7 @@
}
},
{
"Age": 19,
"Age": "19",
"FirstName": "Elaine",
"LastName": "Stewart",
"UserName": "elainestewart",
Expand All @@ -62,7 +62,7 @@
}
},
{
"Age": 37,
"Age": "37",
"FirstName": "Genevieve",
"LastName": "Reeves",
"UserName": "genevievereeves",
Expand All @@ -82,7 +82,7 @@
}
},
{
"Age": 25,
"Age": "25",
"FirstName": "Georgina",
"LastName": "Barlow",
"UserName": "georginabarlow",
Expand All @@ -101,7 +101,7 @@
}
},
{
"Age": 19,
"Age": "19",
"FirstName": "Javier",
"LastName": "Alfred",
"UserName": "javieralfred",
Expand All @@ -120,7 +120,7 @@
}
},
{
"Age": 26,
"Age": "26",
"FirstName": "Joni",
"LastName": "Rosales",
"UserName": "jonirosales",
Expand All @@ -140,7 +140,7 @@
}
},
{
"Age": 41,
"Age": "41",
"FirstName": "Keith",
"LastName": "Pinckney",
"UserName": "keithpinckney",
Expand All @@ -160,7 +160,7 @@
}
},
{
"Age": 30,
"Age": "30",
"FirstName": "Krista",
"LastName": "Kemp",
"UserName": "kristakemp",
Expand All @@ -180,7 +180,7 @@
}
},
{
"Age": 29,
"Age": "29",
"FirstName": "Laurel",
"LastName": "Osborn",
"UserName": "laurelosborn",
Expand All @@ -200,7 +200,7 @@
}
},
{
"Age": 53,
"Age": "53",
"FirstName": "Marshall",
"LastName": "Garay",
"UserName": "marshallgaray",
Expand All @@ -219,7 +219,7 @@
}
},
{
"Age": 66,
"Age": "66",
"FirstName": "Ronald",
"LastName": "Mundy",
"UserName": "ronaldmundy",
Expand All @@ -238,7 +238,7 @@
}
},
{
"Age": 51,
"Age": "51",
"FirstName": "Russell",
"LastName": "Whyte",
"UserName": "russellwhyte",
Expand All @@ -258,7 +258,7 @@
}
},
{
"Age": 57,
"Age": "57",
"FirstName": "Ryan",
"LastName": "Theriault",
"UserName": "ryantheriault",
Expand All @@ -278,7 +278,7 @@
}
},
{
"Age": 34,
"Age": "34",
"FirstName": "Sallie",
"LastName": "Sampson",
"UserName": "salliesampson",
Expand All @@ -298,7 +298,7 @@
}
},
{
"Age": 18,
"Age": "18",
"FirstName": "Sandy",
"LastName": "Osborn",
"UserName": "sandyosborn",
Expand All @@ -318,7 +318,7 @@
}
},
{
"Age": 24,
"Age": "24",
"FirstName": "Scott",
"LastName": "Ketchum",
"UserName": "scottketchum",
Expand All @@ -337,7 +337,7 @@
}
},
{
"Age": 31,
"Age": "31",
"FirstName": "Ursula",
"LastName": "Bright",
"UserName": "ursulabright",
Expand All @@ -356,7 +356,7 @@
}
},
{
"Age": 40,
"Age": "40",
"FirstName": "Vincent",
"LastName": "Calabrese",
"UserName": "vincentcalabrese",
Expand All @@ -376,7 +376,7 @@
}
},
{
"Age": 45,
"Age": "45",
"FirstName": "Willie",
"LastName": "Ashmore",
"UserName": "willieashmore",
Expand Down
83 changes: 59 additions & 24 deletions packages/odatav4/steps/01/webapp/localService/mockserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ let metadata: string; // The string that holds the cached mock service metadata
const namespace = "ui5/tutorial/odatav4";
// Component for writing logs into the console
const logComponent = "ui5.tutorial.odatav4.mockserver";
const rBaseUrl = /services.odata.org\/TripPinRESTierService/;
const baseUrl = /services.odata.org\/TripPinRESTierService/;

/**
* Returns the base URL from a given URL.
Expand Down Expand Up @@ -251,19 +251,50 @@ function applySort(xhr: MockXhr, resultSet: MockUser[]): MockUser[] {
* Does NOT change the given result set but returns a new array.
*/
function applyFilter(xhr: MockXhr, resultSet: MockUser[]): MockUser[] {
let filteredUsers = [...resultSet]; // work with a copy
const matches = xhr.url.match(/\$filter=.*\((.*),'(.*)'\)/);

// If the request contains a filter command, apply the filter
if (Array.isArray(matches) && matches.length >= 3) {
const fieldName = matches[1];
const query = matches[2];

if (fieldName !== "LastName") {
throw new Error("Filters on field " + fieldName + " are not supported.");
const filter = new URL(xhr.url).searchParams.get("$filter");
let filteredUsers = resultSet.slice(); // work with a copy
if (!filter) {
return filteredUsers;
}
// support filters like:
// - "contains(LastName,'e')"
// - "(contains(LastName,'e')) and not (UserName eq 'a' or UserName eq 'b')"
// - "not (UserName eq 'a' or UserName eq 'b')"
// - "UserName eq 'foo'"
let remainingFilter = filter.startsWith("(") ? filter.slice(1) : filter;
if (remainingFilter.startsWith("contains(LastName,'")) { // from search field
remainingFilter = remainingFilter.slice(19); // remove "contains(LastName,'"
const value = remainingFilter.split("')")[0];
filteredUsers = filteredUsers.filter(function (user) {
return user.LastName.includes(value);
});
// remove the rest of "contains" from the filter and the optional following ") and "
remainingFilter = remainingFilter.slice(value.length + 2);
if (remainingFilter.startsWith(") and ")) {
remainingFilter = remainingFilter.slice(6);
}

filteredUsers = users.filter((user) => (user.LastName || "").indexOf(query) !== -1);
}
if (remainingFilter.startsWith("not (UserName eq ")) { // exclusive filter
const end = remainingFilter.indexOf(")");
const excludedUsers = remainingFilter.slice(17, end) // remove "not (UserName eq "
.split(" or UserName eq ")
// after splitting each entry contains the username in single quotes, e.g. "'me'"
.map((filterPart) => filterPart.slice(1, -1));
filteredUsers = filteredUsers.filter(function (user) {
return !excludedUsers.includes(user.UserName);
});
remainingFilter = remainingFilter.slice(end + 1);
}
if (remainingFilter.startsWith("UserName eq '")) { // refresh of selected user
const end = remainingFilter.indexOf("'", 13);
const value = remainingFilter.slice(13, end); // remove "UserName eq '"
filteredUsers = filteredUsers.filter(function (user) {
return user.LastName.includes(value);
});
remainingFilter = remainingFilter.slice(end + 1);
}
if (remainingFilter) {
throw new Error(`"${remainingFilter}" of filter "${filter}" is not supported`);
}

return filteredUsers;
Expand Down Expand Up @@ -292,7 +323,7 @@ function handleGetCountRequests(): MockResponse {
/**
* Handles GET requests for user data and returns a fitting response.
*/
function handleGetUserRequests(xhr: MockXhr, _bCount: boolean): MockResponse {
function handleGetUserRequests(xhr: MockXhr, _count: boolean): MockResponse {
let count: number;
let expand: RegExpMatchArray | string[] | null;
let expand2: string;
Expand Down Expand Up @@ -381,7 +412,7 @@ function handleGetUserRequests(xhr: MockXhr, _bCount: boolean): MockResponse {
* Returns a specific user in the aUsers array.
*/
function getUserByIndex(index: number, properties: string[]): MockUser | null {
const helper: MockUser = { UserName: "" };
const helper = {} as MockUser;
const user = users[index];

if (user) {
Expand Down Expand Up @@ -509,10 +540,16 @@ function handleDeleteUserRequests(xhr: MockXhr): MockResponse {
* Handles POST requests for users and returns a fitting response.
*/
function handlePostUserRequests(xhr: MockXhr): MockResponse {
const user = getUserDataFromRequestBody(xhr.requestBody || "");
let user = getUserDataFromRequestBody(xhr.requestBody || "");

// Check if that user already exists
if (isUnique(user.UserName)) {
user = {
HomeAddress : null, // prevents drillDown errors
LastName : "",
FirstName : "",
...user
};
users.push(user);

let responseBody = '{"@odata.context": "' + getBaseUrl(xhr.url)
Expand Down Expand Up @@ -697,8 +734,7 @@ function handleAllRequests(xhr: MockXhr): void {
"Mockserver: Received " + xhr.method + " request to URL " + xhr.url,
(xhr.requestBody ? "Request body is:\n" + xhr.requestBody : "No request body.")
+ "\n",
logComponent
);
logComponent);

if (xhr.method === "POST" && /\$batch/.test(xhr.url)) {
response2 = handleBatchRequest(xhr);
Expand All @@ -716,16 +752,15 @@ function handleAllRequests(xhr: MockXhr): void {
"Mockserver: Sent response with return code " + response2[0],
("Response headers: " + JSON.stringify(response2[1]) + "\n\nResponse body:\n"
+ (response2[2] || "")) + "\n",
logComponent
);
logComponent);
}
}

export default {

/**
* Creates a Sinon fake service, intercepting all http requests to
* the URL defined in variable rBaseUrl above.
* the URL defined in variable baseUrl above.
* @returns a promise that is resolved when the mock server is started
*/
init(): Promise<unknown> {
Expand All @@ -744,16 +779,16 @@ export default {
sandbox.server.autoRespond = true;

// Register the requests for which responses should be faked.
sandbox.server.respondWith(rBaseUrl, handleAllRequests);
sandbox.server.respondWith(baseUrl, handleAllRequests);

// Apply a filter to the fake XmlHttpRequest.
// Otherwise, ALL requests (e.g. for the component, views etc.) would be
// intercepted.
sinon.FakeXMLHttpRequest.useFilters = true;
sinon.FakeXMLHttpRequest.addFilter((_sMethod: string, url: string) => {
sinon.FakeXMLHttpRequest.addFilter((_method: string, url: string) => {
// If the filter returns true, the request will NOT be faked.
// We only want to fake requests that go to the intended service.
return !rBaseUrl.test(url);
return !baseUrl.test(url);
});

// Set the logging level for console entries from the mock server
Expand Down
Loading