Skip to content

Commit

Permalink
Check validity of regexes before sending them to Historian backend
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud Van der Poorten authored and vdpoora committed Nov 29, 2024
1 parent 9fff2a2 commit fde403a
Show file tree
Hide file tree
Showing 15 changed files with 267 additions and 73 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ node-modules/
factry-historian-datasource/
*.zip
*.sha1
.eslintcache
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib"
}
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
major = 2
minor = 0
patch = 4
minor = 1
patch = 1
prerelease =
project_name=factry-historian-datasource

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "factry-historian-datasource",
"version": "2.1.0",
"version": "2.1.1",
"description": "A datasource plugin for Factry Historian",
"scripts": {
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",
Expand Down
50 changes: 32 additions & 18 deletions src/CustomVariableEditor/AssetFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
import React, { ChangeEvent, FormEvent, useState } from 'react'
import React, { ChangeEvent, useState } from 'react'

import { AsyncMultiSelect, InlineField, InlineFieldRow, InlineSwitch, Input } from '@grafana/ui'
import { AsyncMultiSelect, InlineField, InlineFieldRow, InlineSwitch } from '@grafana/ui'
import { DataSource } from 'datasource'
import { AssetFilter } from 'types'
import { SelectableValue } from '@grafana/data'
import { MaybeRegexInput } from 'components/util/MaybeRegexInput'

export function AssetFilterRow(props: {
datasource: DataSource
onChange: (val: AssetFilter) => void
onChange: (val: AssetFilter, valid: boolean) => void
initialValue?: AssetFilter
templateVariables: Array<SelectableValue<string>>
}) {
const [parentAssets, setParentAssets] = useState<Array<SelectableValue<string>>>()
const [pathValid, setPathValid] = useState<boolean>(false)

const onPathChange = (event: FormEvent<HTMLInputElement>) => {
props.onChange({
...props.initialValue,
Path: (event as ChangeEvent<HTMLInputElement>).target.value,
})
const onPathChange = (value: string, valid: boolean) => {
setPathValid(valid)
props.onChange(
{
...props.initialValue,
Path: value,
},
valid
)
}

const onUseAssetPathChange = (event: ChangeEvent<HTMLInputElement>): void => {
props.onChange({
...props.initialValue,
UseAssetPath: event.target.checked,
})
props.onChange(
{
...props.initialValue,
UseAssetPath: event.target.checked,
},
pathValid
)
}

const loadAssetOptions = async (query: string): Promise<Array<SelectableValue<string>>> => {
Expand All @@ -43,14 +52,19 @@ export function AssetFilterRow(props: {
if (!parentAssets) {
setParentAssets(selectableValues.filter((e) => props.initialValue?.ParentUUIDs?.includes(e.value ?? '')))
}
return [{ label: 'No parent', value: '00000000-0000-0000-0000-000000000000' } as SelectableValue<string>].concat(selectableValues)
return [{ label: 'No parent', value: '00000000-0000-0000-0000-000000000000' } as SelectableValue<string>].concat(
selectableValues
)
}

const onParentAssetsChange = (values: Array<SelectableValue<string>>) => {
props.onChange({
...props.initialValue,
ParentUUIDs: values.map((e) => e.value ?? ''),
})
props.onChange(
{
...props.initialValue,
ParentUUIDs: values.map((e) => e.value ?? ''),
},
pathValid
)
setParentAssets(values)
}

Expand All @@ -63,7 +77,7 @@ export function AssetFilterRow(props: {
labelWidth={20}
tooltip={<div>Searches asset by path, to use a regex surround pattern with /</div>}
>
<Input value={props.initialValue?.Path} onChange={onPathChange} />
<MaybeRegexInput onChange={onPathChange} initialValue={props.initialValue?.Path} />
</InlineField>
</InlineFieldRow>
<InlineFieldRow>
Expand Down
22 changes: 13 additions & 9 deletions src/CustomVariableEditor/DatabaseFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import React, { ChangeEvent, FormEvent } from 'react'
import React from 'react'

import { InlineField, InlineFieldRow, Input } from '@grafana/ui'
import { InlineField, InlineFieldRow } from '@grafana/ui'
import { DataSource } from 'datasource'
import { TimeseriesDatabaseFilter } from 'types'
import { MaybeRegexInput } from 'components/util/MaybeRegexInput'

export function DatabaseFilterRow(props: {
datasource: DataSource
onChange: (val: TimeseriesDatabaseFilter) => void
onChange: (val: TimeseriesDatabaseFilter, valid: boolean) => void
initialValue?: TimeseriesDatabaseFilter
}) {
const onKeywordChange = (event: FormEvent<HTMLInputElement>) => {
props.onChange({
...props.initialValue,
Keyword: (event as ChangeEvent<HTMLInputElement>).target.value,
})
const onKeywordChange = (value: string, valid: boolean) => {
props.onChange(
{
...props.initialValue,
Keyword: value,
},
valid
)
}
return (
<>
Expand All @@ -24,7 +28,7 @@ export function DatabaseFilterRow(props: {
labelWidth={20}
tooltip={<div>Searches database by name, to use a regex surround pattern with /</div>}
>
<Input value={props.initialValue?.Keyword} onChange={onKeywordChange} />
<MaybeRegexInput onChange={onKeywordChange} initialValue={props.initialValue?.Keyword} />
</InlineField>
</InlineFieldRow>
</>
Expand Down
22 changes: 13 additions & 9 deletions src/CustomVariableEditor/EventTypeFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import React, { ChangeEvent, FormEvent } from 'react'
import React from 'react'

import { InlineField, InlineFieldRow, Input } from '@grafana/ui'
import { InlineField, InlineFieldRow } from '@grafana/ui'
import { DataSource } from 'datasource'
import { EventTypeFilter } from 'types'
import { MaybeRegexInput } from 'components/util/MaybeRegexInput'

export function EventTypeFilterRow(props: {
datasource: DataSource
onChange: (val: EventTypeFilter) => void
onChange: (val: EventTypeFilter, valid: boolean) => void
initialValue?: EventTypeFilter
}) {
const onKeywordChange = (event: FormEvent<HTMLInputElement>) => {
props.onChange({
...props.initialValue,
Keyword: (event as ChangeEvent<HTMLInputElement>).target.value,
})
const onKeywordChange = (value: string, valid: boolean) => {
props.onChange(
{
...props.initialValue,
Keyword: value,
},
valid
)
}
return (
<>
Expand All @@ -24,7 +28,7 @@ export function EventTypeFilterRow(props: {
labelWidth={20}
tooltip={<div>Searches database by name, to use a regex surround pattern with /</div>}
>
<Input value={props.initialValue?.Keyword} onChange={onKeywordChange} />
<MaybeRegexInput onChange={onKeywordChange} initialValue={props.initialValue?.Keyword} />
</InlineField>
</InlineFieldRow>
</>
Expand Down
38 changes: 24 additions & 14 deletions src/CustomVariableEditor/MeasurementFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
import React, { ChangeEvent, FormEvent, useState } from 'react'
import React, { useState } from 'react'

import { SelectableValue } from '@grafana/data'
import { InlineField, InlineFieldRow, Input } from '@grafana/ui'
import { InlineField, InlineFieldRow } from '@grafana/ui'
import { DataSource } from 'datasource'
import { DatabaseSelect } from 'components/util/DatabaseSelect'
import { MeasurementFilter } from 'types'
import { MaybeRegexInput } from 'components/util/MaybeRegexInput'

export interface MeasurementFilterProps {
datasource: DataSource
onChange: (val: MeasurementFilter) => void
onChange: (val: MeasurementFilter, filterValid: boolean) => void
initialValue?: MeasurementFilter
templateVariables: Array<SelectableValue<string>>
}

export function MeasurementFilterRow(props: MeasurementFilterProps) {
const [selectedDatabases, setSelectedDatabases] = useState<Array<SelectableValue<string>>>()

const onKeywordChange = (event: FormEvent<HTMLInputElement>) => {
props.onChange({
...props.initialValue,
Keyword: (event as ChangeEvent<HTMLInputElement>).target.value,
})
const onKeywordChange = (keyword: string, valid: boolean) => {
props.onChange(
{
...props.initialValue,
Keyword: keyword,
},
valid
)
}

const onDatabaseChange = (values: string[]) => {
props.onChange({
...props.initialValue,
DatabaseUUIDs: values,
})
props.onChange(
{
...props.initialValue,
DatabaseUUIDs: values,
},
true
)
}

return (
Expand All @@ -37,9 +44,12 @@ export function MeasurementFilterRow(props: MeasurementFilterProps) {
label={'Filter measurement'}
aria-label={'Filter measurement'}
labelWidth={20}
tooltip={<div>Searches measurement by name</div>}
tooltip={<div>Searches measurement by name, to use a regex surround pattern with /</div>}
>
<Input value={props.initialValue?.Keyword} onChange={(e) => onKeywordChange(e)} />
<MaybeRegexInput
onChange={(value, ok) => onKeywordChange(value, ok)}
initialValue={props.initialValue?.Keyword}
/>
</InlineField>
</InlineFieldRow>
<InlineFieldRow>
Expand Down
20 changes: 12 additions & 8 deletions src/CustomVariableEditor/VariableEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,27 +79,31 @@ export function VariableQueryEditor(
props.onChange({
...props.query,
type: value.value!,
valid: false,
filter: {},
})
}
if (value.value! === VariableQueryType.AssetQuery) {
props.onChange({
...props.query,
type: value.value!,
valid: false,
filter: {},
})
}
if (value.value! === VariableQueryType.EventTypeQuery) {
props.onChange({
...props.query,
type: value.value!,
valid: false,
filter: {},
})
}
if (value.value! === VariableQueryType.DatabaseQuery) {
props.onChange({
...props.query,
type: value.value!,
valid: false,
filter: {},
})
}
Expand Down Expand Up @@ -144,9 +148,9 @@ export function VariableQueryEditor(
datasource={props.datasource}
initialValue={props.query.filter}
templateVariables={templateVariables}
onChange={(val) => {
onChange={(val, valid) => {
if (props.query.type === VariableQueryType.MeasurementQuery) {
props.onChange({ ...props.query, filter: val })
props.onChange({ ...props.query, filter: val, valid: valid })
}
}}
/>
Expand All @@ -156,9 +160,9 @@ export function VariableQueryEditor(
datasource={props.datasource}
initialValue={props.query.filter}
templateVariables={templateVariables}
onChange={(val) => {
onChange={(val, valid) => {
if (props.query.type === VariableQueryType.AssetQuery) {
props.onChange({ ...props.query, filter: val })
props.onChange({ ...props.query, filter: val, valid: valid })
}
}}
/>
Expand All @@ -179,9 +183,9 @@ export function VariableQueryEditor(
<DatabaseFilterRow
datasource={props.datasource}
initialValue={props.query.filter}
onChange={(val) => {
onChange={(val, valid) => {
if (props.query.type === VariableQueryType.DatabaseQuery) {
props.onChange({ ...props.query, filter: val })
props.onChange({ ...props.query, filter: val, valid: valid })
}
}}
/>
Expand All @@ -190,9 +194,9 @@ export function VariableQueryEditor(
<EventTypeFilterRow
datasource={props.datasource}
initialValue={props.query.filter}
onChange={(val) => {
onChange={(val, valid) => {
if (props.query.type === VariableQueryType.EventTypeQuery) {
props.onChange({ ...props.query, filter: val })
props.onChange({ ...props.query, filter: val, valid: valid })
}
}}
/>
Expand Down
43 changes: 43 additions & 0 deletions src/components/util/MaybeRegexInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { ChangeEvent, FormEvent, useState } from 'react'
import { Input, Tooltip } from '@grafana/ui'
import { isRegex, isValidRegex } from 'util/util'

export interface MaybeRegexInputProps {
onChange: (val: string, valid: boolean) => void
initialValue?: string
placeHolder?: string
}

export function MaybeRegexInput(props: MaybeRegexInputProps) {
const [error, setError] = useState<string | undefined>()

const onChange = (event: FormEvent<HTMLInputElement>) => {
const keyword = (event as ChangeEvent<HTMLInputElement>).target.value as string
let valid = false
if (isRegex(keyword)) {
if (isValidRegex(keyword)) {
setError(undefined)
valid = true
} else {
setError('Invalid regex')
}
} else {
setError(undefined)
valid = true
}

props.onChange(keyword, valid)
}

return (
<Tooltip
content={() => <>{error}</>}
theme="error"
placement="right"
show={error !== undefined}
interactive={false}
>
<Input value={props.initialValue} onChange={(e) => onChange(e)} />
</Tooltip>
)
}
Loading

0 comments on commit fde403a

Please sign in to comment.