Skip to content
This repository has been archived by the owner on Jun 4, 2024. It is now read-only.

Scheduled query browser (Stage 2) #476

Merged
merged 24 commits into from
Jul 9, 2018
Merged

Scheduled query browser (Stage 2) #476

merged 24 commits into from
Jul 9, 2018

Conversation

mfix22
Copy link
Contributor

@mfix22 mfix22 commented Jul 4, 2018

Hey @n-riesco @nicolaskruchten 👋

Here is the Stage 2 PR for the Scheduled Queries projects.

Just FYI we still have more tests to write, but we are going to include those with Stage 3 to try and balance the front and backend work 👍 Hope that works.

Also FYI: we will be mostly offline tomorrow for the 4th of July, but probably will have time to make some quick fixes in the morning!

@mfix22 mfix22 closed this Jul 4, 2018
@mfix22 mfix22 reopened this Jul 4, 2018
@@ -17,6 +17,13 @@ th, td {
overflow: auto;
}

.scheduleButton {
Copy link
Contributor

@n-riesco n-riesco Jul 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this style rule should go into app/components/Settings/Preview/code-editor.css

@n-riesco
Copy link
Contributor

n-riesco commented Jul 4, 2018

Please, see some examples of Falcon's naming conventions in https://github.com/plotly/falcon-sql-client/blob/master/CODING_GUIDELINES.md .

TODO

  • rename "app/components/Settings/Scheduler/createModal.jsx" to "app/components/Settings/scheduler/create-modal.jsx"
  • rename "app/components/Settings/Scheduler/loginModal.jsx" to "app/components/Settings/scheduler/login-modal.jsx"
  • rename "app/components/Settings/Scheduler/previewModal.jsx" to "app/components/Settings/scheduler/preview-modal.jsx"
  • rename "app/components/Settings/Scheduler/scheduler.css" to "app/components/Settings/scheduler/scheduler.css"
  • rename "app/components/Settings/Scheduler/scheduler.jsx" to "app/components/Settings/scheduler/scheduler.jsx"
  • rename "app/components/Settings/Scheduler/sql.jsx" to "app/components/Settings/scheduler/sql.jsx"
  • rename "test/app/components/Settings/Scheduler/createModal.jsx" to "test/app/components/Settings/scheduler/create-modal.jsx"
  • remove "app/components/Settings/Scheduler/util.js"

@@ -0,0 +1,14 @@
import {DIALECTS} from '../../../constants/constants.js';

export function mapDialect(dialect) {
Copy link
Contributor

@n-riesco n-riesco Jul 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate code: code-editor.jsx already defines this code.

Please, add the following untested code to app/constants/constants.js:

export function getHighlightMode(dialect) {
    if (!SQL_DIALECTS_USING_EDITOR.includes(dialect)) {
        return 'text/plain';
    }

    return {
            [DIALECTS.APACHE_SPARK]: 'text/x-sparksql',
            [DIALECTS.MYSQL]: 'text/x-mysql',
            [DIALECTS.SQLITE]: 'text/x-sqlite',
            [DIALECTS.MARIADB]: 'text/x-mariadb',
            [DIALECTS.ORACLE]: 'text/x-plsql',
            [DIALECTS.POSTGRES]: 'text/x-pgsql',
            [DIALECTS.REDSHIFT]: 'text/x-pgsql',
            [DIALECTS.MSSQL]: 'text/x-mssql'
    }[dialect] || 'text/x-sql';
}

And update the following files accordingly:

  • app/components/Settings/Preview/code-editor.jsx
  • app/components/Settings/Scheduler/createModal.jsx
  • app/components/Settings/Scheduler/updateModal.jsx

@n-riesco
Copy link
Contributor

n-riesco commented Jul 4, 2018

The access token for testing expired yesterday.
I've just updated master with a new token.
Please, rebase this PR against master.

@n-riesco
Copy link
Contributor

n-riesco commented Jul 4, 2018

For this PR, please:

  • For SQL connectors (those listed in SQL_DIALECTS_USING_EDITOR), remove the button Create Scheduled Query from <Scheduler> (we don't want to duplicate the functionality in the Query panel)
  • remove Filename from <PreviewModal> (at the moment Falcon's backend can't list filenames and thus Falcon won't be able to warn the user whether a filename is already in use) (@tarzzz how does Chart Studio generate the filenames?)

TO DISCUSS:

This PR has convinced me that the Schedule panel, should be a subpanel under the Query panel:

  • we don't want the user to switch back and forth between the Query and Schedule panels
  • we don't want the Schedule panel to duplicate the functionality in the Query panel (note the query panel provides a database overview and name auto-completion)

connectionId
}
)).then((res) => {
dispatch(createScheduledQueryAction(res));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is createScheduledQueryAction necessary? It doesn't seem to be used anywhere else.

If not, let's just do:

dispatch({
    type: 'CREATE_SCHEDULED_QUERY',
    payload: res
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scratch the above comment. I'll come back to you about this.

@n-riesco
Copy link
Contributor

n-riesco commented Jul 4, 2018

When I first read the code, I found confusing and I missed the significance of having the following pairs of actions:

  • setScheduledQueries and getScheduledQueries
  • createScheduledQuery and createScheduledQueryAction
  • updateScheduledQuery and updateScheduledQueryAction
  • deleteScheduledQuery and deleteScheduledQueryAction

As the actions getScheduledQueries, createScheduledQueryAction, updateScheduledQueryAction and deleteScheduledQueryAction are only used internally, I'd rather have them removed and avoid the confusion and the time spent to learn their significance.

I've tested the change below and it works for me:

diff --git a/app/actions/sessions.js b/app/actions/sessions.js
index 1c9738d..754da7d 100644
--- a/app/actions/sessions.js
+++ b/app/actions/sessions.js
@@ -9,10 +9,6 @@ export const mergeTabMap = createAction('MERGE_TAB_MAP');
 export const setTab = createAction('SET_TAB');
 export const setTable = createAction('SET_TABLE');
 export const setIndex = createAction('SET_INDEX');
-export const setScheduledQueries = createAction('SET_SCHEDULED_QUERIES');
-export const createScheduledQueryAction = createAction('CREATE_SCHEDULED_QUERY');
-export const updateScheduledQueryAction = createAction('UPDATE_SCHEDULED_QUERY');
-export const deleteScheduledQueryAction = createAction('DELETE_SCHEDULED_QUERY');
 export const mergeConnections = createAction('MERGE_CONNECTIONS');
 export const updateConnection = createAction('UPDATE_CREDENTIAL');
 export const deleteConnection = createAction('DELETE_CREDENTIAL');
@@ -148,7 +144,10 @@ export function getScheduledQueries() {
             'GET',
             'scheduledQueriesRequest'
         )).then((json => {
-            dispatch(setScheduledQueries(json));
+            dispatch({
+                type: 'SET_SCHEDULED_QUERIES',
+                payload: json
+            });
             return json;
         }));
     };
@@ -171,8 +170,11 @@ export function createScheduledQuery(connectionId, payload = {}) {
         connectionId
       }
     )).then((res) => {
-      dispatch(createScheduledQueryAction(res));
-      return res;
+        dispatch({
+            type: 'CREATE_SCHEDULED_QUERY',
+            payload: res
+        });
+        return res;
     });
   };
 }
