Skip to content

Commit

Permalink
feat: otter sdk training - how to use the otter sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
sdo-1A committed Nov 21, 2024
1 parent 8094400 commit 97cfa4c
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 4 deletions.
5 changes: 5 additions & 0 deletions apps/showcase/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
35 changes: 34 additions & 1 deletion apps/showcase/src/assets/trainings/sdk/program.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Serializer<any>>[] = [/* 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<RuntimeChecks> = {\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": {
Expand All @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<table class="table">
<caption align="top">
First 10 pets of status <b>available</b> from the Swagger Petstore
</caption>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
@for (pet of pets(); track pet.id; let index = $index) {
<tr>
<td>{{index + 1}}</td>
<td><!-- Name of pet --></td>
<td><!-- Status of pet --></td>
</tr>
}
</tbody>
</table>
Original file line number Diff line number Diff line change
@@ -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<Pet[]>([]);
public readonly pets = this.#pets.asReadonly();

constructor() {
this.setPets();
}

public setPets() {
/* Get the first 10 pets whose status is 'available' */
}
}
Original file line number Diff line number Diff line change
@@ -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}
]
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
### Objective
For this example, you will use the public <a href="https://petstore3.swagger.io/" target="_blank">Swagger Petstore project API</a>.
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<table class="table">
<caption align="top">
First 10 pets of status <b>available</b> from the Swagger Petstore
</caption>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
@for (pet of pets(); track pet.id; let index = $index) {
<tr>
<td>{{index + 1}}</td>
<td>{{pet.name}}</td>
<td>{{pet.status}}</td>
</tr>
}
</tbody>
</table>
Original file line number Diff line number Diff line change
@@ -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<Pet[]>([]);
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));
}
}
Original file line number Diff line number Diff line change
@@ -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}
]
};
10 changes: 10 additions & 0 deletions packages/@o3r-training/showcase-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,22 @@
},
"./openapi.yml": {
"default": "./openapi.yml"
},
"./structure/src.json": {
"default": "./structure/src.json"
},
"./structure/spec.json": {
"default": "./structure/src.json"
}
},
"scripts": {
"clean": "rimraf test/ test-dev/ dist/ dist-dev/ dist-test/ build/",
"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",
Expand Down Expand Up @@ -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",
Expand Down
17 changes: 16 additions & 1 deletion packages/@o3r-training/showcase-sdk/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"targets": {
"build": {
"executor": "nx:noop",
"dependsOn": ["compile"]
"dependsOn": ["compile", "extract-folder-structure"]
},
"compile": {
"executor": "nx:run-script",
Expand Down Expand Up @@ -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"]
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 97cfa4c

Please sign in to comment.