mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-03 14:57:22 +03:00
When handling API errors, use the type prop and fallback to error if not found
This commit is contained in:
parent
dbefae5a01
commit
ba8cade6fc
6 changed files with 43 additions and 11 deletions
|
@ -28,6 +28,7 @@
|
||||||
"no-warning-comments": "off",
|
"no-warning-comments": "off",
|
||||||
"no-magic-numbers": "off",
|
"no-magic-numbers": "off",
|
||||||
"no-undefined": "off",
|
"no-undefined": "off",
|
||||||
|
"no-inline-comments": "off",
|
||||||
"indent": ["error", 2, {
|
"indent": ["error", 2, {
|
||||||
"SwitchCase": 1
|
"SwitchCase": 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as PropTypes from 'prop-types';
|
||||||
import './ErrorHandler.scss';
|
import './ErrorHandler.scss';
|
||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
|
|
||||||
|
// FIXME Replace with typescript: (window, console)
|
||||||
const ErrorHandler = ({ location }, { error }) => class ErrorHandler extends React.Component {
|
const ErrorHandler = ({ location }, { error }) => class ErrorHandler extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { identity } from 'ramda';
|
||||||
import { shortUrlType } from '../reducers/shortUrlsList';
|
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||||
import { shortUrlDeletionType } from '../reducers/shortUrlDeletion';
|
import { shortUrlDeletionType } from '../reducers/shortUrlDeletion';
|
||||||
|
|
||||||
|
const THRESHOLD_REACHED = 'INVALID_SHORTCODE_DELETION';
|
||||||
|
|
||||||
export default class DeleteShortUrlModal extends React.Component {
|
export default class DeleteShortUrlModal extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
shortUrl: shortUrlType,
|
shortUrl: shortUrlType,
|
||||||
|
@ -39,9 +41,10 @@ export default class DeleteShortUrlModal extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { shortUrl, toggle, isOpen, shortUrlDeletion } = this.props;
|
const { shortUrl, toggle, isOpen, shortUrlDeletion } = this.props;
|
||||||
const THRESHOLD_REACHED = 'INVALID_SHORTCODE_DELETION';
|
const { error, errorData } = shortUrlDeletion;
|
||||||
const hasThresholdError = shortUrlDeletion.error && shortUrlDeletion.errorData.error === THRESHOLD_REACHED;
|
const errorCode = error && (errorData.type || errorData.error);
|
||||||
const hasErrorOtherThanThreshold = shortUrlDeletion.error && shortUrlDeletion.errorData.error !== THRESHOLD_REACHED;
|
const hasThresholdError = errorCode === THRESHOLD_REACHED;
|
||||||
|
const hasErrorOtherThanThreshold = error && errorCode !== THRESHOLD_REACHED;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} toggle={toggle} centered>
|
<Modal isOpen={isOpen} toggle={toggle} centered>
|
||||||
|
@ -63,7 +66,8 @@ export default class DeleteShortUrlModal extends React.Component {
|
||||||
|
|
||||||
{hasThresholdError && (
|
{hasThresholdError && (
|
||||||
<div className="p-2 mt-2 bg-warning text-center">
|
<div className="p-2 mt-2 bg-warning text-center">
|
||||||
This short URL has received too many visits and therefore, it cannot be deleted
|
{errorData.threshold && `This short URL has received more than ${errorData.threshold} visits, and therefore, it cannot be deleted.`}
|
||||||
|
{!errorData.threshold && 'This short URL has received too many visits, and therefore, it cannot be deleted.'}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasErrorOtherThanThreshold && (
|
{hasErrorOtherThanThreshold && (
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { createAction, handleActions } from 'redux-actions';
|
import { createAction, handleActions } from 'redux-actions';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { apiErrorType } from '../../utils/services/ShlinkApiClient';
|
||||||
|
|
||||||
/* eslint-disable padding-line-between-statements */
|
/* eslint-disable padding-line-between-statements */
|
||||||
export const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START';
|
export const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START';
|
||||||
|
@ -13,10 +14,7 @@ export const shortUrlDeletionType = PropTypes.shape({
|
||||||
shortCode: PropTypes.string.isRequired,
|
shortCode: PropTypes.string.isRequired,
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.bool.isRequired,
|
error: PropTypes.bool.isRequired,
|
||||||
errorData: PropTypes.shape({
|
errorData: apiErrorType.isRequired,
|
||||||
error: PropTypes.string,
|
|
||||||
message: PropTypes.string,
|
|
||||||
}).isRequired,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import { isEmpty, isNil, reject } from 'ramda';
|
import { isEmpty, isNil, reject } from 'ramda';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const API_VERSION = '1';
|
const API_VERSION = '1';
|
||||||
|
|
||||||
|
export const apiErrorType = PropTypes.shape({
|
||||||
|
type: PropTypes.string,
|
||||||
|
detail: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
status: PropTypes.number,
|
||||||
|
error: PropTypes.string, // Deprecated
|
||||||
|
message: PropTypes.string, // Deprecated
|
||||||
|
});
|
||||||
|
|
||||||
export const buildShlinkBaseUrl = (url) => url ? `${url}/rest/v${API_VERSION}` : '';
|
export const buildShlinkBaseUrl = (url) => url ? `${url}/rest/v${API_VERSION}` : '';
|
||||||
|
|
||||||
export default class ShlinkApiClient {
|
export default class ShlinkApiClient {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { identity } from 'ramda';
|
import { identity } from 'ramda';
|
||||||
|
import each from 'jest-each';
|
||||||
import DeleteShortUrlModal from '../../../src/short-urls/helpers/DeleteShortUrlModal';
|
import DeleteShortUrlModal from '../../../src/short-urls/helpers/DeleteShortUrlModal';
|
||||||
|
|
||||||
describe('<DeleteShortUrlModal />', () => {
|
describe('<DeleteShortUrlModal />', () => {
|
||||||
|
@ -32,17 +33,34 @@ describe('<DeleteShortUrlModal />', () => {
|
||||||
deleteShortUrl.mockClear();
|
deleteShortUrl.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows threshold error message when threshold error occurs', () => {
|
each([
|
||||||
|
[
|
||||||
|
{ error: 'INVALID_SHORTCODE_DELETION' },
|
||||||
|
'This short URL has received too many visits, and therefore, it cannot be deleted.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ type: 'INVALID_SHORTCODE_DELETION' },
|
||||||
|
'This short URL has received too many visits, and therefore, it cannot be deleted.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ error: 'INVALID_SHORTCODE_DELETION', threshold: 35 },
|
||||||
|
'This short URL has received more than 35 visits, and therefore, it cannot be deleted.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ type: 'INVALID_SHORTCODE_DELETION', threshold: 8 },
|
||||||
|
'This short URL has received more than 8 visits, and therefore, it cannot be deleted.',
|
||||||
|
],
|
||||||
|
]).it('shows threshold error message when threshold error occurs', (errorData, expectedMessage) => {
|
||||||
const wrapper = createWrapper({
|
const wrapper = createWrapper({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: true,
|
error: true,
|
||||||
shortCode: 'abc123',
|
shortCode: 'abc123',
|
||||||
errorData: { error: 'INVALID_SHORTCODE_DELETION' },
|
errorData,
|
||||||
});
|
});
|
||||||
const warning = wrapper.find('.bg-warning');
|
const warning = wrapper.find('.bg-warning');
|
||||||
|
|
||||||
expect(warning).toHaveLength(1);
|
expect(warning).toHaveLength(1);
|
||||||
expect(warning.html()).toContain('This short URL has received too many visits and therefore, it cannot be deleted');
|
expect(warning.html()).toContain(expectedMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows generic error when non-threshold error occurs', () => {
|
it('shows generic error when non-threshold error occurs', () => {
|
||||||
|
|
Loading…
Reference in a new issue