mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
console: Add select all columns option while selecting the columns in event triggers
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2688 Co-authored-by: Abhijeet Khangarot <26903230+abhi40308@users.noreply.github.com> GitOrigin-RevId: 0b98d2f388c7f8428f3c282e8c1662f8b046f4dd
This commit is contained in:
parent
3fc34aa028
commit
bc467a283a
@ -9,6 +9,7 @@
|
||||
- server: fix to allow remote schema response to contain an "extensions" field (#7143)
|
||||
- server: support database-to-database joins with BigQuery
|
||||
- console: add comments to tracked functions
|
||||
- console: add select all columns option while selecting the columns in event triggers
|
||||
- console: add request transforms for events
|
||||
- metadata SDK: add type definitions for config v3
|
||||
- cli: fix cli-console failing to add migrations if there are tabs in SQL body (#7362)
|
||||
|
@ -15,9 +15,9 @@ import {
|
||||
import {
|
||||
setMetaData,
|
||||
validateCT,
|
||||
validateCTrigger,
|
||||
validateInsert,
|
||||
ResultType,
|
||||
validateCTrigger,
|
||||
} from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
import {
|
||||
@ -55,7 +55,7 @@ const statements = {
|
||||
`,
|
||||
};
|
||||
|
||||
const createETForm = () => {
|
||||
const createETForm = (allCols: boolean) => {
|
||||
// Set trigger name and select table
|
||||
cy.getBySel('trigger-name').clear().type(getTriggerName(0, testName));
|
||||
cy.getBySel('select-source').select('default');
|
||||
@ -73,6 +73,10 @@ const createETForm = () => {
|
||||
|
||||
// advanced settings
|
||||
cy.getBySel('advanced-settings').click();
|
||||
if (!allCols) {
|
||||
cy.getBySel('choose-column').click();
|
||||
cy.getBySel('select-column').first().click();
|
||||
}
|
||||
|
||||
// retry configuration
|
||||
cy.getBySel('no-of-retries').clear().type(getNoOfRetries());
|
||||
@ -139,8 +143,10 @@ export const failCTWithoutData = () => {
|
||||
validateCT(getTriggerName(0, testName), ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const passCT = () => {
|
||||
createETForm();
|
||||
export const passCT1 = () => {
|
||||
// select choose column from the radio input
|
||||
const allCols = false;
|
||||
createETForm(allCols);
|
||||
|
||||
// Click on create
|
||||
cy.getBySel('trigger-create').click();
|
||||
@ -159,7 +165,36 @@ export const passCT = () => {
|
||||
getTriggerName(0, testName),
|
||||
getTableName(0, testName),
|
||||
'public',
|
||||
ResultType.SUCCESS
|
||||
ResultType.SUCCESS,
|
||||
allCols
|
||||
);
|
||||
};
|
||||
|
||||
export const passCT2 = () => {
|
||||
cy.getBySel('data-sidebar-add').click();
|
||||
// select all columns from the radio input
|
||||
const allCols = true;
|
||||
createETForm(allCols);
|
||||
|
||||
// Click on create
|
||||
cy.getBySel('trigger-create').click();
|
||||
cy.wait(10000);
|
||||
// Check if the trigger got created and navigated to processed events page
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}${EVENT_TRIGGER_INDEX_ROUTE}/${getTriggerName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
cy.getBySel(getTriggerName(0, testName));
|
||||
// Validate
|
||||
validateCTrigger(
|
||||
getTriggerName(0, testName),
|
||||
getTableName(0, testName),
|
||||
'public',
|
||||
ResultType.SUCCESS,
|
||||
allCols
|
||||
);
|
||||
};
|
||||
|
||||
@ -272,7 +307,7 @@ export const createEtTransform = () => {
|
||||
cy.url().should('eq', `${baseUrl}${EVENT_TRIGGER_INDEX_ROUTE}/add`);
|
||||
|
||||
// fill up the basic event trigger form
|
||||
createETForm();
|
||||
createETForm(true);
|
||||
|
||||
// open request transform section
|
||||
toggleRequestTransformSection();
|
||||
|
@ -6,12 +6,13 @@ import { setMetaData } from '../../validators/validators';
|
||||
import {
|
||||
checkCreateTriggerRoute,
|
||||
failCTWithoutData,
|
||||
passCT,
|
||||
passCT2,
|
||||
failCTDuplicateTrigger,
|
||||
insertTableRow,
|
||||
deleteCTTestTrigger,
|
||||
deleteCTTestTable,
|
||||
passPTCreateTable,
|
||||
passCT1,
|
||||
createEtTransform,
|
||||
modifyEtTransform,
|
||||
deleteEtTransform,
|
||||
@ -37,7 +38,8 @@ export const runCreateTriggerTests = () => {
|
||||
checkCreateTriggerRoute
|
||||
);
|
||||
it('Fails to create trigger without data', failCTWithoutData);
|
||||
it('Successfuly creates trigger', passCT);
|
||||
it('Successfuly creates trigger with selected columns for update', passCT1);
|
||||
it('Successfuly creates trigger with all columns for update', passCT2);
|
||||
it('Fails to create duplicate trigger', failCTDuplicateTrigger);
|
||||
it('Insert a row and invoke trigger', insertTableRow);
|
||||
it("Delete's the test trigger", deleteCTTestTrigger);
|
||||
|
@ -475,7 +475,8 @@ export const validateCTrigger = (
|
||||
triggerName: string,
|
||||
tableName: string,
|
||||
schemaName = 'public',
|
||||
result: ResultType
|
||||
result: ResultType,
|
||||
allCols?: boolean
|
||||
) => {
|
||||
const reqBody = {
|
||||
type: 'export_metadata',
|
||||
@ -505,7 +506,11 @@ export const validateCTrigger = (
|
||||
expect(response.status === 200).to.be.true;
|
||||
expect(trigger.definition.insert.columns === '*').to.be.true;
|
||||
expect(trigger.definition.delete.columns === '*').to.be.true;
|
||||
expect(trigger.definition.update.columns.length === 3).to.be.true;
|
||||
if (allCols) {
|
||||
expect(trigger.definition.update.columns === '*').to.be.true;
|
||||
} else {
|
||||
expect(trigger.definition.update.columns.length === 2).to.be.true;
|
||||
}
|
||||
expect(
|
||||
trigger.retry_conf.interval_sec === parseInt(getIntervalSeconds(), 10)
|
||||
).to.be.true;
|
||||
|
@ -361,6 +361,7 @@ const Add: React.FC<Props> = props => {
|
||||
state={state}
|
||||
databaseInfo={databaseInfo}
|
||||
dataSourcesList={dataSourcesList}
|
||||
readOnlyMode={readOnlyMode}
|
||||
handleTriggerNameChange={handleTriggerNameChange}
|
||||
handleWebhookValueChange={handleWebhookValueChange}
|
||||
handleWebhookTypeChange={handleWebhookTypeChange}
|
||||
@ -371,6 +372,7 @@ const Add: React.FC<Props> = props => {
|
||||
handleOperationsColumnsChange={handleOperationsColumnsChange}
|
||||
handleRetryConfChange={handleRetryConfChange}
|
||||
handleHeadersChange={handleHeadersChange}
|
||||
handleToggleAllColumn={setState.toggleAllColumnChecked}
|
||||
/>
|
||||
<ConfigureTransformation
|
||||
webhookUrl={webhook?.value}
|
||||
|
@ -22,6 +22,7 @@ type CreateETFormProps = {
|
||||
state: LocalEventTriggerState;
|
||||
databaseInfo: DatabaseInfo;
|
||||
dataSourcesList: DataSource[];
|
||||
readOnlyMode: boolean;
|
||||
handleTriggerNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
handleWebhookValueChange: (v: string) => void;
|
||||
handleWebhookTypeChange: (e: React.BaseSyntheticEvent) => void;
|
||||
@ -32,6 +33,7 @@ type CreateETFormProps = {
|
||||
handleOperationsColumnsChange: (oc: ETOperationColumn[]) => void;
|
||||
handleRetryConfChange: (r: RetryConf) => void;
|
||||
handleHeadersChange: (h: Header[]) => void;
|
||||
handleToggleAllColumn: () => void;
|
||||
};
|
||||
|
||||
const CreateETForm: React.FC<CreateETFormProps> = props => {
|
||||
@ -45,9 +47,11 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
|
||||
retryConf,
|
||||
operations,
|
||||
operationColumns,
|
||||
isAllColumnChecked,
|
||||
},
|
||||
databaseInfo,
|
||||
dataSourcesList,
|
||||
readOnlyMode,
|
||||
handleTriggerNameChange,
|
||||
handleDatabaseChange,
|
||||
handleSchemaChange,
|
||||
@ -58,6 +62,7 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
|
||||
handleOperationsColumnsChange,
|
||||
handleRetryConfChange,
|
||||
handleHeadersChange,
|
||||
handleToggleAllColumn,
|
||||
} = props;
|
||||
|
||||
const supportedDrivers = getSupportedDrivers('events.triggers.add');
|
||||
@ -195,6 +200,9 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
|
||||
<ColumnList
|
||||
operationColumns={operationColumns}
|
||||
table={table}
|
||||
isAllColumnChecked={isAllColumnChecked}
|
||||
readOnlyMode={readOnlyMode}
|
||||
handleToggleAllColumn={handleToggleAllColumn}
|
||||
handleOperationsColumnsChange={handleOperationsColumnsChange}
|
||||
/>
|
||||
</div>
|
||||
|
@ -2,15 +2,26 @@ import React from 'react';
|
||||
import { QualifiedTable } from '@/metadata/types';
|
||||
import styles from '../TableCommon/EventTable.scss';
|
||||
import { ETOperationColumn } from '../../types';
|
||||
import { ColumnSelectionRadioButton } from './ColumnSelectionRadioButton';
|
||||
|
||||
type ColumnListProps = {
|
||||
operationColumns: ETOperationColumn[];
|
||||
table: QualifiedTable;
|
||||
isAllColumnChecked: boolean;
|
||||
readOnlyMode: boolean;
|
||||
handleToggleAllColumn: () => void;
|
||||
handleOperationsColumnsChange: (oc: ETOperationColumn[]) => void;
|
||||
};
|
||||
|
||||
const ColumnList: React.FC<ColumnListProps> = props => {
|
||||
const { operationColumns, table, handleOperationsColumnsChange } = props;
|
||||
const {
|
||||
operationColumns,
|
||||
table,
|
||||
isAllColumnChecked,
|
||||
readOnlyMode,
|
||||
handleToggleAllColumn,
|
||||
handleOperationsColumnsChange,
|
||||
} = props;
|
||||
|
||||
if (!table.name) {
|
||||
return <i>Select a table first to get column list</i>;
|
||||
@ -28,24 +39,37 @@ const ColumnList: React.FC<ColumnListProps> = props => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{operationColumns.map(opCol => (
|
||||
<div
|
||||
key={opCol.name}
|
||||
className={`${styles.padd_remove} ${styles.columnListElement}`}
|
||||
>
|
||||
<div className="checkbox ">
|
||||
<label className={styles.cursorPointer}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={opCol.enabled}
|
||||
onChange={() => handleToggleColumn(opCol)}
|
||||
className={`${styles.cursorPointer} legacy-input-fix`}
|
||||
/>
|
||||
{opCol.name}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<ColumnSelectionRadioButton
|
||||
isAllColumnChecked={isAllColumnChecked}
|
||||
handleColumnRadioButton={handleToggleAllColumn}
|
||||
readOnly={readOnlyMode}
|
||||
/>
|
||||
<div className="mt-sm">
|
||||
{!isAllColumnChecked ? (
|
||||
<>
|
||||
<div>List of columns to select:</div>
|
||||
{operationColumns.map(opCol => (
|
||||
<div
|
||||
key={opCol.name}
|
||||
className={`${styles.padd_remove} ${styles.columnListElement}`}
|
||||
>
|
||||
<div className="checkbox">
|
||||
<label className="cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={opCol.enabled}
|
||||
onChange={() => handleToggleColumn(opCol)}
|
||||
className={`${styles.cursorPointer} legacy-input-fix`}
|
||||
data-test="select-column"
|
||||
/>
|
||||
{opCol.name}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
|
||||
interface InputProps extends React.ComponentProps<'input'> {
|
||||
isAllColumnChecked: boolean;
|
||||
handleColumnRadioButton: () => void;
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export const ColumnSelectionRadioButton: React.FC<InputProps> = ({
|
||||
isAllColumnChecked,
|
||||
handleColumnRadioButton,
|
||||
readOnly,
|
||||
}) => {
|
||||
return (
|
||||
<div className="mt-sm">
|
||||
<label className="radio-inline">
|
||||
<input
|
||||
type="radio"
|
||||
checked={isAllColumnChecked}
|
||||
onChange={handleColumnRadioButton}
|
||||
disabled={readOnly}
|
||||
readOnly
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
All columns
|
||||
</label>
|
||||
<label className="radio-inline">
|
||||
<input
|
||||
type="radio"
|
||||
checked={!isAllColumnChecked}
|
||||
onChange={handleColumnRadioButton}
|
||||
disabled={readOnly}
|
||||
readOnly
|
||||
className="cursor-pointer"
|
||||
data-test="choose-column"
|
||||
/>
|
||||
Choose columns
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -326,6 +326,8 @@ const Modify: React.FC<Props> = props => {
|
||||
setOperationColumns={setState.operationColumns}
|
||||
styles={styles}
|
||||
save={saveWrapper('ops')}
|
||||
isAllColumnChecked={state.isAllColumnChecked}
|
||||
handleColumnRadioButton={setState.toggleAllColumnChecked}
|
||||
/>
|
||||
<RetryConfEditor
|
||||
conf={state.retryConf}
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
|
||||
import Operations from '../Common/Operations';
|
||||
import { TableColumn } from '../../../../../dataSources/types';
|
||||
import { ColumnSelectionRadioButton } from '../Common/ColumnSelectionRadioButton';
|
||||
|
||||
type OperationEditorProps = {
|
||||
currentTrigger: EventTrigger;
|
||||
@ -23,6 +24,8 @@ type OperationEditorProps = {
|
||||
setOperationColumns: (operationColumns: ETOperationColumn[]) => void;
|
||||
styles: Record<string, string>;
|
||||
save: (success: VoidCallback, error: VoidCallback) => void;
|
||||
isAllColumnChecked: boolean;
|
||||
handleColumnRadioButton: () => void;
|
||||
};
|
||||
|
||||
const OperationEditor = (props: OperationEditorProps) => {
|
||||
@ -35,8 +38,9 @@ const OperationEditor = (props: OperationEditorProps) => {
|
||||
operationColumns,
|
||||
setOperations,
|
||||
setOperationColumns,
|
||||
isAllColumnChecked,
|
||||
handleColumnRadioButton,
|
||||
} = props;
|
||||
|
||||
const etDef = currentTrigger.configuration.definition;
|
||||
const existingOps = parseEventTriggerOperations(etDef);
|
||||
const existingOpColumns = getETOperationColumns(
|
||||
@ -73,7 +77,7 @@ const OperationEditor = (props: OperationEditorProps) => {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-gray-600 font-medium mb-xs">
|
||||
Trigger Tables
|
||||
Trigger Columns
|
||||
</label>
|
||||
<p className="text-sm text-gray-600 mb-sm">
|
||||
Trigger columns to list to for updates.
|
||||
@ -81,8 +85,25 @@ const OperationEditor = (props: OperationEditorProps) => {
|
||||
</div>
|
||||
<div className={styles.modifyOpsCollapsedContent}>
|
||||
<div className={`col-md-12 ${styles.padd_remove}`}>
|
||||
{ops.update ? (
|
||||
opCols.map(col => {
|
||||
<div className="checkbox ">
|
||||
{ops.update ? (
|
||||
<ColumnSelectionRadioButton
|
||||
isAllColumnChecked={isAllColumnChecked}
|
||||
handleColumnRadioButton={handleColumnRadioButton}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={`col-md-12 ${styles.padd_remove} ${styles.modifyOpsCollapsedtitle}`}
|
||||
>
|
||||
<i>Applicable only if update operation is selected.</i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!isAllColumnChecked ? (
|
||||
<div className={`col-md-12 ${styles.padd_remove}`}>
|
||||
{opCols.map(col => {
|
||||
const toggle = () => {
|
||||
if (!readOnly) {
|
||||
const newCols = opCols.map(oc => {
|
||||
@ -111,15 +132,9 @@ const OperationEditor = (props: OperationEditorProps) => {
|
||||
<small className={styles.addPaddSmall}> ({col.type})</small>
|
||||
</label>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div
|
||||
className={`col-md-12 ${styles.padd_remove} ${styles.modifyOpsCollapsedtitle}`}
|
||||
>
|
||||
<i>Applicable only if update operation is selected.</i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -27,6 +27,7 @@ export type LocalEventTriggerState = {
|
||||
retryConf: RetryConf;
|
||||
headers: Header[];
|
||||
source: string;
|
||||
isAllColumnChecked: boolean;
|
||||
};
|
||||
|
||||
const defaultState: LocalEventTriggerState = {
|
||||
@ -53,6 +54,7 @@ const defaultState: LocalEventTriggerState = {
|
||||
},
|
||||
headers: [defaultHeader],
|
||||
source: '',
|
||||
isAllColumnChecked: true,
|
||||
};
|
||||
|
||||
export const parseServerETDefinition = (
|
||||
@ -86,6 +88,7 @@ export const parseServerETDefinition = (
|
||||
webhook: parseServerWebhook(etConf.webhook, etConf.webhook_from_env),
|
||||
retryConf: etConf.retry_conf,
|
||||
headers: parseServerHeaders(eventTrigger.configuration.headers),
|
||||
isAllColumnChecked: etDef.update.columns === '*',
|
||||
};
|
||||
};
|
||||
|
||||
@ -161,6 +164,12 @@ export const useEventTrigger = (initState?: LocalEventTriggerState) => {
|
||||
bulk: (s: LocalEventTriggerState) => {
|
||||
setState(s);
|
||||
},
|
||||
toggleAllColumnChecked: () => {
|
||||
setState(s => ({
|
||||
...s,
|
||||
isAllColumnChecked: !s.isAllColumnChecked,
|
||||
}));
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -422,9 +422,11 @@ export const modifyEventTrigger = (
|
||||
insert: state.operations.insert ? { columns: '*' } : null,
|
||||
update: state.operations.update
|
||||
? {
|
||||
columns: state.operationColumns
|
||||
.filter(c => !!c.enabled)
|
||||
.map(c => c.name),
|
||||
columns: state.isAllColumnChecked
|
||||
? '*'
|
||||
: state.operationColumns
|
||||
.filter(c => !!c.enabled)
|
||||
.map(c => c.name),
|
||||
}
|
||||
: null,
|
||||
delete: state.operations.delete ? { columns: '*' } : null,
|
||||
|
@ -450,9 +450,9 @@ export const generateCreateEventTriggerQuery = (
|
||||
: null,
|
||||
update: state.operations.update
|
||||
? {
|
||||
columns: state.operationColumns
|
||||
.filter(c => !!c.enabled)
|
||||
.map(c => c.name),
|
||||
columns: state.isAllColumnChecked
|
||||
? '*'
|
||||
: state.operationColumns.filter(c => !!c.enabled).map(c => c.name),
|
||||
}
|
||||
: null,
|
||||
delete: state.operations.delete
|
||||
|
Loading…
Reference in New Issue
Block a user