Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Austenem/CAT-846 update workspace pages #3537

Merged
merged 29 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f14a592
add page links
austenem Aug 23, 2024
38d1fc6
switch to children prop
austenem Aug 24, 2024
e07ef3a
update workspace landing page
austenem Aug 24, 2024
09e3b63
update routes
austenem Aug 26, 2024
9b411ae
add alert and convert title to ts
austenem Aug 26, 2024
e0ace90
update messaging
austenem Aug 26, 2024
e4dc689
add template routes
austenem Aug 26, 2024
91245b8
connect template detail pages to grid
austenem Aug 27, 2024
8507a3a
update imports
austenem Aug 27, 2024
ada0d05
add to template detail
austenem Aug 28, 2024
a7b9756
add login panel and begin detail page
austenem Aug 29, 2024
8af1d33
add components to template detail
austenem Aug 29, 2024
e63c956
merge main
austenem Sep 9, 2024
df2aa8b
add accordion styles
austenem Sep 9, 2024
db57210
remove copy button
austenem Sep 9, 2024
9e57963
add sample dialog
austenem Sep 9, 2024
3b138a8
connect to template data
austenem Sep 9, 2024
600a1fe
add default template
austenem Sep 9, 2024
ea798d1
update table and dialog
austenem Sep 10, 2024
2cf9797
edit link and job type
austenem Sep 10, 2024
35c862f
consolidate table alerts
austenem Sep 10, 2024
f801afe
cleanup
austenem Sep 11, 2024
74873ab
cleanup and add to example type
austenem Sep 11, 2024
d1c57b0
add changelog
austenem Sep 11, 2024
b7d7513
add blank lines
austenem Sep 11, 2024
d5f9690
set default name
austenem Sep 11, 2024
9096e1b
first round of review changes
austenem Sep 11, 2024
2529abc
update to job_types array
austenem Sep 12, 2024
02b0670
sort all template grids
austenem Sep 12, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG-update-workspace-pages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add templates landing page and template detail pages.
20 changes: 20 additions & 0 deletions context/app/routes_workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,23 @@ def workspace_detail(workspace_id):
flask_data=flask_data,
title='Workspace'
)


@blueprint.route('/templates')
def templates():
flask_data = {**get_default_flask_data()}
return render_template(
'base-pages/react-content.html',
flask_data=flask_data,
title='Templates'
)


@blueprint.route('/templates/<template_id>')
def template_detail(template_id):
flask_data = {**get_default_flask_data()}
return render_template(
'base-pages/react-content.html',
flask_data=flask_data,
title='Template'
)
19 changes: 19 additions & 0 deletions context/app/static/js/components/Routes/Routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const Organ = lazy(() => import('js/pages/Organ'));
const Workspaces = lazy(() => import('js/pages/Workspaces'));
const Workspace = lazy(() => import('js/pages/Workspace'));
const WorkspacePleaseWait = lazy(() => import('js/pages/WorkspacePleaseWait'));
const Templates = lazy(() => import('js/pages/Templates'));
const Template = lazy(() => import('js/pages/Template'));
const GeneDetails = lazy(() => import('js/pages/Genes'));
const Biomarkers = lazy(() => import('js/pages/Biomarkers'));
const CellTypes = lazy(() => import('js/pages/CellTypes'));
Expand Down Expand Up @@ -243,6 +245,23 @@ function Routes({ flaskData }) {
);
}

if (urlPath === '/templates') {
return (
<Route>
<Templates />
</Route>
);
}

if (urlPath.startsWith('/templates/')) {
const templateKey = urlPath.split('/').pop();
return (
<Route>
<Template templateKey={templateKey} />
</Route>
);
}

