Compare commits

..

172 Commits

Author SHA1 Message Date
Daniel
f3965ac697 Using upserts for initial data 2022-12-20 13:31:07 +01:00
Daniel
28cfceeaf0 Avoid dangling IO resources 2022-12-20 13:30:17 +01:00
dependabot[bot]
879546829a Bump @sap/cds from 6.3.2 to 6.4.0
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.3.2 to 6.4.0.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-16 13:10:09 +01:00
dependabot[bot]
b7e435d611 Bump sqlite3 from 5.1.3 to 5.1.4
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.1.3 to 5.1.4.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.1.3...v5.1.4)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-13 17:06:51 +01:00
dependabot[bot]
4f5f075697 Bump sqlite3 from 5.1.2 to 5.1.3
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.1.2 to 5.1.3.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.1.2...v5.1.3)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-12 14:33:59 +01:00
dependabot[bot]
710c83aa1c Bump axios from 1.2.0 to 1.2.1
Bumps [axios](https://github.com/axios/axios) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-06 13:39:02 +01:00
dependabot[bot]
e9fbba9f46 Bump axios from 1.1.3 to 1.2.0
Bumps [axios](https://github.com/axios/axios) from 1.1.3 to 1.2.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.1.3...v1.2.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-05 13:41:22 +01:00
dependabot[bot]
0d96447d4b Bump @sap/cds from 6.3.1 to 6.3.2
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.3.1 to 6.3.2.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-05 13:39:50 +01:00
Daniel
ed1510d173 sorting loggers 2022-11-30 17:37:51 +01:00
Daniel
d07b20a689 Added loggers sample 2022-11-28 17:54:32 +01:00
dependabot[bot]
e6584c4c15 Bump chai from 4.3.6 to 4.3.7
Bumps [chai](https://github.com/chaijs/chai) from 4.3.6 to 4.3.7.
- [Release notes](https://github.com/chaijs/chai/releases)
- [Changelog](https://github.com/chaijs/chai/blob/4.x.x/History.md)
- [Commits](https://github.com/chaijs/chai/compare/v4.3.6...v4.3.7)

---
updated-dependencies:
- dependency-name: chai
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-09 16:17:30 +01:00
dependabot[bot]
2a6fca177b Bump axios from 1.1.2 to 1.1.3
Bumps [axios](https://github.com/axios/axios) from 1.1.2 to 1.1.3.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.1.2...v1.1.3)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-08 14:43:24 +01:00
Pierre Fritsch
d9058f9b51 Manage Orders: Switch to translatable texts (#424)
Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
2022-11-08 14:38:41 +01:00
Pierre Fritsch
affd1718ef Fiori app *Manage Orders*: Hide technical UUID's (#423)
* Hide Order ID
* Hide Order Item ID

Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
2022-11-08 13:33:25 +00:00
Pierre Fritsch
5ff72c3f02 Hide ID and ID_Texts for Books.texts (#422)
Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
2022-11-08 13:30:28 +00:00
Pierre Fritsch
666001d564 Filter criteria: Display the Genre name (#421)
Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
2022-11-08 13:28:36 +00:00
Pierre Fritsch
e4214b0e40 Fiori app *Browse Books*: Add missing column labels (#420)
* Add labels for `rating` and `numberOfReviews`
* Add label for `currency.symbol`
* Add label for `descr`
* Add label for `image`

Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
2022-11-08 13:26:48 +00:00
Pierre Fritsch
ad76c699c9 Add column labels for age and lifetime (#419)
Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
2022-11-08 13:23:44 +00:00
dependabot[bot]
0ad8baefc7 Bump @sap/cds from 6.2.2 to 6.3.1
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.2.2 to 6.3.1.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-08 14:21:28 +01:00
Daniel
3f7b43346f Buffer() is deprecated 2022-11-06 18:03:38 +01:00
Christian Georgi
6981841d86 Delete question--feedback-or-bug-.md 2022-10-24 09:54:48 +02:00
Christian Georgi
18c29d8b67 Create config.yml 2022-10-24 09:54:18 +02:00
Daniel
02fc1a89a1 Fixed expected tenants in odata.test.js 2022-10-19 13:00:20 +02:00
dependabot[bot]
94b74d76ad Bump @sap/cds from 6.2.1 to 6.2.2 (#413)
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.2.1 to 6.2.2.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-14 15:41:17 +02:00
Daniel
b0b4412c7a Fixed genre for Eleonora 2022-10-12 14:59:10 +02:00
dependabot[bot]
70cfa82a35 Bump axios from 0.27.2 to 1.1.2 (#410)
Bumps [axios](https://github.com/axios/axios) from 0.27.2 to 1.1.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.27.2...v1.1.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-10 14:03:08 +00:00
dependabot[bot]
d17d74f1ec Bump @sap/cds from 6.1.3 to 6.2.1 (#411)
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.1.3 to 6.2.1.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-10 15:52:35 +02:00
dependabot[bot]
9fbf7c69c9 Bump semver from 7.3.7 to 7.3.8 (#408)
Bumps [semver](https://github.com/npm/node-semver) from 7.3.7 to 7.3.8.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.3.7...v7.3.8)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-05 16:15:26 +02:00
Daniel
f54a8cecb7 Revert "Extensibility Walkthrough in capire (#405)"
This reverts commit 608e16090c.
2022-10-04 12:12:30 +02:00
Daniel Hutzel
608e16090c Extensibility Walkthrough in capire (#405)
* Extension project template

* SalesRegion ->  x_SalesRegion

* enhanced order repo

* updated package-lock.json
2022-10-04 12:05:57 +02:00
Daniel Hutzel
bd0f514026 Updated workspace setup (#406) 2022-10-04 11:59:22 +02:00
Daniel
ee2fdd0989 cosmetics 2022-10-04 11:48:01 +02:00
dependabot[bot]
92f7489def Bump sqlite3 from 5.1.1 to 5.1.2 (#404)
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.1.1...v5.1.2)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-04 10:36:43 +02:00
Daniel
e222645935 . 2022-10-03 12:19:20 +02:00
Daniel
8676f7184d . 2022-10-03 12:18:40 +02:00
Daniel
a069f2d7de Using reflected entitites 2022-09-30 10:07:39 +02:00
Daniel
254362daa1 Fine-tuned data-service definition 2022-09-22 17:09:20 +02:00
Daniel
e419fda74e UserService entities are persistence.skipped 2022-09-21 16:33:29 +02:00
Daniel
6bb51a8807 minor 2022-09-20 21:41:59 +02:00
Daniel Hutzel
36b339a362 Re-enabling tests for cds.ql where clauses fix (#403) 2022-09-20 16:52:40 +02:00
Daniel
47655c5f48 unlock 2022-09-16 18:43:11 +02:00
Christian Georgi
aa5fda976a Support csrf tokens in Vue app (#394) 2022-09-16 15:46:20 +02:00
dependabot[bot]
b97253f266 Bump @types/node from 18.7.14 to 18.7.15 (#395)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 18.7.14 to 18.7.15.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-16 13:41:45 +00:00
dependabot[bot]
65379110e2 Bump sqlite3 from 5.1.0 to 5.1.1 (#401)
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.1.0...v5.1.1)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-16 13:39:32 +00:00
Daniel Hutzel
b8e04027d5 Added few cds.ql tests (#402) 2022-09-16 14:55:38 +02:00
dependabot[bot]
630838a7b4 Bump sqlite3 from 5.0.11 to 5.1.0 (#399)
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.11 to 5.1.0.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.11...v5.1.0)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 14:49:22 +02:00
dependabot[bot]
09c2a40c7d Bump @sap/cds from 6.1.2 to 6.1.3 (#398)
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.1.2 to 6.1.3.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 14:47:17 +02:00
Dr. David A. Kunz
88e527adba Merge pull request #397 from SAP-samples/fix-language-parameters
fix: localized tests
2022-09-09 15:46:24 +02:00
D065023
b0d74b0ea4 fixed localized tests 2022-09-09 15:38:23 +02:00
dependabot[bot]
b4f6d5714c Bump @sap/cds from 6.1.1 to 6.1.2 (#396)
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.1.1 to 6.1.2.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-05 22:33:33 +02:00
Daniel
18f9f6c3f8 Removing ts-jest complexity
-> keeping our CI/CD pipelines fast and simple
2022-09-01 11:02:12 +02:00
Lothar Bender
a0d5355f7d fix hana deploy-format configuration (#392)
Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
2022-08-26 12:34:43 +00:00
dependabot[bot]
37538a368c Bump @sap/cds from 6.1.0 to 6.1.1 (#393)
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.1.0 to 6.1.1.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-26 14:33:05 +02:00
Daniel
662f93a995 cosmetics 2022-08-18 18:02:28 +02:00
Daniel
76bed22f40 Updating .to ES2022 2022-08-18 08:01:50 +02:00
Daniel Hutzel
8ab66b4130 Fix tests to run with mocha (#389) 2022-08-15 14:11:52 +02:00
Daniel
2a37ef8be1 . 2022-08-12 02:52:03 +02:00
dependabot[bot]
de35537c04 Bump @sap/cds from 6.0.4 to 6.1.0 (#388)
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.0.4 to 6.1.0.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-11 14:49:14 +02:00
Christian Georgi
e477879d71 Do not depend on cwd in test 2022-08-08 17:07:13 +02:00
Christian Georgi
38593baeb8 Expose user service for downstream usages 2022-08-04 11:03:44 +02:00
dependabot[bot]
f0e8575e5e Bump sqlite3 from 5.0.10 to 5.0.11 (#386)
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.10 to 5.0.11.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.10...v5.0.11)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-01 14:22:50 +02:00
Christian Georgi
b52a9c3a21 Remove dummy auth
Gets in the way if we want to switch to MT.
Other apps don't set it either.
2022-07-25 21:19:15 +02:00
Christian Georgi
88cb9c0069 Facade model for fiori 2022-07-25 17:14:52 +02:00
dependabot[bot]
42f7e1920b Bump sqlite3 from 5.0.9 to 5.0.10 (#385)
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.9 to 5.0.10.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Changelog](https://github.com/TryGhost/node-sqlite3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.9...v5.0.10)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-22 16:42:12 +02:00
dependabot[bot]
dea92b54d8 Bump @sap/cds from 6.0.3 to 6.0.4 (#384)
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.0.3 to 6.0.4.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-20 15:36:02 +02:00
Dr. David A. Kunz
9a98ffd298 fix(reviews): authorization restrictions (#382) 2022-07-18 14:02:43 +02:00
dependabot[bot]
57cda61b02 Bump sqlite3 from 5.0.8 to 5.0.9 (#381)
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.8 to 5.0.9.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Changelog](https://github.com/TryGhost/node-sqlite3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.8...v5.0.9)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-15 12:17:24 +00:00
dependabot[bot]
8d26fc1f45 Bump @sap/cds from 6.0.2 to 6.0.3 (#380)
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.0.2 to 6.0.3.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-15 14:15:18 +02:00
Daniel Hutzel
5c4247b14c Fix usage of unqialified names with cds.db (#378) 2022-07-13 18:07:18 +02:00
dependabot[bot]
a704f12b40 Bump @types/jest from 27.5.2 to 28.1.4 (#377)
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 27.5.2 to 28.1.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-12 14:32:41 +02:00
Christian Georgi
b981f8fb02 Update package-lock to fix sec. error 2022-07-12 11:30:08 +02:00
dependabot[bot]
f45f0a05b3 Bump @sap/cds from 6.0.1 to 6.0.2 (#376)
Bumps [@sap/cds](https://cap.cloud.sap/) from 6.0.1 to 6.0.2.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-07 16:08:44 +02:00
dependabot[bot]
a3adca1919 Bump @types/jest from 27.5.2 to 28.1.4 (#374)
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 27.5.2 to 28.1.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 18:15:08 +02:00
Christian Georgi
ac5e631a92 Tests for hierarchies (#375) 2022-07-05 17:46:15 +02:00
dependabot[bot]
dc72a7dc9e Bump @sap/cds from 5.9.8 to 6.0.1 (#373)
* Bump @sap/cds from 5.9.8 to 6.0.1

Bumps [@sap/cds](https://cap.cloud.sap/) from 5.9.8 to 6.0.1.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update package-lock

* Remove Node 12 pipeline

* Add axios

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Christian Georgi <christian.georgi@sap.com>
2022-07-05 15:59:46 +02:00
DJ Adams
d915bd7b54 Minor improvements to the samples docu (#372)
* fix Capire URL for Fiori

* bring Fiori notes in line with tour notes

* make sentence more readable

* be explicit about Node.js LTS

This is because earlier versions of Node.js come with earlier versions
of npm, but npm version 8 is requires for the monorepo package features
in this repo.
2022-07-04 18:52:37 +02:00
DJ Adams
ec4d7a4b92 fix Capire URL for Fiori (#371) 2022-07-04 16:56:54 +02:00
Ajinkya Patil
07a686264d Merge pull request #370 from SAP-samples/reuse-compliance
reuse compliance
2022-06-30 19:30:48 +02:00
ajinkyapatil8190
88c9f31a24 reuse compliance 2022-06-30 19:25:16 +02:00
dependabot[bot]
bcb6ffa20f Bump @sap/cds-odata-v2-adapter-proxy from 1.8.20 to 1.8.21 (#368)
Bumps [@sap/cds-odata-v2-adapter-proxy](https://cap.cloud.sap/) from 1.8.20 to 1.8.21.

---
updated-dependencies:
- dependency-name: "@sap/cds-odata-v2-adapter-proxy"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-30 14:28:45 +02:00
Daniel Hutzel
f29f66c41e Switch to npm workspaces (#342)
* Switch to npm workspaces
* Install npm globally

Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
Co-authored-by: Christian Georgi <christian.georgi@sap.com>
2022-06-29 09:39:23 +02:00
Christian Georgi
96375aa221 Disable test temporarily 2022-06-28 09:11:39 +02:00
Christian Georgi
c1329a6f20 Update lock file 2022-06-28 09:11:39 +02:00
Christian Georgi
babb5780b0 Refactor db parts 2022-06-28 09:11:39 +02:00
Christian Georgi
cae45fcd87 Tests 2022-06-28 09:11:39 +02:00
Christian Georgi
bf2b12e4e8 Hierarchy v2 sample
Including
- CDS OData V2 Adapter Proxy
- OData V2
- Fiori Elements V2
- SAPUI5
- List Report (type `TreeTable`)
- `@sap.hierarchy` annotations
2022-06-28 09:11:39 +02:00
dependabot[bot]
e44d62ac49 Bump @sap/cds from 5.9.7 to 5.9.8
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.9.7 to 5.9.8.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-27 16:35:00 +02:00
ajinkyapatil8190
0e053f98f3 reuse compliance 2022-06-23 14:58:33 +02:00
dependabot[bot]
662bfc35d3 Bump @sap/cds from 5.9.6 to 5.9.7
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.9.6 to 5.9.7.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-13 15:23:37 +02:00
Christian Georgi
af605bf685 Some bad restrict stuff 2022-05-31 16:23:28 +02:00
Christian Georgi
04411e35a7 Exclude private cds config 2022-05-31 15:56:36 +02:00
dependabot[bot]
8f5c33f4f5 Bump @sap/cds from 5.9.5 to 5.9.6
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.9.5 to 5.9.6.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-25 14:20:28 +02:00
Daniel Hutzel
a893184736 Add login to bookshop Vue.js app (#358) 2022-05-19 12:16:11 +02:00
dependabot[bot]
ee4dcf116c Bump @sap/cds from 5.9.4 to 5.9.5
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.9.4 to 5.9.5.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-10 14:19:55 +02:00
dependabot[bot]
58cef5558d Bump sqlite3 from 5.0.6 to 5.0.8
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.6 to 5.0.8.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Changelog](https://github.com/TryGhost/node-sqlite3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.6...v5.0.8)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-09 18:39:39 +02:00
dependabot[bot]
7753cda65d Bump @sap/cds from 5.9.3 to 5.9.4
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.9.3 to 5.9.4.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-03 17:23:37 +02:00
dependabot[bot]
df57a9d8d0 Bump sqlite3 from 5.0.5 to 5.0.6
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.5 to 5.0.6.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Changelog](https://github.com/TryGhost/node-sqlite3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.5...v5.0.6)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-28 14:19:35 +02:00
dependabot[bot]
2d92b851f1 Bump @sap/cds from 5.9.2 to 5.9.3
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.9.2 to 5.9.3.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-27 15:42:39 +02:00
dependabot[bot]
e157fc8a92 Bump sqlite3 from 5.0.4 to 5.0.5
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.4 to 5.0.5.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Changelog](https://github.com/TryGhost/node-sqlite3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.4...v5.0.5)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-25 14:37:35 +02:00
Christian Georgi
3b69a80975 Use original sqlite3 again 2022-04-20 09:00:05 +02:00
dependabot[bot]
b80ba7445d Bump semver from 7.3.6 to 7.3.7
Bumps [semver](https://github.com/npm/node-semver) from 7.3.6 to 7.3.7.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.3.6...v7.3.7)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-13 14:24:24 +02:00
Daniel
b733643f2a fixed accidential push 2022-04-09 12:38:24 +02:00
Daniel
bf317bc2c9 fixed 2022-04-09 11:54:12 +02:00
Daniel
bbebff4066 . 2022-04-08 19:52:26 +02:00
dependabot[bot]
eb75394044 Bump @sap/cds from 5.9.1 to 5.9.2
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.9.1 to 5.9.2.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-08 12:35:56 +02:00
dependabot[bot]
b8f65b687f Bump semver from 7.3.5 to 7.3.6
Bumps [semver](https://github.com/npm/node-semver) from 7.3.5 to 7.3.6.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.3.5...v7.3.6)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-08 12:33:12 +02:00
Daniel
c49c9691bd . 2022-04-06 15:29:46 +02:00
Daniel
7e4bc0985e Revert change to npm workspaces
This PARTIALLY reverts commit 912271338d.
2022-04-06 13:17:51 +02:00
Daniel Hutzel
912271338d Prepare for cds6 (#341) 2022-04-05 16:30:04 +02:00
Daniel Hutzel
bbf1194a09 Cosmetics to test outlines (#340) 2022-04-03 12:36:11 +02:00
dependabot[bot]
529c431518 Bump @sap/cds from 5.9.0 to 5.9.1
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.9.0 to 5.9.1.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 14:51:22 +02:00
Christian Georgi
3f85676edd Registry to handle packages with dashes
Like `@capire/data-viewer`, resulting in requests like
`GET /capire-data-viewer-0.1.0.tgz`
2022-04-01 11:49:28 +02:00
Daniel
959c07cee3 minor cosmetics for user service 2022-03-31 09:49:47 +02:00
Daniel
8cc09fde38 Fixed SELECT...columns { foo, bar } 2022-03-28 14:44:06 +02:00
Christian Georgi
bb55c432c9 Bookshop showing user info
Show current user name, locale and tenant (if available).
Useful in deployments w/ real auth and tenant contexts.
2022-03-28 14:36:32 +02:00
dependabot[bot]
5fc86d45ad Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-28 14:31:42 +02:00
Steffen Waldmann
e86d0dc3a2 Use cds.requires.auth.kind instead of cds.requires.auth.strategy
As recently discussed, this is the preferred version as opposed to the legacy `cds.requires.auth.strategy`.
2022-03-28 14:29:58 +02:00
dependabot[bot]
a661fb2d45 Bump @sap/cds from 5.8.4 to 5.9.0
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.8.4 to 5.9.0.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-28 14:26:16 +02:00
Christian Georgi
bfe0c831d6 Relative Fiori dataSource URLs
Since this is what 'real' Fiori apps use as well, to allow serving from
FLP.  CDS's fiori helper takes care that they work w/o FLP as well.
2022-03-28 13:31:58 +02:00
dependabot[bot]
cad615a662 Bump @sap/cds from 5.8.3 to 5.8.4
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.8.3 to 5.8.4.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-17 15:07:20 +01:00
Christian Georgi
7101c58920 Use new horizon theme of Fiori 2022-03-02 15:48:58 +01:00
dependabot[bot]
07dc1e88b3 Bump @sap/cds from 5.8.2 to 5.8.3
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.8.2 to 5.8.3.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-02 11:40:49 +01:00
dependabot[bot]
6f8d74dc9a Bump @sap/cds from 5.8.1 to 5.8.2
Bumps [@sap/cds](https://cap.cloud.sap/) from 5.8.1 to 5.8.2.

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 14:57:33 +01:00
Christian Georgi
ec57c5ea48 Enable dependency updates 2022-03-01 14:53:44 +01:00
Christian Georgi
c040a47279 Merge pull request #326 from MikhailGoncharov/followup-324
Follow-up #324: better version handling
2022-02-21 09:45:44 +01:00
Christian Georgi
30bfd70c49 Update package-lock 2022-02-21 09:42:12 +01:00
Mikhail Goncharov
6fb9581cf1 Update package.json 2022-02-17 10:39:18 +01:00
Mikhail Goncharov
e87d6cdfc5 Update devDependencies 2022-02-17 10:35:35 +01:00
Mikhail Goncharov
29ea2bc2da Better version handling 2022-02-17 10:09:43 +01:00
Heiko Witteborg
12574271ac Merge pull request #324 from MikhailGoncharov/service-api-read-no-keys-if-not-asked
Fixed eagerly read IDs (CAP !== OData)
2022-02-17 08:53:01 +01:00
Mikhail Goncharov
17b5cc1ad2 Merge branch 'main' into service-api-read-no-keys-if-not-asked 2022-02-15 08:56:08 +01:00
Daniel Hutzel
26ca7f54ad Specified files in package.json (#325) 2022-02-15 01:33:02 +01:00
dependabot[bot]
573e78253d Merge pull request #323 from SAP-samples/dependabot/npm_and_yarn/follow-redirects-1.14.8 2022-02-14 12:02:04 +00:00
Mikhail Goncharov
a7511ed677 Sync with cds runtime for next release 2022-02-14 12:06:22 +01:00
dependabot[bot]
f1289b436b Bump follow-redirects from 1.14.7 to 1.14.8
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-14 10:32:09 +00:00
Dr. David A. Kunz
984ea2133b Merge pull request #322 from SAP-samples/fix-change-to-vue3
Changes in Vue 3 and stable version
2022-02-09 10:23:27 +01:00
D065023
179d301acb Use stable URI 2022-02-09 10:12:41 +01:00
D065023
bbf20b1ca3 Change to Vue 3 2022-02-09 10:10:19 +01:00
Christian Georgi
f4f02f6fe5 Merge pull request #320 from SAP-samples/node16
Node16, dependency updates
2022-02-07 10:38:17 +01:00
Christian Georgi
68c2504fc4 package-lock update 2022-02-07 10:37:00 +01:00
Christian Georgi
51a8762738 Add Node.js 16 to CI 2022-02-07 10:31:08 +01:00
Christian Georgi
ff533754b4 Merge pull request #319 from SAP-samples/data-viewer
Data viewer Misc
2022-02-07 10:30:01 +01:00
Christian Georgi
f6b4d534fc Cosmetics 2022-02-07 10:11:38 +01:00
Christian Georgi
3c5107ba42 Require authentication 2022-02-07 10:02:31 +01:00
Christian Georgi
dbc561d2e3 Show errors in UI, migrate to Vue 3 2022-02-07 09:54:17 +01:00
Christian Georgi
f21488fd71 Merge pull request #318 from seVladimirs/patch-1
adds @sap/cds dependency to package.json
2022-02-07 09:53:17 +01:00
Christian Georgi
0bce8d9ce0 Fix glitch 2022-02-07 09:52:17 +01:00
Vladimirs Semikins
0f03ffd3eb adds @sap/cds dependency to package.json
To avoid further questions (e.g. https://answers.sap.com/questions/13577720/i-push-my-capnodejsto-btp-environment-but-always-c.html) this PR will add missing `@sap/cds` dependency.
2022-02-07 10:38:28 +02:00
Daniel Hutzel
bc03cae550 fix duplicate annotates (#317) 2022-02-04 16:19:54 +01:00
sjvans
8197559065 fix: app crashes if SELECT results in null (#298) 2022-02-03 18:04:10 +01:00
Daniel Hutzel
7accf1ae23 Using only bob as reviewer (#315) 2022-02-03 04:35:56 +01:00
Christian Georgi
2ffa1721b3 Merge pull request #314 from SAP-samples/data-browser
Data browser Cosmetics
2022-02-01 19:54:34 +01:00
Christian Georgi
37811381e7 Show errors in UI 2022-02-01 19:52:05 +01:00
Christian Georgi
74f9d5cc1e Sort columns 2022-02-01 19:30:30 +01:00
Christian Georgi
7c0769e059 Stick table headers 2022-02-01 18:42:36 +01:00
Christian Georgi
10dcd6aadd Merge pull request #313 from SAP-samples/browser
Data Browser: UI improvements, cosmetics
2022-01-31 20:24:38 +01:00
Christian Georgi
f8c23f4c54 Better layout; 2022-01-31 20:08:40 +01:00
Christian Georgi
ae6be30aa0 Data browser: cosmetics, reformat 2022-01-31 19:07:00 +01:00
Christian Georgi
e6baa95f74 Data browser: keep data row selection across refresh 2022-01-31 19:03:06 +01:00
Daniel Hutzel
aa28804a51 Basic bookshop w/o reuse deps (#312) 2022-01-29 23:49:30 +01:00
Christian Georgi
ea0773071e Update package-lock 2022-01-27 15:28:04 +01:00
Christian Georgi
1a71a6d28a Simple Data Viewer
- Generic CDS service to fetch data
- Simple Vue.js UI
2022-01-27 15:28:04 +01:00
Christian Georgi
50791bed80 Override tar with newer version 2022-01-17 13:20:10 +01:00
Christian Georgi
d58a71af7f Bump dependencies 2022-01-17 12:13:13 +01:00
Iwona Hahn
c7c979236e Merge pull request #306 from SAP-samples/iwonahahn-update
update year for license in README.md
2022-01-14 15:13:27 +01:00
Iwona Hahn
0675793c87 update year for license in README.md 2022-01-06 12:13:48 +01:00
Daniel
91bca922c4 use common file names for license 2022-01-06 10:24:46 +01:00
Daniel
30403d239a using latest versions 2022-01-06 10:24:46 +01:00
Daniel
589e282288 mocha has to run parallel 2022-01-06 10:24:46 +01:00
Daniel
a8345122ea fixed support for mocha 2022-01-04 13:55:37 +01:00
Daniel
788a101f41 Added .drawio graphics 2021-12-30 05:38:10 +01:00
Daniel
379ddc9fb0 Added npm start script → launching fiori 2021-12-14 10:13:53 +01:00
96 changed files with 4738 additions and 10775 deletions

View File

@@ -1,18 +1,19 @@
{ {
"extends": "eslint:recommended", "extends": [
"eslint:recommended",
"plugin:@sap/cds/recommended"
],
"env": { "env": {
"browser": true, "browser": true,
"es2022": true,
"node": true, "node": true,
"es6": true,
"jest": true, "jest": true,
"mocha": true "mocha": true
}, },
"parserOptions": {
"ecmaVersion": 2018
},
"globals": { "globals": {
"SELECT": true, "SELECT": true,
"INSERT": true, "INSERT": true,
"UPSERT": true,
"UPDATE": true, "UPDATE": true,
"DELETE": true, "DELETE": true,
"CREATE": true, "CREATE": true,
@@ -22,6 +23,7 @@
"rules": { "rules": {
"no-console": "off", "no-console": "off",
"require-atomic-updates": "off", "require-atomic-updates": "off",
"require-await":"warn" "require-await":"warn",
"no-unused-vars": ["warn", { "argsIgnorePattern": "_" }]
} }
} }

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: This channel is CLOSED.
about: Use SAP community instead
url: https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce

View File

@@ -1,10 +0,0 @@
---
name: This channel is CLOSED.
about: Use our community at https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
title: ''
labels: ''
assignees: ''
---
Please use our community on https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce

8
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
versioning-strategy: increase-if-necessary
schedule:
interval: daily

View File

@@ -16,7 +16,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [12.x, 14.x] node-version: [16.x, 14.x]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -24,5 +24,6 @@ jobs:
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: npm i -g npm@8
- run: npm ci - run: npm ci
- run: npm test - run: npm test

3
.gitignore vendored
View File

@@ -12,8 +12,11 @@ target/
*.mtar *.mtar
connection.properties connection.properties
default-env.json default-env.json
.cdsrc-private.json
packages/messageBox packages/messageBox
reviews/msg-box reviews/msg-box
reviews/db/test.db reviews/db/test.db
*.openapi3.json *.openapi3.json
*.sqlite
*.db

View File

@@ -16,7 +16,8 @@ app.use('/-/:tarball', (req,res,next) => {
console.debug ('GET', req.params) console.debug ('GET', req.params)
try { try {
const { tarball } = req.params const { tarball } = req.params
const [, pkg ] = /^\w+-(\w+)/.exec(tarball) const pkgFull = tarball.substring(0, tarball.lastIndexOf('-'))
const [, pkg ] = /^\w+-(.+)/.exec(pkgFull)
fs.lstat(tarball,(err => { fs.lstat(tarball,(err => {
if (err) console.debug (`npm pack ../${pkg}`) if (err) console.debug (`npm pack ../${pkg}`)
if (err) exec(`npm pack ../${pkg}`,{cwd},next) if (err) exec(`npm pack ../${pkg}`,{cwd},next)
@@ -31,7 +32,7 @@ app.use('/-/:tarball', (req,res,next) => {
app.use('/-', express.static(__dirname)) app.use('/-', express.static(__dirname))
app.get('/*', (req,res)=>{ app.get('/*', (req,res)=>{
const urlRegex = /^\/(@\w+)\/(\w+)/ const urlRegex = /^\/(@[\w-]+)\/(.+)/
const url = decodeURIComponent(req.url) const url = decodeURIComponent(req.url)
console.debug ('GET',url) console.debug ('GET',url)
try { try {

View File

@@ -104,7 +104,7 @@
}, },
{ {
"file": "fiori/app/services.cds", "file": "fiori/app/services.cds",
"description": "### Annotations for SAP Fiori Elements\n\n- [Adds an SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookstore, thereby introducing to:\n- [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files\n- Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)\n- Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)\n- Serving SAP Fiori apps locally\n", "description": "### Annotations for SAP Fiori Elements\n\nAdds an SAP Fiori elements application to bookstore, thereby introducing:\n- OData Annotations in `.cds` files\n- Support for Fiori Draft\n- Support for Value Helps\n- Serving SAP Fiori apps locally\n\nSee the [Serving Fiori UIs](https://cap.cloud.sap/docs/advanced/fiori) documentation for more information.",
"line": 1, "line": 1,
"selection": { "selection": {
"start": { "start": {
@@ -136,4 +136,4 @@
], ],
"isPrimary": true, "isPrimary": true,
"description": "Overview of CAP Samples for Node.js" "description": "Overview of CAP Samples for Node.js"
} }

13
.vscode/settings.json vendored
View File

@@ -13,5 +13,14 @@
"**/cds/lib/req/cls.js", "**/cds/lib/req/cls.js",
"**/odata-v4/okra/**" "**/odata-v4/okra/**"
] ]
} },
} "mochaExplorer.parallel": true,
"eslint.validate": [
"cds",
"csn",
"csv",
"csv (semicolon)",
"tsv",
"tab"
]
}

73
LICENSES/Apache-2.0.txt Normal file
View File

@@ -0,0 +1,73 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -7,13 +7,14 @@ Find here a collection of samples for the [SAP Cloud Application Programming Mod
### Preliminaries ### Preliminaries
1. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally: 1. Ensure you have the latest LTS version of Node.js installed (see [Getting Started](https://cap.cloud.sap/docs/get-started/))
2. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally:
```sh ```sh
npm i -g @sap/cds-dk npm i -g @sap/cds-dk
``` ```
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/tools#vscode) 3. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/tools#vscode)
### Download ### Download
@@ -83,4 +84,4 @@ In case you've a question, find a bug, or otherwise need support, use our [commu
## License ## License
Copyright (c) 2021 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.txt) file. Copyright (c) 2022 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.

View File

@@ -1,2 +0,0 @@
// Incorporate pre-build extensions from...
using from '@capire/common';

View File

@@ -3,14 +3,15 @@ const $ = sel => document.querySelector(sel)
const GET = (url) => axios.get('/browse'+url) const GET = (url) => axios.get('/browse'+url)
const POST = (cmd,data) => axios.post('/browse'+cmd,data) const POST = (cmd,data) => axios.post('/browse'+cmd,data)
const books = new Vue ({ const books = Vue.createApp ({
el:'#app', data() {
return {
data: {
list: [], list: [],
book: undefined, book: undefined,
order: { quantity:1, succeeded:'', failed:'' } order: { quantity:1, succeeded:'', failed:'' },
user: undefined
}
}, },
methods: { methods: {
@@ -37,12 +38,52 @@ const books = new Vue ({
book.stock = res.data.stock book.stock = res.data.stock
books.order = { quantity, succeeded: `Successfully ordered ${quantity} item(s).` } books.order = { quantity, succeeded: `Successfully ordered ${quantity} item(s).` }
} catch (e) { } catch (e) {
books.order = { quantity, failed: e.response.data.error.message } books.order = { quantity, failed: e.response.data.error ? e.response.data.error.message : e.response.data }
} }
} },
async login() {
try {
const { data:user } = await axios.post('/user/login',{})
if (user.id !== 'anonymous') books.user = user
} catch (err) { books.user = { id: err.message } }
},
async getUserInfo() {
try {
const { data:user } = await axios.get('/user/me')
if (user.id !== 'anonymous') books.user = user
} catch (err) { books.user = { id: err.message } }
},
} }
}).mount('#app')
books.getUserInfo()
books.fetch() // initially fill list of books
document.addEventListener('keydown', (event) => {
// hide user info on request
if (event.key === 'u') books.user = undefined
}) })
// initially fill list of books axios.interceptors.request.use(csrfToken)
books.fetch() function csrfToken (request) {
if (request.method === 'head' || request.method === 'get') return request
if ('csrfToken' in document) {
request.headers['x-csrf-token'] = document.csrfToken
return request
}
return fetchToken().then(token => {
document.csrfToken = token
request.headers['x-csrf-token'] = document.csrfToken
return request
}).catch(_ => {
document.csrfToken = null // set mark to not try again
return request
})
function fetchToken() {
return axios.get('/', { headers: { 'x-csrf-token': 'fetch' } })
.then(res => res.headers['x-csrf-token'])
}
}

View File

@@ -5,19 +5,32 @@
<title> Capire Books </title> <title> Capire Books </title>
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css"> <link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> <script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
<style> <style>
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; } .hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
.rating-stars { color:teal } .rating-stars { color:teal }
.succeeded { color:teal } .succeeded { color:teal }
.failed { color:red } .failed { color:red }
.user {text-align: end; color: grey;}
</style> </style>
</head> </head>
<body class="small-container", style="margin-top: 70px;"> <body class="small-container", style="margin-top: 70px;">
<div id='app'> <div id='app'>
<h1> {{ document.title }} </h1> <form class="user" @submit.prevent="login">
<div v-if="user">
<div v-if="user.tenant">Tenant: {{ user.tenant }}</div>
<div> User: {{ user.id }}</div>
<div>Locale: {{ user.locale }}</div>
</div>
<div v-else>
<input type="submit" value="Login" class="muted-button">
<!-- <a href="/user/login()">Login</a> -->
</div>
</form>
<h1> Capire Books </h1>
<input type="text" placeholder="Search..." @input="search"> <input type="text" placeholder="Search..." @input="search">

View File

@@ -2,5 +2,5 @@ ID;title;descr;author_ID;stock;price;currency_code;genre_ID
201;Wuthering Heights;"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.";101;12;11.11;GBP;11 201;Wuthering Heights;"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.";101;12;11.11;GBP;11
207;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";107;11;12.34;GBP;11 207;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";107;11;12.34;GBP;11
251;The Raven;"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";150;333;13.13;USD;16 251;The Raven;"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";150;333;13.13;USD;16
252;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";150;555;14;USD;16 252;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";150;555;14;USD;15
271;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;170;22;150;JPY;13 271;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;170;22;150;JPY;13
1 ID title descr author_ID stock price currency_code genre_ID
2 201 Wuthering Heights Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym "Ellis Bell". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850. 101 12 11.11 GBP 11
3 207 Jane Eyre Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name "Currer Bell", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism. 107 11 12.34 GBP 11
4 251 The Raven "The Raven" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word "Nevermore". The poem makes use of folk, mythological, religious, and classical references. 150 333 13.13 USD 16
5 252 Eleonora "Eleonora" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively "happy" ending. 150 555 14 USD 16 15
6 271 Catweazle Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts. 170 22 150 JPY 13

18
bookshop/db/init.js Normal file
View File

@@ -0,0 +1,18 @@
/**
* In order to keep basic bookshop sample as simple as possible, we don't add
* reuse dependencies. This db/init.js ensures we still have a minimum set of
* currencies, if not obtained through @capire/common.
*/
module.exports = async ()=>{
await UPSERT.into ('sap.common.Currencies') .columns (
'code','symbol','name'
) .rows (
[ 'EUR','€','Euro' ],
[ 'USD','$','US Dollar' ],
[ 'GBP','£','British Pound' ],
[ 'ILS','₪','Shekel' ],
[ 'JPY','¥','Yen' ],
)
}

View File

@@ -1,7 +1,7 @@
using { Currency, managed, sap, extensible } from '@sap/cds/common'; using { Currency, managed, sap } from '@sap/cds/common';
namespace sap.capire.bookshop; namespace sap.capire.bookshop;
entity Books : managed, extensible { entity Books : managed {
key ID : Integer; key ID : Integer;
title : localized String(111); title : localized String(111);
descr : localized String(1111); descr : localized String(1111);
@@ -13,7 +13,7 @@ entity Books : managed, extensible {
image : LargeBinary @Core.MediaType : 'image/png'; image : LargeBinary @Core.MediaType : 'image/png';
} }
entity Authors : managed, extensible { entity Authors : managed {
key ID : Integer; key ID : Integer;
name : String(111); name : String(111);
dateOfBirth : Date; dateOfBirth : Date;

View File

@@ -2,3 +2,4 @@ namespace sap.capire.bookshop; //> important for reflection
using from './db/schema'; using from './db/schema';
using from './srv/cat-service'; using from './srv/cat-service';
using from './srv/admin-service'; using from './srv/admin-service';
using from './srv/user-service';

View File

@@ -2,11 +2,17 @@
"name": "@capire/bookshop", "name": "@capire/bookshop",
"version": "1.0.0", "version": "1.0.0",
"description": "A simple self-contained bookshop service.", "description": "A simple self-contained bookshop service.",
"files": [
"app",
"srv",
"db",
"index.cds",
"index.js"
],
"dependencies": { "dependencies": {
"@capire/common": "*", "@sap/cds": ">=5.9",
"@sap/cds": "^5.0.4",
"express": "^4.17.1", "express": "^4.17.1",
"passport": "0.4.1" "passport": ">=0.4.1"
}, },
"scripts": { "scripts": {
"genres": "cds serve test/genres.cds", "genres": "cds serve test/genres.cds",
@@ -15,9 +21,7 @@
}, },
"cds": { "cds": {
"requires": { "requires": {
"db": { "db": "sql"
"kind": "sql"
}
} }
} }
} }

View File

@@ -1,9 +1,10 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
module.exports = cds.service.impl (function(){ module.exports = class AdminService extends cds.ApplicationService { init(){
this.before ('NEW','Authors', genid) this.before ('NEW','Authors', genid)
this.before ('NEW','Books', genid) this.before ('NEW','Books', genid)
}) return super.init()
}}
/** Generate primary keys for target entity in request */ /** Generate primary keys for target entity in request */
async function genid (req) { async function genid (req) {

View File

@@ -3,12 +3,15 @@ const cds = require('@sap/cds')
class CatalogService extends cds.ApplicationService { init(){ class CatalogService extends cds.ApplicationService { init(){
const { Books } = cds.entities ('sap.capire.bookshop') const { Books } = cds.entities ('sap.capire.bookshop')
const { ListOfBooks } = this.entities
// Reduce stock of ordered books if available stock suffices // Reduce stock of ordered books if available stock suffices
this.on ('submitOrder', async req => { this.on ('submitOrder', async req => {
const {book,quantity} = req.data const {book,quantity} = req.data
if (quantity < 1) return req.reject (400,`quantity has to be 1 or more`) if (quantity < 1) return req.reject (400,`quantity has to be 1 or more`)
let {stock} = await SELECT `stock` .from (Books,book) let b = await SELECT `stock` .from (Books,book)
if (!b) return req.error (404,`Book #${book} doesn't exist`)
let {stock} = b
if (quantity > stock) return req.reject (409,`${quantity} exceeds stock for book #${book}`) if (quantity > stock) return req.reject (409,`${quantity} exceeds stock for book #${book}`)
await UPDATE (Books,book) .with ({ stock: stock -= quantity }) await UPDATE (Books,book) .with ({ stock: stock -= quantity })
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id }) await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
@@ -16,7 +19,7 @@ class CatalogService extends cds.ApplicationService { init(){
}) })
// Add some discount for overstocked books // Add some discount for overstocked books
this.after ('READ','ListOfBooks', each => { this.after ('READ', ListOfBooks, each => {
if (each.stock > 111) each.title += ` -- 11% discount!` if (each.stock > 111) each.title += ` -- 11% discount!`
}) })

View File

@@ -0,0 +1,15 @@
/**
* Exposes user information
*/
service UserService {
/**
* The current user
*/
@odata.singleton entity me @cds.persistence.skip {
id : String; // user id
locale : String;
tenant : String;
}
action login() returns me;
}

View File

@@ -0,0 +1,9 @@
const cds = require('@sap/cds')
module.exports = class UserService extends cds.Service { init(){
this.on('READ', 'me', ({ tenant, user, locale }) => ({ id: user.id, locale, tenant }))
this.on('login', (req) => {
if (req.user._is_anonymous)
req._.res.set('WWW-Authenticate','Basic realm="Users"').sendStatus(401)
else return this.read('me')
})
}}

View File

@@ -16,9 +16,9 @@ GET {{server}}/browse/$metadata
### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------
# Browse Books as any user # Browse Books as any user
GET {{server}}/browse/Books? GET {{server}}/browse/ListOfBooks?
# &$select=title,stock # &$select=title,stock
# &$expand=currency &$expand=genre
# &sap-language=de # &sap-language=de
{{me}} {{me}}

View File

@@ -6,7 +6,8 @@
"@capire/reviews": "*", "@capire/reviews": "*",
"@capire/orders": "*", "@capire/orders": "*",
"@capire/common": "*", "@capire/common": "*",
"@sap/cds": "^5", "@capire/data-viewer": "*",
"@sap/cds": ">=5",
"express": "^4.17.1" "express": "^4.17.1"
}, },
"cds": { "cds": {
@@ -25,7 +26,8 @@
"[production]": { "kind": "enterprise-messaging" } "[production]": { "kind": "enterprise-messaging" }
}, },
"db": { "db": {
"kind": "sql" "kind": "sql",
"schema_evolution": "auto"
} }
}, },
"log": { "service": true } "log": { "service": true }

View File

@@ -8,6 +8,7 @@ cds.once('bootstrap',(app)=>{
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue') app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
app.serve ('/reviews') .from ('@capire/reviews','app/vue') app.serve ('/reviews') .from ('@capire/reviews','app/vue')
app.serve ('/orders') .from('@capire/orders','app/orders') app.serve ('/orders') .from('@capire/orders','app/orders')
app.serve ('/data') .from('@capire/data-viewer','app/viewer')
}) })
// Add Swagger UI // Add Swagger UI
@@ -18,4 +19,4 @@ module.exports = cds.server
// For didactic reasons in capire // For didactic reasons in capire
const { ReviewsService, OrdersService } = cds.requires const { ReviewsService, OrdersService } = cds.requires
if (!ReviewsService.credentials && !OrdersService.credentials) cds.requires.messaging = false if (!ReviewsService?.credentials && !OrdersService?.credentials) cds.requires.messaging = false

View File

@@ -12,7 +12,11 @@ using { sap.capire.bookshop.Books } from '@capire/bookshop';
using { ReviewsService.Reviews } from '@capire/reviews'; using { ReviewsService.Reviews } from '@capire/reviews';
extend Books with { extend Books with {
reviews : Composition of many Reviews on reviews.subject = $self.ID; reviews : Composition of many Reviews on reviews.subject = $self.ID;
@Common.Label : '{i18n>Rating}'
rating : Decimal; rating : Decimal;
@Common.Label : '{i18n>NumberOfReviews}'
numberOfReviews : Integer; numberOfReviews : Integer;
} }
@@ -30,3 +34,9 @@ extend Orders with {
// Add orders fiori app (in case of embedded orders service) // Add orders fiori app (in case of embedded orders service)
using from '@capire/orders/app/fiori'; using from '@capire/orders/app/fiori';
// Add data browser
using from '@capire/data-viewer';
// Incorporate pre-build extensions from...
using from '@capire/common';

View File

@@ -20,7 +20,7 @@ extend sap.common.Currencies with {
* annotate sap.common.Countries with @cds.persistence.skip:false; * annotate sap.common.Countries with @cds.persistence.skip:false;
*/ */
context sap.common_countries { context sap.common.countries {
extend sap.common.Countries { extend sap.common.Countries {
regions : Composition of many Regions on regions._parent = $self.code; regions : Composition of many Regions on regions._parent = $self.code;

View File

@@ -0,0 +1,119 @@
/* global Vue axios */ //> from vue.html
const GET = (url) => axios.get('/-data'+url)
const storageGet = (key, def) => localStorage.getItem('data-viewer:'+key) || def
const storageSet = (key, val) => localStorage.setItem('data-viewer:'+key, val)
const columnKeysFirst = (c1, c2) => {
if (c1.isKey && !c2.isKey) return -1
if (!c1.isKey && c2.isKey) return 1
if (c1.isKey && c2.isKey) return c1.name.localeCompare(c2.name)
return 0 // retain natural order of normal columns
}
const vue = Vue.createApp ({
data() { return {
error: undefined,
dataSource: storageGet('data-source', 'db'),
skip: storageGet('skip', 0),
top: storageGet('top', 20),
entity: storageGet('entity') ? JSON.parse(storageGet('entity')) : undefined,
entities: [],
columns: [],
data: [],
rowDetails: {},
rowKey: storageGet('rowKey')
}},
watch: {
dataSource: (v) => { storageSet('data-source', v); vue.fetchEntities() },
skip: (v) => { storageSet('skip', v); if (vue.entity) vue.fetchData() },
top: (v) => { storageSet('top', v); if (vue.entity) vue.fetchData() },
},
methods: {
async fetchEntities () {
let url = `/Entities`
if (vue.dataSource === 'db') url += `?dataSource=db`
const {data} = await GET(url)
vue.entities = data.value
vue.entities.forEach(entity => entity.columns.sort(columnKeysFirst))
const entity = vue.entity && vue.entities.find(e => e.name === vue.entity.name)
if (entity) { // restore selection from previous fetch
vue.columns = entity.columns
await vue.fetchData(entity)
} else {
vue.entity = undefined
vue.columns = []
vue.data = []
vue.rowDetails = {}
}
},
async inspectEntity (eve) {
const entity = vue.entity = vue.entities [eve.currentTarget.rowIndex-1]
storageSet('entity', JSON.stringify(entity))
vue.columns = vue.entities.find(e => e.name === entity.name).columns
return await this.fetchData()
},
async fetchData () {
let url = `/Data?entity=${vue.entity.name}&$skip=${vue.skip}&$top=${vue.top}`
if (vue.dataSource === 'db') url += `&dataSource=db`
try {
const {data} = await GET(url)
// sort data along column order
const columnIndexes = {}
vue.columns.forEach((col, i) => columnIndexes[col.name] = i)
vue.data = data.value.map(d => d.record
.sort((r1, r2) => columnIndexes[r1.column] - columnIndexes[r2.column])
.map(r => r.data)
)
const row = vue.data.find(data => vue._makeRowKey(data) === vue.rowKey)
if (row) vue._setRowDetails(row)
else vue.rowDetails = {}
vue.error = undefined
} catch (err) {
vue.data = []
vue.rowDetails = {}
if (err.response?.data?.error) {
vue.error = err.response.data.error
} else {
vue.error = { code:err.code, message:err.message }
}
}
},
inspectRow (eve) {
vue.rowDetails = {}
const selectedRow = eve.currentTarget.rowIndex-1
vue.rowKey = vue._makeRowKey(vue.data[selectedRow])
storageSet('rowKey', vue.rowKey)
vue._setRowDetails(vue.data[selectedRow])
},
_setRowDetails(row) {
vue.rowDetails = {}
row.forEach((line, colIndex) => {
vue.rowDetails[vue.columns[colIndex].name] = line
})
},
_makeRowKey(row) {
// to identify a row, build a key string out of all key columns' values
return row
.filter((_, colIndex) => vue.columns[colIndex] && vue.columns[colIndex].isKey)
.reduce(((prev, next) => prev += next), '')
},
isActiveRow(row) {
return vue._makeRowKey(row) === vue.rowKey
}
}
})
.mount('#app')
vue.fetchEntities()

View File

@@ -0,0 +1,95 @@
<!DOCTYPE html>
<html>
<head>
<title>Data Browser</title>
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
<script src="app.js" defer></script>
<style>
th { position: sticky; top:0; z-index: 2; background-color: white; }
.noscroll { overflow: hidden; }
.hovering tr:hover td { background: #ebeefc; cursor: pointer; }
.highlight { background: #ebeefc !important; }
.rating-stars { color:teal }
.succeeded { color:teal }
.failed { color:red }
.condensed { max-width: 100px; text-overflow: ellipsis; white-space: nowrap; }
.key { font-weight: bold }
.not-key { font-weight: lighter;}
.with-sidebar { display: flex; flex-wrap: wrap; gap: 1rem; }
.sidebar { flex-basis: 20rem; flex-grow: 1; }
.sidebar-main { height: 100vh; overflow-y: scroll; }
.not-sidebar { flex-basis: 0; flex-grow: 999; min-inline-size: 50%; align-items: stretch;}
.not-sidebar-main { max-height: 40vh; overflow-y: scroll; }
.not-sidebar-sub { max-height: 40vh; overflow-y: scroll; }
.horizontal label { display: inline; }
.horizontal input { width: initial; display: inline; }
.error { color: red; }
</style>
</head>
<body class="noscroll">
<div id='app' class="full-container">
<h1>Data Browser &ndash; {{ entity ? entity.name : '' }}</h1>
<div class="with-sidebar">
<div class="sidebar">
<div class="horizontal" style="padding: 0.75rem 0;">
<label>Datasource:</label>
<input type="radio" id="dataSource-db" value="db" v-model="dataSource">
<label for="dataSource-db">Database</label>
<input type="radio" id="dataSource-srv" value="service" v-model="dataSource">
<label for="dataSource-srv">Service</label>
</div>
<div class="sidebar-main">
<table id='entities' class="hovering">
<thead>
<th>Entity Name</th>
</thead>
<tr v-for="e in entities" :key="e.name" @click="inspectEntity" :class="{'highlight': (entity && e.name === entity.name)}">
<td>{{ e.name }}</td>
</tr>
</table>
</div>
</div>
<div class="not-sidebar">
<div class="horizontal">
<label for="skip">Skip:</label>
<input id="skip" v-model.lazy="skip" title="No. of entries to skip" type="number" min="0">
<label for="top">Top:</label>
<input id="top" v-model.lazy="top" title="No. of entries to read" type="number" min="0">
</div>
<div v-if="data" class="not-sidebar-main">
<table id='data' class="hovering striped-table condensed">
<thead>
<th v-for="col in columns" :title="col.type" :class="[col.isKey ? 'key' : 'not-key']">{{ col.name }} </th>
</thead>
<tr v-for="row in data" @click="inspectRow" :class="{'highlight': isActiveRow(row)}">
<td v-for="d in row" :title="d">{{ d }}</td>
</tr>
</table>
</div>
<div v-if="error" class="not-sidebar-main error">
Error: {{ error.code ? error.code + ' &ndash; ' + error.message : error.message }}
</div>
<p></p>
<div v-if="rowDetails" class="not-sidebar-sub">
<table id='rowDetails'>
<tr v-for="(key, value) in rowDetails" >
<td class="key">{{ value }}</td>
<td>{{ key }}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</body>
</html>

1
data-viewer/index.cds Normal file
View File

@@ -0,0 +1 @@
using from './srv/data-service';

13
data-viewer/package.json Normal file
View File

@@ -0,0 +1,13 @@
{
"name": "@capire/data-viewer",
"version": "0.1.0",
"description": "A generic browser for data",
"dependencies": {
"@sap/cds": ">=5.0.4"
},
"files": [
"app",
"srv",
"index.cds"
]
}

View File

@@ -0,0 +1,29 @@
/**
* Exposes data + entity metadata
*/
@requires:'authenticated-user'
@odata service DataService @( path:'-data' ) {
/**
* Metadata like name and columns/elements
*/
entity Entities @cds.persistence.skip {
key name : String;
columns: Composition of many {
name : String;
type : String;
isKey: Boolean;
}
}
/**
* The actual data, organized by column name
*/
entity Data @cds.persistence.skip {
record : array of {
column : String;
data : String;
}
}
}

View File

@@ -0,0 +1,58 @@
const cds = require('@sap/cds')
const log = cds.log('data')
class DataService extends cds.ApplicationService { init(){
this.on ('READ', 'Entities', req => {
const { dataSource } = req.req.query
const srvPrefixes = cds.db.model.all('service').map(srv => srv.name+'.')
const dataSourceFilter = dataSource === 'db'
? e => e['@cds.persistence.skip'] !== true // for DB, excl. entities w/o persistence
: e => !!srvPrefixes.find(srvName => e.name.startsWith(srvName)) // only entities reachable from a service
return cds.db.model.all('entity')
.filter (e => req.data && req.data.name ? e.name === req.data.name : true) // honor name filter from request, if any
.filter (e => !e.name.startsWith('DRAFT.')) // exclude synthetic stuff
.filter (e => !e.name.startsWith('DataService.')) // exclude this service
.filter (dataSourceFilter)
.sort((e1, e2) => e1.name.localeCompare(e2.name))
.map(e => {
const columns = Object.entries(e.elements)
.filter(([_, el]) => !(el instanceof cds.Association)) // exclude assocs+compositions
.map(([name, el]) => { return { name, type: el.type, isKey:!!el.key }})
return { name: e.name, columns }
})
})
this.on ('READ', 'Data', async req => {
const { entity: entityName, dataSource: dataSourceName } = req.req.query
if (!entityName) return req.reject(400, `Must provide 'entity' query`)
const entity = cds.db.model.definitions[entityName]
if (!entity) return req.reject(404, 'No such entity: ' + entityName)
const query = SELECT.from(entity)
query.SELECT.limit = req.query.SELECT.limit // forward $skip / $top
const dataSource = findDataSource(dataSourceName, entityName)
const res = await dataSource.run(query)
return res.map((line) => {
const record = Object.entries(line).map(([column, data]) => {return {column, data}})
return { record }
})
})
return super.init()
}}
module.exports = { DataService }
function findDataSource(dataSourceName, entityName) {
for (let srv of Object.values(cds.services)) { // all connected services
if (!srv.name) continue // FIXME intermediate/pending in cds.services ?
if (dataSourceName === srv.name || entityName.startsWith(srv.name+'.')) {
log._debug && log.debug(`using ${srv.name} as data source`)
return srv
}
}
return cds.services.db // fallback
}

261
etc/bookshop.drawio.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 67 KiB

172
etc/dark.drawio.svg Normal file
View File

@@ -0,0 +1,172 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="347px" height="279px" viewBox="-0.5 -0.5 347 279" content="&lt;mxfile&gt;&lt;diagram id=&quot;QQJxv4aCTC7ZgE7HHOvM&quot; name=&quot;Page-1&quot;&gt;7Vlbb+I4FP41vCLfL4/T7szuy0oj9WGfczEkasCRMYXur98TEufeqpoBCtUGCezv+Hq+z/bBWdDHzfFPF5XZ3zY1xYKg9LigfywI0QLBdwW81oDUrAbWLk9rCHXAU/6vqUEc0H2eml2D1ZC3tvB5OQQTu92axA+wyDl7GBZb2SIdAGW0NoNhVMBTEhVmUuyfPPVZjSoiO/wvk6+z0DMWurbEUfK8dna/bfpbEIq/VZ/avIlCW02/uyxK7aEH0e8L+uis9XVqc3w0ReXaodt+vGFtx+3M1n+kAqkrvETFvpn6gqEkKnNnqkpERJtyQR+28a76gXwBzT7EDlJr30cCEFv7vMtsGQzQStwVPk3ZvwY3w+zLKpmZY7S2WyhSGpdvjDeuQ38GCJzzcMhyb57KKKmqHUB7gGV+U0AOQ3KVH02QU5XfPRufVB5HJ2NRPNrCulPndLUyIkmqQt7ZZ9OzpFLHCLWWoADSTuHFOG+Ob7oct0TC+jAWBu9eoUhTgcimSrM2KG9UdeiUpht5ZD2RqQaLGm2v25Y7fiHRUDxPNz033avcuvwOuFaJmec6Vpzxq3FNrsg1OzfXzrzkBnbWm2c75UalbI5tRWIqxKXYppwPVza6Htv83GwndrMB4m6e7BWvPhVut76H18+cCMTpuZQIsFIDEbArikCcWwTWpcbdw4qPjFrN7u8iUSZeXY5s+WkrXk7JHnNitum3KhqGXFJEu12eDF08cOnUQVC98b882SPnB/mRq3+cntaxJg0x9ltuhbHavUvMYAeDXtbG96LTqfN7zuUzzg2YM0Xk85fhIOY83vTw0+YwvI5bCOuH5AayQxv16Jtq/SB70pIYtURHLdWTnrR0kkA78Q+pQn0xVYipKuhnqoJIueSKtk8Y4Gtn7lknRH9UMkTjJcOifSQbdqPEsmcVgl5KT/qL6Yncl54ofZ/oM+mJkvdlez49hRueLyModl+CYtcRFLueoPBUUL8X9O72ZVnk/8e978a9o2MvLOsrxL145s5yTOETNAKCRLfPoMEpN7P7khaSRpf7m4rQkEFBrsfgzDXkXZ8BQZL9QwC/4f+bOAXGYUXYvS8cVoRRXOAUmLnt/HKSEp+pKAxUS6Q4QVhqKtv7h7B/aAZKkIghypVk7FfDCqSWIComtSJIgIrpsBfFQNZEcy0YJpxfLqqYuU+9bz2F16i3EqdiTWELUlWoqDUS46tzSpYQm3KOGYJgQ5NflZOADQhLySVCmrJxJ3oJOyFjimuFKVJnUhNkuze4dfHuLTn9/h8=&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(26, 26, 26);">
<defs/>
<g>
<path d="M 192 148 L 242 148 L 262 188 L 242 228 L 192 228 L 172 188 Z" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 188px; margin-left: 173px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
bookshop
</b>
</div>
</div>
</div>
</foreignObject>
<text x="217" y="192" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 192 48 L 242 48 L 262 88 L 242 128 L 192 128 L 172 88 Z" fill="#f8cecc" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 88px; margin-left: 173px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
fiori
</b>
</div>
</div>
</div>
</foreignObject>
<text x="217" y="92" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 276 98 L 326 98 L 346 138 L 326 178 L 276 178 L 256 138 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 138px; margin-left: 257px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
reviews
</b>
</div>
</div>
</div>
</foreignObject>
<text x="301" y="142" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 109 198 L 159 198 L 179 238 L 159 278 L 109 278 L 89 238 Z" fill="#f5f5f5" stroke="#666666" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 238px; margin-left: 90px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
common
</b>
</div>
</div>
</div>
</foreignObject>
<text x="134" y="242" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 108 98 L 158 98 L 178 138 L 158 178 L 108 178 L 88 138 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 138px; margin-left: 89px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
orders
</b>
</div>
</div>
</div>
</foreignObject>
<text x="133" y="142" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 168.58 217.17 L 174.72 213.47" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 180.5 209.99 L 175.11 218.49 L 174.72 213.47 L 170.47 210.78 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 167.68 117.36 L 174.6 113.24" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 180.4 109.79 L 174.97 118.26 L 174.6 113.24 L 170.36 110.52 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 217 148 L 217 136.99" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 217 130.24 L 221.5 139.24 L 217 136.99 L 212.5 139.24 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 266.32 117.36 L 259.4 113.24" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 253.6 109.79 L 263.64 110.52 L 259.4 113.24 L 259.03 118.26 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 107 1 L 157 1 L 177 41 L 157 81 L 107 81 L 87 41 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 41px; margin-left: 88px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
suppliers
</b>
</div>
</div>
</div>
</foreignObject>
<text x="132" y="45" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 21 53 L 71 53 L 91 93 L 71 133 L 21 133 L 1 93 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 93px; margin-left: 2px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<b>
S/4
</b>
</div>
</div>
</div>
</foreignObject>
<text x="46" y="97" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
S/4
</text>
</switch>
</g>
<path d="M 80.55 72.11 L 89.76 66.54" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 95.53 63.05 L 90.16 71.56 L 89.76 66.54 L 85.5 63.86 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 81.75 111.49 L 89.27 115.38" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 95.26 118.48 L 85.2 118.34 L 89.27 115.38 L 89.33 110.35 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 167.25 60.49 L 173.88 64.16" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 179.79 67.42 L 169.74 67.01 L 173.88 64.16 L 174.09 59.13 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Viewer does not support full SVG 1.1
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

281
etc/incidents.drawio.svg Normal file
View File

@@ -0,0 +1,281 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="569px" height="428px" viewBox="-0.5 -0.5 569 428" content="&lt;mxfile&gt;&lt;diagram id=&quot;QQJxv4aCTC7ZgE7HHOvM&quot; name=&quot;Page-1&quot;&gt;7Vpdd9o4EP01PJZjWf7SY8jHds9pd3tKt30WtjDayBZHFknor18ployNDCXEELbtyUPsQRpLvndmrgZG8Lp4+kPg5eIjzwgb+V72NII3I98HMPLUP21Z15YYBbUhFzQzgzaGKf1OjNHMy1c0I1VnoOScSbrsGlNeliSVHRsWgj92h8056z51iXPzRG9jmKaYEWfYN5rJRW1N/Hhjf09ovrBPBhGqPymwHWwcVwuc8ceWCd6O4LXgXNZXxdM1Yfrl2fdSz7vb8WmzMEFKecgEv57wgNnK7G2qBis0vPdXf12ZNcq13bjgqzIjeq43gpPHBZVkusSp/vRRQa1sC1kwdQfUZcoLmpqh1T2R6cLc/Evz/NljoG7mvJR3uKBMc+E9YQ9E0hTrKVLw++b9qjczwYzmpboW9audPBChx7IrY5dcr6BSC6Jlru7Dxss1Z1w8bwEmvv7TK+WCflcPx8yusZ742QAXm8VNzfaBuW/5Cm70n7Kbt6jWQ552IgEafFVgEF4QKdZqiJnwDtioMEEBLEceNxQDKKxtiza9LIrY0DpvnG+QVxcG/H4iQIcIk1VFS1JVI72OiOkXPhPqKtdXn7CQJRF7CALORJAWHLMkDEKvlzpzylhr5DxJSZoOBRxIxmEXuhC50Fk429CF3uuRCxzkGrgsWn+WqUqYaju+9zEvZAtR5bwZ5aJ8+yRJWVFeqhlXy+WFpgNG5odkA7Q3G5wwsg+IaxjELjlggF7PjtBhhyVDdVnBG6UJmc0PCd4Mk2Q+UPD6YdwFB/lu5IKepDtE5EY9kbsFCSmzKy1Y1F1GccHL7MuCKnZP1Ad3lFkw1J1RSSDqwnRSACqpyoBd3+3nApdrazWL8+y9WR5qYCOZ1Vi7QFMvgq9EakeZl6Oc5cSC0I9tGzsbfIIwLOlD95F94Bl3nzh9zpc2hoMOTQIQ2pRvfdRrNdPaimvLU5Owd7uqt+i4eqZTs82DGBY7DLteVVJ5EBcW/VmMZt5hpXtOoqFKN+pC0Rv8pyrbiQPNdJWmSm/d4VTyvfj8/7V3j9R25Phbam8/ilwmoGjcUwh8bwD1jRwyfOPifs519uuV3yqAtSq7pBC+GPUNPXi+MLYtiRZ0t8WS8TXpR+4LLUi1IET+Bq8fvKQn8k4GHniRApsxnt6rnWdYIWiB2ggxryvE/GOF2NG18KWSCrqKKj6fouoK7xDEx+qpHzgaTk1ZCdqiy0dVsHFOLkxNXcZZCnrADeVTnaWA28DaF8qXephqHZMOO1wdF/nhW0a+H8MuTyA8MvQB2u9owNB3u2xTIh7oDn2m1duldUcv4oh1Xm3mNr9+pfKO3CCvaXyWKAfbwg5GxxZ4tOUqCrdWM2Ccv6wnV/KS7CPJxdQPviTlYNXDJsO3acWByB97CYp0Uw6EHoJ+hxx+hI7jGYjgOAAejGMYJgHyoqTrF5yuuLhtOltAemrL1QOmDM8oo3L9u8K4FeasB0i3i/crVRjb/OiUmPMJSeDUmODIGgO8YL+jAYPd7fVNcHqvcFdGoynd46Rtt37AM8I+8YpK/a2sYhSXkhcaavtDEq/NL6/VuU0VkiqldDi1o4vr0EZ5X+qVFE+5/kXPuFA8Xi3HBRb6X7oSbD0Rz2lmT2vplelGcInNrr09bak5o8uvZnsDZJd3YOv7mtDryS8QuPnFHyC/+G538R/db1Cmr5Q8/lQ8abLWaXliM97APEm2G5nn5YnbyGz/duMGS/wzUaVRxyelSqOsB6aKH6A3ZIrbw3SYYaFJ14wqNSngj6XkrNadH2aNQdW0/FmN/r2Syg0x9qoWN/4YQRSiGIIwgJEPwTaAu085h5+UBsAKJF2sAIpdrJALFQhfDJW63fzusxYWm1/Pwtv/AA==&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<rect x="1" y="1" width="195" height="122" fill="#ffffff" stroke="#828282" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)rotate(-90 11 13)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 107px; height: 1px; padding-top: 13px; margin-left: -96px;">
<div style="box-sizing: border-box; font-size: 0; text-align: right; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #4D4D4D; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
S/4 HANA
</div>
</div>
</div>
</foreignObject>
<text x="11" y="25" fill="#4D4D4D" font-family="Helvetica" font-size="12px" text-anchor="end" font-weight="bold">
S/4 HANA
</text>
</switch>
</g>
<rect x="42.5" y="40" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 65px; margin-left: 44px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Business
<br/>
Partner
</div>
</div>
</div>
</foreignObject>
<text x="103" y="69" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Business...
</text>
</switch>
</g>
<rect x="221" y="1" width="347" height="349" fill="#ffffff" stroke="#828282" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 331px; height: 1px; padding-top: 15px; margin-left: 230px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #4D4D4D; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<b>
Incident Mgmt
</b>
<br/>
Extension App
</div>
</div>
</div>
</foreignObject>
<text x="230" y="27" fill="#4D4D4D" font-family="Helvetica" font-size="12px">
Incident Mgmt...
</text>
</switch>
</g>
<rect x="418" y="73" width="115" height="50" rx="7.5" ry="7.5" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 113px; height: 1px; padding-top: 98px; margin-left: 419px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Incidents
</div>
</div>
</div>
</foreignObject>
<text x="476" y="102" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Incidents
</text>
</switch>
</g>
<path d="M 475.5 182 L 475.5 142.97" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 481.5 182 L 475.5 170 L 469.5 182" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 475.5 124.97 L 480.79 133.97 L 475.5 142.97 L 470.21 133.97 Z" fill="#6c8ebf" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<rect x="255" y="73" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 98px; margin-left: 256px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Customers
</div>
</div>
</div>
</foreignObject>
<text x="315" y="102" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Customers
</text>
</switch>
</g>
<rect x="1" y="147" width="196.5" height="202" fill="#ffffff" stroke="#828282" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)rotate(-90 11 159)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 187px; height: 1px; padding-top: 159px; margin-left: -176px;">
<div style="box-sizing: border-box; font-size: 0; text-align: right; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #4D4D4D; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
SuccessFactors
</div>
</div>
</div>
</foreignObject>
<text x="11" y="171" fill="#4D4D4D" font-family="Helvetica" font-size="12px" text-anchor="end" font-weight="bold">
SuccessFactors
</text>
</switch>
</g>
<rect x="42.5" y="184" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 209px; margin-left: 44px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Workforce
<br/>
Person
</div>
</div>
</div>
</foreignObject>
<text x="103" y="213" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Workforce...
</text>
</switch>
</g>
<rect x="42.5" y="267" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 292px; margin-left: 44px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Employee
<br/>
Timesheet
</div>
</div>
</div>
</foreignObject>
<text x="103" y="296" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Employee...
</text>
</switch>
</g>
<path d="M 162.5 74.32 L 238.96 86.19" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/>
<path d="M 252.79 88.34 L 237.88 93.11 L 240.03 79.27 Z" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<rect x="418" y="182" width="115" height="50" rx="7.5" ry="7.5" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 113px; height: 1px; padding-top: 207px; margin-left: 419px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Messages
</div>
</div>
</div>
</foreignObject>
<text x="476" y="211" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Messages
</text>
</switch>
</g>
<path d="M 418 98 L 394.97 98" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 418 92 L 406 98 L 418 104" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 376.97 98 L 385.97 92.71 L 394.97 98 L 385.97 103.29 Z" fill="#6c8ebf" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<rect x="255" y="184" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 209px; margin-left: 256px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Service
<br/>
Worker
</div>
</div>
</div>
</foreignObject>
<text x="315" y="213" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Service...
</text>
</switch>
</g>
<path d="M 162.5 209 L 238.76 209" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/>
<path d="M 252.76 209 L 238.76 216 L 238.76 202 Z" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 354.83 181.46 L 439.35 123" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 358.91 171.95 L 352.99 182.73 L 365.16 180.99" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<rect x="255" y="267" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 292px; margin-left: 256px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Worker
<br/>
Availability
</div>
</div>
</div>
</foreignObject>
<text x="315" y="296" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Worker...
</text>
</switch>
</g>
<path d="M 162.5 292 L 238.76 292" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/>
<path d="M 252.76 292 L 238.76 299 L 238.76 285 Z" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 37 407 C 37 401.48 41.48 397 47 397 L 92.5 397 C 98.02 397 102.5 392.52 102.5 387 C 102.5 392.52 106.98 397 112.5 397 L 158 397 C 163.52 397 168 401.48 168 407" fill="none" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 103px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Backend Services
</div>
</div>
</div>
</foreignObject>
<text x="103" y="426" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Backend Services
</text>
</switch>
</g>
<path d="M 249.5 407 C 249.5 401.48 253.98 397 259.5 397 L 305 397 C 310.52 397 315 392.52 315 387 C 315 392.52 319.48 397 325 397 L 370.5 397 C 376.02 397 380.5 401.48 380.5 407" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 315px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Usage Views
</div>
</div>
</div>
</foreignObject>
<text x="315" y="426" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Usage Views
</text>
</switch>
</g>
<path d="M 410 407 C 410 401.48 414.48 397 420 397 L 465.5 397 C 471.02 397 475.5 392.52 475.5 387 C 475.5 392.52 479.98 397 485.5 397 L 531 397 C 536.52 397 541 401.48 541 407" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 476px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Extension Data
</div>
</div>
</div>
</foreignObject>
<text x="476" y="426" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Extension Data
</text>
</switch>
</g>
<path d="M 350 80.94 C 350 79.32 354.25 78 359.5 78 C 362.02 78 364.44 78.31 366.22 78.86 C 368 79.41 369 80.16 369 80.94 L 369 90.06 C 369 91.68 364.75 93 359.5 93 C 354.25 93 350 91.68 350 90.06 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 369 80.94 C 369 82.56 364.75 83.88 359.5 83.88 C 354.25 83.88 350 82.56 350 80.94" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Viewer does not support full SVG 1.1
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

4
etc/samples.drawio.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -6,16 +6,33 @@ Author = Author
AuthorID = Author ID AuthorID = Author ID
Stock = Stock Stock = Stock
Name = Name Name = Name
Description = Description
Image = Image
AuthorName = Author's Name AuthorName = Author's Name
DateOfBirth = Date of Birth DateOfBirth = Date of Birth
DateOfDeath = Date of Death DateOfDeath = Date of Death
PlaceOfBirth = Place of Birth PlaceOfBirth = Place of Birth
PlaceOfDeath = Place of Death PlaceOfDeath = Place of Death
Age = Age Age = Age
Lifetime = Lifetime
Authors = Authors Authors = Authors
Order = Order Order = Order
Orders = Orders Orders = Orders
OrderNo = Order Number
OrderItems = Order Items
Customer = Customer
Product = Product
ProductID = Product ID
ProductTitle = Product Title
UnitPrice = Unit Price
Quantity = Quantity
Price = Price Price = Price
Currency = Currency
Date = Date
Rating = Rating
NumberOfReviews = Number of Reviews
Genre = Genre Genre = Genre
Genres = Genres Genres = Genres

View File

@@ -43,5 +43,10 @@ extend sap.capire.bookshop.Authors with {
virtual lifetime : String; virtual lifetime : String;
} }
annotate AdminService.Authors with {
age @Common.Label : '{i18n>Age}';
lifetime @Common.Label : '{i18n>Lifetime}'
}
// Workaround for Fiori popup for asking user to enter a new UUID on Create // Workaround for Fiori popup for asking user to enter a new UUID on Create
annotate AdminService.Authors with { ID @Core.Computed; } annotate AdminService.Authors with { ID @Core.Computed; }

View File

@@ -11,7 +11,7 @@
}, },
"dataSources": { "dataSources": {
"AdminService": { "AdminService": {
"uri": "/admin/", "uri": "admin/",
"type": "OData", "type": "OData",
"settings": { "settings": {
"odataVersion": "4.0" "odataVersion": "4.0"
@@ -50,10 +50,6 @@
} }
}, },
"sap.ui5": { "sap.ui5": {
"flexEnabled": true,
"config": {
"experimentalCAPScenario": true
},
"dependencies": { "dependencies": {
"minUI5Version": "1.81.0", "minUI5Version": "1.81.0",
"libs": { "libs": {

View File

@@ -62,10 +62,16 @@ annotate AdminService.Books.texts with @(
} }
); );
annotate AdminService.Books.texts with {
ID @UI.Hidden;
ID_texts @UI.Hidden;
};
// Add Value Help for Locales // Add Value Help for Locales
annotate AdminService.Books.texts { annotate AdminService.Books.texts {
locale @ValueList:{entity:'Languages'}; locale @(
locale @Common.ValueListWithFixedValues:true; //show as drop down, not a dialog ValueList.entity:'Languages', Common.ValueListWithFixedValues, //show as drop down, not a dialog
)
} }
// In addition we need to expose Languages through AdminService as a target for ValueList // In addition we need to expose Languages through AdminService as a target for ValueList
using { sap } from '@sap/cds/common'; using { sap } from '@sap/cds/common';

View File

@@ -8,7 +8,7 @@
"i18n": "i18n/i18n.properties", "i18n": "i18n/i18n.properties",
"dataSources": { "dataSources": {
"AdminService": { "AdminService": {
"uri": "/admin/", "uri": "admin/",
"type": "OData", "type": "OData",
"settings": { "settings": {
"odataVersion": "4.0" "odataVersion": "4.0"
@@ -22,10 +22,6 @@
} }
}, },
"sap.ui5": { "sap.ui5": {
"flexEnabled": true,
"config": {
"experimentalCAPScenario": true
},
"dependencies": { "dependencies": {
"libs": { "libs": {
"sap.fe.templates": {} "sap.fe.templates": {}

View File

@@ -19,6 +19,14 @@
"title": "Browse Books", "title": "Browse Books",
"targetURL": "#Books-display" "targetURL": "#Books-display"
} }
},
{
"id": "BrowseGenres",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Browse Genres (OData v2)",
"targetURL": "#Genres-display"
}
} }
] ]
}, },
@@ -107,6 +115,24 @@
"url": "/admin-authors/webapp" "url": "/admin-authors/webapp"
} }
}, },
"BrowseGenres": {
"semanticObject": "Genres",
"action": "display",
"title": "Browse Genres",
"signature": {
"parameters": {
"Genre.ID": {
"renameTo": "ID"
}
},
"additionalParameters": "ignored"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=genres",
"url": "/genres/webapp"
}
},
"ManageBooks": { "ManageBooks": {
"semanticObject": "Books", "semanticObject": "Books",
"action": "manage", "action": "manage",

View File

@@ -33,7 +33,7 @@ annotate CatalogService.Books with @(UI : {
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Books Object Page // Books List Page
// //
annotate CatalogService.Books with @(UI : { annotate CatalogService.Books with @(UI : {
SelectionFields : [ SelectionFields : [
@@ -52,9 +52,6 @@ annotate CatalogService.Books with @(UI : {
}, },
{Value : genre.name}, {Value : genre.name},
{Value : price}, {Value : price},
{ {Value : currency.symbol},
Value : currency.symbol,
Label : ' '
},
] ]
}, ); }, );

View File

@@ -11,7 +11,7 @@
}, },
"dataSources": { "dataSources": {
"CatalogService": { "CatalogService": {
"uri": "/browse/", "uri": "browse/",
"type": "OData", "type": "OData",
"settings": { "settings": {
"odataVersion": "4.0" "odataVersion": "4.0"
@@ -32,7 +32,7 @@
"renameTo": "ID" "renameTo": "ID"
}, },
"Authors.books.ID": { "Authors.books.ID": {
"renameTo": "ID" "renameTo": "ID"
} }
}, },
"additionalParameters": "ignored" "additionalParameters": "ignored"

View File

@@ -4,57 +4,53 @@
using { sap.capire.bookshop as my } from '@capire/bookstore'; using { sap.capire.bookshop as my } from '@capire/bookstore';
using { sap.common } from '@capire/common'; using { sap.common } from '@capire/common';
using { sap.common.Currencies } from '@sap/cds/common';
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Books Lists // Books Lists
// //
annotate my.Books with @( annotate my.Books with @(
Common.SemanticKey : [ID], Common.SemanticKey : [ID],
UI : { UI : {
Identification : [{Value : title}], Identification : [{ Value: title }],
SelectionFields : [ SelectionFields : [
ID, ID,
author_ID, author_ID,
price, price,
currency_code currency_code
], ],
LineItem : [ LineItem : [
{ { Value: ID, Label: '{i18n>Title}' },
Value : ID, { Value: author.ID, Label: '{i18n>Author}' },
Label : '{i18n>Title}' { Value: genre.name },
}, { Value: stock },
{ { Value: price },
Value : author.ID, { Value: currency.symbol },
Label : '{i18n>Author}' ]
}, }
{Value : genre.name},
{Value : stock},
{Value : price},
{
Value : currency.symbol,
Label : ' '
},
]
}
) { ) {
ID @Common: { ID @Common: {
SemanticObject : 'Books', SemanticObject : 'Books',
Text: title, Text: title,
TextArrangement : #TextOnly TextArrangement : #TextOnly
}; };
author @ValueList.entity : 'Authors'; author @ValueList.entity : 'Authors';
}; };
annotate Currencies with {
symbol @Common.Label : '{i18n>Currency}';
}
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Books Details // Books Details
// //
annotate my.Books with @(UI : {HeaderInfo : { annotate my.Books with @(UI : {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 }
}, }); }, });
@@ -63,19 +59,14 @@ annotate my.Books with @(UI : {HeaderInfo : {
// Books Elements // Books Elements
// //
annotate my.Books with { annotate my.Books with {
ID @title : '{i18n>ID}'; ID @title: '{i18n>ID}';
title @title : '{i18n>Title}'; title @title: '{i18n>Title}';
genre @title : '{i18n>Genre}' @Common : { genre @title: '{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
Text : genre.name, author @title: '{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
TextArrangement : #TextOnly price @title: '{i18n>Price}' @Measures.ISOCurrency : currency_code;
}; stock @title: '{i18n>Stock}';
author @title : '{i18n>Author}' @Common : { descr @title: '{i18n>Description}' @UI.MultiLineText;
Text : author.name, image @title: '{i18n>Image}';
TextArrangement : #TextOnly
};
price @title : '{i18n>Price}' @Measures.ISOCurrency : currency_code;
stock @title : '{i18n>Stock}';
descr @UI.MultiLineText;
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -83,36 +74,40 @@ annotate my.Books with {
// Genres List // Genres List
// //
annotate my.Genres with @( annotate my.Genres with @(
Common.SemanticKey : [name], Common.SemanticKey : [name],
UI : { UI : {
SelectionFields : [name], SelectionFields : [name],
LineItem : [ LineItem : [
{Value : name}, { Value: name },
{ {
Value : parent.name, Value : parent.name,
Label : 'Main Genre' Label: 'Main Genre'
}, },
], ],
} }
); );
annotate my.Genres with {
ID @Common.Text : name @Common.TextArrangement : #TextOnly;
}
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Genre Details // Genre Details
// //
annotate my.Genres with @(UI : { annotate my.Genres with @(UI : {
Identification : [{Value : name}], Identification : [{ Value: name}],
HeaderInfo : { HeaderInfo : {
TypeName : '{i18n>Genre}', TypeName : '{i18n>Genre}',
TypeNamePlural : '{i18n>Genres}', TypeNamePlural : '{i18n>Genres}',
Title : {Value : name}, Title : { Value: name },
Description : {Value : ID} Description : { Value: ID }
}, },
Facets : [{ Facets : [{
$Type : 'UI.ReferenceFacet', $Type : 'UI.ReferenceFacet',
Label : '{i18n>SubGenres}', Label : '{i18n>SubGenres}',
Target : 'children/@UI.LineItem' Target : 'children/@UI.LineItem'
}, ], }, ],
}); });
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -120,8 +115,8 @@ annotate my.Genres with @(UI : {
// Genres Elements // Genres Elements
// //
annotate my.Genres with { annotate my.Genres with {
ID @title : '{i18n>ID}'; ID @title: '{i18n>ID}';
name @title : '{i18n>Genre}'; name @title: '{i18n>Genre}';
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -129,24 +124,24 @@ annotate my.Genres with {
// Authors List // Authors List
// //
annotate my.Authors with @( annotate my.Authors with @(
Common.SemanticKey : [ID], Common.SemanticKey : [ID],
UI : { UI : {
Identification : [{Value : name}], Identification : [{ Value: name}],
SelectionFields : [name], SelectionFields : [name],
LineItem : [ LineItem : [
{Value : ID}, { Value: ID },
{Value : dateOfBirth}, { Value: dateOfBirth },
{Value : dateOfDeath}, { Value: dateOfDeath },
{Value : placeOfBirth}, { Value: placeOfBirth },
{Value : placeOfDeath}, { Value: placeOfDeath },
], ],
} }
) { ) {
ID @Common: { ID @Common: {
SemanticObject : 'Authors', SemanticObject : 'Authors',
Text: name, Text: name,
TextArrangement : #TextOnly, TextArrangement : #TextOnly,
}; };
}; };
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -154,16 +149,16 @@ annotate my.Authors with @(
// Author Details // Author Details
// //
annotate my.Authors with @(UI : { annotate my.Authors with @(UI : {
HeaderInfo : { HeaderInfo : {
TypeName : '{i18n>Author}', TypeName : '{i18n>Author}',
TypeNamePlural : '{i18n>Authors}', TypeNamePlural : '{i18n>Authors}',
Title : {Value : name}, Title : { Value: name },
Description : {Value : dateOfBirth} Description : { Value: dateOfBirth }
}, },
Facets : [{ Facets : [{
$Type : 'UI.ReferenceFacet', $Type : 'UI.ReferenceFacet',
Target : 'books/@UI.LineItem' Target : 'books/@UI.LineItem'
}, ], }, ],
}); });
@@ -172,12 +167,12 @@ annotate my.Authors with @(UI : {
// Authors Elements // Authors Elements
// //
annotate my.Authors with { annotate my.Authors with {
ID @title : '{i18n>ID}'; ID @title: '{i18n>ID}';
name @title : '{i18n>Name}'; name @title: '{i18n>Name}';
dateOfBirth @title : '{i18n>DateOfBirth}'; dateOfBirth @title: '{i18n>DateOfBirth}';
dateOfDeath @title : '{i18n>DateOfDeath}'; dateOfDeath @title: '{i18n>DateOfDeath}';
placeOfBirth @title : '{i18n>PlaceOfBirth}'; placeOfBirth @title: '{i18n>PlaceOfBirth}';
placeOfDeath @title : '{i18n>PlaceOfDeath}'; placeOfDeath @title: '{i18n>PlaceOfDeath}';
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -185,18 +180,18 @@ annotate my.Authors with {
// Languages List // Languages List
// //
annotate common.Languages with @( annotate common.Languages with @(
Common.SemanticKey : [code], Common.SemanticKey : [code],
Identification : [{Value : code}], Identification : [{ Value: code}],
UI : { UI : {
SelectionFields : [ SelectionFields : [
name, name,
descr descr
], ],
LineItem : [ LineItem : [
{Value : code}, { Value: code },
{Value : name}, { Value: name },
], ],
} }
); );
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -204,22 +199,22 @@ annotate common.Languages with @(
// Language Details // Language Details
// //
annotate common.Languages with @(UI : { annotate common.Languages with @(UI : {
HeaderInfo : { HeaderInfo : {
TypeName : '{i18n>Language}', TypeName : '{i18n>Language}',
TypeNamePlural : '{i18n>Languages}', TypeNamePlural : '{i18n>Languages}',
Title : {Value : name}, Title : { Value: name },
Description : {Value : descr} Description : { Value: descr }
}, },
Facets : [{ Facets : [{
$Type : 'UI.ReferenceFacet', $Type : 'UI.ReferenceFacet',
Label : '{i18n>Details}', Label : '{i18n>Details}',
Target : '@UI.FieldGroup#Details' Target : '@UI.FieldGroup#Details'
}, ], }, ],
FieldGroup #Details : {Data : [ FieldGroup #Details : {Data : [
{Value : code}, { Value: code },
{Value : name}, { Value: name },
{Value : descr} { Value: descr }
]}, ]},
}); });
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -227,19 +222,19 @@ annotate common.Languages with @(UI : {
// Currencies List // Currencies List
// //
annotate common.Currencies with @( annotate common.Currencies with @(
Common.SemanticKey : [code], Common.SemanticKey : [code],
Identification : [{Value : code}], Identification : [{ Value: code}],
UI : { UI : {
SelectionFields : [ SelectionFields : [
name, name,
descr descr
], ],
LineItem : [ LineItem : [
{Value : descr}, { Value: descr },
{Value : symbol}, { Value: symbol },
{Value : code}, { Value: code },
], ],
} }
); );
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -247,35 +242,35 @@ annotate common.Currencies with @(
// Currency Details // Currency Details
// //
annotate common.Currencies with @(UI : { annotate common.Currencies with @(UI : {
HeaderInfo : { HeaderInfo : {
TypeName : '{i18n>Currency}', TypeName : '{i18n>Currency}',
TypeNamePlural : '{i18n>Currencies}', TypeNamePlural : '{i18n>Currencies}',
Title : {Value : descr}, Title : { Value: descr },
Description : {Value : code} Description : { Value: code }
},
Facets : [
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Details}',
Target : '@UI.FieldGroup#Details'
}, },
Facets : [ {
{ $Type : 'UI.ReferenceFacet',
$Type : 'UI.ReferenceFacet', Label : '{i18n>Extended}',
Label : '{i18n>Details}', Target : '@UI.FieldGroup#Extended'
Target : '@UI.FieldGroup#Details' },
}, ],
{ FieldGroup #Details : {Data : [
$Type : 'UI.ReferenceFacet', { Value: name },
Label : '{i18n>Extended}', { Value: symbol },
Target : '@UI.FieldGroup#Extended' { Value: code },
}, { Value: descr }
], ]},
FieldGroup #Details : {Data : [ FieldGroup #Extended : {Data : [
{Value : name}, { Value: numcode },
{Value : symbol}, { Value: minor },
{Value : code}, { Value: exponent }
{Value : descr} ]},
]},
FieldGroup #Extended : {Data : [
{Value : numcode},
{Value : minor},
{Value : exponent}
]},
}); });
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -283,7 +278,7 @@ annotate common.Currencies with @(UI : {
// Currencies Elements // Currencies Elements
// //
annotate common.Currencies with { annotate common.Currencies with {
numcode @title : '{i18n>NumCode}'; numcode @title: '{i18n>NumCode}';
minor @title : '{i18n>MinorUnit}'; minor @title: '{i18n>MinorUnit}';
exponent @title : '{i18n>Exponent}'; exponent @title: '{i18n>Exponent}';
} }

View File

@@ -10,39 +10,20 @@
<script> <script>
window["sap-ushell-config"] = { window["sap-ushell-config"] = {
defaultRenderer: "fiori2", defaultRenderer: "fiori2",
applications: {}, applications: {}
bootstrapPlugins: {
RuntimeAuthoringPlugin: {
component: "sap.ushell.plugins.rta",
config: {
validateAppVersion: false,
},
},
PersonalizePlugin: {
component: "sap.ushell.plugins.rta-personalize",
config: {
validateAppVersion: false,
},
},
}
}; };
</script> </script>
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script> <script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js" <script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout" data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
data-sap-ui-compatVersion="edge" data-sap-ui-compatVersion="edge"
data-sap-ui-theme="sap_fiori_3" data-sap-ui-theme="sap_horizon"
data-sap-ui-frameOptions="allow" data-sap-ui-frameOptions="allow"
></script> ></script>
<script> <script>
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content")); sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
sap.ui </script>
.getCore()
.getConfiguration()
.setFlexibilityServices([{ connector: "SessionStorageConnector" }]);
sap.ui.getCore().getConfiguration().setLanguage("en");
</script>
</head> </head>
<body class="sapUiBody" id="content"></body> <body class="sapUiBody" id="content"></body>

View File

@@ -0,0 +1,8 @@
using { sap.capire.bookshop } from '../../db/common';
annotate bookshop.GenreHierarchy {
ID @sap.hierarchy.node.for;
parent @sap.hierarchy.parent.node.for;
hierarchyLevel @sap.hierarchy.level.for;
drillState @sap.hierarchy.drill.state.for;
}

View File

@@ -0,0 +1,7 @@
sap.ui.define(["sap/suite/ui/generic/template/lib/AppComponent"], (AppComponent) =>
AppComponent.extend("genres.Component", {
metadata: {
manifest: "json",
},
})
);

View File

@@ -0,0 +1,4 @@
#XTIT
appTitle=Genres
#XTXT
appDescription=Browse Genres

View File

@@ -0,0 +1,155 @@
{
"_version": "1.8.0",
"sap.app": {
"id": "genres",
"type": "application",
"i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"title": "Browse Genres Hierarchy (OData v2)",
"description": "{{appDescription}}",
"tags": {
"keywords": []
},
"crossNavigation": {
"inbounds": {
"appShow": {
"title": "{{appTitle}}",
"semanticObject": "GenreHierarchy",
"action": "display",
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
},
"icon": "sap-icon://settings",
"size": "1x1"
}
},
"outbounds": {}
},
"ach": "",
"resources": "resources.json",
"dataSources": {
"main": {
"uri": "/v2/browse",
"type": "OData",
"settings": {
"annotations": ["localAnnotations"],
"localUri": "localService/metadata.xml"
}
},
"localAnnotations": {
"type": "ODataAnnotation",
"uri": "annotations/localAnnotations.xml",
"settings": {
"localUri": "annotations/localAnnotations.xml"
}
}
},
"offline": false,
"sourceTemplate": {
"id": "ui5template.smartTemplate",
"version": "1.40.12"
}
},
"sap.ui": {
"technology": "UI5",
"icons": {
"icon": "",
"favIcon": "",
"phone": "",
"phone@2": "",
"tablet": "",
"tablet@2": ""
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
},
"supportedThemes": ["sap_hcb", "sap_belize", "sap_belize_deep", "sap_fiori_3"]
},
"sap.ui5": {
"resources": {
"js": [],
"css": []
},
"dependencies": {
"minUI5Version": "1.65.6",
"libs": {},
"components": {}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/i18n.properties"
},
"@i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/i18n.properties"
},
"json": {
"type": "sap.ui.model.json.JSONModel"
},
"i18n|sap.suite.ui.generic.template.ListReport|Genres": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/ListReport/Genres/i18n.properties"
},
"": {
"dataSource": "main",
"preload": true,
"settings": {
"useBatch": true,
"defaultBindingMode": "TwoWay",
"defaultCountMode": "Inline",
"refreshAfterChange": true,
"metadataUrlParams": {
"sap-value-list": "none"
}
}
}
},
"contentDensities": {
"compact": true,
"cozy": true
}
},
"sap.ui.generic.app": {
"_version": "1.3.0",
"settings": {
"forceGlobalRefresh": false,
"useColumnLayoutForSmartForm": false,
"showBasicSearch": false
},
"pages": {
"ListReport|Genres": {
"entitySet": "GenreHierarchy",
"component": {
"name": "sap.suite.ui.generic.template.ListReport",
"list": true,
"settings": {
"condensedTableLayout": true,
"smartVariantManagement": true,
"tableType": "TreeTable",
"enableTableFilterInPageVariant": true,
"dataLoadSettings": {
"loadDataOnAppLaunch": "always"
}
}
}
}
}
},
"sap.fiori": {
"registrationIds": [],
"archeType": "transactional"
},
"sap.platform.hcp": {
"uri": ""
},
"sap.platform.cf": {
"oAuthScopes": []
}
}

View File

@@ -5,5 +5,6 @@
using from './admin-authors/fiori-service'; using from './admin-authors/fiori-service';
using from './admin-books/fiori-service'; using from './admin-books/fiori-service';
using from './browse/fiori-service'; using from './browse/fiori-service';
using from './genres/fiori-service';
using from './common'; using from './common';
using from '@capire/bookstore/srv/mashup'; using from '@capire/bookstore/srv/mashup';

14
fiori/db/common.cds Normal file
View File

@@ -0,0 +1,14 @@
namespace sap.capire.bookshop;
using { sap.capire.bookshop } from '@capire/bookstore/srv/mashup';
entity GenreHierarchy : bookshop.Genres {
hierarchyLevel : Integer default 0;
drillState : String default 'leaf';
parent : Association to GenreHierarchy;
children : Composition of many GenreHierarchy on children.parent = $self;
}
extend service CatalogService with {
@readonly entity GenreHierarchy as projection on bookshop.GenreHierarchy;
}

View File

@@ -0,0 +1,16 @@
ID;parent_ID;name;hierarchyLevel;drillState
10;;Fiction;0;expanded
11;10;Drama;1;leaf
12;10;Poetry;1;leaf
13;10;Fantasy;1;leaf
14;10;Science Fiction;1;leaf
15;10;Romance;1;leaf
16;10;Mystery;1;leaf
17;10;Thriller;1;leaf
18;10;Dystopia;1;leaf
20;;Non-Fiction;0;expanded
19;10;Fairy Tale;1;leaf
21;20;Biography;1;expanded
22;21;Autobiography;2;leaf
23;20;Essay;1;leaf
24;20;Speech;1;leaf
1 ID parent_ID name hierarchyLevel drillState
2 10 Fiction 0 expanded
3 11 10 Drama 1 leaf
4 12 10 Poetry 1 leaf
5 13 10 Fantasy 1 leaf
6 14 10 Science Fiction 1 leaf
7 15 10 Romance 1 leaf
8 16 10 Mystery 1 leaf
9 17 10 Thriller 1 leaf
10 18 10 Dystopia 1 leaf
11 20 Non-Fiction 0 expanded
12 19 10 Fairy Tale 1 leaf
13 21 20 Biography 1 expanded
14 22 21 Autobiography 2 leaf
15 23 20 Essay 1 leaf
16 24 20 Speech 1 leaf

1
fiori/index.cds Normal file
View File

@@ -0,0 +1 @@
using from './db/common';

View File

@@ -3,9 +3,10 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@capire/bookstore": "*", "@capire/bookstore": "*",
"@sap/cds": "^5", "@sap/cds": ">=5",
"@sap/cds-odata-v2-adapter-proxy": "^1.9.0",
"express": "^4.17.1", "express": "^4.17.1",
"passport": "^0.4.1" "passport": ">=0.4.1"
}, },
"scripts": { "scripts": {
"start": "cds run --in-memory?", "start": "cds run --in-memory?",
@@ -13,12 +14,6 @@
}, },
"cds": { "cds": {
"requires": { "requires": {
"extensibility": {
"kind": "uiflex"
},
"auth": {
"strategy": "dummy"
},
"ReviewsService": { "ReviewsService": {
"kind": "odata", "kind": "odata",
"model": "@capire/reviews" "model": "@capire/reviews"
@@ -39,7 +34,8 @@
} }
}, },
"db": { "db": {
"kind": "sql" "kind": "sql",
"schema_evolution": "auto"
}, },
"db-ext": { "db-ext": {
"[development]": { "[development]": {
@@ -48,10 +44,10 @@
"[production]": { "[production]": {
"model": "db/hana" "model": "db/hana"
} }
},
"hana": {
"deploy-format": "hdbtable"
} }
},
"hana": {
"deploy-format": "hdbtable"
} }
} }
} }

View File

@@ -1 +1,8 @@
module.exports = require('@capire/bookstore/server.js') const cds = require("@sap/cds")
// install OData v2 adapter
const proxy = require('@sap/cds-odata-v2-adapter-proxy')
const proxyOpts = global.it ? { target:'auto' } : {} // for tests, set 'auto' to detect port dynamically
cds.on('bootstrap', app => app.use(proxy(proxyOpts)))
module.exports = require('@capire/bookstore/server.js')

View File

@@ -6,25 +6,13 @@
"start": "cds serve srv/world.cds", "start": "cds serve srv/world.cds",
"start:ts": "cds-ts serve srv/world.cds" "start:ts": "cds-ts serve srv/world.cds"
}, },
"devDependencies": { "dependencies": {
"@types/jest": "^27.0.2", "@sap/cds": ">=5.0.4"
"@types/node": "^16.11.6",
"ts-jest": "^27.0.2",
"typescript": "^4.3.5"
}, },
"jest": { "devDependencies": {
"testEnvironment": "node", "@types/jest": "*",
"preset": "ts-jest", "@types/node": "*",
"globals": { "typescript": "^4.3.5"
"ts-jest": {
"diagnostics": {
"_comment": "see https://githubmemory.com/repo/kulshekhar/ts-jest/issues/2722",
"ignoreCodes": [
151001
]
}
}
}
}, },
"eslintConfig": { "eslintConfig": {
"extends": "eslint:recommended", "extends": "eslint:recommended",

View File

@@ -1,3 +1,7 @@
module.exports = class say { module.exports = class say {
hello(req) { return `Hello ${req.data.to}!` } hello(req) {
let {to} = req.data
if (to === 'me') to = require('os').userInfo().username
return `Hello ${to}!`
}
} }

View File

@@ -0,0 +1,13 @@
const cds = require ('@sap/cds')
describe('Hello world!', () => {
beforeAll (()=> process.env.CDS_TYPESCRIPT = true)
afterAll (()=> delete process.env.CDS_TYPESCRIPT)
const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds')
it('should say hello with class impl', async () => {
const {data} = await GET`/say/hello(to='world')`
expect(data.value).toMatch(/Hello world.*typescript.*/i)
})
})

View File

@@ -1,15 +0,0 @@
process.env.CDS_TYPESCRIPT = 'true';
import * as cds from '@sap/cds';
//@ts-ignore
const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds');
describe('Hello world!', () => {
afterAll(() => { delete process.env.CDS_TYPESCRIPT; });
it('should say hello with class impl from a typescript file', async () => {
const {data} = await GET`/say/hello(to='world')`
expect(data.value).toMatch(/Hello world.*typescript.*/i)
})
})

76
loggers/app/loggers.html Normal file
View File

@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html>
<head>
<title> cds.log </title>
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
<style>
select { border-color: transparent; padding: 4px 12px; margin: 0px; }
button { padding: 2px 11px; margin: 0px 4px; font: 90% italic; }
</style>
</head>
<body class="small-container" , style="margin-top: 70px;">
<div id='app'>
<h1> Log Levels </h1>
<input type="text" placeholder="Search by ID or Log Level..." @input="fetch">
<table id='loggers'>
<thead>
<th> Module ID </th>
<th> Log Level </th>
</thead>
<tr v-for="each in list">
<td>{{ each.id }}</td>
<td><select v-bind:id="each.id" v-model="each.level" @change="set">
<option>SILENT</option>
<option>ERROR</option>
<option>WARN</option>
<option>INFO</option>
<option>DEBUG</option>
<option>TRACE</option>
</select>
</td>
</tr>
</table>
<h4>Log Format:</h4>
[ <button class="round-button" :class={'muted-button':!format.timestamp} @click="toggle_format" id="timestamp">Timestamp </button>
| <button class="round-button" :class={'muted-button':!format.level} @click="toggle_format" id="level">Log Level </button>
| <button class="round-button" :class={'muted-button':!format.tenant} @click="toggle_format" id="tenant">Tenant </button>
| <button class="round-button" :class={'muted-button':!format.reqid} @click="toggle_format" id="reqid">Request ID </button>
| <button class="round-button" :class={'muted-button':!format.id} @click="toggle_format" id="module">Logger ID </button>
] - <i>log message ...</i>
</div>
</body>
<script>
axios.defaults.headers['Content-Type'] = 'application/json'
axios.defaults.baseURL = '/log'
const loggers = Vue.createApp({ el: '#app',
data() {
return {
format: { timestamp:false, level:false, tenant:false, reqid:false, id:true, },
list: [],
}
},
methods: {
async fetch (eve) {
this.list = (await axios.get (`/Loggers${
eve && eve.target.value ? `?$search=${eve.target.value}` : ''
}`)).data
},
async set (eve) {
const { id, value:level } = eve.target
await axios.put (`/Logger/${id}`, {id,level})
},
async toggle_format (eve) {
this.format[eve.target.id] = !this.format[eve.target.id]
await axios.post (`/format`, this.format)
},
},
}).mount('#app')
loggers.fetch() // initially fill list of loggers
</script>
</html>

22
loggers/package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "@capire/loggers",
"version": "1.0.0",
"description": "Simple sample on how to dynamically set cds.log levels and formats.",
"files": [
"app",
"srv"
],
"dependencies": {
"@sap/cds": ">=5.9",
"express": "^4.17.1"
},
"scripts": {
"start": "cds run",
"watch": "cds watch"
},
"cds": {
"requires": {
"db": "sql"
}
}
}

11
loggers/readme.md Normal file
View File

@@ -0,0 +1,11 @@
# Dynamically Set `cds.log` Levels and Formats
### Run
```sh
cds watch
```
### Test
Either using the UI through http://localhost:4004/loggers.html, or try the requests in `test/requests.http`

3
loggers/srv/dummy.cds Normal file
View File

@@ -0,0 +1,3 @@
service Sue {
entity Dummy { key ID: UUID; title: String; }
}

20
loggers/srv/loggers.cds Normal file
View File

@@ -0,0 +1,20 @@
@rest service LogService {
@readonly entity Loggers : Logger {};
entity Logger {
key id : String;
level : String;
}
action format (
timestamp : Boolean,
level : Boolean,
tenant : Boolean,
reqid : Boolean,
id : Boolean,
);
action debug (logger : String) returns Logger;
action reset (logger : String) returns Logger;
}

56
loggers/srv/loggers.js Normal file
View File

@@ -0,0 +1,56 @@
const cds = require ('@sap/cds/lib')
const LOG = cds.log('cds.log')
module.exports = class LogService extends cds.Service {
init(){
this.on('GET','Loggers', (req)=>{
let loggers = Object.values(cds.log.loggers).map (_logger)
let {$search} = req._.req.query
if ($search) {
const re = RegExp($search,'i')
loggers = loggers.filter (l => re.test(l.id) || re.test(l.level))
}
return loggers.sort ((a,b) => a.id < b.id ? -1 : 1)
})
this.on('PUT','Logger', (req)=>{
const {id} = req.params[0] || req.data
if (!id) return req.reject('No logger id specified in request')
return _logger (cds.log (id, req.data))
})
this.on('debug', (req)=>{
const {logger:id} = req.params[0] || req.data
if (!id) return req.reject('No logger id specified in request')
return _logger (cds.log (id, {level:'debug'}))
})
this.on('reset', (req)=>{
const {logger:id} = req.params[0] || req.data
if (!id) return req.reject('No logger id specified in request')
return _logger (cds.log (id, {level:'info'}))
})
this.on('format', (req)=>{
const $ = req.data; LOG.info('format:',$)
// Set format for new loggers constructed subsequently
cds.log.format = (id, level, ...args) => {
const fmt = []
if ($.timestamp) fmt.push ('|', (new Date).toISOString())
if ($.level) fmt.push ('|', _levels[level].padEnd(5))
if ($.tenant) fmt.push ('|', cds.context && cds.context.tenant)
if ($.reqid) fmt.push ('|', cds.context && cds.context.id)
if ($.id) fmt.push ('|', id)
fmt[0] = '[', fmt.push ('] -', ...args)
return fmt
}
// Apply this format to all existing loggers
Object.values(cds.log.loggers).forEach (l => l.setFormat (cds.log.format))
})
}
}
const _logger = ({id,level}) => ({id, level:_levels[level] })
const _levels = [ 'SILENT', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE' ]

View File

@@ -0,0 +1,18 @@
http://localhost:4004/loggers.html
@body: = Content-Type: application/json\n\n
###
GET http://localhost:4004/log/Loggers
###
PUT http://localhost:4004/log/Logger/sqlite
{{body:}} { "level": "debug" }
###
POST http://localhost:4004/log/debug(logger='sqlite')
###
POST http://localhost:4004/log/reset(logger='sqlite')
### Dummy request to see sqlite debug output
GET http://localhost:4004/sue/Dummy

View File

@@ -40,7 +40,7 @@ module.exports = srv => {
req.reject(404, 'Media not found for the ID') req.reject(404, 'Media not found for the ID')
return return
} }
const decodedMedia = new Buffer( const decodedMedia = Buffer.from(
mediaObj.media.split(';base64,').pop(), mediaObj.media.split(';base64,').pop(),
'base64' 'base64'
) )

View File

@@ -16,23 +16,24 @@ using { OrdersService } from '../srv/orders-service';
@odata.draft.enabled @odata.draft.enabled
annotate OrdersService.Orders with @( annotate OrdersService.Orders with @(
UI: { UI: {
SelectionFields: [ createdAt, createdBy ], SelectionFields: [ createdBy ],
LineItem: [ LineItem: [
{Value: OrderNo, Label:'OrderNo'}, {Value: OrderNo, Label:'{i18n>OrderNo}'},
{Value: buyer, Label:'Customer'}, {Value: buyer, Label:'{i18n>Customer}'},
{Value: createdAt, Label:'Date'} {Value: currency.symbol, Label:'{i18n>Currency}'},
{Value: createdAt, Label:'{i18n>Date}'},
], ],
HeaderInfo: { HeaderInfo: {
TypeName: 'Order', TypeNamePlural: 'Orders', TypeName: '{i18n>Order}', TypeNamePlural: '{i18n>Orders}',
Title: { Title: {
Label: 'Order number ', //A label is possible but it is not considered on the ObjectPage yet Label: '{i18n>OrderNo}', //A label is possible but it is not considered on the ObjectPage yet
Value: OrderNo Value: OrderNo
}, },
Description: {Value: createdBy} Description: {Value: createdBy}
}, },
Identification: [ //Is the main field group Identification: [ //Is the main field group
{Value: createdBy, Label:'Customer'}, {Value: createdBy, Label:'{i18n>Customer}'},
{Value: createdAt, Label:'Date'}, {Value: createdAt, Label:'{i18n>Date}'},
{Value: OrderNo }, {Value: OrderNo },
], ],
HeaderFacets: [ HeaderFacets: [
@@ -45,7 +46,7 @@ annotate OrdersService.Orders with @(
], ],
FieldGroup#Details: { FieldGroup#Details: {
Data: [ Data: [
{Value: currency.code, Label:'Currency'} {Value: currency.code, Label:'{i18n>Currency}'}
] ]
}, },
FieldGroup#Created: { FieldGroup#Created: {
@@ -64,6 +65,7 @@ annotate OrdersService.Orders with @(
) { ) {
createdAt @UI.HiddenFilter:false; createdAt @UI.HiddenFilter:false;
createdBy @UI.HiddenFilter:false; createdBy @UI.HiddenFilter:false;
ID @UI.Hidden;
}; };
@@ -71,15 +73,15 @@ annotate OrdersService.Orders with @(
annotate OrdersService.Orders.Items with @( annotate OrdersService.Orders.Items with @(
UI: { UI: {
LineItem: [ LineItem: [
{Value: product_ID, Label:'Product ID'}, {Value: product_ID, Label:'{i18n>ProductID}'},
{Value: title, Label:'Product Title'}, {Value: title, Label:'{i18n>ProductTitle}'},
{Value: price, Label:'Unit Price'}, {Value: price, Label:'{i18n>UnitPrice}'},
{Value: quantity, Label:'Quantity'}, {Value: quantity, Label:'{i18n>Quantity}'},
], ],
Identification: [ //Is the main field group Identification: [ //Is the main field group
{Value: quantity, Label:'Quantity'}, {Value: quantity, Label:'{i18n>Quantity}'},
{Value: title, Label:'Product'}, {Value: title, Label:'{i18n>Product}'},
{Value: price, Label:'Unit Price'}, {Value: price, Label:'{i18n>UnitPrice}'},
], ],
Facets: [ Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'}, {$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'},
@@ -89,4 +91,7 @@ annotate OrdersService.Orders.Items with @(
quantity @( quantity @(
Common.FieldControl: #Mandatory Common.FieldControl: #Mandatory
); );
ID @UI.Hidden;
up_ @UI.Hidden;
}; };

View File

@@ -13,7 +13,7 @@
applications: { applications: {
"manage-orders": { "manage-orders": {
title: "Manage Orders", title: "Manage Orders",
description: "... testing FE v42", description: "CAP Sample App",
additionalInformation: "SAPUI5.Component=orders", additionalInformation: "SAPUI5.Component=orders",
applicationType : "URL", applicationType : "URL",
url: "/orders/webapp", url: "/orders/webapp",
@@ -25,10 +25,10 @@
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script> <script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js" <script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout" data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
data-sap-ui-compatVersion="edge" data-sap-ui-compatVersion="edge"
data-sap-ui-theme="sap_fiori_3" data-sap-ui-theme="sap_horizon"
data-sap-ui-frameOptions="allow" data-sap-ui-frameOptions="allow"
></script> ></script>
<script> <script>
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content")) sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))

View File

@@ -3,8 +3,8 @@
"sap.app": { "sap.app": {
"id": "orders", "id": "orders",
"type": "application", "type": "application",
"title": "Order Books", "title": "Order Management",
"description": "Sample Application", "description": "CAP Sample Application",
"i18n": "i18n/i18n.properties", "i18n": "i18n/i18n.properties",
"dataSources": { "dataSources": {
"OrdersService": { "OrdersService": {

View File

@@ -2,7 +2,7 @@ using { Currency, User, managed, cuid } from '@sap/cds/common';
namespace sap.capire.orders; namespace sap.capire.orders;
entity Orders : cuid, managed { entity Orders : cuid, managed {
OrderNo : String @title:'Order Number'; //> readable key OrderNo : String(22) @title:'Order Number'; //> readable key
Items : Composition of many { Items : Composition of many {
key ID : UUID; key ID : UUID;
product : Association to Products; product : Association to Products;

View File

@@ -3,6 +3,6 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@capire/common": "*", "@capire/common": "*",
"@sap/cds": "^5" "@sap/cds": ">=5"
} }
} }

12612
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,20 +5,26 @@
"repository": "https://github.com/sap-samples/cloud-cap-samples.git", "repository": "https://github.com/sap-samples/cloud-cap-samples.git",
"author": "daniel.hutzel@sap.com", "author": "daniel.hutzel@sap.com",
"dependencies": { "dependencies": {
"@capire/bookstore": "./bookstore", "@sap/cds": ">=5.5.3"
"@capire/bookshop": "./bookshop",
"@capire/common": "./common",
"@capire/fiori": "./fiori",
"@capire/hello": "./hello",
"@capire/media": "./media",
"@capire/orders": "./orders",
"@capire/reviews": "./reviews",
"@sap/cds": "^5.5.3"
}, },
"workspaces": [
"./bookshop",
"./bookstore",
"./common",
"./data-viewer",
"./fiori",
"./hello",
"./media",
"./orders",
"./loggers",
"./reviews"
],
"devDependencies": { "devDependencies": {
"axios": "^1",
"chai": "^4.3.4", "chai": "^4.3.4",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0", "chai-subset": "^1.6.0",
"semver": "^7",
"sqlite3": "^5" "sqlite3": "^5"
}, },
"scripts": { "scripts": {
@@ -30,17 +36,18 @@
"media": "cds watch media", "media": "cds watch media",
"mocha": "npx mocha || echo", "mocha": "npx mocha || echo",
"jest": "npx jest", "jest": "npx jest",
"start": "cds watch fiori",
"test": "npm run jest -- --silent", "test": "npm run jest -- --silent",
"test:hello": "cd hello && npm test" "test:hello": "cd hello && npm test"
}, },
"jest": { "jest": {
"testEnvironment": "node",
"testTimeout": 20000, "testTimeout": 20000,
"testMatch": [ "testMatch": [
"**/*.test.js" "**/*.test.js"
] ]
}, },
"mocha": { "mocha": {
"recursive": true,
"parallel": true "parallel": true
}, },
"license": "SAP SAMPLE CODE LICENSE", "license": "SAP SAMPLE CODE LICENSE",

View File

@@ -4,21 +4,21 @@ const GET = (url) => axios.get('/reviews'+url)
const PUT = (cmd,data) => axios.patch('/reviews'+cmd,data) const PUT = (cmd,data) => axios.patch('/reviews'+cmd,data)
const POST = (cmd,data) => axios.post('/reviews'+cmd,data) const POST = (cmd,data) => axios.post('/reviews'+cmd,data)
const reviews = new Vue ({ const reviews = Vue.createApp ({
el:'#app', data() {
return {
data: { list: [],
list: [], review: undefined,
review: undefined, message: {},
message: {}, Ratings: Object.entries({
Ratings: Object.entries({
5 : '★★★★★', 5 : '★★★★★',
4 : '★★★★', 4 : '★★★★',
3 : '★★★', 3 : '★★★',
2 : '★★', 2 : '★★',
1 : '★', 1 : '★',
}).reverse() }).reverse()
}
}, },
methods: { methods: {
@@ -66,7 +66,7 @@ const reviews = new Vue ({
datetime: (d) => d && new Date(d).toLocaleString(), datetime: (d) => d && new Date(d).toLocaleString(),
}, },
}) }).mount("#app")
// initially fill list of my reviews // initially fill list of my reviews
reviews.fetch() reviews.fetch()

View File

@@ -5,7 +5,7 @@
<title> Capire Reviews </title> <title> Capire Reviews </title>
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css"> <link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> <script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
<style> <style>
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; } .hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
.rating-stars { color:teal } .rating-stars { color:teal }
@@ -18,7 +18,7 @@
<body class="small-container", style="margin-top: 70px;"> <body class="small-container", style="margin-top: 70px;">
<div id='app'> <div id='app'>
<h1> {{ document.title }} </h1> <h1> Capire Reviews </h1>
<input type="text" placeholder="Search..." @input="search"> <input type="text" placeholder="Search..." @input="search">

View File

@@ -1,5 +1,5 @@
subject;rating;reviewer;title;text ID;subject;rating;reviewer;title;text
201;5;adam;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. 1223a37c-9dca-4c4c-a629-9dd93b947864;201;5;bob;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
201;4;bob;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum. ae275d4f-1fb5-491b-ba6e-79b339e1edef;201;4;bob;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum.
207;2;bob;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius. 3fffe4b7-d34f-4fd3-9aab-f609f41539cc;207;2;bob;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius.
251;3;bob;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse. 71c037b3-767d-4f5d-9e90-90b894a2043a;251;3;bob;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse.
1 ID subject rating reviewer title text
2 1223a37c-9dca-4c4c-a629-9dd93b947864 201 5 adam bob Intriguing Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
3 ae275d4f-1fb5-491b-ba6e-79b339e1edef 201 4 bob Fascinating Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum.
4 3fffe4b7-d34f-4fd3-9aab-f609f41539cc 207 2 bob What is this? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius.
5 71c037b3-767d-4f5d-9e90-90b894a2043a 251 3 bob It's dark... Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse.

View File

@@ -32,7 +32,6 @@ entity Likes {
// Auto-fill reviewers and review dates // Auto-fill reviewers and review dates
annotate Reviews with { annotate Reviews with {
reviewer @cds.on.insert:$user; reviewer @cds.on:{insert:$user};
date @cds.on.insert:$now; date @cds.on:{insert:$now,update:$now};
date @cds.on.update:$now;
} }

View File

@@ -7,7 +7,7 @@
"index.cds" "index.cds"
], ],
"dependencies": { "dependencies": {
"@sap/cds": "^5", "@sap/cds": ">=5",
"express": "^4.17.1" "express": "^4.17.1"
}, },
"cds": { "cds": {

View File

@@ -58,23 +58,31 @@ Each sub directory essentially is an individual npm package arranged in an [all-
- [@capire/reviews](reviews) - [@capire/reviews](reviews)
- [@capire/orders](orders) - [@capire/orders](orders)
- [@capire/common](common) - [@capire/common](common)
- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well - [@capire/data-viewer](data-viewer)
- [The Vue.js app](reviews/app/vue) imported from reviews is served as well - [The Vue.js app](bookshop/app/vue) imported from `bookshop` is served as well
- [The Fiori app](orders/app) imported from orders is served as well - [The Vue.js app](reviews/app/vue) imported from `reviews` is served as well
- [The Vue.js app](data-viewer/app/data) imported from `data-viewer` is served as well
- [The Fiori app](orders/app) imported from `orders` is served as well
- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi) - [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)
## [@capire/fiori](fiori) ## [@capire/fiori](fiori)
- [Adds an SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookstore, thereby introducing to: - Adds an SAP Fiori elements application to bookstore, thereby introducing:
- [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files - OData Annotations in `.cds` files
- Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft) - Support for Fiori Draft
- Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help) - Support for Value Helps
- Serving SAP Fiori apps locally - Serving SAP Fiori apps locally
- Fiori Elements V2
- OData V2 using CDS OData V2 Adapter Proxy
- List Report (type `TreeTable`)
- `@sap.hierarchy` annotations
See the [Serving Fiori UIs](https://cap.cloud.sap/docs/advanced/fiori) documentation for more information.
<br> <br>
# All-in-one Monorepo # All-in-one Monorepo
Each sample sub directory essentially is a standard npm package, some with standard npm dependencies to other samples. The root folder's [package.json](package.json) has local links to the sub folders, such that an `npm install` populates a local `node_modules` folder acts like a local npm registry to the individual sample packages. Each sample sub directory essentially is a standard npm package, some with standard npm dependencies to other samples. The root folder's [package.json](package.json) has local links to the sub folders, such that an `npm install` populates a local `node_modules` folder and acts like a local npm registry to the individual sample packages.

View File

@@ -1,28 +1,26 @@
const cds = require('@sap/cds/lib')
const { expect } = cds.test
const { cdr } = cds.ql
const Foo = { name: 'Foo' }
const Books = { name: 'capire.bookshop.Books' }
const STAR = cdr ? '*' : { ref: ['*'] }
const skip = {to:{eql:()=>skip}}
const srv = new cds.Service
let cqn
expect.plain = (cqn) => !cqn.SELECT.one && !cqn.SELECT.distinct ? expect(cqn) : skip
expect.one = (cqn) => !cqn.SELECT.distinct ? expect(cqn) : skip
describe('cds.ql → cqn', () => { describe('cds.ql → cqn', () => {
//
for (let each of ['SELECT', 'SELECT one', 'SELECT distinct']) { const cds = require('@sap/cds/lib')
const { expect } = cds.test
const { cdr } = cds.ql
const Foo = { name: 'Foo' }
const Books = { name: 'capire.bookshop.Books' }
const STAR = cdr ? '*' : { ref: ['*'] }
const skip = {to:{eql:()=>skip}}
const srv = new cds.Service
let cqn
expect.plain = (cqn) => !cqn.SELECT.one && !cqn.SELECT.distinct ? expect(cqn) : skip
expect.one = (cqn) => !cqn.SELECT.distinct ? expect(cqn) : skip
describe.each(['SELECT', 'SELECT one', 'SELECT distinct'])(`%s...`, (each) => {
let SELECT; beforeEach(()=> SELECT = ( let SELECT; beforeEach(()=> SELECT = (
each === 'SELECT distinct' ? cds.ql.SELECT.distinct : each === 'SELECT distinct' ? cds.ql.SELECT.distinct :
each === 'SELECT one' ? cds.ql.SELECT.one : each === 'SELECT one' ? cds.ql.SELECT.one :
cds.ql.SELECT cds.ql.SELECT
)) ))
describe(`${each}...`, () => {
test(`from Foo`, () => { test(`from Foo`, () => {
expect(cqn = SELECT `from Foo`) expect(cqn = SELECT `from Foo`)
@@ -81,6 +79,8 @@ describe('cds.ql → cqn', () => {
.to.eql(SELECT('Foo','Boo').from('Bar')) .to.eql(SELECT('Foo','Boo').from('Bar'))
.to.eql(SELECT(['Foo','Boo']).from('Bar')) .to.eql(SELECT(['Foo','Boo']).from('Bar'))
.to.eql(SELECT `Bar` .columns `Foo, Boo`) .to.eql(SELECT `Bar` .columns `Foo, Boo`)
.to.eql(SELECT `Bar` .columns `{ Foo, Boo }`)
.to.eql(SELECT `Bar` .columns ('{ Foo, Boo }'))
.to.eql(SELECT `Bar` .columns ('Foo','Boo')) .to.eql(SELECT `Bar` .columns ('Foo','Boo'))
.to.eql(SELECT `Bar` .columns (['Foo','Boo'])) .to.eql(SELECT `Bar` .columns (['Foo','Boo']))
.to.eql(SELECT.from `Bar` .columns ('Foo','Boo')) .to.eql(SELECT.from `Bar` .columns ('Foo','Boo'))
@@ -333,7 +333,7 @@ describe('cds.ql → cqn', () => {
}) })
}) })
})} })
describe ('SELECT where...', ()=>{ describe ('SELECT where...', ()=>{
@@ -411,18 +411,67 @@ describe('cds.ql → cqn', () => {
] ]
}}) }})
expect ( const ql_with_groups_fix = !!cds.ql.Query.prototype.flat
SELECT.from(Foo).where({x:1,or:{y:2}}) if (ql_with_groups_fix) {
).to.eql (
CQL`SELECT from Foo where x=1 or y=2` expect (
).to.eql ({ SELECT: { SELECT.from(Foo).where({x:1}).or({y:2}).and({z:3})
from: {ref:['Foo']}, ).to.eql ({ SELECT: {
where: [ from: {ref:['Foo']},
{ref:['x']}, '=', {val:1}, where: [
'or', {ref:['x']}, '=', {val:1},
{ref:['y']}, '=', {val:2} 'or',
] {ref:['y']}, '=', {val:2},
}}) 'and',
{ref:['z']}, '=', {val:3},
]
}})
expect (
SELECT.from(Foo).where({x:1,or:{y:2}}).and({z:3})
).to.eql ({ SELECT: {
from: {ref:['Foo']},
where: [
{xpr:[
{ref:['x']}, '=', {val:1},
'or',
{ref:['y']}, '=', {val:2},
]},
'and',
{ref:['z']}, '=', {val:3},
]
}})
expect (
SELECT.from(Foo).where({a:1}).or({x:1,or:{y:2}}).and({z:3})
).to.eql ({ SELECT: {
from: {ref:['Foo']},
where: [
{ref:['a']}, '=', {val:1},
'or',
{xpr:[
{ref:['x']}, '=', {val:1},
'or',
{ref:['y']}, '=', {val:2},
]},
'and',
{ref:['z']}, '=', {val:3},
]
}})
expect (
{ SELECT: SELECT.from(Foo).where({x:1,or:{y:2}}).SELECT }
).to.eql ({ SELECT: {
from: {ref:['Foo']},
where: [
{ref:['x']}, '=', {val:1},
'or',
{ref:['y']}, '=', {val:2},
]
}})
}
expect ( expect (
SELECT.from(Foo).where({x:1,and:{y:2}}).or({z:3}) SELECT.from(Foo).where({x:1,and:{y:2}}).or({z:3})
@@ -630,6 +679,34 @@ describe('cds.ql → cqn', () => {
// //
}) })
describe(`SELECT for update`, () => {
beforeAll(() => {
delete cds.env.sql.lock_acquire_timeout
})
it('no wait', () => {
const q = SELECT.from('Foo').forUpdate()
expect(q.SELECT.forUpdate).eqls({})
})
it('specific wait', () => {
const q = SELECT.from('Foo').forUpdate({ wait: 1 })
expect(q.SELECT.forUpdate).eqls({ wait: 1 })
})
it('default wait', () => {
cds.env.sql.lock_acquire_timeout = 2
const q = SELECT.from('Foo').forUpdate()
expect(q.SELECT.forUpdate).eqls({ wait: 2 })
})
it('override default', () => {
cds.env.sql.lock_acquire_timeout = 1
const q = SELECT.from('Foo').forUpdate({ wait:-1 })
expect(q.SELECT.forUpdate).eqls({})
})
})
describe(`INSERT...`, () => { describe(`INSERT...`, () => {
test('entries ({a,b}, ...)', () => { test('entries ({a,b}, ...)', () => {
const entries = [{ foo: 1 }, { boo: 2 }] const entries = [{ foo: 1 }, { boo: 2 }]

View File

@@ -1,22 +1,21 @@
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const { expect } = cds.test (
'serve', 'AdminService', '--from', '@capire/bookshop,@capire/common', '--in-memory'
)
describe('Consuming Services locally', () => { describe('cap/samples - Consuming Services locally', () => {
//
const { expect } = cds.test ('@capire/bookshop')
it('bootstrapped the database successfully', ()=>{ it('bootstrapped the database successfully', ()=>{
const { AdminService } = cds.services const { AdminService } = cds.services
const { Authors } = AdminService.entities const { Authors } = AdminService.entities
expect(AdminService).not.to.be.undefined expect(AdminService).to.exist
expect(Authors).not.to.be.undefined expect(Authors).to.exist
}) })
it('supports targets as strings or reflected defs', async () => { it('supports targets as strings or reflected defs', async () => {
const AdminService = await cds.connect.to('AdminService') const AdminService = await cds.connect.to('AdminService')
const { Authors } = AdminService.entities const { Authors } = AdminService.entities
expect (await SELECT.from(Authors)) expect (await SELECT.from(Authors))
.to.eql(await SELECT.from('Authors')) // .to.eql(await SELECT.from('Authors'))
.to.eql(await AdminService.read(Authors)) .to.eql(await AdminService.read(Authors))
.to.eql(await AdminService.read('Authors')) .to.eql(await AdminService.read('Authors'))
.to.eql(await AdminService.run(SELECT.from(Authors))) .to.eql(await AdminService.run(SELECT.from(Authors)))
@@ -34,11 +33,33 @@ describe('Consuming Services locally', () => {
}) })
}) })
}).where(`name like`, 'E%') }).where(`name like`, 'E%')
if (require('semver').gte(cds.version, '5.9.0')) {
expect(authors).to.containSubset([
{
name: 'Emily Brontë',
books: [
{
title: 'Wuthering Heights',
currency: { name: 'British Pound', symbol: '£' },
},
],
},
{
name: 'Edgar Allen Poe',
books: [
{ title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
{ title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
],
},
])
return
}
expect(authors).to.containSubset([ expect(authors).to.containSubset([
{ {
name: 'Emily Brontë', name: 'Emily Brontë',
books: [ books: [
{ {
ID: 201,
title: 'Wuthering Heights', title: 'Wuthering Heights',
currency: { name: 'British Pound', symbol: '£' }, currency: { name: 'British Pound', symbol: '£' },
}, },
@@ -47,8 +68,8 @@ describe('Consuming Services locally', () => {
{ {
name: 'Edgar Allen Poe', name: 'Edgar Allen Poe',
books: [ books: [
{ title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } }, { ID: 251, title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
{ title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } }, { ID: 252, title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
], ],
}, },
]) ])

View File

@@ -1,9 +1,10 @@
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
describe('Custom Handlers', () => { describe('cap/samples - Custom Handlers', () => {
const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
it('should reject out-of-stock orders', async () => { it('should reject out-of-stock orders', async () => {
await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}` await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`

23
test/fiori.test.js Normal file
View File

@@ -0,0 +1,23 @@
const cds = require('@sap/cds/lib')
describe('cap/samples - Fiori APIs - v2', () => {
const { GET, expect, axios } = cds.test ('@capire/fiori', '--with-mocks')
axios.defaults.auth = { username: 'alice', password: 'admin' }
it('serves $metadata documents in v2', async () => {
const { headers, data } = await GET `/v2/browse/$metadata`
expect(headers).to.contain({
'content-type': 'application/xml',
'dataserviceversion': '2.0',
})
expect(data).to.contain('<EntitySet Name="GenreHierarchy" EntityType="CatalogService.GenreHierarchy"/>')
})
it('serves Books in v2', async () => {
const { data } = await GET `/v2/browse/Books`
expect(data).to.containSubset({d:{results:[]}})
expect(data.d.results.length).to.be.greaterThanOrEqual(5)
})
})

View File

@@ -1,7 +1,8 @@
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const { GET, expect } = cds.test (__dirname+'/../hello')
describe('Hello world!', () => { describe('cap/samples - Hello world!', () => {
const { GET, expect } = cds.test (__dirname+'/../hello')
it('should say hello with class impl', async () => { it('should say hello with class impl', async () => {
const {data} = await GET `/say/hello(to='world')` const {data} = await GET `/say/hello(to='world')`
@@ -9,8 +10,7 @@ describe('Hello world!', () => {
}) })
it('should say hello with another impl', async () => { it('should say hello with another impl', async () => {
const cds = require ('@sap/cds') await cds.serve('say').from(cds.model)
cds.serve('say').from(cds.model)
.at('/say-again').in(cds.app) .at('/say-again').in(cds.app)
.with(srv => { .with(srv => {
srv.on('hello', (req) => `Hello again ${req.data.to}!`) srv.on('hello', (req) => `Hello again ${req.data.to}!`)

View File

@@ -1,19 +1,17 @@
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const {expect} = cds.test
// should become cds.compile(...) when cds5 is released describe('cap/samples - Hierarchical Data', ()=>{
const model = cds.compile.to.csn (`
entity Categories {
key ID : Integer;
name : String;
children : Composition of many Categories on children.parent = $self;
parent : Association to Categories;
}
`)
const {Categories:Cats} = model.definitions
const model = CDL`
describe('Hierarchical Data', ()=>{ entity Categories {
key ID : Integer;
name : String;
children : Composition of many Categories on children.parent = $self;
parent : Association to Categories;
}
`
const {Categories:Cats} = model.definitions
const {expect} = cds.test
before ('bootstrap sqlite in-memory db...', async()=>{ before ('bootstrap sqlite in-memory db...', async()=>{
await cds.deploy (model) .to ('sqlite::memory:') await cds.deploy (model) .to ('sqlite::memory:')
@@ -35,6 +33,21 @@ describe('Hierarchical Data', ()=>{
)) ))
it ('supports nested reads', async()=>{ it ('supports nested reads', async()=>{
if (require('semver').gte(cds.version, '5.9.0')) {
expect (await
SELECT.one.from (Cats, c=>{
c.ID, c.name.as('parent'), c.children (c=>{
c.name.as('child')
})
}) .where ({name:'Cat'})
) .to.eql (
{ ID:101, parent:'Cat', children:[
{ child:'Kitty' },
{ child:'Catwoman' },
]}
)
return
}
expect (await expect (await
SELECT.one.from (Cats, c=>{ SELECT.one.from (Cats, c=>{
c.ID, c.name.as('parent'), c.children (c=>{ c.ID, c.name.as('parent'), c.children (c=>{
@@ -50,6 +63,25 @@ describe('Hierarchical Data', ()=>{
}) })
it ('supports deeply nested reads', async()=>{ it ('supports deeply nested reads', async()=>{
if (require('semver').gte(cds.version, '5.9.0')) {
expect (await SELECT.one.from (Cats, c=>{
c.ID, c.name, c.children (
c => { c.name },
{levels:3}
)
}) .where ({name:'Cat'})
) .to.eql (
{ ID:101, name:'Cat', children:[
{ name:'Kitty', children:[
{ name:'Kitty Cat', children:[
{ name:'Aristocat' }, ]}, // level 3
{ name:'Kitty Bat', children:[] }, ]},
{ name:'Catwoman', children:[
{ name:'Catalina', children:[] } ]},
]}
)
return
}
expect (await SELECT.one.from (Cats, c=>{ expect (await SELECT.one.from (Cats, c=>{
c.ID, c.name, c.children ( c.ID, c.name, c.children (
c => { c.name }, c => { c.name },

View File

@@ -0,0 +1,10 @@
{
"cds": {
"requires": {
"db": {
"kind": "sql",
"schema_evolution": "auto"
}
}
}
}

View File

@@ -1,26 +1,14 @@
const cds = require('@sap/cds/lib') describe('cap/samples - Localized Data', () => {
const { GET, expect } = cds.test.run ('serve', __dirname+'/localized-data.cds', '--in-memory')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
describe('Localized Data', () => { const { GET, expect, cds } = require('@sap/cds/lib').test (__dirname)
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
it('serves localized $metadata documents', async () => { it('serves localized $metadata documents', async () => {
const { data } = await GET`/browse/$metadata?sap-language=de` const { data } = await GET(`/browse/$metadata?sap-language=de`, { headers: { 'accept-language': 'de' }})
expect(data).to.contain('<Annotation Term="Common.Label" String="Währung"/>') expect(data).to.contain('<Annotation Term="Common.Label" String="Währung"/>')
}) })
it('supports sap-language param', async () => {
const { data } = await GET(`/browse/Books?$select=title,author` + '&sap-language=de')
expect(data.value).to.containSubset([
{ title: 'Sturmhöhe', author: 'Emily Brontë' },
{ title: 'Jane Eyre', author: 'Charlotte Brontë' },
{ title: 'The Raven', author: 'Edgar Allen Poe' },
{ title: 'Eleonora', author: 'Edgar Allen Poe' },
{ title: 'Catweazle', author: 'Richard Carpenter' },
])
})
it('supports accept-language header', async () => { it('supports accept-language header', async () => {
const { data } = await GET(`/browse/Books?$select=title,author`, { const { data } = await GET(`/browse/Books?$select=title,author`, {
headers: { 'Accept-Language': 'de' }, headers: { 'Accept-Language': 'de' },

View File

@@ -1,14 +1,20 @@
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const { expect } = cds.test const {resolve} = require('path')
const _model = '@capire/reviews'
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
describe('Messaging', ()=>{ describe('cap/samples - Messaging', ()=>{
const { expect } = cds.test
const _model = '@capire/reviews'
const Reviews = 'sap.capire.reviews.Reviews'
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
beforeAll(() => { cds.root = resolve(__dirname, '..') })
afterAll(() => { cds.root = process.cwd() })
it ('should bootstrap sqlite in-memory db', async()=>{ it ('should bootstrap sqlite in-memory db', async()=>{
const db = await cds.deploy (_model) .to ('sqlite::memory:') const db = await cds.deploy (_model) .to ('sqlite::memory:')
await db.delete('Reviews') await db.delete(Reviews)
expect (db.model) .not.undefined expect (db.model) .not.undefined
}) })

View File

@@ -1,9 +1,48 @@
const cds = require('@sap/cds/lib') const cds = require('@sap/cds/lib')
const { GET, expect } = cds.test ('@capire/bookshop')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
describe('OData Protocol', () => { describe('cap/samples - Bookshop APIs', () => {
const { GET, expect, axios } = cds.test ('@capire/bookshop')
axios.defaults.auth = { username: 'alice', password: 'admin' }
// Genres
const Drama = {
"name": "Drama",
"descr": null,
"ID": 11,
"parent_ID": 10
}
const Mystery = {
"name": "Mystery",
"descr": null,
"ID": 16,
"parent_ID": 10
}
const Romance = {
"name": "Romance",
"descr": null,
"ID": 15,
"parent_ID": 10
}
// Currencies
const GBP = {
"name": "British Pound",
"descr": null,
"code": "GBP",
"symbol": "£"
}
const USD = {
"name": "US Dollar",
"descr": null,
"code": "USD",
"symbol": "$"
}
const JPY = {
"name": "Yen",
"descr": null,
"code": "JPY",
"symbol": "¥"
}
it('serves $metadata documents in v4', async () => { it('serves $metadata documents in v4', async () => {
@@ -17,6 +56,16 @@ describe('OData Protocol', () => {
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>') expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
}) })
it('serves ListOfBooks?$expand=genre,currency', async () => {
const { data } = await GET `/browse/ListOfBooks ${{
params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` },
}}`
expect(data.value).to.eql([
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe', genre:Romance, currency:USD },
])
})
it('supports $search in multiple fields', async () => { it('supports $search in multiple fields', async () => {
const { data } = await GET `/browse/Books ${{ const { data } = await GET `/browse/Books ${{
params: { $search: 'Po', $select: `title,author` }, params: { $search: 'Po', $select: `title,author` },
@@ -75,4 +124,16 @@ describe('OData Protocol', () => {
{ ID: 271, title: 'Catweazle' }, { ID: 271, title: 'Catweazle' },
]) ])
}) })
it('serves user info', async () => {
{
const { data } = await GET (`/user/me`)
expect(data).to.containSubset({ id: 'alice', locale:'en' })
}
{
const { data } = await GET (`/user/me`, {auth: { username: 'joe' }})
expect(data).to.containSubset({ id: 'joe', locale:'en' })
}
})
}) })

View File

@@ -1,37 +1,40 @@
const cds = require('@sap/cds/lib')
const { fork } = require('child_process') const { fork } = require('child_process')
const { resolve } = require('path') const { resolve } = require('path')
const Axios = require('axios')
const verbose = process.env.CDS_TEST_VERBOSE const verbose = process.env.CDS_TEST_VERBOSE
// ||true
describe('Local NPM registry', () => { describe('cap/samples - Local NPM registry', () => {
const { expect } = cds.test
// ||true
let registry let registry
let axios let axios
const cwd = resolve(__dirname, '..') const cwd = resolve(__dirname, '..')
beforeAll(async ()=> { before(async ()=> {
const env = Object.assign(process.env, {PORT:'0'}) const env = Object.assign(process.env, {PORT:'0'})
const res = await exec (resolve(cwd, '.registry/server.js'), {cwd, stdio: 'pipe', env}) const res = await exec (resolve(cwd, '.registry/server.js'), {cwd, stdio: 'pipe', env})
registry = res.cp registry = res.cp
axios = Axios.default.create ({ baseURL: res.url, validateStatus: (status)=>status<500 }) axios = require('axios').create ({ baseURL: res.url, validateStatus: (status)=>status<500 })
}) })
afterAll(() => { registry.kill() }) after(done => { registry.once('exit',done); registry.kill() })
for (const mod of ['bookshop','fiori','orders','reviews']) { for (const mod of ['bookshop', 'data-viewer', 'fiori','orders','reviews']) {
it(`should serve ${mod}`, async () => { it(`should serve ${mod}`, async () => {
const resp = await axios.get(`/@capire/${mod}`) const resp = await axios.get(`/@capire/${mod}`)
expect(resp.data).toMatchObject({name: `@capire/${mod}`, versions:{}}) expect(resp.data).to.containSubset({name: `@capire/${mod}`, versions:{}})
const versions = Object.values(resp.data.versions) const versions = Object.values(resp.data.versions)
await axios.get(versions[0].dist.tarball) await axios.get(versions[0].dist.tarball)
}) })
} }
it(`should return 404 for unknown packages`, async () => { it(`should return 404 for unknown packages`, async () => {
let resp = await axios.get(`/@capire/foo`) let resp = await axios.get(`/@capire/foo`)
expect(resp.status).toEqual(404) expect(resp.status).to.equal(404)
resp = await axios.get(`/foo`) resp = await axios.get(`/foo`)
expect(resp.status).toEqual(404) expect(resp.status).to.equal(404)
}) })
}) })