Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-widgets': patch
---

Avoid hard failures in ActiveTextInput when `fetch:response:value` points to a missing response property. If JSONata resolves to `undefined` or `null`, treat it as an empty string so retriggered dynamic selector edits do not break the form UI.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { applySelectorArray } from './applySelector';
import { applySelectorArray, applySelectorString } from './applySelector';
import { JsonObject } from '@backstage/types';

describe('applySelectorArray', () => {
Expand Down Expand Up @@ -258,6 +258,40 @@ describe('applySelectorArray', () => {
});
});

describe('applySelectorString', () => {
const data: JsonObject = { status: 'UP', nested: { name: 'x' } };

it('returns string when selector evaluates to a string', async () => {
await expect(applySelectorString(data, 'status')).resolves.toBe('UP');
});

it('throws when selector is missing and emptyStringWhenMissing is false', async () => {
await expect(applySelectorString(data, 'doesNotExist')).rejects.toThrow(
'Unexpected result of "doesNotExist" selector, expected string type',
);
});

it('returns empty string when selector is missing and emptyStringWhenMissing is true', async () => {
await expect(applySelectorString(data, 'doesNotExist', true)).resolves.toBe(
'',
);
});

it('returns empty string when JSONata yields null and emptyStringWhenMissing is true', async () => {
const withNull: JsonObject = { absent: null };
await expect(applySelectorString(withNull, 'absent', true)).resolves.toBe(
'',
);
});

it('still throws for non-string non-nullish when emptyStringWhenMissing is true', async () => {
const withNum: JsonObject = { n: 42 };
await expect(applySelectorString(withNum, 'n', true)).rejects.toThrow(
'expected string type',
);
});
});

describe('applySelectorArray - complex queries', () => {
it('handles complex queries', async () => {
const data = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const applySelectorArray = async (
export const applySelectorString = async (
data: JsonObject,
selector: string,
emptyStringWhenMissing: boolean = false,
): Promise<string> => {
const expression = jsonata(selector);
const value = await expression.evaluate(data);
Expand All @@ -54,6 +55,10 @@ export const applySelectorString = async (
return value;
}

if (emptyStringWhenMissing && (value === undefined || value === null)) {
return '';
}

throw new Error(
`Unexpected result of "${selector}" selector, expected string type. Value "${JSON.stringify(value)}"`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export const ActiveTextInput: Widget<
const fetchedValue = await applySelectorString(
data,
resolvedSelector,
true,
);

if (
Expand Down
Loading