Compare commits
38 Commits
preview
...
adding-que
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae0cfc8c1e | ||
|
|
5c5afd2790 | ||
|
|
74ee6f34e4 | ||
|
|
9a9b7aeb86 | ||
|
|
cfc01bbc4f | ||
|
|
aaac6cc678 | ||
|
|
99fdf0c038 | ||
|
|
6ccecfecae | ||
|
|
e5f0a7ef73 | ||
|
|
de54f70570 | ||
|
|
e1c6118cb4 | ||
|
|
a3e4865d97 | ||
|
|
641df50422 | ||
|
|
0f026ed56c | ||
|
|
57a3c5f178 | ||
|
|
522ec8e071 | ||
|
|
7b1c3d8b3a | ||
|
|
5ba69b5021 | ||
|
|
fd796b54ef | ||
|
|
660344b623 | ||
|
|
d20c29a758 | ||
|
|
604cc0514c | ||
|
|
ff351455dd | ||
|
|
8f74bd32a9 | ||
|
|
c181afe8c6 | ||
|
|
9ade3e6b6a | ||
|
|
6f9737ae38 | ||
|
|
0a552b4346 | ||
|
|
6367081e9d | ||
|
|
3e73683d99 | ||
|
|
2b345ca447 | ||
|
|
20593f2fa2 | ||
|
|
ca45aa1cf7 | ||
|
|
e408836c2a | ||
|
|
3000a9e2df | ||
|
|
b83236de2a | ||
|
|
46b3b8aaec | ||
|
|
59f5c82684 |
10
.github/ISSUE_TEMPLATE/question--feedback-or-bug-.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/question--feedback-or-bug-.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: Question, feedback or bug?
|
||||||
|
about: Use our community!
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Please use our community on https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
|
||||||
28
.github/workflows/node.js.yml
vendored
Normal file
28
.github/workflows/node.js.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||||
|
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [10.x, 12.16.2]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm test
|
||||||
1
.mocharc.yml
Normal file
1
.mocharc.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
parallel: true
|
||||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
// List of extensions which should be recommended for users of this workspace.
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
// "CDS Editor !",
|
"SAPSE.vscode-cds",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"mechatroner.rainbow-csv",
|
"mechatroner.rainbow-csv",
|
||||||
|
|||||||
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@@ -6,24 +6,16 @@
|
|||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "bookshop",
|
"name": "bookshop",
|
||||||
"cwd": "${workspaceFolder}/bookshop",
|
"command": "cds watch bookshop",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "node",
|
"type": "node-terminal",
|
||||||
"runtimeExecutable": "npx",
|
|
||||||
"runtimeArgs": ["-n"],
|
|
||||||
"args": ["--", "cds", "run", "--with-mocks", "--in-memory?"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"skipFiles": ["<node_internals>/**"]
|
"skipFiles": ["<node_internals>/**"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Fiori App",
|
"name": "Fiori app",
|
||||||
"cwd": "${workspaceFolder}/fiori",
|
"command": "cds watch fiori",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "node",
|
"type": "node-terminal",
|
||||||
"runtimeExecutable": "npx",
|
|
||||||
"runtimeArgs": ["-n"],
|
|
||||||
"args": ["--", "cds", "run", "--with-mocks", "--in-memory?"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"skipFiles": ["<node_internals>/**"]
|
"skipFiles": ["<node_internals>/**"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo). → See [**Overview** of contained samples](samples.md)
|
Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo). → See [**Overview** of contained samples](samples.md)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Preliminaries
|
### Preliminaries
|
||||||
|
|
||||||
@@ -39,19 +40,19 @@ After that open this link in your browser: [http://localhost:4004](http://localh
|
|||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), e.g.:
|
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), for example:
|
||||||
```sh
|
```sh
|
||||||
npx jest
|
npx jest
|
||||||
```
|
```
|
||||||
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation which allows to run all tests.
|
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests.
|
||||||
|
|
||||||
|
|
||||||
## Get Support
|
## Get Support
|
||||||
|
|
||||||
Check out the cap docs at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
|
Check out the documentation at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
|
||||||
In case you find a bug or need support, please [open an issue in here](https://github.com/SAP-samples/cloud-cap-samples/issues/new).
|
In case you have a question, find a bug, or otherwise need support, please use our [community](https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce).
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSE) file.
|
Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSE) file.
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ ID;parent_ID;name
|
|||||||
19;10;Fairy Tale
|
19;10;Fairy Tale
|
||||||
20;;Non-Fiction
|
20;;Non-Fiction
|
||||||
21;20;Biography
|
21;20;Biography
|
||||||
22;20;Autobiography
|
22;21;Autobiography
|
||||||
23;20;Essay
|
23;20;Essay
|
||||||
24;20;Speech
|
24;20;Speech
|
||||||
|
|||||||
|
@@ -2,7 +2,7 @@ code;symbol;name;descr;numcode;minor;exponent
|
|||||||
EUR;€;Euro;European Euro;978;Cent;2
|
EUR;€;Euro;European Euro;978;Cent;2
|
||||||
USD;$;US Dollar;United States Dollar;840;Cent;2
|
USD;$;US Dollar;United States Dollar;840;Cent;2
|
||||||
CAD;$;Canadian Dollar;Canadian Dollar;124;Cent;2
|
CAD;$;Canadian Dollar;Canadian Dollar;124;Cent;2
|
||||||
AUD;$;Australian Dollar;Canadian Dollar;036;Cent;2
|
AUD;$;Australian Dollar;Australian Dollar;036;Cent;2
|
||||||
GBP;£;British Pound;Great Britain Pound;826;Penny;2
|
GBP;£;British Pound;Great Britain Pound;826;Penny;2
|
||||||
ILS;₪;Shekel;Israeli New Shekel;376;Agorat;2
|
ILS;₪;Shekel;Israeli New Shekel;376;Agorat;2
|
||||||
INR;₹;Rupee;Indian Rupee;356;Paise;2
|
INR;₹;Rupee;Indian Rupee;356;Paise;2
|
||||||
|
|||||||
|
@@ -7,7 +7,19 @@ AuthorID = Author ID
|
|||||||
Stock = Stock
|
Stock = Stock
|
||||||
Name = Name
|
Name = Name
|
||||||
AuthorName = Author's Name
|
AuthorName = Author's Name
|
||||||
|
DateOfBirth = Date of Birth
|
||||||
|
DateOfDeath = Date of Death
|
||||||
|
PlaceOfBirth = Place of Birth
|
||||||
|
PlaceOfDeath = Place of Death
|
||||||
Authors = Authors
|
Authors = Authors
|
||||||
Order = Order
|
Order = Order
|
||||||
Orders = Orders
|
Orders = Orders
|
||||||
Price = Price
|
Price = Price
|
||||||
|
|
||||||
|
Genre = Genre
|
||||||
|
Genres = Genres
|
||||||
|
SubGenres = Sub Genres
|
||||||
|
|
||||||
|
NumCode = Numeric Code
|
||||||
|
MinorUnit = Minor Unit
|
||||||
|
Exponent = Exponent
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("admin.Component", {
|
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
|
||||||
metadata:{ manifest:'json' }
|
"use strict";
|
||||||
}))
|
return AppComponent.extend("admin.Component", {
|
||||||
|
metadata: { manifest: "json" }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/* eslint no-undef:0 */
|
/* eslint no-undef:0 */
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"sap.ui5": {
|
"sap.ui5": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"libs": {
|
"libs": {
|
||||||
"sap.fe": {}
|
"sap.fe.templates": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models": {
|
"models": {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("bookshop.Component", {
|
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
|
||||||
metadata:{ manifest:'json' }
|
"use strict";
|
||||||
}))
|
return AppComponent.extend("bookshop.Component", {
|
||||||
|
metadata: { manifest: "json" }
|
||||||
|
});
|
||||||
|
});
|
||||||
/* eslint no-undef:0 */
|
/* eslint no-undef:0 */
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"sap.ui5": {
|
"sap.ui5": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"libs": {
|
"libs": {
|
||||||
"sap.fe": {}
|
"sap.fe.templates": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models": {
|
"models": {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
Common Annotations shared by all apps
|
Common Annotations shared by all apps
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using { sap.capire.bookshop as my } from '@capire/bookshop';
|
using { sap.capire.bookshop as my } from '@capire/bookshop';
|
||||||
|
using { sap.common } from '@capire/common';
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -13,7 +13,7 @@ annotate my.Books with @(
|
|||||||
Common.SemanticKey: [title],
|
Common.SemanticKey: [title],
|
||||||
UI: {
|
UI: {
|
||||||
Identification: [{Value:title}],
|
Identification: [{Value:title}],
|
||||||
SelectionFields: [ ID, author_ID, price, currency_code ],
|
SelectionFields: [ ID, author_ID, price, currency_code ],
|
||||||
LineItem: [
|
LineItem: [
|
||||||
{Value: ID},
|
{Value: ID},
|
||||||
{Value: title},
|
{Value: title},
|
||||||
@@ -28,25 +28,18 @@ annotate my.Books with @(
|
|||||||
author @ValueList.entity:'Authors';
|
author @ValueList.entity:'Authors';
|
||||||
};
|
};
|
||||||
|
|
||||||
annotate my.Authors with @(
|
|
||||||
UI: {
|
|
||||||
Identification: [{Value:name}],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Books Details
|
// Books Details
|
||||||
//
|
//
|
||||||
annotate my.Books with @(
|
annotate my.Books with @(
|
||||||
UI: {
|
UI: {
|
||||||
HeaderInfo: {
|
HeaderInfo: {
|
||||||
TypeName: '{i18n>Book}',
|
TypeName: '{i18n>Book}',
|
||||||
TypeNamePlural: '{i18n>Books}',
|
TypeNamePlural: '{i18n>Books}',
|
||||||
Title: {Value: title},
|
Title: {Value: title},
|
||||||
Description: {Value: author.name}
|
Description: {Value: author.name}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -66,15 +59,199 @@ annotate my.Books with {
|
|||||||
descr @UI.MultiLineText;
|
descr @UI.MultiLineText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Genres List
|
||||||
|
//
|
||||||
|
annotate my.Genres with @(
|
||||||
|
Common.SemanticKey: [name],
|
||||||
|
UI: {
|
||||||
|
SelectionFields: [ name ],
|
||||||
|
LineItem:[
|
||||||
|
{Value: name},
|
||||||
|
{Value: parent.name, Label: 'Main Genre'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Genre Details
|
||||||
|
//
|
||||||
|
annotate my.Genres with @(
|
||||||
|
UI: {
|
||||||
|
Identification: [{Value:name}],
|
||||||
|
HeaderInfo: {
|
||||||
|
TypeName: '{i18n>Genre}',
|
||||||
|
TypeNamePlural: '{i18n>Genres}',
|
||||||
|
Title: {Value: name},
|
||||||
|
Description: {Value: ID}
|
||||||
|
},
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>SubGenres}', Target: 'children/@UI.LineItem'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Genres Elements
|
||||||
|
//
|
||||||
annotate my.Genres with {
|
annotate my.Genres with {
|
||||||
name @title: '{i18n>Genre}';
|
ID @title: '{i18n>ID}';
|
||||||
|
name @title: '{i18n>Genre}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Authors List
|
||||||
|
//
|
||||||
|
annotate my.Authors with @(
|
||||||
|
Common.SemanticKey: [name],
|
||||||
|
UI: {
|
||||||
|
Identification: [{Value:name}],
|
||||||
|
SelectionFields: [ name ],
|
||||||
|
LineItem:[
|
||||||
|
{Value: ID},
|
||||||
|
{Value: name},
|
||||||
|
{Value: dateOfBirth},
|
||||||
|
{Value: dateOfDeath},
|
||||||
|
{Value: placeOfBirth},
|
||||||
|
{Value: placeOfDeath},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Author Details
|
||||||
|
//
|
||||||
|
annotate my.Authors with @(
|
||||||
|
UI: {
|
||||||
|
HeaderInfo: {
|
||||||
|
TypeName: '{i18n>Author}',
|
||||||
|
TypeNamePlural: '{i18n>Authors}',
|
||||||
|
Title: {Value: name},
|
||||||
|
Description: {Value: dateOfBirth}
|
||||||
|
},
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Target: 'books/@UI.LineItem'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Authors Elements
|
// Authors Elements
|
||||||
//
|
//
|
||||||
annotate my.Authors with {
|
annotate my.Authors with {
|
||||||
ID @title:'{i18n>ID}' @UI.HiddenFilter;
|
ID @title:'{i18n>ID}' @UI.HiddenFilter;
|
||||||
name @title:'{i18n>AuthorName}';
|
name @title:'{i18n>Name}';
|
||||||
|
dateOfBirth @title:'{i18n>DateOfBirth}';
|
||||||
|
dateOfDeath @title:'{i18n>DateOfDeath}';
|
||||||
|
placeOfBirth @title:'{i18n>PlaceOfBirth}';
|
||||||
|
placeOfDeath @title:'{i18n>PlaceOfDeath}';
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Languages List
|
||||||
|
//
|
||||||
|
annotate common.Languages with @(
|
||||||
|
Common.SemanticKey: [code],
|
||||||
|
Identification: [{Value:code}],
|
||||||
|
UI: {
|
||||||
|
SelectionFields: [ name, descr ],
|
||||||
|
LineItem:[
|
||||||
|
{Value: code},
|
||||||
|
{Value: name},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Language Details
|
||||||
|
//
|
||||||
|
annotate common.Languages with @(
|
||||||
|
UI: {
|
||||||
|
HeaderInfo: {
|
||||||
|
TypeName: '{i18n>Language}',
|
||||||
|
TypeNamePlural: '{i18n>Languages}',
|
||||||
|
Title: {Value: name},
|
||||||
|
Description: {Value: descr}
|
||||||
|
},
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
||||||
|
],
|
||||||
|
FieldGroup#Details: {
|
||||||
|
Data: [
|
||||||
|
{Value: code},
|
||||||
|
{Value: name},
|
||||||
|
{Value: descr}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Currencies List
|
||||||
|
//
|
||||||
|
annotate common.Currencies with @(
|
||||||
|
Common.SemanticKey: [code],
|
||||||
|
Identification: [{Value:code}],
|
||||||
|
UI: {
|
||||||
|
SelectionFields: [ name, descr ],
|
||||||
|
LineItem:[
|
||||||
|
{Value: descr},
|
||||||
|
{Value: symbol},
|
||||||
|
{Value: code},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Currency Details
|
||||||
|
//
|
||||||
|
annotate common.Currencies with @(
|
||||||
|
UI: {
|
||||||
|
HeaderInfo: {
|
||||||
|
TypeName: '{i18n>Currency}',
|
||||||
|
TypeNamePlural: '{i18n>Currencies}',
|
||||||
|
Title: {Value: descr},
|
||||||
|
Description: {Value: code}
|
||||||
|
},
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Extended}', Target: '@UI.FieldGroup#Extended'},
|
||||||
|
],
|
||||||
|
FieldGroup#Details: {
|
||||||
|
Data: [
|
||||||
|
{Value: name},
|
||||||
|
{Value: symbol},
|
||||||
|
{Value: code},
|
||||||
|
{Value: descr}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
FieldGroup#Extended: {
|
||||||
|
Data: [
|
||||||
|
{Value: numcode},
|
||||||
|
{Value: minor},
|
||||||
|
{Value: exponent}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Currencies Elements
|
||||||
|
//
|
||||||
|
annotate common.Currencies with {
|
||||||
|
numcode @title:'{i18n>NumCode}';
|
||||||
|
minor @title:'{i18n>MinorUnit}';
|
||||||
|
exponent @title:'{i18n>Exponent}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("orders.Component", {
|
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
|
||||||
metadata:{ manifest:'json' }
|
"use strict";
|
||||||
}))
|
return AppComponent.extend("orders.Component", {
|
||||||
|
metadata: { manifest: "json" }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/* eslint no-undef:0 */
|
/* eslint no-undef:0 */
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"sap.ui5": {
|
"sap.ui5": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"libs": {
|
"libs": {
|
||||||
"sap.fe": {}
|
"sap.fe.templates": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models": {
|
"models": {
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
"sqlite3": "^4"
|
"sqlite3": "^4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"bookshop": "cds watch bookshop",
|
||||||
|
"fiori": "cds watch fiori",
|
||||||
"mocha": "npx mocha || echo",
|
"mocha": "npx mocha || echo",
|
||||||
"jest": "npx jest --verbose",
|
"jest": "npx jest --verbose",
|
||||||
"test": "npm run jest -s"
|
"test": "npm run jest -s"
|
||||||
|
|||||||
1
reviewed/.env
Normal file
1
reviewed/.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
cds.requires.messaging.kind = file-based-messaging
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
"name": "@capire/bookshop-with-reviews",
|
"name": "@capire/bookshop-with-reviews",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/bookshop": "../../../bookshop",
|
"@capire/bookshop": "../bookshop",
|
||||||
"@capire/reviews": "..",
|
"@capire/reviews": "../reviews",
|
||||||
"@sap/cds": "^3.33.1",
|
"@sap/cds": "^3.33.1",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
},
|
},
|
||||||
@@ -14,10 +14,7 @@
|
|||||||
},
|
},
|
||||||
"ReviewsService": {
|
"ReviewsService": {
|
||||||
"kind": "odata", "model": "@capire/reviews"
|
"kind": "odata", "model": "@capire/reviews"
|
||||||
},
|
|
||||||
"messaging": {
|
|
||||||
"kind": "file-based-messaging"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,9 +12,13 @@ cds.once('served', async()=>{
|
|||||||
// react on event messages from reviews service
|
// react on event messages from reviews service
|
||||||
const ReviewsService = await cds.connect.to ('ReviewsService')
|
const ReviewsService = await cds.connect.to ('ReviewsService')
|
||||||
const db = await cds.connect.to ('db')
|
const db = await cds.connect.to ('db')
|
||||||
|
|
||||||
|
// reflect entities required below...
|
||||||
|
const { Books } = db.entities('sap.capire.bookshop')
|
||||||
|
const { Reviews } = ReviewsService.entities
|
||||||
|
|
||||||
ReviewsService.on ('reviewed', (msg) => {
|
ReviewsService.on ('reviewed', (msg) => {
|
||||||
console.debug ('> received:', msg.event, msg.data)
|
console.debug ('> received:', msg.event, msg.data)
|
||||||
const { Books } = db.entities('sap.capire.bookshop')
|
|
||||||
const { subject, rating } = msg.data
|
const { subject, rating } = msg.data
|
||||||
const tx = db.tx (msg) // TODO: db.tx(msg) fully implemented?
|
const tx = db.tx (msg) // TODO: db.tx(msg) fully implemented?
|
||||||
return tx.update (Books,subject) .with ({rating})
|
return tx.update (Books,subject) .with ({rating})
|
||||||
@@ -24,17 +28,17 @@ cds.once('served', async()=>{
|
|||||||
const CatalogService = await cds.connect.to ('CatalogService')
|
const CatalogService = await cds.connect.to ('CatalogService')
|
||||||
CatalogService.impl (srv => srv.on ('READ', 'Books/reviews', (req) => {
|
CatalogService.impl (srv => srv.on ('READ', 'Books/reviews', (req) => {
|
||||||
console.debug ('> delegating to ReviewsService')
|
console.debug ('> delegating to ReviewsService')
|
||||||
const { Reviews } = ReviewsService.entities
|
const [ id ] = req.params
|
||||||
const [ subject ] = req.params
|
const tx = ReviewsService.tx(req)
|
||||||
const tx = ReviewsService.tx (req)
|
return tx.read (Reviews) .where ({ subject: String(id) })
|
||||||
return tx.read (Reviews) .where({subject}) .columns (req.query.SELECT.columns)
|
.columns (req.query.SELECT.columns)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Other bootstrapping events you could hook in to...
|
// Other bootstrapping events you could hook in to...
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
cds.on('boostrap',(app) => {/* ... */})
|
cds.on('bootstrap',(app) => {/* ... */})
|
||||||
cds.on('loaded', (model) => {/* ... */})
|
cds.on('loaded', (model) => {/* ... */})
|
||||||
cds.on('connect', (srv) => {/* ... */})
|
cds.on('connect', (srv) => {/* ... */})
|
||||||
cds.on('serving', (srv) => {/* ... */})
|
cds.on('serving', (srv) => {/* ... */})
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
#################################################
|
#################################################
|
||||||
#
|
#
|
||||||
# To ReviewsService mocked in bookshop process
|
# To ReviewsService
|
||||||
#
|
#
|
||||||
|
# move the right down:
|
||||||
|
@reviews-service = http://localhost:4004/reviews
|
||||||
|
@reviews-service = http://localhost:5005/reviews
|
||||||
|
|
||||||
GET http://localhost:4004/reviews/Reviews?
|
### Get all reviews
|
||||||
|
GET {{reviews-service}}/Reviews
|
||||||
|
|
||||||
###
|
### Add a new review (with random rating)
|
||||||
|
POST {{reviews-service}}/Reviews
|
||||||
POST http://localhost:4004/reviews/Reviews
|
|
||||||
Content-Type: application/json;IEEE754Compatible=true
|
Content-Type: application/json;IEEE754Compatible=true
|
||||||
|
|
||||||
{"subject":"201", "title":"boo"}
|
{"subject":"201", "title":"boo"}
|
||||||
@@ -20,17 +23,18 @@ Content-Type: application/json;IEEE754Compatible=true
|
|||||||
# (both in-process as well as separate one)
|
# (both in-process as well as separate one)
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@bookshop = http://localhost:4004
|
||||||
|
|
||||||
### Request to CatalogService > delegated to ReviewsService
|
### Request to CatalogService > delegated to ReviewsService
|
||||||
GET http://localhost:4004/browse/Books(201)/reviews?
|
GET {{bookshop}}/browse/Books(201)/reviews?
|
||||||
&$select=rating,date,reviewer,title
|
&$select=rating,date,reviewer,title
|
||||||
|
|
||||||
### Alternative OData URL
|
### Alternative OData URL
|
||||||
GET http://localhost:4004/browse/Books/201/reviews?
|
GET {{bookshop}}/browse/Books/201/reviews?
|
||||||
&$select=rating,date,reviewer,title
|
&$select=rating,date,reviewer,title
|
||||||
|
|
||||||
###
|
###
|
||||||
GET http://localhost:4004/browse/Books(201)?
|
GET {{bookshop}}/browse/Books(201)?
|
||||||
&$select=ID,title,rating
|
&$select=ID,title,rating
|
||||||
&$expand=reviews
|
&$expand=reviews
|
||||||
# Note: the $expand only works in case of ReviewsService in same process
|
# Note: the $expand only works in case of ReviewsService in same process
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
cds.requires.messaging.kind = file-based-messaging
|
||||||
PORT = 5005
|
PORT = 5005
|
||||||
@@ -12,15 +12,12 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"reviews-service": "cds watch",
|
"reviews-service": "cds watch",
|
||||||
"bookshop": "cds watch test/bookshop"
|
"books-reviewed": "cds watch ../reviewed"
|
||||||
},
|
},
|
||||||
"cds": {
|
"cds": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"db": {
|
"db": {
|
||||||
"kind": "sql"
|
"kind": "sql"
|
||||||
},
|
|
||||||
"messaging": {
|
|
||||||
"kind": "file-based-messaging"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
#################################################
|
|
||||||
#
|
|
||||||
# To ReviewsService running as separate process
|
|
||||||
#
|
|
||||||
|
|
||||||
GET http://localhost:5005/reviews/Reviews?
|
|
||||||
|
|
||||||
###
|
|
||||||
|
|
||||||
POST http://localhost:5005/reviews/Reviews
|
|
||||||
Content-Type: application/json;IEEE754Compatible=true
|
|
||||||
|
|
||||||
{"subject":"201", "title":"boo"}
|
|
||||||
@@ -261,7 +261,7 @@ describe('cds.ql → cqn', () => {
|
|||||||
// same for works distinct
|
// same for works distinct
|
||||||
})
|
})
|
||||||
|
|
||||||
xtest('where ( ... cql | {x:y} )', () => {
|
test.skip('where ( ... cql | {x:y} )', () => {
|
||||||
const args = [`foo`, "'bar'", 3]
|
const args = [`foo`, "'bar'", 3]
|
||||||
const ID = 11
|
const ID = 11
|
||||||
|
|
||||||
@@ -359,6 +359,11 @@ describe('cds.ql → cqn', () => {
|
|||||||
CQL`SELECT from Foo where x in (SELECT y from Bar)`
|
CQL`SELECT from Foo where x in (SELECT y from Bar)`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// using query api
|
||||||
|
expect(SELECT.from('Books').where(
|
||||||
|
`author.name in`, SELECT('name').from('Authors'))).to.eql(CQL`SELECT from Books where author.name in (SELECT name from Authors)`
|
||||||
|
)
|
||||||
|
|
||||||
// in classical semi joins
|
// in classical semi joins
|
||||||
expect(
|
expect(
|
||||||
SELECT('x').from(Foo) .where ( `exists`,
|
SELECT('x').from(Foo) .where ( `exists`,
|
||||||
@@ -461,47 +466,38 @@ describe('cds.ql → cqn', () => {
|
|||||||
UPDATE.with allows to pass in plain data payloads, e.g. as obtained from REST clients.
|
UPDATE.with allows to pass in plain data payloads, e.g. as obtained from REST clients.
|
||||||
In addition, UPDATE.with supports specifying expressions, either in CQL fragements
|
In addition, UPDATE.with supports specifying expressions, either in CQL fragements
|
||||||
notation or as simple expression objects.
|
notation or as simple expression objects.
|
||||||
*/
|
|
||||||
test('with', () => {
|
UPDATE.data allows to pass in plain data payloads, e.g. as obtained from REST clients.
|
||||||
expect(UPDATE(Foo).with(`foo=`, 11, `bar-=`, 22))
|
The passed in object can be modified subsequently, e.g. by adding or modifying values
|
||||||
|
before the query is finally executed.
|
||||||
|
*/
|
||||||
|
test('with + data', () => {
|
||||||
|
if (cds.version < '4.1.0') return
|
||||||
|
const o = {}
|
||||||
|
const q = UPDATE(Foo).data(o).with(`bar-=`, 22)
|
||||||
|
o.foo = 11
|
||||||
|
expect(q)
|
||||||
|
.to.eql(UPDATE(Foo).with(`foo=`, 11, `bar-=`, 22))
|
||||||
.to.eql(UPDATE(Foo).with({ foo: 11, bar: { '-=': 22 } }))
|
.to.eql(UPDATE(Foo).with({ foo: 11, bar: { '-=': 22 } }))
|
||||||
.to.eql({
|
.to.eql({
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
entity: 'Foo',
|
entity: 'Foo',
|
||||||
|
data: { foo: 11 },
|
||||||
with: {
|
with: {
|
||||||
foo: { val: 11 },
|
|
||||||
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
|
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// some more
|
// some more
|
||||||
// expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({
|
expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({
|
||||||
// UPDATE: {
|
|
||||||
// entity: 'Foo',
|
|
||||||
// with: {
|
|
||||||
// bar: { func: 'coalesce', args: [{ ref: ['x'] }, { ref: ['y'] }] },
|
|
||||||
// car: { val: "foo's bar, car" },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
UPDATE.data allows to pass in plain data payloads, e.g. as obtained from REST clients.
|
|
||||||
The passed in object can be modified subsequently, e.g. by adding or modifying values
|
|
||||||
before the query is finally executed.
|
|
||||||
*/
|
|
||||||
test('data', () => {
|
|
||||||
const o = {}
|
|
||||||
const q = UPDATE(Foo).data(o).with(`bar-=`, 22)
|
|
||||||
o.foo = 11
|
|
||||||
expect(q).to.eql({
|
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
entity: 'Foo',
|
entity: 'Foo',
|
||||||
data: { foo: 11 },
|
data: {
|
||||||
|
car: "foo's bar, car",
|
||||||
|
},
|
||||||
with: {
|
with: {
|
||||||
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
|
bar: { func: 'coalesce', args: [{ ref: ['x'] }, { ref: ['y'] }] },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
const { expect } = require('./capire')
|
const { expect } = require('./capire')
|
||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
|
const cwd = process.cwd()
|
||||||
|
before (()=> process.chdir(__dirname))
|
||||||
|
after(()=> process.chdir(cwd))
|
||||||
|
|
||||||
describe('Consuming Services locally', () => {
|
describe('Consuming Services locally', () => {
|
||||||
//
|
//
|
||||||
before('bootstrap db and services', async () => {
|
before('bootstrap db and services', async () => {
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
const cwd = process.cwd()
|
||||||
|
const is_jest = !!global.test
|
||||||
|
if (is_jest) { // it's jest
|
||||||
|
global.before = (msg,fn) => global.beforeAll(fn||msg)
|
||||||
|
global.after = (msg,fn) => global.afterAll(fn||msg)
|
||||||
|
}
|
||||||
|
before (()=> process.chdir(__dirname))
|
||||||
|
after (()=> process.chdir(cwd))
|
||||||
|
|
||||||
describe('Custom Handlers', () => {
|
describe('Custom Handlers', () => {
|
||||||
const { GET, POST, expect } = require('./capire').launch('bookshop')
|
const { GET, POST, expect } = require('./capire').launch('bookshop')
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ describe('Localized Data', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('supports @cds.localized:false', async ()=>{
|
xit('supports @cds.localized:false', async ()=>{
|
||||||
const { data } = await GET(`/browse/BooksSans?&$select=title,localized_title&$expand=currency&$filter=locale eq 'de' or locale eq null`, {
|
const { data } = await GET(`/browse/BooksSans?&$select=title,localized_title&$expand=currency&$filter=locale eq 'de' or locale eq null`, {
|
||||||
headers: { 'Accept-Language': 'de' },
|
headers: { 'Accept-Language': 'de' },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe('Messaging', ()=>{
|
|||||||
|
|
||||||
let N=0, received=[], M=0
|
let N=0, received=[], M=0
|
||||||
it ('should add messaging event handlers', ()=>{
|
it ('should add messaging event handlers', ()=>{
|
||||||
srv.on('reviewed', (msg)=> received.push(msg))
|
srv.on('reviewed', (msg)=> {received.push(msg)})
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('should add more messaging event handlers', ()=>{
|
it ('should add more messaging event handlers', ()=>{
|
||||||
|
|||||||
71
test/querying.test.js
Normal file
71
test/querying.test.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
describe('Querying', () => {
|
||||||
|
const { GET, expect } = require('./capire').launch('bookshop')
|
||||||
|
const Authors = {name:'sap.capire.bookshop.Authors'}
|
||||||
|
|
||||||
|
it('should SELECT from Authors', async () => {
|
||||||
|
const authors = await SELECT.from (Authors, a => {
|
||||||
|
a.name
|
||||||
|
})
|
||||||
|
expect(authors).to.eql([
|
||||||
|
{ name: 'Emily Brontë' },
|
||||||
|
{ name: 'Charlotte Brontë' },
|
||||||
|
{ name: 'Edgar Allen Poe' },
|
||||||
|
{ name: 'Richard Carpenter' },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should SELECT name from Authors', async () => {
|
||||||
|
const authors = await SELECT.from (Authors, a => {
|
||||||
|
a.name
|
||||||
|
})
|
||||||
|
expect(authors).to.eql([
|
||||||
|
{ name: 'Emily Brontë' },
|
||||||
|
{ name: 'Charlotte Brontë' },
|
||||||
|
{ name: 'Edgar Allen Poe' },
|
||||||
|
{ name: 'Richard Carpenter' },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should GET /Authors?$select=name&$expand=books($select=title)', async () => {
|
||||||
|
const {data:{value:authors}} = await GET ('/admin/Authors?$select=name&$expand=books($select=title)')
|
||||||
|
expect(authors).to.containSubset([
|
||||||
|
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
|
||||||
|
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
|
||||||
|
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
|
||||||
|
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should SELECT from Authors { name, books { title } }', async () => {
|
||||||
|
const authors = await SELECT.from(Authors, a => { a.name, a.books(b=>{ b.title }) })
|
||||||
|
expect(authors).to.eql([
|
||||||
|
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
|
||||||
|
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
|
||||||
|
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
|
||||||
|
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should GET /Authors?$expand=books', async () => {
|
||||||
|
const {data:{value:authors}} = await GET ('/admin/Authors?$select=name&$expand=books($select=title)')
|
||||||
|
expect(authors).to.containSubset([
|
||||||
|
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
|
||||||
|
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
|
||||||
|
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
|
||||||
|
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should SELECT from Authors { *, books{*} }', async () => {
|
||||||
|
const authors = await SELECT.from(Authors, a => { a('*'), a.books(b => b('*')) })
|
||||||
|
// const authors = await SELECT.from(Authors, ['*',{ref:['books'], expand:['*']}])
|
||||||
|
// const authors = await SELECT.from(Authors, [{ref:['*']},{ref:['books'], expand:[{ref:['*']}]}])
|
||||||
|
expect(authors).to.eql([
|
||||||
|
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
|
||||||
|
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
|
||||||
|
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
|
||||||
|
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user