-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: otter sdk training - integration in angular
- Loading branch information
Showing
9 changed files
with
332 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 3 additions & 3 deletions
6
apps/showcase/src/assets/trainings/sdk/shared/monorepo-template.json
Large diffs are not rendered by default.
Oops, something went wrong.
30 changes: 30 additions & 0 deletions
30
apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<div class="row m-2"> | ||
<div class="col"> | ||
<button class="btn btn-outline-primary justify-content-center" (click)="getAvailablePets()">Get Available Pets</button> | ||
<table class="table"> | ||
<caption align="top"> | ||
Pets with the 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> | ||
</div> | ||
<div class="col"> | ||
<button class="btn btn-outline-primary" (click)="getPetInventory()">Get Inventory</button> | ||
<div>{{petsInventory() | json}}</div> | ||
</div> | ||
</div> |
38 changes: 38 additions & 0 deletions
38
apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { JsonPipe } from '@angular/common'; | ||
import { Component, inject, signal } from '@angular/core'; | ||
import { ApiFactoryService } from '@o3r/apis-manager'; | ||
import { type Pet, PetApi, StoreApi } from 'sdk'; | ||
|
||
@Component({ | ||
selector: 'app-root', | ||
standalone: true, | ||
imports: [JsonPipe], | ||
templateUrl: './app.component.html', | ||
styleUrl: './app.component.scss' | ||
}) | ||
export class AppComponent { | ||
/** Title of the application */ | ||
public title = 'tutorial-app'; | ||
|
||
// TODO Inject the ApiFactoryService and get the corresponding APIs | ||
private readonly petApi = inject(PetApi); | ||
private readonly storeApi = inject(StoreApi); | ||
|
||
private readonly petsWritable = signal<Pet[]>([]); | ||
public readonly pets = this.petsWritable.asReadonly(); | ||
|
||
private readonly petsInventoryWritable = signal<{ [key: string]: number }>({}); | ||
public readonly petsInventory = this.petsInventoryWritable.asReadonly(); | ||
|
||
/** Get the pets whose status is 'available' */ | ||
public async getAvailablePets() { | ||
const availablePets = await this.petApi.findPetsByStatus({status: 'available'}); | ||
this.petsWritable.set(availablePets); | ||
} | ||
|
||
/** Get the pets inventory */ | ||
public async getPetInventory() { | ||
const inventory = await this.storeApi.getInventory({}); | ||
this.petsInventoryWritable.set(inventory); | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.config.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { ApiFetchClient } from '@ama-sdk/client-fetch'; | ||
import { ApiClient, PluginRunner, RequestOptions, RequestPlugin } from '@ama-sdk/core'; | ||
import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'; | ||
import { provideRouter } from '@angular/router'; | ||
import { ApiManager, ApiManagerModule } from '@o3r/apis-manager'; | ||
import { PetApi, StoreApi } from 'sdk'; | ||
import { routes } from './app.routes'; | ||
|
||
class MockInterceptRequest implements RequestPlugin { | ||
public load(): PluginRunner<RequestOptions, RequestOptions> { | ||
return { | ||
transform: async (data: RequestOptions) => { | ||
const mockData = data.api?.apiName === 'PetApi' | ||
? [{ name : "mockPetName", photoUrls: ["mockPhotoUrl"], status: "available"}] | ||
: { mockPropertyName : "mockPropertyValue"}; | ||
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() | ||
}; | ||
} | ||
}; | ||
} | ||
} | ||
|
||
class RequestAlertPlugin implements RequestPlugin { | ||
public load(): PluginRunner<RequestOptions, RequestOptions> { | ||
return { | ||
transform: (data: RequestOptions) => { | ||
alert(JSON.stringify(data)); | ||
return data; | ||
} | ||
}; | ||
} | ||
} | ||
|
||
function petApiFactory() { | ||
const apiFetchClient = new ApiFetchClient( | ||
{ | ||
basePath: 'https://petstore3.swagger.io/api/v3', | ||
requestPlugins: [new MockInterceptRequest()], | ||
replyPlugins: [], | ||
fetchPlugins: [] | ||
} | ||
); | ||
return new PetApi(apiFetchClient); | ||
} | ||
|
||
function storeApiFactory() { | ||
const apiFetchClient = new ApiFetchClient( | ||
{ | ||
basePath: 'https://petstore3.swagger.io/api/v3', | ||
requestPlugins: [new MockInterceptRequest()], | ||
replyPlugins: [], | ||
fetchPlugins: [] | ||
} | ||
); | ||
return new StoreApi(apiFetchClient); | ||
} | ||
|
||
// TODO Initialize apiConfig with ApiFetchClient | ||
|
||
// TODO Add the configuration override for a specific API | ||
|
||
export const appConfig: ApplicationConfig = { | ||
providers: [ | ||
provideZoneChangeDetection({ eventCoalescing: true }), | ||
provideRouter(routes), | ||
// TODO Replace the api factory providers with the ApiManagerModule | ||
{provide: PetApi, useFactory: petApiFactory}, | ||
{provide: StoreApi, useFactory: storeApiFactory} | ||
] | ||
}; |
69 changes: 69 additions & 0 deletions
69
apps/showcase/src/assets/trainings/sdk/steps/angular-integration/instructions.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
When dealing with an Angular project, you need to ensure that your `ApiClient` will be shared across your application. | ||
The Otter framework provides the `ApiManager` service to manage your API collection. | ||
|
||
### Objective | ||
- Leverage the `ApiManager` service to access two different clients to retrieve the list of available pets and get the Swagger Petstore inventory. | ||
- Add a plugin to the `StoreApi` to alert each time a call is sent. | ||
|
||
### Prerequisite | ||
- The package `@o3r/apis-manager` needs to be installed (which has already been done for you). | ||
|
||
### Exercise | ||
|
||
#### Existing plugins | ||
As you can see in the `app.config.ts` file, a plugin `RequestAlertPlugin` has been created which displays an alert box when the API receives a request. | ||
There is also a `MockInterceptRequest` plugin, similar to the one created in the previous step, to mock the request plugin. | ||
|
||
#### Integrate the ApiManagerModule with default configuration | ||
First, create the variable `apiConfig` with the properties of `ApiFetchClient`. This default configuration should contain the `MockInterceptRequest` | ||
plugin (similar to the exercise in the previous step). Here is a template to get you started: | ||
```typescript | ||
// Default configuration for all the APIs defined in the ApiManager | ||
const apiConfig: ApiClient = new ApiFetchClient( | ||
{ | ||
// Properties of ApiFetchClient | ||
} | ||
); | ||
``` | ||
|
||
Next, create the `apiManager` variable like this: | ||
```typescript | ||
const apiManager = new ApiManager(apiConfig); | ||
``` | ||
|
||
You can now integrate the `ApiManagerModule` in the providers of your `ApplicationConfig`. You can use the following lines to guide you: | ||
```typescript | ||
export const appConfig: ApplicationConfig = { | ||
providers: [importProvidersFrom(ApiManagerModule.forRoot(apiManager))] | ||
}; | ||
``` | ||
|
||
> [!NOTE] | ||
> This integration should replace the previous providers of `PetApi` and `StoreApi` using the factories `petApiFactory` and `storeApiFactory` | ||
> in the `ApplicationConfig`. | ||
Then, checkout the `app.component.ts` file and update the variables `petApi` and `storeApi` by injecting the `ApiFactoryService` to use your | ||
unique instance of the `StoreApi` and `PetApi`. | ||
|
||
Now, when clicking the **Get Available Pets** button, your table should be updated with the mock value of available pets and when clicking the | ||
**Get Inventory** button, you should see the mock value of inventory. | ||
|
||
#### Override of the default configuration | ||
Let's override the default configuration by updating `apiManager` and configuring it to use the `RequestAlertPlugin` in the `StoreApi`. | ||
You can inspire yourself with the following lines: | ||
|
||
```typescript | ||
const apiManager = new ApiManager(apiConfig, { | ||
// Configuration override for a specific API | ||
StoreApi: new ApiFetchClient({ | ||
// Properties of ApiFetchClient | ||
}) | ||
}); | ||
``` | ||
|
||
Let's see how this new configuration override impacts the default configuration. | ||
When clicking the **Get Available Pets** button, your table should still display the mock value of available pets (without the alert box). | ||
When clicking the **Get Inventory** button, you should see the request to the `StoreApi` logged in the alert box and the actual result displayed | ||
in the UI (not the mock value like before). | ||
|
||
We can conclude that the configuration override does not merge the plugins, but replaces them. |
37 changes: 37 additions & 0 deletions
37
apps/showcase/src/assets/trainings/sdk/steps/angular-integration/solution/app.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { JsonPipe } from '@angular/common'; | ||
import { Component, inject, signal } from '@angular/core'; | ||
import { ApiFactoryService } from '@o3r/apis-manager'; | ||
import { type Pet, PetApi, StoreApi } from 'sdk'; | ||
|
||
@Component({ | ||
selector: 'app-root', | ||
standalone: true, | ||
imports: [JsonPipe], | ||
templateUrl: './app.component.html', | ||
styleUrl: './app.component.scss' | ||
}) | ||
export class AppComponent { | ||
/** Title of the application */ | ||
public title = 'tutorial-app'; | ||
|
||
private readonly petApi = inject(ApiFactoryService).getApi(PetApi); | ||
private readonly storeApi = inject(ApiFactoryService).getApi(StoreApi); | ||
|
||
private readonly petsWritable = signal<Pet[]>([]); | ||
public readonly pets = this.petsWritable.asReadonly(); | ||
|
||
private readonly petsInventoryWritable = signal<{ [key: string]: number }>({}); | ||
public readonly petsInventory = this.petsInventoryWritable.asReadonly(); | ||
|
||
/** Get the pets whose status is 'available' */ | ||
public async getAvailablePets() { | ||
const availablePets = await this.petApi.findPetsByStatus({status: 'available'}); | ||
this.petsWritable.set(availablePets); | ||
} | ||
|
||
/** Get the pets inventory */ | ||
public async getPetInventory() { | ||
const inventory = await this.storeApi.getInventory({}); | ||
this.petsInventoryWritable.set(inventory); | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
apps/showcase/src/assets/trainings/sdk/steps/angular-integration/solution/app.config.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { ApiFetchClient } from '@ama-sdk/client-fetch'; | ||
import { ApiClient, PluginRunner, RequestOptions, RequestPlugin } from '@ama-sdk/core'; | ||
import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'; | ||
import { provideRouter } from '@angular/router'; | ||
import { ApiManager, ApiManagerModule } from '@o3r/apis-manager'; | ||
import { routes } from './app.routes'; | ||
|
||
class MockInterceptRequest implements RequestPlugin { | ||
public load(): PluginRunner<RequestOptions, RequestOptions> { | ||
return { | ||
transform: async (data: RequestOptions) => { | ||
const mockData = data.api?.apiName === 'PetApi' | ||
? [{ name : "mockPetName", photoUrls: ["mockPhotoUrl"], status: "available"}] | ||
: { mockPropertyName : "mockPropertyValue"}; | ||
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() | ||
}; | ||
} | ||
}; | ||
} | ||
} | ||
|
||
class RequestAlertPlugin implements RequestPlugin { | ||
public load(): PluginRunner<RequestOptions, RequestOptions> { | ||
return { | ||
transform: (data: RequestOptions) => { | ||
alert(JSON.stringify(data)); | ||
return data; | ||
} | ||
}; | ||
} | ||
} | ||
|
||
// Default configuration for all the APIs defined in the ApiManager | ||
const apiConfig: ApiClient = new ApiFetchClient( | ||
{ | ||
basePath: 'https://petstore3.swagger.io/api/v3', | ||
requestPlugins: [new MockInterceptRequest()], | ||
fetchPlugins: [] | ||
} | ||
); | ||
|
||
const apiManager = new ApiManager(apiConfig, { | ||
// Configuration override for a specific API | ||
StoreApi: new ApiFetchClient({ | ||
basePath: 'https://petstore3.swagger.io/api/v3', | ||
requestPlugins: [new RequestAlertPlugin()], | ||
fetchPlugins: [] | ||
}) | ||
}); | ||
|
||
export const appConfig: ApplicationConfig = { | ||
providers: [ | ||
provideZoneChangeDetection({ eventCoalescing: true }), | ||
provideRouter(routes), | ||
importProvidersFrom(ApiManagerModule.forRoot(apiManager)) | ||
] | ||
}; |
27 changes: 0 additions & 27 deletions
27
tools/github-actions/new-version/packaged-action/LICENSE.txt
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.