Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
0e3cada
feat(Scheduler): Stage 3
mfix22 Jul 10, 2018
b846773
Address review comments:
mfix22 Jul 11, 2018
39672a3
Add cron-picker style improvements
mfix22 Jul 11, 2018
d46eb53
Address cron mode estimate comments
mfix22 Jul 11, 2018
14d11cb
Add pretty cron formatter for cron intervals
mfix22 Jul 11, 2018
ba7e1fd
Only allow updates from current user
mfix22 Jul 11, 2018
842ec07
Default cron to '* * * * *'
mfix22 Jul 11, 2018
84c606f
prettycron -> cronstrue
mfix22 Jul 12, 2018
e281891
Add create-modal unit tests
mfix22 Jul 12, 2018
a5ff72d
Add cronstrue to Preview
mfix22 Jul 12, 2018
ec42323
Add test for preview/create, fix bugs accordingly
mfix22 Jul 12, 2018
d8d7bd5
Scheduler tests
mfix22 Jul 12, 2018
087562e
Move cronstrue to devDeps
mfix22 Jul 12, 2018
edd4b94
Improve test comments
mfix22 Jul 13, 2018
53b67ec
Update openLogin callback
mfix22 Jul 13, 2018
8182b47
add cron scheduling support to backend
briandennis Jul 11, 2018
fe7685b
update scheduler tests
briandennis Jul 13, 2018
7da6474
Update route tests
mfix22 Jul 13, 2018
06f87a9
updated scheduler call count tests to use fake timers
briandennis Jul 15, 2018
e9602d7
update cron mapping to support dynamic estimates and 5 minute offset
briandennis Jul 16, 2018
8ead9f3
fix linting
briandennis Jul 16, 2018
90ac22f
add back old tests with xit, add tests for new functionality
briandennis Jul 17, 2018
63207c2
Set error to false while loading
mfix22 Jul 17, 2018
765d2f9
Show success message on save and fix 'Loading' message
mfix22 Jul 17, 2018
0d5cd47
Removed implied help link
mfix22 Jul 17, 2018
58f7741
Saving instead of Loading for edit
mfix22 Jul 17, 2018
936d4be
Only delete if canEdit
mfix22 Jul 17, 2018
82327af
Update syntax error copy
mfix22 Jul 17, 2018
c1ec12f
update day field on weekly cron to be multi select
briandennis Jul 17, 2018
2c12804
Better error messages
mfix22 Jul 17, 2018
91ef95b
Add timed message and formatted errors
mfix22 Jul 17, 2018
798017a
Run prettier
mfix22 Jul 18, 2018
026751d
Move `node-schedule` to devDeps
mfix22 Jul 18, 2018
791aa0b
Update comments for getInitialCronMode()
mfix22 Jul 18, 2018
8f8a1f2
Don't update query on error (bug-fix)
mfix22 Jul 18, 2018
f1564e1
map cron intervals more intelligently, fix unit tests for query sched…
briandennis Jul 18, 2018
933c331
update error message in invalid syntax test
briandennis Jul 18, 2018
219fcbd
Fix line height text wrapping issue
jakedex Jul 18, 2018
a569ab5
fix state syncing issues on create/update scheduled query
briandennis Jul 18, 2018
48aaec2
Fix data grid horizontal overflow
jakedex Jul 18, 2018
ab85fe6
Remove react-grid-cell cell border
jakedex Jul 18, 2018
0e3551b
Add save action warnings
jakedex Jul 18, 2018
447bd43
handle query plurality correctly in scheduled query count
briandennis Jul 19, 2018
824d243
Update save button text and save help message
mfix22 Jul 19, 2018
c2c2b49
Messaging: 'Interval' -> 'Schedule'
mfix22 Jul 19, 2018
9a86a9c
Keep Create and Preview modals open
mfix22 Jul 19, 2018
25d2211
freeze sequelize version
briandennis Jul 22, 2018
477bd70
freeze chrome driver
briandennis Jul 23, 2018
0cc33b0
add name prop to backend
briandennis Jul 19, 2018
cc94469
Implement Query name input support
mfix22 Jul 19, 2018
b07b373
query.queryName -> query.name
mfix22 Jul 20, 2018
b377031
query name input max length = 150
mfix22 Jul 20, 2018
27d02bc
clean up table + modal responsiveness
mfix22 Jul 21, 2018
e20d276
only store query name if set
briandennis Jul 23, 2018
2ca3b75
(revert) payload.fid -> payload.filename
mfix22 Jul 23, 2018
122dd60
search for query 'name's too
mfix22 Jul 23, 2018
39f4323
clean up SQL title styles
mfix22 Jul 23, 2018
d21b156
initialize cron-picker modes based on current cronInterval
briandennis Jul 23, 2018
6a574e1
use cronstrue to validate expression passed to mapCronToHourFormat
briandennis Jul 23, 2018
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
10 changes: 8 additions & 2 deletions app/actions/sessions.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,10 @@ export function createScheduledQuery(connectionId, payload = {}) {
uids: payload.uids,
fid: payload.fid,
filename: payload.filename,
name: payload.name,
refreshInterval: payload.refreshInterval,
query: payload.query,
cronInterval: payload.cronInterval,
connectionId
}
)).then((res) => {
Expand All @@ -186,8 +188,10 @@ export function updateScheduledQuery(connectionId, payload = {}) {
uids: payload.uids,
fid: payload.fid,
filename: payload.filename,
name: payload.name,
refreshInterval: payload.refreshInterval,
query: payload.query,
cronInterval: payload.cronInterval,
connectionId
};

Expand All @@ -198,10 +202,12 @@ export function updateScheduledQuery(connectionId, payload = {}) {
payload.filename,
body
)).then((res) => {
dispatch({
if (!res.error) {
dispatch({
type: 'UPDATE_SCHEDULED_QUERY',
payload: body
});
});
}
return res;
});
};
Expand Down
7 changes: 7 additions & 0 deletions app/components/Settings/Preview/sql-table.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
border-color: #c8d4e3 !important;
}

