From d90ec6fb5a98818ecc127c28b6a7a1d6c179c29a Mon Sep 17 00:00:00 2001 From: Salome DO Date: Tue, 26 Nov 2024 14:52:52 +0100 Subject: [PATCH] feat: otter sdk training - plugins --- .../src/assets/trainings/sdk/program.json | 37 ++++++++++++++- .../steps/plugins/exercise/app.component.html | 21 +++++++++ .../steps/plugins/exercise/app.component.ts | 28 +++++++++++ .../sdk/steps/plugins/exercise/app.config.ts | 46 +++++++++++++++++++ .../sdk/steps/plugins/instructions.md | 27 +++++++++++ .../sdk/steps/plugins/solution/app.config.ts | 45 ++++++++++++++++++ .../typescript-sdk/solution/app.component.ts | 8 ++-- 7 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.component.html create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.component.ts create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.config.ts create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/plugins/instructions.md create mode 100644 apps/showcase/src/assets/trainings/sdk/steps/plugins/solution/app.config.ts diff --git a/apps/showcase/src/assets/trainings/sdk/program.json b/apps/showcase/src/assets/trainings/sdk/program.json index cfdfb412f7..beb897b52f 100644 --- a/apps/showcase/src/assets/trainings/sdk/program.json +++ b/apps/showcase/src/assets/trainings/sdk/program.json @@ -44,7 +44,40 @@ }, { "stepTitle": "Customize your fetch client with plugins", - "htmlContentUrl": "./steps/plugins/instructions.md" + "htmlContentUrl": "./steps/plugins/instructions.md", + "filesConfiguration": { + "name": "plugins", + "startingFile": "apps/tutorial-app/src/app/app.config.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/plugins/exercise.json" + } + ], + "solutionUrls": [ + { + "path": "./apps/tutorial-app/src/app", + "contentUrl": "./steps/plugins/solution.json" + } + ], + "mode": "interactive", + "commands": [ + "npm install --legacy-peer-deps --ignore-scripts --force", + "npm run ng run tutorial-app:serve" + ] + } }, { "stepTitle": "Integrate your SDK in Angular", @@ -113,7 +146,7 @@ "urls": [ { "path": ".", - "contentUrl": "./shared/monorepo-with-app.json" + "contentUrl": "./shared/monorepo-template.json" }, { "path": "./apps/app/src/app", diff --git a/apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.component.html b/apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.component.html new file mode 100644 index 0000000000..95494c76a7 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.component.html @@ -0,0 +1,21 @@ + + + + + + + + + + + @for (pet of pets(); track pet.id; let index = $index) { + + + + + + } + +
+ First 10 pets with the status available from the Swagger Petstore +
#NameStatus
{{ index + 1 }}{{ pet.name }}{{ pet.status }}
diff --git a/apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.component.ts b/apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.component.ts new file mode 100644 index 0000000000..10e026745b --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.component.ts @@ -0,0 +1,28 @@ +import { Component, inject, signal } from '@angular/core'; +import { type Pet, PetApi } from 'sdk'; + +@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); + private readonly petsWritable = signal([]); + public readonly pets = this.petsWritable.asReadonly(); + + constructor() { + void this.setPets(); + } + + public async setPets() { + /* Get the first 10 pets whose status is 'available' */ + const availablePets = await this.petStoreApi.findPetsByStatus({status: 'available'}); + this.petsWritable.set(availablePets.slice(0, 10)); + } +} diff --git a/apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.config.ts b/apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.config.ts new file mode 100644 index 0000000000..a7d1e2534a --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/plugins/exercise/app.config.ts @@ -0,0 +1,46 @@ +import { ApiFetchClient } from '@ama-sdk/client-fetch'; +import { PluginRunner, RequestOptions, RequestPlugin } from '@ama-sdk/core'; +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { Pet, PetApi } from 'sdk'; +import { routes } from './app.routes'; + +class MockInterceptRequest implements RequestPlugin { + public load(): PluginRunner { + return { + transform: async () => { + // TODO Create a mock response of type Pet[] + // TODO Store the stringified object in a Blob and create an object URL + // TODO Replace the original base path with the URL of the Blob + const basePath = '/target your blob resource/'; + + return { + method: 'GET', + headers: new Headers(), + basePath + } + } + }; + } +} + +// TODO Add your plugin to the Api Fetch Client +function petApiFactory() { + const apiFetchClient = new ApiFetchClient( + { + basePath: 'https://petstore3.swagger.io/api/v3', + requestPlugins: [], + replyPlugins: [], + fetchPlugins: [] + } + ); + return new PetApi(apiFetchClient); +} + +export const appConfig: ApplicationConfig = { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + {provide: PetApi, useFactory: petApiFactory} + ] +}; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/plugins/instructions.md b/apps/showcase/src/assets/trainings/sdk/steps/plugins/instructions.md new file mode 100644 index 0000000000..2d8778d4e4 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/plugins/instructions.md @@ -0,0 +1,27 @@ +Thanks to the `@ama-sdk/core` plugins, you can intercept the API requests and responses to transform their data or +perform actions such as authentication, logging, etc. + +These plugins will be applied before sending all the API requests and after the reception of the responses. + +### Objective +In this tutorial, you will configure the `PetApi` to create a plugin whose purpose is to intercept the request before it is sent and serve a mocked response instead. + +### Prerequisite +- The package `@ama-sdk/core` needs to be installed (which has already been done for you). + +### Exercise +Create your own plugin that implements the `RequestPlugin` interface to mock the request plugin. +The exercise already contains the beginning of the code to inspire you. Here are some hints to help you as well: +- First, create a mock response of type `Pet[]` from the SDK. +- This object should be stringified and used to create a Blob. +- Use the created `Blob` object to replace the base path (**hint**: check out the method URL.createObjectURL()). + +If implemented correctly, you should now see your mock response in the table of available pets, instead of the response from the Swagger Petstore. + +> [!NOTE] +> Don't forget to check the exercise solution! + +> [!TIP] +> The Otter framework provides several mock intercept plugins, including a mock intercept request plugin, which you can look further into if wanted to compare to this exercise. +> For more information, you can find all the project plugins +> in the @ama-sdk/core package of the Otter framework. diff --git a/apps/showcase/src/assets/trainings/sdk/steps/plugins/solution/app.config.ts b/apps/showcase/src/assets/trainings/sdk/steps/plugins/solution/app.config.ts new file mode 100644 index 0000000000..9520d9d332 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/plugins/solution/app.config.ts @@ -0,0 +1,45 @@ +import { ApiFetchClient } from '@ama-sdk/client-fetch'; +import { PluginRunner, RequestOptions, RequestPlugin } from '@ama-sdk/core'; +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { Pet, PetApi } from 'sdk'; +import { routes } from './app.routes'; + +class MockInterceptRequest implements RequestPlugin { + public load(): PluginRunner { + return { + transform: async () => { + const mockData: Pet[] = [{ name : "mockPetName", photoUrls: ["mockPhotoUrl"], status: "available"}]; + const text = JSON.stringify(mockData); + const blob = new Blob([text], { type: 'application/json' }); + const basePath = URL.createObjectURL(blob); + + return { + method: 'GET', + basePath, + headers: new Headers() + }; + } + }; + } +} + +function petApiFactory() { + const apiFetchClient = new ApiFetchClient( + { + basePath: 'https://petstore3.swagger.io/api/v3', + requestPlugins: [new MockInterceptRequest()], + replyPlugins: [], + fetchPlugins: [] + } + ); + return new PetApi(apiFetchClient); +} + +export const appConfig: ApplicationConfig = { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + {provide: PetApi, useFactory: petApiFactory} + ] +}; 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 index 0b3c883d73..10e026745b 100644 --- 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 @@ -13,8 +13,8 @@ export class AppComponent { public title = 'tutorial-app'; private readonly petStoreApi = inject(PetApi); - readonly #pets = signal([]); - public readonly pets = this.#pets.asReadonly(); + private readonly petsWritable = signal([]); + public readonly pets = this.petsWritable.asReadonly(); constructor() { void this.setPets(); @@ -22,7 +22,7 @@ export class AppComponent { 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)); + const availablePets = await this.petStoreApi.findPetsByStatus({status: 'available'}); + this.petsWritable.set(availablePets.slice(0, 10)); } }