Skip to content
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
3 changes: 3 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { App } from 'src/App';
import { fetchApplicationMetadata } from 'src/queries/queries';
import { renderWithInstanceAndLayout, renderWithoutInstanceAndLayout } from 'src/test/renderWithProviders';

// Need to unmock axios to get actual implementation of isAxiosError
jest.unmock('axios');

describe('App', () => {
beforeEach(() => {
jest.spyOn(window, 'logError').mockImplementation(() => {});
Expand Down
2 changes: 1 addition & 1 deletion src/components/altinnError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const AltinnError = ({
</span>
)}
<h1 className={cn(classes.title, classes.contentMargin)}>{title}</h1>
<p className={cn(classes.articleText, classes.contentMargin)}>{content}</p>
<section className={cn(classes.articleText, classes.contentMargin)}>{content}</section>
{showContactInfo && (
<p>
<Lang
Expand Down
2 changes: 1 addition & 1 deletion src/core/errorHandling/DisplayError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ export function DisplayError({ error }: Props) {
return <InvalidSubformLayoutError error={error} />;
}

return <UnknownError />;
return <UnknownError error={error} />;
}
24 changes: 0 additions & 24 deletions src/features/instantiate/InstantiationError.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const InstantiateContainer = () => {
}
return <MissingRolesError />;
} else if (instantiation.error) {
return <UnknownError />;
return <UnknownError error={instantiation.error} />;
}

return <Loader reason='instantiating' />;
Expand Down
3 changes: 3 additions & 0 deletions src/features/instantiate/containers/UnknownError.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.errorDetails {
margin-top: 16px;
}
23 changes: 22 additions & 1 deletion src/features/instantiate/containers/UnknownError.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@ import React from 'react';

import { jest } from '@jest/globals';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { UnknownError } from 'src/features/instantiate/containers/UnknownError';
import { renderWithMinimalProviders } from 'src/test/renderWithProviders';

// Need to unmock axios to get actual implementation of isAxiosError
jest.unmock('axios');

describe('Unknown error', () => {
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});

it('should be able to render with minimal providers', async () => {
const user = userEvent.setup({ delay: null });
jest.spyOn(console, 'error').mockImplementation(() => {});
await renderWithMinimalProviders({
renderer: () => <UnknownError />,
renderer: () => <UnknownError error={new Error('Error test message')} />,
});

expect(screen.getByTestId('StatusCode')).toBeInTheDocument();
Expand All @@ -20,5 +30,16 @@ describe('Unknown error', () => {
);

expect(console.error).not.toHaveBeenCalled();

const showDetailsButton = screen.getByRole('button', { name: 'Vis detaljer om feilen' });
await user.click(showDetailsButton);
expect(screen.getByText('Error test message')).toBeInTheDocument();

const writeTextMock = jest.spyOn(navigator.clipboard, 'writeText').mockResolvedValue();

const copyButton = screen.getByRole('button', { name: 'Kopier' });
await user.click(copyButton);
expect(writeTextMock).toHaveBeenCalledWith(expect.stringContaining('Error test message'));
expect(copyButton).toHaveAccessibleName('Kopiert');
});
});
16 changes: 15 additions & 1 deletion src/features/instantiate/containers/UnknownError.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import React from 'react';

import { type AxiosError } from 'axios';

import { Button } from 'src/app-components/Button/Button';
import { useDevToolsStore } from 'src/features/devtools/data/DevToolsStore';
import { DevToolsTab } from 'src/features/devtools/data/types';
import { InstantiationErrorPage } from 'src/features/instantiate/containers/InstantiationErrorPage';
import styles from 'src/features/instantiate/containers/UnknownError.module.css';
import { UnknownErrorDetails } from 'src/features/instantiate/containers/UnknownErrorDetails';
import { Lang } from 'src/features/language/Lang';
import { isDev } from 'src/utils/isDev';

export function UnknownError() {
interface Props {
error: Error | AxiosError;
}

export function UnknownError({ error }: Props) {
const open = useDevToolsStore((s) => s.actions.open);
const setActiveTab = useDevToolsStore((s) => s.actions.setActiveTab);

Expand All @@ -34,6 +42,12 @@ export function UnknownError() {
/>,
]}
/>

<UnknownErrorDetails
error={error}
className={styles.errorDetails}
/>

{isDev() && (
<>
<br />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.detailsContainer {
display: flex;
gap: 16px;
flex-direction: column;
}
118 changes: 118 additions & 0 deletions src/features/instantiate/containers/UnknownErrorDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React, { useState } from 'react';

import { CheckmarkCircleIcon } from '@navikt/aksel-icons';
import { type AxiosError, isAxiosError } from 'axios';

import { AccordionItem } from 'src/app-components/Accordion/AccordionItem';
import { Button } from 'src/app-components/Button/Button';
import { Flex } from 'src/app-components/Flex/Flex';
import classes from 'src/features/instantiate/containers/UnknownErrorDetails.module.css';
import { Lang } from 'src/features/language/Lang';

interface UnknownErrorDetailsProps {
error: Error | AxiosError;
className?: string;
}

export function UnknownErrorDetails({ error, className }: UnknownErrorDetailsProps) {
const [now] = useState(new Date());
const [copied, setCopied] = useState(false);
const [axiosError] = useState(() => {
if (isAxiosError(error)) {
return {
responseStatus: error.response?.status,
responseData: error.response?.data,
};
}
return null;
});
const [location] = useState(window?.location.href);

async function handleCopyErrorClicked() {
const errorInfo = {
name: error.name,
message: error.message,
stack: error.stack,
cause: error.cause,
location,
time: now.toISOString(),
...axiosError,
};
if (navigator.clipboard) {
// clipboard is only available in secure contexts (https)
try {
await navigator.clipboard.writeText(JSON.stringify(errorInfo, null, 2));
setCopied(true);
} catch (err) {
window.logError('Failed to copy error info to clipboard', err);
}
}
}

return (
<AccordionItem
title={<Lang id='instantiate.unknown_error_show_details' />}
className={className}
>
<div className={classes.detailsContainer}>
<Flex
container
justifyContent='space-between'
alignItems='center'
direction='row'
>
<DetailItem
name={error.name}
value={error.message}
/>
<Button
variant='secondary'
onClick={handleCopyErrorClicked}
>
{copied ? <Lang id='general.copied' /> : <Lang id='general.copy' />}
{copied && (
<CheckmarkCircleIcon
fontSize='1rem'
aria-hidden={true}
/>
)}
</Button>
</Flex>

<DetailItem
name='Location'
value={location}
/>

<DetailItem
name='Time'
value={now.toISOString()}
/>

{axiosError && (
<DetailItem
name='Response status'
value={axiosError.responseStatus ? axiosError.responseStatus.toString() : ''}
/>
)}
{error.stack && (
<DetailItem
name='Stacktrace'
value={error.stack}
/>
)}
</div>
</AccordionItem>
);
}

function DetailItem({ name, value }: { name: string; value: string }) {
return (
<div>
<div>
<strong>{name}:</strong>
</div>
<div>{value}</div>
</div>
);
}
7 changes: 5 additions & 2 deletions src/language/texts/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ export function en() {
'general.close': 'Close',
'general.contains': 'Contains{0}',
'general.control_submit': 'Control and submit',
'general.copy': 'Copy',
'general.copied': 'Copied',
'general.create_new': 'Create new',
'general.create': 'Create',
'general.customer_service_phone_number': '+47 75 00 60 00',
Expand Down Expand Up @@ -233,10 +235,11 @@ export function en() {
'instantiate.all_forms': 'all forms',
'instantiate.inbox': 'inbox',
'instantiate.profile': 'profile',
'instantiate.unknown_error_title': 'Unknow error',
'instantiate.unknown_error_title': 'Unknown error',
'instantiate.unknown_error_text': 'An unknown error occcurred, please try again later.',
'instantiate.unknown_error_status': 'Unknow error',
'instantiate.unknown_error_status': 'Unknown error',
'instantiate.unknown_error_customer_support': 'If the problem persists, contact us at customer service at {0}.',
'instantiate.unknown_error_show_details': 'Show error details',
'instantiate.forbidden_action_error_title': 'You do not have permission to perform this action.',
'instantiate.forbidden_action_error_text': 'It looks like you do not have permission to perform this action.',
'instantiate.forbidden_action_error_status': '403 - Forbidden',
Expand Down
3 changes: 3 additions & 0 deletions src/language/texts/nb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ export function nb() {
'general.close': 'Lukk',
'general.contains': 'Inneholder',
'general.control_submit': 'Kontroller og send inn',
'general.copy': 'Kopier',
'general.copied': 'Kopiert',
'general.create_new': 'Opprett ny',
'general.create': 'Opprett',
'general.customer_service_phone_number': '+47 75 00 60 00',
Expand Down Expand Up @@ -238,6 +240,7 @@ export function nb() {
'instantiate.unknown_error_text': 'Det har skjedd en ukjent feil, vennligst prøv igjen senere.',
'instantiate.unknown_error_status': 'Ukjent feil',
'instantiate.unknown_error_customer_support': 'Om problemet vedvarer, ta kontakt med oss på brukerservice {0}.',
'instantiate.unknown_error_show_details': 'Vis detaljer om feilen',
'instantiate.forbidden_action_error_title': 'Du mangler rettigheter til å utføre denne handlingen',
'instantiate.forbidden_action_error_text':
'Det ser ut til at du mangler rettigheter til å utføre denne handlingen.',
Expand Down
3 changes: 3 additions & 0 deletions src/language/texts/nn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ export function nn() {
'general.close': 'Lukk',
'general.contains': 'Inneheld',
'general.control_submit': 'Kontroller og send inn',
'general.copy': 'Kopier',
'general.copied': 'Kopiert',
'general.create_new': 'Opprett ny',
'general.create': 'Opprett',
'general.customer_service_phone_number': '+47 75 00 60 00',
Expand Down Expand Up @@ -238,6 +240,7 @@ export function nn() {
'instantiate.unknown_error_text': 'Det har skjedd ein ukjent feil, ver venleg prøv igjen seinare.',
'instantiate.unknown_error_status': 'Ukjent feil',
'instantiate.unknown_error_customer_support': 'Om problemet hald fram, ta kontakt med oss på brukarservice {0}.',
'instantiate.unknown_error_show_details': 'Vis detaljar om feilen',
'instantiate.forbidden_action_error_title': 'Du manglar rett til å utføre denne handlinga',
'instantiate.forbidden_action_error_text': 'Det ser ut til at du ikkje har rett til å utføre denne handlinga.',
'instantiate.forbidden_action_error_status': '403 - Forbidden',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export function AwaitingCurrentUserSignaturePanel({

// This shouldn't really happen, but if it does it indicates that our backend is out of sync with Autorisasjon somehow
if (!canSign) {
return <UnknownError />;
return <UnknownError error={new Error('Unknown error during signing: User cannot sign in')} />;
}

if (isApiLoading) {
Expand Down