Skip to content

Commit

Permalink
Introduce inheritance to the Parameter structure (#4049)
Browse files Browse the repository at this point in the history
* Start draft for new Parameter structure

* Add the rest of the methods

* EnumParameter

* QueryBasedDropdownParameter

* DateParameter

* DateRangeParameter

* Update Parameter usage on code

* Merge dynamicValue into normalizedValue

* Add updateLocals and omit unwanted props

* Allow null NumberParameter and omit parentQueryId

* Rename parameter getValue to getExecutionValue

* Update $$value to normalizedValue + omit on save

* Add a few comments

* Remove ngModel property from Parameter

* Use value directly in DateRangeParameter

* Use simpler separator for DateRange url param

* Add backward compatibility

* Use normalizeValue null value for isEmpty

* Start creating jest tests

* Add more tests

* Normalize null value for multi mode in Enum

* Use saved value for param isEmpty
  • Loading branch information
gabrieldutra authored and arikfr committed Oct 24, 2019
1 parent 246eca1 commit 9f78446
Show file tree
Hide file tree
Showing 23 changed files with 1,023 additions and 442 deletions.
6 changes: 3 additions & 3 deletions client/app/components/ParameterMappingInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Form from 'antd/lib/form';
import Tooltip from 'antd/lib/tooltip';
import ParameterValueInput from '@/components/ParameterValueInput';
import { ParameterMappingType } from '@/services/widget';
import { Parameter } from '@/services/query';
import { Parameter } from '@/services/parameters';
import HelpTrigger from '@/components/HelpTrigger';

import './ParameterMappingInput.less';
Expand Down Expand Up @@ -545,11 +545,11 @@ export class ParameterMappingListInput extends React.Component {
param = param.clone().setValue(mapping.value);
}

let value = Parameter.getValue(param);
let value = Parameter.getExecutionValue(param);

// in case of dynamic value display the name instead of value
if (param.hasDynamicValue) {
value = param.dynamicValue.name;
value = param.normalizedValue.name;
}

return this.getStringValue(value);
Expand Down
8 changes: 5 additions & 3 deletions client/app/components/ParameterValueInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Input from 'antd/lib/input';
import InputNumber from 'antd/lib/input-number';
import DateParameter from '@/components/dynamic-parameters/DateParameter';
import DateRangeParameter from '@/components/dynamic-parameters/DateRangeParameter';
import { toString } from 'lodash';
import { isEqual } from 'lodash';
import { QueryBasedParameterInput } from './QueryBasedParameterInput';

import './ParameterValueInput.less';
Expand Down Expand Up @@ -59,7 +59,7 @@ class ParameterValueInput extends React.Component {
}

onSelect = (value) => {
const isDirty = toString(value) !== toString(this.props.value);
const isDirty = !isEqual(value, this.props.value);
this.setState({ value, isDirty });
this.props.onSelect(value, isDirty);
}
Expand Down Expand Up @@ -96,13 +96,15 @@ class ParameterValueInput extends React.Component {
const { enumOptions, parameter } = this.props;
const { value } = this.state;
const enumOptionsArray = enumOptions.split('\n').filter(v => v !== '');
// Antd Select doesn't handle null in multiple mode
const normalize = val => (parameter.multiValuesOptions && val === null ? [] : val);
return (
<Select
className={this.props.className}
mode={parameter.multiValuesOptions ? 'multiple' : 'default'}
optionFilterProp="children"
disabled={enumOptionsArray.length === 0}
value={value}
value={normalize(value)}
onChange={this.onSelect}
dropdownMatchSelectWidth={false}
showSearch
Expand Down
4 changes: 2 additions & 2 deletions client/app/components/Parameters.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { size, filter, forEach, extend } from 'lodash';
import { react2angular } from 'react2angular';
import { SortableContainer, SortableElement, DragHandle } from '@/components/sortable';
import { $location } from '@/services/ng';
import { Parameter } from '@/services/query';
import { Parameter } from '@/services/parameters';
import ParameterApplyButton from '@/components/ParameterApplyButton';
import ParameterValueInput from '@/components/ParameterValueInput';
import EditParameterSettingsDialog from './EditParameterSettingsDialog';
Expand Down Expand Up @@ -111,7 +111,7 @@ export class Parameters extends React.Component {
.result.then((updated) => {
this.setState(({ parameters }) => {
const updatedParameter = extend(parameter, updated);
parameters[index] = new Parameter(updatedParameter, updatedParameter.parentQueryId);
parameters[index] = Parameter.create(updatedParameter, updatedParameter.parentQueryId);
onParametersEdit();
return { parameters };
});
Expand Down
14 changes: 7 additions & 7 deletions client/app/components/dynamic-parameters/DateParameter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import moment from 'moment';
import { includes } from 'lodash';
import { isDynamicDate, getDynamicDate } from '@/services/query';
import { isDynamicDate, getDynamicDateFromString } from '@/services/parameters/DateParameter';
import DateInput from '@/components/DateInput';
import DateTimeInput from '@/components/DateTimeInput';
import DynamicButton from '@/components/dynamic-parameters/DynamicButton';
Expand All @@ -12,11 +12,11 @@ import './DynamicParameters.less';

const DYNAMIC_DATE_OPTIONS = [
{ name: 'Today/Now',
value: 'd_now',
label: () => getDynamicDate('d_now').value().format('MMM D') },
value: getDynamicDateFromString('d_now'),
label: () => getDynamicDateFromString('d_now').value().format('MMM D') },
{ name: 'Yesterday',
value: 'd_yesterday',
label: () => getDynamicDate('d_yesterday').value().format('MMM D') },
value: getDynamicDateFromString('d_yesterday'),
label: () => getDynamicDateFromString('d_yesterday').value().format('MMM D') },
];

class DateParameter extends React.Component {
Expand Down Expand Up @@ -44,7 +44,7 @@ class DateParameter extends React.Component {
onDynamicValueSelect = (dynamicValue) => {
const { onSelect, parameter } = this.props;
if (dynamicValue === 'static') {
const parameterValue = parameter.getValue();
const parameterValue = parameter.getExecutionValue();
if (parameterValue) {
onSelect(moment(parameterValue));
} else {
Expand Down Expand Up @@ -77,7 +77,7 @@ class DateParameter extends React.Component {
}

if (hasDynamicValue) {
const dynamicDate = getDynamicDate(value);
const dynamicDate = value;
additionalAttributes.placeholder = dynamicDate && dynamicDate.name;
additionalAttributes.value = null;
}
Expand Down
47 changes: 27 additions & 20 deletions client/app/components/dynamic-parameters/DateRangeParameter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import moment from 'moment';
import { includes, isArray, isObject } from 'lodash';
import { isDynamicDateRange, getDynamicDateRange } from '@/services/query';
import { isDynamicDateRange, getDynamicDateRangeFromString } from '@/services/parameters/DateRangeParameter';
import DateRangeInput from '@/components/DateRangeInput';
import DateTimeRangeInput from '@/components/DateTimeRangeInput';
import DynamicButton from '@/components/dynamic-parameters/DynamicButton';
Expand All @@ -12,29 +12,37 @@ import './DynamicParameters.less';

const DYNAMIC_DATE_OPTIONS = [
{ name: 'This week',
value: 'd_this_week',
label: () => getDynamicDateRange('d_this_week').value()[0].format('MMM D') + ' - ' +
getDynamicDateRange('d_this_week').value()[1].format('MMM D') },
{ name: 'This month', value: 'd_this_month', label: () => getDynamicDateRange('d_this_month').value()[0].format('MMMM') },
{ name: 'This year', value: 'd_this_year', label: () => getDynamicDateRange('d_this_year').value()[0].format('YYYY') },
value: getDynamicDateRangeFromString('d_this_week'),
label: () => getDynamicDateRangeFromString('d_this_week').value()[0].format('MMM D') + ' - ' +
getDynamicDateRangeFromString('d_this_week').value()[1].format('MMM D') },
{ name: 'This month',
value: getDynamicDateRangeFromString('d_this_month'),
label: () => getDynamicDateRangeFromString('d_this_month').value()[0].format('MMMM') },
{ name: 'This year',
value: getDynamicDateRangeFromString('d_this_year'),
label: () => getDynamicDateRangeFromString('d_this_year').value()[0].format('YYYY') },
{ name: 'Last week',
value: 'd_last_week',
label: () => getDynamicDateRange('d_last_week').value()[0].format('MMM D') + ' - ' +
getDynamicDateRange('d_last_week').value()[1].format('MMM D') },
{ name: 'Last month', value: 'd_last_month', label: () => getDynamicDateRange('d_last_month').value()[0].format('MMMM') },
{ name: 'Last year', value: 'd_last_year', label: () => getDynamicDateRange('d_last_year').value()[0].format('YYYY') },
value: getDynamicDateRangeFromString('d_last_week'),
label: () => getDynamicDateRangeFromString('d_last_week').value()[0].format('MMM D') + ' - ' +
getDynamicDateRangeFromString('d_last_week').value()[1].format('MMM D') },
{ name: 'Last month',
value: getDynamicDateRangeFromString('d_last_month'),
label: () => getDynamicDateRangeFromString('d_last_month').value()[0].format('MMMM') },
{ name: 'Last year',
value: getDynamicDateRangeFromString('d_last_year'),
label: () => getDynamicDateRangeFromString('d_last_year').value()[0].format('YYYY') },
{ name: 'Last 7 days',
value: 'd_last_7_days',
label: () => getDynamicDateRange('d_last_7_days').value()[0].format('MMM D') + ' - Today' },
value: getDynamicDateRangeFromString('d_last_7_days'),
label: () => getDynamicDateRangeFromString('d_last_7_days').value()[0].format('MMM D') + ' - Today' },
];

const DYNAMIC_DATETIME_OPTIONS = [
{ name: 'Today',
value: 'd_today',
label: () => getDynamicDateRange('d_today').value()[0].format('MMM D') },
value: getDynamicDateRangeFromString('d_today'),
label: () => getDynamicDateRangeFromString('d_today').value()[0].format('MMM D') },
{ name: 'Yesterday',
value: 'd_yesterday',
label: () => getDynamicDateRange('d_yesterday').value()[0].format('MMM D') },
value: getDynamicDateRangeFromString('d_yesterday'),
label: () => getDynamicDateRangeFromString('d_yesterday').value()[0].format('MMM D') },
...DYNAMIC_DATE_OPTIONS,
];

Expand Down Expand Up @@ -73,7 +81,7 @@ class DateRangeParameter extends React.Component {
onDynamicValueSelect = (dynamicValue) => {
const { onSelect, parameter } = this.props;
if (dynamicValue === 'static') {
const parameterValue = parameter.getValue();
const parameterValue = parameter.getExecutionValue();
if (isObject(parameterValue) && parameterValue.start && parameterValue.end) {
onSelect([moment(parameterValue.start), moment(parameterValue.end)]);
} else {
Expand Down Expand Up @@ -107,8 +115,7 @@ class DateRangeParameter extends React.Component {
}

if (hasDynamicValue) {
const dynamicDateRange = getDynamicDateRange(value);
additionalAttributes.placeholder = [dynamicDateRange && dynamicDateRange.name];
additionalAttributes.placeholder = [value && value.name];
additionalAttributes.value = null;
}

Expand Down
4 changes: 3 additions & 1 deletion client/app/components/dynamic-parameters/DynamicButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Dropdown from 'antd/lib/dropdown';
import Icon from 'antd/lib/icon';
import Menu from 'antd/lib/menu';
import Typography from 'antd/lib/typography';
import { DynamicDateType } from '@/services/parameters/DateParameter';
import { DynamicDateRangeType } from '@/services/parameters/DateRangeParameter';

import './DynamicButton.less';

Expand Down Expand Up @@ -62,7 +64,7 @@ function DynamicButton({ options, selectedDynamicValue, onSelect, enabled }) {

DynamicButton.propTypes = {
options: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
selectedDynamicValue: PropTypes.string,
selectedDynamicValue: PropTypes.oneOfType([DynamicDateType, DynamicDateRangeType]),
onSelect: PropTypes.func,
enabled: PropTypes.bool,
};
Expand Down
4 changes: 2 additions & 2 deletions client/app/pages/queries/view.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pick, some, find, minBy, map, intersection, isEmpty, isArray, omit } from 'lodash';
import { pick, some, find, minBy, map, intersection, isEmpty, isArray } from 'lodash';
import { SCHEMA_NOT_SUPPORTED, SCHEMA_LOAD_ERROR } from '@/services/data-source';
import getTags from '@/services/getTags';
import { policy } from '@/services/policy';
Expand Down Expand Up @@ -261,7 +261,7 @@ function QueryViewCtrl(
if (request.options && request.options.parameters) {
request.options = {
...request.options,
parameters: map(request.options.parameters, p => omit(p, 'pendingValue')),
parameters: map(request.options.parameters, p => p.toSaveableObject()),
};
}

Expand Down
95 changes: 95 additions & 0 deletions client/app/services/parameters/DateParameter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { findKey, startsWith, has, includes, isNull, values } from 'lodash';
import moment from 'moment';
import PropTypes from 'prop-types';
import { Parameter } from '.';

const DATETIME_FORMATS = {
// eslint-disable-next-line quote-props
'date': 'YYYY-MM-DD',
'datetime-local': 'YYYY-MM-DD HH:mm',
'datetime-with-seconds': 'YYYY-MM-DD HH:mm:ss',
};

const DYNAMIC_PREFIX = 'd_';

const DYNAMIC_DATES = {
now: {
name: 'Today/Now',
value: () => moment(),
},
yesterday: {
name: 'Yesterday',
value: () => moment().subtract(1, 'day'),
},
};

export const DynamicDateType = PropTypes.oneOf(values(DYNAMIC_DATES));

function isDynamicDateString(value) {
return startsWith(value, DYNAMIC_PREFIX) && has(DYNAMIC_DATES, value.substring(DYNAMIC_PREFIX.length));
}

export function isDynamicDate(value) {
return includes(DYNAMIC_DATES, value);
}

export function getDynamicDateFromString(value) {
if (!isDynamicDateString(value)) {
return null;
}
return DYNAMIC_DATES[value.substring(DYNAMIC_PREFIX.length)];
}

class DateParameter extends Parameter {
constructor(parameter, parentQueryId) {
super(parameter, parentQueryId);
this.useCurrentDateTime = parameter.useCurrentDateTime;
this.setValue(parameter.value);
}

get hasDynamicValue() {
return isDynamicDate(this.normalizedValue);
}

// eslint-disable-next-line class-methods-use-this
normalizeValue(value) {
if (isDynamicDateString(value)) {
return getDynamicDateFromString(value);
}

if (isDynamicDate(value)) {
return value;
}

const normalizedValue = moment(value);
return normalizedValue.isValid() ? normalizedValue : null;
}

setValue(value) {
const normalizedValue = this.normalizeValue(value);
if (isDynamicDate(normalizedValue)) {
this.value = DYNAMIC_PREFIX + findKey(DYNAMIC_DATES, normalizedValue);
} else if (moment.isMoment(normalizedValue)) {
this.value = normalizedValue.format(DATETIME_FORMATS[this.type]);
} else {
this.value = normalizedValue;
}
this.$$value = normalizedValue;

this.updateLocals();
this.clearPendingValue();
return this;
}

getExecutionValue() {
if (this.hasDynamicValue) {
return this.normalizedValue.value().format(DATETIME_FORMATS[this.type]);
}
if (isNull(this.value) && this.useCurrentDateTime) {
return moment().format(DATETIME_FORMATS[this.type]);
}
return this.value;
}
}

export default DateParameter;
Loading

0 comments on commit 9f78446

Please sign in to comment.