Skip to content

Commit

Permalink
feat(dashboard): SKFP-1066 re-enable notebook widget with new api
Browse files Browse the repository at this point in the history
  • Loading branch information
lflangis committed May 22, 2024
1 parent 85643b9 commit 740a59c
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 81 deletions.
1 change: 1 addition & 0 deletions src/common/featureToggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
export const FT_DASHBOARD_BANNER = 'DASHBOARD_BANNER';
export const FT_DASHBOARD_BANNER_TYPE = 'DASHBOARD_BANNER_TYPE';
export const FT_DASHBOARD_BANNER_MSG = 'DASHBOARD_BANNER_MSG';
export const FT_DASHBOARD_NOTEBOOK = 'DASHBOARD_NOTEBOOK';
Binary file added src/components/assets/jupyterLab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/helpers/EnvVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default class EnvironmentVariables {
PERSONA_API: process.env.REACT_APP_PERSONA_API,
LEGACY_MEMBERS_API: process.env.REACT_APP_LEGACY_MEMBERS_API,
ARRANGER_API: process.env.REACT_APP_ARRANGER_API_URL,
VARIANT_CLUSTER_API: process.env.REACT_APP_VARIANT_CLUSTER_API,
VWB_CAVATICA_API: process.env.REACT_APP_VWB_CAVATICA,
ARRANGER_PROJECT_ID: process.env.REACT_APP_ARRANGER_PROJECT_ID,
USERS_API: process.env.REACT_APP_USERS_API_URL,
// CAVATICA
Expand Down
22 changes: 18 additions & 4 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,18 +717,32 @@ const en = {
},
notebook: {
title: 'Variant Workbench',
dataStudio: 'Data Studio',
tooltip: {
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.',
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',
applyingForDataAccess: 'applying for data access',
},
account: 'Account Settings',
firstConnectError:
'In order to lauch your notebook, you must first connect to your data repositories in your',
description:
'Connect to our data respository partners in order to access the Kids First variant database in your own <b>high performance compute environment</b>.',
description: {
part1:
'Access the Kids First variant database within your own high-performance compute environment using Cavatica’s',
part2: 'and combine participant clinical data with variant annotations.',
},
contactSupport:
'We were unable to complete this operation. <a href="mailto:support@kidsfirstdrc.org">Contact support</a> if the issue persists',
tryAgain: 'Try Again',
notAllowed: 'Currently available for Kids First investigators only.',
wait: 'This process may take up to 10 minutes. You can safely navigate away from this page.',
wait: 'This process may take a few moments.',
open: 'Open notebooks',
launch: 'Launch environment',
launch: 'Launch in Cavatica',
},
fhirDataResource: {
title: 'Kids First FHIR API',
Expand Down
24 changes: 12 additions & 12 deletions src/services/api/notebook/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import EnvironmentVariables from 'helpers/EnvVariables';

import { sendRequest } from 'services/api';

import { TNotebookApiResponse } from './model';

export const NOTEBOOK_API_URL = `${EnvironmentVariables.configFor('VARIANT_CLUSTER_API')}`;
export const NOTEBOOK_API_URL = `${EnvironmentVariables.configFor('VWB_CAVATICA_API')}`;

const start = () => {
return sendRequest<TNotebookApiResponse>({
method: 'POST',
url: NOTEBOOK_API_URL,
const getManifest = () =>
sendRequest<TNotebookApiResponse>({
method: 'GET',
url: `${NOTEBOOK_API_URL}/vwb/manifest`,
});
};

const get = () => {
return sendRequest<TNotebookApiResponse>({
const getStatus = () =>
sendRequest<TNotebookApiResponse>({
method: 'GET',
url: NOTEBOOK_API_URL,
url: `${NOTEBOOK_API_URL}/status`,
});
};

const stop = () =>
sendRequest<void>({
Expand All @@ -25,7 +25,7 @@ const stop = () =>
});

export const NotebookApi = {
start,
get,
getManifest,
getStatus,
stop,
};
2 changes: 1 addition & 1 deletion src/services/api/user/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export type TUserConfig = {
};

export enum TUserGroups {
INVESTIGATOR = 'kf-investigator',
BETA = 'kf-beta',
}

export type TUserInsert = Omit<TUser, 'id' | 'keycloak_id' | 'creation_date' | 'update_date'>;
Expand Down
16 changes: 11 additions & 5 deletions src/store/notebook/slice.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { initialState } from './types';
import { getNotebookClusterStatus, startNotebookCluster, stopNotebookCluster } from './thunks';

import { NotebookApiStatus } from 'services/api/notebook/model';

import {
getNotebookClusterManifest,
getNotebookClusterStatus,
stopNotebookCluster,
} from './thunks';
import { initialState } from './types';

export const NotebookState: initialState = {
isLoading: false,
url: '',
Expand All @@ -27,17 +33,17 @@ const notebookSlice = createSlice({
},
extraReducers: (builder) => {
// START cluster
builder.addCase(startNotebookCluster.pending, (state) => ({
builder.addCase(getNotebookClusterManifest.pending, (state) => ({
...state,
isLoading: true,
status: NotebookApiStatus.createInProgress,
error: null,
}));
builder.addCase(startNotebookCluster.fulfilled, (state) => ({
builder.addCase(getNotebookClusterManifest.fulfilled, (state) => ({
...state,
isLoading: false,
}));
builder.addCase(startNotebookCluster.rejected, (state, action) => ({
builder.addCase(getNotebookClusterManifest.rejected, (state, action) => ({
...state,
error: action.payload,
status: NotebookApiStatus.unverified,
Expand Down
11 changes: 6 additions & 5 deletions src/store/notebook/thunks.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { createAsyncThunk } from '@reduxjs/toolkit';

import { NotebookApi } from 'services/api/notebook';
import { TNotebookApiResponse } from 'services/api/notebook/model';
import { RootState } from 'store/types';
import { handleThunkApiReponse } from 'store/utils';

const startNotebookCluster = createAsyncThunk<
const getNotebookClusterManifest = createAsyncThunk<
TNotebookApiResponse,
{
onSuccess: () => void;
},
{ rejectValue: string; state: RootState }
>('notebook/start', async (args, thunkAPI) => {
const { data, error } = await NotebookApi.start();
>('notebook/manifest', async (args, thunkAPI) => {
const { data, error } = await NotebookApi.getManifest();

if (error) {
return thunkAPI.rejectWithValue(error?.message);
Expand All @@ -31,7 +32,7 @@ const getNotebookClusterStatus = createAsyncThunk<
void,
{ rejectValue: string; state: RootState }
>('notebook/get', async (_, thunkAPI) => {
const { data, error } = await NotebookApi.get();
const { data, error } = await NotebookApi.getStatus();

if (error) {
return thunkAPI.rejectWithValue(error?.message);
Expand Down Expand Up @@ -66,4 +67,4 @@ const stopNotebookCluster = createAsyncThunk<
});
});

export { startNotebookCluster, getNotebookClusterStatus, stopNotebookCluster };
export { getNotebookClusterManifest, getNotebookClusterStatus, stopNotebookCluster };
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@
margin-top: 8px;
font-size: 12px;
}

.link {
margin-left: 4px;
margin-right: 4px;
}

.tooltipLinkRight {
margin-right: 4px;
}

.tooltipLinkLeft {
margin-left: 4px;
}
57 changes: 48 additions & 9 deletions src/views/Dashboard/components/DashboardCards/Notebook/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useEffect, useState } from 'react';
import intl from 'react-intl-universal';
import { useDispatch } from 'react-redux';
import { ApiOutlined, RocketOutlined } from '@ant-design/icons';
import ExternalLink from '@ferlab/ui/core/components/ExternalLink';
import PopoverContentLink from '@ferlab/ui/core/components/PopoverContentLink';
import { IFenceService } from '@ferlab/ui/core/components/Widgets/AuthorizedStudies';
import FencesAuthentificationModal from '@ferlab/ui/core/components/Widgets/AuthorizedStudies/FencesAuthentificationModal';
import GridCard from '@ferlab/ui/core/view/v2/GridCard';
Expand All @@ -11,7 +13,7 @@ import CardHeader from 'views/Dashboard/components/CardHeader';
import { DashboardCardProps } from 'views/Dashboard/components/DashboardCards';

import { FENCE_NAMES } from 'common/fenceTypes';
import ZeppelinImg from 'components/assets/appache-zeppelin.png';
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';
Expand All @@ -20,7 +22,7 @@ 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 { getNotebookClusterStatus, startNotebookCluster } from 'store/notebook/thunks';
import { getNotebookClusterManifest, getNotebookClusterStatus } from 'store/notebook/thunks';
import { useUser } from 'store/user';

import styles from './index.module.scss';
Expand Down Expand Up @@ -63,21 +65,22 @@ const Notebook = ({ id, key, className = '' }: DashboardCardProps) => {
const onCloseFenceAuthentificationModal = () => {
setIsFenceModalAuthentificationOpen(false);
if (hasAtLeastOneAuthentificatedFence) {
handleStartNotebookCluster();
handleGetManifest();
}
};

const hasAtLeastOneAuthentificatedFence = useAtLeastOneFenceConnected();

const isAllowed = groups.includes(TUserGroups.INVESTIGATOR);
const isAllowed = groups.includes(TUserGroups.BETA);
const isProcessing = (isLoading || isNotebookStatusInProgress(status)) && !error;

const handleStartNotebookCluster = () =>
const handleGetManifest = () => {
dispatch(
startNotebookCluster({
getNotebookClusterManifest({
onSuccess: () => dispatch(getNotebookClusterStatus()),
}),
);
};

useInterval(
() => {
Expand Down Expand Up @@ -112,16 +115,52 @@ const Notebook = ({ id, key, className = '' }: DashboardCardProps) => {
id={id}
key={key}
title={intl.get('screen.dashboard.cards.notebook.title')}
infoPopover={{
title: intl.get('screen.dashboard.cards.notebook.tooltip.title'),
content: (
<Space direction="vertical" className={styles.content}>
<Text>
{intl.get('screen.dashboard.cards.notebook.tooltip.part1')}
<PopoverContentLink
className={styles.tooltipLinkRight}
externalHref="https://docs.cavatica.org/docs/about-data-cruncher"
title={intl.get('screen.dashboard.cards.notebook.dataStudio')}
/>
{intl.get('screen.dashboard.cards.notebook.tooltip.part2')}
</Text>
<Text>{intl.get('screen.dashboard.cards.notebook.tooltip.part3')}</Text>
<Text>
{intl.get('screen.dashboard.cards.notebook.tooltip.readMore')}
<PopoverContentLink
className={styles.tooltipLinkLeft}
externalHref="https://kidsfirstdrc.org/help-center/accessing-controlled-data-via-dbgap/"
title={intl.get(
'screen.dashboard.cards.notebook.tooltip.applyingForDataAccess',
)}
/>
</Text>
</Space>
),
}}
withHandle
/>
}
content={
<div className={styles.wrapper}>
<Space className={styles.textCentered} align="center" direction="vertical" size={16}>
<div>
<img src={ZeppelinImg} alt="Appache-Zeppelin-Logo" width={105} />
<img src={logo} alt="Appache-Zeppelin-Logo" width={125} />
</div>
<Text>{intl.getHTML('screen.dashboard.cards.notebook.description')}</Text>
<Text>
{intl.get('screen.dashboard.cards.notebook.description.part1')}
<ExternalLink
href="https://docs.cavatica.org/docs/about-data-cruncher"
className={styles.link}
>
{intl.get('screen.dashboard.cards.notebook.dataStudio')}
</ExternalLink>
{intl.get('screen.dashboard.cards.notebook.description.part2')}
</Text>
{!hasAtLeastOneAuthentificatedFence && (
<Button
type="primary"
Expand All @@ -143,7 +182,7 @@ const Notebook = ({ id, key, className = '' }: DashboardCardProps) => {
type="primary"
size="small"
icon={<RocketOutlined width={11} height={14} />}
onClick={() => handleStartNotebookCluster()}
onClick={() => handleGetManifest()}
>
{intl.get('screen.dashboard.cards.notebook.launch')}
</Button>
Expand Down
Loading

0 comments on commit 740a59c

Please sign in to comment.