Skip to content

Commit

Permalink
Added functionality to run recursive calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
juliarobles committed Sep 5, 2024
1 parent 33c80bb commit e61b75f
Show file tree
Hide file tree
Showing 17 changed files with 745 additions and 214 deletions.
55 changes: 55 additions & 0 deletions src/components/editors/extraCalcEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react'
import { StandardEditorProps } from "@grafana/data";
import { IExtraCalc } from 'utils/types';
import { ControlledCollapse, useTheme2 } from '@grafana/ui';
import { Mode } from 'utils/constants';
import { ExtraCalcDefault } from 'utils/default';
import { css } from '@emotion/css';
import { ExtraCalcForm } from './extraCalcForm';


interface Props extends StandardEditorProps<IExtraCalc[]> { }

export const ExtraCalcEditor: React.FC<Props> = ({ value: elements, onChange, context }) => {

if (!elements || !elements.length) {
elements = [];
}

const addElement = (newExtraCalc: IExtraCalc) => {
const updated = [...elements, newExtraCalc]
onChange(updated)
}

const updateElement = (idx: number, extraCalcUpdated: IExtraCalc) => {
const updated = [...elements]
updated[idx] = extraCalcUpdated
onChange(updated)
}

const deleteElement = (idx: number) => {
const updated = [...elements]
updated.splice(idx, 1)
onChange(updated)
}

const listExtraCalcs = elements.map((element: IExtraCalc, idx: number) => {
return <div>
<ControlledCollapse label={element.id} collapsible>
<ExtraCalcForm extraCalc={element}
mode={Mode.EDIT}
updateFunction={(m: IExtraCalc) => updateElement(idx, m)}
deleteFunction={() => deleteElement(idx)}
context={context} />
</ControlledCollapse>
</div>
})

return (<div style={{ marginRight: '15px' }}>
<p>This is an <b>optional</b> feature. For now only recursive calculations are allowed.</p>
{listExtraCalcs}
<ControlledCollapse label="Add new extra calculation" collapsible isOpen={false} className={css({ color: useTheme2().colors.info.main })}>
<ExtraCalcForm extraCalc={ExtraCalcDefault} updateFunction={addElement} mode={Mode.CREATE} context={context} />
</ControlledCollapse>
</div>)
}
181 changes: 181 additions & 0 deletions src/components/editors/extraCalcForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import React, { ChangeEvent, useEffect, useState } from 'react'
import { SelectableValue, StandardEditorContext } from "@grafana/data";
import { Calc, ExtraCalcFormat, IExtraCalc, ISelect } from 'utils/types';
import { Alert, Button, Collapse, ConfirmButton, Form, FormAPI, HorizontalGroup, InlineField, Input, InputControl, Select, useTheme2 } from '@grafana/ui';
import { ExtraCalcDefault } from 'utils/default';
import { Mode } from 'utils/constants';
import { enumToSelect } from 'utils/utils';
import { css } from '@emotion/css';

interface Props {
extraCalc: IExtraCalc,
updateFunction: any,
deleteFunction?: any,
mode: Mode,
context: StandardEditorContext<any, any>
}

