Web console: make retention dialog clearer (#14793)

* make retention dialog clearer

* tweak

* another tweak

* Update web-console/src/dialogs/retention-dialog/retention-dialog.tsx

Co-authored-by: Suneet Saldanha <suneet@apache.org>

* update snapshot for copy

---------

Co-authored-by: Suneet Saldanha <suneet@apache.org>
This commit is contained in:
Vadim Ogievetsky 2023-08-11 09:43:00 -07:00 committed by GitHub
parent a0234c4e13
commit b0c78ff295
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 363 additions and 68 deletions

View File

@ -66,7 +66,7 @@ interface InternalValue {
interface JsonInputProps {
value: any;
onChange: (value: any) => void;
onChange?: (value: any) => void;
setError?: (error: Error | undefined) => void;
placeholder?: string;
focus?: boolean;
@ -123,7 +123,7 @@ export const JsonInput = React.memo(function JsonInput(props: JsonInputProps) {
setError?.(error);
if (!error) {
onChange(value);
onChange?.(value);
}
if (showErrorIfNeeded) {
@ -131,6 +131,7 @@ export const JsonInput = React.memo(function JsonInput(props: JsonInputProps) {
}
}}
onBlur={() => setShowErrorIfNeeded(true)}
readOnly={!onChange}
focus={focus}
fontSize={12}
width={width || '100%'}

View File

@ -42,22 +42,23 @@ const PERIOD_SUGGESTIONS: string[] = ['P1D', 'P7D', 'P1M', 'P1Y', 'P1000Y'];
export interface RuleEditorProps {
rule: Rule;
tiers: string[];
onChange(newRule: Rule): void;
onDelete(): void;
moveUp: (() => void) | undefined;
moveDown: (() => void) | undefined;
onChange?: (newRule: Rule) => void;
onDelete?: () => void;
moveUp?: () => void;
moveDown?: () => void;
}
export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps) {
const { rule, onChange, tiers, onDelete, moveUp, moveDown } = props;
const [isOpen, setIsOpen] = useState(true);
const disabled = !onChange;
function removeTier(key: string) {
const newTierReplicants = { ...rule.tieredReplicants };
delete newTierReplicants[key];
const newRule = { ...rule, tieredReplicants: newTierReplicants };
onChange(newRule);
onChange?.(newRule);
}
function addTier() {
@ -72,7 +73,7 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
}
}
onChange(RuleUtil.addTieredReplicant(rule, newTierName, 1));
onChange?.(RuleUtil.addTieredReplicant(rule, newTierName, 1));
}
function renderTiers() {
@ -90,14 +91,15 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
<FormGroup>
{tieredReplicantsList.map(([tier, replication]) => (
<ControlGroup key={tier}>
<Button minimal style={{ pointerEvents: 'none' }}>
<Button minimal disabled={disabled} style={{ pointerEvents: 'none' }}>
Tier:
</Button>
<HTMLSelect
fill
value={tier}
disabled={disabled}
onChange={(e: any) =>
onChange(RuleUtil.renameTieredReplicants(rule, tier, e.target.value))
onChange?.(RuleUtil.renameTieredReplicants(rule, tier, e.target.value))
}
>
<option key={tier} value={tier}>
@ -111,19 +113,20 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
</option>
))}
</HTMLSelect>
<Button minimal style={{ pointerEvents: 'none' }}>
<Button minimal disabled={disabled} style={{ pointerEvents: 'none' }}>
Replicants:
</Button>
<NumericInput
value={replication}
disabled={disabled}
onValueChange={(v: number) => {
if (isNaN(v)) return;
onChange(RuleUtil.addTieredReplicant(rule, tier, v));
onChange?.(RuleUtil.addTieredReplicant(rule, tier, v));
}}
min={0}
max={256}
/>
<Button onClick={() => removeTier(tier)} icon={IconNames.TRASH} />
{onChange && <Button onClick={() => removeTier(tier)} icon={IconNames.TRASH} />}
</ControlGroup>
))}
</FormGroup>
@ -131,7 +134,7 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
}
function renderTierAdder() {
const { rule, tiers } = props;
if (!onChange) return;
const disabled = Object.keys(rule.tieredReplicants || {}).length >= Object.keys(tiers).length;
return (
@ -163,7 +166,7 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
<div className="spacer" />
{moveUp && <Button minimal icon={IconNames.ARROW_UP} onClick={moveUp} />}
{moveDown && <Button minimal icon={IconNames.ARROW_DOWN} onClick={moveDown} />}
<Button minimal icon={IconNames.TRASH} onClick={onDelete} />
{onDelete && <Button minimal icon={IconNames.TRASH} onClick={onDelete} />}
</div>
<Collapse isOpen={isOpen}>
@ -172,7 +175,8 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
<ControlGroup>
<HTMLSelect
value={rule.type}
onChange={(e: any) => onChange(RuleUtil.changeRuleType(rule, e.target.value))}
disabled={disabled}
onChange={(e: any) => onChange?.(RuleUtil.changeRuleType(rule, e.target.value))}
>
{RuleUtil.TYPES.map(type => (
<option key={type} value={type}>
@ -184,9 +188,10 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
<SuggestibleInput
value={rule.period || ''}
sanitizer={durationSanitizer}
disabled={disabled}
onValueChange={period => {
if (typeof period === 'undefined') return;
onChange(RuleUtil.changePeriod(rule, period));
onChange?.(RuleUtil.changePeriod(rule, period));
}}
placeholder={PERIOD_SUGGESTIONS[0]}
suggestions={PERIOD_SUGGESTIONS}
@ -197,15 +202,17 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
className="include-future"
checked={rule.includeFuture || false}
label="Include future"
disabled={disabled}
onChange={() => {
onChange(RuleUtil.changeIncludeFuture(rule, !rule.includeFuture));
onChange?.(RuleUtil.changeIncludeFuture(rule, !rule.includeFuture));
}}
/>
)}
{RuleUtil.hasInterval(rule) && (
<InputGroup
value={rule.interval || ''}
onChange={(e: any) => onChange(RuleUtil.changeInterval(rule, e.target.value))}
readOnly={!onChange}
onChange={(e: any) => onChange?.(RuleUtil.changeInterval(rule, e.target.value))}
placeholder="2010-01-01/2020-01-01"
/>
)}

View File

@ -580,31 +580,325 @@ exports[`RetentionDialog matches snapshot 1`] = `
<div
class="bp4-form-group"
>
<label
class="bp4-label"
>
Cluster defaults (
<a>
edit
</a>
)
<span
class="bp4-text-muted"
/>
</label>
<div
class="bp4-form-content"
>
<p>
Cluster defaults (
<a>
edit
</a>
):
The cluster default rules are evaluated if none of the above rules match.
</p>
<div
class="default-rule"
class="rule-editor"
>
<button
class="bp4-button bp4-disabled"
disabled=""
tabindex="-1"
type="button"
<div
class="title"
>
<span
class="bp4-button-text"
<button
class="bp4-button bp4-minimal left"
type="button"
>
loadForever(2x)
</span>
</button>
<span
class="bp4-button-text"
>
loadForever(2x)
</span>
<span
aria-hidden="true"
class="bp4-icon bp4-icon-caret-down"
icon="caret-down"
>
<svg
data-icon="caret-down"
height="16"
role="img"
viewBox="0 0 16 16"
width="16"
>
<path
d="M12 6.5c0-.28-.22-.5-.5-.5h-7a.495.495 0 00-.37.83l3.5 4c.09.1.22.17.37.17s.28-.07.37-.17l3.5-4c.08-.09.13-.2.13-.33z"
fill-rule="evenodd"
/>
</svg>
</span>
</button>
<div
class="spacer"
/>
</div>
<div
class="bp4-collapse"
style="height: auto; overflow-y: visible; transition: none;"
>
<div
aria-hidden="false"
class="bp4-collapse-body"
style="transform: translateY(0); transition: none;"
>
<div
class="bp4-card bp4-elevation-2"
>
<div
class="bp4-form-group"
>
<div
class="bp4-form-content"
>
<div
class="bp4-control-group"
>
<div
class="bp4-html-select bp4-disabled"
>
<select
disabled=""
>
<option
value="loadForever"
>
loadForever
</option>
<option
value="loadByInterval"
>
loadByInterval
</option>
<option
value="loadByPeriod"
>
loadByPeriod
</option>
<option
value="dropForever"
>
dropForever
</option>
<option
value="dropByInterval"
>
dropByInterval
</option>
<option
value="dropByPeriod"
>
dropByPeriod
</option>
<option
value="dropBeforeByPeriod"
>
dropBeforeByPeriod
</option>
<option
value="broadcastForever"
>
broadcastForever
</option>
<option
value="broadcastByInterval"
>
broadcastByInterval
</option>
<option
value="broadcastByPeriod"
>
broadcastByPeriod
</option>
</select>
<span
class="bp4-icon bp4-icon-double-caret-vertical"
icon="double-caret-vertical"
>
<svg
aria-labelledby="iconTitle-12"
data-icon="double-caret-vertical"
height="16"
role="img"
viewBox="0 0 16 16"
width="16"
>
<title
id="iconTitle-12"
>
Open dropdown
</title>
<path
d="M5 7h6a1.003 1.003 0 00.71-1.71l-3-3C8.53 2.11 8.28 2 8 2s-.53.11-.71.29l-3 3A1.003 1.003 0 005 7zm6 2H5a1.003 1.003 0 00-.71 1.71l3 3c.18.18.43.29.71.29s.53-.11.71-.29l3-3A1.003 1.003 0 0011 9z"
fill-rule="evenodd"
/>
</svg>
</span>
</div>
</div>
</div>
</div>
<div
class="bp4-form-group"
>
<div
class="bp4-form-content"
>
<div
class="bp4-control-group"
>
<button
class="bp4-button bp4-disabled bp4-minimal"
disabled=""
style="pointer-events: none;"
tabindex="-1"
type="button"
>
<span
class="bp4-button-text"
>
Tier:
</span>
</button>
<div
class="bp4-html-select bp4-disabled bp4-fill"
>
<select
disabled=""
>
<option
value="_default_tier"
>
_default_tier
</option>
</select>
<span
class="bp4-icon bp4-icon-double-caret-vertical"
icon="double-caret-vertical"
>
<svg
aria-labelledby="iconTitle-13"
data-icon="double-caret-vertical"
height="16"
role="img"
viewBox="0 0 16 16"
width="16"
>
<title
id="iconTitle-13"
>
Open dropdown
</title>
<path
d="M5 7h6a1.003 1.003 0 00.71-1.71l-3-3C8.53 2.11 8.28 2 8 2s-.53.11-.71.29l-3 3A1.003 1.003 0 005 7zm6 2H5a1.003 1.003 0 00-.71 1.71l3 3c.18.18.43.29.71.29s.53-.11.71-.29l3-3A1.003 1.003 0 0011 9z"
fill-rule="evenodd"
/>
</svg>
</span>
</div>
<button
class="bp4-button bp4-disabled bp4-minimal"
disabled=""
style="pointer-events: none;"
tabindex="-1"
type="button"
>
<span
class="bp4-button-text"
>
Replicants:
</span>
</button>
<div
class="bp4-control-group bp4-numeric-input"
>
<div
class="bp4-input-group bp4-disabled"
>
<input
aria-valuemax="256"
aria-valuemin="0"
aria-valuenow="2"
autocomplete="off"
class="bp4-input"
disabled=""
id="numericInput-1"
max="256"
min="0"
role="spinbutton"
type="text"
value="2"
/>
</div>
<div
class="bp4-button-group bp4-vertical bp4-fixed"
>
<button
aria-controls="numericInput-1"
aria-label="increment"
class="bp4-button bp4-disabled"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-hidden="true"
class="bp4-icon bp4-icon-chevron-up"
icon="chevron-up"
>
<svg
data-icon="chevron-up"
height="16"
role="img"
viewBox="0 0 16 16"
width="16"
>
<path
d="M12.71 9.29l-4-4C8.53 5.11 8.28 5 8 5s-.53.11-.71.29l-4 4a1.003 1.003 0 001.42 1.42L8 7.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71z"
fill-rule="evenodd"
/>
</svg>
</span>
</button>
<button
aria-controls="numericInput-1"
aria-label="decrement"
class="bp4-button bp4-disabled"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-hidden="true"
class="bp4-icon bp4-icon-chevron-down"
icon="chevron-down"
>
<svg
data-icon="chevron-down"
height="16"
role="img"
viewBox="0 0 16 16"
width="16"
>
<path
d="M12 5c-.28 0-.53.11-.71.29L8 8.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42l4 4c.18.18.43.29.71.29s.53-.11.71-.29l4-4A1.003 1.003 0 0012 5z"
fill-rule="evenodd"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -45,9 +45,5 @@
padding: 0 15px;
}
}
.default-rule {
margin-top: 10px;
}
}
}

View File

@ -28,7 +28,6 @@ import { getLink } from '../../links';
import { Api } from '../../singletons';
import { filterMap, queryDruidSql, swapElements } from '../../utils';
import type { Rule } from '../../utils/load-rule';
import { RuleUtil } from '../../utils/load-rule';
import { SnitchDialog } from '..';
import './retention-dialog.scss';
@ -115,28 +114,6 @@ ORDER BY 1`,
setCurrentRules(swapElements(currentRules, index, index + direction));
}
function renderRule(rule: Rule, index: number) {
return (
<RuleEditor
rule={rule}
tiers={tiers}
key={index}
onChange={r => changeRule(r, index)}
onDelete={() => deleteRule(index)}
moveUp={index > 0 ? () => moveRule(index, -1) : undefined}
moveDown={index < currentRules.length - 1 ? () => moveRule(index, 1) : undefined}
/>
);
}
function renderDefaultRule(rule: Rule, index: number) {
return (
<div className="default-rule" key={index}>
<Button disabled>{RuleUtil.ruleToString(rule)}</Button>
</div>
);
}
return (
<SnitchDialog
className="retention-dialog"
@ -167,7 +144,17 @@ ORDER BY 1`,
{currentTab === 'form' ? (
<FormGroup>
{currentRules.length ? (
currentRules.map(renderRule)
currentRules.map((rule, index) => (
<RuleEditor
key={index}
rule={rule}
tiers={tiers}
onChange={r => changeRule(r, index)}
onDelete={() => deleteRule(index)}
moveUp={index > 0 ? () => moveRule(index, -1) : undefined}
moveDown={index < currentRules.length - 1 ? () => moveRule(index, 1) : undefined}
/>
))
) : datasource !== CLUSTER_DEFAULT_FAKE_DATASOURCE ? (
<p className="no-rules-message">
This datasource currently has no rules, it will use the cluster defaults.
@ -194,11 +181,21 @@ ORDER BY 1`,
{datasource !== CLUSTER_DEFAULT_FAKE_DATASOURCE && (
<>
<Divider />
<FormGroup>
<p>
Cluster defaults (<a onClick={onEditDefaults}>edit</a>):
</p>
{defaultRules.map(renderDefaultRule)}
<FormGroup
label={
<>
Cluster defaults (<a onClick={onEditDefaults}>edit</a>)
</>
}
>
<p>The cluster default rules are evaluated if none of the above rules match.</p>
{currentTab === 'form' ? (
defaultRules.map((rule, index) => (
<RuleEditor key={index} rule={rule} tiers={tiers} />
))
) : (
<JsonInput value={defaultRules} />
)}
</FormGroup>
</>
)}