Skip to content

Commit

Permalink
fix(website): show error when parsing the collection variant filter f…
Browse files Browse the repository at this point in the history
…ails (#438)

* refactor(website): extract hook

* fix(website): show error when parsing the collection variant filter fails

preparation for #436
  • Loading branch information
fengelniederhammer authored Dec 20, 2024
1 parent 5de749f commit 8373c73
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 59 deletions.
44 changes: 40 additions & 4 deletions website/src/components/ErrorReportInstruction.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,48 @@
import { useRef, useState } from 'react';
import { toast } from 'react-toastify';
import { v4 as uuidv4 } from 'uuid';

import { Modal, useModalRef } from '../styles/containers/Modal.tsx';
import type { InstanceLogger } from '../types/logMessage.ts';

type ErrorToastArguments = {
error: Error;
logMessage: string;
errorToastMessages: [string, ...string[]];
};

export function useErrorToast(logger: InstanceLogger) {
return {
showErrorToast: ({ error, logMessage, errorToastMessages }: ErrorToastArguments) => {
const errorId = uuidv4();
logger.error(logMessage, {
errorId,
});
toast.error(
<>
{errorToastMessages.map((toastMessage, index) => (
<p className='mb-2' key={index}>
{toastMessage}
</p>
))}
<ErrorReportToastModal errorId={errorId} error={error} />
</>,
{
position: 'bottom-left',
autoClose: false,
},
);
},
};
}

/**
* Throw this error if you want to display the error message to the user.
*/
export class UserFacingError extends Error {}

export function ErrorReportToastModal({ errorId, error }: { errorId: string; error: Error }) {
const modalRef = useRef<HTMLDialogElement>(null);
const modalRef = useModalRef();

const openModal = () => {
if (modalRef.current) {
Expand All @@ -23,16 +59,16 @@ export function ErrorReportToastModal({ errorId, error }: { errorId: string; err
Help us fix the issue.
</button>
</p>
<dialog ref={modalRef} className='modal'>
<div className='modal-box'>
<Modal modalRef={modalRef} size='large'>
<div className='p-8'>
<ErrorReportInstruction errorId={errorId} currentUrl={window.location.href} error={error} />
<div className='modal-action'>
<form method='dialog'>
<button className='btn'>Close</button>
</form>
</div>
</div>
</dialog>
</Modal>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import '@genspectrum/dashboard-components/components';
import '@genspectrum/dashboard-components/style.css';
import { useMutation } from '@tanstack/react-query';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { v4 as uuidv4 } from 'uuid';

import { FilterDisplay } from './FilterDisplay.tsx';
import { IntervalInput } from './IntervalInput.tsx';
Expand All @@ -22,7 +20,7 @@ import { type EvaluationInterval, EvaluationIntervals } from '../../../types/Eva
import { type Organism, Organisms } from '../../../types/Organism.ts';
import type { SubscriptionRequest, Trigger } from '../../../types/Subscription.ts';
import { getErrorLogMessage } from '../../../util/getErrorLogMessage.ts';
import { ErrorReportToastModal } from '../../ErrorReportInstruction.tsx';
import { useErrorToast } from '../../ErrorReportInstruction.tsx';
import { GsApp } from '../../genspectrum/GsApp.tsx';
import { getBackendServiceForClientside } from '../backendApi/backendService.ts';
import { withQueryProvider } from '../backendApi/withQueryProvider.tsx';
Expand All @@ -42,27 +40,20 @@ export function SubscriptionsCreateInner({
// TODO: Enable notificationChannels in #82, #128
// notificationChannels: NotificationChannels;
}) {
const { showErrorToast } = useErrorToast(logger);

const createSubscription = useMutation({
mutationFn: () =>
getBackendServiceForClientside().postSubscription({
subscription: getSubscription(),
userId,
}),
onError: (error) => {
const errorId = uuidv4();
logger.error(`Failed to create a new subscription: ${getErrorLogMessage(error)}`, {
errorId,
showErrorToast({
error,
logMessage: `Failed to create a new subscription: ${getErrorLogMessage(error)}`,
errorToastMessages: ['We could not create your subscription. Please try again later.'],
});
toast.error(
<>
<p className='mb-2'>We could not create your subscription. Please try again later.</p>
<ErrorReportToastModal errorId={errorId} error={error} />
</>,
{
position: 'bottom-left',
autoClose: false,
},
);
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useMutation } from '@tanstack/react-query';
import { type JSX, type RefObject } from 'react';
import { toast } from 'react-toastify';
import { v4 as uuidv4 } from 'uuid';

import { SubscriptionDisplay } from './SubscriptionDisplay.tsx';
import { getClientLogger } from '../../../clientLogger.ts';
Expand All @@ -13,7 +12,7 @@ import { ModalHeader } from '../../../styles/containers/ModalHeader.tsx';
import { organismConfig } from '../../../types/Organism.ts';
import type { Subscription } from '../../../types/Subscription.ts';
import { getErrorLogMessage } from '../../../util/getErrorLogMessage.ts';
import { ErrorReportToastModal } from '../../ErrorReportInstruction.tsx';
import { useErrorToast } from '../../ErrorReportInstruction.tsx';
import { getBackendServiceForClientside } from '../backendApi/backendService.ts';

const logger = getClientLogger('SubscriptionEntry');
Expand Down Expand Up @@ -102,6 +101,8 @@ function MoreDropdown({
userId: string;
refetchSubscriptions: () => void;
}) {
const { showErrorToast } = useErrorToast(logger);

const deleteSubscription = useMutation({
mutationFn: () =>
getBackendServiceForClientside().deleteSubscription({
Expand All @@ -116,22 +117,13 @@ function MoreDropdown({
});
},
onError: (error) => {
const errorId = uuidv4();
logger.error(`Failed to delete subscription with id '${subscription.id}': ${getErrorLogMessage(error)}`, {
errorId,
showErrorToast({
error,
logMessage: `Failed to delete subscription with id '${subscription.id}': ${getErrorLogMessage(error)}`,
errorToastMessages: [
`We could not delete your subscription "${subscription.name}". Please try again later.`,
],
});
toast.error(
<>
<p className='mb-2'>
We could not delete your subscription "{subscription.name}". Please try again later.
</p>
<ErrorReportToastModal errorId={errorId} error={error} />
</>,
{
position: 'bottom-left',
autoClose: false,
},
);
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import { useMemo, useState } from 'react';
import { z } from 'zod';

import { getClientLogger } from '../../../clientLogger.ts';
import type { OrganismsConfig } from '../../../config.ts';
import { type CovidVariantData } from '../../../views/covid.ts';
import { Routing } from '../../../views/routing.ts';
import { useErrorToast } from '../../ErrorReportInstruction.tsx';
import { withQueryProvider } from '../../subscriptions/backendApi/withQueryProvider.tsx';

type CollectionVariant = {
Expand Down Expand Up @@ -98,14 +100,51 @@ const querySchema = z.object({
function CollectionVariantList({ collection, organismsConfig }: CollectionVariantListProps) {
const variants = collection.variants;

const selectVariant = useSelectVariant(organismsConfig, collection);

return (
<div className='flex flex-col'>
{variants.map((variant, index) => (
<button
key={`${variant.name}_${variant.query}_${index}`}
className='border bg-white px-4 py-2 hover:bg-cyan'
onClick={() => selectVariant(variant)}
>
{variant.name}
</button>
))}
</div>
);
}

const logger = getClientLogger('CollectionList');

function useSelectVariant(organismsConfig: OrganismsConfig, collection: Collection) {
const routing = useMemo(() => new Routing(organismsConfig), [organismsConfig]);

const selectVariant = (variant: CollectionVariant) => {
const { showErrorToast } = useErrorToast(logger);

return (variant: CollectionVariant) => {
const currentPageState = routing
.getOrganismView('covid.singleVariantView')
.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href));
let newPageState: CovidVariantData;
const query = querySchema.parse(JSON.parse(variant.query));

const queryParseResult = querySchema.safeParse(JSON.parse(variant.query));

if (!queryParseResult.success) {
showErrorToast({
error: queryParseResult.error,
logMessage: `Failed to parse query of variant ${variant.name} of collection ${collection.id}: ${queryParseResult.error.message}`,
errorToastMessages: [
`The variant filter of the collection variant "${variant.name}" seems to be invalid.`,
],
});
return;
}

const query = queryParseResult.data;

if (query.variantQuery !== undefined) {
newPageState = {
...currentPageState,
Expand Down Expand Up @@ -135,18 +174,4 @@ function CollectionVariantList({ collection, organismsConfig }: CollectionVarian
}
window.location.href = routing.getOrganismView('covid.singleVariantView').pageStateHandler.toUrl(newPageState);
};

return (
<div className='flex flex-col'>
{variants.map((variant, index) => (
<button
key={`${variant.name}_${variant.query}_${index}`}
className='border bg-white px-4 py-2 hover:bg-cyan'
onClick={() => selectVariant(variant)}
>
{variant.name}
</button>
))}
</div>
);
}
15 changes: 11 additions & 4 deletions website/src/styles/containers/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ export function useModalRef() {
return useRef<HTMLDialogElement>(null);
}

type ModalProps =
const modalSize = {
large: 'max-w-screen-lg',
};

type ModalProps = (
| {
/** set this when using from React and open it via `modalRef.current?.showModal()` */
modalRef: RefObject<HTMLDialogElement>;
Expand All @@ -16,12 +20,15 @@ type ModalProps =
/** set this when using from Astro and open it via `onclick="id.showModal()"` */
id: string;
modalRef?: undefined;
};
}
) & {
size?: keyof typeof modalSize;
};

export const Modal: FC<PropsWithChildren<ModalProps>> = ({ children, modalRef, id }) => {
export const Modal: FC<PropsWithChildren<ModalProps>> = ({ children, modalRef, id, size }) => {
return (
<dialog className='modal' ref={modalRef} id={id}>
<ModalBox>{children}</ModalBox>
<ModalBox className={size !== undefined ? modalSize[size] : ''}>{children}</ModalBox>
<form method='dialog' className='modal-backdrop'>
<button>close on clicking outside of modal</button>
</form>
Expand Down
4 changes: 2 additions & 2 deletions website/src/styles/containers/ModalBox.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type PropsWithChildren } from 'react';

export function ModalBox({ children }: PropsWithChildren) {
return <div className='modal-box p-0'>{children}</div>;
export function ModalBox({ children, className }: PropsWithChildren<{ className?: string }>) {
return <div className={`modal-box p-0 ${className ?? ''}`}>{children}</div>;
}

0 comments on commit 8373c73

Please sign in to comment.