From b2fd57a8e25f9fd2088bc0ea7fc463e93744dc92 Mon Sep 17 00:00:00 2001 From: soorajshankar Date: Fri, 29 May 2020 14:11:02 +0530 Subject: [PATCH] console: fix visiting view modify page overwriting raw sql content (close #4798) (#4810) --- CHANGELOG.md | 11 +- .../cypress/integration/data/raw-sql/spec.ts | 44 +++--- console/src/components/Common/Alert/index.tsx | 16 ++ .../src/components/Common/Button/Button.tsx | 2 +- .../src/components/Services/Data/DataState.js | 1 + .../components/Services/Data/RawSQL/RawSQL.js | 147 +++++++----------- .../Data/RawSQL/molecules/NotesSection.tsx | 22 +++ .../Services/Data/TableCommon/TableReducer.js | 6 + .../Data/TableModify/ModifyActions.js | 5 +- .../Services/Data/TableModify/ModifyView.js | 43 +---- .../Data/TableModify/ViewDefinitions.tsx | 35 +++++ .../components/PlaceHolder.tsx | 8 +- 12 files changed, 181 insertions(+), 159 deletions(-) create mode 100644 console/src/components/Common/Alert/index.tsx create mode 100644 console/src/components/Services/Data/RawSQL/molecules/NotesSection.tsx create mode 100644 console/src/components/Services/Data/TableModify/ViewDefinitions.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index cb954dc1c58..0b1babeaa4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,23 +12,20 @@ It works similar to table relationships. Head to the `Relationship` tab in your 2. select the remote schema 3. give the join configuration from table columns to remote schema fields. -[Add docs links] -[Add console screenshot] +[Add docs links][add console screenshot] ### Scheduled Triggers A scheduled trigger can be used to execute custom business logic based on time. There are two types of timing events: cron based or timestamp based. -A cron trigger will be useful when something needs to be done periodically. For example, you can create a cron trigger to generate an end-of-day sales report every weekday at 9pm. +A cron trigger will be useful when something needs to be done periodically. For example, you can create a cron trigger to generate an end-of-day sales report every weekday at 9pm. You can also schedule one-off events based on a timestamp. For example, a new scheduled event can be created for 2 weeks from when a user signs up to send them an email about their experience. -[Add docs links] -[Add console screenshot] +[Add docs links][add console screenshot] (close #1914) - ### Allow access to session variables by computed fields (fix #3846) Sometimes it is useful for computed fields to have access to the Hasura session variables directly. For example, suppose you want to fetch some articles but also get related user info, say `likedByMe`. Now, you can define a function like: @@ -63,6 +60,7 @@ Read more about the session argument for computed fields in the [docs](https://h ### Bug fixes and improvements (Add entries here in the order of: server, console, cli, docs, others) + - server: fix explain queries with role permissions (fix #4816) - server: compile with GHC 8.10.1, closing a space leak with subscriptions. (close #4517) (#3388) @@ -81,6 +79,7 @@ Read more about the session argument for computed fields in the [docs](https://h - console: fix inconsistency between selected rows state and displayed rows (fix #4654) (#4673) - console: fix displaying boolean values in `Edit Row` tab (#4682) - console: fix underscores not being displayed on raw sql page (close #4754) (#4799) +- console: fix visiting view modify page overwriting raw sql content (fix #4798) (#4810) - console: add help button and move about page to settings (#4848) - cli: list all available commands in root command help (fix #4623) (#4628) - docs: add section on actions vs. remote schemas to actions documentation (#4284) diff --git a/console/cypress/integration/data/raw-sql/spec.ts b/console/cypress/integration/data/raw-sql/spec.ts index 4d301917e40..1d39dd443e6 100644 --- a/console/cypress/integration/data/raw-sql/spec.ts +++ b/console/cypress/integration/data/raw-sql/spec.ts @@ -14,63 +14,67 @@ export const openRawSQL = () => { // Match URL cy.url().should('eq', `${baseUrl}/data/sql`); }; - +const clearText = () => { + cy.get('textarea').type('{selectall}', { force: true }); + cy.get('textarea').trigger('keydown', { + keyCode: 46, + which: 46, + force: true, + }); + cy.wait(2000); // ace editor textarea doesn't expose the value to check, so wait +}; export const passCreateTable = () => { prevStr = 'CREATE TABLE Apic_test_table_rsql (id serial PRIMARY KEY);'; cy.get('textarea').type(prevStr, { force: true }); + cy.wait(1000); // debounce cy.get(getElementFromAlias('run-sql')).click(); cy.wait(5000); }; export const passInsertValues = () => { - for (let i = 0; i < prevStr.length; i++) { - cy.get('textarea').type('{backspace}', { force: true }); - } + clearText(); prevStr = 'INSERT INTO Apic_test_table_rsql VALUES (1);'; cy.get('textarea').type(prevStr, { force: true }); + cy.wait(1000); cy.get(getElementFromAlias('run-sql')).click(); cy.wait(5000); }; export const passAlterTable = () => { - for (let i = 0; i < prevStr.length; i++) { - cy.get('textarea').type('{backspace}', { force: true }); - } + clearText(); prevStr = 'ALTER TABLE Apic_test_table_rsql ADD COLUMN name text;'; cy.get('textarea').type(prevStr, { force: true }); // Untrack table + cy.wait(1000); cy.get(getElementFromAlias('raw-sql-track-check')).uncheck(); cy.get(getElementFromAlias('run-sql')).click(); cy.wait(5000); }; export const passCreateView = () => { - for (let i = 0; i < prevStr.length; i++) { - cy.get('textarea').type('{backspace}', { force: true }); - } + clearText(); prevStr = 'CREATE VIEW abcd AS SELECT * FROM Apic_test_table_rsql;'; cy.get('textarea').type(prevStr, { force: true }); // Track table + cy.wait(1000); cy.get(getElementFromAlias('raw-sql-track-check')).check(); cy.get(getElementFromAlias('run-sql')).click(); cy.wait(5000); }; export const delTestTables = () => { - for (let i = 0; i < prevStr.length; i++) { - cy.get('textarea').type('{backspace}', { force: true }); - } + clearText(); prevStr = 'DROP TABLE Apic_test_table_rsql CASCADE;'; cy.get('textarea').type(prevStr, { force: true }); + cy.wait(1000); cy.get(getElementFromAlias('raw-sql-migration-check')).uncheck(); cy.get(getElementFromAlias('run-sql')).click(); cy.get(getElementFromAlias('not-migration-confirm')).click(); cy.wait(5000); - for (let i = 0; i < prevStr.length; i++) { - cy.get('textarea').type('{backspace}', { force: true }); - } - prevStr = 'DROP TABLE abcd;'; - cy.get('textarea').type(prevStr, { force: true }); - cy.get(getElementFromAlias('run-sql')).click(); - cy.wait(5000); + // cy.visit(`${baseUrl}/data/schema/public`); + // cy.get(getElementFromAlias('add-track-table-Apic_test_table_rsql')).click(); + // cy.get(getElementFromAlias('delete-table')).click(); + // cy.on('window:confirm', () => true); + // cy.wait(5000); + // validateCT('Apic_test_table_rsql', 'failure'); }; diff --git a/console/src/components/Common/Alert/index.tsx b/console/src/components/Common/Alert/index.tsx new file mode 100644 index 00000000000..d0681cd3a9c --- /dev/null +++ b/console/src/components/Common/Alert/index.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +export type AlertType = 'warning' | 'danger' | 'success'; + +interface AlertProps { + type: AlertType; + text: string; +} + +const Alert: React.FC = ({ type, text }) => ( +
+ {text} +
+); + +export default Alert; diff --git a/console/src/components/Common/Button/Button.tsx b/console/src/components/Common/Button/Button.tsx index f9b16a37380..59c4f14136e 100644 --- a/console/src/components/Common/Button/Button.tsx +++ b/console/src/components/Common/Button/Button.tsx @@ -12,7 +12,7 @@ import styles from '../Common.scss'; export interface ButtonProps extends React.ComponentProps<'button'> { size: string; - color: 'yellow' | 'red' | 'green' | 'gray' | 'white' | 'black'; + color?: 'yellow' | 'red' | 'green' | 'gray' | 'white' | 'black'; } const Button: React.FC = props => { diff --git a/console/src/components/Services/Data/DataState.js b/console/src/components/Services/Data/DataState.js index ff3ab238d39..d183fe52dd2 100644 --- a/console/src/components/Services/Data/DataState.js +++ b/console/src/components/Services/Data/DataState.js @@ -142,6 +142,7 @@ const defaultModifyState = { lastSuccess: null, viewDefinition: null, viewDefinitionError: null, + viewDefSql: '', tableCommentEdit: { enabled: false, editedValue: null }, alterColumnOptions: [], // Store supported implicit column -> column casts alterColumnOptionsFetchErr: null, diff --git a/console/src/components/Services/Data/RawSQL/RawSQL.js b/console/src/components/Services/Data/RawSQL/RawSQL.js index 2fc605cda39..7acb7141549 100644 --- a/console/src/components/Services/Data/RawSQL/RawSQL.js +++ b/console/src/components/Services/Data/RawSQL/RawSQL.js @@ -24,7 +24,31 @@ import { ACE_EDITOR_FONT_SIZE, } from '../../../Common/AceEditor/utils'; import { CLI_CONSOLE_MODE } from '../../../../constants'; +import NotesSection from './molecules/NotesSection'; +import Alert from '../../../Common/Alert'; +/** + * # RawSQL React FC + * ## renders raw SQL page on route `/data/sql` + * + * @typedef Props + * @property {string} sql + * @property {string} resultType + * @property {array} result + * @property {array} resultHeaders + * @property {function} dispatch + * @property {boolean} ongoingRequest + * @property {object} lastError + * @property {boolean} lastSuccess + * @property {boolean} isModalOpen + * @property {boolean} isCascadeChecked + * @property {boolean} isMigrationChecked + * @property {boolean} isTableTrackChecked + * @property {boolean} migrationMode + * @property {array} allSchemas + * + * @param {Props} + */ const RawSQL = ({ sql, resultType, @@ -51,7 +75,6 @@ const RawSQL = ({ // set up sqlRef to use in unmount const sqlRef = useRef(sql); - // set SQL from localStorage on mount and write back to localStorage on unmount useEffect(() => { if (!sql) { const sqlFromLocalStorage = localStorage.getItem(LS_RAW_SQL_SQL); @@ -59,12 +82,10 @@ const RawSQL = ({ dispatch({ type: SET_SQL, data: sqlFromLocalStorage }); } } - return () => { localStorage.setItem(LS_RAW_SQL_SQL, sqlRef.current); }; }, []); - // set SQL to sqlRef useEffect(() => { sqlRef.current = sql; @@ -127,34 +148,6 @@ const RawSQL = ({ } }; - let alert = null; - - if (ongoingRequest) { - alert = ( -
-
- Running... -
-
- ); - } else if (lastError) { - alert = ( -
-
- Error: {JSON.stringify(lastError)} -
-
- ); - } else if (lastSuccess) { - alert = ( -
-
- Executed Query -
-
- ); - } - const getMigrationWarningModal = () => { const onModalClose = () => { dispatch(modalClose()); @@ -256,6 +249,8 @@ const RawSQL = ({ }, ]} onChange={handleSQLChange} + // prevents unwanted frequent event triggers + debounceChangePeriod={200} /> ); @@ -305,25 +300,6 @@ const RawSQL = ({ return resultTable; }; - const getNotesSection = () => { - return ( -
    -
  • - You can create views, alter tables or just about run any SQL - statements directly on the database. -
  • -
  • - Multiple SQL statements can be separated by semicolons, ; - , however, only the result of the last SQL statement will be returned. -
  • -
  • - Multiple SQL statements will be run as a transaction. i.e. if any - statement fails, none of the statements will be applied. -
  • -
- ); - }; - const getMetadataCascadeSection = () => { return (
@@ -426,15 +402,7 @@ const RawSQL = ({
{ - return ( - - ); - }; - return (
-
-
- {getNotesSection()} -
+
+ +
-
- {getSQLSection()} -
+
+ {getSQLSection()} +
-
+ {getTrackThisSection()} + {getMetadataCascadeSection()} + {getMigrationSection()} + +
- {getRunButton()} +
+
+ {ongoingRequest && } + {lastError && ( + + )} + {lastSuccess && };
-
{alert}
{getMigrationWarningModal()} diff --git a/console/src/components/Services/Data/RawSQL/molecules/NotesSection.tsx b/console/src/components/Services/Data/RawSQL/molecules/NotesSection.tsx new file mode 100644 index 00000000000..f267e8bc27f --- /dev/null +++ b/console/src/components/Services/Data/RawSQL/molecules/NotesSection.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const NotesSection = () => { + return ( +
    +
  • + You can create views, alter tables or just about run any SQL statements + directly on the database. +
  • +
  • + Multiple SQL statements can be separated by semicolons, ;, + however, only the result of the last SQL statement will be returned. +
  • +
  • + Multiple SQL statements will be run as a transaction. i.e. if any + statement fails, none of the statements will be applied. +
  • +
+ ); +}; + +export default NotesSection; diff --git a/console/src/components/Services/Data/TableCommon/TableReducer.js b/console/src/components/Services/Data/TableCommon/TableReducer.js index a59e2fa7e24..6e1a3b7e00d 100644 --- a/console/src/components/Services/Data/TableCommon/TableReducer.js +++ b/console/src/components/Services/Data/TableCommon/TableReducer.js @@ -23,6 +23,7 @@ import { TOGGLE_ENUM_FAILURE, MODIFY_ROOT_FIELD, SET_CHECK_CONSTRAINTS, + SET_VIEW_DEF_SQL, } from '../TableModify/ModifyActions'; // TABLE RELATIONSHIPS @@ -119,6 +120,11 @@ const modifyReducer = (tableName, schemas, modifyStateOrig, action) => { ...modifyState, viewDefinitionError: action.data, }; + case SET_VIEW_DEF_SQL: + return { + ...modifyState, + viewDefSql: action.data, + }; case REL_ADD_NEW_CLICKED: return { diff --git a/console/src/components/Services/Data/TableModify/ModifyActions.js b/console/src/components/Services/Data/TableModify/ModifyActions.js index 8603959dbb5..ddd3429979f 100644 --- a/console/src/components/Services/Data/TableModify/ModifyActions.js +++ b/console/src/components/Services/Data/TableModify/ModifyActions.js @@ -8,7 +8,6 @@ import { LOAD_SCHEMA, } from '../DataActions'; import _push from '../push'; -import { SET_SQL } from '../RawSQL/Actions'; import { showErrorNotification, showSuccessNotification, @@ -73,6 +72,7 @@ const DELETE_PK_WARNING = const VIEW_DEF_REQUEST_SUCCESS = 'ModifyTable/VIEW_DEF_REQUEST_SUCCESS'; const VIEW_DEF_REQUEST_ERROR = 'ModifyTable/VIEW_DEF_REQUEST_ERROR'; +const SET_VIEW_DEF_SQL = 'ModifyTable/SET_VIEW_DEF_SQL'; const SAVE_NEW_TABLE_NAME = 'ModifyTable/SAVE_NEW_TABLE_NAME'; @@ -967,7 +967,7 @@ WHERE c.relname = '${viewName}' ' AS \n' + finalDef; } - dispatch({ type: SET_SQL, data: runSqlDef }); + dispatch({ type: SET_VIEW_DEF_SQL, data: runSqlDef }); }, err => { dispatch( @@ -2369,6 +2369,7 @@ export { FETCH_COLUMN_TYPE_CASTS_FAIL, VIEW_DEF_REQUEST_SUCCESS, VIEW_DEF_REQUEST_ERROR, + SET_VIEW_DEF_SQL, SET_COLUMN_EDIT, TABLE_COMMENT_EDIT, TABLE_COMMENT_INPUT_EDIT, diff --git a/console/src/components/Services/Data/TableModify/ModifyView.js b/console/src/components/Services/Data/TableModify/ModifyView.js index 8e83fb58bef..cd21f03072e 100644 --- a/console/src/components/Services/Data/TableModify/ModifyView.js +++ b/console/src/components/Services/Data/TableModify/ModifyView.js @@ -1,6 +1,5 @@ import PropTypes from 'prop-types'; import React from 'react'; -import AceEditor from 'react-ace'; import TableHeader from '../TableCommon/TableHeader'; import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor'; import { @@ -28,10 +27,11 @@ import RootFields from './RootFields'; import Tooltip from '../../../Common/Tooltip/Tooltip'; import { changeViewRootFields } from '../Common/TooltipMessages'; import styles from './ModifyTable.scss'; +import ViewDefinitions from './ViewDefinitions'; const ModifyView = props => { const { - sql, + viewDefSql, tableName, tableType, allSchemas, @@ -98,12 +98,6 @@ const ModifyView = props => { ); } - const modifyViewDefinition = viewName => { - // fetch the definition - dispatch(fetchViewDefinition(viewName, true)); - // redirect the user to run_sql page and set state - }; - const getViewColumnsSection = () => { const columns = tableSchema.columns.sort(ordinalColSort); @@ -200,21 +194,6 @@ const ModifyView = props => { ); }; - const modifyViewOnClick = () => { - modifyViewDefinition(tableName); - }; - const modifyBtn = ( - - ); - const untrackOnclick = () => { const confirmMessage = `This will remove the view "${tableName}" from the GraphQL schema`; const isOk = getConfirmation(confirmMessage); @@ -276,21 +255,8 @@ const ModifyView = props => {

Columns

{getViewColumnsSection()}
-

- View Definition: - {modifyBtn} -

- + +
{getViewRootFieldsSection()} {untrackBtn} @@ -342,7 +308,6 @@ const mapStateToProps = (state, ownProps) => { tableType, currentSchema: schemaName, allSchemas: state.tables.allSchemas, - sql: state.rawSQL.sql, migrationMode: state.main.migrationMode, readOnlyMode: state.main.readOnlyMode, serverVersion: state.main.serverVersion, diff --git a/console/src/components/Services/Data/TableModify/ViewDefinitions.tsx b/console/src/components/Services/Data/TableModify/ViewDefinitions.tsx new file mode 100644 index 00000000000..d546424a02e --- /dev/null +++ b/console/src/components/Services/Data/TableModify/ViewDefinitions.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import styles from './ModifyTable.scss'; +import TextAreaWithCopy from '../../../Common/TextAreaWithCopy/TextAreaWithCopy'; +import RawSqlButton from '../Common/Components/RawSqlButton'; + +export interface ViewDefinitionsProps { + dispatch: () => void; + sql: string | object; +} + +const ViewDefinitions: React.FC = ({ dispatch, sql }) => ( + <> +

+ View Definition: + + + Modify + + +

+ + + +); + +export default ViewDefinitions; diff --git a/console/src/components/Services/Data/TableRelationships/RemoteRelationships/components/PlaceHolder.tsx b/console/src/components/Services/Data/TableRelationships/RemoteRelationships/components/PlaceHolder.tsx index 8c2ed533d90..8fd98299085 100644 --- a/console/src/components/Services/Data/TableRelationships/RemoteRelationships/components/PlaceHolder.tsx +++ b/console/src/components/Services/Data/TableRelationships/RemoteRelationships/components/PlaceHolder.tsx @@ -21,9 +21,11 @@ export const LoadingSkeleton = () => { ); return (
- {Array(5).fill(null).map((_, i) => ( -
{skeletonItem}
- ))} + {Array(5) + .fill(null) + .map((_, i) => ( +
{skeletonItem}
+ ))}
); };