export const ExtraCalcForm: React.FC<Props> = ({ extraCalc, updateFunction, deleteFunction, mode, context }) => {

const calcOptions: ISelect[] = enumToSelect(Calc)
const formatOptions: ISelect[] = enumToSelect(ExtraCalcFormat)

const [currentCalc, setCurrentCalc] = useState<IExtraCalc>(ExtraCalcDefault)
const [selectedCalc, setSelectedCalc] = useState<SelectableValue<string>>(calcOptions[0])
const [selectedFormat, setSelectedFormat] = useState<SelectableValue<string>>(formatOptions[0])
const [disabled, setDisabled] = useState(false)

const updateCurrentState = () => {
setCurrentCalc(extraCalc)
setSelectedCalc({value: extraCalc.calc, label: extraCalc.calc as string})
setSelectedFormat({value: extraCalc.resFormat, label: extraCalc.resFormat as string})
}

const handleOnChangeCalc = (event: ChangeEvent<HTMLInputElement>) => {
setCurrentCalc({
...currentCalc,
[event.currentTarget.name]: event.target.value
})
}

const handleOnSubmitAddFormat = () => {
updateFunction({
...currentCalc,
calc: selectedCalc.value,
resFormat: selectedFormat.value
})
if (mode === Mode.EDIT) {
setDisabled(true)
} else {
setCurrentCalc(ExtraCalcDefault)
}
//console.log("SUBMIT ADD")
}

const handleOnClickEdit = () => {
setDisabled(!disabled)
}

const handleOnClickCancel = () => {
updateCurrentState()
setDisabled(true)
//console.log("cancel")
}

const handleOnConfirmDeleteFormat = () => {
if (deleteFunction) deleteFunction()
}

useEffect(() => {
if (mode === Mode.EDIT) setDisabled(true)
}, [mode])

useEffect(() => {
updateCurrentState()
}, [extraCalc])

useEffect(() => {
}, [currentCalc])

const buttonEdit = () => {
if (mode === Mode.CREATE) {
return <div></div>
} else {
return (
<div style={{ marginBottom: '15px', marginRight: '10px' }}>
<HorizontalGroup justify='flex-end'>
<ConfirmButton
closeOnConfirm
confirmText='Delete'
disabled={!disabled}
onConfirm={handleOnConfirmDeleteFormat}
confirmVariant='destructive'
>
<Button variant='destructive' icon='trash-alt' disabled={!disabled} />
</ConfirmButton>
<Button variant='primary' icon='edit' disabled={!disabled} onClick={handleOnClickEdit}>Edit</Button>
</HorizontalGroup>
</div>)
}
}

return <div>
{buttonEdit()}

<Form id="formatForm" onSubmit={handleOnSubmitAddFormat} maxWidth="none">{({ register, errors, control }: FormAPI<any>) => {
return (
<div>
<InlineField label="ID" labelWidth={17} required grow disabled={disabled}>
<Input {...register("id")} value={currentCalc.id} disabled={disabled} onChange={handleOnChangeCalc} required />
</InlineField>
<InlineField label="Name" labelWidth={17} required grow disabled={disabled}>
<Input {...register("name")} value={currentCalc.name} disabled={disabled} onChange={handleOnChangeCalc} required/>
</InlineField>
<InlineField label="Maximum iterations" labelWidth={17} grow disabled={disabled}>
<Input {...register("maxIterations")} value={currentCalc.maxIterations} disabled={disabled} onChange={handleOnChangeCalc} required/>
</InlineField>
<InlineField label="Dynamic field name" labelWidth={17} grow disabled={disabled}>
<Input {...register("dynamicFieldName")} value={currentCalc.dynamicFieldName} disabled={disabled} onChange={handleOnChangeCalc} required/>
</InlineField>
<Collapse label="Recursive calculation" collapsible={false} isOpen={true} className={css({ color: useTheme2().colors.text.primary })}>
<Alert title="Info" severity='info'>
$out = model output<br />$dyn = value of dynamic field<br />$[X] = value of tag X (replace X with the tag desired)
</Alert>
<InlineField label="Initial tag" labelWidth={17} grow required disabled={disabled}>
<Input {...register("tag")} value={currentCalc.tag} disabled={disabled} onChange={handleOnChangeCalc} required/>
</InlineField>
<InlineField label="Calculation" labelWidth={17} required disabled={disabled}>
<InputControl
render={({ field }) =>
<Select
value={selectedCalc}
options={calcOptions}
onChange={(v) => setSelectedCalc(v)}
disabled={disabled}
defaultValue={calcOptions[0]}
/>
}
control={control}
name="calc"
/>
</InlineField>
<InlineField label="Value to consider" labelWidth={17} grow required disabled={disabled}>
<Input {...register("calcValue")} value={currentCalc.calcValue} disabled={disabled} onChange={handleOnChangeCalc} required />
</InlineField>
<InlineField label="Execute until" labelWidth={17} grow required disabled={disabled}>
<Input {...register("until")} value={currentCalc.until} disabled={disabled} onChange={handleOnChangeCalc} required />
</InlineField>
</Collapse>
<Collapse label="Result processing" collapsible={false} isOpen={true} className={css({ color: useTheme2().colors.text.primary })}>
<Alert title="Info" severity='info'>
$res = number of iterations
</Alert>
<InlineField label="Format" labelWidth={17} grow required disabled={disabled}>
<InputControl
render={({ field }) =>
<Select
value={selectedFormat}
options={formatOptions}
onChange={(v) => setSelectedFormat(v)}
disabled={disabled}
defaultValue={formatOptions[0]}
/>
}
control={control}
name="resFormat"
/>
</InlineField>
<InlineField label="Result processing" labelWidth={17} grow hidden={selectedFormat.value === ExtraCalcFormat.raw } required={selectedFormat.value === ExtraCalcFormat.process} disabled={disabled}>
<Input {...register("resProcess")} value={currentCalc.resProcess} disabled={disabled} onChange={handleOnChangeCalc} required={selectedFormat.value === ExtraCalcFormat.process} />
</InlineField>
</Collapse>
</div>
)
}}
</Form>
<HorizontalGroup justify='flex-end'>
<Button type="button" hidden={(mode === Mode.EDIT) ? disabled : true} variant='primary' disabled={disabled} fill="text" onClick={handleOnClickCancel}>Cancel</Button>
<Button type='submit' form='formatForm' hidden={disabled} variant='primary' disabled={disabled} icon={(mode === Mode.EDIT) ? 'save' : 'plus'}>{mode.valueOf()} calculation</Button>
</HorizontalGroup>
</div>
}
2 changes: 1 addition & 1 deletion src/components/editors/formatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const FormatForm: React.FC<Props> = ({ format, updateFunction, deleteFunc
return (
<div>
<InlineField label="ID" labelWidth={10} required disabled={disabled}>
<Input {...register("id", { required: true })} value={currentFormat.id} disabled={disabled} onChange={handleOnChangeFormat} required />
<Input {...register("id")} value={currentFormat.id} disabled={disabled} onChange={handleOnChangeFormat} required />
</InlineField>
</div>
)
Expand Down
31 changes: 29 additions & 2 deletions src/components/editors/modelForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { ChangeEvent, useEffect, useState } from 'react'
import { SelectableValue, StandardEditorContext } from "@grafana/data";
import { FormatTags, ICredentials, IFormat, IModel, ISelect, ITag, Method } from 'utils/types';
import { FormatTags, ICredentials, IExtraCalc, IFormat, IModel, ISelect, ITag, Method } from 'utils/types';
import { Button, Checkbox, CodeEditor, Collapse, ConfirmButton, ControlledCollapse, Form, FormAPI, HorizontalGroup, InlineField, InlineFieldRow, Input, InputControl, Select, useTheme2 } from '@grafana/ui';
import { ModelDefault } from 'utils/default';
import { dataFrameToOptions, enumToSelect, formatsToOptions } from 'utils/utils'
import { dataFrameToOptions, enumToSelect, extraCalcToOptions, formatsToOptions } from 'utils/utils'
import { TagsForm } from './tagsForm';
import { Mode } from 'utils/constants';
import { css } from '@emotion/css';
Expand Down Expand Up @@ -31,11 +31,13 @@ export const ModelForm: React.FC<Props> = ({ model, updateFunction, deleteFuncti
const [selectedQuery, setSelectedQuery] = useState<SelectableValue<string>>()
const [selectedExtraInfo, setSelectedExtraInfo] = useState<SelectableValue<string>>()
const [selectedFormat, setSelectedFormat] = useState<SelectableValue<IFormat>>()
const [selectedExtraCalc, setSelectedExtraCalc] = useState<SelectableValue<IExtraCalc>>()
const [selectedVarTime, setSelectedVarTime] = useState<SelectableValue<string>>()
const [selectedVarTags, setSelectedVarTags] = useState<SelectableValue<string>>()
const [selectedQuotesListItems, setSelectedQuotesListItems] = useState<SelectableValue<string>>({ label: FormatTags.None, value: FormatTags['None'] })
const [queryOptions, setQueryOptions] = useState<ISelect[]>([])
const [formatOptions, setFormatOptions] = useState<ISelect[]>([])
const [extraCalcOptions, setExtraCalcOptions] = useState<ISelect[]>([])
const [code, setCode] = useState<string>("")
const [disabled, setDisabled] = useState(false)
const [scaler, setScaler] = useState("")
Expand All @@ -49,6 +51,7 @@ export const ModelForm: React.FC<Props> = ({ model, updateFunction, deleteFuncti
setSelectedQuery({ label: model.queryId, value: model.queryId })
if (model.extraInfo !== undefined) setSelectedExtraInfo({ label: model.extraInfo, value: model.extraInfo })
if (model.format !== undefined) setSelectedFormat({ label: model.format.id, value: model.format })
if (model.extraCalc !== undefined) setSelectedExtraCalc({ label: model.extraCalc.id, value: model.extraCalc })
setSelectedVarTags({ label: model.varTags, value: model.varTags })
setSelectedVarTime({ label: model.varTime, value: model.varTime })
setSelectedQuotesListItems({ label: model.formatTags, value: model.formatTags })
Expand Down Expand Up @@ -90,6 +93,7 @@ export const ModelForm: React.FC<Props> = ({ model, updateFunction, deleteFuncti
varTags: selectedVarTags?.value,
formatTags: selectedQuotesListItems?.value,
format: selectedFormat?.value,
extraCalc: selectedExtraCalc?.value,
preprocess: code,
credentials: credentials,
isListValues: listValues,
Expand Down Expand Up @@ -147,6 +151,14 @@ export const ModelForm: React.FC<Props> = ({ model, updateFunction, deleteFuncti
setQueryOptions(dataFrameToOptions(context.data))
}, [context.data])

useEffect(() => {
if (context.options.extraCalcs !== undefined) {
setExtraCalcOptions(extraCalcToOptions(context.options.extraCalcs))
} else {
setExtraCalcOptions([])
}
}, [context.options.formats])

useEffect(() => {
if (context.options.formats !== undefined) {
setFormatOptions(formatsToOptions(context.options.formats))
Expand Down Expand Up @@ -211,6 +223,21 @@ export const ModelForm: React.FC<Props> = ({ model, updateFunction, deleteFuncti
<InlineField label="Decimals" labelWidth={10} disabled={disabled} grow>
<Input {...register("decimals")} disabled={disabled} value={currentModel.decimals} onChange={handleOnChangeModel} type='number' />
</InlineField>
<InlineField label="Extra calculation" labelWidth={15} disabled={disabled} grow>
<InputControl
render={({ field }) =>
<Select
value={selectedExtraCalc}
options={extraCalcOptions}
onChange={(v) => setSelectedExtraCalc(v)}
disabled={disabled}
menuPosition='fixed'
/>
}
control={control}
name="extraCalc"
/>
</InlineField>
<Collapse label="Model queries" collapsible={false} isOpen={true} className={css({ color: useTheme2().colors.text.primary })}>
<p style={{ marginBottom: '5px', marginTop: '5px' }}>Import data query</p>
<InlineField label="Query" labelWidth={20} required disabled={disabled} grow>
Expand Down
10 changes: 8 additions & 2 deletions src/components/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ModifyData } from './step3_ModifyData'
import { PredictModel } from './step4_PredictModel'
import { ExportData } from './step5_ExportData'
import { Context, getMessagesByLanguage, tagsToString } from 'utils/utils'
import { IContext, IDataCollection, IFormat, IModel, Options } from 'utils/types'
import { IContext, IDataCollection, IExtraCalc, IFormat, IModel, Options } from 'utils/types'
import { Steps } from 'utils/constants'
import { locationService } from '@grafana/runtime'
import { saveVariableValue } from 'utils/datasources/grafana'
Expand Down Expand Up @@ -100,7 +100,13 @@ export const Main: React.FC<Props> = ({ options, data, width, height, replaceVar
})
}, [options.formats])


useEffect(() => {
options.models.forEach((model: IModel) => {
if(model.extraCalc) {
model.extraCalc = options.extraCalcs.find((v: IExtraCalc) => v.id === model.extraCalc?.id)
}
})
}, [options.extraCalcs])

return <Context.Provider value={contextData}>
<div className="containerType scrollMain" style={{ width: width, height: height - 10, padding: '10px', paddingBottom: '0px' }} >
Expand Down
Loading

0 comments on commit e61b75f

Please sign in to comment.