coveo-search-ui
Version:
Coveo JavaScript Search Framework
335 lines (284 loc) • 10.4 kB
text/typescript
import 'styling/_ErrorReport';
import { IQueryErrorEventArgs, QueryEvents } from '../../events/QueryEvents';
import { exportGlobally } from '../../GlobalExports';
import { Assert } from '../../misc/Assert';
import { IEndpointError } from '../../rest/EndpointError';
import { MissingAuthenticationError } from '../../rest/MissingAuthenticationError';
import { l } from '../../strings/Strings';
import { AccessibleButton } from '../../utils/AccessibleButton';
import { $$, Dom } from '../../utils/Dom';
import { analyticsActionCauseList, IAnalyticsNoMeta } from '../Analytics/AnalyticsActionListMeta';
import { Component } from '../Base/Component';
import { IComponentBindings } from '../Base/ComponentBindings';
import { ComponentOptions } from '../Base/ComponentOptions';
import { Initialization } from '../Base/Initialization';
export interface IErrorReportOptions {
showDetailedError: boolean;
}
/**
* The ErrorReport component takes care of handling fatal error when doing a query on the index / Search API.
*
* For example, the ErrorReport component displays a message when the service responds with a 401 or 503 error. This
* component also renders a small text area with the JSON content of the error response, for debugging purposes.
*/
export class ErrorReport extends Component {
static ID = 'ErrorReport';
static doExport = () => {
exportGlobally({
ErrorReport: ErrorReport
});
};
/**
* The options for the component
* @componentOptions
*/
static options: IErrorReportOptions = {
/**
* Specifies whether to display a detailed error message as a JSON in a text content area.
*
* Default value is `true`.
*/
showDetailedError: ComponentOptions.buildBooleanOption({ defaultValue: true })
};
private organizationId;
private message: Dom;
private container: Dom;
private helpSuggestion: Dom;
private closePopup: () => void;
/**
* Creates a new ErrorReport component.
* @param element The HTMLElement on which to instantiate the component.
* @param options The options for the ErrorReport component.
* @param bindings The bindings that the component requires to function normally. If not set, these will be
* automatically resolved (with a slower execution time).
*/
constructor(public element: HTMLElement, public options?: IErrorReportOptions, bindings?: IComponentBindings) {
super(element, ErrorReport.ID, bindings);
this.options = ComponentOptions.initComponentOptions(element, ErrorReport, options);
this.container = $$('div', { className: 'coveo-error-report-container' });
this.element.appendChild(this.container.el);
if (this.options.showDetailedError) {
this.message = $$('div', {
className: 'coveo-error-report-message'
});
this.container.append(this.message.el);
}
this.helpSuggestion = $$('div', {
className: 'coveo-error-report-help-suggestion'
});
$$(this.element).hide();
this.bind.onRootElement(QueryEvents.newQuery, () => this.handleNewQuery());
this.bind.onRootElement(QueryEvents.queryError, (data: IQueryErrorEventArgs) => this.handleQueryError(data));
}
/**
* Performs the "back" action in the browser.
* Also logs an `errorBack` event in the usage analytics.
*/
public back(): void {
this.usageAnalytics.logCustomEvent<IAnalyticsNoMeta>(analyticsActionCauseList.errorBack, {}, this.root);
this.usageAnalytics.logSearchEvent<IAnalyticsNoMeta>(analyticsActionCauseList.errorBack, {});
history.back();
}
/**
* Resets the current state of the query and triggers a new query.
* Also logs an `errorClearQuery` event in the usage analytics.
*/
public reset(): void {
this.queryStateModel.reset();
this.usageAnalytics.logSearchEvent<IAnalyticsNoMeta>(analyticsActionCauseList.errorClearQuery, {});
this.usageAnalytics.logCustomEvent<IAnalyticsNoMeta>(analyticsActionCauseList.errorClearQuery, {}, this.root);
this.queryController.executeQuery();
}
/**
* Retries the same query, in case of a temporary service error.
* Also logs an `errorRetry` event in the usage analytics.
*/
public retry(): void {
this.usageAnalytics.logSearchEvent<IAnalyticsNoMeta>(analyticsActionCauseList.errorRetry, {});
this.usageAnalytics.logCustomEvent<IAnalyticsNoMeta>(analyticsActionCauseList.errorRetry, {}, this.root);
this.queryController.executeQuery();
}
private buildOrGetTitleElements() {
const titleElement = $$(this.element).find('.coveo-error-report-title');
let title: Dom;
if (titleElement) {
title = $$(titleElement);
} else {
title = $$('div', { className: 'coveo-error-report-title' });
this.container.prepend(title.el);
}
let firstHeading = title.find('h1');
if (!firstHeading) {
firstHeading = $$('h1').el;
title.append(firstHeading);
}
let secondHeading = title.find('h2');
if (!secondHeading) {
secondHeading = $$('h2').el;
title.append(secondHeading);
}
return {
title,
h1: $$(firstHeading),
h2: $$(secondHeading)
};
}
private setErrorTitle(errorName?: string, helpSuggestion?: string): void {
const errorTitle = {
h1: errorName ? l(errorName) : l('OopsError'),
h2: helpSuggestion ? l(helpSuggestion) : l('ProblemPersists')
};
const { h1, h2 } = this.buildOrGetTitleElements();
if (h1 && h2) {
$$(h1).text(errorTitle.h1);
$$(h2).text(errorTitle.h2);
}
}
private buildPrevious(): HTMLElement {
const previous = $$(
'span',
{
className: 'coveo-error-report-previous'
},
l('GoBack')
);
new AccessibleButton()
.withElement(previous)
.withSelectAction(() => this.back())
.withLabel(l('GoBack'))
.build();
return previous.el;
}
private buildReset(): HTMLElement {
const reset = $$(
'span',
{
className: 'coveo-error-report-clear'
},
l('Reset')
);
new AccessibleButton()
.withElement(reset)
.withSelectAction(() => this.reset())
.withLabel(l('Reset'))
.build();
return reset.el;
}
private buildRetry(): HTMLElement {
const retry = $$(
'span',
{
className: 'coveo-error-report-retry'
},
l('Retry')
);
new AccessibleButton()
.withElement(retry)
.withSelectAction(() => this.retry())
.withLabel(l('Retry'))
.build();
return retry.el;
}
private handleNewQuery(): void {
$$(this.element).hide();
const { h1, h2 } = this.buildOrGetTitleElements();
h1.remove();
h2.remove();
if (this.closePopup != null) {
this.closePopup();
}
}
private handleQueryError(data: IQueryErrorEventArgs): void {
Assert.exists(data);
Assert.exists(data.error);
if (data.endpoint.options.queryStringArguments.organizationId) {
this.organizationId = data.endpoint.options.queryStringArguments.organizationId;
} else {
this.organizationId = l('CoveoOrganization');
}
// Do not display the panel if the error is for missing authentication. The
// appropriate authentication provider should take care of redirecting.
if ((<MissingAuthenticationError>data.error).isMissingAuthentication) {
return;
}
switch (data.error.name) {
case 'NoEndpointsException':
this.options.showDetailedError = false;
this.buildEndpointErrorElements('https://docs.coveo.com/en/331/');
this.setErrorTitle(l('NoEndpoints', this.organizationId), l('AddSources'));
break;
case 'InvalidTokenException':
this.options.showDetailedError = false;
this.buildEndpointErrorElements('https://docs.coveo.com/en/56/');
this.setErrorTitle(l('CannotAccess', this.organizationId), l('InvalidToken'));
break;
case 'GroupByAndFacetBothExistingException':
this.options.showDetailedError = false;
this.buildEndpointErrorElements('https://docs.coveo.com/en/2917');
this.setErrorTitle(undefined, l('GroupByAndFacetRequestsCannotCoexist'));
break;
case 'OrganizationIsPausedException':
this.options.showDetailedError = false;
this.buildEndpointErrorElements('https://docs.coveo.com/l6af0467');
this.setErrorTitle(l('OrganizationWillResume'), l('OrganizationIsPaused'));
break;
default:
this.buildOptionsElement();
this.setErrorTitle();
}
if (this.options.showDetailedError) {
this.message.empty();
const moreInfo = $$(
'span',
{
className: 'coveo-error-report-more-info'
},
l('MoreInfo')
);
moreInfo.on('click', () => {
moreInfo.empty();
this.message.el.appendChild(this.buildErrorInfo(data.error));
});
this.message.el.appendChild(moreInfo.el);
}
$$(this.element).show();
}
private buildErrorInfo(data: IEndpointError): HTMLElement {
const errorInfo = $$('div', {
className: 'coveo-error-info'
});
let textArea = $$('textarea', undefined, JSON.stringify(data, null, 2));
errorInfo.el.appendChild(textArea.el);
const infoLabel = $$(
'div',
{
className: 'coveo-error-info-label'
},
l('CopyPasteToSupport')
);
errorInfo.el.appendChild(infoLabel.el);
return errorInfo.el;
}
private buildOptionsElement() {
const oldOptions = this.container.find('.coveo-error-report-options');
if (oldOptions) {
$$(oldOptions).remove();
}
const optionsElement = $$('div', { className: 'coveo-error-report-options' });
optionsElement.el.appendChild(this.buildPrevious());
optionsElement.el.appendChild(this.buildReset());
optionsElement.el.appendChild(this.buildRetry());
this.container.append(optionsElement.el);
}
private buildEndpointErrorElements(helpLink: string = 'https://docs.coveo.com/en/331/') {
this.helpSuggestion.empty();
const link = $$('a', {
href: helpLink,
className: 'coveo-error-report-help-link'
});
link.setHtml(l('CoveoOnlineHelp'));
this.helpSuggestion.append(link.el);
this.container.el.insertBefore(this.helpSuggestion.el, this.message.el);
}
}
Initialization.registerAutoCreateComponent(ErrorReport);