mirror of https://github.com/apache/druid.git
Web console: Interval input component (#8777)
* Created temporary interval input component * Make reusable interval component * Fixed errors with typing invalid dates * Fix interval input styling and place into autoform * Fix styling of popover calendar that opens off the page * Add snapshot test and change interval to required props * Add functionality to enter hours minutes second * Fix min date limit * Remove console log * Fix difference in timezone * Update snapshot test * Fixed snapshot test without changing min max date * Made changes based on discussion before converting to hooks * Rewrote using hooks and deleted duplicate states * Remove unused states * Change sql query view numbers to monospace * Made changes based on discussion * Removed duplicate state
This commit is contained in:
parent
7aafcf8bca
commit
b03aa060bd
|
@ -991,6 +991,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@blueprintjs/datetime": {
|
||||||
|
"version": "3.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@blueprintjs/datetime/-/datetime-3.14.0.tgz",
|
||||||
|
"integrity": "sha512-NfCiV19zMPWZ7kcx1A2OympSLy7vp8wFrhMaMAKK6R+ndTUuEDZi3Z5NxoyX2jBJ71F9nhWYFjH1nQVOn/eDaw==",
|
||||||
|
"requires": {
|
||||||
|
"@blueprintjs/core": "^3.19.0",
|
||||||
|
"classnames": "^2.2",
|
||||||
|
"react-day-picker": "^7.3.2",
|
||||||
|
"tslib": "~1.9.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@blueprintjs/icons": {
|
"@blueprintjs/icons": {
|
||||||
"version": "3.11.0",
|
"version": "3.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-3.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-3.11.0.tgz",
|
||||||
|
@ -11279,6 +11297,14 @@
|
||||||
"prop-types": "^15.7.2"
|
"prop-types": "^15.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-day-picker": {
|
||||||
|
"version": "7.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-7.4.0.tgz",
|
||||||
|
"integrity": "sha512-dqfr96EY7mHSpbW23hJI6of2JvxClDfHLNQ7VqctxBvNsJIzEiwh3zS8hEhqNza7xuR0vC4KN517zxndgb3/fw==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-dom": {
|
"react-dom": {
|
||||||
"version": "16.11.0",
|
"version": "16.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz",
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blueprintjs/core": "^3.19.1",
|
"@blueprintjs/core": "^3.19.1",
|
||||||
"@blueprintjs/icons": "^3.11.0",
|
"@blueprintjs/icons": "^3.11.0",
|
||||||
|
"@blueprintjs/datetime": "^3.11.0",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"brace": "^0.11.1",
|
"brace": "^0.11.1",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
|
|
@ -22,6 +22,7 @@ import React from 'react';
|
||||||
import { deepDelete, deepGet, deepSet } from '../../utils/object-change';
|
import { deepDelete, deepGet, deepSet } from '../../utils/object-change';
|
||||||
import { ArrayInput } from '../array-input/array-input';
|
import { ArrayInput } from '../array-input/array-input';
|
||||||
import { FormGroupWithInfo } from '../form-group-with-info/form-group-with-info';
|
import { FormGroupWithInfo } from '../form-group-with-info/form-group-with-info';
|
||||||
|
import { IntervalInput } from '../interval-input/interval-input';
|
||||||
import { JsonInput } from '../json-input/json-input';
|
import { JsonInput } from '../json-input/json-input';
|
||||||
import { SuggestibleInput, SuggestionGroup } from '../suggestible-input/suggestible-input';
|
import { SuggestibleInput, SuggestionGroup } from '../suggestible-input/suggestible-input';
|
||||||
|
|
||||||
|
@ -31,7 +32,15 @@ export interface Field<T> {
|
||||||
name: string;
|
name: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
info?: React.ReactNode;
|
info?: React.ReactNode;
|
||||||
type: 'number' | 'size-bytes' | 'string' | 'duration' | 'boolean' | 'string-array' | 'json';
|
type:
|
||||||
|
| 'number'
|
||||||
|
| 'size-bytes'
|
||||||
|
| 'string'
|
||||||
|
| 'duration'
|
||||||
|
| 'boolean'
|
||||||
|
| 'string-array'
|
||||||
|
| 'json'
|
||||||
|
| 'interval';
|
||||||
defaultValue?: any;
|
defaultValue?: any;
|
||||||
suggestions?: (string | SuggestionGroup)[];
|
suggestions?: (string | SuggestionGroup)[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -288,6 +297,21 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderIntervalInput(field: Field<T>): JSX.Element {
|
||||||
|
const { model } = this.props;
|
||||||
|
|
||||||
|
const modelValue = deepGet(model as any, field.name);
|
||||||
|
return (
|
||||||
|
<IntervalInput
|
||||||
|
interval={modelValue != null ? modelValue : field.defaultValue || ''}
|
||||||
|
onValueChange={(v: any) => {
|
||||||
|
this.fieldChange(field, v);
|
||||||
|
}}
|
||||||
|
placeholder={field.placeholder}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderFieldInput(field: Field<T>) {
|
renderFieldInput(field: Field<T>) {
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'number':
|
case 'number':
|
||||||
|
@ -306,6 +330,8 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
|
||||||
return this.renderStringArrayInput(field);
|
return this.renderStringArrayInput(field);
|
||||||
case 'json':
|
case 'json':
|
||||||
return this.renderJsonInput(field);
|
return this.renderJsonInput(field);
|
||||||
|
case 'interval':
|
||||||
|
return this.renderIntervalInput(field);
|
||||||
default:
|
default:
|
||||||
throw new Error(`unknown field type '${field.type}'`);
|
throw new Error(`unknown field type '${field.type}'`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`interval calendar component matches snapshot 1`] = `
|
||||||
|
<div
|
||||||
|
class="bp3-input-group"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="bp3-input"
|
||||||
|
placeholder="2010-01-01/2020-01-01"
|
||||||
|
style="padding-right: 0px;"
|
||||||
|
type="text"
|
||||||
|
value="2010-01-01/2020-01-01"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="bp3-input-action"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
class="bp3-popover-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bp3-popover-target"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="bp3-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bp3-icon bp3-icon-calendar"
|
||||||
|
icon="calendar"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
data-icon="calendar"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
width="16"
|
||||||
|
>
|
||||||
|
<desc>
|
||||||
|
calendar
|
||||||
|
</desc>
|
||||||
|
<path
|
||||||
|
d="M11 3c.6 0 1-.5 1-1V1c0-.6-.4-1-1-1s-1 .4-1 1v1c0 .5.4 1 1 1zm3-2h-1v1c0 1.1-.9 2-2 2s-2-.9-2-2V1H6v1c0 1.1-.9 2-2 2s-2-.9-2-2V1H1c-.6 0-1 .5-1 1v12c0 .6.4 1 1 1h13c.6 0 1-.4 1-1V2c0-.6-.5-1-1-1zM5 13H2v-3h3v3zm0-4H2V6h3v3zm4 4H6v-3h3v3zm0-4H6V6h3v3zm4 4h-3v-3h3v3zm0-4h-3V6h3v3zM4 3c.6 0 1-.5 1-1V1c0-.6-.4-1-1-1S3 .4 3 1v1c0 .5.4 1 1 1z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.calendar {
|
||||||
|
.bp3-popover-content {
|
||||||
|
right: 310px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { IntervalInput } from './interval-input';
|
||||||
|
|
||||||
|
describe('interval calendar component', () => {
|
||||||
|
it('matches snapshot', () => {
|
||||||
|
const intervalInput = (
|
||||||
|
<IntervalInput
|
||||||
|
interval={'2010-01-01/2020-01-01'}
|
||||||
|
placeholder={'2010-01-01/2020-01-01'}
|
||||||
|
onValueChange={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const { container } = render(intervalInput);
|
||||||
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Button, InputGroup, Popover, Position } from '@blueprintjs/core';
|
||||||
|
import { DateRange, DateRangePicker } from '@blueprintjs/datetime';
|
||||||
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import './interval-input.scss';
|
||||||
|
|
||||||
|
const CURRENT_YEAR = new Date().getUTCFullYear();
|
||||||
|
|
||||||
|
function removeLocalTimezone(localDate: Date): Date {
|
||||||
|
// Function removes the local timezone of the date and displays it in UTC
|
||||||
|
return new Date(localDate.getTime() - localDate.getTimezoneOffset() * 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseInterval(interval: string): DateRange {
|
||||||
|
const dates = interval.split('/');
|
||||||
|
if (dates.length !== 2) {
|
||||||
|
return [undefined, undefined];
|
||||||
|
}
|
||||||
|
const startDate = Date.parse(dates[0]) ? new Date(dates[0]) : undefined;
|
||||||
|
const endDate = Date.parse(dates[1]) ? new Date(dates[1]) : undefined;
|
||||||
|
// Must check if the start and end dates are within range
|
||||||
|
return [
|
||||||
|
startDate && startDate.getFullYear() < CURRENT_YEAR - 20 ? undefined : startDate,
|
||||||
|
endDate && endDate.getFullYear() > CURRENT_YEAR ? undefined : endDate,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
function stringifyDateRange(localRange: DateRange): string {
|
||||||
|
// This function takes in the dates selected from datepicker in local time, and displays them in UTC
|
||||||
|
// Shall Blueprint make any changes to the way dates are selected, this function will have to be reworked
|
||||||
|
const [localStartDate, localEndDate] = localRange;
|
||||||
|
return `${
|
||||||
|
localStartDate
|
||||||
|
? removeLocalTimezone(localStartDate)
|
||||||
|
.toISOString()
|
||||||
|
.substring(0, 19)
|
||||||
|
: ''
|
||||||
|
}/${
|
||||||
|
localEndDate
|
||||||
|
? removeLocalTimezone(localEndDate)
|
||||||
|
.toISOString()
|
||||||
|
.substring(0, 19)
|
||||||
|
: ''
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IntervalInputProps {
|
||||||
|
interval: string;
|
||||||
|
placeholder: string | undefined;
|
||||||
|
onValueChange: (interval: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IntervalInput = React.memo(function IntervalInput(props: IntervalInputProps) {
|
||||||
|
const { interval, placeholder, onValueChange } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputGroup
|
||||||
|
value={interval}
|
||||||
|
placeholder={placeholder}
|
||||||
|
rightElement={
|
||||||
|
<div>
|
||||||
|
<Popover
|
||||||
|
popoverClassName={'calendar'}
|
||||||
|
content={
|
||||||
|
<DateRangePicker
|
||||||
|
timePrecision={'second'}
|
||||||
|
value={parseInterval(interval)}
|
||||||
|
contiguousCalendarMonths={false}
|
||||||
|
onChange={(selectedRange: DateRange) => {
|
||||||
|
onValueChange(stringifyDateRange(selectedRange));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
position={Position.BOTTOM_RIGHT}
|
||||||
|
>
|
||||||
|
<Button rightIcon={IconNames.CALENDAR} />
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onChange={(e: any) => {
|
||||||
|
const value = e.target.value.replace(/[^\-0-9T:/]/g, '').substring(0, 39);
|
||||||
|
onValueChange(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
@import '../node_modules/normalize.css/normalize';
|
@import '../node_modules/normalize.css/normalize';
|
||||||
@import '../node_modules/@blueprintjs/core/lib/css/blueprint';
|
@import '../node_modules/@blueprintjs/core/lib/css/blueprint';
|
||||||
|
@import '../node_modules/@blueprintjs/datetime/lib/css/blueprint-datetime';
|
||||||
@import '../lib/react-table';
|
@import '../lib/react-table';
|
||||||
@import '../node_modules/react-splitter-layout/lib/index.css';
|
@import '../node_modules/react-splitter-layout/lib/index.css';
|
||||||
|
|
||||||
|
|
|
@ -1092,13 +1092,8 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
|
||||||
{
|
{
|
||||||
name: 'firehose.interval',
|
name: 'firehose.interval',
|
||||||
label: 'Interval',
|
label: 'Interval',
|
||||||
type: 'string',
|
type: 'interval',
|
||||||
placeholder: `${CURRENT_YEAR}-01-01/${CURRENT_YEAR + 1}-01-01`,
|
placeholder: `${CURRENT_YEAR}-01-01/${CURRENT_YEAR + 1}-01-01`,
|
||||||
suggestions: [
|
|
||||||
`${CURRENT_YEAR}-01-01T00:00:00/${CURRENT_YEAR + 1}-01-01T00:00:00`,
|
|
||||||
`${CURRENT_YEAR}-01-01/${CURRENT_YEAR + 1}-01-01`,
|
|
||||||
`${CURRENT_YEAR}/${CURRENT_YEAR + 1}`,
|
|
||||||
],
|
|
||||||
required: true,
|
required: true,
|
||||||
info: (
|
info: (
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
font-feature-settings: tnum;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
.clickable-cell {
|
.clickable-cell {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
Loading…
Reference in New Issue