Skip to content

Commit 4b93275

Browse files
committed
Refactoring templates after review:
Added default project Template Choice as cards Default spot-policy filter for offers Fix small defects
1 parent 642416f commit 4b93275

7 files changed

Lines changed: 84 additions & 23 deletions

File tree

frontend/src/libs/filters.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,37 @@ export const EMPTY_QUERY: PropertyFilterProps.Query = {
7878
export const requestParamsToTokens = <RequestParamsKeys extends string>({
7979
searchParams,
8080
filterKeys,
81+
defaultFilterValues,
8182
}: {
8283
searchParams: URLSearchParams;
8384
filterKeys: Record<string, RequestParamsKeys>;
85+
defaultFilterValues?: Partial<Record<RequestParamsKeys, string | string[]>>;
8486
}): PropertyFilterProps.Query => {
8587
const tokens = [];
88+
const filterKeysValues = Object.values(filterKeys);
89+
90+
if (defaultFilterValues) {
91+
Object.keys(defaultFilterValues).forEach((defaultFilterKey) => {
92+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
93+
// @ts-expect-error
94+
const defaultFilterValue: string[] = Array.isArray(defaultFilterValues[defaultFilterKey])
95+
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
96+
// @ts-expect-error
97+
defaultFilterValues[defaultFilterKey]
98+
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
99+
// @ts-expect-error
100+
[defaultFilterValues[defaultFilterKey]];
101+
102+
defaultFilterValue.forEach((value) => {
103+
tokens.push({ propertyKey: defaultFilterKey, operator: '=', value: value });
104+
});
105+
});
106+
}
86107

87108
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
88109
// @ts-ignore
89110
for (const [paramKey, paramValue] of searchParams.entries()) {
90-
if (Object.values(filterKeys).includes(paramKey)) {
111+
if (filterKeysValues.includes(paramKey)) {
91112
tokens.push({ propertyKey: paramKey, operator: '=', value: paramValue });
92113
}
93114
}

frontend/src/pages/Offers/List/hooks/useFilters.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type UseFiltersArgs = {
2020
gpus: IGpu[];
2121
withSearchParams?: boolean;
2222
permanentFilters?: Partial<Record<RequestParamsKeys, string>>;
23+
defaultFilters?: Partial<Record<RequestParamsKeys, string>>;
2324
};
2425

2526
export const filterKeys: Record<string, RequestParamsKeys> = {
@@ -87,13 +88,13 @@ const defaultGroupByOptions = [{ ...gpuFilterOption }, { label: 'Backend', value
8788

8889
const groupByRequestParamName: RequestParamsKeys = 'group_by';
8990

90-
export const useFilters = ({ gpus, withSearchParams = true, permanentFilters = {} }: UseFiltersArgs) => {
91+
export const useFilters = ({ gpus, withSearchParams = true, permanentFilters = {}, defaultFilters }: UseFiltersArgs) => {
9192
const [searchParams, setSearchParams] = useSearchParams();
9293
const { projectOptions } = useProjectFilter({ localStorePrefix: 'offers-list-projects' });
9394
const projectNameIsChecked = useRef(false);
9495

9596
const [propertyFilterQuery, setPropertyFilterQuery] = useState<PropertyFilterProps.Query>(() =>
96-
requestParamsToTokens<RequestParamsKeys>({ searchParams, filterKeys }),
97+
requestParamsToTokens<RequestParamsKeys>({ searchParams, filterKeys, defaultFilterValues: defaultFilters }),
9798
);
9899

99100
const [groupBy, setGroupBy] = useState<MultiselectProps.Options>(() => {

frontend/src/pages/Offers/List/index.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,21 @@ const getRequestParams = ({
6767
};
6868

6969
type OfferListProps = Pick<CardsProps, 'variant' | 'header' | 'onSelectionChange' | 'selectedItems' | 'selectionType'> &
70-
Pick<UseFiltersArgs, 'permanentFilters'> & {
70+
Pick<UseFiltersArgs, 'permanentFilters' | 'defaultFilters'> & {
7171
withSearchParams?: boolean;
7272
onChangeProjectName?: (value: string) => void;
7373
};
7474

75-
export const OfferList: React.FC<OfferListProps> = ({ withSearchParams, onChangeProjectName, permanentFilters, ...props }) => {
75+
export const OfferList: React.FC<OfferListProps> = ({
76+
withSearchParams,
77+
onChangeProjectName,
78+
permanentFilters,
79+
defaultFilters,
80+
...props
81+
}) => {
7682
const { t } = useTranslation();
7783
const [requestParams, setRequestParams] = useState<TGpusListQueryParams | undefined>();
84+
7885
const { data, isLoading, isFetching } = useGetGpusListQuery(
7986
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
8087
// @ts-expect-error
@@ -94,7 +101,7 @@ export const OfferList: React.FC<OfferListProps> = ({ withSearchParams, onChange
94101
groupBy,
95102
groupByOptions,
96103
onChangeGroupBy,
97-
} = useFilters({ gpus: data?.gpus ?? [], withSearchParams, permanentFilters });
104+
} = useFilters({ gpus: data?.gpus ?? [], withSearchParams, permanentFilters, defaultFilters });
98105

99106
useEffect(() => {
100107
setRequestParams(

frontend/src/pages/Runs/CreateDevEnvironment/hooks/useGenerateYaml.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export const useGenerateYaml = ({ formValues, template }: UseGenerateYamlArgs) =
3131
},
3232

3333
backends: offer.backends,
34+
...(offer.spot.length === 1 ? { spot_policy: offer.spot[0] } : {}),
35+
...(offer.spot.length > 1 ? { spot_policy: 'auto' } : {}),
3436
}
3537
: {}),
3638

@@ -41,7 +43,6 @@ export const useGenerateYaml = ({ formValues, template }: UseGenerateYamlArgs) =
4143
: {}),
4244

4345
...(working_dir ? { working_dir } : {}),
44-
spot_policy: 'auto',
4546
});
4647
}, [formValues, template]);
4748
};

frontend/src/pages/Runs/CreateDevEnvironment/hooks/useValidationResolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ const workingDirFormatError = 'Must be an absolute path';
1010

1111
export const useYupValidationResolver = (template?: ITemplate) => {
1212
const validationSchema = useMemo(() => {
13-
const schema: Partial<Record<IRunEnvironmentFormKeys, yup.StringSchema>> = {
13+
const schema: Partial<Record<IRunEnvironmentFormKeys, yup.StringSchema | yup.ArraySchema<yup.StringSchema>>> = {
1414
project: yup.string().required(requiredFieldError),
15-
template: yup.string().required(requiredFieldError),
15+
template: yup.array().min(1, requiredFieldError).of(yup.string()).required(requiredFieldError),
1616
config_yaml: yup.string().required(requiredFieldError),
1717
};
1818

frontend/src/pages/Runs/CreateDevEnvironment/index.tsx

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import cn from 'classnames';
66
import { Box, Link, WizardProps } from '@cloudscape-design/components';
77
import { CardsProps } from '@cloudscape-design/components/cards';
88

9-
import { Container, FormCodeEditor, FormField, FormSelect, SpaceBetween, Wizard } from 'components';
9+
import { Container, FormCards, FormCodeEditor, FormField, FormSelect, FormSelectProps, SpaceBetween, Wizard } from 'components';
1010

1111
import { useBreadcrumbs, useNotifications } from 'hooks';
1212
import { useCheckingForFleetsInProjects } from 'hooks/useCheckingForFleetsInProjectsOfMember';
13+
import { useLocalStorageState } from 'hooks/useLocalStorageState';
1314
import { useProjectFilter } from 'hooks/useProjectFilter';
1415
import { getServerError } from 'libs';
1516
import { ROUTES } from 'routes';
@@ -45,6 +46,10 @@ export const CreateDevEnvironment: React.FC = () => {
4546
const [searchParams] = useSearchParams();
4647
const navigate = useNavigate();
4748
const [pushNotification] = useNotifications();
49+
const [defaultProject, setDefaultProject] = useLocalStorageState<IProject['project_name'] | undefined>(
50+
'createEnvironmentDefaultProject',
51+
undefined,
52+
);
4853
const [activeStepIndex, setActiveStepIndex] = useState(0);
4954
const [selectedOffers, setSelectedOffers] = useState<IGpu[]>([]);
5055
const [selectedTemplate, setSelectedTemplate] = useState<ITemplate | undefined>();
@@ -74,9 +79,11 @@ export const CreateDevEnvironment: React.FC = () => {
7479
},
7580
});
7681

77-
const { handleSubmit, control, trigger, setValue, watch, formState, getValues } = formMethods;
82+
const { handleSubmit, control, trigger, setValue, watch, formState, getValues, unregister } = formMethods;
7883
const formValues = watch();
7984

85+
console.log({ formValues });
86+
8087
const projectHavingFleetMap = useCheckingForFleetsInProjects({
8188
projectNames: formValues.project ? [formValues.project] : [],
8289
});
@@ -101,7 +108,14 @@ export const CreateDevEnvironment: React.FC = () => {
101108
}, [templatesData]);
102109

103110
useEffect(() => {
104-
setSelectedTemplate(templatesData?.find((t) => t.id === formValues.template));
111+
if (!defaultProject && projectOptions?.[0]?.value) {
112+
setValue(FORM_FIELD_NAMES.project, projectOptions[0].value);
113+
setDefaultProject(projectOptions[0].value);
114+
}
115+
}, [defaultProject, projectOptions]);
116+
117+
useEffect(() => {
118+
setSelectedTemplate(templatesData?.find((t) => t.id === formValues.template?.[0]));
105119
}, [templatesData, formValues.template]);
106120

107121
const validateProjectAndTemplate = async () => await trigger(templateStepFieldNames);
@@ -130,6 +144,8 @@ export const CreateDevEnvironment: React.FC = () => {
130144
window.scrollTo(0, 0);
131145
}
132146
});
147+
} else if (reason === 'step' && requestedStepIndex - activeStepIndex > 1) {
148+
return;
133149
} else {
134150
setActiveStepIndex(requestedStepIndex);
135151
}
@@ -139,6 +155,15 @@ export const CreateDevEnvironment: React.FC = () => {
139155
onNavigate({ requestedStepIndex, reason });
140156
};
141157

158+
const onChangeProject: FormSelectProps<IRunEnvironmentFormValues>['onChange'] = ({ detail }) => {
159+
setValue(FORM_FIELD_NAMES.template, []);
160+
setDefaultProject(detail.selectedOption.value);
161+
};
162+
163+
const onChangeTemplate = () => {
164+
unregister(FORM_FIELD_NAMES.ide);
165+
};
166+
142167
const onChangeOffer: CardsProps<IGpu>['onSelectionChange'] = ({ detail }) => {
143168
const newSelectedOffers = detail?.selectedItems ?? [];
144169
setSelectedOffers(newSelectedOffers);
@@ -245,22 +270,27 @@ export const CreateDevEnvironment: React.FC = () => {
245270
empty={t('runs.dev_env.wizard.project_empty')}
246271
loadingText={t('runs.dev_env.wizard.project_loading')}
247272
statusType={isLoadingProjectOptions ? 'loading' : undefined}
248-
onChange={() => setValue(FORM_FIELD_NAMES.template, '')}
273+
onChange={onChangeProject}
274+
defaultValue={defaultProject}
249275
/>
250276

251-
<FormSelect
277+
<FormField
252278
label={t('runs.dev_env.wizard.template')}
253279
description={t('runs.dev_env.wizard.template_description')}
254-
placeholder={
255-
!formValues.project ? t('runs.dev_env.wizard.template_placeholder') : undefined
256-
}
280+
errorText={formState.errors.template?.message}
281+
/>
282+
283+
<FormCards
257284
control={control}
258285
name={FORM_FIELD_NAMES.template}
259-
options={templateOptions}
260-
disabled={loading}
261-
empty={t('runs.dev_env.wizard.template_empty')}
262-
loadingText={t('runs.dev_env.wizard.template_loading')}
263-
statusType={isLoadingTemplates ? 'loading' : undefined}
286+
items={templateOptions}
287+
selectionType="single"
288+
loading={isLoadingTemplates}
289+
cardDefinition={{
290+
header: (item) => item.label,
291+
}}
292+
cardsPerRow={[{ cards: 1 }, { minWidth: 400, cards: 2 }, { minWidth: 800, cards: 3 }]}
293+
onSelectionChange={onChangeTemplate}
264294
/>
265295
</SpaceBetween>
266296
</Container>
@@ -285,6 +315,7 @@ export const CreateDevEnvironment: React.FC = () => {
285315
selectedItems={selectedOffers}
286316
onSelectionChange={onChangeOffer}
287317
permanentFilters={{ project_name: formValues.project ?? '' }}
318+
defaultFilters={{ spot_policy: 'on-demand' }}
288319
/>
289320
</>
290321
),

frontend/src/pages/Runs/CreateDevEnvironment/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export interface IRunEnvironmentFormValues {
22
project: IProject['project_name'];
3-
template: string;
3+
template: string[];
44
offer: IGpu;
55
name: string;
66
ide: 'cursor' | 'vscode' | 'windsurf' | 'coder';

0 commit comments

Comments
 (0)