Skip to content

Commit

Permalink
Merge pull request #16505 from swarajsaaj/vue-error-alert
Browse files Browse the repository at this point in the history
[vue] Add error handling toast
  • Loading branch information
pascalgrimaud authored Oct 2, 2021
2 parents 507a607 + c286043 commit de416a2
Show file tree
Hide file tree
Showing 16 changed files with 250 additions and 8 deletions.
2 changes: 2 additions & 0 deletions generators/client/files-vue.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const vueFiles = {
'shims-vue.d.ts',
'constants.ts',
'main.ts',
'shared/alert/alert.service.ts',
'shared/config/axios-interceptor.ts',
'shared/config/config.ts',
'shared/config/config-bootstrap-vue.ts',
Expand Down Expand Up @@ -231,6 +232,7 @@ const vueFiles = {
'spec/app/core/error/error.component.spec.ts',
'spec/app/core/jhi-navbar/jhi-navbar.component.spec.ts',
'spec/app/core/ribbon/ribbon.component.spec.ts',
'spec/app/shared/alert/alert.service.spec.ts',
'spec/app/shared/config/axios-interceptor.spec.ts',
'spec/app/shared/data/data-utils.service.spec.ts',
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { email, maxLength, minLength, required } from 'vuelidate/lib/validators'
import { Component, Inject, Vue } from 'vue-property-decorator';
import UserManagementService from './user-management.service';
import { IUser, User } from '@/shared/model/user.model';
import AlertService from '@/shared/alert/alert.service';

const loginValidator = (value: string) => {
if (!value) {
Expand Down Expand Up @@ -37,6 +38,8 @@ const validations: any = {
})
export default class <%=jhiPrefixCapitalized%>UserManagementEdit extends Vue {
@Inject('userService') private userManagementService: () => UserManagementService;
@Inject('alertService') private alertService: () => AlertService;

public userAccount: IUser;
public isSaving = false;
public authorities: any[] = [];
Expand Down Expand Up @@ -87,6 +90,9 @@ export default class <%=jhiPrefixCapitalized%>UserManagementEdit extends Vue {
solid: true,
autoHideDelay: 5000,
});
}).catch(error => {
this.isSaving = true;
this.alertService().showHttpError(this,error.response);
});
} else {
<%_ if (!enableTranslation) { _%>
Expand All @@ -101,6 +107,9 @@ export default class <%=jhiPrefixCapitalized%>UserManagementEdit extends Vue {
solid: true,
autoHideDelay: 5000,
});
}).catch(error => {
this.isSaving = true;
this.alertService().showHttpError(this,error.response);
});
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import Vue from 'vue';
import { Component, Inject } from 'vue-property-decorator';
import UserManagementService from './user-management.service';
import AlertService from '@/shared/alert/alert.service';