@@ -196,8 +198,11 @@ export function updateScheduledQuery(connectionId, payload = {}) {
       payload.filename,
       body
     )).then((res) => {
-      dispatch(updateScheduledQueryAction(body));
-      return res;
+        dispatch({
+            type: 'UPDATE_SCHEDULED_QUERY',
+            payload: body
+        });
+        return res;
     });
   };
 }
@@ -209,8 +214,11 @@ export function deleteScheduledQuery(fid) {
       'DELETE',
       'createScheduledQueryRequest'
     )).then((res) => {
-      dispatch(deleteScheduledQueryAction(fid));
-      return res;
+        dispatch({
+            type: 'DELETE_SCHEDULED_QUERY',
+            payload: fid
+        });
+        return res;
     });
   };
 }

@@ -347,6 +348,7 @@ class Preview extends Component {

dialect={dialect}
runQuery={this.runQuery}
scheduleQuery={this.props.openScheduler}
Copy link
Contributor

@n-riesco n-riesco Jul 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to rename this property to openScheduler? (this seems to be the name used elsewhere)

className="btn btn-outline"
>
Upload Dataset (auto-updating)
</button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this button as it doesn't do anything in this PR.

@@ -25,6 +25,7 @@ export default class CodeEditor extends React.Component {

dialect: PropTypes.string,
runQuery: PropTypes.func,
scheduleQuery: PropTypes.func,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};
const boxStyle = { boxSizing: 'border-box', width: '50%' };

export class PreviewModal extends Component {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add description; e.g.: "implements a modal window to view details of a scheduled query"

onChange: PropTypes.func
};

class CreateModal extends Component {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add description; e.g.: "implements a modal window to schedule a new query"

{ label: 'Run weekly', value: 7 * 24 * 60 * 60 }
];

const styles = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocking change, but these styles should go into its own file, e.g. create-modal.css; (having so many <xxxx style={styles.xxxx}> is a maintenance burden).

import { Row, Column } from '../../layout.jsx';
import Modal from '../../modal.jsx';

const styles = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocking change, but these styles should go into its own file, e.g. login-modal.css; (having so many <xxxx style={styles.xxxx}> is a maintenance burden).

@tarzzz
Copy link
Contributor

tarzzz commented Jul 4, 2018

(@tarzzz how does Chart Studio generate the filenames?)

@n-riesco Its automatically filled in if the user hasn't supplied any filename (during creation/updates). The default naming is "Grid 121" (or "Filetype unique_number").

@n-riesco
Copy link
Contributor

n-riesco commented Jul 4, 2018

@tarzzz At the moment Falcon uses https://github.com/plotly/falcon-sql-client/blob/6797d9e9c1411a23d1cffc25ccfdd63a7f4e0431/backend/persistent/plotly-api.js#L97-L120 to create a new grid. How should we do so that this function doesn't require a filename?

@tarzzz
Copy link
Contributor

tarzzz commented Jul 4, 2018

How should we do so that this function doesn't require a filename?

The best way to handle this is to hit the lookup endpoint with the exists parameter to check if the filename exists on server(its an optimized endpoint and is reliably fast): https://api.plot.ly/v2/files#lookup

this.close = this.close.bind(this);
}

