diff --git a/webapp/src/components/jira_ticket_tooltip/jira_ticket_tooltip.tsx b/webapp/src/components/jira_ticket_tooltip/jira_ticket_tooltip.tsx index 7501fd937..bd35e7845 100644 --- a/webapp/src/components/jira_ticket_tooltip/jira_ticket_tooltip.tsx +++ b/webapp/src/components/jira_ticket_tooltip/jira_ticket_tooltip.tsx @@ -1,6 +1,8 @@ import React, {ReactNode} from 'react'; import {Instance} from 'types/model'; +import SVGWrapper from 'components/svgWrapper'; +import {SVGIcons} from 'components/plugin_constants/icons'; import {TicketData, TicketDetails} from 'types/tooltip'; import DefaultAvatar from 'components/default_avatar/default_avatar'; @@ -18,6 +20,7 @@ export type Props = { export type State = { ticketId: string; ticketDetails?: TicketDetails | null; + error: string | null; }; const isAssignedLabel = ' is assigned'; @@ -46,6 +49,7 @@ export default class TicketPopover extends React.PureComponent { this.state = { ticketId: ticketID, + error: null, }; } @@ -86,11 +90,16 @@ export default class TicketPopover extends React.PureComponent { const {instanceID} = issueKey; const {ticketId, ticketDetails} = this.state; if (!ticketDetails && this.props.show && ticketId) { - this.props.fetchIssueByKey(this.state.ticketId, instanceID).then((res: {data?: TicketData}) => { + this.props.fetchIssueByKey(this.state.ticketId, instanceID).then((res: { data?: TicketData; error?: any}) => { + if (res.error) { + this.setState({error: 'There was a problem loading the details for this Jira link'}); + return; + } const updatedTicketDetails = getJiraTicketDetails(res.data); if (this.props.connected && updatedTicketDetails && updatedTicketDetails.ticketId === ticketId) { this.setState({ ticketDetails: updatedTicketDetails, + error: null, }); } }); @@ -166,7 +175,24 @@ export default class TicketPopover extends React.PureComponent { return null; } - const {ticketDetails} = this.state; + const {ticketDetails, error} = this.state; + if (error) { + return ( +
+ + {SVGIcons.exclamationTriangle} + +
{error}
+

{'Check your connection or try again later'}

+
+ ); + } + if (!ticketDetails) { // Display the spinner loader while ticket details are being fetched return ( diff --git a/webapp/src/components/jira_ticket_tooltip/ticketStyle.scss b/webapp/src/components/jira_ticket_tooltip/ticketStyle.scss index 3def07884..e340d1769 100644 --- a/webapp/src/components/jira_ticket_tooltip/ticketStyle.scss +++ b/webapp/src/components/jira_ticket_tooltip/ticketStyle.scss @@ -231,3 +231,23 @@ opacity: 0.72; } } + +.jira-issue-tooltip-error { + height: 210px; + font-size: 28px; + display: flex; + flex-direction: column; + justify-content: center; + gap: 10px; + padding: 12px; + + .jira-issue-error-message { + font-size: 19px; + text-align: center; + } + + .jira-issue-error-footer { + font-size: 13px; + font-weight: 100; + } +} diff --git a/webapp/src/components/plugin_constants/icons.tsx b/webapp/src/components/plugin_constants/icons.tsx new file mode 100644 index 000000000..6742f6d07 --- /dev/null +++ b/webapp/src/components/plugin_constants/icons.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +type SvgIconNames = 'exclamationTriangle'; + +export const SVGIcons: Record = { + exclamationTriangle: ( + <> + + + + ), +}; diff --git a/webapp/src/components/svgWrapper/index.tsx b/webapp/src/components/svgWrapper/index.tsx new file mode 100644 index 000000000..35f4050b1 --- /dev/null +++ b/webapp/src/components/svgWrapper/index.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +type SVGWrapperProps = { + viewBox?: string; + height?: number; + width?: number; + fill?: string; + onHoverFill?: string; + children: React.ReactNode; + className?: string; +} + +const SVGWrapper = ({ + children, + viewBox = '0 0 16 16', + height = 36, + width = 36, + fill = 'none', + onHoverFill, + className = '', +}: SVGWrapperProps): JSX.Element => { + return ( + + {children} + + ); +}; + +export default SVGWrapper;