Skip to content

Commit 3e54fc4

Browse files
authored
Merge pull request #175 from htrc/error-messages
Error messages
2 parents 2daf7b5 + db0678a commit 3e54fc4

File tree

8 files changed

+136
-22
lines changed

8 files changed

+136
-22
lines changed

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.2.4] – 2025-05-19
11+
12+
### Added
13+
- Dialog component that displays error messages to the user to explain why widgets aren't loading
14+
- Error message for loading a shared link to a dashbaord using private workset while unauthenticated
15+
- Error message for when the workset an authenticated user has been working on has been removed since they last logged in.
16+
- Error message when trying to load a workset where none of the HTIDs are in TORCHLITE
17+
- Error message for when Analytics Gateway is inaccessible
18+
19+
### Fixed
20+
- Bug where logging out while viewing a private workset would try to load the same workset while unauthenticated. Now logouts redirect to the default URL.
21+
1022
## [0.2.3] – 2025-02-26
1123

1224
### Changed
@@ -48,7 +60,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4860
- This CHANGELOG file.
4961
- Share button and popup [#61](https://github.com/htrc/torchlite-app/issues/61)
5062

51-
[unreleased]: https://github.com/htrc/torchlite-frontend/compare/0.2.3...HEAD
63+
[unreleased]: https://github.com/htrc/torchlite-frontend/compare/0.2.4...HEAD
64+
[0.2.4]: https://github.com/htrc/torchlite-frontend/compare/0.2.3...0.2.4
5265
[0.2.3]: https://github.com/htrc/torchlite-frontend/compare/0.2.2...0.2.3
5366
[0.2.2]: https://github.com/htrc/torchlite-frontend/compare/0.2.1...0.2.2
5467
[0.2.1]: https://github.com/htrc/torchlite-frontend/compare/0.2.0...0.2.1

src/components/AlertDialog.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React, { useState } from 'react';
2+
import { Dialog, Button, DialogActions, DialogContent, DialogContentText } from '@mui/material';
3+
4+
interface CustomAlert {
5+
message: string;
6+
}
7+
8+
const AlertDialog = ({message}: CustomAlert) => {
9+
const [open, setOpen] = useState<boolean>(true);
10+
11+
const handleClose = () => {
12+
setOpen(false);
13+
}
14+
15+
return (
16+
<Dialog
17+
open={open}
18+
onClose={handleClose}
19+
>
20+
<DialogContent>
21+
<DialogContentText>
22+
{message}
23+
</DialogContentText>
24+
</DialogContent>
25+
<DialogActions>
26+
<Button onClick={handleClose}>
27+
OK
28+
</Button>
29+
</DialogActions>
30+
</Dialog>
31+
)
32+
}
33+
34+
export default AlertDialog;

src/contexts/AppContext.tsx

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
'use client';
22
import { createContext, ReactNode, useEffect, useState } from 'react';
33
import { useRouter } from 'next/router';
4+
import { NextApiResponse } from 'next';
45
import qs from 'qs';
56

67
// types
78
import { DashboardContextProps, DashboardState, DashboardStatePatch, WorksetList } from 'types/torchlite';
89
import { useSession } from 'next-auth/react';
910
import { getAvailableDashboards, getAvailableWorksets, getDashboardState, updateDashboardState } from 'services';
1011
import CustomBackdrop from 'components/Backdrop';
12+
import AlertDialog from 'components/AlertDialog';
1113

1214
// initial state
1315
const initialState: DashboardContextProps = {
@@ -32,6 +34,8 @@ function AppProvider({ children }: AppProviderProps) {
3234
const [widgetLoadingState, setWidgetLoadingState] = useState<any>({});
3335
const router = useRouter();
3436
const [loading, setLoading] = useState<boolean>(true);
37+
const [errorAlert, setErrorAlert] = useState<boolean>(false);
38+
const [errorText, setErrorText] = useState<string>("");
3539
const { data: session, status } = useSession();
3640

3741
const initializeWidgetLoadingState = (dashboardWidgets: any) => {
@@ -68,16 +72,47 @@ function AppProvider({ children }: AppProviderProps) {
6872
let dashboardState: DashboardState;
6973
if (status === 'unauthenticated') {
7074
if (dashboardId) {
71-
dashboardState = await getDashboardState(dashboardId);
75+
try {
76+
dashboardState = await getDashboardState(dashboardId);
77+
} catch (err) {
78+
console.error(`Error loading available dashboards while unauthenticated: ${err}`);
79+
setErrorAlert(true);
80+
setErrorText('The workset you are trying to access had been deleted or been made private. Contact the workset owner to check the workset status. Worksets must me public in order to have access to it in the dashboard.');
81+
dashboardState = { id: (dashboardId ? dashboardId : ""), worksetId: "", filters: {}, widgets: [], isShared: true, importedId: "", worksetInfo: { id: "", name: "", author: "", isPublic: true, numVolumes: 0, volumes: []} }
82+
}
7283
} else {
73-
const dashboards = await getAvailableDashboards();
74-
dashboardState = dashboards[0];
84+
try {
85+
const dashboards = await getAvailableDashboards();
86+
dashboardState = dashboards[0];
87+
} catch (err: any) {
88+
console.error(`Error loading available worksets while unauthenticated: ${err}`);
89+
setErrorAlert(true);
90+
setErrorText('Worksets are currently unavailable, please try again later.')
91+
dashboardState = { id: (dashboardId ? dashboardId : ""), worksetId: "", filters: {}, widgets: [], isShared: true, importedId: "", worksetInfo: { id: "", name: "", author: "", isPublic: true, numVolumes: 0, volumes: []} }
92+
}
7593
}
7694

7795
sessionStorage.setItem('dashboard_id', dashboardState.id);
7896
} else {
79-
const dashboards = await getAvailableDashboards(dashboardId);
80-
dashboardState = dashboards[0];
97+
try {
98+
const dashboards = await getAvailableDashboards(dashboardId);
99+
dashboardState = dashboards[0];
100+
} catch (err: any) {
101+
console.error(`Error loading available dashboards while authenticated: ${err}`);
102+
setErrorAlert(true);
103+
104+
if (err.status == 404) {
105+
setErrorText('The workset you are trying to access had been deleted or been made private. Contact the workset owner to check the workset status. Worksets must be public in order to have access to it in the dashboard.');
106+
} else if (err.status == 422) {
107+
setErrorText('The selected workset contains invalid htids. The workset cannot be loaded into the dashboard. Please select a different workset. For more information about valid htids, review the documentation.')
108+
} else if (err.status == 503) {
109+
setErrorText('Worksets are currently unavailable, please try again later.');
110+
} else {
111+
setErrorText('Internal server error');
112+
}
113+
114+
dashboardState = { id: (dashboardId ? dashboardId : ""), worksetId: "", filters: {}, widgets: [], isShared: true, importedId: "", worksetInfo: { id: "", name: "", author: "", isPublic: true, numVolumes: 0, volumes: []} }
115+
}
81116

82117
if (dashboardId) {
83118
sessionStorage.removeItem('dashboard_id');
@@ -99,10 +134,16 @@ function AppProvider({ children }: AppProviderProps) {
99134
}
100135
}
101136
}
102-
await updateDashboardState(dashboardState.id, {
103-
importedId: selectedWorksetId,
104-
filters: appliedFilters
105-
});
137+
try {
138+
await updateDashboardState(dashboardState.id, {
139+
importedId: selectedWorksetId,
140+
filters: appliedFilters
141+
});
142+
} catch (err) {
143+
console.error(`Error loading workset from URL: ${err}`);
144+
setErrorAlert(true);
145+
setErrorText('This dashboard’s workset is private. Contact the workset’s owner to make the workset is public to see their dashboard.');
146+
}
106147
dashboardState = await getDashboardState(dashboardState.id);
107148
} else {
108149
selectedWorksetId = dashboardState.importedId;
@@ -166,13 +207,16 @@ function AppProvider({ children }: AppProviderProps) {
166207
const onChangeDashboardState = async (newDashboardState: DashboardStatePatch) => {
167208
try {
168209
if (dashboardState) {
210+
setErrorAlert(false);
169211
setLoading(true);
170212
await updateDashboardState(dashboardState.id, newDashboardState);
171213
const updatedState = await getDashboardState(dashboardState.id);
172214
setDashboardState(updatedState);
173215
}
174216
} catch (error) {
175217
console.error(error);
218+
setErrorAlert(true);
219+
setErrorText('The selected workset contains invalid htids. The workset cannot be loaded into the dashboard. Please select a different workset. For more information about valid htids, review the documentation.');
176220
} finally {
177221
setLoading(false);
178222
}
@@ -200,6 +244,11 @@ function AppProvider({ children }: AppProviderProps) {
200244
}}
201245
>
202246
<CustomBackdrop loading={loading} />
247+
{!loading && errorAlert ?
248+
<AlertDialog
249+
message={errorText}
250+
/> : <></>
251+
}
203252
{children}
204253
</AppContext.Provider>
205254
);

src/layout/MainLayout/Header/HeaderContent/Profile/index.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,7 @@ const Profile = () => {
4141
const router = useRouter();
4242

4343
const handleLogout = () => {
44-
signOut({ redirect: true });
45-
46-
router.push({
47-
pathname: APP_DEFAULT_PATH,
48-
query: {}
49-
});
44+
signOut({ callbackUrl: APP_DEFAULT_PATH, redirect: true });
5045
};
5146

5247
const anchorRef = useRef<any>(null);

src/pages/api/dashboards/[id]/index.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import { DashboardState, DashboardStatePatch, DashboardStatePatchSchema, Dashboa
77
import { getSessionAuthInfo } from 'utils/database';
88
import { isValidBody } from 'utils/helpers';
99

10-
async function getDashboard(dashboardId: string, headers: any): Promise<DashboardState> {
11-
const dashboardSummary = await axios.get<DashboardSummary>(`/dashboards/${dashboardId}`, {
10+
async function getDashboard(dashboardId: string | null, headers: any): Promise<DashboardState> {
11+
const request_url = `/dashboards/${dashboardId ? dashboardId : 'private'}`;
12+
const dashboardSummary = await axios.get<DashboardSummary>(request_url, {
1213
headers: headers
1314
});
15+
1416
const worksetInfo = await axios.get<WorksetInfo>(`/worksets/${dashboardSummary.importedId}/metadata`, {
1517
headers: headers
1618
});
@@ -43,7 +45,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
4345

4446
switch (req.method) {
4547
case 'GET':
46-
const dashboardState = await getDashboard(dashboardId, headers);
48+
const dashboardState = await getDashboard(session ? null : dashboardId, headers);
4749
res.status(200).json(dashboardState);
4850
break;
4951

@@ -55,10 +57,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
5557
break;
5658
}
5759
} catch (err: any) {
58-
console.log(err);
60+
console.error(`${req.method} /dashboards/${req.query.id}`);
61+
console.error(err);
5962
if (err.status == 400) {
6063
res.status(400).end();
6164
}
65+
else if (err.status == 404) {
66+
res.status(404).end();
67+
}
68+
else if (err.status == 422) {
69+
res.status(422).end();
70+
}
6271
else {
6372
res.status(500).json({ message: 'Internal server error' });
6473
}

src/pages/api/dashboards/[id]/widgets/[type]/data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
2929
});
3030
res.status(200).json(widgetData);
3131
} catch (err) {
32+
console.error(`${req.method} /dashboards/${req.query.id}/widgets/${req.query.type}/data`);
3233
console.error(err);
3334
res.status(500).json({ message: 'Internal server error' });
3435
}

src/pages/api/dashboards/index.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
103103

104104
const dashboardState: DashboardState = { ...dashboardSummary, worksetInfo: worksetInfo };
105105
res.status(200).json([dashboardState]);
106-
} catch (err) {
106+
} catch (err: any) {
107+
console.error(`${req.method} /dashboards/`);
107108
console.error(err);
108-
res.status(500).json({ message: 'Internal server error' });
109+
if (err.status == 404) {
110+
res.status(404).end();
111+
}
112+
else if (err.status == 422) {
113+
res.status(422).end();
114+
}
115+
else if (err.status == 503) {
116+
res.status(503).end();
117+
}
118+
else {
119+
res.status(500).json({ message: 'Internal server error' });
120+
}
109121
}
110122
}

src/pages/api/worksets/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
2828
});
2929
res.status(200).json(worksets);
3030
} catch (err) {
31+
console.error(`${req.method} /worksets/`);
3132
console.error(err);
3233
res.status(500).json({ message: 'Internal server error' });
3334
}

0 commit comments

Comments
 (0)