blob: a431ce6875971418d139715ae85618f1b0845e37 [file] [log] [blame]
/*
* 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 * as React from 'react';
import axios from 'axios';
import { Button, InputGroup, Collapse } from "@blueprintjs/core";
import { IconNames, FormGroup, HTMLSelect, Card, ControlGroup, NumericInput, TagInput } from "../components/filler";
import './rule-editor.scss';
export interface Rule {
type: 'loadForever' | 'loadByInterval' | 'loadByPeriod' | 'dropForever' | 'dropByInterval' | 'dropByPeriod' | 'broadcastForever' | 'broadcastByInterval' | 'broadcastByPeriod';
interval?: string;
period?: string;
tieredReplicants?: Record<string, number>;
colocatedDataSources?: string[];
}
export type LoadType = 'load' | 'drop' | 'broadcast';
export type TimeType = 'Forever' | 'ByInterval' | 'ByPeriod';
export interface RuleEditorProps extends React.Props<any> {
rule: Rule;
tiers: any[];
onChange: (newRule: Rule) => void;
onDelete: () => void;
moveUp: (() => void) | null;
moveDown: (() => void) | null;
}
export interface RuleEditorState {
isOpen: boolean;
}
export class RuleEditor extends React.Component<RuleEditorProps, RuleEditorState> {
static ruleToString(rule: Rule): string {
return rule.type + (rule.period ? `(${rule.period})` : '') + (rule.interval ? `(${rule.interval})` : '');
}
static getLoadType(rule: Rule): LoadType {
const m = rule.type.match(/^(load|drop|broadcast)(\w+)$/);
if (!m) throw new Error(`unknown rule type: '${rule.type}'`);
return m[1] as any;
}
static getTimeType(rule: Rule): TimeType {
const m = rule.type.match(/^(load|drop|broadcast)(\w+)$/);
if (!m) throw new Error(`unknown rule type: '${rule.type}'`);
return m[2] as any;
}
static changeLoadType(rule: Rule, loadType: LoadType): Rule {
const newRule = Object.assign({}, rule, { type: loadType + RuleEditor.getTimeType(rule) });
if (loadType !== 'load') delete newRule.tieredReplicants;
if (loadType !== 'broadcast') delete newRule.colocatedDataSources;
return newRule;
}
static changeTimeType(rule: Rule, timeType: TimeType): Rule {
const newRule = Object.assign({}, rule, { type: RuleEditor.getLoadType(rule) + timeType });
if (timeType !== 'ByPeriod') delete newRule.period;
if (timeType !== 'ByInterval') delete newRule.interval;
return newRule;
}
static changePeriod(rule: Rule, period: string): Rule {
return Object.assign({}, rule, { period });
}
static changeInterval(rule: Rule, interval: string): Rule {
return Object.assign({}, rule, { interval });
}
static changeTier(rule: Rule, oldTier: string, newTier: string): Rule {
const newRule = Object.assign({}, rule);
newRule.tieredReplicants = Object.assign({}, newRule.tieredReplicants);
newRule.tieredReplicants[newTier] = newRule.tieredReplicants[oldTier];
delete newRule.tieredReplicants[oldTier];
return newRule;
}
static changeTierReplication(rule: Rule, tier: string, replication: number): Rule {
const newRule = Object.assign({}, rule);
newRule.tieredReplicants = Object.assign({}, newRule.tieredReplicants, { [tier]: replication });
return newRule;
}
static changeColocatedDataSources(rule: Rule, colocatedDataSources: string[]): Rule {
return Object.assign({}, rule, { colocatedDataSources });
}
constructor(props: RuleEditorProps) {
super(props);
this.state = {
isOpen: true
}
}
private removeTier = (key: string) => {
const { rule, onChange } = this.props;
const newTierReplicants = Object.assign({}, rule.tieredReplicants);
delete newTierReplicants[key];
const newRule = Object.assign({}, rule, {tieredReplicants: newTierReplicants});
onChange(newRule);
}
private addTier = () => {
const { rule, onChange, tiers } = this.props;
let newTierName = tiers[0];
if (rule.tieredReplicants) {
for (let i = 0; i < tiers.length; i++) {
if (rule.tieredReplicants[tiers[i]] === undefined) {
newTierName = tiers[i];
break;
}
}
}
onChange(RuleEditor.changeTierReplication(rule, newTierName, 1));
}
renderTiers() {
const { tiers, onChange, rule } = this.props;
if (!rule) return null;
if (RuleEditor.getLoadType(rule) !== 'load') return null;
const tieredReplicants = rule.tieredReplicants;
if (!tieredReplicants) return null;
const ruleTiers = Object.keys(tieredReplicants).sort();
return ruleTiers.map(tier => {
return <ControlGroup key={tier}>
<Button className="pt-minimal" style={{pointerEvents: 'none'}}>Replicants:</Button>
<NumericInput
value={tieredReplicants[tier]}
onValueChange={(v: number) => {
if (isNaN(v)) return;
onChange(RuleEditor.changeTierReplication(rule, tier, v));
}}
min={1}
max={256}
/>
<Button className="pt-minimal" style={{pointerEvents: 'none'}}>Tier:</Button>
<HTMLSelect
fill={true}
value={tier}
onChange={(e: any) => onChange(RuleEditor.changeTier(rule, tier, e.target.value))}
>
{
tiers.filter(t => t === tier || !tieredReplicants[t]).map((t) => {
return <option key={t} value={t}>{t}</option>;
})
}
</HTMLSelect>
<Button
disabled={ruleTiers.length === 1}
onClick={() => this.removeTier(tier)}
iconName={IconNames.TRASH}
/>
</ControlGroup>;
});
}
renderTierAdder() {
const { rule, tiers } = this.props;
if (Object.keys(rule.tieredReplicants || {}).length >= Object.keys(tiers).length) return null;
return <FormGroup className="right">
<Button onClick={this.addTier} className="pt-minimal" iconName={IconNames.PLUS}>Add a tier</Button>
</FormGroup>;
}
renderColocatedDataSources() {
const { rule, onChange } = this.props;
return <FormGroup label="Colocated datasources:">
<TagInput
values={rule.colocatedDataSources || []}
onChange={(v: any) => onChange(RuleEditor.changeColocatedDataSources(rule, v)) }
fill
/>
</FormGroup>;
}
render() {
const { tiers, onChange, rule, onDelete, moveUp, moveDown } = this.props;
const { isOpen } = this.state;
if (!rule) return null;
const ruleLoadType = RuleEditor.getLoadType(rule);
const ruleTimeType = RuleEditor.getTimeType(rule);
return <div className="rule-editor">
<div className="title">
<Button className="left pt-minimal" rightIconName={isOpen ? IconNames.CARET_DOWN : IconNames.CARET_RIGHT} onClick={() => this.setState({isOpen: !isOpen})}>
{RuleEditor.ruleToString(rule)}
</Button>
<div className="spacer"/>
{moveUp ? <Button className="pt-minimal" iconName={IconNames.ARROW_UP} onClick={moveUp}/> : null}
{moveDown ? <Button className="pt-minimal" iconName={IconNames.ARROW_DOWN} onClick={moveDown}/> : null}
<Button className="pt-minimal" iconName={IconNames.TRASH} onClick={onDelete}/>
</div>
<Collapse isOpen={isOpen}>
<Card>
<FormGroup>
<ControlGroup>
<HTMLSelect
value={ruleLoadType}
onChange={(e: any) => onChange(RuleEditor.changeLoadType(rule, e.target.value as any))}
>
<option value="load">Load</option>
<option value="drop">Drop</option>
<option value="broadcast">Broadcast</option>
</HTMLSelect>
<HTMLSelect
value={ruleTimeType}
onChange={(e: any) => onChange(RuleEditor.changeTimeType(rule, e.target.value as any))}
>
<option value="Forever">forever</option>
<option value="ByPeriod">by period</option>
<option value="ByInterval">by interval</option>
</HTMLSelect>
{ ruleTimeType === 'ByPeriod' && <InputGroup value={rule.period || ''} onChange={(e: any) => onChange(RuleEditor.changePeriod(rule, e.target.value as any))}/>}
{ ruleTimeType === 'ByInterval' && <InputGroup value={rule.interval || ''} onChange={(e: any) => onChange(RuleEditor.changeInterval(rule, e.target.value as any))}/>}
</ControlGroup>
</FormGroup>
{
ruleLoadType === 'load' &&
<FormGroup>
{ this.renderTiers() }
{ this.renderTierAdder() }
</FormGroup>
}
{
ruleLoadType === 'broadcast' &&
<FormGroup>
{ this.renderColocatedDataSources() }
</FormGroup>
}
</Card>
</Collapse>
</div>;
}
}