diff --git a/docs/installation.rst b/docs/installation.rst index c7c24fc1d4e67..82459c7c53eea 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -858,13 +858,19 @@ To allow scheduled queries, add the following to your `config.py`: }, 'start_date': { 'type': 'string', - 'format': 'date-time', 'title': 'Start date', + # date-time is parsed using the chrono library, see + # https://www.npmjs.com/package/chrono-node#usage + 'format': 'date-time', + 'default': 'tomorrow at 9am', }, 'end_date': { 'type': 'string', - 'format': 'date-time', 'title': 'End date', + # date-time is parsed using the chrono library, see + # https://www.npmjs.com/package/chrono-node#usage + 'format': 'date-time', + 'default': '9am in 30 days', }, 'schedule_interval': { 'type': 'string', @@ -890,6 +896,16 @@ To allow scheduled queries, add the following to your `config.py`: ), }, }, + 'VALIDATION': [ + # ensure that start_date <= end_date + { + 'name': 'less_equal', + 'arguments': ['start_date', 'end_date'], + 'message': 'End date cannot be before start date', + # this is where the error message is shown + 'container': 'end_date', + }, + ], }, } diff --git a/superset/assets/package-lock.json b/superset/assets/package-lock.json index e5075d9699665..da886a0c95d17 100644 --- a/superset/assets/package-lock.json +++ b/superset/assets/package-lock.json @@ -5662,6 +5662,21 @@ "tslib": "^1.9.0" } }, + "chrono-node": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-1.3.11.tgz", + "integrity": "sha512-jDWRnY6nYvzfV3HPYBqo+tot7tcsUs9i3arGbMdI0TouPSXP2C2y/Ctp27rxKTQDi6yuTxAB2cw+Q6igGhOhdQ==", + "requires": { + "moment": "2.21.0" + }, + "dependencies": { + "moment": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", + "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" + } + } + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", diff --git a/superset/assets/package.json b/superset/assets/package.json index 6edb971331dc9..52e82b12e01a1 100644 --- a/superset/assets/package.json +++ b/superset/assets/package.json @@ -84,6 +84,7 @@ "bootstrap": "^3.3.6", "bootstrap-slider": "^10.0.0", "brace": "^0.11.1", + "chrono-node": "^1.3.11", "classnames": "^2.2.5", "d3-array": "^1.2.4", "d3-color": "^1.2.0", diff --git a/superset/assets/src/SqlLab/components/ScheduleQueryButton.jsx b/superset/assets/src/SqlLab/components/ScheduleQueryButton.jsx index 2e7e16e3167af..99ecaa5256052 100644 --- a/superset/assets/src/SqlLab/components/ScheduleQueryButton.jsx +++ b/superset/assets/src/SqlLab/components/ScheduleQueryButton.jsx @@ -19,11 +19,56 @@ import React from 'react'; import PropTypes from 'prop-types'; import Form from 'react-jsonschema-form'; +import chrono from 'chrono-node'; import { t } from '@superset-ui/translation'; import Button from '../../components/Button'; import ModalTrigger from '../../components/ModalTrigger'; +const validators = { + greater: (a, b) => a > b, + greater_equal: (a, b) => a >= b, + less: (a, b) => a < b, + less_equal: (a, b) => a <= b, +}; + +function getJSONSchema() { + const jsonSchema = window.featureFlags.SCHEDULED_QUERIES.JSONSCHEMA; + // parse date-time into usable value (eg, 'today' => `new Date()`) + Object.entries(jsonSchema.properties).forEach(([key, properties]) => { + if (properties.default && properties.format === 'date-time') { + jsonSchema.properties[key] = { + ...properties, + default: chrono.parseDate(properties.default).toISOString(), + }; + } + }); + return jsonSchema; +} + +function getUISchema() { + return window.featureFlags.SCHEDULED_QUERIES.UISCHEMA; +} + +function getValidationRules() { + return window.featureFlags.SCHEDULED_QUERIES.VALIDATION || []; +} + +function getValidator() { + const rules = getValidationRules(); + return (formData, errors) => { + rules.forEach((rule) => { + const test = validators[rule.name]; + const args = rule.arguments.map(name => formData[name]); + const container = rule.container || rule.arguments.slice(-1)[0]; + if (!test(...args)) { + errors[container].addError(rule.message); + } + }); + return errors; + }; +} + const propTypes = { defaultLabel: PropTypes.string, sql: PropTypes.string.isRequired, @@ -79,9 +124,10 @@ class ScheduleQueryButton extends React.PureComponent { renderModalBody() { return (
); }