diff --git a/frontend/src/__mocks__/mockConnectionType.ts b/frontend/src/__mocks__/mockConnectionType.ts new file mode 100644 index 0000000000..9731c3ad9b --- /dev/null +++ b/frontend/src/__mocks__/mockConnectionType.ts @@ -0,0 +1,393 @@ +import { + ConnectionTypeConfigMap, + ConnectionTypeConfigMapObj, + ConnectionTypeField, +} from '~/concepts/connectionTypes/types'; +import { toConnectionTypeConfigMap } from '~/concepts/connectionTypes/utils'; + +type MockConnectionTypeConfigMap = { + name?: string; + namespace?: string; + displayName?: string; + description?: string; + enabled?: boolean; + username?: string; + preInstalled?: boolean; + fields?: ConnectionTypeField[]; +}; + +export const mockConnectionTypeConfigMap = ( + options: MockConnectionTypeConfigMap, +): ConnectionTypeConfigMap => toConnectionTypeConfigMap(mockConnectionTypeConfigMapObj(options)); + +export const mockConnectionTypeConfigMapObj = ({ + name = 'connection-type-sample', + namespace = 'opendatahub', + displayName = name, + description = 'Connection type description', + enabled = true, + username = 'dashboard-admin', + preInstalled = false, + ...rest +}: MockConnectionTypeConfigMap): ConnectionTypeConfigMapObj => ({ + kind: 'ConfigMap', + apiVersion: 'v1', + metadata: { + name, + namespace, + resourceVersion: '173155965', + creationTimestamp: '2024-08-29T00:00:00Z', + labels: { 'opendatahub.io/dashboard': 'true', 'opendatahub.io/connection-type': 'true' }, + annotations: { + 'openshift.io/display-name': displayName, + 'openshift.io/description': description, + 'opendatahub.io/enabled': enabled ? 'true' : 'false', + 'opendatahub.io/username': username || '', + }, + ...(preInstalled + ? { + ownerReferences: [ + { + apiVersion: 'datasciencecluster.opendatahub.io/v1', + kind: 'DataScienceCluster', + name: 'default-dsc', + uid: '06dd5a40-8473-4d5f-8afa-36885aa26ca9', + controller: true, + blockOwnerDeletion: true, + }, + ], + } + : undefined), + }, + data: { + fields: 'fields' in rest ? rest.fields : mockFields, + }, +}); + +const mockFields: ConnectionTypeField[] = [ + { + type: 'section', + name: 'Short text', + description: 'This section contains short text fields.', + }, + { + type: 'short-text', + name: 'Short text 1', + description: 'Test short text', + envVar: 'short-text-1', + required: false, + properties: {}, + }, + { + type: 'short-text', + name: 'Short text 2', + description: 'Test short text with default value', + envVar: 'short-text-2', + required: true, + properties: { + defaultValue: 'This is the default value', + defaultReadOnly: false, + }, + }, + { + type: 'short-text', + name: 'Short text 3', + description: 'Test short text with default value and read only', + envVar: 'short-text-1', + required: false, + properties: { + defaultValue: 'This is the default value and is read only', + defaultReadOnly: true, + }, + }, + + { + type: 'section', + name: 'Paragraph', + description: 'This section contains paragraph fields.', + }, + { + type: 'paragraph', + name: 'Paragraph 1', + description: 'Test paragraph', + envVar: 'paragraph-1', + required: false, + properties: {}, + }, + { + type: 'paragraph', + name: 'Paragraph 2', + description: 'Test paragraph with default value', + envVar: 'paragraph-2', + required: true, + properties: { + defaultValue: 'This is the default value', + defaultReadOnly: false, + }, + }, + { + type: 'paragraph', + name: 'Paragraph 3', + description: 'Test paragraph with default value and read only', + envVar: 'paragraph-3', + required: false, + properties: { + defaultValue: 'This is the default value', + defaultReadOnly: true, + }, + }, + + { + type: 'section', + name: 'Hidden', + description: 'This section contains hidden fields.', + }, + { + type: 'hidden', + name: 'Hidden 1', + description: 'Test hidden', + envVar: 'hidden-1', + required: false, + properties: {}, + }, + { + type: 'hidden', + name: 'Hidden 2', + description: 'Test hidden with default value', + envVar: 'hidden-2', + required: true, + properties: { + defaultValue: 'This is the default value', + defaultReadOnly: false, + }, + }, + { + type: 'hidden', + name: 'Hidden 3', + description: 'Test hidden with default value and read only', + envVar: 'hidden-3', + required: false, + properties: { + defaultValue: 'This is the default value', + defaultReadOnly: true, + }, + }, + + { + type: 'section', + name: 'URI', + description: 'This section contains URI fields.', + }, + { + type: 'uri', + name: 'URI 1', + description: 'Test URI', + envVar: 'uri-1', + required: false, + properties: {}, + }, + { + type: 'uri', + name: 'URI 2', + description: 'Test URI with default value', + envVar: 'uri-2', + required: true, + properties: { + defaultValue: 'https://www.redhat.com', + defaultReadOnly: false, + }, + }, + { + type: 'uri', + name: 'URI 3', + description: 'Test URI with default value and read only', + envVar: 'uri-3', + required: false, + properties: { + defaultValue: 'https://www.redhat.com', + defaultReadOnly: true, + }, + }, + + { + type: 'section', + name: 'File', + description: 'This section contains file fields.', + }, + { + type: 'file', + name: 'File 1', + description: 'Test file', + envVar: 'file-1', + required: false, + properties: {}, + }, + { + type: 'file', + name: 'File 2', + description: 'Test file with default value', + envVar: 'file-2', + required: true, + properties: { + defaultValue: 'This is the default value', + defaultReadOnly: false, + }, + }, + { + type: 'file', + name: 'File 3', + description: 'Test file with default value and read only', + envVar: 'file-3', + required: false, + properties: { + defaultValue: 'This is the default value', + defaultReadOnly: true, + }, + }, + + { + type: 'section', + name: 'Boolean', + description: 'This section contains boolean fields.', + }, + { + type: 'boolean', + name: 'Boolean 1', + description: 'Test boolean', + envVar: 'boolean-1', + required: false, + properties: {}, + }, + { + type: 'boolean', + name: 'Boolean 2', + description: 'Test boolean with default value', + envVar: 'boolean-2', + required: true, + properties: { + label: 'Input label', + defaultValue: true, + defaultReadOnly: false, + }, + }, + { + type: 'boolean', + name: 'Boolean 3', + description: 'Test boolean with default value and read only', + envVar: 'boolean-3', + required: false, + properties: { + label: 'Input label', + defaultValue: false, + defaultReadOnly: true, + }, + }, + + { + type: 'section', + name: 'Numeric', + description: 'This section contains numeric fields.', + }, + { + type: 'numeric', + name: 'Numeric 1', + description: 'Test numeric', + envVar: 'numeric-1', + required: false, + properties: {}, + }, + { + type: 'numeric', + name: 'Numeric 2', + description: 'Test numeric with default value', + envVar: 'numeric-2', + required: true, + properties: { + defaultValue: 2, + defaultReadOnly: false, + }, + }, + { + type: 'numeric', + name: 'Numeric 3', + description: 'Test numeric with default value and read only', + envVar: 'numeric-3', + required: false, + properties: { + defaultValue: 3, + defaultReadOnly: true, + }, + }, + + { + type: 'section', + name: 'Dropdown', + description: 'This section contains dropdown fields.', + }, + { + type: 'dropdown', + name: 'Dropdown 1', + description: 'Test dropdown single variant', + envVar: 'dropdown-1', + required: false, + properties: { + variant: 'single', + items: [ + { value: '1', label: 'One' }, + { value: '2', label: 'Two' }, + { value: '3', label: 'Three' }, + { value: '4', label: 'Four' }, + ], + }, + }, + { + type: 'dropdown', + name: 'Dropdown 2', + description: 'Test dropdown single variant with default value', + envVar: 'dropdown-2', + required: true, + properties: { + variant: 'single', + items: [ + { value: '1', label: 'One' }, + { value: '2', label: 'Two' }, + { value: '3', label: 'Three' }, + { value: '4', label: 'Four' }, + ], + defaultValue: ['3'], + }, + }, + { + type: 'dropdown', + name: 'Dropdown 3', + description: 'Test dropdown multi variant', + envVar: 'dropdown-3', + required: false, + properties: { + variant: 'multi', + items: [ + { value: '1', label: 'One' }, + { value: '2', label: 'Two' }, + { value: '3', label: 'Three' }, + { value: '4', label: 'Four' }, + ], + }, + }, + { + type: 'dropdown', + name: 'Dropdown 4', + description: 'Test dropdown multi variant with default values', + envVar: 'dropdown-4', + required: false, + properties: { + variant: 'multi', + items: [ + { value: '1', label: 'One' }, + { value: '2', label: 'Two' }, + { value: '3', label: 'Three' }, + { value: '4', label: 'Four' }, + ], + defaultValue: ['2', '3'], + }, + }, +]; diff --git a/frontend/src/concepts/connectionTypes/__tests__/utils.spec.ts b/frontend/src/concepts/connectionTypes/__tests__/utils.spec.ts new file mode 100644 index 0000000000..cbb9557561 --- /dev/null +++ b/frontend/src/concepts/connectionTypes/__tests__/utils.spec.ts @@ -0,0 +1,21 @@ +import { mockConnectionTypeConfigMapObj } from '~/__mocks__/mockConnectionType'; +import { + toConnectionTypeConfigMap, + toConnectionTypeConfigMapObj, +} from '~/concepts/connectionTypes/utils'; + +describe('utils', () => { + it('should serialize / deserialize connection type fields', () => { + const ct = mockConnectionTypeConfigMapObj({}); + const configMap = toConnectionTypeConfigMap(ct); + expect(typeof configMap.data.fields).toBe('string'); + expect(ct).toEqual(toConnectionTypeConfigMapObj(toConnectionTypeConfigMap(ct))); + }); + + it('should serialize / deserialize connection type with missing fields', () => { + const ct = mockConnectionTypeConfigMapObj({ fields: undefined }); + const configMap = toConnectionTypeConfigMap(ct); + expect(configMap.data.fields).toBeUndefined(); + expect(ct).toEqual(toConnectionTypeConfigMapObj(configMap)); + }); +}); diff --git a/frontend/src/concepts/connectionTypes/types.ts b/frontend/src/concepts/connectionTypes/types.ts new file mode 100644 index 0000000000..289643f57d --- /dev/null +++ b/frontend/src/concepts/connectionTypes/types.ts @@ -0,0 +1,108 @@ +import { K8sResourceCommon } from '@openshift/dynamic-plugin-sdk-utils'; +import { DashboardLabels, DisplayNameAnnotations } from '~/k8sTypes'; + +export enum ConnectionTypeFieldType { + Boolean = 'boolean', + Dropdown = 'dropdown', + File = 'file', + Hidden = 'hidden', + Numeric = 'numeric', + Paragraph = 'paragraph', + Section = 'section', + ShortText = 'short-text', + URI = 'uri', +} + +// exclude 'section' +export const connectionTypeDataFields = [ + ConnectionTypeFieldType.Boolean, + ConnectionTypeFieldType.Dropdown, + ConnectionTypeFieldType.File, + ConnectionTypeFieldType.Hidden, + ConnectionTypeFieldType.Numeric, + ConnectionTypeFieldType.Paragraph, + ConnectionTypeFieldType.ShortText, + ConnectionTypeFieldType.URI, +]; + +type Field = { + type: T; + name: string; + description?: string; +}; + +type DataField = Field & { + envVar: string; + required?: boolean; + properties: P; +}; + +type TextProps = { + defaultValue?: string; + defaultReadOnly?: boolean; +}; + +export type SectionField = Field; + +export type HiddenField = DataField; +export type ParagraphField = DataField; +export type FileField = DataField; +export type ShortTextField = DataField; +export type UriField = DataField; +export type BooleanField = DataField< + ConnectionTypeFieldType.Boolean | 'boolean', + { + label?: string; + defaultValue?: boolean; + defaultReadOnly?: boolean; + } +>; +export type DropdownField = DataField< + ConnectionTypeFieldType.Dropdown | 'dropdown', + { + variant: 'single' | 'multi'; + items: { label: string; value: string }[]; + defaultValue?: string[]; + } +>; +export type NumericField = DataField< + ConnectionTypeFieldType.Numeric | 'numeric', + { + defaultValue?: number; + defaultReadOnly?: boolean; + } +>; + +export type ConnectionTypeField = + | BooleanField + | DropdownField + | FileField + | HiddenField + | NumericField + | ParagraphField + | SectionField + | ShortTextField + | UriField; + +export type ConnectionTypeConfigMap = K8sResourceCommon & { + metadata: { + name: string; + annotations: DisplayNameAnnotations & { + 'opendatahub.io/enabled'?: 'true' | 'false'; + 'opendatahub.io/username'?: string; + }; + labels: DashboardLabels & { + 'opendatahub.io/connection-type': 'true'; + }; + }; + data: { + // JSON of type ConnectionTypeField + fields?: string; + }; +}; + +export type ConnectionTypeConfigMapObj = Omit & { + data: { + fields?: ConnectionTypeField[]; + }; +}; diff --git a/frontend/src/concepts/connectionTypes/utils.ts b/frontend/src/concepts/connectionTypes/utils.ts new file mode 100644 index 0000000000..7fb9121a2f --- /dev/null +++ b/frontend/src/concepts/connectionTypes/utils.ts @@ -0,0 +1,18 @@ +import { + ConnectionTypeConfigMap, + ConnectionTypeConfigMapObj, +} from '~/concepts/connectionTypes/types'; + +export const toConnectionTypeConfigMapObj = ( + configMap: ConnectionTypeConfigMap, +): ConnectionTypeConfigMapObj => ({ + ...configMap, + data: { fields: configMap.data.fields ? JSON.parse(configMap.data.fields) : undefined }, +}); + +export const toConnectionTypeConfigMap = ( + obj: ConnectionTypeConfigMapObj, +): ConnectionTypeConfigMap => ({ + ...obj, + data: { fields: obj.data.fields ? JSON.stringify(obj.data.fields) : undefined }, +}); diff --git a/frontend/src/k8sTypes.ts b/frontend/src/k8sTypes.ts index 4805772c98..5c5b918ffc 100644 --- a/frontend/src/k8sTypes.ts +++ b/frontend/src/k8sTypes.ts @@ -39,7 +39,7 @@ export type K8sVerb = * Annotations that we will use to allow the user flexibility in describing items outside of the * k8s structure. */ -type DisplayNameAnnotations = Partial<{ +export type DisplayNameAnnotations = Partial<{ 'openshift.io/description': string; // the description provided by the user 'openshift.io/display-name': string; // the name provided by the user }>;