From 97cfa4ce6a2d2c6d0974b1b79f2c1507a6bd5ea3 Mon Sep 17 00:00:00 2001 From: Salome DO Date: Wed, 20 Nov 2024 15:19:29 +0100 Subject: [PATCH] feat: otter sdk training - how to use the otter sdk --- apps/showcase/project.json | 5 +++ .../src/assets/trainings/sdk/program.json | 35 ++++++++++++++++++- .../sdk/shared/monorepo-template.json | 4 +-- .../exercise/app.component.html | 21 +++++++++++ .../typescript-sdk/exercise/app.component.ts | 27 ++++++++++++++ .../typescript-sdk/exercise/app.config.ts | 21 +++++++++++ .../sdk/steps/typescript-sdk/instructions.md | 25 +++++++++++++ .../solution/app.component.html | 21 +++++++++++ .../typescript-sdk/solution/app.component.ts | 29 +++++++++++++++ .../typescript-sdk/solution/app.config.ts | 29 +++++++++++++++ .../@o3r-training/showcase-sdk/package.json | 10 ++++++ .../@o3r-training/showcase-sdk/project.json | 17 ++++++++- yarn.lock | 1 + 13 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.component.html create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.component.ts create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.config.ts create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/instructions.md create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.component.html create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.component.ts create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.config.ts diff --git a/apps/showcase/project.json b/apps/showcase/project.json index 4000325b8b..e5de60f899 100644 --- a/apps/showcase/project.json +++ b/apps/showcase/project.json @@ -96,6 +96,11 @@ "glob": "*.json", "input": "packages/@o3r-training/training-sdk/dist/structure", "output": "/assets/@o3r-training/training-sdk/structure" + }, + { + "glob": "*.json", + "input": "packages/@o3r-training/showcase-sdk/dist/structure", + "output": "/assets/@o3r-training/showcase-sdk/structure" } ], "styles": [ diff --git a/apps/showcase/src/assets/trainings/sdk/program.json b/apps/showcase/src/assets/trainings/sdk/program.json index 94fa110b2f..cfdfb412f7 100644 --- a/apps/showcase/src/assets/trainings/sdk/program.json +++ b/apps/showcase/src/assets/trainings/sdk/program.json @@ -7,7 +7,40 @@ }, { "stepTitle": "How to use the Otter SDK?", - "htmlContentUrl": "./steps/typescript-sdk/instructions.md" + "htmlContentUrl": "./steps/typescript-sdk/instructions.md", + "filesConfiguration": { + "name": "how-to-use-otter-sdk", + "startingFile": "apps/tutorial-app/src/app/app.component.ts", + "urls": [ + { + "path": ".", + "contentUrl": "./shared/monorepo-template.json" + }, + { + "path": "./libs/sdk", + "contentUrl": "@o3r-training/showcase-sdk/structure/spec.json" + }, + { + "path": "./libs/sdk/src", + "contentUrl": "@o3r-training/showcase-sdk/structure/src.json" + }, + { + "path": "./apps/tutorial-app/src/app", + "contentUrl": "./steps/typescript-sdk/exercise.json" + } + ], + "solutionUrls": [ + { + "path": "./apps/tutorial-app/src/app", + "contentUrl": "./steps/typescript-sdk/solution.json" + } + ], + "mode": "interactive", + "commands": [ + "npm install --legacy-peer-deps --ignore-scripts --force", + "npm run ng run tutorial-app:serve" + ] + } }, { "stepTitle": "Customize your fetch client with plugins", diff --git a/apps/showcase/src/assets/trainings/sdk/shared/monorepo-template.json b/apps/showcase/src/assets/trainings/sdk/shared/monorepo-template.json index a28c812aa2..f78dac2e61 100644 --- a/apps/showcase/src/assets/trainings/sdk/shared/monorepo-template.json +++ b/apps/showcase/src/assets/trainings/sdk/shared/monorepo-template.json @@ -65,7 +65,7 @@ }, "app.config.ts": { "file": { - "contents": "import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';\nimport { provideRouter } from '@angular/router';\n\nimport { routes } from './app.routes';\nimport { StorageSync } from '@o3r/store-sync';\nimport { RuntimeChecks, StoreModule } from '@ngrx/store';\nimport { Serializer } from '@o3r/core';\nimport { environment, additionalModules } from '../environments/environment';\nimport { EffectsModule } from '@ngrx/effects';\nimport { prefersReducedMotion } from '@o3r/application';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\n\nconst localStorageStates: Record>[] = [/* Store to register in local storage */];\nconst storageSync = new StorageSync({\n keys: localStorageStates, rehydrate: true\n});\n\nconst rootReducers = {\n \n};\n\nconst metaReducers = [storageSync.localStorageSync()];\nconst runtimeChecks: Partial = {\n strictActionImmutability: false,\n strictActionSerializability: false,\n strictActionTypeUniqueness: !environment.production,\n strictActionWithinNgZone: !environment.production,\n strictStateImmutability: !environment.production,\n strictStateSerializability: false\n};\n\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideZoneChangeDetection({ eventCoalescing: true }),\n provideRouter(routes),\n importProvidersFrom(EffectsModule.forRoot([])),\n importProvidersFrom(StoreModule.forRoot(rootReducers, {metaReducers, runtimeChecks})),\n importProvidersFrom(additionalModules),\n importProvidersFrom(BrowserAnimationsModule.withConfig({disableAnimations: prefersReducedMotion()}))\n ]\n};\n" + "contents": "import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';\nimport { provideRouter } from '@angular/router';\n\nimport { routes } from './app.routes';\nimport { prefersReducedMotion } from '@o3r/application';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideZoneChangeDetection({ eventCoalescing: true }),\n provideRouter(routes),\n importProvidersFrom(BrowserAnimationsModule.withConfig({disableAnimations: prefersReducedMotion()}))\n ]\n};\n" } }, "app.routes.ts": { @@ -79,7 +79,7 @@ "directory": { "environment.development.ts": { "file": { - "contents": "import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\nexport const environment = {\n production: false,\n};\n\nexport const additionalModules = [\n StoreDevtoolsModule.instrument()\n];\n" + "contents": "export const environment = {\n production: false,\n};\n\nexport const additionalModules = [];\n" } }, "environment.ts": { diff --git a/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.component.html b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.component.html new file mode 100644 index 0000000000..9dd3a42c61 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.component.html @@ -0,0 +1,21 @@ + + + + + + + + + + + @for (pet of pets(); track pet.id; let index = $index) { + + + + + + } + +
+ First 10 pets of status available from the Swagger Petstore +
#NameStatus
{{index + 1}}
diff --git a/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.component.ts b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.component.ts new file mode 100644 index 0000000000..2ac617542c --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.component.ts @@ -0,0 +1,27 @@ +import { Component, inject, signal } from '@angular/core'; +import { PetApi } from '../../../../libs/sdk/src/api'; +import type { Pet } from '../../../../libs/sdk/src/models'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [], + templateUrl: './app.component.html', + styleUrl: './app.component.scss' +}) +export class AppComponent { + /** Title of the application */ + public title = 'tutorial-app'; + + private readonly petStoreApi = inject(PetApi); + readonly #pets = signal([]); + public readonly pets = this.#pets.asReadonly(); + + constructor() { + this.setPets(); + } + + public setPets() { + /* Get the first 10 pets whose status is 'available' */ + } +} diff --git a/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.config.ts b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.config.ts new file mode 100644 index 0000000000..16393d4285 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/exercise/app.config.ts @@ -0,0 +1,21 @@ +import { ApiFetchClient } from '@ama-sdk/client-fetch'; +import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { provideRouter } from '@angular/router'; +import { prefersReducedMotion } from '@o3r/application'; +import { PetApi } from '../../../../libs/sdk/src/api'; +import { routes } from './app.routes'; + + +function petApiFactory() { + /* Create an ApiFetchClient and return a PetApi object */ +} + +export const appConfig: ApplicationConfig = { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + importProvidersFrom(BrowserAnimationsModule.withConfig({disableAnimations: prefersReducedMotion()})), + {provide: PetApi, useFactory: petApiFactory} + ] +}; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/instructions.md b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/instructions.md new file mode 100644 index 0000000000..5a777d65ea --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/instructions.md @@ -0,0 +1,25 @@ +### Objective +For this example, you will use the public Swagger Petstore project API. +You will perform a simple call and retrieve a list of pets from an SDK programmatically generated from their public specification. +Since this step requires a Java setup, the generation has already been done for you. You will just need to integrate the Otter SDK client and perform your call. + +### Exercise + +#### Creation of fetch client in the application configuration +Let's create a fetch client in your application and use it to access your API.\ +Here are a couple of steps and hints to help you: +- In the file `app.config.ts`, you will create an API client object of type `ApiFetchClient` from `@ama-sdk/client-fetch` in the existing function `petApiFactory()`. +- The constructor of `ApiFetchClient` requires some options, including the `basePath` which should be the Swagger Petstore API: https://petstore3.swagger.io/api/v3 +- The configuration variable can then be used to create an API object of type `PetApi` from the SDK. +- The function `petApiFactory()` should return this `PetApi` object, which has been added to the providers of the application configuration. + +#### Using the API object to perform an HTTP request +Now, you can call the API object to perform the HTTP request you are looking for.\ +As you can see in `app.component.ts`, the `PetApi` has been injected and is ready to be used.\ +For this exercise, you will look for the pets that are of status **available** and display in the UI the names of the first ten pets. + +> [!TIP] +> Have a look at the `sdk/src/api/pet/pet.api.ts` file and look for `findPetsByStatus`. + +#### Solution +You can check out the exercise solution or compare your answer to the result of the petstore API: https://petstore3.swagger.io/api/v3/pet/findByStatus?status=available diff --git a/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.component.html b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.component.html new file mode 100644 index 0000000000..e70bd77e1c --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.component.html @@ -0,0 +1,21 @@ + + + + + + + + + + + @for (pet of pets(); track pet.id; let index = $index) { + + + + + + } + +
+ First 10 pets of status available from the Swagger Petstore +
#NameStatus
{{index + 1}}{{pet.name}}{{pet.status}}
diff --git a/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.component.ts b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.component.ts new file mode 100644 index 0000000000..ccc8805d4b --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.component.ts @@ -0,0 +1,29 @@ +import { Component, inject, signal } from '@angular/core'; +import { PetApi } from '../../../../libs/sdk/src/api'; +import type { Pet } from '../../../../libs/sdk/src/models'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [], + templateUrl: './app.component.html', + styleUrl: './app.component.scss' +}) +export class AppComponent { + /** Title of the application */ + public title = 'tutorial-app'; + + private readonly petStoreApi = inject(PetApi); + readonly #pets = signal([]); + public readonly pets = this.#pets.asReadonly(); + + constructor() { + void this.setPets(); + } + + public async setPets() { + /* Get the first 10 pets whose status is 'available' */ + const pets = await this.petStoreApi.findPetsByStatus({status: 'available'}); + this.#pets.set(pets.slice(0, 10)); + } +} diff --git a/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.config.ts b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.config.ts new file mode 100644 index 0000000000..4da01bdbd9 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/typescript-sdk/solution/app.config.ts @@ -0,0 +1,29 @@ +import { ApiFetchClient } from '@ama-sdk/client-fetch'; +import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { provideRouter } from '@angular/router'; +import { prefersReducedMotion } from '@o3r/application'; +import { PetApi } from '../../../../libs/sdk/src/api'; +import { routes } from './app.routes'; + + +function petApiFactory() { + /* Create an ApiFetchClient and return a PetApi object */ + const apiFetchClient = new ApiFetchClient( + { + basePath: 'https://petstore3.swagger.io/api/v3', + requestPlugins: [], + fetchPlugins: [] + } + ); + return new PetApi(apiFetchClient); +} + +export const appConfig: ApplicationConfig = { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + importProvidersFrom(BrowserAnimationsModule.withConfig({disableAnimations: prefersReducedMotion()})), + {provide: PetApi, useFactory: petApiFactory} + ] +}; diff --git a/packages/@o3r-training/showcase-sdk/package.json b/packages/@o3r-training/showcase-sdk/package.json index 9e2c590dec..1b10c36875 100644 --- a/packages/@o3r-training/showcase-sdk/package.json +++ b/packages/@o3r-training/showcase-sdk/package.json @@ -33,6 +33,12 @@ }, "./openapi.yml": { "default": "./openapi.yml" + }, + "./structure/src.json": { + "default": "./structure/src.json" + }, + "./structure/spec.json": { + "default": "./structure/src.json" } }, "scripts": { @@ -40,6 +46,9 @@ "lint:ci": "eslint '**/*[jt]s' --quiet --format junit --output-file ./dist-lint/result.xml", "lint": "eslint '**/*[jt]s' --cache", "start": "tsc-watch -b tsconfigs/esm2020 --noClear --onFirstSuccess \"yarn run files:pack --watch\"", + "extract": "yarn run extract-src && yarn run extract-spec", + "extract-src": "o3r-extract-folder-structure --files \"src\" -o dist/structure/src.json", + "extract-spec": "o3r-extract-folder-structure --files \"./openapi.yml\",\"./openapitools.json\" -o dist/structure/spec.json", "build": "yarn run build:cjs && yarn run build:esm2015 && yarn run build:esm2020 && cpy openapi.yml ./dist && yarn run files:pack", "build:cjs": "swc src -d dist/cjs -C module.type=commonjs -q", "build:esm2015": "swc src -d dist/esm2015 -C module.type=es6 -q", @@ -81,6 +90,7 @@ "@commitlint/config-conventional": "^19.0.0", "@nx/eslint-plugin": "~19.5.0", "@nx/jest": "~19.5.0", + "@o3r-training/training-tools": "workspace:^", "@o3r/eslint-config-otter": "workspace:^", "@o3r/eslint-plugin": "workspace:^", "@openapitools/openapi-generator-cli": "~2.15.0", diff --git a/packages/@o3r-training/showcase-sdk/project.json b/packages/@o3r-training/showcase-sdk/project.json index d497bd33bf..7649ff48d4 100644 --- a/packages/@o3r-training/showcase-sdk/project.json +++ b/packages/@o3r-training/showcase-sdk/project.json @@ -7,7 +7,7 @@ "targets": { "build": { "executor": "nx:noop", - "dependsOn": ["compile"] + "dependsOn": ["compile", "extract-folder-structure"] }, "compile": { "executor": "nx:run-script", @@ -36,6 +36,21 @@ "options": { "command": "npm publish packages/@o3r-training/showcase-sdk/dist" } + }, + "extract-folder-structure": { + "cache": true, + "executor": "nx:run-script", + "options": { + "script": "extract" + }, + "inputs": [ + "source", + "^cli", + "{projectRoot}/package.json", + "{projectRoot}/openapi.yml" + ], + "outputs": ["{projectRoot}/dist/structure"], + "dependsOn": ["^build", "compile"] } }, "tags": ["showcase"] diff --git a/yarn.lock b/yarn.lock index a47b318c47..fc8cdeaa09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7033,6 +7033,7 @@ __metadata: "@commitlint/config-conventional": "npm:^19.0.0" "@nx/eslint-plugin": "npm:~19.5.0" "@nx/jest": "npm:~19.5.0" + "@o3r-training/training-tools": "workspace:^" "@o3r/eslint-config-otter": "workspace:^" "@o3r/eslint-plugin": "workspace:^" "@openapitools/openapi-generator-cli": "npm:~2.15.0"