这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down
44 changes: 24 additions & 20 deletions console/cypress/integration/data/raw-sql/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
};
16 changes: 16 additions & 0 deletions console/src/components/Common/Alert/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';

export type AlertType = 'warning' | 'danger' | 'success';

interface AlertProps {
type: AlertType;
text: string;
}

const Alert: React.FC<AlertProps> = ({ type, text }) => (
<div className={`hidden alert alert-${type}`} role="alert">
{text}
</div>
);

export default Alert;
2 changes: 1 addition & 1 deletion console/src/components/Common/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ButtonProps> = props => {
Expand Down
1 change: 1 addition & 0 deletions console/src/components/Services/Data/DataState.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
149 changes: 60 additions & 89 deletions console/src/components/Services/Data/RawSQL/RawSQL.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -51,20 +75,17 @@ 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);
if (sqlFromLocalStorage) {
dispatch({ type: SET_SQL, data: sqlFromLocalStorage });
}
}

return () => {
localStorage.setItem(LS_RAW_SQL_SQL, sqlRef.current);
};
}, []);

// set SQL to sqlRef
useEffect(() => {
sqlRef.current = sql;
Expand Down Expand Up @@ -127,34 +148,6 @@ const RawSQL = ({
}
};

let alert = null;

if (ongoingRequest) {
alert = (
<div className={`${styles.padd_left_remove} col-xs-12`}>
<div className="hidden alert alert-warning" role="alert">
Running...
</div>
</div>
);
} else if (lastError) {
alert = (
<div className={`${styles.padd_left_remove} col-xs-12`}>
<div className="hidden alert alert-danger" role="alert">
Error: {JSON.stringify(lastError)}
</div>
</div>
);
} else if (lastSuccess) {
alert = (
<div className={`${styles.padd_left_remove} col-xs-12`}>
<div className="hidden alert alert-success" role="alert">
Executed Query
</div>
</div>
);
}

const getMigrationWarningModal = () => {
const onModalClose = () => {
dispatch(modalClose());
Expand Down Expand Up @@ -256,6 +249,8 @@ const RawSQL = ({
},
]}
onChange={handleSQLChange}
// prevents unwanted frequent event triggers
debounceChangePeriod={200}
/>
</div>
);
Expand Down Expand Up @@ -305,25 +300,6 @@ const RawSQL = ({
return resultTable;
};

const getNotesSection = () => {
return (
<ul>
<li>
You can create views, alter tables or just about run any SQL
statements directly on the database.
</li>
<li>
Multiple SQL statements can be separated by semicolons, <code>;</code>
, however, only the result of the last SQL statement will be returned.
</li>
<li>
Multiple SQL statements will be run as a transaction. i.e. if any
statement fails, none of the statements will be applied.
</li>
</ul>
);
};

const getMetadataCascadeSection = () => {
return (
<div className={styles.add_mar_top_small}>
Expand Down Expand Up @@ -426,15 +402,7 @@ const RawSQL = ({
<div>
<label className={styles.add_mar_right}>Migration name:</label>
<input
className={
styles.inline_block +
' ' +
styles.tableNameInput +
' ' +
styles.add_mar_right_small +
' ' +
' form-control'
}
className={`${styles.inline_block} ${styles.tableNameInput} ${styles.add_mar_right_small} form-control`}
placeholder={'run_sql_migration'}
id="migration-name"
type="text"
Expand Down Expand Up @@ -473,21 +441,6 @@ const RawSQL = ({
return migrationSection;
};

const getRunButton = () => {
return (
<Button
type="submit"
className={styles.add_mar_top}
onClick={submitSQL}
color="yellow"
size="sm"
data-test="run-sql"
>
Run!
</Button>
);
};

return (
<div
className={`${styles.clear_fix} ${styles.padd_left} ${styles.padd_top}`}
Expand All @@ -500,26 +453,44 @@ const RawSQL = ({
<div className="clearfix" />
</div>
<div className={styles.add_mar_top}>
<div>
<div className={`${styles.padd_left_remove} col-xs-8`}>
{getNotesSection()}
</div>
<div className={`${styles.padd_left_remove} col-xs-8`}>
<NotesSection />
</div>

<div className={`${styles.padd_left_remove} col-xs-10`}>
{getSQLSection()}
</div>
<div className={`${styles.padd_left_remove} col-xs-10`}>
{getSQLSection()}
</div>

<div
className={`${styles.padd_left_remove} ${styles.add_mar_bottom} col-xs-8`}
<div
className={`${styles.padd_left_remove} ${styles.add_mar_bottom} col-xs-8`}
>
{getTrackThisSection()}
{getMetadataCascadeSection()}
{getMigrationSection()}
<Button
type="submit"
className={styles.add_mar_top}
onClick={submitSQL}
color="yellow"
size="sm"
data-test="run-sql"
>
{getTrackThisSection()}
{getMetadataCascadeSection()}
{getMigrationSection()}
Run!
</Button>
</div>

{getRunButton()}
<div className="hidden col-xs-4">
<div className={`${styles.padd_left_remove} col-xs-12`}>
{ongoingRequest && <Alert type="warning" text="Running..." />}
{lastError && (
<Alert
type="danger"
text={`Error: ${JSON.stringify(lastError)}`}
/>
)}
{lastSuccess && <Alert type="success" text="Executed Query" />};
</div>
</div>
<div className="hidden col-xs-4">{alert}</div>
</div>

{getMigrationWarningModal()}
Expand Down
Loading