From dae8a348abbfb6bc2625b6d2c636ae16f34934dd Mon Sep 17 00:00:00 2001 From: Austen Money Date: Fri, 13 Sep 2024 12:38:16 -0400 Subject: [PATCH 1/8] re-sort and add r tags --- .../useCreateWorkspaceForm.ts | 4 +- .../workspaces/TemplateGrid/TemplateGrid.tsx | 37 +++++++++++-------- .../TemplateSelectStep/TemplateSelectStep.tsx | 11 +++--- .../js/components/workspaces/constants.ts | 7 +++- .../static/js/components/workspaces/types.ts | 1 + .../static/js/components/workspaces/utils.ts | 18 ++++++--- .../cards/SelectableCard/SelectableCard.tsx | 1 + 7 files changed, 50 insertions(+), 29 deletions(-) diff --git a/context/app/static/js/components/workspaces/NewWorkspaceDialog/useCreateWorkspaceForm.ts b/context/app/static/js/components/workspaces/NewWorkspaceDialog/useCreateWorkspaceForm.ts index a63ade7abf..5d55e7831a 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'; @@ -80,7 +80,7 @@ function useCreateWorkspaceForm({ defaultValues: { 'workspace-name': checkedWorkspaceName, 'protected-datasets': checkedProtectedDatasets, - templates: [defaultTemplate ?? DEFAULT_TEMPLATE_KEY], + templates: [defaultTemplate ?? DEFAULT_PYTHON_TEMPLATE_KEY], workspaceJobTypeId: DEFAULT_JOB_TYPE, workspaceResourceOptions: { num_cpus: DEFAULT_NUM_CPUS, diff --git a/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx b/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx index a0d75a09c8..57b4f0a35a 100644 --- a/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx +++ b/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx @@ -2,6 +2,7 @@ import React, { ChangeEvent } from 'react'; import Grid from '@mui/material/Grid'; import SelectableCard from 'js/shared-styles/cards/SelectableCard/SelectableCard'; +import { R_JOB_TYPE, R_TEMPLATE_LABEL } from 'js/components/workspaces/constants'; import { TemplatesTypes } from '../types'; interface TemplateGridProps { @@ -19,22 +20,26 @@ function TemplateGrid({ }: TemplateGridProps) { return ( - {Object.entries(templates).map(([templateKey, { title, description, tags }]) => ( - - - - ))} + {Object.entries(templates).map(([templateKey, { title, description, tags, job_types }]) => { + const isRTemplate = job_types?.includes(R_JOB_TYPE); + return ( + + + + ); + })} ); } diff --git a/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx b/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx index 607c0dee2d..665f0cd106 100644 --- a/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx +++ b/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx @@ -6,10 +6,11 @@ import Stack from '@mui/material/Stack'; import { SelectedItems } from 'js/hooks/useSelectItems'; import Step, { StepDescription } from 'js/shared-styles/surfaces/Step'; import ContactUsLink from 'js/shared-styles/Links/ContactUsLink'; -import SelectableTemplateGrid from '../SelectableTemplateGrid'; -import { TemplatesTypes } from '../types'; -import { FormWithTemplates } from '../NewWorkspaceDialog/useCreateWorkspaceForm'; -import TemplateTagsAutocomplete from '../TemplateTagsAutocomplete'; +import SelectableTemplateGrid from 'js/components/workspaces/SelectableTemplateGrid'; +import { TemplatesTypes } from 'js/components/workspaces/types'; +import { FormWithTemplates } from 'js/components/workspaces/NewWorkspaceDialog/useCreateWorkspaceForm'; +import TemplateTagsAutocomplete from 'js/components/workspaces/TemplateTagsAutocomplete'; +import { R_TEMPLATE_LABEL } from 'js/components/workspaces/constants'; function ContactPrompt() { return ( @@ -24,7 +25,7 @@ const description = [ , ]; -const recommendedTags = ['visualization', 'api']; +const recommendedTags = ['visualization', 'api', R_TEMPLATE_LABEL]; interface TemplateSelectProps { title: string; diff --git a/context/app/static/js/components/workspaces/constants.ts b/context/app/static/js/components/workspaces/constants.ts index 6d0c150846..8d06215f6a 100644 --- a/context/app/static/js/components/workspaces/constants.ts +++ b/context/app/static/js/components/workspaces/constants.ts @@ -1,6 +1,11 @@ /* Workspace defaults */ export const DEFAULT_JOB_TYPE = 'jupyter_lab'; -export const DEFAULT_TEMPLATE_KEY = 'blank'; +export const DEFAULT_PYTHON_TEMPLATE_KEY = 'blank'; +export const DEFAULT_R_TEMPLATE_KEY = 'blank_r'; + +/* Workspace R templates */ +export const R_TEMPLATE_LABEL = 'Jupyter Lab: Python + R'; +export const R_JOB_TYPE = 'jupyter_lab_r'; /* Workspace resource defaults */ export const DEFAULT_NUM_CPUS = 1; diff --git a/context/app/static/js/components/workspaces/types.ts b/context/app/static/js/components/workspaces/types.ts index dad088b505..4bee1abe1b 100644 --- a/context/app/static/js/components/workspaces/types.ts +++ b/context/app/static/js/components/workspaces/types.ts @@ -108,6 +108,7 @@ interface TemplateTypes { is_multi_dataset_template: boolean; template_format: string; is_hidden: boolean; + job_types?: string[]; } type TemplatesTypes = Record; diff --git a/context/app/static/js/components/workspaces/utils.ts b/context/app/static/js/components/workspaces/utils.ts index 9543ef3617..ecdfa91cf9 100644 --- a/context/app/static/js/components/workspaces/utils.ts +++ b/context/app/static/js/components/workspaces/utils.ts @@ -3,7 +3,8 @@ import { DEFAULT_JOB_TYPE, DEFAULT_MEMORY_MB, DEFAULT_NUM_CPUS, - DEFAULT_TEMPLATE_KEY, + DEFAULT_PYTHON_TEMPLATE_KEY, + DEFAULT_R_TEMPLATE_KEY, DEFAULT_TIME_LIMIT_MINUTES, } from './constants'; import { @@ -316,8 +317,8 @@ function getDefaultJobType({ workspace }: { workspace: Workspace }) { } /** - * Sort templates alphabetically by title, with all disabled templates first, then the default template. - * Ex: [disabled1, disabled2, default, templateA, templateB, ...] + * Sort templates alphabetically by title, with all disabled templates first, then the default python template, then the default R template. + * Ex: [disabled1, disabled2, defaultPython, defaultR, templateA, templateB, ...] * @param templates The templates to sort. * @param disabledTemplates The templates that are disabled. * @returns The sorted templates. @@ -328,12 +329,19 @@ function sortTemplates(templates: TemplatesTypes, disabledTemplates?: TemplatesT const isSelectedA = disabledTemplates && keyA in disabledTemplates; const isSelectedB = disabledTemplates && keyB in disabledTemplates; + // Disabled templates if (isSelectedA && !isSelectedB) return -1; if (!isSelectedA && isSelectedB) return 1; - if (keyA === DEFAULT_TEMPLATE_KEY && keyB !== DEFAULT_TEMPLATE_KEY) return -1; - if (keyB === DEFAULT_TEMPLATE_KEY && keyA !== DEFAULT_TEMPLATE_KEY) return 1; + // Default Python template + if (keyA === DEFAULT_PYTHON_TEMPLATE_KEY && keyB !== DEFAULT_PYTHON_TEMPLATE_KEY) return -1; + if (keyB === DEFAULT_PYTHON_TEMPLATE_KEY && keyA !== DEFAULT_PYTHON_TEMPLATE_KEY) return 1; + // Default R template + if (keyA === DEFAULT_R_TEMPLATE_KEY && keyB !== DEFAULT_PYTHON_TEMPLATE_KEY) return -1; + if (keyB === DEFAULT_R_TEMPLATE_KEY && keyA !== DEFAULT_PYTHON_TEMPLATE_KEY) return 1; + + // Alphabetical sorting by title for remaining templates return templateA.title.localeCompare(templateB.title); }), ) as TemplatesTypes; diff --git a/context/app/static/js/shared-styles/cards/SelectableCard/SelectableCard.tsx b/context/app/static/js/shared-styles/cards/SelectableCard/SelectableCard.tsx index b0a12c700f..f2d4e790c8 100644 --- a/context/app/static/js/shared-styles/cards/SelectableCard/SelectableCard.tsx +++ b/context/app/static/js/shared-styles/cards/SelectableCard/SelectableCard.tsx @@ -30,6 +30,7 @@ function SelectableCard({ ...rest }: SelectableCardProps) { const colorVariant = isSelected ? 'primaryContainer' : 'secondaryContainer'; + return ( From f81e10fc349ddd8d5e1d9e6c011eb841cf7d2503 Mon Sep 17 00:00:00 2001 From: Austen Money Date: Mon, 16 Sep 2024 12:10:25 -0400 Subject: [PATCH 2/8] filter templates correctly --- .../workspaces/NewWorkspaceDialog/hooks.ts | 29 ++++++++++++------- .../workspaces/TemplateGrid/TemplateGrid.tsx | 6 ++-- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts b/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts index da0fc7f0c1..4a0ad50ad2 100644 --- a/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts +++ b/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts @@ -5,15 +5,11 @@ import { useAppContext } from 'js/components/Contexts'; import { fetcher } from 'js/helpers/swr'; import { trackEvent } from 'js/helpers/trackers'; import { SWRError } from 'js/helpers/swr/errors'; -import { - TemplatesResponse, - CreateTemplateNotebooksTypes, - TemplateTagsResponse, - TemplatesTypes, -} from 'js/components/workspaces/types'; +import { TemplatesResponse, CreateTemplateNotebooksTypes, TemplateTagsResponse } 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 { R_JOB_TYPE, R_TEMPLATE_LABEL } from 'js/components/workspaces/constants'; interface UserTemplatesTypes { templatesURL: string; @@ -39,16 +35,27 @@ function useUserTemplatesAPI({ templatesURL, config = { fallbackData: {} } }: function useWorkspaceTemplates(tags: string[] = []) { const { userTemplatesEndpoint } = useAppContext(); - const queryParams = tags.map((tag, i) => `${i === 0 ? '' : '&'}tags=${encodeURIComponent(tag)}`).join(''); - - const url = `${userTemplatesEndpoint}/templates/jupyter_lab/?${queryParams}`; + const url = `${userTemplatesEndpoint}/templates/jupyter_lab`; const result = useUserTemplatesAPI({ templatesURL: url }); const templates = result?.data?.data ?? {}; + // Manually update tags and title for R templates + const updatedTemplates = Object.fromEntries( + Object.entries(templates).map(([key, template]) => { + const isRTemplate = template.job_types?.includes(R_JOB_TYPE); + + const updatedTitle = isRTemplate ? `${template.title} (${R_TEMPLATE_LABEL})` : template.title; + const updatedTags = [...template.tags, ...(isRTemplate ? [R_TEMPLATE_LABEL] : [])]; + + return [key, { ...template, tags: updatedTags, title: updatedTitle }]; + }), + ); + + // Filter templates by tags const filteredTemplates = Object.fromEntries( - Object.entries(templates).filter(([, template]) => !template?.is_hidden), - ) as TemplatesTypes; + Object.entries(updatedTemplates).filter(([_, template]) => tags.every((tag) => template.tags?.includes(tag))), + ); return { templates: filteredTemplates, diff --git a/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx b/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx index 57b4f0a35a..9f41183cdb 100644 --- a/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx +++ b/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx @@ -2,7 +2,6 @@ import React, { ChangeEvent } from 'react'; import Grid from '@mui/material/Grid'; import SelectableCard from 'js/shared-styles/cards/SelectableCard/SelectableCard'; -import { R_JOB_TYPE, R_TEMPLATE_LABEL } from 'js/components/workspaces/constants'; import { TemplatesTypes } from '../types'; interface TemplateGridProps { @@ -21,13 +20,12 @@ function TemplateGrid({ return ( {Object.entries(templates).map(([templateKey, { title, description, tags, job_types }]) => { - const isRTemplate = job_types?.includes(R_JOB_TYPE); return ( Date: Mon, 16 Sep 2024 12:47:59 -0400 Subject: [PATCH 3/8] select r template automatically --- .../SelectableTemplateGrid.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx b/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx index 23f9ac9698..d54eaca3ed 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, useMemo } 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,6 +7,7 @@ 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, R_JOB_TYPE } from 'js/components/workspaces/constants'; import { TemplatesTypes } from '../types'; import TemplateGrid from '../TemplateGrid'; import { FormWithTemplates } from '../NewWorkspaceDialog/useCreateWorkspaceForm'; @@ -44,6 +45,20 @@ function SelectableTemplateGrid({ 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 === R_JOB_TYPE) { + setSelectedTemplates([...selectedTemplates, DEFAULT_R_TEMPLATE_KEY]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [jobType.value, setSelectedTemplates]); + const sortedTemplates = useMemo(() => sortTemplates(templates, disabledTemplates), [templates, disabledTemplates]); const updateTemplates = useCallback( From 3685188d8566c0ea3220841634d29fb086cde04b Mon Sep 17 00:00:00 2001 From: Austen Money Date: Mon, 16 Sep 2024 13:09:51 -0400 Subject: [PATCH 4/8] add tooltip to r templates --- .../SelectableTemplateGrid.tsx | 1 + .../workspaces/TemplateGrid/TemplateGrid.tsx | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx b/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx index d54eaca3ed..a536f38e4d 100644 --- a/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx +++ b/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx @@ -109,6 +109,7 @@ function SelectableTemplateGrid({ selectItem={selectItem} selectedTemplates={selectedTemplates} disabledTemplates={disabledTemplates} + jobType={jobType.value as string} /> ); diff --git a/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx b/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx index 9f41183cdb..e5790f8848 100644 --- a/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx +++ b/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx @@ -2,6 +2,7 @@ import React, { ChangeEvent } from 'react'; import Grid from '@mui/material/Grid'; import SelectableCard from 'js/shared-styles/cards/SelectableCard/SelectableCard'; +import { R_JOB_TYPE } from 'js/components/workspaces/constants'; import { TemplatesTypes } from '../types'; interface TemplateGridProps { @@ -9,6 +10,7 @@ interface TemplateGridProps { selectItem?: (e: ChangeEvent) => void; selectedTemplates?: Set; disabledTemplates?: TemplatesTypes; + jobType?: string; } function TemplateGrid({ @@ -16,7 +18,19 @@ function TemplateGrid({ selectItem, selectedTemplates = new Set([]), disabledTemplates = {}, + 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 !== R_JOB_TYPE && job_types?.includes(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; + }; + return ( {Object.entries(templates).map(([templateKey, { title, description, tags, job_types }]) => { @@ -32,7 +46,7 @@ function TemplateGrid({ sx={{ height: '100%', minHeight: 225 }} key={templateKey} disabled={templateKey in disabledTemplates} - tooltip={templateKey in disabledTemplates ? 'This template is already in your workspace.' : undefined} + tooltip={getTooltip(templateKey, job_types)} jobTypes={job_types} /> From 5c647a7f65fa9761365def8c10905c0349f1f60a Mon Sep 17 00:00:00 2001 From: Austen Money Date: Mon, 16 Sep 2024 13:20:34 -0400 Subject: [PATCH 5/8] add changelog --- CHANGELOG-r-template-updates.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CHANGELOG-r-template-updates.md 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 From 736b2fddcd776b01b8b35e988b197dcc0cfbf1d6 Mon Sep 17 00:00:00 2001 From: Austen Money Date: Mon, 16 Sep 2024 13:26:25 -0400 Subject: [PATCH 6/8] naming updates --- .../js/components/workspaces/NewWorkspaceDialog/hooks.ts | 6 +++--- .../workspaces/TemplateSelectStep/TemplateSelectStep.tsx | 4 ++-- context/app/static/js/components/workspaces/constants.ts | 2 +- .../shared-styles/cards/SelectableCard/SelectableCard.tsx | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts b/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts index 4a0ad50ad2..b8331cd63e 100644 --- a/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts +++ b/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts @@ -9,7 +9,7 @@ import { TemplatesResponse, CreateTemplateNotebooksTypes, TemplateTagsResponse } import { useCreateAndLaunchWorkspace, useCreateTemplates } from 'js/components/workspaces/hooks'; import { buildDatasetSymlinks } from 'js/components/workspaces/utils'; import { useWorkspaceToasts } from 'js/components/workspaces/toastHooks'; -import { R_JOB_TYPE, R_TEMPLATE_LABEL } from 'js/components/workspaces/constants'; +import { R_JOB_TYPE, R_TEMPLATE_TAG } from 'js/components/workspaces/constants'; interface UserTemplatesTypes { templatesURL: string; @@ -45,8 +45,8 @@ function useWorkspaceTemplates(tags: string[] = []) { Object.entries(templates).map(([key, template]) => { const isRTemplate = template.job_types?.includes(R_JOB_TYPE); - const updatedTitle = isRTemplate ? `${template.title} (${R_TEMPLATE_LABEL})` : template.title; - const updatedTags = [...template.tags, ...(isRTemplate ? [R_TEMPLATE_LABEL] : [])]; + const updatedTitle = isRTemplate ? `${template.title} (${R_TEMPLATE_TAG})` : template.title; + const updatedTags = [...template.tags, ...(isRTemplate ? [R_TEMPLATE_TAG] : [])]; return [key, { ...template, tags: updatedTags, title: updatedTitle }]; }), diff --git a/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx b/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx index 665f0cd106..1ef9fce5d0 100644 --- a/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx +++ b/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx @@ -10,7 +10,7 @@ import SelectableTemplateGrid from 'js/components/workspaces/SelectableTemplateG import { TemplatesTypes } from 'js/components/workspaces/types'; import { FormWithTemplates } from 'js/components/workspaces/NewWorkspaceDialog/useCreateWorkspaceForm'; import TemplateTagsAutocomplete from 'js/components/workspaces/TemplateTagsAutocomplete'; -import { R_TEMPLATE_LABEL } from 'js/components/workspaces/constants'; +import { R_TEMPLATE_TAG } from 'js/components/workspaces/constants'; function ContactPrompt() { return ( @@ -25,7 +25,7 @@ const description = [ , ]; -const recommendedTags = ['visualization', 'api', R_TEMPLATE_LABEL]; +const recommendedTags = ['visualization', 'api', R_TEMPLATE_TAG]; interface TemplateSelectProps { title: string; diff --git a/context/app/static/js/components/workspaces/constants.ts b/context/app/static/js/components/workspaces/constants.ts index 8d06215f6a..1403511009 100644 --- a/context/app/static/js/components/workspaces/constants.ts +++ b/context/app/static/js/components/workspaces/constants.ts @@ -4,7 +4,7 @@ export const DEFAULT_PYTHON_TEMPLATE_KEY = 'blank'; export const DEFAULT_R_TEMPLATE_KEY = 'blank_r'; /* Workspace R templates */ -export const R_TEMPLATE_LABEL = 'Jupyter Lab: Python + R'; +export const R_TEMPLATE_TAG = 'Jupyter Lab: Python + R'; export const R_JOB_TYPE = 'jupyter_lab_r'; /* Workspace resource defaults */ diff --git a/context/app/static/js/shared-styles/cards/SelectableCard/SelectableCard.tsx b/context/app/static/js/shared-styles/cards/SelectableCard/SelectableCard.tsx index f2d4e790c8..b0a12c700f 100644 --- a/context/app/static/js/shared-styles/cards/SelectableCard/SelectableCard.tsx +++ b/context/app/static/js/shared-styles/cards/SelectableCard/SelectableCard.tsx @@ -30,7 +30,6 @@ function SelectableCard({ ...rest }: SelectableCardProps) { const colorVariant = isSelected ? 'primaryContainer' : 'secondaryContainer'; - return ( From f1aa04991a0896d911de0539898ac81458db4cee Mon Sep 17 00:00:00 2001 From: Austen Money Date: Tue, 17 Sep 2024 15:50:04 -0400 Subject: [PATCH 7/8] rely on r tag from api and review changes --- .../workspaces/NewWorkspaceDialog/hooks.ts | 29 ++++++++----------- .../SelectableTemplateGrid.tsx | 13 +++++---- .../TemplateSelectStep/TemplateSelectStep.tsx | 4 --- .../TemplateTagsAutocomplete.tsx | 7 ++--- .../js/components/workspaces/constants.ts | 5 +++- .../workspaces/workspaceMessaging.tsx | 3 -- 6 files changed, 26 insertions(+), 35 deletions(-) diff --git a/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts b/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts index 0aea2e9b6c..9161406ac6 100644 --- a/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts +++ b/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts @@ -10,12 +10,13 @@ import { CreateTemplateNotebooksTypes, TemplateTagsResponse, TemplateExample, + TemplatesTypes, } 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, R_JOB_TYPE, R_TEMPLATE_TAG } from 'js/components/workspaces/constants'; +import { DEFAULT_JOB_TYPE, R_TEMPLATE_TITLE } from 'js/components/workspaces/constants'; interface UserTemplatesTypes { templatesURL: string; @@ -41,27 +42,21 @@ function useUserTemplatesAPI({ templatesURL, config = { fallbackData: {} } }: function useWorkspaceTemplates(tags: string[] = []) { const { userTemplatesEndpoint } = useAppContext(); - const url = `${userTemplatesEndpoint}/templates/jupyter_lab`; + const queryParams = tags.map((tag, i) => `${i === 0 ? '' : '&'}tags=${encodeURIComponent(tag)}`).join(''); + + const url = `${userTemplatesEndpoint}/templates/jupyter_lab/?${queryParams}`; const result = useUserTemplatesAPI({ templatesURL: url }); const templates = result?.data?.data ?? {}; - // Manually update tags and title for R templates - const updatedTemplates = Object.fromEntries( - Object.entries(templates).map(([key, template]) => { - const isRTemplate = template.job_types?.includes(R_JOB_TYPE); - - const updatedTitle = isRTemplate ? `${template.title} (${R_TEMPLATE_TAG})` : template.title; - const updatedTags = [...template.tags, ...(isRTemplate ? [R_TEMPLATE_TAG] : [])]; - - return [key, { ...template, tags: updatedTags, title: updatedTitle }]; - }), - ); - - // Filter templates by tags const filteredTemplates = Object.fromEntries( - Object.entries(updatedTemplates).filter(([_, template]) => tags.every((tag) => template.tags?.includes(tag))), - ); + Object.entries(templates) + .filter(([, template]) => !template?.is_hidden) + .map(([key, template]) => { + const newTitle = template?.tags?.includes('R') ? `${template.title} (${R_TEMPLATE_TITLE})` : template.title; + return [key, { ...template, title: newTitle }]; + }), + ) as TemplatesTypes; return { templates: filteredTemplates, diff --git a/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx b/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx index a536f38e4d..00979f44ba 100644 --- a/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx +++ b/context/app/static/js/components/workspaces/SelectableTemplateGrid/SelectableTemplateGrid.tsx @@ -41,9 +41,11 @@ 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, @@ -54,10 +56,9 @@ function SelectableTemplateGrid({ // If the Python + R job type is selected, select the default R template useEffect(() => { if (jobType.value === R_JOB_TYPE) { - setSelectedTemplates([...selectedTemplates, DEFAULT_R_TEMPLATE_KEY]); + addItem(DEFAULT_R_TEMPLATE_KEY); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [jobType.value, setSelectedTemplates]); + }, [jobType.value, addItem]); const sortedTemplates = useMemo(() => sortTemplates(templates, disabledTemplates), [templates, disabledTemplates]); diff --git a/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx b/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx index f0ad3e7a2e..ebb74ee690 100644 --- a/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx +++ b/context/app/static/js/components/workspaces/TemplateSelectStep/TemplateSelectStep.tsx @@ -10,7 +10,6 @@ import SelectableTemplateGrid from 'js/components/workspaces/SelectableTemplateG import { TemplatesTypes } from 'js/components/workspaces/types'; import { FormWithTemplates } from 'js/components/workspaces/NewWorkspaceDialog/useCreateWorkspaceForm'; import TemplateTagsAutocomplete from 'js/components/workspaces/TemplateTagsAutocomplete'; -import { R_TEMPLATE_TAG } from 'js/components/workspaces/constants'; function ContactPrompt() { return ( @@ -25,8 +24,6 @@ const description = [ , ]; -const recommendedTags = ['visualization', 'api', R_TEMPLATE_TAG]; - interface TemplateSelectProps { title: string; stepIndex?: number; @@ -59,7 +56,6 @@ function TemplateSelectStep({ void; setSelectedTags: React.Dispatch>; selectedRecommendedTags: SelectedItems; @@ -27,7 +27,6 @@ function TagComponent({ option, ...rest }: TagTypes) { function TemplateTagsAutocomplete({ selectedTags, - recommendedTags, toggleTag, setSelectedTags, selectedRecommendedTags, @@ -39,7 +38,7 @@ function TemplateTagsAutocomplete({ !recommendedTags.includes(tag)) + .filter((tag) => !RECOMMENDED_TAGS.includes(tag)) .sort((a, b) => a.localeCompare(b))} multiple filterSelectedOptions @@ -59,7 +58,7 @@ function TemplateTagsAutocomplete({ Recommended Tags - {recommendedTags.map((tag) => ( + {RECOMMENDED_TAGS.map((tag) => ( ([]); const { templates } = useWorkspaceTemplates([...selectedTags, ...selectedRecommendedTags]); - const recommendedTags = ['visualization', 'api']; - return ( Date: Tue, 17 Sep 2024 16:38:26 -0400 Subject: [PATCH 8/8] review changes --- .../js/components/workspaces/NewWorkspaceDialog/hooks.ts | 6 ++++-- .../js/components/workspaces/TemplateGrid/TemplateGrid.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts b/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts index da98874761..8da67aca28 100644 --- a/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts +++ b/context/app/static/js/components/workspaces/NewWorkspaceDialog/hooks.ts @@ -17,7 +17,7 @@ import { useCreateAndLaunchWorkspace, useCreateTemplates } from 'js/components/w 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, R_TEMPLATE_TITLE } from 'js/components/workspaces/constants'; +import { DEFAULT_JOB_TYPE, R_JOB_TYPE, R_TEMPLATE_TITLE } from 'js/components/workspaces/constants'; interface UserTemplatesTypes { templatesURL: string; @@ -54,7 +54,9 @@ function useWorkspaceTemplates(tags: string[] = []) { Object.entries(templates) .filter(([, template]) => !template?.is_hidden) .map(([key, template]) => { - const newTitle = template?.tags?.includes('R') ? `${template.title} (${R_TEMPLATE_TITLE})` : template.title; + const newTitle = template?.job_types?.includes(R_JOB_TYPE) + ? `${template.title} (${R_TEMPLATE_TITLE})` + : template.title; return [key, { ...template, title: newTitle }]; }), ) as TemplatesTypes; diff --git a/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx b/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx index b50fb5f04f..cd56c2eda0 100644 --- a/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx +++ b/context/app/static/js/components/workspaces/TemplateGrid/TemplateGrid.tsx @@ -27,8 +27,8 @@ function TemplateGrid({ 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 the template is an R template and the job type is not R if (jobType !== R_JOB_TYPE && job_types?.includes(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.'; }