Skip to content

Commit 5a9cc6f

Browse files
authored
Merge branch 'deephaven:main' into DH-19210-handle-copy-pasting-excel-hash-symbols-into-web-i-ts
2 parents 3b4db5c + 49781a7 commit 5a9cc6f

4 files changed

Lines changed: 72 additions & 33 deletions

File tree

packages/iris-grid/src/sidebar/conditional-formatting/ConditionEditor.tsx

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useCallback, useEffect, useMemo, useState } from 'react';
2+
import classNames from 'classnames';
23
import { TableUtils } from '@deephaven/jsapi-utils';
34
import type { dh as DhType } from '@deephaven/jsapi-types';
45
import Log from '@deephaven/log';
@@ -140,6 +141,7 @@ function getNumberInputs(
140141
handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
141142
handleStartValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
142143
handleEndValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
144+
isInvalid: boolean,
143145
conditionValue?: string,
144146
startValue?: string,
145147
endValue?: string
@@ -154,7 +156,7 @@ function getNumberInputs(
154156
return (
155157
<input
156158
type="number"
157-
className="form-control"
159+
className={classNames('form-control', { 'is-invalid': isInvalid })}
158160
placeholder="Enter value"
159161
value={conditionValue ?? ''}
160162
onChange={handleValueChange}
@@ -165,14 +167,18 @@ function getNumberInputs(
165167
<div className="d-flex flex-row">
166168
<input
167169
type="number"
168-
className="form-control d-flex mr-2"
170+
className={classNames('form-control', 'd-flex', 'mr-2', {
171+
'is-invalid': isInvalid,
172+
})}
169173
placeholder="Start value"
170174
value={startValue ?? ''}
171175
onChange={handleStartValueChange}
172176
/>
173177
<input
174178
type="number"
175-
className="form-control d-flex"
179+
className={classNames('form-control', 'd-flex', {
180+
'is-invalid': isInvalid,
181+
})}
176182
placeholder="End value"
177183
value={endValue ?? ''}
178184
onChange={handleEndValueChange}
@@ -188,6 +194,7 @@ function getNumberInputs(
188194
function getStringInputs(
189195
selectedCondition: StringCondition,
190196
handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
197+
isInvalid: boolean,
191198
conditionValue?: string
192199
): JSX.Element | null {
193200
switch (selectedCondition) {
@@ -198,7 +205,7 @@ function getStringInputs(
198205
return (
199206
<input
200207
type="text"
201-
className="form-control"
208+
className={classNames('form-control', { 'is-invalid': isInvalid })}
202209
placeholder="Enter value"
203210
value={conditionValue ?? ''}
204211
onChange={handleValueChange}
@@ -210,6 +217,7 @@ function getStringInputs(
210217
function getDateInputs(
211218
selectedCondition: DateCondition,
212219
handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
220+
isInvalid: boolean,
213221
conditionValue?: string
214222
): JSX.Element | null {
215223
switch (selectedCondition) {
@@ -220,7 +228,7 @@ function getDateInputs(
220228
return (
221229
<input
222230
type="text"
223-
className="form-control"
231+
className={classNames('form-control', { 'is-invalid': isInvalid })}
224232
placeholder="Enter value"
225233
value={conditionValue ?? ''}
226234
onChange={handleValueChange}
@@ -236,6 +244,7 @@ function getBooleanInputs(): null {
236244
function getCharInputs(
237245
selectedCondition: CharCondition,
238246
handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
247+
isInvalid: boolean,
239248
conditionValue?: string
240249
): JSX.Element | null {
241250
switch (selectedCondition) {
@@ -246,7 +255,7 @@ function getCharInputs(
246255
return (
247256
<input
248257
type="text"
249-
className="form-control"
258+
className={classNames('form-control', { 'is-invalid': isInvalid })}
250259
maxLength={1}
251260
placeholder="Enter value"
252261
value={conditionValue ?? ''}
@@ -264,6 +273,7 @@ function ConditionEditor(props: ConditionEditorProps): JSX.Element {
264273
const [conditionValue, setValue] = useState(config.value);
265274
const [startValue, setStartValue] = useState(config.start);
266275
const [endValue, setEndValue] = useState(config.end);
276+
const [isValid, setIsValid] = useState(true);
267277

268278
if (selectedColumnType !== prevColumnType) {
269279
// Column type changed, reset condition and value fields
@@ -329,13 +339,13 @@ function ConditionEditor(props: ConditionEditorProps): JSX.Element {
329339

330340
useEffect(
331341
function changeCondition() {
332-
let isValid = true;
342+
let isConditionValid = true;
333343

334344
if (selectedCondition === undefined) {
335345
log.debug(
336346
'Unable to create formatting rule. Condition is not selected.'
337347
);
338-
isValid = false;
348+
isConditionValid = false;
339349
} else if (
340350
TableUtils.isNumberType(column.type) &&
341351
!isNumberConditionValid(
@@ -349,7 +359,7 @@ function ConditionEditor(props: ConditionEditorProps): JSX.Element {
349359
'Unable to create formatting rule. Invalid value',
350360
conditionValue
351361
);
352-
isValid = false;
362+
isConditionValid = false;
353363
} else if (
354364
TableUtils.isDateType(column.type) &&
355365
!isDateConditionValid(
@@ -362,17 +372,18 @@ function ConditionEditor(props: ConditionEditorProps): JSX.Element {
362372
'Unable to create formatting rule. Invalid date condition',
363373
conditionValue
364374
);
365-
isValid = false;
375+
isConditionValid = false;
366376
}
367377

378+
setIsValid(isConditionValid);
368379
onChange(
369380
{
370381
condition: selectedCondition,
371382
value: conditionValue,
372383
start: startValue,
373384
end: endValue,
374385
},
375-
isValid
386+
isConditionValid
376387
);
377388
},
378389
[
@@ -391,12 +402,26 @@ function ConditionEditor(props: ConditionEditorProps): JSX.Element {
391402
// Column not selected
392403
return null;
393404
}
405+
406+
// Show invalid state only when there's a non-empty value that fails validation
407+
const hasInvalidValue =
408+
!isValid && conditionValue !== undefined && conditionValue !== '';
409+
394410
if (TableUtils.isNumberType(selectedColumnType)) {
411+
// For IS_BETWEEN, show invalid on each field only if that field has a value
412+
const showInvalid =
413+
selectedCondition === NumberCondition.IS_BETWEEN
414+
? !isValid &&
415+
((startValue !== undefined && startValue !== '') ||
416+
(endValue !== undefined && endValue !== ''))
417+
: hasInvalidValue;
418+
395419
return getNumberInputs(
396420
selectedCondition as NumberCondition,
397421
handleValueChange,
398422
handleStartValueChange,
399423
handleEndValueChange,
424+
showInvalid,
400425
conditionValue,
401426
startValue,
402427
endValue
@@ -406,20 +431,23 @@ function ConditionEditor(props: ConditionEditorProps): JSX.Element {
406431
return getCharInputs(
407432
selectedCondition as CharCondition,
408433
handleValueChange,
434+
hasInvalidValue,
409435
conditionValue
410436
);
411437
}
412438
if (TableUtils.isStringType(selectedColumnType)) {
413439
return getStringInputs(
414440
selectedCondition as StringCondition,
415441
handleValueChange,
442+
hasInvalidValue,
416443
conditionValue
417444
);
418445
}
419446
if (TableUtils.isDateType(selectedColumnType)) {
420447
return getDateInputs(
421448
selectedCondition as DateCondition,
422449
handleValueChange,
450+
hasInvalidValue,
423451
conditionValue
424452
);
425453
}
@@ -432,6 +460,7 @@ function ConditionEditor(props: ConditionEditorProps): JSX.Element {
432460
conditionValue,
433461
startValue,
434462
endValue,
463+
isValid,
435464
handleValueChange,
436465
handleStartValueChange,
437466
handleEndValueChange,

packages/iris-grid/src/sidebar/conditional-formatting/ConditionalFormattingAPIUtils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ import {
1010
// https://github.com/facebook/jest/issues/936#issuecomment-545080082
1111

1212
export function makeTernaryFormatRule(
13+
dh: typeof DhType,
1314
config: BaseFormatConfig,
1415
prevRule: string
1516
): string | undefined {
1617
const styleDBString = getStyleDBString(config);
1718
if (styleDBString === undefined) {
1819
return undefined;
1920
}
20-
const conditionDBString = getConditionDBString(config);
21+
const conditionDBString = getConditionDBString(dh, config);
2122
return `${conditionDBString} ? ${styleDBString} : ${prevRule}`;
2223
}
2324

packages/iris-grid/src/sidebar/conditional-formatting/ConditionalFormattingUtils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const irisGridTestUtils = new IrisGridTestUtils(dh);
1515

1616
jest.mock('./ConditionalFormattingAPIUtils', () => ({
1717
makeTernaryFormatRule: jest.fn(
18-
(rule, prevRule = null) =>
18+
(_dh, rule, prevRule = null) =>
1919
`${rule.column.name} - ${rule.style.type} : ${prevRule}`
2020
),
2121
makeColumnFormatColumn: jest.fn((col, rule) => `[col] ${rule}`),

packages/iris-grid/src/sidebar/conditional-formatting/ConditionalFormattingUtils.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,13 @@ function getStringConditionText(config: BaseFormatConfig): string {
222222
);
223223
}
224224

225-
function getDateConditionText(config: BaseFormatConfig): string {
225+
function getDateConditionText(
226+
dh: typeof DhType,
227+
config: BaseFormatConfig
228+
): string {
226229
const { column, value } = config;
227230
return getTextForDateCondition(
231+
dh,
228232
column.name,
229233
config.condition as DateCondition,
230234
value
@@ -248,7 +252,10 @@ function getCharConditionText(config: BaseFormatConfig): string {
248252
);
249253
}
250254

251-
export function getConditionDBString(config: BaseFormatConfig): string {
255+
export function getConditionDBString(
256+
dh: typeof DhType,
257+
config: BaseFormatConfig
258+
): string {
252259
const { column } = config;
253260

254261
if (TableUtils.isNumberType(column.type)) {
@@ -261,7 +268,7 @@ export function getConditionDBString(config: BaseFormatConfig): string {
261268
return getStringConditionText(config);
262269
}
263270
if (TableUtils.isDateType(column.type)) {
264-
return getDateConditionText(config);
271+
return getDateConditionText(dh, config);
265272
}
266273
if (TableUtils.isBooleanType(column.type)) {
267274
return getBooleanConditionText(config);
@@ -563,23 +570,35 @@ export function getTextForStringCondition(
563570
}
564571

565572
export function getTextForDateCondition(
573+
dh: typeof DhType,
566574
columnName: ColumnName,
567575
condition: DateCondition,
568576
value: unknown
569577
): string {
578+
let formattedValue = value;
579+
if (typeof value === 'string') {
580+
// The date time formatting may return a timezone that is not supported by the backend (e.g. 'EDT' instead of 'ET')
581+
// so we need to parse the date time string and reformat it with a supported timezone ID.
582+
// Note that we know this will be valid because the input value has already been validated with isDateConditionValid.
583+
const [dateTimeString, ...rest] = value.split(' ');
584+
const tzCode = rest.join(' ');
585+
const tz = dh.i18n.TimeZone.getTimeZone(tzCode);
586+
formattedValue = `${dateTimeString} ${tz.id}`;
587+
}
588+
570589
switch (condition) {
571590
case DateCondition.IS_EXACTLY:
572-
return `${columnName} == '${value}'`;
591+
return `${columnName} == '${formattedValue}'`;
573592
case DateCondition.IS_NOT_EXACTLY:
574-
return `${columnName} != '${value}'`;
593+
return `${columnName} != '${formattedValue}'`;
575594
case DateCondition.IS_BEFORE:
576-
return `${columnName} < '${value}'`;
595+
return `${columnName} < '${formattedValue}'`;
577596
case DateCondition.IS_BEFORE_OR_EQUAL:
578-
return `${columnName} <= '${value}'`;
597+
return `${columnName} <= '${formattedValue}'`;
579598
case DateCondition.IS_AFTER:
580-
return `${columnName} > '${value}'`;
599+
return `${columnName} > '${formattedValue}'`;
581600
case DateCondition.IS_AFTER_OR_EQUAL:
582-
return `${columnName} >= '${value}'`;
601+
return `${columnName} >= '${formattedValue}'`;
583602
case DateCondition.IS_NULL:
584603
return `${columnName} == null`;
585604
case DateCondition.IS_NOT_NULL:
@@ -684,7 +703,7 @@ export function getFormatColumns(
684703
FormatterType.CONDITIONAL
685704
? columnFormatConfigMap.get(col.name)
686705
: rowFormatConfig) ?? ['null', undefined];
687-
const rule = makeTernaryFormatRule(config, prevRule);
706+
const rule = makeTernaryFormatRule(dh, config, prevRule);
688707
if (rule === undefined) {
689708
log.debug(`Ignoring format rule.`, config);
690709
return;
@@ -745,16 +764,6 @@ export function isDateConditionValid(
745764
return false;
746765
}
747766

748-
// The backend timestamp parsing does not support timezones that end with ST or DT (e.g. EST, EDT)
749-
// Passing these to the backend will cause the table to fail.
750-
if (
751-
tzCode.toUpperCase().endsWith('ST') ||
752-
tzCode.toUpperCase().endsWith('DT')
753-
) {
754-
log.debug('Timezone ending with ST or DT not supported', tzCode);
755-
return false;
756-
}
757-
758767
return true;
759768
}
760769
}

0 commit comments

Comments
 (0)