Skip to content
Merged
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-react': patch
---

Hide wizard steps when conditional `ui:hidden` rules evaluate to true, and add `isNotEmptyList`/`notContains` operators for conditional hidden expressions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch
---

Remove unnecessary gaps from conditionally hidden form fields.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@red-hat-developer-hub/backstage-plugin-orchestrator-backend': patch
---

Fix an issue where filtering workflow runs by status or date could show an error instead of results.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-widgets': patch
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-api': patch
'@red-hat-developer-hub/backstage-plugin-orchestrator': patch
---

detect GitHub SAML SSO session expiry and prompt users to re-authorize
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,53 @@ function handleNestedFilter(
return filterClause;
}

function handleBetweenOperator(filter: FieldFilter): FilterClause {
function getGraphQLVariableType(
fieldName: string,
fieldDef: IntrospectionField | undefined,
type: ProcessType,
isArray: boolean,
): string {
if (isEnumFilter(fieldName, type)) {
return isArray ? '[ProcessInstanceState!]' : 'ProcessInstanceState';
}

if (fieldDef?.type.name === TypeName.Date) {
return 'DateTime!';
}

if (isArray) {
return '[String!]';
}

return 'String';
}

function handleBetweenOperator(
filter: FieldFilter,
fieldDef: IntrospectionField | undefined,
): FilterClause {
if (!Array.isArray(filter.value) || filter.value.length !== 2) {
throw new Error('Between operator requires an array of two elements');
}
const paramType = getGraphQLVariableType(
filter.field,
fieldDef,
'ProcessInstance',
false,
);
const filterClauseVariableArray: FilterClauseVariable[] = [];
const clauseVariableName1 = `clauseVariable${nonSecureRandomAlphaNumeric()}`;
const filterClauseVariable1: FilterClauseVariable = {
clauseVariableName: clauseVariableName1,
formattedValue: filter.value[0],
clauseVariableType: 'String',
clauseVariableType: paramType,
};

const clauseVariableName2 = `clauseVariable${nonSecureRandomAlphaNumeric()}`;
const filterClauseVariable2: FilterClauseVariable = {
clauseVariableName: clauseVariableName2,
formattedValue: filter.value[1],
clauseVariableType: 'String',
clauseVariableType: paramType,
};

const clause = `${filter.field}: {${getGraphQLOperator(
Expand Down Expand Up @@ -193,23 +223,25 @@ function handleBinaryOperator(
}
}
let formattedValue: any;
let paramType: string;
if (Array.isArray(binaryFilter.value)) {
formattedValue = binaryFilter.value.map(v =>
const isArray = Array.isArray(binaryFilter.value);
if (isArray) {
formattedValue = (binaryFilter.value as unknown[]).map((v: unknown) =>
formatValue(binaryFilter.field, v, fieldDef, type),
);
paramType = isEnumFilter(binaryFilter.field, type)
? '[ProcessInstanceState!]'
: '[String!]';
} else {
formattedValue = formatValue(
binaryFilter.field,
binaryFilter.value,
fieldDef,
type,
);
paramType = 'String';
}
const paramType = getGraphQLVariableType(
binaryFilter.field,
fieldDef,
type,
isArray,
);

const clauseVariableName = `clauseVariable${nonSecureRandomAlphaNumeric()}`;
const clause = `${binaryFilter.field}: {${getGraphQLOperator(binaryFilter.operator)}: $${clauseVariableName}}`;
Expand Down Expand Up @@ -273,7 +305,7 @@ export function buildFilterCondition(
case FieldFilterOperatorEnum.IsNull:
return handleIsNullOperator(filters);
case FieldFilterOperatorEnum.Between:
return handleBetweenOperator(filters);
return handleBetweenOperator(filters, fieldDef);
case FieldFilterOperatorEnum.Eq:
case FieldFilterOperatorEnum.Like:
case FieldFilterOperatorEnum.In:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ describe('column filters', () => {
filter: Filter | undefined;
expectedResult: string | FilterClause;
expectedFormattedValue: Array<string | boolean | string[]>;
expectedVariableTypes?: string[];
};
describe('empty filter testcases', () => {
const emptyFilterTestCases: FilterTestCase[] = [
Expand Down Expand Up @@ -519,6 +520,7 @@ describe('column filters', () => {
),
expectedResult: `start: {equal: $variable1}`,
expectedFormattedValue: [testDate1],
expectedVariableTypes: ['DateTime!'],
},
{
name: 'returns correct filter for single date field with isNull operator (false as boolean)',
Expand Down Expand Up @@ -595,6 +597,7 @@ describe('column filters', () => {
]),
expectedResult: `start: {between: {from: $variable1, to: $variable2}}`,
expectedFormattedValue: [testDate1, testDate2],
expectedVariableTypes: ['DateTime!', 'DateTime!'],
},
{
name: 'returns correct OR filter for multiple id fields with equal, isNull, and GT operators',
Expand Down Expand Up @@ -683,6 +686,7 @@ describe('column filters', () => {
filter,
expectedResult,
expectedFormattedValue,
expectedVariableTypes,
}) => {
it(`${name}`, () => {
const result = buildFilterCondition(
Expand All @@ -698,6 +702,9 @@ describe('column filters', () => {
`$${item.clauseVariableName}`,
);
expect(item.formattedValue).toEqual(expectedFormattedValue[index]);
expect(item.clauseVariableType).toBe(
expectedVariableTypes?.[index] ?? item.clauseVariableType,
);
});
expect(formattedClause).toBe(result.clause);
});
Expand All @@ -718,6 +725,7 @@ describe('column filters', () => {
),
expectedResult: `state: {equal: $variable1}`,
expectedFormattedValue: ['COMPLETED'],
expectedVariableTypes: ['ProcessInstanceState'],
},
];

Expand All @@ -728,6 +736,7 @@ describe('column filters', () => {
filter,
expectedResult,
expectedFormattedValue,
expectedVariableTypes,
}) => {
it(`${name}`, () => {
const result = buildFilterCondition(
Expand All @@ -743,6 +752,9 @@ describe('column filters', () => {
`$${item.clauseVariableName}`,
);
expect(item.formattedValue).toEqual(expectedFormattedValue[index]);
expect(item.clauseVariableType).toBe(
expectedVariableTypes?.[index] ?? item.clauseVariableType,
);
});
expect(formattedClause).toBe(result.clause);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export type OrchestratorFormContextProps = {
setIsChangedByUser: (id: string, isChangedByUser: boolean) => void;
handleFetchStarted?: () => void;
handleFetchEnded?: () => void;
onSamlSsoError?: (error: Error) => void;
};

// @public
Expand Down Expand Up @@ -90,7 +91,7 @@ export const useOrchestratorFormApiOrDefault: () => OrchestratorFormApi;

// Warnings were encountered during analysis:
//
// src/api.d.ts:131:22 - (ae-undocumented) Missing documentation for "useOrchestratorFormApiOrDefault".
// src/api.d.ts:132:22 - (ae-undocumented) Missing documentation for "useOrchestratorFormApiOrDefault".

// (No @packageDocumentation comment for this package)
```
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export type OrchestratorFormContextProps = {
setIsChangedByUser: (id: string, isChangedByUser: boolean) => void;
handleFetchStarted?: () => void;
handleFetchEnded?: () => void;
onSamlSsoError?: (error: Error) => void;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export interface HiddenConditionObject {
is?: JsonValue | JsonValue[];
isEmpty?: boolean;
isNot?: JsonValue | JsonValue[];
isNotEmptyList?: boolean;
notContains?: JsonValue;
when: string;
}

Expand All @@ -56,6 +58,7 @@ export type OrchestratorFormProps = {
schema: JSONSchema7;
updateSchema: OrchestratorFormContextProps['updateSchema'];
setAuthTokenDescriptors: OrchestratorFormContextProps['setAuthTokenDescriptors'];
onSamlSsoError?: OrchestratorFormContextProps['onSamlSsoError'];
isExecuting: boolean;
handleExecute: (parameters: JsonObject) => Promise<void>;
handleExecuteAsEvent?: (parameters: JsonObject) => Promise<void>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ const HiddenObjectFieldTemplate = (
ButtonTemplates: { AddButton },
} = registry.templates;

const rootFormData =
(formContext?.formData as JsonObject) || (formData as JsonObject) || {};
const rootFormData = (formContext?.formData as JsonObject) || {};
const localFormData = (formData as JsonObject) || rootFormData;

return (
<>
Expand Down Expand Up @@ -105,7 +105,11 @@ const HiddenObjectFieldTemplate = (
const hiddenCondition = getHiddenCondition(uiSchema, element.name);
const isHiddenByCondition =
hiddenCondition !== undefined
? evaluateHiddenCondition(hiddenCondition, rootFormData)
? evaluateHiddenCondition(
hiddenCondition,
localFormData,
rootFormData,
)
: false;
const isHidden = element.hidden || isHiddenByCondition;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export type OrchestratorFormProps = {
schema: JSONSchema7;
updateSchema: OrchestratorFormContextProps['updateSchema'];
setAuthTokenDescriptors: OrchestratorFormContextProps['setAuthTokenDescriptors'];
onSamlSsoError?: OrchestratorFormContextProps['onSamlSsoError'];
isExecuting: boolean;
handleExecute: (parameters: JsonObject) => Promise<void>;
handleExecuteAsEvent?: (parameters: JsonObject) => Promise<void>;
Expand Down Expand Up @@ -157,6 +158,7 @@ const OrchestratorForm = ({
isExecuting,
initialFormData,
setAuthTokenDescriptors,
onSamlSsoError,
t,
executeLabel,
executeAsEventLabel,
Expand Down Expand Up @@ -270,6 +272,7 @@ const OrchestratorForm = ({
formData={formData}
setFormData={setFormData}
setAuthTokenDescriptors={setAuthTokenDescriptors}
onSamlSsoError={onSamlSsoError}
getIsChangedByUser={getIsChangedByUser}
setIsChangedByUser={setIsChangedByUser}
>
Expand All @@ -284,6 +287,7 @@ const OrchestratorForm = ({
formData={formData}
setFormData={setFormData}
setAuthTokenDescriptors={setAuthTokenDescriptors}
onSamlSsoError={onSamlSsoError}
getIsChangedByUser={getIsChangedByUser}
setIsChangedByUser={setIsChangedByUser}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const FormComponent = (decoratorProps: FormDecoratorProps) => {
return undefined;
}

return getActiveStepKey(schema, activeStep);
return getActiveStepKey(schema, activeStep, formData);
};

const onSubmit = async (_formData: JsonObject) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type SingleStepFormProps = Pick<
| 'formData'
| 'setFormData'
| 'setAuthTokenDescriptors'
| 'onSamlSsoError'
| 'getIsChangedByUser'
| 'setIsChangedByUser'
>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ const StepperObjectField = ({
const { t } = useTranslation();

const sortedStepEntries = useMemo(
() => getSortedStepEntries(schema),
[schema],
() => getSortedStepEntries(schema, formData),
[schema, formData],
);
if (sortedStepEntries === undefined) {
throw new Error(t('stepperObjectField.error'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ export interface HiddenConditionObject {
* Hide if the field is empty (undefined, null, empty string, or empty array)
*/
isEmpty?: boolean;

/**
* Hide if the field is a non-empty array (when true) or an empty/non-array value (when false).
*/
isNotEmptyList?: boolean;

/**
* Hide if the field value (an array) does NOT contain this value.
*/
notContains?: JsonValue;
}

/**
Expand Down
Loading
Loading