diff --git a/CHANGELOG.md b/CHANGELOG.md index 7327855a287..96938d0a78e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -159,6 +159,7 @@ function: - console: enable searching tables within a schema - console: fixed the ability to create updated_at and created_at in the modify page (#8239) - console: disable search indexing with HTML meta tag +- console: add support for setting comments on the custom root fields of tables/views - cli: fix inherited roles metadata not being updated when dropping all roles (#7872) - cli: add support for customization field in sources metadata (#8292) - ci: ubuntu and centos flavoured graphql-engine images are now available diff --git a/console/src/components/Common/SelectInputSplitField/SelectInputSplitField.tsx b/console/src/components/Common/SelectInputSplitField/SelectInputSplitField.tsx new file mode 100644 index 00000000000..409ddf7ddb5 --- /dev/null +++ b/console/src/components/Common/SelectInputSplitField/SelectInputSplitField.tsx @@ -0,0 +1,79 @@ +import React, { ReactText } from 'react'; +import clsx from 'clsx'; + +export type SelectItem = { + label: ReactText; + value: ReactText; + disabled?: boolean; +}; + +export type SelectInputSplitFieldProps = { + inputType?: 'text' | 'email' | 'password'; + selectOptions: SelectItem[]; + size?: 'full' | 'medium'; + placeholder?: string; + inputDisabled?: boolean; + selectDisabled?: boolean; + inputValue: string; + selectValue: string; + inputOnChange: (e: React.ChangeEvent) => void; + selectOnChange: (e: React.ChangeEvent) => void; +}; + +export const SelectInputSplitField = ({ + inputType = 'text', + size = 'full', + selectOptions, + placeholder, + inputDisabled, + selectDisabled, + inputValue, + selectValue, + inputOnChange, + selectOnChange, +}: SelectInputSplitFieldProps) => { + return ( +
+ + + +
+ ); +}; diff --git a/console/src/components/Services/Data/Common/Components/CommentInput.tsx b/console/src/components/Services/Data/Common/Components/CommentInput.tsx new file mode 100644 index 00000000000..15ff8b92284 --- /dev/null +++ b/console/src/components/Services/Data/Common/Components/CommentInput.tsx @@ -0,0 +1,56 @@ +import { SelectInputSplitField } from '@/components/Common/SelectInputSplitField/SelectInputSplitField'; +import React, { ChangeEvent, useCallback } from 'react'; + +export type CommentProps = { + value: string | null; + defaultComment: string; + onChange: (comment: string | null) => void; +}; + +const selectOptions = [ + { label: 'Value', value: 'Value' }, + { label: 'Disabled', value: 'Disabled' }, +]; + +export const CommentInput = ({ + value, + defaultComment, + onChange, +}: CommentProps) => { + const inputValue = value ?? ''; + const selectValue = value === '' ? 'Disabled' : 'Value'; + const placeholder = selectValue === 'Value' ? defaultComment : ''; + + const inputOnChange = useCallback( + (e: ChangeEvent) => { + if (selectValue === 'Disabled') { + onChange(''); + } else { + onChange(e.target.value === '' ? null : e.target.value); + } + }, + [selectValue, onChange] + ); + + const selectOnChange = useCallback( + (e: ChangeEvent) => { + if (e.target.value === selectValue) return; // No change + + const newComment = e.target.value === 'Disabled' ? '' : null; + onChange(newComment); + }, + [selectValue, onChange] + ); + + return ( + + ); +}; diff --git a/console/src/components/Services/Data/Common/Components/RootFieldEditor.tsx b/console/src/components/Services/Data/Common/Components/RootFieldEditor.tsx index d70f856d5f4..3bcfd27b155 100644 --- a/console/src/components/Services/Data/Common/Components/RootFieldEditor.tsx +++ b/console/src/components/Services/Data/Common/Components/RootFieldEditor.tsx @@ -1,13 +1,13 @@ import React from 'react'; -import { - CustomRootField, - CustomRootFields, -} from '../../../../../dataSources/types'; +import { CustomRootFields } from '../../../../../dataSources/types'; import CollapsibleToggle from '../../../../Common/CollapsibleToggle/CollapsibleToggle'; -import { getRootFieldLabel } from './utils'; import { Nullable } from '../../../../../components/Common/utils/tsUtils'; -import { getTableCustomRootFieldName } from '../../../../../dataSources'; +import { + getTableCustomRootFieldComment, + getTableCustomRootFieldName, +} from '../../../../../dataSources'; +import { CommentInput } from './CommentInput'; interface RootFieldEditorProps { rootFields: CustomRootFields; @@ -15,7 +15,7 @@ interface RootFieldEditorProps { tableName: string; tableSchema: string; customName?: string; - customNameOnChange: ChangeHandler; + customNameOnChange: (e: React.ChangeEvent) => void; selectOnChange: ChangeHandler; selectByPkOnChange: ChangeHandler; selectAggOnChange: ChangeHandler; @@ -27,7 +27,22 @@ interface RootFieldEditorProps { deleteByPkOnChange: ChangeHandler; } -type ChangeHandler = (e: React.ChangeEvent) => void; +interface ChangeHandler { + onNameChange: (e: React.ChangeEvent) => void; + onCommentChange: (comment: string | null) => void; +} + +export const rootFieldLabels: Record = { + select: 'Select', + select_by_pk: 'Select by PK', + select_aggregate: 'Select Aggregate', + insert: 'Insert', + insert_one: 'Insert One', + update: 'Update', + update_by_pk: 'Update by PK', + delete: 'Delete', + delete_by_pk: 'Delete by PK', +}; const RootFieldEditor: React.FC = ({ rootFields, @@ -46,17 +61,19 @@ const RootFieldEditor: React.FC = ({ customName, tableSchema, }) => { - const { - select, - select_by_pk: selectByPk, - select_aggregate: selectAgg, - insert, - insert_one: insertOne, - update, - update_by_pk: updateByPk, - delete: _delete, - delete_by_pk: deleteByPk, - } = rootFields; + const qualifiedTableName = + tableSchema === '' ? `"${tableName}"` : `"${tableSchema}.${tableName}"`; + const rootFieldDefaultComments: Record = { + select: `fetch data from the table: ${qualifiedTableName}`, + select_by_pk: `fetch data from the table: ${qualifiedTableName} using primary key columns`, + select_aggregate: `fetch aggregated fields from the table: ${qualifiedTableName}`, + insert: `insert data into the table: ${qualifiedTableName}`, + insert_one: `insert a single row into the table: ${qualifiedTableName}`, + update: `update data of the table: ${qualifiedTableName}`, + update_by_pk: `update single row of the table: ${qualifiedTableName}`, + delete: `delete data from the table: ${qualifiedTableName}`, + delete_by_pk: `delete single row from the table: ${qualifiedTableName}`, + }; const getRootField = () => { if (customName) { @@ -85,27 +102,53 @@ const RootFieldEditor: React.FC = ({ return `${rfType}_${rootField}`; }; - const getRow = ( - rfType: string, - value: Nullable | CustomRootField, - onChange: ChangeHandler + const getCustomNameRow = ( + value: Nullable, + onChange: (e: React.ChangeEvent) => void ) => ( -
-
{getRootFieldLabel(rfType)}
-
+
+
Custom Table Name
+
+
); - const getSection = (rfType: string) => { + const getRootFieldRow = ( + rfType: keyof CustomRootFields, + onChange: ChangeHandler + ) => ( +
+
{rootFieldLabels[rfType]}
+
+ +
+
+ +
+
+ ); + + const getSection = (rfType: 'query' | 'mutation') => { return (
= ({ useDefaultTitleStyle isOpen > +
+
+
Field Name
+
Comment
+
{rfType === 'query' && (
- {getRow('select', select, selectOnChange)} - {getRow('select_by_pk', selectByPk, selectByPkOnChange)} - {getRow('select_aggregate', selectAgg, selectAggOnChange)} + {getRootFieldRow('select', selectOnChange)} + {getRootFieldRow('select_by_pk', selectByPkOnChange)} + {getRootFieldRow('select_aggregate', selectAggOnChange)}
)} {rfType === 'mutation' && (
- {getRow('insert', insert, insertOnChange)} - {getRow('insert_one', insertOne, insertOneOnChange)} - {getRow('update', update, updateOnChange)} - {getRow('update_by_pk', updateByPk, updateByPkOnChange)} - {getRow('delete', _delete, deleteOnChange)} - {getRow('delete_by_pk', deleteByPk, deleteByPkOnChange)} + {getRootFieldRow('insert', insertOnChange)} + {getRootFieldRow('insert_one', insertOneOnChange)} + {getRootFieldRow('update', updateOnChange)} + {getRootFieldRow('update_by_pk', updateByPkOnChange)} + {getRootFieldRow('delete', deleteOnChange)} + {getRootFieldRow('delete_by_pk', deleteByPkOnChange)}
)}
); }; - return (
- {getRow('custom_name', customName, customNameOnChange)} + {getCustomNameRow(customName, customNameOnChange)}
{getSection('query')} {getSection('mutation')} diff --git a/console/src/components/Services/Data/Common/Components/utils.js b/console/src/components/Services/Data/Common/Components/utils.js index 84785ea1e25..661820e75ee 100644 --- a/console/src/components/Services/Data/Common/Components/utils.js +++ b/console/src/components/Services/Data/Common/Components/utils.js @@ -110,19 +110,3 @@ export const getKeyDef = (config, constraintName) => {
); }; - -export const getRootFieldLabel = rfType => { - const labels = { - custom_name: 'Custom Table Name', - select: 'Select', - select_by_pk: 'Select by PK', - select_aggregate: 'Select Aggregate', - insert: 'Insert', - insert_one: 'Insert One', - update: 'Update', - update_by_pk: 'Update by PK', - delete: 'Delete', - delete_by_pk: 'Delete by PK', - }; - return labels[rfType]; -}; diff --git a/console/src/components/Services/Data/TableModify/ColumnEditorList.js b/console/src/components/Services/Data/TableModify/ColumnEditorList.js index 0e0ca7f74b1..1a7ae13c20b 100644 --- a/console/src/components/Services/Data/TableModify/ColumnEditorList.js +++ b/console/src/components/Services/Data/TableModify/ColumnEditorList.js @@ -145,15 +145,17 @@ const ColumnEditorList = ({ const keyPropertiesString = propertiesList.join(', '); propertiesDisplay.push( - + {keyPropertiesString} ); propertiesDisplay.push( - - {columnProperties.comment && `${columnProperties.comment}`} - +
+ + {columnProperties.comment && `${columnProperties.comment}`} + +
); return propertiesDisplay; @@ -161,15 +163,13 @@ const ColumnEditorList = ({ const collapsedLabel = () => { return ( -
-
- {colName} - - {columnProperties.customFieldName && - ` → ${columnProperties.customFieldName}`} - -
{' '} - {gqlCompatibilityWarning()} - {keyProperties()} +
+ {colName} + + {columnProperties.customFieldName && + ` → ${columnProperties.customFieldName}`} + + - {gqlCompatibilityWarning()} {keyProperties()}
); }; diff --git a/console/src/components/Services/Data/TableModify/ModifyTable.js b/console/src/components/Services/Data/TableModify/ModifyTable.js index b1c12bed26e..629e3bb002b 100644 --- a/console/src/components/Services/Data/TableModify/ModifyTable.js +++ b/console/src/components/Services/Data/TableModify/ModifyTable.js @@ -217,7 +217,7 @@ class ModifyTable extends React.Component { )} -
+

Configure Fields

diff --git a/console/src/components/Services/Data/TableModify/RootFieldsEditor.tsx b/console/src/components/Services/Data/TableModify/RootFieldsEditor.tsx index 2caeb293d94..c2f6057cc02 100644 --- a/console/src/components/Services/Data/TableModify/RootFieldsEditor.tsx +++ b/console/src/components/Services/Data/TableModify/RootFieldsEditor.tsx @@ -8,9 +8,14 @@ import { } from './ModifyActions'; import { Dispatch } from '../../../../types'; -import { CustomRootFields } from '../../../../dataSources/types'; import { + CustomRootField, + CustomRootFields, +} from '../../../../dataSources/types'; +import { + getTableCustomRootFieldComment, getTableCustomRootFieldName, + setTableCustomRootFieldComment, setTableCustomRootFieldName, } from '../../../../dataSources'; @@ -37,56 +42,69 @@ const RootFieldsEditor = ({ dispatch(modifyRootFields(rf)); }; - const onChange = (field: keyof CustomRootFields, customField: string) => { - const newRootFields = { - ...rootFieldsEdit, - [field]: setTableCustomRootFieldName(rootFieldsEdit[field], customField), - }; - dispatch(modifyRootFields(newRootFields)); - }; + const onRootFieldChange = (field: keyof CustomRootFields) => ({ + onNameChange: (e: React.ChangeEvent) => { + const newRootFields = { + ...rootFieldsEdit, + [field]: setTableCustomRootFieldName( + rootFieldsEdit[field], + e.target.value + ), + }; + dispatch(modifyRootFields(newRootFields)); + }, + + onCommentChange: (comment: string | null) => { + const newRootFields = { + ...rootFieldsEdit, + [field]: setTableCustomRootFieldComment(rootFieldsEdit[field], comment), + }; + dispatch(modifyRootFields(newRootFields)); + }, + }); const onChangeCustomName = (newName: string) => { dispatch(modifyTableCustomName(newName)); }; - const getRootFieldNames = ( - rootFields: CustomRootFields - ): Record => { - const rootFieldNames = Object.entries(rootFields).map(([key, value]) => - value ? { [key]: getTableCustomRootFieldName(value) } : {} + const renderCustomRootFieldLabel = ( + rootFieldName: string, + rootFieldConfig: string | CustomRootField + ) => { + const rfCustomName = getTableCustomRootFieldName(rootFieldConfig); + const rfComment = getTableCustomRootFieldComment(rootFieldConfig); + return ( +
+ + {rootFieldName} + {rfCustomName ? ( + <> + + {rfCustomName} + + ) : null} + + {rfComment} +
); - - return Object.assign({}, ...rootFieldNames); }; const collapsedLabel = () => { - const customRootFieldLabels: React.ReactNode[] = []; + const customNameLabel = existingCustomName + ? [renderCustomRootFieldLabel('custom_table_name', existingCustomName)] + : []; - if (existingCustomName) { - customRootFieldLabels.push( - - custom_table_name{' '} - {' '} - {existingCustomName} - - ); - } - - Object.entries(getRootFieldNames(existingRootFields)).forEach( - ([rootField, customRootField]) => { - customRootFieldLabels.push( - <> - - {rootField} - - {customRootField} - - - ); - } + const existingRootFieldLabels = Object.entries( + existingRootFields + ).map(([rootField, customRootField]) => + customRootField === null + ? [] + : [renderCustomRootFieldLabel(rootField, customRootField)] ); - return
{customRootFieldLabels}
; + const allLabels = customNameLabel.concat(...existingRootFieldLabels); + + return
{allLabels}
; }; const editorExpanded = () => ( @@ -99,33 +117,15 @@ const RootFieldsEditor = ({ customNameOnChange={(e: React.ChangeEvent) => { onChangeCustomName(e.target.value); }} - selectOnChange={(e: React.ChangeEvent) => { - onChange('select', e.target.value); - }} - selectByPkOnChange={(e: React.ChangeEvent) => { - onChange('select_by_pk', e.target.value); - }} - selectAggOnChange={(e: React.ChangeEvent) => { - onChange('select_aggregate', e.target.value); - }} - insertOnChange={(e: React.ChangeEvent) => { - onChange('insert', e.target.value); - }} - insertOneOnChange={(e: React.ChangeEvent) => { - onChange('insert_one', e.target.value); - }} - updateOnChange={(e: React.ChangeEvent) => { - onChange('update', e.target.value); - }} - updateByPkOnChange={(e: React.ChangeEvent) => { - onChange('update_by_pk', e.target.value); - }} - deleteOnChange={(e: React.ChangeEvent) => { - onChange('delete', e.target.value); - }} - deleteByPkOnChange={(e: React.ChangeEvent) => { - onChange('delete_by_pk', e.target.value); - }} + selectOnChange={onRootFieldChange('select')} + selectByPkOnChange={onRootFieldChange('select_by_pk')} + selectAggOnChange={onRootFieldChange('select_aggregate')} + insertOnChange={onRootFieldChange('insert')} + insertOneOnChange={onRootFieldChange('insert_one')} + updateOnChange={onRootFieldChange('update')} + updateByPkOnChange={onRootFieldChange('update_by_pk')} + deleteOnChange={onRootFieldChange('delete')} + deleteByPkOnChange={onRootFieldChange('delete_by_pk')} /> ); diff --git a/console/src/components/Services/Data/TableModify/utils.js b/console/src/components/Services/Data/TableModify/utils.js index 78baf992e5f..16c6b1b943c 100644 --- a/console/src/components/Services/Data/TableModify/utils.js +++ b/console/src/components/Services/Data/TableModify/utils.js @@ -57,7 +57,8 @@ export const sanitiseRootFields = rootFields => { rootField = rootField.trim() || null; } else if (rootField) { rootField.name = rootField.name ? rootField.name.trim() : null; - rootField.comment = rootField.comment ? rootField.comment.trim() : null; + rootField.comment = + typeof rootField.comment === 'string' ? rootField.comment.trim() : null; } santisedRootFields[rootFieldType] = rootField; }); diff --git a/console/src/dataSources/common/index.ts b/console/src/dataSources/common/index.ts index 4a088f1a2ba..9b90bce9f89 100644 --- a/console/src/dataSources/common/index.ts +++ b/console/src/dataSources/common/index.ts @@ -312,6 +312,40 @@ export const setTableCustomRootFieldName = ( return newName; }; +export const getTableCustomRootFieldComment = ( + rootFieldValue: Nullable | CustomRootField +): string | null => { + if (rootFieldValue) { + if ( + typeof rootFieldValue === 'string' || + rootFieldValue.comment === undefined + ) { + return null; + } + + return rootFieldValue.comment; + } + return null; +}; + +export const setTableCustomRootFieldComment = ( + existingRootFieldValue: Nullable | CustomRootField, + newComment: string | null +): Nullable | CustomRootField => { + if (typeof existingRootFieldValue === 'string') { + return { + name: existingRootFieldValue, + comment: newComment, + }; + } else if (typeof existingRootFieldValue === 'object') { + return { + ...existingRootFieldValue, + comment: newComment, + }; + } + return { comment: newComment }; +}; + export const getTableColumnConfig = (table: NormalizedTable) => table?.configuration?.column_config || {};