Skip to content
Snippets Groups Projects
Commit 42d8cf15 authored by Peter Weber's avatar Peter Weber Committed by GitHub
Browse files

[OCTO-3598] Preselect error catch (#299)

parent 7555cca9
No related branches found
No related tags found
No related merge requests found
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
!function(e){function t(t){for(var n,i,u=t[0],l=t[1],c=t[2],d=0,p=[];d<u.length;d++)i=u[d],Object.prototype.hasOwnProperty.call(a,i)&&a[i]&&p.push(a[i][0]),a[i]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(s&&s(t);p.length;)p.shift()();return o.push.apply(o,c||[]),r()}function r(){for(var e,t=0;t<o.length;t++){for(var r=o[t],n=!0,u=1;u<r.length;u++){var l=r[u];0!==a[l]&&(n=!1)}n&&(o.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},a={3:0},o=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.e=function(e){var t=[],r=a[e];if(0!==r)if(r)t.push(r[2]);else{var n=new Promise((function(t,n){r=a[e]=[t,n]}));t.push(r[2]=n);var o,u=document.createElement("script");u.charset="utf-8",u.timeout=120,i.nc&&u.setAttribute("nonce",i.nc),u.src=function(e){return i.p+""+({5:"tracker"}[e]||e)+".bundle.js"}(e);var l=new Error;o=function(t){u.onerror=u.onload=null,clearTimeout(c);var r=a[e];if(0!==r){if(r){var n=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;l.message="Loading chunk "+e+" failed.\n("+n+": "+o+")",l.name="ChunkLoadError",l.type=n,l.request=o,r[1](l)}a[e]=void 0}};var c=setTimeout((function(){o({type:"timeout",target:u})}),12e4);u.onerror=u.onload=o,document.head.appendChild(u)}return Promise.all(t)},i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var u=window.webpackJsonp=window.webpackJsonp||[],l=u.push.bind(u);u.push=t,u=u.slice();for(var c=0;c<u.length;c++)t(u[c]);var s=l;o.push([226,0]),r()}({226:function(e,t,r){"use strict";r.r(t);var n=r(0),a=r.n(n),o=r(28),i=r(22),u=r(17),l=r.n(u),c=r(1),s=r.n(c),d=r(38),p=r(19),f=r(30),m=r(63),b=r(49),v=r(29),h=function(e){var t=e.id,r=e.title,n=e.version;return a.a.createElement("summary",null,a.a.createElement("p",null,r),a.a.createElement("span",null,t," - ",n))},g=function(e){var t=e.module,r=t.id,n=t.title,o=t.version,i=t.replacementCandidates;return a.a.createElement("tr",null,a.a.createElement("td",null,a.a.createElement(h,{id:r,title:n,version:o})),a.a.createElement("td",null,i.map((function(e){return a.a.createElement(h,{key:e.id,id:e.id,title:e.title,version:e.version})}))))},y=g;h.propTypes={id:s.a.string,title:s.a.string,version:s.a.string},h.defaultProps={id:"",title:"",version:""},g.propTypes={module:s.a.shape({id:s.a.string,title:s.a.string,version:s.a.string,replacementCandidates:s.a.arrayOf(s.a.object)}).isRequired};var E=r(23),O=r(21),j=r(2),w=r.n(j),P=r(5),D=r.n(P),k=r(18),q=r(48);function T(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function M(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?T(Object(r),!0).forEach((function(t){D()(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):T(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}var R=function(e){return{id:e.id,title:e.attributes.humanName,version:e.attributes.version}},C=a.a.createContext(),S=function(e){var t=e.source,r=e.basepathDashboard,o=e.children,i=Object(n.useState)([]),u=w()(i,2),l=u[0],c=u[1],s=Object(n.useContext)(k.c).throwError,d=Object(q.b)({href:t,handleError:s}),p=d.isLoading,f=d.document;return Object(n.useEffect)((function(){var e;f&&c((e=f.data).filter((function(e){return"sourceModule"===e.type})).map((function(t){return M(M({},R(t)),{},{replacementCandidates:t.relationships.replacementCandidates.data.map((function(t){return R(e.find((function(e){return e.id===t.id&&e.type===t.type})))}))})})))}),[f]),a.a.createElement(C.Provider,{value:{isLoading:p,basepathDashboard:r,modules:l}},o)};S.propTypes={source:s.a.string.isRequired,basepathDashboard:s.a.string.isRequired,children:s.a.oneOfType([s.a.arrayOf(s.a.node),s.a.node]).isRequired};var x=function(e){var t=e.pending;return a.a.createElement("tbody",null,a.a.createElement("tr",null,a.a.createElement("td",{colSpan:"2"},t?a.a.createElement(O.a,{message:"Loading…"}):a.a.createElement("em",null,"No Module info"))))},L=function(e){var t=e.modules;return a.a.createElement("tbody",null,t.map((function(e){return a.a.createElement(y,{key:e.id,module:e})})))},_=function(){var e=Object(u.useTracking)(),t=Object(n.useContext)(C),r=t.isLoading,o=t.modules,i=t.basepathDashboard;return Object(n.useEffect)((function(){setTimeout((function(){e.trackEvent({type:"User viewed modules auditor"})}),1e3)}),[]),a.a.createElement("div",null,a.a.createElement(v.a,null,a.a.createElement(d.a,null,a.a.createElement(p.a,null,a.a.createElement(E.a,{href:"../../",title:"Home"},"Home")),a.a.createElement(p.a,null,a.a.createElement(E.a,{href:i,title:"Migrations"},"Migrations")),a.a.createElement(p.a,null,a.a.createElement("span",null,"Modules"))),a.a.createElement(f.a,{title:"Modules"}),a.a.createElement(m.a,null,[{name:"found",title:"Found",path:"/",to:"./"}].map((function(e){return a.a.createElement(b.a,{key:"tab-".concat(e.name),to:e.to},e.title)})))),a.a.createElement("table",null,a.a.createElement("thead",null,a.a.createElement("tr",null,a.a.createElement("th",null,"Drupal 7"),a.a.createElement("th",null,"Drupal 9"))),o.length?a.a.createElement(L,{modules:o}):a.a.createElement(x,{pending:r})))};x.propTypes={pending:s.a.bool.isRequired},L.propTypes={modules:s.a.arrayOf(s.a.shape({id:s.a.string.isRequired,title:s.a.string.isRequired,version:s.a.string.isRequired})).isRequired};var A=function(e){var t=e.basepathDashboard,r=e.source;return a.a.createElement("div",{className:"migrate-ui"},a.a.createElement(S,{source:r,basepathDashboard:t},a.a.createElement(_,null)))},N=new i.a,H=l()({},{dispatch:function(e){return N.logEvent(e)}})(A);A.propTypes={source:s.a.string.isRequired,basepathDashboard:s.a.string.isRequired};var J=r(46);document.addEventListener("DOMContentLoaded",(function(){var e=document.querySelector("#decoupled-page-root"),t=e.getAttribute("data-module-path");r.p="/".concat(t,"/ui/dist/");var n=e.getAttribute("data-basepath-dashboard"),u=e.getAttribute("data-source");(new i.a).init(e.getAttribute("data-tracking-api-key"),Object(J.a)(e),void 0),e&&Object(o.render)(a.a.createElement(H,{source:u,basepathDashboard:n}),e)}))}});
\ No newline at end of file
!function(e){function t(t){for(var n,i,u=t[0],l=t[1],c=t[2],d=0,p=[];d<u.length;d++)i=u[d],Object.prototype.hasOwnProperty.call(a,i)&&a[i]&&p.push(a[i][0]),a[i]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(s&&s(t);p.length;)p.shift()();return o.push.apply(o,c||[]),r()}function r(){for(var e,t=0;t<o.length;t++){for(var r=o[t],n=!0,u=1;u<r.length;u++){var l=r[u];0!==a[l]&&(n=!1)}n&&(o.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},a={3:0},o=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.e=function(e){var t=[],r=a[e];if(0!==r)if(r)t.push(r[2]);else{var n=new Promise((function(t,n){r=a[e]=[t,n]}));t.push(r[2]=n);var o,u=document.createElement("script");u.charset="utf-8",u.timeout=120,i.nc&&u.setAttribute("nonce",i.nc),u.src=function(e){return i.p+""+({5:"tracker"}[e]||e)+".bundle.js"}(e);var l=new Error;o=function(t){u.onerror=u.onload=null,clearTimeout(c);var r=a[e];if(0!==r){if(r){var n=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;l.message="Loading chunk "+e+" failed.\n("+n+": "+o+")",l.name="ChunkLoadError",l.type=n,l.request=o,r[1](l)}a[e]=void 0}};var c=setTimeout((function(){o({type:"timeout",target:u})}),12e4);u.onerror=u.onload=o,document.head.appendChild(u)}return Promise.all(t)},i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var u=window.webpackJsonp=window.webpackJsonp||[],l=u.push.bind(u);u.push=t,u=u.slice();for(var c=0;c<u.length;c++)t(u[c]);var s=l;o.push([229,0]),r()}({229:function(e,t,r){"use strict";r.r(t);var n=r(0),a=r.n(n),o=r(28),i=r(23),u=r(18),l=r.n(u),c=r(1),s=r.n(c),d=r(40),p=r(20),f=r(30),m=r(65),b=r(51),v=r(29),h=function(e){var t=e.id,r=e.title,n=e.version;return a.a.createElement("summary",null,a.a.createElement("p",null,r),a.a.createElement("span",null,t," - ",n))},g=function(e){var t=e.module,r=t.id,n=t.title,o=t.version,i=t.replacementCandidates;return a.a.createElement("tr",null,a.a.createElement("td",null,a.a.createElement(h,{id:r,title:n,version:o})),a.a.createElement("td",null,i.map((function(e){return a.a.createElement(h,{key:e.id,id:e.id,title:e.title,version:e.version})}))))},y=g;h.propTypes={id:s.a.string,title:s.a.string,version:s.a.string},h.defaultProps={id:"",title:"",version:""},g.propTypes={module:s.a.shape({id:s.a.string,title:s.a.string,version:s.a.string,replacementCandidates:s.a.arrayOf(s.a.object)}).isRequired};var E=r(19),O=r(22),j=r(2),w=r.n(j),P=r(5),D=r.n(P),k=r(15),q=r(50);function T(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function M(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?T(Object(r),!0).forEach((function(t){D()(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):T(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}var R=function(e){return{id:e.id,title:e.attributes.humanName,version:e.attributes.version}},C=a.a.createContext(),S=function(e){var t=e.source,r=e.basepathDashboard,o=e.children,i=Object(n.useState)([]),u=w()(i,2),l=u[0],c=u[1],s=Object(n.useContext)(k.c).throwError,d=Object(q.b)({href:t,handleError:s}),p=d.isLoading,f=d.document;return Object(n.useEffect)((function(){var e;f&&c((e=f.data).filter((function(e){return"sourceModule"===e.type})).map((function(t){return M(M({},R(t)),{},{replacementCandidates:t.relationships.replacementCandidates.data.map((function(t){return R(e.find((function(e){return e.id===t.id&&e.type===t.type})))}))})})))}),[f]),a.a.createElement(C.Provider,{value:{isLoading:p,basepathDashboard:r,modules:l}},o)};S.propTypes={source:s.a.string.isRequired,basepathDashboard:s.a.string.isRequired,children:s.a.oneOfType([s.a.arrayOf(s.a.node),s.a.node]).isRequired};var x=function(e){var t=e.pending;return a.a.createElement("tbody",null,a.a.createElement("tr",null,a.a.createElement("td",{colSpan:"2"},t?a.a.createElement(O.a,{message:"Loading…"}):a.a.createElement("em",null,"No Module info"))))},L=function(e){var t=e.modules;return a.a.createElement("tbody",null,t.map((function(e){return a.a.createElement(y,{key:e.id,module:e})})))},_=function(){var e=Object(u.useTracking)(),t=Object(n.useContext)(C),r=t.isLoading,o=t.modules,i=t.basepathDashboard;return Object(n.useEffect)((function(){setTimeout((function(){e.trackEvent({type:"User viewed modules auditor"})}),1e3)}),[]),a.a.createElement("div",null,a.a.createElement(v.a,null,a.a.createElement(d.a,null,a.a.createElement(p.a,null,a.a.createElement(E.a,{href:"../../",title:"Home"},"Home")),a.a.createElement(p.a,null,a.a.createElement(E.a,{href:i,title:"Migrations"},"Migrations")),a.a.createElement(p.a,null,a.a.createElement("span",null,"Modules"))),a.a.createElement(f.a,{title:"Modules"}),a.a.createElement(m.a,null,[{name:"found",title:"Found",path:"/",to:"./"}].map((function(e){return a.a.createElement(b.a,{key:"tab-".concat(e.name),to:e.to},e.title)})))),a.a.createElement("table",null,a.a.createElement("thead",null,a.a.createElement("tr",null,a.a.createElement("th",null,"Drupal 7"),a.a.createElement("th",null,"Drupal 9"))),o.length?a.a.createElement(L,{modules:o}):a.a.createElement(x,{pending:r})))};x.propTypes={pending:s.a.bool.isRequired},L.propTypes={modules:s.a.arrayOf(s.a.shape({id:s.a.string.isRequired,title:s.a.string.isRequired,version:s.a.string.isRequired})).isRequired};var A=function(e){var t=e.basepathDashboard,r=e.source;return a.a.createElement("div",{className:"migrate-ui"},a.a.createElement(S,{source:r,basepathDashboard:t},a.a.createElement(_,null)))},N=new i.a,H=l()({},{dispatch:function(e){return N.logEvent(e)}})(A);A.propTypes={source:s.a.string.isRequired,basepathDashboard:s.a.string.isRequired};var J=r(48);document.addEventListener("DOMContentLoaded",(function(){var e=document.querySelector("#decoupled-page-root"),t=e.getAttribute("data-module-path");r.p="/".concat(t,"/ui/dist/");var n=e.getAttribute("data-basepath-dashboard"),u=e.getAttribute("data-source");(new i.a).init(e.getAttribute("data-tracking-api-key"),Object(J.a)(e),void 0),e&&Object(o.render)(a.a.createElement(H,{source:u,basepathDashboard:n}),e)}))}});
\ No newline at end of file
This diff is collapsed.
......@@ -2,8 +2,41 @@ import React from 'react';
import PropTypes from 'prop-types';
import ClaroMessage from './claro/message';
import ExtLink from './ext-link';
import APIError from '../errors/api-error';
import useToggle from '../hooks/use-toggle';
import ApplicationError from '../errors/application-error';
const APISuggestion = ({ suggestion }) => {
const { text, link } = suggestion;
const { href, title } = link;
return (
<p>
{text}
<br />
<ExtLink href={href} title={title}>
{title}
</ExtLink>
</p>
);
};
const ToggleDetails = ({ children }) => {
const [expanded, toggleFn] = useToggle(false);
return (
<div>
<em>
{expanded ? (
<a onClick={toggleFn}>Hide developer details.</a>
) : (
<a onClick={toggleFn}>Show developer details.</a>
)}
</em>
{expanded ? children : null}
</div>
);
};
/**
* @param {APIError} error
......@@ -11,13 +44,17 @@ import useToggle from '../hooks/use-toggle';
* <APIErrorDetails error={error} />
*/
const APIErrorDetails = ({ error }) => {
const { status, reason } = error;
const { status, reason, suggestion } = error;
return (
<p>
Status code: <em>{status}</em>
<br />
Reason phrase: <em>{reason}</em>
</p>
<div>
<p>
Status code: <em>{status}</em>
</p>
<p>
Reason phrase: <em>{reason}</em>
</p>
{suggestion ? <APISuggestion suggestion={suggestion} /> : null}
</div>
);
};
......@@ -27,33 +64,34 @@ const APIErrorDetails = ({ error }) => {
* <APIErrorContent />
*/
const APIErrorContent = ({ error }) => {
const [expanded, toggleFn] = useToggle(false);
const message =
(error.errors[0] || {}).detail || 'An unrecognized API error occurred.';
return (
<p>
<span>{message}</span>
<br />
<em>
{expanded ? (
<a onClick={toggleFn}>Hide developer details.</a>
) : (
<a onClick={toggleFn}>Show developer details.</a>
)}
</em>
{expanded ? <APIErrorDetails {...{ error }} /> : null}
</p>
<div>
<p>{message}</p>
<ToggleDetails>
<APIErrorDetails {...{ error }} />
</ToggleDetails>
</div>
);
};
const AppErrorContent = ({ error }) => (
<ToggleDetails>
<p>{error.stack}</p>
</ToggleDetails>
);
const ErrorContent = ({ error }) => {
return error instanceof APIError ? (
<APIErrorContent {...{ error }} />
) : (
if (error instanceof APIError) {
return <APIErrorContent {...{ error }} />;
}
if (error instanceof ApplicationError) {
return <AppErrorContent {...{ error }} />;
}
return (
<p>
<span>
{error.description || error.stack || 'An unrecognized error occurred.'}
</span>
<span>{error.description || 'An unrecognized error occurred.'}</span>
</p>
);
};
......@@ -80,11 +118,13 @@ const DismissableAlert = ({ children, dismiss, title, severity }) => {
) : null;
};
const ErrorAlert = ({ error }) => (
<Alert title={error.message} severity="error">
<ErrorContent {...{ error }} />
</Alert>
);
const ErrorAlert = ({ error }) => {
return (
<Alert title={error.message || error.description} severity="error">
<ErrorContent {...{ error }} />
</Alert>
);
};
const DismissableErrorAlert = ({ error, dismiss }) => (
<DismissableAlert severity="error" title={error.message} dismiss={dismiss}>
......@@ -101,6 +141,23 @@ const errorProps = {
]).isRequired,
};
APISuggestion.propTypes = {
suggestion: PropTypes.shape({
text: PropTypes.string,
link: PropTypes.shape({
href: PropTypes.string,
title: PropTypes.string,
}),
}).isRequired,
};
ToggleDetails.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
};
Alert.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
......
......@@ -23,6 +23,11 @@ export default class APIError extends ApplicationError {
*/
#reason;
/**
* @type {object}
*/
#suggestion;
/**
* Creates a new HttpError.
*
......@@ -51,4 +56,22 @@ export default class APIError extends ApplicationError {
get reason() {
return this.#reason;
}
/**
* Offer a recommendation for an error response.
*
* @return {object|null}
* The recommendation if available.
*/
get suggestion() {
return this.#status === 500
? {
text: 'A 500 error indicates that there is a problem with Drupal.',
link: {
title: 'Check recent log messages for more info.',
href: '/admin/reports/dblog',
},
}
: null;
}
}
......@@ -10,18 +10,18 @@ const throwErrorForResponse = async (response) => {
const isJsonApi = response.headers
.get('Content-Type')
.startsWith('application/vnd.api+json');
const errors = isJsonApi ? (await response.json()).errors : [];
if (response.status >= 500) {
throw new ServerError(response.status, response.statusText, errors);
}
if (response.status >= 400) {
throw new ClientError(response.status, response.statusText, errors);
}
if (isJsonApi) {
const { errors } = await response.json();
if (response.status >= 500) {
throw new ServerError(response.status, response.statusText, errors);
} else if (response.status >= 400) {
throw new ClientError(response.status, response.statusText, errors);
} else {
throw new APIError(response.status, response.statusText);
}
} else {
throw new ApplicationError('Unrecognized response content type.');
throw new APIError(response.status, response.statusText);
}
throw new ApplicationError('Unrecognized response content type.');
};
/**
......
import React from 'react';
import { render } from 'react-dom';
import EventTracking from './lib/events';
import App from './preselect-app';
import { ErrorBoundary, Try, Catch } from './errors/try-catch';
import { ErrorAlert } from './components/alerts';
import withError from './errors/with-error';
import EventTracking from './lib/events';
import { getEnvGlobals } from './lib/acquiaDrupalEnvironment';
const DisplayError = withError(ErrorAlert);
document.addEventListener('DOMContentLoaded', () => {
const domContainer = document.querySelector('#decoupled-page-root');
const modulePath = domContainer.getAttribute('data-module-path');
......@@ -19,6 +24,16 @@ document.addEventListener('DOMContentLoaded', () => {
);
if (domContainer) {
render(<App basepath={basepath} source={source} />, domContainer);
render(
<ErrorBoundary>
<Try>
<App basepath={basepath} source={source} />
</Try>
<Catch>
<DisplayError />
</Catch>
</ErrorBoundary>,
domContainer,
);
}
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment