Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(integrations): create integration #6996

Merged
merged 1 commit into from Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Expand Up @@ -125,6 +125,12 @@ export class GioSideNavComponent implements OnInit, OnDestroy {
displayName: 'APIs',
category: 'Apis',
},
{
icon: 'gio:box',
routerLink: './integrations',
displayName: 'Integrations',
category: 'Integrations',
},
{
icon: 'gio:multi-window',
routerLink: './applications',
Expand Down
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Integration } from '../../management/integrations/integrations.model';

export function fakeIntegration(attribute?: Partial<Integration>) {
const base: Integration = {
id: 'test_id',
name: 'test_name',
description: 'test_description',
provider: 'test_provider',
owner: 'test_owner',
status: 'test_status',
agent: 'test_agent',
};

return {
...base,
...attribute,
};
}
@@ -0,0 +1,105 @@
<!--

Copyright (C) 2015 The Gravitee team (http://gravitee.io)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

-->

<div>
<div class="page-header">
<h1 class="page-header__page-title">Integrations</h1>
<span class="page-header__description"
>Connect to third-party API gateways and event brokers to create a unified control plane and API portal with Gravitee</span
>
</div>

<mat-card>
<mat-card-content>
<div class="card-header">
<div class="card-header__title">
<h3>Create Integration</h3>
</div>

<div class="card-header__actions">
<button mat-raised-button [disabled]="false" data-testid="exit-create-integration-button" routerLink="..">
<mat-icon svgIcon="gio:cancel"></mat-icon>
Exit without saving
</button>
</div>
</div>

<form [formGroup]="informationForm" class="form" (ngSubmit)="onSubmit()">
<div class="form__body">
<mat-card class="form-card">
<mat-card-content>
<div>
<h5>General Information</h5>
<p class="info">Enter the general information for this new integration.</p>
</div>
<div>
<mat-form-field appearance="outline" class="form-field">
<input
id="name"
type="text"
matInput
formControlName="name"
required="true"
data-testid="create-integration-name-input"
/>
<mat-label>Name</mat-label>
<mat-error *ngIf="informationForm.get('name').hasError('required')">Integration name is required.</mat-error>
<mat-error *ngIf="informationForm.get('name').hasError('maxlength')"
>Integration name has to be less than 50 characters long.</mat-error
>
<mat-error *ngIf="informationForm.get('name').hasError('minlength')"
>Integration name has to be more than 1 characters long.</mat-error
>
</mat-form-field>
</div>
<div>
<mat-form-field appearance="outline" class="form-field">
<textarea
id="description"
matInput
#input
formControlName="description"
maxlength="250"
rows="2"
data-testid="create-integration-description"
></textarea>
<mat-label>Description</mat-label>
<mat-hint align="start" class="hint">{{ input.value.length }}/250</mat-hint>
</mat-form-field>
</div>
</mat-card-content>
</mat-card>
</div>

<div class="form__actions">
<button mat-flat-button [disabled]="true" data-testid="create-integration-back-button" routerLink="..">Back</button>

<button
type="submit"
mat-raised-button
color="primary"
data-testid="create-integration-submit-button"
[disabled]="!informationForm.valid"
>
Create Integration
</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
@@ -0,0 +1,89 @@
/*
* Copyright (C) 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@use 'sass:map';
@use '@angular/material' as mat;
@use '@gravitee/ui-particles-angular' as gio;
@use '../../../scss/gio-layout' as gio-layout;

$typography: map.get(gio.$mat-theme, typography);

:host {
@include gio-layout.gio-responsive-content-container;
}

.page-header {
margin-bottom: 24px;

&__page-title {
@include mat.typography-level($typography, headline-6);
}

&__description {
color: mat.get-color-from-palette(gio.$mat-space-palette, 'lighter40');
@include mat.typography-level($typography, body-2);
}
}

.card-header {
display: flex;
justify-content: space-between;
padding-bottom: 24px;

&__title {
@include mat.typography-level($typography, subtitle-1);
display: flex;
flex-direction: column;
justify-content: center;

h3 {
margin: 0;
}
}
}

.form {
&__body {
display: flex;
justify-content: center;
padding: 24px 0;

.mat-mdc-card {
width: 600px;
border: 1px solid mat.get-color-from-palette(gio.$mat-dove-palette, 'darker10');
box-shadow: 0 2px 4px 0 mat.get-color-from-palette(gio.$mat-dove-palette, 'darker20');
}
}

&__actions {
display: flex;
justify-content: space-between;
padding-top: 24px;
}

.form-field {
width: 100%;
}
}

textarea {
resize: none;
}

.info,
.hint {
color: mat.get-color-from-palette(gio.$mat-space-palette, 'lighter40');
}
@@ -0,0 +1,154 @@
/*
* Copyright (C) 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { HttpTestingController } from '@angular/common/http/testing';
import { InteractivityChecker } from '@angular/cdk/a11y';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatErrorHarness } from '@angular/material/form-field/testing';

import { CreateIntegrationComponent } from './create-integration.component';
import { CreateIntegrationHarness } from './create-integration.harness';

import { IntegrationsModule } from '../integrations.module';
import { CONSTANTS_TESTING, GioTestingModule } from '../../../shared/testing';
import { SnackBarService } from '../../../services-ngx/snack-bar.service';
import { CreateIntegrationPayload } from '../integrations.model';

describe('CreateIntegrationComponent', () => {
let fixture: ComponentFixture<CreateIntegrationComponent>;
let componentHarness: CreateIntegrationHarness;
let httpTestingController: HttpTestingController;

const fakeSnackBarService = {
error: jest.fn(),
};

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CreateIntegrationComponent],
imports: [GioTestingModule, IntegrationsModule, BrowserAnimationsModule, NoopAnimationsModule],
providers: [
{
provide: SnackBarService,
useValue: fakeSnackBarService,
},
],
})
.overrideProvider(InteractivityChecker, {
useValue: {
isFocusable: () => true, // This traps focus checks and so avoid warnings when dealing with
isTabbable: () => true, // This traps focus checks and so avoid warnings when dealing with
},
})
.compileComponents();
});

beforeEach(async () => {
fixture = TestBed.createComponent(CreateIntegrationComponent);
httpTestingController = TestBed.inject(HttpTestingController);
componentHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, CreateIntegrationHarness);
fixture.detectChanges();
});

afterEach(() => {
httpTestingController.verify();
});

describe('form', () => {
it('should not submit integration with too short name', async () => {
await componentHarness.setName('');
await componentHarness.setDescription('Some description');
fixture.detectChanges();

const error: MatErrorHarness = await componentHarness.matErrorMessage();
expect(await error.getText()).toEqual('Integration name is required.');

await componentHarness.clickOnSubmit();
httpTestingController.expectNone(`${CONSTANTS_TESTING.env.v2BaseURL}/integrations`);
});

it('should not submit integration with too long name', async () => {
await componentHarness.setName('test too long name 01234567890123456789012345678901234567890123456789');
await componentHarness.setDescription('Test Description');
fixture.detectChanges();

const error: MatErrorHarness = await componentHarness.matErrorMessage();
expect(await error.getText()).toEqual('Integration name has to be less than 50 characters long.');

await componentHarness.clickOnSubmit();
httpTestingController.expectNone(`${CONSTANTS_TESTING.env.v2BaseURL}/integrations`);
});

it('should not submit integration with too long description', async () => {
await componentHarness.setName('test0');
await componentHarness.setDescription(
'TOO long description: loa hdvoiah dfopivioa fdo[ivu[au f[09vu a[09eu v9[ua09efu 0v9u e09fv u09qw uef09v uq0w9duf v0 qu0efdu 0vwu df09vu 0wduf09v wu0dfu v0 wud0fv uqw0 uf90v uw9efuv9wu efvu wqpefuvqwu e0fu v0wu ef0vu w0euf 0vqwu 0efu v0qwuef uvqw uefvru wfeuvwufvu w0 ufev',
);
await componentHarness.clickOnSubmit();
httpTestingController.expectNone(`${CONSTANTS_TESTING.env.v2BaseURL}/integrations`);
});

it('should create integration with valid name', async () => {
const expectedPayload: CreateIntegrationPayload = {
name: 'TEST123',
description: '',
provider: 'AWS',
};
await componentHarness.setName('TEST123');
await componentHarness.clickOnSubmit();
expectIntegrationPostRequest(expectedPayload);
});

it('should create integration with description', async () => {
const expectedPayload: CreateIntegrationPayload = {
name: 'TEST123',
description: 'Test Description',
provider: 'AWS',
};
await componentHarness.setName('TEST123');
await componentHarness.setDescription('Test Description');
await componentHarness.clickOnSubmit();

expectIntegrationPostRequest(expectedPayload);
});

it('should handle error with message', async () => {
pes-spyro-soft-com marked this conversation as resolved.
Show resolved Hide resolved
await componentHarness.setName('TEST123');
await componentHarness.clickOnSubmit();

expectIntegrationWithError();

fixture.detectChanges();

expect(fakeSnackBarService.error).toHaveBeenCalledWith('An error occurred. Integration not created');
});
});

function expectIntegrationPostRequest(payload: CreateIntegrationPayload): void {
const req = httpTestingController.expectOne(`${CONSTANTS_TESTING.env.v2BaseURL}/integrations`);
req.flush([]);
expect(req.request.method).toEqual('POST');
expect(req.request.body).toEqual(payload);
}

function expectIntegrationWithError(): void {
const req = httpTestingController.expectOne(`${CONSTANTS_TESTING.env.v2BaseURL}/integrations`);
expect(req.request.method).toEqual('POST');
req.flush({}, { status: 400, statusText: 'Bad Request' });
}
});