-
Notifications
You must be signed in to change notification settings - Fork 27
EDM-2925: Adding helm application type #457
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import yaml from 'js-yaml'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AppType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // eslint-disable-next-line no-restricted-imports | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -38,6 +39,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isComposeImageAppForm, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isGitConfigTemplate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isGitProviderSpec, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isHelmImageAppForm, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isHttpConfigTemplate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isHttpProviderSpec, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isImageAppProvider, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -215,6 +217,32 @@ export const getDeviceSpecConfigPatches = ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const toAPIApplication = (app: AppForm): ApplicationProviderSpec => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isHelmImageAppForm(app)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const data: ImageApplicationProviderSpec & ApplicationProviderSpec = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: app.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| image: app.image, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appType: app.appType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (app.namespace) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data.namespace = app.namespace; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (app.valuesYaml) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const values = yaml.load(app.valuesYaml) as Record<string, unknown>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (values && Object.keys(values).length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data.values = values; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Values content is not valid YAML.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fileNames = app.valuesFiles.filter((file) => file && file.trim() !== ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fileNames.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data.valuesFiles = fileNames; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+220
to
+244
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "deviceSpecUtils.ts" -type f | head -20Repository: flightctl/flightctl-ui Length of output: 144 🏁 Script executed: find . -name "EditDeviceWizard*" -type f | head -20Repository: flightctl/flightctl-ui Length of output: 383 🏁 Script executed: rg -l "valuesYaml" --type ts --type tsx | head -20Repository: flightctl/flightctl-ui Length of output: 93 🏁 Script executed: head -n 250 ./libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | tail -n 40Repository: flightctl/flightctl-ui Length of output: 1094 🏁 Script executed: rg "app\.valuesYaml" --type ts | head -20Repository: flightctl/flightctl-ui Length of output: 322 🏁 Script executed: rg "valuesYaml" -A 3 -B 3 ./libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsx | head -60Repository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: rg "type AppForm\|interface AppForm" --type ts -A 10 | head -50Repository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: rg "YamlEditor" --type ts -B 3 -A 3 | head -80Repository: flightctl/flightctl-ui Length of output: 8291 🏁 Script executed: grep -r "valuesYaml" ./libs/ui-components/src/components/Device/EditDeviceWizard/ | head -20Repository: flightctl/flightctl-ui Length of output: 1007 🏁 Script executed: cat ./libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.tsxRepository: flightctl/flightctl-ui Length of output: 6260 🏁 Script executed: rg "UploadField" -A 5 -B 5 ./libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.tsxRepository: flightctl/flightctl-ui Length of output: 1025 🏁 Script executed: rg "type AppForm|interface AppForm" -r . --type ts --type tsx | head -20Repository: flightctl/flightctl-ui Length of output: 93 🏁 Script executed: find . -name "deviceSpec.ts" -o -name "deviceSpec.tsx" | head -10Repository: flightctl/flightctl-ui Length of output: 110 🏁 Script executed: rg "export.*HelmImageAppForm|interface HelmImageAppForm|type HelmImageAppForm" --type ts -A 8Repository: flightctl/flightctl-ui Length of output: 1375 🏁 Script executed: find . -name "UploadField.tsx" -o -name "UploadField.ts" | head -5Repository: flightctl/flightctl-ui Length of output: 122 🏁 Script executed: cat ./libs/ui-components/src/components/form/UploadField.tsxRepository: flightctl/flightctl-ui Length of output: 4511 🏁 Script executed: rg "toAPIApplication" -B 5 -A 2 ./libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsxRepository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: rg "toAPIApplication" --type ts | head -20Repository: flightctl/flightctl-ui Length of output: 802 🏁 Script executed: rg "toAPIApplication\(" -B 10 -A 2 | head -60Repository: flightctl/flightctl-ui Length of output: 1548 🏁 Script executed: cat -n ./libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | sed -n '210,250p'Repository: flightctl/flightctl-ui Length of output: 1404 🏁 Script executed: rg "yaml\.load" --type ts -B 2 -A 2 | head -40Repository: flightctl/flightctl-ui Length of output: 1668 🏁 Script executed: rg "valuesYaml" ./libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts -B 2 -A 10 | head -80Repository: flightctl/flightctl-ui Length of output: 1349 🏁 Script executed: rg "valuesYaml" ./libs/ui-components/src/components/form/validations.ts -B 10 -A 10Repository: flightctl/flightctl-ui Length of output: 1047 🏁 Script executed: rg "yaml.load.*Record" --type ts -B 3 -A 3Repository: flightctl/flightctl-ui Length of output: 1683 🏁 Script executed: cat ./libs/ui-components/src/components/form/validations.ts | head -100Repository: flightctl/flightctl-ui Length of output: 3770 🏁 Script executed: rg "yaml\.load.*as Record" --type ts -B 5 -A 5Repository: flightctl/flightctl-ui Length of output: 2495 🏁 Script executed: cat ./libs/ui-components/src/components/form/validations.ts | sed -n '60,85p'Repository: flightctl/flightctl-ui Length of output: 1201 🏁 Script executed: cat ./libs/ui-components/src/components/form/validations.ts | sed -n '160,190p'Repository: flightctl/flightctl-ui Length of output: 1050 🏁 Script executed: rg "valuesYaml.*Yup" --type ts -A 12Repository: flightctl/flightctl-ui Length of output: 1306 🏁 Script executed: cat ./libs/ui-components/src/components/form/validations.ts | sed -n '370,405p'Repository: flightctl/flightctl-ui Length of output: 1100 🏁 Script executed: cat -n ./libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | sed -n '250,270p'Repository: flightctl/flightctl-ui Length of output: 711 🏁 Script executed: python3 << 'EOF'
import json
# Simulate yaml.load behavior with different inputs
test_cases = [
("hello", "scalar string"),
("123", "scalar number"),
("[1, 2, 3]", "array"),
("key: value", "object/mapping"),
("null", "null value"),
]
for yaml_str, description in test_cases:
print(f"\n{description}: {yaml_str}")
print(f" typeof check: typeof would be 'object' for arrays/objects/null, 'string'/'number' for scalars")
if yaml_str in ["123", "hello"]:
print(f" Object.keys() would fail or return empty")
elif yaml_str == "null":
print(f" Object.keys(null) would throw TypeError")
elif yaml_str.startswith("["):
print(f" Object.keys() would return numeric indices: ['0', '1', '2']")
else:
print(f" Object.keys() would work correctly for object")
EOFRepository: flightctl/flightctl-ui Length of output: 152 🏁 Script executed: cat ./libs/ui-components/src/components/form/validations.ts | sed -n '395,425p'Repository: flightctl/flightctl-ui Length of output: 1037 🏁 Script executed: cat ./libs/ui-components/src/components/form/validations.ts | sed -n '327,345p'Repository: flightctl/flightctl-ui Length of output: 874 🏁 Script executed: cat -n ./libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | sed -n '255,275p'Repository: flightctl/flightctl-ui Length of output: 710 Validate that valuesYaml parses to a mapping (not scalar/array).
🐛 Proposed fix if (app.valuesYaml) {
try {
const parsed = yaml.load(app.valuesYaml);
const isObject = !!parsed && typeof parsed === 'object' && !Array.isArray(parsed);
if (!isObject) {
throw new Error('Values content must be a YAML mapping.');
}
if (Object.keys(parsed as Record<string, unknown>).length > 0) {
data.values = parsed as Record<string, unknown>;
}
} catch (error) {
throw new Error('Values content is not valid YAML.');
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const envVars = app.variables.reduce((acc, variable) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| acc[variable.name] = variable.value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return acc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -265,7 +293,7 @@ export const toAPIApplication = (app: AppForm): ApplicationProviderSpec => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limits: appLimits, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data as ApplicationProviderSpec; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isQuadletImageAppForm(app) || isComposeImageAppForm(app)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -433,15 +461,38 @@ const hasApplicationChanged = (currentApp: ApplicationProviderSpec, updatedApp: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!areEnvVariablesEqual(currentApp.envVars, updatedApp.variables)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The app is a single container application | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isSingleContainerAppForm(updatedApp)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return hasSingleContainerAppChanged(currentApp, updatedApp); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The app is a Helm application | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isHelmImageAppForm(updatedApp)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const imageApp = currentApp as ImageApplicationProviderSpec; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (imageApp.image !== updatedApp.image || imageApp.namespace !== updatedApp.namespace) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Compare valuesFiles arrays | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentValuesFiles = (imageApp.valuesFiles || []).filter((file) => file !== ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updatedValuesFiles = updatedApp.valuesFiles.filter((file) => file !== ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (currentValuesFiles.length !== updatedValuesFiles.length) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!currentValuesFiles.every((file, index) => file === updatedValuesFiles[index])) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updatedValues = yaml.load(updatedApp.valuesYaml || ' ') as Record<string, unknown>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (JSON.stringify(imageApp.values || {}) !== JSON.stringify(updatedValues)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!areEnvVariablesEqual(currentApp.envVars, updatedApp.variables)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+469
to
+494
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Find the specific file and check the code at lines 469-494
find . -type f -name "deviceSpecUtils.ts" | head -5Repository: flightctl/flightctl-ui Length of output: 144 🏁 Script executed: # Once we locate the file, examine the specific lines
if [ -f "libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts" ]; then
wc -l "libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts"
fiRepository: flightctl/flightctl-ui Length of output: 146 🏁 Script executed: # Check the yaml library being used and its behavior
grep -r "import.*yaml" libs/ui-components/src/components/Device/EditDeviceWizard/ | head -5Repository: flightctl/flightctl-ui Length of output: 700 🏁 Script executed: # Test yaml.load behavior with empty strings
node <<'EOF'
const yaml = require('js-yaml');
console.log("Testing yaml.load behavior:");
console.log("yaml.load(' '):", yaml.load(' '));
console.log("yaml.load(''):", yaml.load(''));
console.log("yaml.load(null):", yaml.load(null));
console.log("yaml.load(undefined):", yaml.load(undefined));
console.log("typeof yaml.load(' '):", typeof yaml.load(' '));
console.log("yaml.load(' ') === undefined:", yaml.load(' ') === undefined);
console.log("yaml.load('') === undefined:", yaml.load('') === undefined);
EOFRepository: flightctl/flightctl-ui Length of output: 903 🏁 Script executed: sed -n '469,494p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 1062 🏁 Script executed: # Let's also see context around these lines to understand the function
sed -n '450,510p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 2365 🏁 Script executed: # Check js-yaml documentation and behavior - search for how it's used elsewhere
grep -A 5 "yaml.load" libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | head -20Repository: flightctl/flightctl-ui Length of output: 554 🏁 Script executed: # Let's check the package.json to confirm js-yaml version
grep -A 2 "js-yaml" package.jsonRepository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Check how imageApp.values is typically set/populated
grep -B 5 -A 5 "imageApp.values" libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | head -30Repository: flightctl/flightctl-ui Length of output: 828 🏁 Script executed: # Look for where updatedApp.valuesYaml comes from and whether it can be empty
grep -B 3 -A 3 "valuesYaml" libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | head -40Repository: flightctl/flightctl-ui Length of output: 928 🏁 Script executed: # Check the structure of ImageApplicationProviderSpec and AppForm to understand values field
rg "interface.*ImageApplicationProviderSpec|type.*ImageApplicationProviderSpec" -A 10Repository: flightctl/flightctl-ui Length of output: 3106 🏁 Script executed: # Look for where hasApplicationChanged is called to understand the data flow
rg "hasApplicationChanged" -B 2 -A 2 libs/ui-components/src/components/Device/EditDeviceWizard/Repository: flightctl/flightctl-ui Length of output: 1327 🏁 Script executed: # Check AppForm interface to see valuesYaml type
rg "interface.*AppForm|type.*AppForm" -A 15 | head -50Repository: flightctl/flightctl-ui Length of output: 3593 🏁 Script executed: # See how js-yaml is referenced in package.json
cat package.json | grep -A 5 -B 5 "js-yaml"Repository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Find package.json and check js-yaml version
find . -name "package.json" -type f | head -3Repository: flightctl/flightctl-ui Length of output: 155 🏁 Script executed: # Look at the root package.json
head -50 package.json | grep -A 5 -B 5 "js-yaml" || echo "Not in first 50 lines, checking further"Repository: flightctl/flightctl-ui Length of output: 105 🏁 Script executed: # Search more broadly in package.json
cat package.json | grep "js-yaml"Repository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Let's understand the logic better - look at what happens with imageApp.values
sed -n '1,100p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | head -50Repository: flightctl/flightctl-ui Length of output: 1200 🏁 Script executed: # Check root directory package.json more carefully
cat package.json | grep -C 10 "dependencies\|devDependencies" | grep -A 50 "dependencies" | grep "js-yaml"Repository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Let's look at the actual storage/conversion of valuesYaml to understand the flow better
rg "valuesYaml" libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts -B 5 -A 5Repository: flightctl/flightctl-ui Length of output: 1220 🏁 Script executed: # Check the entire function to see how values comparison works
sed -n '440,520p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 2820 🏁 Script executed: # Let's search for other uses of yaml.load in the codebase to understand behavior patterns
rg "yaml\.load" -B 2 -A 2Repository: flightctl/flightctl-ui Length of output: 1668 🏁 Script executed: # Check how valuesFiles are initialized and used in the YAML editor
rg "valuesFiles" libs/ui-components/src/components/Device/EditDeviceWizard/ -B 3 -A 3Repository: flightctl/flightctl-ui Length of output: 7933 🏁 Script executed: # Let's look at how the form initializes and what happens with empty valuesYaml
sed -n '1,50p' libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.tsxRepository: flightctl/flightctl-ui Length of output: 2040 🏁 Script executed: # Check the YamlEditorBase mentioned in the learning to understand empty YAML handling
find . -name "YamlEditorBase.tsx" -type fRepository: flightctl/flightctl-ui Length of output: 138 🏁 Script executed: # Let's trace through - when convertDeviceSpecToForm is called, what's the initial state?
rg "convertDeviceSpecToForm" -A 20 libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | head -40Repository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Let's check the YamlEditorBase to understand the context from the learning
sed -n '59,64p' libs/ui-components/src/components/common/CodeEditor/YamlEditorBase.tsxRepository: flightctl/flightctl-ui Length of output: 215 🏁 Script executed: # Look at what yaml.load actually returns - let's check if there are any tests or documentation
rg "yaml\.load.*undefined\|undefined.*yaml\.load" -iRepository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Let's verify the issue by looking at a broader context - how values are actually initialized
sed -n '360,395p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 1259 🏁 Script executed: # Let's look at the convertDeviceSpecToForm function more carefully
sed -n '250,320p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 1897 🏁 Script executed: # Let's look at how imageApp.values is initialized/handled in the incoming data
rg "\.values\s*=" libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | head -15Repository: flightctl/flightctl-ui Length of output: 97 🏁 Script executed: # Check what happens when an app is serialized - does it ever have undefined values?
sed -n '200,250p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 1274 🏁 Script executed: # Let's trace convertDeviceSpecToForm to see how valuesYaml is converted back
rg "convertDeviceSpecToForm" -B 5 -A 30 | grep -A 30 "HelmImageAppForm\|isHelmImageAppForm" | head -50Repository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Let's find where convertDeviceSpecToForm is defined
grep -n "const convertDeviceSpecToForm" libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Look at that function
sed -n '120,180p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 2072 🏁 Script executed: # Search for the function that converts DeviceSpec to Form
rg "convertDeviceSpecToForm|convertSpecToForm" -B 2 -A 30 libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts | head -80Repository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Let's search for where the form is initialized from the device spec
rg "HelmImageAppForm|valuesYaml.*=" libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts -B 3 -A 3 | head -80Repository: flightctl/flightctl-ui Length of output: 723 🏁 Script executed: # Let's look for where the form is converted back from the device spec
rg "fromAppProvider|toAppForm" -B 2 -A 30 libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Let's search more broadly for form initialization from app spec
rg "imageApp\.values|app\.values" libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts -B 5 -A 5Repository: flightctl/flightctl-ui Length of output: 1846 🏁 Script executed: # Let's understand the full flow - let's see where the ApplicationProviderSpec comes from
rg "imageApp\.values" libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts -B 10 -A 5Repository: flightctl/flightctl-ui Length of output: 973 🏁 Script executed: # Let's verify yaml.load behavior by checking js-yaml documentation or similar code patterns
rg "yaml\." libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 343 🏁 Script executed: # Let's check if there's a test file for deviceSpecUtils
find . -name "*deviceSpecUtils*.test.ts" -o -name "*deviceSpecUtils*.spec.ts"Repository: flightctl/flightctl-ui Length of output: 48 🏁 Script executed: # Let's check what the actual ImageApplicationProviderSpec structure looks like and how values field is defined
cat libs/types/models/ImageApplicationProviderSpec.tsRepository: flightctl/flightctl-ui Length of output: 1258 🏁 Script executed: # Let's verify the exact logic by reviewing how the comparison works step by step
sed -n '480,490p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 430 🏁 Script executed: # Let's verify the exact type signature for imageApp to understand what imageApp.values could be
rg "type ImageApplicationProviderSpec|interface ImageApplicationProviderSpec" -A 20 libs/types/models/ImageApplicationProviderSpec.tsRepository: flightctl/flightctl-ui Length of output: 873 🏁 Script executed: # Let's check how valuesFiles is handled in the conversion - specifically in both directions
sed -n '115,155p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 1588 🏁 Script executed: # Most importantly, let's see the complete flow - where does the form come from initially?
rg "convertAppProviderSpecToForm|appProviderSpec.*HelmImageAppForm" -B 5 -A 20 libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.tsRepository: flightctl/flightctl-ui Length of output: 48 Fix Helm change detection for empty values and trim valuesFiles consistently. When 🐛 Proposed fix- const currentValuesFiles = (imageApp.valuesFiles || []).filter((file) => file !== '');
- const updatedValuesFiles = updatedApp.valuesFiles.filter((file) => file !== '');
+ const normalizeFiles = (files: string[]) => files.map((f) => f.trim()).filter(Boolean);
+ const currentValuesFiles = normalizeFiles(imageApp.valuesFiles || []);
+ const updatedValuesFiles = normalizeFiles(updatedApp.valuesFiles);
@@
- const updatedValues = yaml.load(updatedApp.valuesYaml || ' ') as Record<string, unknown>;
- if (JSON.stringify(imageApp.values || {}) !== JSON.stringify(updatedValues)) {
+ const updatedValuesRaw = yaml.load(updatedApp.valuesYaml || '') as unknown;
+ const updatedValues =
+ updatedValuesRaw && typeof updatedValuesRaw === 'object' && !Array.isArray(updatedValuesRaw)
+ ? (updatedValuesRaw as Record<string, unknown>)
+ : {};
+ if (JSON.stringify(imageApp.values || {}) !== JSON.stringify(updatedValues)) {
return true;
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The app is an image application (Quadlet/Compose image apps) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isCurrentImageApp) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const imageApp = currentApp as ImageApplicationProviderSpec; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -595,8 +646,8 @@ export const getApplicationValues = (deviceSpec?: DeviceSpec): AppForm[] => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) || []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| specType: AppSpecType.OCI_IMAGE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appType: AppType.AppTypeContainer, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| specType: AppSpecType.OCI_IMAGE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: app.name || '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| image: app.image, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variables: getAppFormVariables(app), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -611,23 +662,36 @@ export const getApplicationValues = (deviceSpec?: DeviceSpec): AppForm[] => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Helm application | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (app.appType === AppType.AppTypeHelm && isImageAppProvider(app)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appType: AppType.AppTypeHelm, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| specType: AppSpecType.OCI_IMAGE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: app.name || '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| image: app.image, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace: app.namespace, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| valuesYaml: app.values && Object.keys(app.values).length > 0 ? yaml.dump(app.values) : undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| valuesFiles: app.valuesFiles || [''], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Compose or Quadlet image application | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isImageAppProvider(app)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appType: app.appType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| specType: AppSpecType.OCI_IMAGE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: app.name || '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| image: app.image, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appType: app.appType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variables: getAppFormVariables(app), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| volumes: convertVolumesToForm(app.volumes), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } as QuadletImageAppForm | ComposeImageAppForm; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Compose or Quadlet inline application | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const inlineApp = app as InlineApplicationProviderSpec; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| specType: AppSpecType.INLINE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appType: app.appType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| specType: AppSpecType.INLINE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: app.name || '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| files: inlineApp.inline, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variables: getAppFormVariables(app), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix typo in user-facing string.
"ordered listed" should be "order listed" or "listed order".
✏️ Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix in a separate PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@celdrake, sounds good! Thanks for catching that—I'll look for the fix in the follow-up PR.🧠 Learnings used