if (urlPath === '/profile') {
return (
<Route>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import ArrowDropDownRounded from '@mui/icons-material/ArrowDropDownRounded';
import Skeleton from '@mui/material/Skeleton';

import { VisualizationIcon } from 'js/shared-styles/icons';
import PrimaryColorAccordion from 'js/shared-styles/accordions/PrimaryColorAccordion';

import { datasetSectionId } from 'js/pages/Dataset/utils';
import { useInView } from 'react-intersection-observer';
import { useHash } from 'js/hooks/useHash';
import { useTrackEntityPageEvent } from '../../useTrackEntityPageEvent';
import StatusIcon from '../../StatusIcon';
import { StyledProcessedDatasetAccordion } from './styles';
import { useProcessedDatasetContext } from './ProcessedDatasetContext';
import useProcessedDataStore from '../store';
import { DetailPageSection } from '../../DetailPageSection';
Expand Down Expand Up @@ -57,7 +57,7 @@ export function ProcessedDatasetAccordion({ children }: PropsWithChildren) {

return (
<DetailPageSection id={datasetSectionId(sectionDataset, 'section')}>
<StyledProcessedDatasetAccordion
<PrimaryColorAccordion
defaultExpanded={defaultExpanded}
expanded={isExpanded}
onChange={(_, expanded) => {
Expand All @@ -80,7 +80,7 @@ export function ProcessedDatasetAccordion({ children }: PropsWithChildren) {
</Typography>
</AccordionSummary>
{dataset && !isLoading ? <AccordionDetails ref={ref}>{children}</AccordionDetails> : <LoadingFallback />}
</StyledProcessedDatasetAccordion>
</PrimaryColorAccordion>
</DetailPageSection>
);
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,6 @@
import Accordion from '@mui/material/Accordion';
import { styled } from '@mui/material/styles';

export const StyledProcessedDatasetAccordion = styled(Accordion)(({ theme }) => ({
'& > .MuiAccordionSummary-root': {
backgroundColor: theme.palette.secondaryContainer.main,
'& > .MuiAccordionSummary-content': {
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
color: theme.palette.primary.main,
'&.Mui-expanded': {
color: theme.palette.common.white,
},
},
'& > .MuiAccordionSummary-expandIconWrapper': {
marginRight: theme.spacing(1),
color: theme.palette.primary.main,
'&.Mui-expanded': {
color: theme.palette.common.white,
},
},
'&.Mui-expanded': {
backgroundColor: theme.palette.primary.main,
},
},
}));

export const StyledSubsectionAccordion = styled(Accordion)(({ theme }) => ({
'& > .MuiAccordionSummary-root': {
flexDirection: 'row-reverse',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import StatusIcon from '../../StatusIcon';

type EntityTypesWithIcons = Exclude<
keyof typeof entityIconMap,
'Support' | 'Collection' | 'Workspace' | 'VerifiedUser'
'Support' | 'Collection' | 'Workspace' | 'WorkspaceTemplate' | 'VerifiedUser'
>;

export interface AssayMetadata {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const entitiesWithStatus = datasetEntityTypes.concat(...publicationEntityTypes);
interface SummaryDataProps extends PropsWithChildren {
entity_type: AllEntityTypes;
entityTypeDisplay?: string;
status: string;
mapped_data_access_level: string;
status?: string;
mapped_data_access_level?: string;
title?: string;
mapped_external_group_name?: string;
otherButtons?: React.ReactNode;
Expand Down Expand Up @@ -58,9 +58,9 @@ function SummaryData({
<>
<SummaryItem
showDivider={Boolean(mapped_external_group_name)}
statusIcon={<StatusIcon status={status} />}
statusIcon={<StatusIcon status={status ?? ''} />}
>
{status} ({mapped_data_access_level})
{status} ({mapped_data_access_level ?? ''})
</SummaryItem>
{mapped_external_group_name && <SummaryItem>{mapped_external_group_name}</SummaryItem>}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import Autocomplete, { AutocompleteRenderInputParams } from '@mui/material/Autoc
import InputAdornment from '@mui/material/InputAdornment';
import Typography from '@mui/material/Typography';

import { Alert } from 'js/shared-styles/alerts/Alert';
import WorkspacesNoDatasetsAlert from 'js/components/workspaces/WorkspacesNoDatasetsAlert';
import WorkspaceDatasetsTable from 'js/components/workspaces/WorkspaceDatasetsTable';
import { UseDatasetsAutocompleteReturnType, SearchAheadHit } from './hooks';
import WorkspaceDatasetsTable from '../WorkspaceDatasetsTable';

function DatasetOption(props: React.HTMLAttributes<HTMLLIElement>, option: SearchAheadHit) {
const {
Expand Down Expand Up @@ -76,7 +76,8 @@ function AddDatasetsTable({
datasetsUUIDs={allDatasets}
disabledIDs={new Set(workspaceDatasets)}
removeDatasets={removeDatasets}
emptyAlert={<Alert severity="info">No datasets available.</Alert>}
emptyAlert={<WorkspacesNoDatasetsAlert />}
copyDatasets
/>
</Stack>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';

import Step, { StepDescription } from 'js/shared-styles/surfaces/Step';
import { Alert } from 'js/shared-styles/alerts/Alert';
import WorkspaceField from 'js/components/workspaces/WorkspaceField';
import { useLaunchWorkspaceStore } from 'js/stores/useWorkspaceModalStore';
import { useSelectItems } from 'js/hooks/useSelectItems';
import InternalLink from 'js/shared-styles/Links/InternalLink';

import WorkspacesNoDatasetsAlert from 'js/components/workspaces/WorkspacesNoDatasetsAlert';
import { useWorkspaceTemplates } from './hooks';
import { CreateWorkspaceFormTypes } from './useCreateWorkspaceForm';
import { CreateTemplateNotebooksTypes } from '../types';
Expand Down Expand Up @@ -181,7 +181,8 @@ function NewWorkspaceDialog({
<WorkspaceDatasetsTable
datasetsUUIDs={allDatasets}
removeDatasets={removeDatasets}
emptyAlert={<Alert severity="info">No datasets available.</Alert>}
emptyAlert={<WorkspacesNoDatasetsAlert />}
copyDatasets
/>
)}
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import React, { PropsWithChildren, useCallback } from 'react';
import { UseFormReturn, FieldErrors } from 'react-hook-form';

import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Button from '@mui/material/Button';
import LoadingButton from '@mui/lab/LoadingButton';
import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
import ArrowDropDownRounded from '@mui/icons-material/ArrowDropDownRounded';

import Step from 'js/shared-styles/surfaces/Step';
import WorkspaceField from 'js/components/workspaces/WorkspaceField';
import { useLaunchWorkspaceStore } from 'js/stores/useWorkspaceModalStore';
import { CreateTemplateNotebooksTypes, TemplateExample } from 'js/components/workspaces/types';
import WorkspaceDatasetsTable from 'js/components/workspaces/WorkspaceDatasetsTable';
import AdvancedConfigOptions from 'js/components/workspaces/AdvancedConfigOptions';
import { StyledSubtitle1 } from 'js/components/workspaces/style';
import WorkspacesNoDatasetsAlert from 'js/components/workspaces/WorkspacesNoDatasetsAlert';
import { useJobTypeName } from 'js/components/workspaces/NewWorkspaceDialog/hooks';
import { CreateWorkspaceFormTypes } from './useCreateWorkspaceForm';

const text = {
overview: {
title: 'Try Sample Workspace',
description:
'Sample workspaces are provided to help you get started with this template and to better understand the types of data that are compatible with it.',
},
datasets: {
title: 'Selected Datasets',
},
configure: {
title: 'Configure Workspace',
selected: {
title: 'Selected Configurations',
description:
'The recommended configurations have been selected for this launch. You can edit these settings in future launches.',
},
advanced: {
title: 'Advanced Configurations (Optional)',
description: 'The suggested advanced configurations for this template have been selected as defaults.',
},
},
};

type ReactHookFormProps = Pick<UseFormReturn<CreateWorkspaceFormTypes>, 'handleSubmit' | 'control'> & {
errors: FieldErrors<CreateWorkspaceFormTypes>;
};

interface NewWorkspaceDialogFromExampleProps {
example: TemplateExample;
dialogIsOpen: boolean;
handleClose: () => void;
onSubmit: ({ workspaceName, templateKeys, uuids }: CreateTemplateNotebooksTypes) => void;
allDatasets: string[];
isSubmitting?: boolean;
}

function NewWorkspaceDialogFromExample({
example,
dialogIsOpen,
handleClose,
handleSubmit,
control,
errors,
onSubmit,
allDatasets,
isSubmitting,
}: PropsWithChildren<NewWorkspaceDialogFromExampleProps & ReactHookFormProps>) {
const { isOpen: isLaunchWorkspaceDialogOpen } = useLaunchWorkspaceStore();
const jobTypeName = useJobTypeName(example);

const submit = useCallback(
({
'workspace-name': workspaceName,
templates: templateKeys,
workspaceJobTypeId,
datasets,
workspaceResourceOptions,
}: CreateWorkspaceFormTypes) => {
onSubmit({
workspaceName,
templateKeys,
uuids: datasets,
workspaceJobTypeId,
workspaceResourceOptions,
});
},
[onSubmit],
);

return (
<Dialog
open={dialogIsOpen && !isLaunchWorkspaceDialogOpen}
onClose={handleClose}
scroll="paper"
aria-labelledby="create-workspace-dialog-title"
maxWidth="lg"
>
<Box mb={2}>
<DialogTitle id="create-workspace-dialog-title" variant="h3">
{text.overview.title}
</DialogTitle>
<Box sx={{ px: 3 }}>
<Stack spacing={1} p={2} component={Paper}>
<Typography>{text.overview.description}</Typography>
<Typography variant="subtitle2">{example.title}</Typography>
<Typography>{example.description}</Typography>
</Stack>
</Box>
</Box>
<DialogContent dividers>
<Step title={text.datasets.title} index={0} hideRequiredText>
<WorkspaceDatasetsTable
datasetsUUIDs={allDatasets}
emptyAlert={<WorkspacesNoDatasetsAlert />}
isSelectable={false}
/>
</Step>
<Step title={text.configure.title} index={1} isRequired>
<Stack
gap={2}
mt={1}
component="form"
id="create-workspace-form"
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onSubmit={handleSubmit(submit)}
>
<WorkspaceField
control={control}
name="workspace-name"
label="Workspace Name"
placeholder="Like “Spleen-Related Data” or “ATAC-seq Visualizations”"
autoFocus
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
e.stopPropagation();
}}
/>
<Accordion>
<AccordionSummary expandIcon={<ArrowDropDownRounded color="primary" />}>
<StyledSubtitle1>{text.configure.selected.title}</StyledSubtitle1>
</AccordionSummary>
<AccordionDetails>
<Stack spacing={1}>
<Typography>{text.configure.selected.description}</Typography>
<Typography variant="subtitle2">Environment</Typography>
<Typography>{jobTypeName}</Typography>
</Stack>
</AccordionDetails>
</Accordion>
<AdvancedConfigOptions control={control} description={text.configure.advanced.description} />
</Stack>
</Step>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<LoadingButton
loading={isSubmitting}
type="submit"
form="create-workspace-form"
disabled={Object.keys(errors).length > 0}
>
Launch Workspace
</LoadingButton>
</DialogActions>
</Dialog>
);
}

export default NewWorkspaceDialogFromExample;
Loading
Loading