/* fix horizontal overflow issues */
.react-grid-Cell {
box-sizing: border-box;
border-right: none !important;
}


.react-grid-Header {
box-shadow: unset !important;
max-height: 64px;
Expand Down
12 changes: 8 additions & 4 deletions app/components/Settings/Settings.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Settings extends Component {
this.renderSettingsForm = this.renderSettingsForm.bind(this);
this.updateSelectedPanel = this.updateSelectedPanel.bind(this);
this.openScheduler = this.openScheduler.bind(this);
this.openLogin = this.updateSelectedPanel.bind(this, 3);
this.openLogin = () => window.location.assign('/login');
this.state = {
editMode: true,
selectedPanel: {},
Expand Down Expand Up @@ -272,9 +272,12 @@ class Settings extends Component {
openScheduler() {
this.setState({ scheduledQuery: this.props.preview.code }, () => {
this.updateSelectedPanel(2, () => {
// Setting scheduledQuery to `null` right after opening the Schedule panel ensures that,
// when the user switches to another connector,
// `initialCode` isn't set to the `scheduledQuery` used in the previous connector.
/*
* Setting scheduledQuery to `null` right after opening the Schedule panel
* ensures that when the user switches to another connector, or back
* and forth between tabs, `initialCode` isn't set to the
* `scheduledQuery` causing the create modal to be open..
*/
this.setState({ scheduledQuery: null });
});
});
Expand Down Expand Up @@ -423,6 +426,7 @@ class Settings extends Component {
openLogin={this.openLogin}
requestor={username}
dialect={dialect}
openQueryPage={() => this.updateSelectedPanel(1)}
/>
</TabPanel>

Expand Down
94 changes: 94 additions & 0 deletions app/components/Settings/cron-picker/cron-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import cronstrue from 'cronstrue';

export function mapHourToCronFormat(hour, amPm) {
if (hour === 12) {
if (amPm === 'AM') {
return 0;
}
} else if (amPm === 'PM') {
return hour + 12;
}

return hour;
}

export function mapCronToHourFormat(cronExpression) {
try {
cronstrue.toString(cronExpression);
} catch (_) {
return null;
}

const cronParts = cronExpression.split(' ');
if (cronParts.length === 6) {
// disregard seconds component
cronParts.shift();
}
const [cronMinute, cronHour, cronDayOfMonth, cronMonth, cronDaysOfWeek] = cronParts;

const time = {};
time.minute = cronMinute.includes('*') ? 0 : Number(cronMinute);
time.date = cronDayOfMonth.includes('*') ? 1 : Number(cronDayOfMonth);
time.month = cronMonth.includes('*') ? 1 : Number(cronMonth);
time.days = cronDaysOfWeek.includes('*') ? ['MON'] : cronDaysOfWeek.split(',');

if (cronHour.includes('*') && Number(cronHour) > 12) {
time.hour = Number(cronHour) - 12;
time.amPm = 'PM';
} else {
time.hour = cronHour.includes('*') || Number(cronHour) === 0 ? 12 : Number(cronHour);
time.amPm = 'AM';
}

return time;
}

// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Maximum_delay_value
const MAXIMUM_REFRESH_INTERVAL = 2147483647 / 1000;
export function getInitialCronMode(query) {
const {cronInterval, refreshInterval} = query;

if (cronInterval) {
if (cronInterval === '* * * * *') {
return 'MINUTE';
} else if (cronInterval === '*/5 * * * *') {
return 'FREQUENTLY';
} else if (cronInterval.match(/\S+? \* \* \* \*/)) {
return 'HOURLY';
} else if (cronInterval.match(/\S+? \S+? \* \* \*/)) {
return 'DAILY';
} else if (cronInterval.match(/\S+? \S+? \* \* \S+?/)) {
return 'WEEKLY';
} else if (cronInterval.match(/\S+? \S+? \S+? \* \*/)) {
return 'MONTHLY';
}
} else if (refreshInterval) {
// case: refreshInterval is closer to 1 minute than to 5 minutes
if (refreshInterval <= (60 * (1 + 5)) / 2) {
return 'MINUTE';
}
// case: refreshInterval is closer to 5 minutes than to 1 hour
else if (refreshInterval <= (60 * (5 + 60)) / 2) {
return 'FREQUENTLY';
}
// case: refreshInterval is closer to 1 hour than to 1 day
else if (refreshInterval <= (60 * 60 * (1 + 24)) / 2) {
return 'HOURLY';
}
// case: refreshInterval is closer to 1 day than to 1 week
else if (refreshInterval <= (24 * 60 * 60 * (1 + 7)) / 2) {
return 'DAILY';
}
// case: refreshInterval is closer to 1 week than to 1 month
else if (refreshInterval <= (24 * 60 * 60 * (7 + 30)) / 2) {
return 'WEEKLY';
}
// Values larger than MAXIMUM_REFRESH_INTERVAL are invalid, and setTimeout treats them as 0
// We don't want to run the queries so often, so FREQUENTLY is safer default.
else if (refreshInterval < MAXIMUM_REFRESH_INTERVAL) {
return 'MONTHLY';
}
} else {
return 'FREQUENTLY';
}
}
19 changes: 19 additions & 0 deletions app/components/Settings/cron-picker/cron-picker.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.time-picker .Select-control {
/* all other time related dropdowns */
width: 56px;
}

.cron-row {
vertical-align: center;
align-items: center;
margin-bottom: 16px;
}

.cron-details {
align-items: center;
min-height: 56px;
background: #E8E8E8;
border-left: 4px solid #828282;
padding: 8px 16px;
margin-bottom: 16px;
}
92 changes: 92 additions & 0 deletions app/components/Settings/cron-picker/cron-picker.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import {find, propEq} from 'ramda';

import MODES from './modes';
import {mapCronToHourFormat} from './cron-helpers.js';

import './cron-picker.css';

import {Row} from '../../layout.jsx';

// react-select requires options to have `label` and `value` keys
const MODE_OPTIONS = MODES.map(currMode => ({
...currMode,
label: currMode.name,
value: currMode.id
}));

const rowStyle = {
justifyContent: 'flex-start',
verticalAlign: 'center',
alignItems: 'center',
marginBottom: '16px'
};

export default class CronPicker extends React.Component {
static defaultProps = {
initialModeId: 'FREQUENTLY'
};

static propTypes = {
initialCronExpression: PropTypes.string,
initialModeId: PropTypes.string,
onChange: PropTypes.func.isRequired
};

constructor(props) {
super(props);

this.state = {
currentModeId: props.initialModeId
};

// if possible, immediately emit default expression
const initialMode = this.getModeById(props.initialModeId);
if (initialMode.staticCronExpression) {
props.onChange(initialMode.staticCronExpression);
}

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

getModeById(id) {
return find(propEq('id', id), MODES);
}

onModeChange(selectedOption) {
const newMode = this.getModeById(selectedOption.value);
this.setState({currentModeId: newMode.id});

if (newMode.staticCronExpression) {
// new expression is fixed, so emit it immediately
this.props.onChange(newMode.staticCronExpression);
}
}

render() {
const ModeComponent = this.getModeById(this.state.currentModeId).component;
const initialTime = mapCronToHourFormat(this.props.initialCronExpression);
return (
<React.Fragment>
<Row style={rowStyle}>
<div style={{width: '50%', minWidth: '232px'}}>
<Select
className="mode-picker"
searchable={false}
clearable={false}
value={this.state.currentModeId}
onChange={this.onModeChange}
options={MODE_OPTIONS}
/>
</div>
</Row>
{ModeComponent ? (
// eslint-disable-next-line
<ModeComponent initialTime={initialTime || undefined} onChange={this.props.onChange} />
) : null}
</React.Fragment>
);
}
}
28 changes: 28 additions & 0 deletions app/components/Settings/cron-picker/details.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';

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

const style = {justifyContent: 'flex-start'};

const DetailsRow = props => (
<Row style={style} className="cron-details">
{props.children}
</Row>
);

DetailsRow.propTypes = {
children: PropTypes.node
};

const DetailsColumn = props => (
<Column style={style} className="cron-details">
{props.children}
</Column>
);

DetailsColumn.propTypes = {
children: PropTypes.node
};

export default {DetailsRow, DetailsColumn};
Loading