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

[vue] Add error handling toast #16505

Merged
merged 3 commits into from
Oct 2, 2021
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
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to have alert.service.spec.ts too plz ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added for translation/entity-header scenarios in 02c6214 :)

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