diff --git a/CHANGELOG-r-template-updates.md b/CHANGELOG-r-template-updates.md new file mode 100644 index 0000000000..aa4aa8dea4 --- /dev/null +++ b/CHANGELOG-r-template-updates.md @@ -0,0 +1 @@ +- Update Launch Workspace dialogs to account for new R template. \ No newline at end of file diff --git a/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts b/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts index fbb995b710..3e1da15bb8 100644 --- a/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts +++ b/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts @@ -9,15 +9,15 @@ import { TemplatesResponse, CreateTemplateNotebooksTypes, TemplateTagsResponse, - TemplatesTypes, TemplateExample, + TemplatesTypes, WorkspacesEventCategories, } from 'js/components/workspaces/types'; import { useCreateAndLaunchWorkspace, useCreateTemplates } from 'js/components/workspaces/hooks'; import { buildDatasetSymlinks } from 'js/components/workspaces/utils'; import { useWorkspaceToasts } from 'js/components/workspaces/toastHooks'; import { useJobTypes } from 'js/components/workspaces/api'; -import { DEFAULT_JOB_TYPE } from 'js/components/workspaces/constants'; +import { DEFAULT_JOB_TYPE, JUPYTER_LAB_R_JOB_TYPE, R_TEMPLATE_TITLE } from 'js/components/workspaces/constants'; interface UserTemplatesTypes { templatesURL: string; @@ -51,7 +51,14 @@ function useWorkspaceTemplates(tags: string[] = []) { const templates = result?.data?.data ?? {}; const filteredTemplates = Object.fromEntries( - Object.entries(templates).filter(([, template]) => !template?.is_hidden), + Object.entries(templates) + .filter(([, template]) => !template?.is_hidden) + .map(([key, template]) => { + const newTitle = template?.job_types?.includes(JUPYTER_LAB_R_JOB_TYPE) + ? `${template.title} (${R_TEMPLATE_TITLE})` + : template.title; + return [key, { ...template, title: newTitle }]; + }), ) as TemplatesTypes; return { diff --git a/context/app/static/js/components/workspaces/NewWorkspaceDialog/useCreateWorkspaceForm.ts b/context/app/static/js/components/workspaces/NewWorkspaceDialog/useCreateWorkspaceForm.ts index c1912eaffa..4f116c3e12 100644 --- a/context/app/static/js/components/workspaces/NewWorkspaceDialog/useCreateWorkspaceForm.ts +++ b/context/app/static/js/components/workspaces/NewWorkspaceDialog/useCreateWorkspaceForm.ts @@ -20,7 +20,7 @@ import { DEFAULT_JOB_TYPE, DEFAULT_MEMORY_MB, DEFAULT_NUM_CPUS, - DEFAULT_TEMPLATE_KEY, + DEFAULT_PYTHON_TEMPLATE_KEY, DEFAULT_TIME_LIMIT_MINUTES, } from '../constants'; import { useDatasetsAutocomplete } from '../AddDatasetsTable/hooks'; @@ -59,7 +59,7 @@ const schema = z function useCreateWorkspaceForm({ defaultName, - defaultTemplate = DEFAULT_TEMPLATE_KEY, + defaultTemplate = DEFAULT_PYTHON_TEMPLATE_KEY, defaultJobType = DEFAULT_JOB_TYPE, defaultResourceOptions = { num_cpus: DEFAULT_NUM_CPUS, diff --git a/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx b/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx index 99c2c5bba7..5bfa0ff685 100644 --- a/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx +++ b/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, ChangeEvent } from 'react'; +import React, { useCallback, ChangeEvent, useMemo, useEffect } from 'react'; import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; import Box from '@mui/material/Box'; @@ -7,9 +7,12 @@ import { useController, Control, Path } from 'react-hook-form'; import { SpacedSectionButtonRow } from 'js/shared-styles/sections/SectionButtonRow'; import { useSelectItems } from 'js/hooks/useSelectItems'; import ErrorOrWarningMessages from 'js/shared-styles/alerts/ErrorOrWarningMessages'; +import { DEFAULT_R_TEMPLATE_KEY, JUPYTER_LAB_R_JOB_TYPE } from 'js/components/workspaces/constants'; + import { TemplatesTypes, WorkspacesEventCategories } from '../types'; import TemplateGrid from '../TemplateGrid'; import { FormWithTemplates } from '../NewWorkspaceDialog/useCreateWorkspaceForm'; +import { sortTemplates } from '../utils'; interface TemplateGridProps { disabledTemplates?: TemplatesTypes; @@ -39,9 +42,26 @@ function SelectableTemplateGrid({ control, name: inputName as Path, }); - const { selectedItems: selectedTemplates, setSelectedItems: setSelectedTemplates } = useSelectItems( - field.value satisfies FormType[typeof inputName], - ); + const { + selectedItems: selectedTemplates, + setSelectedItems: setSelectedTemplates, + addItem, + } = useSelectItems(field.value satisfies FormType[typeof inputName]); + + const { field: jobType } = useController({ + name: 'workspaceJobTypeId' as Path, + control, + rules: { required: true }, + }); + + // If the Python + R job type is selected, select the default R template + useEffect(() => { + if (jobType.value === JUPYTER_LAB_R_JOB_TYPE) { + addItem(DEFAULT_R_TEMPLATE_KEY); + } + }, [jobType.value, addItem]); + + const sortedTemplates = useMemo(() => sortTemplates(templates, disabledTemplates), [templates, disabledTemplates]); const updateTemplates = useCallback( (templateKeys: string[]) => { @@ -87,11 +107,12 @@ function SelectableTemplateGrid({ } /> ); diff --git a/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx b/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx index 055314d9a1..c57eca9c1d 100644 --- a/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx +++ b/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx @@ -3,6 +3,7 @@ import Grid from '@mui/material/Grid'; import SelectableCard from 'js/shared-styles/cards/SelectableCard/SelectableCard'; import { InternalLink } from 'js/shared-styles/Links'; import { sortTemplates } from 'js/components/workspaces/utils'; +import { JUPYTER_LAB_R_JOB_TYPE } from 'js/components/workspaces/constants'; import { TemplatesTypes, WorkspacesEventInfo } from 'js/components/workspaces/types'; import { trackEvent } from 'js/helpers/trackers'; @@ -12,6 +13,7 @@ interface TemplateGridProps { selectedTemplates?: Set; disabledTemplates?: TemplatesTypes; trackingInfo: WorkspacesEventInfo; + jobType?: string; } function TemplateGrid({ @@ -20,12 +22,24 @@ function TemplateGrid({ selectedTemplates = new Set([]), disabledTemplates = {}, trackingInfo, + jobType, }: TemplateGridProps) { + const getTooltip = (templateKey: string, job_types?: string[]) => { + if (templateKey in disabledTemplates) { + return 'This template is already in your workspace.'; + } + // If the template is an R template and the job type is not R + if (jobType !== JUPYTER_LAB_R_JOB_TYPE && job_types?.includes(JUPYTER_LAB_R_JOB_TYPE)) { + return 'This template is not compatible with your current environment. To avoid potential issues, please ensure that you have selected the correct environment for your workspace.'; + } + return undefined; + }; + const sortedTemplates = useMemo(() => sortTemplates(templates, disabledTemplates), [templates, disabledTemplates]); return ( - {Object.entries(sortedTemplates).map(([templateKey, { title, description, tags }]) => ( + {Object.entries(sortedTemplates).map(([templateKey, { title, description, tags, job_types }]) => ( ))} diff --git a/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx b/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx index a7ef6bcd44..6cb4c9861d 100644 --- a/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx +++ b/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { Control } from 'react-hook-form'; import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; import { SelectedItems } from 'js/hooks/useSelectItems'; import Step, { StepDescription } from 'js/shared-styles/surfaces/Step'; import ContactUsLink from 'js/shared-styles/Links/ContactUsLink'; -import Typography from '@mui/material/Typography'; -import SelectableTemplateGrid from '../SelectableTemplateGrid'; -import { TemplatesTypes, WorkspacesEventCategories } from '../types'; -import { FormWithTemplates } from '../NewWorkspaceDialog/useCreateWorkspaceForm'; -import TemplateTagsAutocomplete from '../TemplateTagsAutocomplete'; +import SelectableTemplateGrid from 'js/components/workspaces/SelectableTemplateGrid'; +import { TemplatesTypes, WorkspacesEventCategories } from 'js/components/workspaces/types'; +import { FormWithTemplates } from 'js/components/workspaces/NewWorkspaceDialog/useCreateWorkspaceForm'; +import TemplateTagsAutocomplete from 'js/components/workspaces/TemplateTagsAutocomplete'; function ContactPrompt() { return ( @@ -24,8 +24,6 @@ const description = [ , ]; -const recommendedTags = ['visualization', 'api']; - interface TemplateSelectProps { title: string; stepIndex?: number; @@ -58,7 +56,6 @@ function TemplateSelectStep({ void; setSelectedTags: React.Dispatch>; selectedRecommendedTags: SelectedItems; @@ -30,7 +30,6 @@ function TagComponent({ option, ...rest }: TagTypes) { function TemplateTagsAutocomplete({ selectedTags, - recommendedTags, toggleTag, setSelectedTags, selectedRecommendedTags, @@ -43,7 +42,7 @@ function TemplateTagsAutocomplete({ !recommendedTags.includes(tag)) + .filter((tag) => !RECOMMENDED_TAGS.includes(tag)) .sort((a, b) => a.localeCompare(b))} multiple filterSelectedOptions @@ -73,7 +72,7 @@ function TemplateTagsAutocomplete({ Recommended Tags - {recommendedTags.map((tag) => ( + {RECOMMENDED_TAGS.map((tag) => ( ([]); const { templates } = useWorkspaceTemplates([...selectedTags, ...selectedRecommendedTags]); - const recommendedTags = ['visualization', 'api']; - return ( {!isAuthenticated && ( @@ -159,7 +157,6 @@ function TemplatePreviewSection({ trackingInfo }: TemplatePreviewSectionProps) {