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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple modules screen #2055

Merged
merged 1 commit into from
Jun 14, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/main/webapp/app/loader/primary/Loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export class Loader<T> {
private assignedValue?: T;

private constructor(private loading: boolean) {}

public static loading<T>(): Loader<T> {
return new Loader(true);
}

public static loaded<T>(value: T): Loader<T> {
const result = this.loading<T>();

result.loaded(value);

return result;
}

isLoading(): boolean {
return this.loading;
}

loaded(value: T) {
this.assignedValue = value;

this.loading = false;
}

value(): T {
if (this.assignedValue === undefined) {
throw new Error('Trying to get the value of a loading item');
}

return this.assignedValue;
}
}
3 changes: 3 additions & 0 deletions src/main/webapp/app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'bootstrap-icons/font/bootstrap-icons.css';
import 'bootstrap';
import '../content/css/custom.css';
import { MittAlertListener } from '@/common/secondary/alert/MittAlertListener';
import { RestModulesRepository } from './module/secondary/RestModulesRepository';

const app = createApp(App);
const pinia = createPinia();
Expand All @@ -45,6 +46,7 @@ const reactRepository = new ReactRepository(axiosHttp, projectHistoryRepository)
const springBootRepository = new SpringBootRepository(axiosHttp, projectHistoryRepository);
const svelteRepository = new SvelteRepository(axiosHttp, projectHistoryRepository);
const vueRepository = new VueRepository(axiosHttp, projectHistoryRepository);
const modulesRepository = new RestModulesRepository(axiosHttp);

app.provide('alertBus', alertBus);
app.provide('alertListener', alertListener);
Expand All @@ -60,6 +62,7 @@ app.provide('reactService', reactRepository);
app.provide('springBootService', springBootRepository);
app.provide('vueService', vueRepository);
app.provide('svelteService', svelteRepository);
app.provide('modules', modulesRepository);
app.use(router);

app.mount('#app');
8 changes: 8 additions & 0 deletions src/main/webapp/app/module/domain/Category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Module } from './Module';

type CategoryName = string;

export interface Category {
name: CategoryName;
modules: Module[];
}
8 changes: 8 additions & 0 deletions src/main/webapp/app/module/domain/Module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ModuleSlug } from './ModuleSlug';

type ModuleDescription = string;

export interface Module {
slug: ModuleSlug;
description: ModuleDescription;
}
5 changes: 5 additions & 0 deletions src/main/webapp/app/module/domain/ModuleProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ModuleProperty } from './ModuleProperty';

export interface ModuleProperties {
properties: ModuleProperty[];
}
12 changes: 12 additions & 0 deletions src/main/webapp/app/module/domain/ModuleProperty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type ModulePropertyType = 'STRING' | 'INTEGER' | 'BOOLEAN';
export type ModulePropertyKey = string;
export type ModulePropertyDescription = string;
export type ModulePropertyExample = string;

export interface ModuleProperty {
type: ModulePropertyType;
mandatory: boolean;
key: ModulePropertyKey;
description?: ModulePropertyDescription;
example?: ModulePropertyExample;
}
1 change: 1 addition & 0 deletions src/main/webapp/app/module/domain/ModuleSlug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ModuleSlug = string;
5 changes: 5 additions & 0 deletions src/main/webapp/app/module/domain/Modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Category } from './Category';

export interface Modules {
categories: Category[];
}
8 changes: 8 additions & 0 deletions src/main/webapp/app/module/domain/ModulesRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ModuleProperties } from './ModuleProperties';
import { Modules } from './Modules';
import { ModuleSlug } from './ModuleSlug';

export interface ModulesRepository {
list(): Promise<Modules>;
get(slug: ModuleSlug): Promise<ModuleProperties>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Loader } from '@/loader/primary/Loader';
import { Modules } from '@/module/domain/Modules';
import { ModulesRepository } from '@/module/domain/ModulesRepository';
import { defineComponent, inject, onMounted, reactive } from 'vue';

export default defineComponent({
name: 'ModulesListVue',
setup() {
const modules = inject('modules') as ModulesRepository;

const modulesContent = reactive({
content: Loader.loading<Modules>(),
});

onMounted(() => {
modules.list().then(response => modulesContent.content.loaded(response));
});

return {
content: modulesContent.content,
};
},
});
16 changes: 16 additions & 0 deletions src/main/webapp/app/module/primary/modules-list/ModulesList.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="jhipster-modules-list">
<h1 data-selector="modules-title" class="jhister-modules-title">Modules</h1>

<div data-selector="modules-loader" v-if="content.isLoading()">Loading</div>
<div data-selector="modules-list" v-else>
<div v-for="category in content.value().categories">
<div class="jhipster-module-category">
<h2 class="jhipster-module-category--name">{{ category.name }}</h2>
<div v-for="module in category.modules" class="jhipster-module" :data-selector="`${module.slug}-module`">
<div class="jhipster-module--slug">{{ module.slug }}</div>
<div class="jhipster-module--description">{{ module.description}}</div>
</div>
</div>
</div>
</div>
</div>
50 changes: 50 additions & 0 deletions src/main/webapp/app/module/primary/modules-list/ModulesList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<template src="./ModulesList.html"></template>

<script lang="ts" src="./ModulesList.component.ts"></script>