@Component
export default class <%=jhiPrefixCapitalized%>UserManagementView extends Vue {
@Inject('userService') private userManagementService: () => UserManagementService;
@Inject('alertService') private alertService: () => AlertService;

public user: any = null;

beforeRouteEnter(to, from, next) {
Expand All @@ -17,6 +20,8 @@ export default class <%=jhiPrefixCapitalized%>UserManagementView extends Vue {
public init(userId: <% if (userPrimaryKeyTypeString || userPrimaryKeyTypeUUID) { %>string<% } else { %>number<% } %>): void {
this.userManagementService().get(userId).then(res => {
this.user = res.data;
}).catch(error => {
this.alertService().showHttpError(this,error.response);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Component, Inject, Vue } from 'vue-property-decorator';
import Vue2Filters from 'vue2-filters';
import UserManagementService from './user-management.service';
import AlertService from '@/shared/alert/alert.service';

@Component({
mixins: [Vue2Filters.mixin]
})
export default class <%=jhiPrefixCapitalized%>UserManagementComponent extends Vue {
@Inject('userService') private userManagementService: () => UserManagementService;
@Inject('alertService') private alertService: () => AlertService;

public error = '';
public success = '';
public users: any[] = [];
Expand Down Expand Up @@ -110,6 +113,8 @@ _%>
this.removeId = null;
this.loadAll();
this.closeDialog();
}).catch(error => {
this.alertService().showHttpError(this,error.response);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import UserManagementService from '@/admin/user-management/user-management.servi
<%_ } _%>
import LoginService from './account/login.service';
import AccountService from './account/account.service';
import AlertService from './shared/alert/alert.service';

import '../content/scss/vendor.scss';
<%_ if (enableTranslation) { _%>
Expand Down Expand Up @@ -120,7 +121,8 @@ new Vue({
translationService: () => translationService,
<%_ } _%>
// jhipster-needle-add-entity-service-to-main - JHipster will import entities services here
accountService: () => accountService
accountService: () => accountService,
alertService: () => new AlertService()

},
<%_ if (enableTranslation) { _%> i18n,<%_ } _%>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Vue from 'vue';

export default class AlertService {
public showError(instance: Vue, message: string, params?: any) {
<%_ if (enableTranslation) { _%>
const alertMessage = instance.$t(message, params);
<%_ } else {_%>
const alertMessage = message;
<%_ } _%>
instance.$root.$bvToast.toast(alertMessage.toString(), {
toaster: 'b-toaster-top-center',
title: 'Error',
variant: 'danger',
solid: true,
autoHideDelay: 5000,
});
}

public showHttpError(instance: Vue, httpErrorResponse: any) {
switch (httpErrorResponse.status) {
case 0:
this.showError(instance,<% if (enableTranslation) { %> 'error.server.not.reachable'<% } else { %> 'Server not reachable'<% } %>);
break;

case 400: {
const arr = Object.keys(httpErrorResponse.headers);
let errorHeader: string | null = null;
<%_ if (enableTranslation) { _%>
let entityKey: string | null = null;
<%_ } _%>
for (const entry of arr) {
if (entry.toLowerCase().endsWith('app-error')) {
errorHeader = httpErrorResponse.headers[entry];
<%_ if (enableTranslation) { _%>
} else if (entry.toLowerCase().endsWith('app-params')) {
entityKey = httpErrorResponse.headers[entry];
<%_ } _%>
}
}
if (errorHeader) {
<%_ if (enableTranslation) { _%>
const alertData = entityKey ? { entityName: instance.$t(`global.menu.entities.${entityKey}`) } : undefined;
<%_ } _%>
this.showError(instance, errorHeader<% if (enableTranslation) { %>, alertData<% } %>);
} else if (httpErrorResponse.data !== '' && httpErrorResponse.data.fieldErrors) {
this.showError(instance, <% if (enableTranslation) { %>httpErrorResponse.data.message<% } else { %>'Validation error'<% } %>);
} else {
this.showError(instance, httpErrorResponse.data.message);
}
break;
}

case 404:
this.showError(instance, <% if (enableTranslation) { %>'error.http.404'<% } else { %>'Not found'<% } %>);
break;

default:
this.showError(instance, httpErrorResponse.data.message);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import UserManagementEdit from '@/admin/user-management/user-management-edit.vue
import UserManagementEditClass from '@/admin/user-management/user-management-edit.component';
import UserManagementService from '@/admin/user-management/user-management.service';
import VueRouter from 'vue-router';
import AlertService from '@/shared/alert/alert.service';

const localVue = createLocalVue();
localVue.use(VueRouter);
Expand Down Expand Up @@ -42,7 +43,8 @@ describe('UserManagementEdit Component', () => {
<%_ } _%>
localVue,
provide: {
userService: () => new UserManagementService()
userService: () => new UserManagementService(),
alertService: () => new AlertService(),
}
});
userManagementEdit = wrapper.vm;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import UserManagementView from '@/admin/user-management/user-management-view.vue
import UserManagementViewClass from '@/admin/user-management/user-management-view.component';
import UserManagementService from '@/admin/user-management/user-management.service';
import {Authority} from '@/shared/security/authority';
import AlertService from '@/shared/alert/alert.service';

const localVue = createLocalVue();

Expand All @@ -32,7 +33,7 @@ describe('UserManagementView Component', () => {
let userManagementView: UserManagementViewClass;

beforeEach(() => {
wrapper = shallowMount<UserManagementViewClass>(UserManagementView, { store, <% if (enableTranslation) { %>i18n, <% } %>localVue, provide: { userService: () => new UserManagementService() } });
wrapper = shallowMount<UserManagementViewClass>(UserManagementView, { store, <% if (enableTranslation) { %>i18n, <% } %>localVue, provide: { userService: () => new UserManagementService(), alertService: () => new AlertService(), } });
userManagementView = wrapper.vm;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as config from '@/shared/config/config';
import UserManagement from '@/admin/user-management/user-management.vue';
import UserManagementClass from '@/admin/user-management/user-management.component';
import UserManagementService from '@/admin/user-management/user-management.service';
import AlertService from '@/shared/alert/alert.service';

const localVue = createLocalVue();

Expand Down Expand Up @@ -57,7 +58,8 @@ describe('UserManagement Component', () => {
bModal: true
},
provide: {
userService: () => new UserManagementService()
userService: () => new UserManagementService(),
alertService: () => new AlertService(),
}
});
userManagement = wrapper.vm;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import sinon from 'sinon';

import AlertService from '@/shared/alert/alert.service';
import Vue from 'vue';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
<%_ if (enableTranslation) { _%>
const translationStub = sinon.stub();
<%_ } _%>
const toastStub = sinon.stub();

const vueInstance = {
<% if (enableTranslation) { %>$t: translationStub,<% } %>
$root: {
$bvToast: {
toast: toastStub,
},
},
};

describe('Alert Service test suite', () => {
let alertService: AlertService;

beforeEach(() => {
<%_ if (enableTranslation) { _%>
translationStub.reset();
<%_ } _%>
toastStub.reset();
alertService = new AlertService();
});

it('should show error toast with translation/message', async () => {
const message = 'translatedMessage';
<%_ if (enableTranslation) { _%>
const translationKey = 'err.code';

// GIVEN
translationStub.withArgs(translationKey).returns(message);
<%_ } _%>

// WHEN
alertService.showError((<any>vueInstance) as Vue, <% if (enableTranslation) { %>translationKey<%} else {%>message<% } %>);

//THEN
<%_ if (enableTranslation) { _%>
expect(translationStub.withArgs(translationKey).callCount).toEqual(1);
<%_ } _%>
expect(
toastStub.calledOnceWith(message, {
toaster: 'b-toaster-top-center',
title: 'Error',
variant: 'danger',
solid: true,
autoHideDelay: 5000,
})
).toBeTruthy();
});

it('should show not reachable toast when http status = 0', async () => {
<%_ if (enableTranslation) { _%>
const translationKey = 'error.server.not.reachable';
<%_ } _%>
const message = 'Server not reachable';
const httpErrorResponse = {
status: 0,
};
<%_ if (enableTranslation) { _%>
// GIVEN
translationStub.withArgs(translationKey).returns(message);
<%_ } _%>

// WHEN
alertService.showHttpError((<any>vueInstance) as Vue, httpErrorResponse);

//THEN
<%_ if (enableTranslation) { _%>
expect(translationStub.withArgs(translationKey).callCount).toEqual(1);
<%_ } _%>
expect(
toastStub.calledOnceWith(message, {
toaster: 'b-toaster-top-center',
title: 'Error',
variant: 'danger',
solid: true,
autoHideDelay: 5000,
})
).toBeTruthy();
});

it('should show parameterized error toast when http status = 400 and entity headers', async () => {
<%_ if (enableTranslation) { _%>
const translationKey = 'error.update';
<%_ } _%>
const message = 'Updation Error';
const httpErrorResponse = {
status: 400,
headers: {
'x-jhipsterapp-error': <% if (enableTranslation) { %>translationKey<% } else {%>message<% } %>,
'x-jhipsterapp-params': 'dummyEntity',
},
};
<%_ if (enableTranslation) { _%>
// GIVEN
translationStub.withArgs(translationKey).returns(message);
translationStub.withArgs('global.menu.entities.dummyEntity').returns('DummyEntity');
<%_ } _%>

// WHEN
alertService.showHttpError((<any>vueInstance) as Vue, httpErrorResponse);

//THEN
<%_ if (enableTranslation) { _%>
expect(translationStub.withArgs(translationKey, { entityName: 'DummyEntity' }).callCount).toEqual(1);
<%_ } _%>
expect(
toastStub.calledOnceWith(message, {
toaster: 'b-toaster-top-center',
title: 'Error',
variant: 'danger',
solid: true,
autoHideDelay: 5000,
})
).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import JhiDataUtils from '@/shared/data/data-utils.service';
<% } %>
import { I<%= entityAngularName %> } from '@/shared/model/<%= entityModelFileName %>.model';
import <%= entityAngularName %>Service from './<%= entityFileName %>.service';
import AlertService from '@/shared/alert/alert.service';

@Component
export default class <%= entityAngularName %>Details extends <% if (fieldsContainBlob) { %>mixins(JhiDataUtils)<% } else { %>Vue<% } %> {
@Inject('<%= entityInstance %>Service') private <%= entityInstance %>Service: () => <%= entityAngularName %>Service;
@Inject('alertService') private alertService: () => AlertService;

public <%= entityInstance %>: I<%= entityAngularName %> = {};

beforeRouteEnter(to, from, next) {
Expand All @@ -22,6 +25,8 @@ export default class <%= entityAngularName %>Details extends <% if (fieldsContai
public retrieve<%= entityAngularName %>(<%= entityInstance %>Id) {
this.<%= entityInstance %>Service().find(<%= entityInstance %>Id).then((res) => {
this.<%= entityInstance %> = res;
}).catch(error => {
this.alertService().showHttpError(this,error.response);
});
}

Expand Down
Loading

0 comments on commit de416a2

Please sign in to comment.