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": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-3.11.0.tgz",
|
||||
|
@ -11279,6 +11297,14 @@
|
|||
"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": {
|
||||
"version": "16.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz",
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.19.1",
|
||||
"@blueprintjs/icons": "^3.11.0",
|
||||
"@blueprintjs/datetime": "^3.11.0",
|
||||
"axios": "^0.19.0",
|
||||
"brace": "^0.11.1",
|
||||
"classnames": "^2.2.6",
|
||||
|
|
|
@ -22,6 +22,7 @@ import React from 'react';
|
|||
import { deepDelete, deepGet, deepSet } from '../../utils/object-change';
|
||||
import { ArrayInput } from '../array-input/array-input';
|
||||
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 { SuggestibleInput, SuggestionGroup } from '../suggestible-input/suggestible-input';
|
||||
|
||||
|
@ -31,7 +32,15 @@ export interface Field<T> {
|
|||
name: string;
|
||||
label?: string;
|
||||
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;
|
||||
suggestions?: (string | SuggestionGroup)[];
|
||||
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>) {
|
||||
switch (field.type) {
|
||||
case 'number':
|
||||
|
@ -306,6 +330,8 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
|
|||
return this.renderStringArrayInput(field);
|
||||
case 'json':
|
||||
return this.renderJsonInput(field);
|
||||
case 'interval':
|
||||
return this.renderIntervalInput(field);
|
||||
default:
|
||||
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/@blueprintjs/core/lib/css/blueprint';
|
||||
@import '../node_modules/@blueprintjs/datetime/lib/css/blueprint-datetime';
|
||||
@import '../lib/react-table';
|
||||
@import '../node_modules/react-splitter-layout/lib/index.css';
|
||||
|
||||
|
|
|
@ -1092,13 +1092,8 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
|
|||
{
|
||||
name: 'firehose.interval',
|
||||
label: 'Interval',
|
||||
type: 'string',
|
||||
type: 'interval',
|
||||
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,
|
||||
info: (
|
||||
<p>
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
font-feature-settings: tnum;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.clickable-cell {
|
||||
cursor: pointer;
|
||||
|
|
Loading…
Reference in New Issue