<style>
* {
color: #29293b;
}
.jhipster-modules-list {
max-width: 1200px;
margin: auto;
}
.jhister-modules-title {
font-size: 2em;
}
.jhipster-module {
border-radius: 2em;
border: 1px solid #29293b;
margin: 1em 0;
display: grid;
grid-template-columns: 25% auto;
grid-gap: 10px;
grid-auto-rows: minmax(2em, auto);
align-items: center;
}
.jhipster-module--slug {
grid-column: 1 / 2;
grid-row: 1;
border-radius: 2em;
padding: 0.5em 1.5em;
margin-right: 0.5em;
background-color: #29293b;
color: #fff;
font-weight: bold;
text-align: center;
}
.jhipster-module--description {
grid-column: 2 / 2;
grid-row: 1;
}
.jhipster-module-category--name {
font-size: 1.5em;
}
</style>
3 changes: 3 additions & 0 deletions src/main/webapp/app/module/primary/modules-list/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ModulesListVue from './ModulesList.vue';

export { ModulesListVue };
73 changes: 73 additions & 0 deletions src/main/webapp/app/module/secondary/RestModulesRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { AxiosHttp } from '@/http/AxiosHttp';
import { AxiosResponse } from 'axios';
import { Modules } from '../domain/Modules';
import { Category } from '../domain/Category';
import { Module } from '../domain/Module';
import { ModulesRepository } from '../domain/ModulesRepository';
import { ModuleProperties } from '../domain/ModuleProperties';
import { ModuleSlug } from '../domain/ModuleSlug';
import { ModuleProperty, ModulePropertyType } from '../domain/ModuleProperty';

export interface RestModules {
categories: RestCategory[];
}

export interface RestCategory {
name: string;
modules: RestModule[];
}

export interface RestModule {
slug: string;
description: string;
}

export interface RestModuleProperties {
definitions: RestModuleProperty[];
}

export interface RestModuleProperty {
type: ModulePropertyType;
mandatory: boolean;
key: string;
description?: string;
example?: string;
}

export class RestModulesRepository implements ModulesRepository {
constructor(private axiosInstance: AxiosHttp) {}

list(): Promise<Modules> {
return this.axiosInstance.get<RestModules>('/api/modules').then(mapToModules);
}

get(slug: ModuleSlug): Promise<ModuleProperties> {
return this.axiosInstance.get<RestModuleProperties>(`/api/modules/${slug}`).then(mapToModule);
}
}

const mapToModules = (response: AxiosResponse<RestModules>): Modules => ({
categories: response.data.categories.map(toCategory),
});

const toCategory = (restCategory: RestCategory): Category => ({
name: restCategory.name,
modules: restCategory.modules.map(toModule),
});

const toModule = (restModule: RestModule): Module => ({
slug: restModule.slug,
description: restModule.description,
});

const mapToModule = (response: AxiosResponse<RestModuleProperties>): ModuleProperties => ({
properties: response.data.definitions.map(toProperty),
});

const toProperty = (restProperty: RestModuleProperty): ModuleProperty => ({
type: restProperty.type,
mandatory: restProperty.mandatory,
key: restProperty.key,
description: restProperty.description,
example: restProperty.example,
});
6 changes: 6 additions & 0 deletions src/main/webapp/app/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ModulesListVue } from '@/module/primary/modules-list';
import { GeneratorVue } from '@/springboot/primary';
import { createRouter, createWebHistory } from 'vue-router';

Expand All @@ -11,6 +12,11 @@ const routes = [
name: 'Generator',
component: GeneratorVue,
},
{
path: '/modules',
name: 'ModulesList',
component: ModulesListVue,
},
];

const router = createRouter({
Expand Down
9 changes: 9 additions & 0 deletions src/test/javascript/cypress/integration/Modules.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { dataSelector } from '../support/selector';

describe('Modules', () => {
it('Should display modules', () => {
cy.visit('/modules');

cy.get(dataSelector('modules-title')).contains('Modules');
});
});
1 change: 1 addition & 0 deletions src/test/javascript/spec/WrappedElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const wrappedElement = (selector: string): string => `[data-selector="${selector}"]`;
27 changes: 27 additions & 0 deletions src/test/javascript/spec/loader/primary/Loader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Loader } from '@/loader/primary/Loader';

describe('Loader', () => {
it('Should be loading for loading loader', () => {
expect(Loader.loading().isLoading()).toBe(true);
});

it('Should not get value for not loaded loader', () => {
expect(() => Loader.loading().value()).toThrow();
});

it('Should build loaded value', () => {
const loader = Loader.loaded('pouet');

expect(loader.isLoading()).toBe(false);
expect(loader.value()).toBe('pouet');
});

it('Should load value', () => {
const loader = Loader.loading();

loader.loaded('pouet');

expect(loader.isLoading()).toBe(false);
expect(loader.value()).toBe('pouet');
});
});
49 changes: 49 additions & 0 deletions src/test/javascript/spec/module/domain/Modules.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Modules } from '@/module/domain/Modules';
import { ModuleProperties } from '@/module/domain/ModuleProperties';
import { ModulesRepository } from '@/module/domain/ModulesRepository';
import sinon, { SinonStub } from 'sinon';

interface ModulesRepositoryStub extends ModulesRepository {
list: SinonStub;
}

export const stubModulesRepository = (): ModulesRepositoryStub =>
({
list: sinon.stub(),
} as ModulesRepositoryStub);

export const defaultModules = (): Modules => ({
categories: [
{
name: 'Spring',
modules: [
{
slug: 'spring-cucumber',
description: 'Add cucumber to the application',
},
],
},
],
});

export const defaultModuleProperties = (): ModuleProperties => ({
properties: [
{
type: 'STRING',
mandatory: true,
key: 'baseName',
description: 'Application base name',
example: 'jhipster',
},
{
type: 'BOOLEAN',
mandatory: false,
key: 'optionalBoolean',
},
{
type: 'INTEGER',
mandatory: false,
key: 'optionalInteger',
},
],
});
Loading