componentDidUpdate(prevProps) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use componentWillReceiveProps instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want to use componentWillReceiveProps? That API is deprecated in react@^16.3.0. No worries either way, just wanted to make sure you knew 😄

});
this.setState({ editing: false, confirmedDelete: false });
} else {
this.toggleEditing();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit confusing to read because I have to remember what the current value of this.state.editing is.

Since toggleEditting() is only used here, please remove toggleEditing() and use this.setState({editing: true}); instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely (:

}
}

onDelete() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neat!

}
}

close() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, the modal window closes as soon as the user clicks a button. With this behaviour, users can't tell whether the request is still being processed or it has failed.

Ideally, we want to tell the user whether the request failed and if it did fail we don't want to close the modal window.

>
{editing ? (
<div>
<CodeMirror
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an issue with size of the cursor. See below:

peek 2018-07-04 15-51


expect(component.find(Modal).get(2).props.open).toBe(true);

const modalSqlElements = component
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs updating (test failure here). see https://circleci.com/gh/plotly/falcon-sql-client/5113

@n-riesco
Copy link
Contributor

n-riesco commented Jul 4, 2018

I still need to review the changes to app/components/Settings/Settings.react.js

I'll do it tomorrow.

I noticed there is a race that allows users to open the schedule panel first and then immediately after switch to the query panel before it's ready.

updateScheduledQuery={updateScheduledQuery}
deleteScheduledQuery={deleteScheduledQuery}
initialCode={this.state.scheduledQuery}
openLogin={this.updateSelectedPanel.bind(this, 3)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we define this.openLogin (as was done with `this.openScheduler)?

}

openScheduler() {
this.setState({ scheduledQuery: this.props.preview.code }, () => {
Copy link
Contributor

@n-riesco n-riesco Jul 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how this code works. Why do we need to set scheduleQuery before opening the panel and unset it immediately after?

Also note that the value of this.props.preview.code isn't properly set before the connection has successfully connected to the database and retrieve the list of available tables.

When I reviewed the previous PR, I thought it was OK for the schedule panel to open before the Query panel is ready (because Falcon runs the scheduled queries even if the user isn't connected).

Now, I think it's easier if we stop the Scheduler panel from being open before the connection is ready.

How about having <Tab disabled={queryPanelDisabled}>Schedule</Tab> here?

let CreateModal;
beforeAll(() => {
configure({ adapter: new Adapter() });
global.document.createRange = function() {
Copy link
Contributor

@n-riesco n-riesco Jul 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. I moved it to the global context, but if I'm not mistaken, you all don't already have polyfills for document.createRange so I'll keep the one I wrote 👍

@briandennis
Copy link
Contributor

For SQL connectors (those listed in SQL_DIALECTS_USING_EDITOR), remove the button Create Scheduled Query from (we don't want to duplicate the functionality in the Query panel)

@n-riesco for connection dialects that don't use the query panel editor, is it still meaningful to display a query input within the create scheduled query modal?

How are scheduled queries currently handled for dialects which are not contained in SQL_DIALECTS_USING_EDITOR?

@nicolaskruchten
Copy link
Contributor

Re the filename I would love it if the user could specify a filename and if the API doesn't like it that we bubble that error through. If the API currently doesn't return an informative message, let's change that on the API side to enable this feature /cc @tarzzz FYI

@nicolaskruchten
Copy link
Contributor

I also think we should ask for and store a Query Name, independent of the filename

@tarzzz
Copy link
Contributor

tarzzz commented Jul 5, 2018

If the API currently doesn't return an informative message, let's change that on the API side to enable this feature

Yep, It's already covered in API .. ⬇️

Sorry, a file already exists with this name.

@mfix22
Copy link
Contributor Author

mfix22 commented Jul 5, 2018

Re the filename I would love it if the user could specify a filename and if the API doesn't like it that we bubble that error through. If the API currently doesn't return an informative message, let's change that on the API side to enable this feature /cc @tarzzz FYI

@nicolaskruchten I currently have this implemented, but maybe since we are adding "Query Name"/"Query Title" we can just uncomment it and enable this with Stage 3?

@n-riesco
Copy link
Contributor

n-riesco commented Jul 5, 2018

@briandennis

for connection dialects that don't use the query panel editor, is it still meaningful to display a query input within the create scheduled query modal?

yes, it is possible to have scheduled queries for those connectors too.
if you want to test it, you can try the apache drill connector:

  • falcon provides credentials to a test server (see screen capture below)
  • and the test query:
    SELECT * FROM s3.root.`sample-data.parquet` LIMIT 10

peek 2018-07-06 00-47

How are scheduled queries currently handled for dialects which are not contained in SQL_DIALECTS_USING_EDITOR?

Same as for other connectors (it already works with your PR)

<a
className="btn btn-primary runButton"
onClick={runQuery}
disabled={!isLoading}
disabled={isLoading}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch!

error: 'Please enter the query to be scheduled above.'
});
}
// if (!this.state.filename || !this.state.filename.length) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, ignore this comment for now. The second condition isn't necessary, if (!this.state.code) is enough, because an empty string is converted to false.

onClickAway={this.closePreview}
query={this.state.selectedQuery}
onSave={this.handleUpdate}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just noticed while testing. We need to disable onSave and onDelete when !loggedIn.

}
};
};
const CreateModal = require('../../../../../app/components/Settings/scheduler/create-modal.jsx').default;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not terribly familiar with jest. Don't we need to use jest.unmock?

All the other tests use it; e.g. https://github.com/plotly/falcon-sql-client/blob/824b3920738f0f51d631aee2421aa9f1ae23135b/test/app/components/Settings/Preview/chart-editor.test.jsx#L10

Also, is there a reason for using require instead of import?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to use jest.unmock in this test since we aren't mocking anything using jest.mock. If anything I should just delete global.document.createRange in the afterAll hook.

Also the reason for require instead of import is because require is run sequentially after global.document.createRange = function() { and import is hoisted to the top. Typically this doesn't matter either but CodeMirror has side-effects at the top level which is causing failures on import.

@n-riesco
Copy link
Contributor

n-riesco commented Jul 6, 2018

@mfix22 @briandennis the PR looks very good and ready to merge (after addressing the few minor comments I posted today).

I'm aiming at merging tonight when I'm back.

@n-riesco
Copy link
Contributor

n-riesco commented Jul 6, 2018

@mfix22 I've tested 508ca66 and it doesn't look good:

peek 2018-07-06 22-12

How about hiding the buttons instead?


Please, don't forget about the comment here.

@mfix22
Copy link
Contributor Author

mfix22 commented Jul 6, 2018

@n-riesco yes for sure! I was planning on doing it and then forgot. Just pushed the update. Thanks for catching that! 👍

@n-riesco
Copy link
Contributor

n-riesco commented Jul 9, 2018

I've tested the latest changes and it looks good to me.

There is an issue that I can't reproduce reliably (sometimes I need to click a schedule twice to be able to open the PreviewModal).

I'm merging so that we can progress to stage-3.

@n-riesco n-riesco merged commit e31cc51 into plotly:master Jul 9, 2018
@mfix22 mfix22 deleted the stage-2 branch July 10, 2018 15:53
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants