Skip to content

Commit

Permalink
feat: showcase sdk (#857)
Browse files Browse the repository at this point in the history
## Proposed change

https://fpaul-1a.github.io/otter/#/sdk

## Related issues

- 🐛 Fixes #(issue)
- 🚀 Feature #(issue)

<!-- Please make sure to follow the contributing guidelines on
https://github.com/amadeus-digital/Otter/blob/main/CONTRIBUTING.md -->
  • Loading branch information
fpaul-1A authored Oct 20, 2023
2 parents 19bcd3b + d3e6fbb commit 0bdb17c
Show file tree
Hide file tree
Showing 118 changed files with 4,840 additions and 14 deletions.
1 change: 1 addition & 0 deletions apps/showcase/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"dependencies": {
"@ama-sdk/core": "workspace:^",
"@ama-sdk/schematics": "workspace:^",
"@ama-sdk/showcase-sdk": "workspace:^",
"@angular/animations": "~16.2.0",
"@angular/cdk": "~16.2.0",
"@angular/common": "~16.2.0",
Expand Down
3 changes: 2 additions & 1 deletion apps/showcase/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -268,5 +268,6 @@
"^build-builders"
]
}
}
},
"tags": ["showcase"]
}
1 change: 1 addition & 0 deletions apps/showcase/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const appRoutes: Routes = [
{path: 'rules-engine', loadComponent: () => import('./rules-engine/index').then((m) => m.RulesEngineComponent)},
{path: 'home', loadComponent: () => import('./home/index').then((m) => m.HomeComponent)},
{path: 'run-app-locally', loadComponent: () => import('./run-app-locally/index').then((m) => m.RunAppLocallyComponent)},
{path: 'sdk', loadComponent: () => import('./sdk/index').then((m) => m.SdkComponent)},
{path: '**', redirectTo: '/home', pathMatch: 'full'}
];

Expand Down
6 changes: 6 additions & 0 deletions apps/showcase/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export class AppComponent implements OnDestroy {
{ url: '/dynamic-content', label: 'Dynamic content' },
{ url: '/rules-engine', label: 'Rules engine' }
]
},
{
label: '',
links: [
{ url: '/sdk', label: 'SDK Generator' }
]
}
];

Expand Down
16 changes: 15 additions & 1 deletion apps/showcase/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {ApiClient, ApiFetchClient} from '@ama-sdk/core';
import {PetApi} from '@ama-sdk/showcase-sdk';
import { registerLocaleData } from '@angular/common';
import localeEN from '@angular/common/locales/en';
import localeFR from '@angular/common/locales/fr';
Expand Down Expand Up @@ -35,6 +37,17 @@ const runtimeChecks: Partial<RuntimeChecks> = {
registerLocaleData(localeEN, 'en-GB');
registerLocaleData(localeFR, 'fr-FR');

function petApiFactory() {
const apiConfig: ApiClient = new ApiFetchClient(
{
basePath: 'https://petstore3.swagger.io/api/v3',
requestPlugins: [],
fetchPlugins: []
}
);
return new PetApi(apiConfig);
}

/**
* localizationConfigurationFactory
*/
Expand Down Expand Up @@ -89,7 +102,8 @@ export function localizationConfigurationFactory(): Partial<LocalizationConfigur
xml: () => import('highlight.js/lib/languages/xml')
}
}
}
},
{provide: PetApi, useFactory: petApiFactory}
],
bootstrap: [AppComponent]
})
Expand Down
3 changes: 3 additions & 0 deletions apps/showcase/src/app/sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SdkComponent

the sdk page
1 change: 1 addition & 0 deletions apps/showcase/src/app/sdk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './sdk.component';
40 changes: 40 additions & 0 deletions apps/showcase/src/app/sdk/sdk.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {AfterViewInit, ChangeDetectionStrategy, Component, QueryList, ViewChildren, ViewEncapsulation} from '@angular/core';
import { CommonModule } from '@angular/common';
import {RouterLink} from '@angular/router';
import { O3rComponent } from '@o3r/core';
import {
CopyTextPresComponent,
IN_PAGE_NAV_PRES_DIRECTIVES,
InPageNavLink,
InPageNavLinkDirective,
InPageNavPresService,
SdkPresComponent
} from '../../components';

@O3rComponent({ componentType: 'Page' })
@Component({
selector: 'o3r-sdk',
standalone: true,
imports: [
CommonModule,
CopyTextPresComponent,
RouterLink,
SdkPresComponent,
IN_PAGE_NAV_PRES_DIRECTIVES
],
templateUrl: './sdk.template.html',
styleUrls: ['./sdk.style.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SdkComponent implements AfterViewInit {
@ViewChildren(InPageNavLinkDirective)
private inPageNavLinkDirectives!: QueryList<InPageNavLink>;
public links$ = this.inPageNavPresService.links$;

constructor(private inPageNavPresService: InPageNavPresService) {}

public ngAfterViewInit() {
this.inPageNavPresService.initialize(this.inPageNavLinkDirectives);
}
}
33 changes: 33 additions & 0 deletions apps/showcase/src/app/sdk/sdk.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { PetApi } from '@ama-sdk/showcase-sdk';
import { PetApiFixture } from '@ama-sdk/showcase-sdk/fixtures';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterModule } from '@angular/router';

import { SdkComponent } from './sdk.component';
import '@angular/localize/init';

describe('SdkComponent', () => {
let component: SdkComponent;
let fixture: ComponentFixture<SdkComponent>;
const petApiFixture = new PetApiFixture();
petApiFixture.findPetsByStatus = petApiFixture.findPetsByStatus.mockResolvedValue([]);

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
SdkComponent,
RouterModule.forRoot([])
],
providers: [
{provide: PetApi, useValue: petApiFixture}
]
});
fixture = TestBed.createComponent(SdkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Empty file.
46 changes: 46 additions & 0 deletions apps/showcase/src/app/sdk/sdk.template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<h1>SDK Generator</h1>
<div class="row">
<div class="right-nav order-1 order-lg-2 col-12 col-lg-2 sticky-lg-top pt-5 pt-lg-0">
<o3r-in-page-nav-pres
id="sdk-nav"
[links]="links$ | async"
>
</o3r-in-page-nav-pres>
</div>
<div class="order-2 order-lg-1 col-12 col-lg-10">
<h2 id="sdk-description">Description</h2>
<div>
<p>This module provide generators to create an SDK that can be used to simplify the communication with an API.</p>
<p>It also generates the Typescript classes matching the models of the API.</p>
</div>

<h2 id="sdk-example">Example</h2>
<div>
<p>
Let's try to use the API <a href="https://petstore3.swagger.io" target="_blank" rel="noopener">https://petstore3.swagger.io</a>
<br>
Fortunately, this API provides the specification as <a href="https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml" target="_blank" rel="noopener">Yaml file</a>
that we can use to generate an SDK.
<br>
Here, you can check the <a href="https://github.com/AmadeusITGroup/otter/blob/main/packages/@ama-sdk/showcase-sdk" target="_blank" rel="noopener">generated SDK</a>
</p>
<o3r-sdk-pres></o3r-sdk-pres>
<p>
Do not hesitate to run the application locally, if not installed yet, follow the <a routerLink="/run-app-locally">instructions</a>.
</p>
<a href="https://github.com/AmadeusITGroup/otter/blob/main/apps/showcase/src/components/showcase/sdk" target="_blank" rel="noopener">Source code</a>
</div>

<h2 id="rules-engine-install">How to use</h2>
<div>
<o3r-copy-text-pres [wrap]="true" language="bash" text="yarn create @ama-sdk typescript <project-name> [--spec-path=./path/to/spec.yaml]"></o3r-copy-text-pres>
</div>

<h2 id="rules-engine-references">References</h2>
<ul>
<li>
<a href="https://docs.otter.digitalforairlines.com/additional-documentation/sdk-tools/sdk-models-hierarchy.html" target="_blank" rel="noopener">Documentation</a>
</li>
</ul>
</div>
</div>
2 changes: 1 addition & 1 deletion apps/showcase/src/assets/otter-sticker.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/showcase/src/components/showcase/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './configuration/index';
export * from './dynamic-content/index';
export * from './localization/index';
export * from './rules-engine/index';
export * from './sdk/index';
Empty file.
1 change: 1 addition & 0 deletions apps/showcase/src/components/showcase/sdk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './sdk-pres.component';
135 changes: 135 additions & 0 deletions apps/showcase/src/components/showcase/sdk/sdk-pres.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { PetApi, Tag } from '@ama-sdk/showcase-sdk';
import type { Pet } from '@ama-sdk/showcase-sdk';
import { ChangeDetectionStrategy, Component, computed, inject, signal, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgbHighlight, NgbPagination } from '@ng-bootstrap/ng-bootstrap';
import { O3rComponent } from '@o3r/core';

@O3rComponent({ componentType: 'Component' })
@Component({
selector: 'o3r-sdk-pres',
standalone: true,
imports: [CommonModule, NgbHighlight, FormsModule, NgbPagination],
templateUrl: './sdk-pres.template.html',
styleUrls: ['./sdk-pres.style.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SdkPresComponent {
private petStoreApi = inject(PetApi);

/**
* Name input used to create new pets
*/
public petName = signal('');

/**
* Search term used to filter the list of pets
*/
public searchTerm = signal('');

/**
* Number of items to display on a table page
*/
public pageSize = signal(10);

/**
* Currently opened page on the table
*/
public currentPage = signal(1);

/**
* Complete list of pets retrieved from the API
*/
public pets = signal<Pet[]>([]);

/**
* Loading state of the API
*/
public isLoading = signal(true);

/**
* List of pets filtered according to search term
*/
public filteredPets = computed(() => {
let pets = this.pets();
if (this.searchTerm()) {
const matchString = new RegExp(this.searchTerm().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), 'i');
const matchTag = (tag: Tag) => tag.name && matchString.test(tag.name);
pets = pets.filter((pet) =>
(pet.id && matchString.test(String(pet.id))) ||
matchString.test(pet.name) ||
(pet.category?.name && matchString.test(pet.category.name)) ||
(pet.tags && pet.tags.some(matchTag)));
}
return pets;
});

/**
* Total amount of pet in the filtered list
*/
public totalPetsAmount = computed(() => this.filteredPets().length);

/**
* List of pets displayed in the currently selected table page
*/
public displayedPets = computed(() =>
this.filteredPets().slice((this.currentPage() - 1) * this.pageSize(), (this.currentPage()) * this.pageSize())
);

constructor() {
void this.reload();
}

private getNextId() {
return this.pets().reduce<number>((maxId, pet) => Math.max(maxId, pet.id || 0), 0) + 1;
}

/**
* Trigger a full reload of the list of pets by calling the API
*/
public reload() {
this.isLoading.set(true);
return this.petStoreApi.findPetsByStatus({status: 'available'}).then((pets) => {
this.pets.set(pets.sort((a, b) => a.id && b.id && a.id - b.id || 0));
this.isLoading.set(false);
});
}

/**
* Call the API to create a new pet
*/
public async create() {
const pet: Pet = {
id: this.getNextId(),
name: this.petName(),
category: {name: 'otter'},
tags: [{name: 'otter'}],
status: 'available',
photoUrls: []
};
this.isLoading.set(true);
await this.petStoreApi.addPet({
// eslint-disable-next-line @typescript-eslint/naming-convention
Pet: pet
});
await this.reload();
}

public async delete(petToDelete: Pet) {
if (petToDelete.id) {
this.isLoading.set(true);
try {
await this.petStoreApi.deletePet({petId: petToDelete.id});
} catch (ex) {
// The backend respond with incorrect header application/json while the response is just a string
}
await this.reload();
}
}

public getTags(pet: Pet) {
return pet.tags?.map((tag) => tag.name).join(',');
}
}
29 changes: 29 additions & 0 deletions apps/showcase/src/components/showcase/sdk/sdk-pres.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { PetApi } from '@ama-sdk/showcase-sdk';
import { PetApiFixture } from '@ama-sdk/showcase-sdk/fixtures';
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { SdkPresComponent } from './sdk-pres.component';
import '@angular/localize/init';

describe('SdkPresComponent', () => {
let component: SdkPresComponent;
let fixture: ComponentFixture<SdkPresComponent>;
const petApiFixture = new PetApiFixture();
petApiFixture.findPetsByStatus = petApiFixture.findPetsByStatus.mockResolvedValue([]);

beforeEach(() => {
TestBed.configureTestingModule({
imports: [SdkPresComponent],
providers: [
{provide: PetApi, useValue: petApiFixture}
]
});
fixture = TestBed.createComponent(SdkPresComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
5 changes: 5 additions & 0 deletions apps/showcase/src/components/showcase/sdk/sdk-pres.style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
o3r-sdk-pres {
.table-container {
min-height: 33rem;
}
}
Loading

0 comments on commit 0bdb17c

Please sign in to comment.