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

John conroy/update workspace templates #3350

Merged
merged 32 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2a50202
Add hooks to update workspaces
john-conroy Nov 27, 2023
9dbdba9
Add store to control edit dialog
john-conroy Nov 27, 2023
a7533f0
Create basic workspace name dialog
john-conroy Nov 27, 2023
25b1c68
Reset form on open
john-conroy Nov 27, 2023
35de9e3
Handle disabled button tooltip
john-conroy Nov 28, 2023
3e181d2
Pass mouse events to tooltip button
john-conroy Nov 28, 2023
8c4ed59
Fix update types
john-conroy Nov 28, 2023
d963730
Handle update errors
john-conroy Nov 28, 2023
fa93bbf
Dedupe workspace stores
john-conroy Nov 28, 2023
760b306
Share workspace form fields
john-conroy Nov 29, 2023
72998dc
Jump through typescript hoops
john-conroy Nov 29, 2023
ad0190a
Add step styling to modal
john-conroy Nov 29, 2023
5d082b6
Pull template select step into own component
john-conroy Nov 29, 2023
fd8177b
Pull out create templates hook for reuse
john-conroy Nov 30, 2023
61fec81
Add edit templates dialog
john-conroy Nov 30, 2023
2b3f96d
Match templates in current workspace details
john-conroy Nov 30, 2023
4c27d62
Add comment
john-conroy Nov 30, 2023
78fb8f6
Disable templates already in workspace
john-conroy Nov 30, 2023
244a508
Do not select disabled templates when selecting all
john-conroy Nov 30, 2023
71e3a1b
Add tooltip if template is already in workspace
john-conroy Nov 30, 2023
352477e
Create reusable edit workspace dialog
john-conroy Nov 30, 2023
cb675c1
Move workspace templates and datasets into swr hook
john-conroy Dec 1, 2023
755022a
Handle reset after submit successful
john-conroy Dec 1, 2023
4358ca9
Consolidate edit dialogs
john-conroy Dec 1, 2023
682be52
Add changelog
john-conroy Dec 1, 2023
f1307ae
Pass workspace datasets to new templates
john-conroy Dec 1, 2023
dee066e
Fix TS errors related to template form types (#3353)
NickAkhmetov Dec 1, 2023
f87aa90
Update context/app/static/js/shared-styles/cards/SelectableCard/Selec…
john-conroy Dec 1, 2023
185feb0
Use store instead of props
john-conroy Dec 1, 2023
1214ff7
Use padding prop in stack in step
john-conroy Dec 1, 2023
cd93dc3
Use promise chain to avoid async submit callback
john-conroy Dec 1, 2023
2ab322c
Console error error
john-conroy Dec 1, 2023
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-templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add dialogs to workspace detail page to update workspace name and templates.
6 changes: 4 additions & 2 deletions context/app/static/js/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { StyledAlert, FlexContainer } from './style';
// Importing Search styles here so the CSS import order is correct.
import 'js/components/searchPage/Search.scss';
import LaunchWorkspaceDialog from './workspaces/LaunchWorkspaceDialog/LaunchWorkspaceDialog';
import EditWorkspaceDialog from './workspaces/EditWorkspaceDialog';
import MarkdownRenderer from './Markdown/MarkdownRenderer';

// TODO: Delete this when workspaces are publicly released.
Expand Down Expand Up @@ -70,10 +71,11 @@ function App(props) {
<Routes flaskData={flaskData} />
<Footer />
<StyledSnackbar />
{/* This dialog is placed at the root of the application because
it is launchable from many places. In the future, this should be
{/* These dialogs are placed at the root of the application because
they are launchable from many places. In the future, this should be
improved on by using a global modal stack with portals. */}
<LaunchWorkspaceDialog />
<EditWorkspaceDialog />
</Providers>
</StrictMode>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { useCallback, PropsWithChildren, BaseSyntheticEvent } from 'react';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import LoadingButton from '@mui/lab/LoadingButton';
import { UseFormReturn, FormState, FieldValues } from 'react-hook-form';

import DialogModal from 'js/shared-styles/DialogModal';
import { useEditWorkspaceStore } from 'js/stores/useWorkspaceModalStore';
import { useSnackbarActions } from 'js/shared-styles/snackbars';
import EditWorkspaceTemplatesDialog from '../EditWorkspaceTemplatesDialog';
import EditWorkspaceNameDialog from '../EditWorkspaceNameDialog';

const formId = 'edit-workspace-form';

type FormProps<T extends FieldValues> = Pick<UseFormReturn<T>, 'reset' | 'handleSubmit'> & Pick<FormState<T>, 'errors'>;

interface EditWorkspaceDialogTypes<T extends FieldValues> extends PropsWithChildren {
title: string;
isSubmitting: boolean;
onSubmit: (fieldValues: T) => Promise<void>;
}

function EditWorkspaceDialogContent<T extends FieldValues>({
title,
reset,
children,
onSubmit,
handleSubmit,
errors,
isSubmitting,
}: EditWorkspaceDialogTypes<T> & FormProps<T>) {
const { isOpen, close } = useEditWorkspaceStore();
const { toastSuccess, toastError } = useSnackbarActions();

const submit = useCallback(
(e: BaseSyntheticEvent) => {
if (isSubmitting) return;

handleSubmit(onSubmit)(e)
.then(() => {
toastSuccess('Workspace successfully updated.');
reset();
close();
})
.catch((error) => {
console.error(error);
toastError('Failed to update workspace.');
});
},
[onSubmit, reset, close, isSubmitting, handleSubmit, toastError, toastSuccess],
);

return (
<DialogModal
title={title}
maxWidth="lg"
content={
<form id={formId} onSubmit={submit}>
{children}
</form>
}
isOpen={isOpen}
handleClose={close}
actions={
<Stack direction="row" spacing={2} alignItems="end">
<Button type="button" onClick={close} disabled={isSubmitting}>
Cancel
</Button>
<LoadingButton loading={isSubmitting} type="submit" form={formId} disabled={Object.keys(errors).length > 0}>
Save
</LoadingButton>
</Stack>
}
withCloseButton
/>
);
}

function EditWorkspaceDialog() {
const { workspace, dialogType } = useEditWorkspaceStore();

if (!workspace) {
return null;
}
if (dialogType === 'UPDATE_NAME') {
return <EditWorkspaceNameDialog workspace={workspace} />;
}

if (dialogType === 'UPDATE_TEMPLATES') {
return <EditWorkspaceTemplatesDialog workspace={workspace} />;
}

return null;
}

export { EditWorkspaceDialogContent };
export default EditWorkspaceDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import EditWorkspaceDialog, { EditWorkspaceDialogContent } from './EditWorkspaceDialog';

export { EditWorkspaceDialogContent };
export default EditWorkspaceDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useCallback } from 'react';

import Step from 'js/shared-styles/surfaces/Step';
import { useEditWorkspaceForm, EditWorkspaceFormTypes } from './hooks';
import WorkspaceField from '../WorkspaceField/WorkspaceField';
import { EditWorkspaceDialogContent } from '../EditWorkspaceDialog';
import { Workspace } from '../types';

function EditWorkspaceNameDialog({ workspace }: { workspace: Workspace }) {
const workspaceName = workspace.name;
const workspaceId = workspace.id;
const { onSubmit, control, handleSubmit, isSubmitting, errors, reset } = useEditWorkspaceForm({
defaultName: workspaceName,
workspaceId,
});

const submit = useCallback(
async ({ 'workspace-name': wsName }: EditWorkspaceFormTypes) => {
await onSubmit({
workspaceName: wsName,
});
},
[onSubmit],
);

return (
<EditWorkspaceDialogContent
title="Edit Workspace Name"
reset={reset}
handleSubmit={handleSubmit}
onSubmit={submit}
errors={errors}
isSubmitting={isSubmitting}
>
<Step title="Rename Workspace">
<WorkspaceField
control={control}
name="workspace-name"
label="Name"
placeholder="Like “Spleen-Related Data” or “ATAC-seq Visualizations”"
autoFocus
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
e.stopPropagation();
}}
/>
</Step>
</EditWorkspaceDialogContent>
);
}

