1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-18 21:09:40 +02:00

fix(template): app template v3 error [BE-11998] (#854)

This commit is contained in:
Oscar Zhou 2025-07-05 02:49:33 +12:00 committed by GitHub
parent 8ffe4e284a
commit c20a8b5a68
6 changed files with 21 additions and 19 deletions

View file

@ -40,11 +40,13 @@ func (handler *Handler) fetchTemplates() (*listResponse, *httperror.HandlerError
} }
defer resp.Body.Close() defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&body) if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err != nil {
return nil, httperror.InternalServerError("Unable to parse template file", err) return nil, httperror.InternalServerError("Unable to parse template file", err)
} }
for i := range body.Templates {
body.Templates[i].ID = portainer.TemplateID(i + 1)
}
return body, nil return body, nil
} }

View file

@ -80,10 +80,7 @@ test('The form should submit the correct request body for a given app template',
// fill in the name and select the docker edge group // fill in the name and select the docker edge group
const user = userEvent.setup(); const user = userEvent.setup();
await user.type(getByRole('textbox', { name: 'Name *' }), 'my-stack'); await user.type(getByRole('textbox', { name: 'Name *' }), 'my-stack');
await user.type( await user.type(getByRole('textbox', { name: 'License key' }), 'license-123');
getByRole('textbox', { name: 'License key *' }),
'license-123'
);
const selectElement = getByLabelText('Edge groups'); const selectElement = getByLabelText('Edge groups');
await selectEvent.select(selectElement, 'docker'); await selectEvent.select(selectElement, 'docker');

View file

@ -51,7 +51,7 @@ test('calls onChange when input value changes', async () => {
const inputElement = screen.getByDisplayValue(value.VAR1); const inputElement = screen.getByDisplayValue(value.VAR1);
await user.clear(inputElement); await user.clear(inputElement);
expect(onChange).toHaveBeenCalledWith({ VAR1: '' }); expect(onChange).toHaveBeenCalledWith({ VAR1: undefined });
const newValue = 'New Value'; const newValue = 'New Value';
await user.type(inputElement, newValue); await user.type(inputElement, newValue);
@ -107,11 +107,14 @@ test('validates env vars fieldset', () => {
]); ]);
const validData = { VAR1: 'Value 1', VAR2: 'Value 2' }; const validData = { VAR1: 'Value 1', VAR2: 'Value 2' };
const invalidData = { VAR1: '', VAR2: 'Value 2' }; const emptyData = { VAR1: '', VAR2: 'Value 2' };
const undefinedData = { VAR1: undefined, VAR2: 'Value 2' };
const validResult = schema.isValidSync(validData); const validResult = schema.isValidSync(validData);
const invalidResult = schema.isValidSync(invalidData); const emptyResult = schema.isValidSync(emptyData);
const undefinedResult = schema.isValidSync(undefinedData);
expect(validResult).toBe(true); expect(validResult).toBe(true);
expect(invalidResult).toBe(false); expect(emptyResult).toBe(true);
expect(undefinedResult).toBe(true);
}); });

View file

@ -6,7 +6,7 @@ import { TemplateEnv } from '@/react/portainer/templates/app-templates/types';
import { FormControl } from '@@/form-components/FormControl'; import { FormControl } from '@@/form-components/FormControl';
import { Input, Select } from '@@/form-components/Input'; import { Input, Select } from '@@/form-components/Input';
type Value = Record<string, string>; type Value = Record<string, string | undefined>;
export { type Value as EnvVarsValue }; export { type Value as EnvVarsValue };
@ -27,7 +27,7 @@ export function EnvVarsFieldset({
<Item <Item
key={env.name} key={env.name}
option={env} option={env}
value={values[env.name]} value={values[env.name] || ''}
onChange={(value) => handleChange(env.name, value)} onChange={(value) => handleChange(env.name, value)}
errors={errors?.[env.name]} errors={errors?.[env.name]}
/> />
@ -36,7 +36,7 @@ export function EnvVarsFieldset({
); );
function handleChange(name: string, envValue: string) { function handleChange(name: string, envValue: string) {
onChange({ ...values, [name]: envValue }); onChange({ ...values, [name]: envValue || undefined });
} }
} }
@ -55,7 +55,7 @@ function Item({
return ( return (
<FormControl <FormControl
label={option.label || option.name} label={option.label || option.name}
required={!option.preset} required={false}
errors={errors} errors={errors}
inputId={inputId} inputId={inputId}
> >
@ -101,7 +101,9 @@ export function envVarsFieldsetValidation(
): SchemaOf<Value> { ): SchemaOf<Value> {
return object( return object(
Object.fromEntries( Object.fromEntries(
definitions.map((v) => [v.name, string().required('Required')]) definitions
.filter((v) => !v.preset)
.map((v) => [v.name, string().optional()])
) )
); );
} }

View file

@ -2,6 +2,6 @@ import { AccessControlFormData } from '@/react/portainer/access-control/types';
export interface FormValues { export interface FormValues {
name: string; name: string;
envVars: Record<string, string>; envVars: Record<string, string | undefined>;
accessControl: AccessControlFormData; accessControl: AccessControlFormData;
} }

View file

@ -88,10 +88,8 @@ function setTemplatesV3(this: TemplateViewModel, template: AppTemplate) {
this.Id = template.id; this.Id = template.id;
} }
let templateV2ID = 0;
function setTemplatesV2(this: TemplateViewModel, template: AppTemplate) { function setTemplatesV2(this: TemplateViewModel, template: AppTemplate) {
this.Id = templateV2ID++; this.Id = template.id;
this.Title = template.title; this.Title = template.title;
this.Type = template.type; this.Type = template.type;
this.Description = template.description; this.Description = template.description;