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:
Evan Ren 2019-11-07 13:07:17 -08:00 committed by Vadim Ogievetsky
parent 7aafcf8bca
commit b03aa060bd
10 changed files with 274 additions and 7 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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}'`);
} }

View File

@ -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>
`;

View File

@ -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;
}
}

View File

@ -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();
});
});

View File

@ -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);
}}
/>
);
});

View File

@ -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';

View File

@ -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>

View File

@ -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;