export default EditWorkspaceNameDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

import { useHandleUpdateWorkspace } from '../hooks';
import { workspaceNameField } from '../workspaceFormFields';

interface EditWorkspaceFormTypes {
'workspace-name': string;
}

interface UseEditWorkspaceNameFormTypes {
defaultName: string;
workspaceId: number;
}

interface SubmitEditWorkspaceNameTypes {
workspaceName: string;
}

const schema = z
.object({
...workspaceNameField,
})
.partial()
.required({ 'workspace-name': true });

function useEditWorkspaceForm({ defaultName, workspaceId }: UseEditWorkspaceNameFormTypes) {
const { handleUpdateWorkspace } = useHandleUpdateWorkspace({ workspaceId });

const {
handleSubmit,
control,
reset,
formState: { errors, isSubmitting, isSubmitSuccessful },
} = useForm({
defaultValues: {
'workspace-name': defaultName,
},
mode: 'onChange',
resolver: zodResolver(schema),
});

async function onSubmit({ workspaceName }: SubmitEditWorkspaceNameTypes) {
await handleUpdateWorkspace({ name: workspaceName });
}

return {
handleSubmit,
control,
errors,
onSubmit,
reset,
isSubmitting: isSubmitting || isSubmitSuccessful,
};
}

export { useEditWorkspaceForm, type EditWorkspaceFormTypes };
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import EditWorkspaceNameDialog from './EditWorkspaceNameDialog';

