diff --git a/frontend/src/concepts/connectionTypes/ConnectionTypeForm.tsx b/frontend/src/concepts/connectionTypes/ConnectionTypeForm.tsx
index 939fa9ce70..35f44f9342 100644
--- a/frontend/src/concepts/connectionTypes/ConnectionTypeForm.tsx
+++ b/frontend/src/concepts/connectionTypes/ConnectionTypeForm.tsx
@@ -27,44 +27,76 @@ import {
} from '~/concepts/k8s/K8sNameDescriptionField/types';
import { ConnectionTypeDetailsHelperText } from './ConnectionTypeDetailsHelperText';
+const createSelectOption = (
+ connectionType: ConnectionTypeConfigMapObj,
+ isSelected: boolean,
+): TypeaheadSelectOption => {
+ const description = getDescriptionFromK8sResource(connectionType);
+ return {
+ value: getResourceNameFromK8sResource(connectionType),
+ content: getDisplayNameFromK8sResource(connectionType),
+ description: (
+
+ {description && (
+
+
+
+ )}
+ {connectionType.data?.category?.length && (
+
+
+
+ )}
+
+ ),
+ data: `${description} ${connectionType.data?.category?.join(' ')}`,
+ isSelected,
+ };
+};
+
const getConnectionTypeSelectOptions = (
isPreview: boolean,
- selectedConnectionType?: ConnectionTypeConfigMapObj,
connectionTypes?: ConnectionTypeConfigMapObj[],
+ selectedConnectionType?: ConnectionTypeConfigMapObj,
+ selectedConnectionTypeName?: string,
): TypeaheadSelectOption[] => {
- if (isPreview && selectedConnectionType?.metadata.annotations?.['openshift.io/display-name']) {
- return [
- {
- value: '',
- content: selectedConnectionType.metadata.annotations['openshift.io/display-name'],
- isSelected: true,
- },
- ];
- }
- if (!isPreview && connectionTypes) {
- return connectionTypes.map((t) => ({
- value: getResourceNameFromK8sResource(t),
- content: getDisplayNameFromK8sResource(t),
- description: (
-
- {getDescriptionFromK8sResource(t) && (
-
-
-
- )}
- {t.data?.category?.length && (
-
-
-
- )}
-
- ),
- data: `${getDescriptionFromK8sResource(t)} ${t.data?.category?.join(' ')}`,
- isSelected:
- !!selectedConnectionType &&
- getResourceNameFromK8sResource(t) ===
- getResourceNameFromK8sResource(selectedConnectionType),
- }));
+ if (isPreview) {
+ const displayName = selectedConnectionType?.metadata.annotations?.['openshift.io/display-name'];
+ if (displayName) {
+ return [
+ {
+ value: '',
+ content: displayName,
+ isSelected: true,
+ },
+ ];
+ }
+ } else {
+ if (!connectionTypes || connectionTypes.length === 0) {
+ if (selectedConnectionType) {
+ return [createSelectOption(selectedConnectionType, true)];
+ }
+ if (selectedConnectionTypeName) {
+ return [
+ {
+ value: selectedConnectionTypeName,
+ content: selectedConnectionTypeName,
+ isSelected: true,
+ },
+ ];
+ }
+ }
+ if (connectionTypes) {
+ return connectionTypes.map((t) =>
+ createSelectOption(
+ t,
+ !!selectedConnectionType &&
+ getResourceNameFromK8sResource(t) ===
+ (selectedConnectionTypeName ||
+ getResourceNameFromK8sResource(selectedConnectionType)),
+ ),
+ );
+ }
}
return [];
};
@@ -73,33 +105,36 @@ type Props = Pick<
React.ComponentProps,
'onChange' | 'onValidate'
> & {
- connectionType?: ConnectionTypeConfigMapObj;
- setConnectionType?: (obj?: ConnectionTypeConfigMapObj) => void;
- connectionTypes?: ConnectionTypeConfigMapObj[];
+ connectionType?: ConnectionTypeConfigMapObj | string;
+ setConnectionType?: (name: string) => void;
+ options?: ConnectionTypeConfigMapObj[];
isPreview?: boolean;
connectionNameDesc?: K8sNameDescriptionFieldData;
setConnectionNameDesc?: K8sNameDescriptionFieldUpdateFunction;
connectionValues?: {
[key: string]: ConnectionTypeValueType;
};
- disableTypeSelection?: boolean;
};
const ConnectionTypeForm: React.FC = ({
- connectionType,
+ connectionType: connectionTypeUnion,
setConnectionType,
- connectionTypes,
+ options,
isPreview = false,
connectionNameDesc,
setConnectionNameDesc,
connectionValues,
onChange,
onValidate,
- disableTypeSelection,
}) => {
- const options: TypeaheadSelectOption[] = React.useMemo(
- () => getConnectionTypeSelectOptions(isPreview, connectionType, connectionTypes),
- [isPreview, connectionType, connectionTypes],
+ const [connectionTypeName, connectionType] =
+ typeof connectionTypeUnion === 'string'
+ ? [connectionTypeUnion]
+ : [connectionTypeUnion?.metadata.name, connectionTypeUnion];
+
+ const selectOptions: TypeaheadSelectOption[] = React.useMemo(
+ () => getConnectionTypeSelectOptions(isPreview, options, connectionType, connectionTypeName),
+ [isPreview, options, connectionType, connectionTypeName],
);
return (
@@ -108,11 +143,13 @@ const ConnectionTypeForm: React.FC = ({
- setConnectionType?.(connectionTypes?.find((c) => c.metadata.name === selection))
- }
- isDisabled={isPreview || disableTypeSelection}
+ selectOptions={selectOptions}
+ onSelect={(_, selection) => {
+ if (typeof selection === 'string') {
+ setConnectionType?.(selection);
+ }
+ }}
+ isDisabled={isPreview || !options || options.length <= 1}
placeholder={
isPreview && !connectionType?.metadata.annotations?.['openshift.io/display-name']
? 'Unspecified'
@@ -137,7 +174,7 @@ const ConnectionTypeForm: React.FC = ({
)}
- {(isPreview || connectionType?.metadata.name) && (
+ {(isPreview || connectionTypeName) && (
> = ({
'data-testid': dataTestId,
}) => {
const isPreview = mode === 'preview';
-
- // ensure the value is not undefined
- React.useEffect(() => {
- if (value == null) {
- onChange?.(field.properties.defaultValue ?? false);
- }
- // do not run when callback changes
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [value]);
-
return (
(
+ connectionTypes: T[],
+): T[] =>
+ connectionTypes.filter((t) => t.metadata.annotations?.['opendatahub.io/enabled'] === 'true');
diff --git a/frontend/src/pages/projects/screens/detail/connections/ConnectionsList.tsx b/frontend/src/pages/projects/screens/detail/connections/ConnectionsList.tsx
index 3ea827c639..286f4f98e9 100644
--- a/frontend/src/pages/projects/screens/detail/connections/ConnectionsList.tsx
+++ b/frontend/src/pages/projects/screens/detail/connections/ConnectionsList.tsx
@@ -11,6 +11,7 @@ import { ProjectObjectType, typedEmptyImage } from '~/concepts/design/utils';
import { Connection } from '~/concepts/connectionTypes/types';
import { useWatchConnectionTypes } from '~/utilities/useWatchConnectionTypes';
import { createSecret, replaceSecret } from '~/api';
+import { filterEnabledConnectionTypes } from '~/concepts/connectionTypes/utils';
import ConnectionsTable from './ConnectionsTable';
import { ManageConnectionModal } from './ManageConnectionsModal';
@@ -23,6 +24,10 @@ const ConnectionsList: React.FC = () => {
currentProject,
} = React.useContext(ProjectDetailsContext);
const [connectionTypes, connectionTypesLoaded, connectionTypesError] = useWatchConnectionTypes();
+ const enabledConnectionTypes = React.useMemo(
+ () => filterEnabledConnectionTypes(connectionTypes),
+ [connectionTypes],
+ );
const [manageConnectionModal, setManageConnectionModal] = React.useState<{
connection?: Connection;
@@ -47,6 +52,7 @@ const ConnectionsList: React.FC = () => {
onClick={() => {
setManageConnectionModal({});
}}
+ isDisabled={enabledConnectionTypes.length === 0}
>
Add connection
,
@@ -65,6 +71,10 @@ const ConnectionsList: React.FC = () => {
key={`action-${ProjectSectionID.CONNECTIONS}`}
variant="primary"
data-testid="create-connection-button"
+ onClick={() => {
+ setManageConnectionModal({});
+ }}
+ isDisabled={enabledConnectionTypes.length === 0}
>
Create connection
diff --git a/frontend/src/pages/projects/screens/detail/connections/ManageConnectionsModal.tsx b/frontend/src/pages/projects/screens/detail/connections/ManageConnectionsModal.tsx
index 6888a7096c..32a6f97bf1 100644
--- a/frontend/src/pages/projects/screens/detail/connections/ManageConnectionsModal.tsx
+++ b/frontend/src/pages/projects/screens/detail/connections/ManageConnectionsModal.tsx
@@ -13,6 +13,7 @@ import { ProjectKind, SecretKind } from '~/k8sTypes';
import { useK8sNameDescriptionFieldData } from '~/concepts/k8s/K8sNameDescriptionField/K8sNameDescriptionField';
import {
assembleConnectionSecret,
+ filterEnabledConnectionTypes,
getDefaultValues,
parseConnectionSecretValues,
} from '~/concepts/connectionTypes/utils';
@@ -40,36 +41,38 @@ export const ManageConnectionModal: React.FC = ({
const [isModified, setIsModified] = React.useState(false);
const enabledConnectionTypes = React.useMemo(
- () =>
- connectionTypes.filter((t) => t.metadata.annotations?.['opendatahub.io/enabled'] === 'true'),
+ () => filterEnabledConnectionTypes(connectionTypes),
[connectionTypes],
);
+ const connectionTypeRef = connection?.metadata.annotations['opendatahub.io/connection-type'];
+
const [selectedConnectionType, setSelectedConnectionType] = React.useState<
ConnectionTypeConfigMapObj | undefined
>(() => {
- if (isEdit && connection) {
- return connectionTypes.find(
- (t) =>
- t.metadata.name === connection.metadata.annotations['opendatahub.io/connection-type'],
- );
+ if (isEdit) {
+ return connectionTypes.find((t) => t.metadata.name === connectionTypeRef);
}
if (enabledConnectionTypes.length === 1) {
return enabledConnectionTypes[0];
}
return undefined;
});
+
+ const connectionTypeName = selectedConnectionType?.metadata.name || connectionTypeRef;
+
const { data: nameDescData, onDataChange: setNameDescData } = useK8sNameDescriptionFieldData({
initialData: connection,
});
const [connectionValues, setConnectionValues] = React.useState<{
[key: string]: ConnectionTypeValueType;
}>(() => {
- if (connection?.data) {
- return parseConnectionSecretValues(connection, selectedConnectionType);
- }
- if (enabledConnectionTypes.length === 1) {
- return getDefaultValues(enabledConnectionTypes[0]);
+ if (isEdit) {
+ if (connection) {
+ return parseConnectionSecretValues(connection, selectedConnectionType);
+ }
+ } else if (selectedConnectionType) {
+ return getDefaultValues(selectedConnectionType);
}
return {};
});
@@ -79,9 +82,9 @@ export const ManageConnectionModal: React.FC = ({
}>({});
const isFormValid = React.useMemo(
() =>
- !!selectedConnectionType &&
+ !!connectionTypeName &&
!!nameDescData.name &&
- !selectedConnectionType.data?.fields?.find(
+ !selectedConnectionType?.data?.fields?.find(
(field) =>
isConnectionTypeDataField(field) &&
field.required &&
@@ -89,7 +92,7 @@ export const ManageConnectionModal: React.FC = ({
field.type !== ConnectionTypeFieldType.Boolean,
) &&
!Object.values(validations).includes(false),
- [selectedConnectionType, nameDescData, connectionValues, validations],
+ [connectionTypeName, selectedConnectionType, nameDescData, connectionValues, validations],
);
// if user changes connection types, don't discard previous entries in case of accident
@@ -137,19 +140,14 @@ export const ManageConnectionModal: React.FC = ({
setError(undefined);
// this shouldn't ever happen, but type safety
- if (!selectedConnectionType) {
+ if (!connectionTypeName) {
setError(new Error('No connection type selected'));
setIsSaving(false);
return;
}
onSubmit(
- assembleConnectionSecret(
- project,
- selectedConnectionType,
- nameDescData,
- connectionValues,
- ),
+ assembleConnectionSecret(project, connectionTypeName, nameDescData, connectionValues),
)
.then(() => {
onClose(true);
@@ -160,7 +158,7 @@ export const ManageConnectionModal: React.FC = ({
});
}}
error={error}
- isSubmitDisabled={!isFormValid || !isModified}
+ isSubmitDisabled={!isFormValid || !isModified || isSaving}
isSubmitLoading={isSaving}
alertTitle=""
/>
@@ -168,7 +166,7 @@ export const ManageConnectionModal: React.FC = ({
>
{isEdit && (
= ({
)}
{
+ options={!isEdit ? enabledConnectionTypes : undefined}
+ connectionType={selectedConnectionType || (isEdit ? connectionTypeRef : undefined)}
+ setConnectionType={(name: string) => {
+ const obj = connectionTypes.find((c) => c.metadata.name === name);
if (!isModified) {
setIsModified(true);
}
@@ -203,7 +202,6 @@ export const ManageConnectionModal: React.FC = ({
onValidate={(field, isValid) =>
setValidations((prev) => ({ ...prev, [field.envVar]: isValid }))
}
- disableTypeSelection={isEdit || enabledConnectionTypes.length === 1}
/>
);
diff --git a/frontend/src/pages/projects/screens/detail/connections/__tests__/ManageConnectionsModal.spec.tsx b/frontend/src/pages/projects/screens/detail/connections/__tests__/ManageConnectionsModal.spec.tsx
index 3c02ac2e4f..3c8162994f 100644
--- a/frontend/src/pages/projects/screens/detail/connections/__tests__/ManageConnectionsModal.spec.tsx
+++ b/frontend/src/pages/projects/screens/detail/connections/__tests__/ManageConnectionsModal.spec.tsx
@@ -496,7 +496,7 @@ describe('Edit connection modal', () => {
connectionType: 's3',
data: {
UNMATCHED_1: window.btoa('unmatched1!'),
- env1: window.btoa('saved data'),
+ env1: window.btoa('true'),
UNMATCHED_2: window.btoa('unmatched2!'),
},
})}
@@ -505,8 +505,8 @@ describe('Edit connection modal', () => {
name: 's3',
fields: [
{
- type: 'short-text',
- name: 'Short text',
+ type: 'boolean',
+ name: 'Checkbox field',
envVar: 'env1',
properties: {},
},
@@ -517,8 +517,35 @@ describe('Edit connection modal', () => {
);
expect(screen.getByRole('combobox')).toHaveValue('s3');
- expect(screen.getByRole('textbox', { name: 'Short text' })).toHaveValue('saved data');
+ expect(screen.getByRole('checkbox', { name: 'Checkbox field' })).toBeChecked();
+ expect(screen.getByRole('textbox', { name: 'UNMATCHED_1' })).toHaveValue('unmatched1!');
+ expect(screen.getByRole('textbox', { name: 'UNMATCHED_2' })).toHaveValue('unmatched2!');
+ });
+
+ it('should list non matching values as short text with missing connection type', async () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByRole('combobox')).toHaveValue('s3');
expect(screen.getByRole('textbox', { name: 'UNMATCHED_1' })).toHaveValue('unmatched1!');
+ expect(screen.getByRole('textbox', { name: 'env1' })).toHaveValue('true');
expect(screen.getByRole('textbox', { name: 'UNMATCHED_2' })).toHaveValue('unmatched2!');
});
});