From fe14c52a2576beb26f967f2cff6153191cb10323 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luc-Fr=C3=A9d=C3=A9ric=20Langis?=
<65532894+lflangis@users.noreply.github.com>
Date: Thu, 23 May 2024 14:23:11 -0400
Subject: [PATCH] fix(notebook): SFKP-1081 add error message, fix typo (#4003)
---
src/locales/en.ts | 18 ++++-
src/store/notebook/slice.ts | 44 +----------
src/store/notebook/thunks.ts | 76 +++++++------------
.../DashboardCards/Notebook/index.tsx | 31 ++------
4 files changed, 52 insertions(+), 117 deletions(-)
diff --git a/src/locales/en.ts b/src/locales/en.ts
index 815e43909..c023a2bde 100644
--- a/src/locales/en.ts
+++ b/src/locales/en.ts
@@ -722,7 +722,7 @@ const en = {
title: 'CAVATICA VWB — Data Studio',
part1: 'Analyze Kids First’s variant data in Cavatica’s',
part2:
- 'for enhanced data manipulation. Once your files are copied into a Cavatica project, you can explore and combine Kids First participant clinical data, variant annotations, and public external variant databases (such as Ensembl, gnomAD, dbSNFP, OMIM) in JupyterLab with PySpark to conduct statistical analyses, integrate multi-omics data, generate predictive models, and create compelling visualizations.',
+ 'for enhanced data manipulation. Once your files are copied into a Cavatica project, you can explore and combine Kids First participant clinical data, variant annotations, and public external variant databases (such as Ensembl, gnomAD, dbNSFP, OMIM) in JupyterLab with PySpark to conduct statistical analyses, integrate multi-omics data, generate predictive models, and create compelling visualizations.',
part3:
'In order to access and copy variant data in a Cavatica project, you must have authorizations to access select NCI and Kids First controlled data. Connect to our data repository partners using your eRA Commons account to obtain controlled access to variant data.',
readMore: 'Read more on',
@@ -743,6 +743,22 @@ const en = {
wait: 'This process may take a few moments.',
open: 'Open notebooks',
launch: 'Launch in Cavatica',
+ error: {
+ title: 'Error',
+ no_fence_connection: {
+ title: 'Connection Error',
+ description: `We couldn't establish a connection to the data repository partners. Please use your eRA Commons account to connect through the Authorized Studies widget.`,
+ },
+ no_acl: {
+ title: 'Access denied: insufficient permissions',
+ description:
+ 'You do not have the necessary permissions to access this controlled data. Please try again or Contact support.',
+ },
+ no_file_for_acls: {
+ title: 'No variant data available',
+ description: 'No variant data was found for your permitted controlled access list.',
+ },
+ },
},
fhirDataResource: {
title: 'Kids First FHIR API',
diff --git a/src/store/notebook/slice.ts b/src/store/notebook/slice.ts
index b18d8f02f..81020c5a8 100644
--- a/src/store/notebook/slice.ts
+++ b/src/store/notebook/slice.ts
@@ -2,11 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { NotebookApiStatus } from 'services/api/notebook/model';
-import {
- getNotebookClusterManifest,
- getNotebookClusterStatus,
- stopNotebookCluster,
-} from './thunks';
+import { getNotebookClusterManifest } from './thunks';
import { initialState } from './types';
export const NotebookState: initialState = {
@@ -49,44 +45,6 @@ const notebookSlice = createSlice({
status: NotebookApiStatus.unverified,
isLoading: false,
}));
- // GET cluster
- builder.addCase(getNotebookClusterStatus.pending, (state) => ({
- ...state,
- isLoading: true,
- error: null,
- }));
- builder.addCase(getNotebookClusterStatus.fulfilled, (state, action) => ({
- ...state,
- isLoading: false,
- status: action.payload.status,
- url: action.payload.url!,
- }));
- builder.addCase(getNotebookClusterStatus.rejected, (state, action) => ({
- ...state,
- error: action.payload,
- status: NotebookApiStatus.unverified,
- isLoading: false,
- }));
- // STOP cluster
- builder.addCase(stopNotebookCluster.pending, (state) => ({
- ...state,
- isLoading: true,
- status: NotebookApiStatus.deleteInProgress,
- url: '',
- error: null,
- }));
- builder.addCase(stopNotebookCluster.fulfilled, (state) => ({
- ...state,
- isLoading: true,
- status: NotebookApiStatus.deleteInProgress,
- url: '',
- }));
- builder.addCase(stopNotebookCluster.rejected, (state, action) => ({
- ...state,
- error: action.payload,
- status: NotebookApiStatus.createComplete,
- isLoading: false,
- }));
},
});
diff --git a/src/store/notebook/thunks.ts b/src/store/notebook/thunks.ts
index f6aa50087..fab49e259 100644
--- a/src/store/notebook/thunks.ts
+++ b/src/store/notebook/thunks.ts
@@ -1,3 +1,4 @@
+import intl from 'react-intl-universal';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { NotebookApi } from 'services/api/notebook';
@@ -5,66 +6,47 @@ import { TNotebookApiResponse } from 'services/api/notebook/model';
import { RootState } from 'store/types';
import { handleThunkApiReponse } from 'store/utils';
+import { globalActions } from '../global';
+
const getNotebookClusterManifest = createAsyncThunk<
TNotebookApiResponse,
- {
- onSuccess: () => void;
- },
+ void,
{ rejectValue: string; state: RootState }
>('notebook/manifest', async (args, thunkAPI) => {
const { data, error } = await NotebookApi.getManifest();
+ let errorMessage = '';
+ let errorDescription = '';
if (error) {
- return thunkAPI.rejectWithValue(error?.message);
- }
-
- args.onSuccess();
-
- return handleThunkApiReponse({
- error,
- data: data!,
- reject: thunkAPI.rejectWithValue,
- });
-});
-
-const getNotebookClusterStatus = createAsyncThunk<
- TNotebookApiResponse,
- void,
- { rejectValue: string; state: RootState }
->('notebook/get', async (_, thunkAPI) => {
- const { data, error } = await NotebookApi.getStatus();
-
- if (error) {
- return thunkAPI.rejectWithValue(error?.message);
+ const msg = error.response?.data?.error ?? '';
+ if (msg === 'no_fence_connection') {
+ errorMessage = 'screen.dashboard.cards.notebook.error.no_fence_connection.message';
+ errorDescription = 'screen.dashboard.cards.notebook.error.no_fence_connection.description';
+ } else if (msg === 'no_acl') {
+ errorMessage = 'screen.dashboard.cards.notebook.error.no_acl.message';
+ errorDescription = 'screen.dashboard.cards.notebook.error.no_acl.description';
+ } else if (msg === 'no_file_for_acls') {
+ errorMessage = 'screen.dashboard.cards.notebook.error.no_file_for_acls.message';
+ errorDescription = 'screen.dashboard.cards.notebook.error.no_file_for_acls.description';
+ }
}
return handleThunkApiReponse({
error,
+ onError: () => {
+ if (errorMessage && errorDescription) {
+ thunkAPI.dispatch(
+ globalActions.displayNotification({
+ type: 'error',
+ message: intl.get('screen.dashboard.cards.notebook.error.title'),
+ description: intl.get(errorDescription),
+ }),
+ );
+ }
+ },
data: data!,
reject: thunkAPI.rejectWithValue,
});
});
-const stopNotebookCluster = createAsyncThunk<
- void,
- {
- onSuccess: () => void;
- },
- { rejectValue: string; state: RootState }
->('notebook/stop', async (args, thunkAPI) => {
- const { data, error } = await NotebookApi.stop();
-
- if (error) {
- return thunkAPI.rejectWithValue(error?.message);
- }
-
- args.onSuccess();
-
- return handleThunkApiReponse({
- error,
- data,
- reject: thunkAPI.rejectWithValue,
- });
-});
-
-export { getNotebookClusterManifest, getNotebookClusterStatus, stopNotebookCluster };
+export { getNotebookClusterManifest };
diff --git a/src/views/Dashboard/components/DashboardCards/Notebook/index.tsx b/src/views/Dashboard/components/DashboardCards/Notebook/index.tsx
index fe58efe01..9398ec814 100644
--- a/src/views/Dashboard/components/DashboardCards/Notebook/index.tsx
+++ b/src/views/Dashboard/components/DashboardCards/Notebook/index.tsx
@@ -17,19 +17,16 @@ import logo from 'components/assets/jupyterLab.png';
import KidsFirstLoginIcon from 'components/Icons/KidsFirstLoginIcon';
import NciIcon from 'components/Icons/NciIcon';
import OpenInNewIcon from 'components/Icons/OpenInIcon';
-import useInterval from 'hooks/useInterval';
import { TUserGroups } from 'services/api/user/models';
import { useAtLeastOneFenceConnected, useFenceAuthentification } from 'store/fences';
import { fenceDisconnection, fenceOpenAuhentificationTab } from 'store/fences/thunks';
import { useNotebook } from 'store/notebook';
-import { getNotebookClusterManifest, getNotebookClusterStatus } from 'store/notebook/thunks';
+import { getNotebookClusterManifest } from 'store/notebook/thunks';
import { useUser } from 'store/user';
import styles from './index.module.scss';
const { Text } = Typography;
-const REFRESH_INTERVAL = 30000;
-
const Notebook = ({ id, key, className = '' }: DashboardCardProps) => {
const dispatch = useDispatch();
const gen3 = useFenceAuthentification(FENCE_NAMES.gen3);
@@ -69,35 +66,16 @@ const Notebook = ({ id, key, className = '' }: DashboardCardProps) => {
}
};
- const hasAtLeastOneAuthentificatedFence = useAtLeastOneFenceConnected();
+ // const hasAtLeastOneAuthentificatedFence = useAtLeastOneFenceConnected();
+ const hasAtLeastOneAuthentificatedFence = true;
const isAllowed = groups.includes(TUserGroups.BETA);
const isProcessing = (isLoading || isNotebookStatusInProgress(status)) && !error;
const handleGetManifest = () => {
- dispatch(
- getNotebookClusterManifest({
- onSuccess: () => dispatch(getNotebookClusterStatus()),
- }),
- );
+ dispatch(getNotebookClusterManifest());
};
- useInterval(
- () => {
- dispatch(getNotebookClusterStatus());
- },
- // Delay in milliseconds or null to stop it
- isProcessing ? REFRESH_INTERVAL : null,
- );
-
- useEffect(() => {
- // can check status
- if (isAllowed && !error) {
- dispatch(getNotebookClusterStatus());
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
return (
<>
{
'screen.dashboard.cards.notebook.tooltip.applyingForDataAccess',
)}
/>
+ .
),