Web console: Improve retention rules dialog in all sorts of ways (#10226)

* improve ret rules

* tidy up tests
This commit is contained in:
Vadim Ogievetsky 2020-08-04 16:43:31 -07:00 committed by GitHub
parent 4bb198eb4c
commit 9a29496b6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 573 additions and 67 deletions

View File

@ -21,8 +21,6 @@
text-align: right;
}
margin-bottom: 20px;
.title {
display: flex;

View File

@ -31,9 +31,12 @@ import { IconNames } from '@blueprintjs/icons';
import React, { useState } from 'react';
import { Rule, RuleUtil } from '../../utils/load-rule';
import { SuggestibleInput } from '../suggestible-input/suggestible-input';
import './rule-editor.scss';
const PERIOD_SUGGESTIONS: string[] = ['P1D', 'P7D', 'P1M', 'P1Y', 'P1000Y'];
export interface RuleEditorProps {
rule: Rule;
tiers: any[];
@ -46,7 +49,6 @@ export interface RuleEditorProps {
export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps) {
const { rule, onChange, tiers, onDelete, moveUp, moveDown } = props;
const [isOpen, setIsOpen] = useState(true);
if (!rule) return null;
function removeTier(key: string) {
const newTierReplicants = Object.assign({}, rule.tieredReplicants);
@ -173,12 +175,16 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
})}
</HTMLSelect>
{RuleUtil.hasPeriod(rule) && (
<InputGroup
<SuggestibleInput
value={rule.period || ''}
onChange={(e: any) =>
onChange(RuleUtil.changePeriod(rule, e.target.value as any))
}
placeholder="P1D"
onValueChange={period => {
if (typeof period === 'undefined') return;
// Ensure the period is upper case and does not contain anytihng but the allowed chars
period = period.toUpperCase().replace(/[^PYMDTHS0-9]/g, '');
onChange(RuleUtil.changePeriod(rule, period));
}}
placeholder={PERIOD_SUGGESTIONS[0]}
suggestions={PERIOD_SUGGESTIONS}
/>
)}
{RuleUtil.hasIncludeFuture(rule) && (

View File

@ -23,7 +23,7 @@ exports[`retention dialog matches snapshot 1`] = `
<h4
class="bp3-heading"
>
Edit retention rules: test
Edit retention rules: test-datasource
</h4>
<button
aria-label="Close"
@ -71,41 +71,498 @@ exports[`retention dialog matches snapshot 1`] = `
>
<div
class="bp3-form-content"
/>
>
<div
class="rule-editor"
>
<div
class="title"
>
<button
class="bp3-button bp3-minimal left"
type="button"
>
<span
class="bp3-button-text"
>
loadByPeriod(P1000Y+future)
</span>
<span
class="bp3-icon bp3-icon-caret-down"
icon="caret-down"
>
<svg
data-icon="caret-down"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
caret-down
</desc>
<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"
/>
<button
class="bp3-button bp3-minimal"
type="button"
>
<span
class="bp3-icon bp3-icon-trash"
icon="trash"
>
<svg
data-icon="trash"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
trash
</desc>
<path
d="M14.49 3.99h-13c-.28 0-.5.22-.5.5s.22.5.5.5h.5v10c0 .55.45 1 1 1h10c.55 0 1-.45 1-1v-10h.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5zm-8.5 9c0 .55-.45 1-1 1s-1-.45-1-1v-6c0-.55.45-1 1-1s1 .45 1 1v6zm3 0c0 .55-.45 1-1 1s-1-.45-1-1v-6c0-.55.45-1 1-1s1 .45 1 1v6zm3 0c0 .55-.45 1-1 1s-1-.45-1-1v-6c0-.55.45-1 1-1s1 .45 1 1v6zm2-12h-4c0-.55-.45-1-1-1h-2c-.55 0-1 .45-1 1h-4c-.55 0-1 .45-1 1v1h14v-1c0-.55-.45-1-1-1z"
fill-rule="evenodd"
/>
</svg>
</span>
</button>
</div>
<div
class="bp3-collapse"
style="height: auto; overflow-y: visible; transition: none;"
>
<div
aria-hidden="false"
class="bp3-collapse-body"
style="transform: translateY(0); transition: none;"
>
<div
class="bp3-card bp3-elevation-0"
>
<div
class="bp3-form-group"
>
<div
class="bp3-form-content"
>
<div
class="bp3-control-group"
>
<div
class="bp3-html-select"
>
<select>
<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="bp3-icon bp3-icon-double-caret-vertical"
icon="double-caret-vertical"
>
<svg
data-icon="double-caret-vertical"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
double-caret-vertical
</desc>
<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
class="bp3-input-group suggestible-input"
>
<input
class="bp3-input"
placeholder="P1D"
style="padding-right: 0px;"
suggestions="P1D,P7D,P1M,P1Y,P1000Y"
type="text"
value="P1000Y"
/>
<span
class="bp3-input-action"
>
<span
class="bp3-popover-wrapper"
>
<span
class="bp3-popover-target"
>
<button
class="bp3-button bp3-minimal"
type="button"
>
<span
class="bp3-icon bp3-icon-caret-down"
icon="caret-down"
>
<svg
data-icon="caret-down"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
caret-down
</desc>
<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>
</span>
</span>
</span>
</div>
<label
class="bp3-control bp3-switch include-future"
>
<input
checked=""
type="checkbox"
/>
<span
class="bp3-control-indicator"
/>
Include future
</label>
</div>
</div>
</div>
<div
class="bp3-form-group"
>
<div
class="bp3-form-content"
>
<div
class="bp3-control-group"
>
<button
class="bp3-button bp3-minimal"
style="pointer-events: none;"
type="button"
>
<span
class="bp3-button-text"
>
Replicants:
</span>
</button>
<div
class="bp3-control-group bp3-numeric-input"
>
<div
class="bp3-input-group"
>
<input
autocomplete="off"
class="bp3-input"
max="256"
min="1"
style="padding-right: 10px;"
type="text"
value="2"
/>
</div>
<div
class="bp3-button-group bp3-vertical bp3-fixed"
>
<button
class="bp3-button"
type="button"
>
<span
class="bp3-icon bp3-icon-chevron-up"
icon="chevron-up"
>
<svg
data-icon="chevron-up"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
chevron-up
</desc>
<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
class="bp3-button"
type="button"
>
<span
class="bp3-icon bp3-icon-chevron-down"
icon="chevron-down"
>
<svg
data-icon="chevron-down"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
chevron-down
</desc>
<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>
<button
class="bp3-button bp3-minimal"
style="pointer-events: none;"
type="button"
>
<span
class="bp3-button-text"
>
Tier:
</span>
</button>
<div
class="bp3-html-select bp3-fill"
>
<select>
<option
value="_default_tier"
>
_default_tier
</option>
<option
value="tier1"
>
tier1
</option>
<option
value="tier2"
>
tier2
</option>
</select>
<span
class="bp3-icon bp3-icon-double-caret-vertical"
icon="double-caret-vertical"
>
<svg
data-icon="double-caret-vertical"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
double-caret-vertical
</desc>
<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="bp3-button bp3-disabled"
disabled=""
tabindex="-1"
type="button"
>
<span
class="bp3-icon bp3-icon-trash"
icon="trash"
>
<svg
data-icon="trash"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
trash
</desc>
<path
d="M14.49 3.99h-13c-.28 0-.5.22-.5.5s.22.5.5.5h.5v10c0 .55.45 1 1 1h10c.55 0 1-.45 1-1v-10h.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5zm-8.5 9c0 .55-.45 1-1 1s-1-.45-1-1v-6c0-.55.45-1 1-1s1 .45 1 1v6zm3 0c0 .55-.45 1-1 1s-1-.45-1-1v-6c0-.55.45-1 1-1s1 .45 1 1v6zm3 0c0 .55-.45 1-1 1s-1-.45-1-1v-6c0-.55.45-1 1-1s1 .45 1 1v6zm2-12h-4c0-.55-.45-1-1-1h-2c-.55 0-1 .45-1 1h-4c-.55 0-1 .45-1 1v1h14v-1c0-.55-.45-1-1-1z"
fill-rule="evenodd"
/>
</svg>
</span>
</button>
</div>
<div
class="bp3-form-group right"
>
<div
class="bp3-form-content"
>
<button
class="bp3-button bp3-minimal"
type="button"
>
<span
class="bp3-icon bp3-icon-plus"
icon="plus"
>
<svg
data-icon="plus"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
plus
</desc>
<path
d="M13 7H9V3c0-.55-.45-1-1-1s-1 .45-1 1v4H3c-.55 0-1 .45-1 1s.45 1 1 1h4v4c0 .55.45 1 1 1s1-.45 1-1V9h4c.55 0 1-.45 1-1s-.45-1-1-1z"
fill-rule="evenodd"
/>
</svg>
</span>
<span
class="bp3-button-text"
>
Add a tier
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<button
class="bp3-button"
type="button"
>
<span
class="bp3-icon bp3-icon-plus"
icon="plus"
>
<svg
data-icon="plus"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
plus
</desc>
<path
d="M13 7H9V3c0-.55-.45-1-1-1s-1 .45-1 1v4H3c-.55 0-1 .45-1 1s.45 1 1 1h4v4c0 .55.45 1 1 1s1-.45 1-1V9h4c.55 0 1-.45 1-1s-.45-1-1-1z"
fill-rule="evenodd"
/>
</svg>
</span>
<span
class="bp3-button-text"
>
New rule
</span>
</button>
</div>
</div>
</div>
<div
class="bp3-form-group right"
class="bp3-divider"
/>
<div
class="bp3-form-group"
>
<div
class="bp3-form-content"
>
<p>
Cluster defaults (
<a>
edit
</a>
):
</p>
<button
class="bp3-button"
class="bp3-button bp3-disabled"
disabled=""
tabindex="-1"
type="button"
>
<span
class="bp3-icon bp3-icon-plus"
icon="plus"
>
<svg
data-icon="plus"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
plus
</desc>
<path
d="M13 7H9V3c0-.55-.45-1-1-1s-1 .45-1 1v4H3c-.55 0-1 .45-1 1s.45 1 1 1h4v4c0 .55.45 1 1 1s1-.45 1-1V9h4c.55 0 1-.45 1-1s-.45-1-1-1z"
fill-rule="evenodd"
/>
</svg>
</span>
<span
class="bp3-button-text"
>
New rule
loadForever
</span>
</button>
</div>

View File

@ -22,16 +22,13 @@
width: 750px;
}
.dialog-body {
overflow: scroll;
max-height: 70vh;
.form-group {
margin: 0 0 5px;
.bp3-dialog-body {
.rule-editor {
margin-bottom: 15px;
}
.small {
width: 0px;
.no-rules-message {
font-style: italic;
}
.comment {

View File

@ -25,9 +25,17 @@ describe('retention dialog', () => {
it('matches snapshot', () => {
const retentionDialog = (
<RetentionDialog
datasource={'test'}
rules={[null]}
tiers={['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']}
datasource={'test-datasource'}
rules={[
{
period: 'P1000Y',
includeFuture: true,
tieredReplicants: { _default_tier: 2 },
type: 'loadByPeriod',
},
]}
defaultRules={[{ tieredReplicants: { _default_tier: 2 }, type: 'loadForever' }]}
tiers={['tier1', 'tier2']}
onEditDefaults={() => {}}
onCancel={() => {}}
onSave={() => {}}

View File

@ -16,7 +16,7 @@
* limitations under the License.
*/
import { Button, FormGroup } from '@blueprintjs/core';
import { Button, Divider, FormGroup } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import axios from 'axios';
import React from 'react';
@ -25,6 +25,7 @@ import { SnitchDialog } from '..';
import { ExternalLink, RuleEditor } from '../../components';
import { getLink } from '../../links';
import { QueryManager } from '../../utils';
import { Rule, RuleUtil } from '../../utils/load-rule';
import './retention-dialog.scss';
@ -40,23 +41,24 @@ export function reorderArray<T>(items: T[], oldIndex: number, newIndex: number):
export interface RetentionDialogProps {
datasource: string;
rules: any[];
rules: Rule[];
defaultRules: Rule[];
tiers: string[];
onEditDefaults: () => void;
onCancel: () => void;
onSave: (datasource: string, newRules: any[], comment: string) => void;
onSave: (datasource: string, newRules: Rule[], comment: string) => void;
}
export interface RetentionDialogState {
currentRules: any[];
historyRecords: any[];
currentRules: Rule[];
historyRecords: any[] | undefined;
}
export class RetentionDialog extends React.PureComponent<
RetentionDialogProps,
RetentionDialogState
> {
private historyQueryManager: QueryManager<string, any>;
private historyQueryManager: QueryManager<string, any[]>;
constructor(props: RetentionDialogProps) {
super(props);
@ -91,7 +93,7 @@ export class RetentionDialog extends React.PureComponent<
onSave(datasource, currentRules, comment);
};
private changeRule = (newRule: any, index: number) => {
private changeRule = (newRule: Rule, index: number) => {
const { currentRules } = this.state;
const newRules = (currentRules || []).map((r, i) => {
@ -125,7 +127,7 @@ export class RetentionDialog extends React.PureComponent<
});
}
renderRule = (rule: any, index: number) => {
renderRule = (rule: Rule, index: number) => {
const { tiers } = this.props;
const { currentRules } = this.state;
@ -142,6 +144,14 @@ export class RetentionDialog extends React.PureComponent<
);
};
renderDefaultRule = (rule: Rule, index: number) => {
return (
<Button disabled key={index}>
{RuleUtil.ruleToString(rule)}
</Button>
);
};
reset = () => {
const { rules } = this.props;
@ -165,7 +175,7 @@ export class RetentionDialog extends React.PureComponent<
};
render(): JSX.Element {
const { datasource, onCancel, onEditDefaults } = this.props;
const { datasource, onCancel, onEditDefaults, defaultRules } = this.props;
const { currentRules, historyRecords } = this.state;
return (
@ -188,17 +198,32 @@ export class RetentionDialog extends React.PureComponent<
</ExternalLink>
.
</p>
<FormGroup>{(currentRules || []).map(this.renderRule)}</FormGroup>
<FormGroup className="right">
<Button icon={IconNames.PLUS} onClick={this.addRule}>
New rule
</Button>
<FormGroup>
{currentRules.length ? (
currentRules.map(this.renderRule)
) : datasource !== '_default' ? (
<p className="no-rules-message">
This datasource currently has no rules, it will use the cluster defaults.
</p>
) : (
undefined
)}
<div>
<Button icon={IconNames.PLUS} onClick={this.addRule}>
New rule
</Button>
</div>
</FormGroup>
{!currentRules.length && datasource !== '_default' && (
<p>
This datasource currently has no rules, it will use the cluster defaults (
<a onClick={onEditDefaults}>edit cluster defaults</a>)
</p>
{datasource !== '_default' && (
<>
<Divider />
<FormGroup>
<p>
Cluster defaults (<a onClick={onEditDefaults}>edit</a>):
</p>
{defaultRules.map(this.renderDefaultRule)}
</FormGroup>
</>
)}
</SnitchDialog>
);

View File

@ -21,6 +21,15 @@ exports[`data source view matches snapshot 1`] = `
shouldDismissPopover={true}
text="View SQL query for table"
/>
<Blueprint3.MenuItem
disabled={false}
icon="edit"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Edit default retention rules"
/>
</Memo(MoreButton)>
<Blueprint3.Switch
checked={false}

View File

@ -53,7 +53,7 @@ import {
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
import { Capabilities, CapabilitiesMode } from '../../utils/capabilities';
import { RuleUtil } from '../../utils/load-rule';
import { Rule, RuleUtil } from '../../utils/load-rule';
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
import { deepGet } from '../../utils/object-change';
@ -107,7 +107,7 @@ function formatLoadDrop(segmentsToLoad: number, segmentsToDrop: number): string
interface Datasource {
datasource: string;
rules: any[];
rules: Rule[];
[key: string]: any;
}
@ -125,7 +125,7 @@ interface DatasourceQueryResultRow {
interface RetentionDialogOpenOn {
datasource: string;
rules: any[];
rules: Rule[];
}
interface CompactionDialogOpenOn {
@ -145,7 +145,7 @@ export interface DatasourcesViewState {
datasourcesLoading: boolean;
datasources: Datasource[] | null;
tiers: string[];
defaultRules: any[];
defaultRules: Rule[];
datasourcesError?: string;
datasourceFilter: Filter[];
@ -497,6 +497,11 @@ GROUP BY 1`;
onClick={() => goToQuery(DatasourcesView.DATASOURCE_SQL)}
/>
)}
<MenuItem
icon={IconNames.EDIT}
text="Edit default retention rules"
onClick={this.editDefaultRules}
/>
</MoreButton>
);
}
@ -700,13 +705,14 @@ GROUP BY 1`;
}
renderRetentionDialog() {
const { retentionDialogOpenOn, tiers } = this.state;
const { retentionDialogOpenOn, tiers, defaultRules } = this.state;
if (!retentionDialogOpenOn) return null;
return (
<RetentionDialog
datasource={retentionDialogOpenOn.datasource}
rules={retentionDialogOpenOn.rules}
defaultRules={defaultRules}
tiers={tiers}
onEditDefaults={this.editDefaultRules}
onCancel={() => this.setState({ retentionDialogOpenOn: undefined })}