export default EditWorkspaceNameDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState, useCallback } from 'react';

import { useSelectItems } from 'js/hooks/useSelectItems';
import { useWorkspaceTemplates, useWorkspaceTemplateTags } from 'js/components/workspaces/NewWorkspaceDialog/hooks';
import TemplateSelectStep from '../TemplateSelectStep';
import { useEditWorkspaceForm, EditTemplatesFormTypes } from './hooks';
import { Workspace } from '../types';
import { EditWorkspaceDialogContent } from '../EditWorkspaceDialog';
import { useWorkspaceDetail } from '../hooks';

function EditWorkspaceTemplatesDialog({ workspace }: { workspace: Workspace }) {
const { selectedItems: selectedRecommendedTags, toggleItem: toggleTag } = useSelectItems([]);
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const { templates } = useWorkspaceTemplates([...selectedTags, ...selectedRecommendedTags]);
const { tags } = useWorkspaceTemplateTags();

const workspaceId = workspace.id;
const { workspaceTemplates, workspaceDatasets } = useWorkspaceDetail({ workspaceId });

const { onSubmit, control, handleSubmit, isSubmitting, errors, reset } = useEditWorkspaceForm({
workspaceId,
});

const submit = useCallback(
async ({ templates: templateKeys }: EditTemplatesFormTypes) => {
await onSubmit({
templateKeys,
uuids: workspaceDatasets,
});
},
[onSubmit, workspaceDatasets],
);

return (
<EditWorkspaceDialogContent
title="Add Templates"
reset={reset}
handleSubmit={handleSubmit}
onSubmit={submit}
errors={errors}
isSubmitting={isSubmitting}
>
<TemplateSelectStep
title="Add Templates"
control={control}
toggleTag={toggleTag}
tags={tags}
selectedRecommendedTags={selectedRecommendedTags}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
templates={templates}
disabledTemplates={workspaceTemplates}
/>
</EditWorkspaceDialogContent>
);
}

export default EditWorkspaceTemplatesDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

import { useHandleUpdateWorkspace, useCreateTemplates } from '../hooks';
import { templatesField } from '../workspaceFormFields';
import { FormWithTemplates } from '../NewWorkspaceDialog/useCreateWorkspaceForm';

type EditTemplatesFormTypes = FormWithTemplates;

interface UseEditTemplatesFormTypes {
workspaceId: number;
}

interface SubmitEditTemplatesTypes {
templateKeys: string[];
uuids: string[];
}

const schema = z
.object({
...templatesField,
})
.partial()
.required({ templates: true });

function useEditWorkspaceForm({ workspaceId }: UseEditTemplatesFormTypes) {
const { handleUpdateWorkspace } = useHandleUpdateWorkspace({ workspaceId });
const { createTemplates } = useCreateTemplates();

const {
handleSubmit,
control,
reset,
formState: { errors, isSubmitting, isSubmitSuccessful },
} = useForm<EditTemplatesFormTypes>({
defaultValues: {
templates: [],
},
mode: 'onChange',
resolver: zodResolver(schema),
});

async function onSubmit({ templateKeys, uuids }: SubmitEditTemplatesTypes) {
const templatesDetails = await createTemplates({ templateKeys, uuids });
await handleUpdateWorkspace({
workspace_details: {
files: templatesDetails,
},
});
}

return {
handleSubmit,
control,
errors,
onSubmit,
reset,
isSubmitting: isSubmitting || isSubmitSuccessful,
};
}

export { useEditWorkspaceForm, type EditTemplatesFormTypes };
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import EditWorkspaceTemplatesDialog from './EditWorkspaceTemplatesDialog';

export default EditWorkspaceTemplatesDialog;
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import Typography from '@mui/material/Typography';
import DialogModal from 'js/shared-styles/DialogModal';
import { useSnackbarActions } from 'js/shared-styles/snackbars';

import { useLaunchWorkspaceStore } from 'js/stores/useWorkspaceModalStore';
import { useLaunchWorkspace, useRunningWorkspace, useWorkspacesList } from '../hooks';
import { useLaunchWorkspaceStore } from './store';

function LaunchWorkspaceDialog() {
const runningWorkspace = useRunningWorkspace();
Expand Down
Loading
Loading