Skip to content

Commit

Permalink
implements filter selection
Browse files Browse the repository at this point in the history
  • Loading branch information
MGJamJam committed May 8, 2024
1 parent 1c11662 commit ed1af68
Show file tree
Hide file tree
Showing 4 changed files with 445 additions and 0 deletions.
74 changes: 74 additions & 0 deletions bitmovin-analytics-datasource/src/components/FilterInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import { SelectableValue } from '@grafana/data';
import { HorizontalGroup, IconButton, Input, Select, Tooltip } from '@grafana/ui';

import { QueryAttribute } from '../types/queryAttributes';
import { QueryAdAttribute } from '../types/queryAdAttributes';
import { REORDER_DIRECTION } from './GroupByInput';
import { QueryFilterOperator, SELECTABLE_QUERY_FILTER_OPERATORS } from '../types/queryFilter';

type Props = {
readonly isAdAnalytics: boolean;
readonly selectableFilterAttributes: Array<SelectableValue<QueryAttribute | QueryAdAttribute>>;
readonly onAttributeChange: (newValue: SelectableValue<QueryAdAttribute | QueryAttribute>) => void;
readonly onOperatorChange: (newValue: SelectableValue<QueryFilterOperator>) => void;
readonly onValueChange: (newValue: string) => void;
readonly onDelete: () => void;
readonly isFirst: boolean;
readonly isLast: boolean;
readonly onReorderFilter: (direction: REORDER_DIRECTION) => void;
readonly parsingValueError?: string;
};

export function FilterInput(props: Props) {
//TODOMY implement headers of the 'table'
return (
<HorizontalGroup spacing="xs">
<Select
onChange={(value) => props.onAttributeChange(value)}
options={props.selectableFilterAttributes}
width={30}
/>
<Select
onChange={(value) => props.onOperatorChange(value)}
options={SELECTABLE_QUERY_FILTER_OPERATORS}
width={15}
/>

{
//TODOMY debounce the text editing a little bit...
}
<Tooltip
content={props.parsingValueError ? props.parsingValueError : ''}
show={!!props.parsingValueError}
theme="error"
>
<Input
invalid={!!props.parsingValueError}
type="text"
onChange={(value) => props.onValueChange(value.currentTarget.value)}
width={30}
/>
</Tooltip>
<IconButton
tooltip="Move down"
onClick={() => props.onReorderFilter(REORDER_DIRECTION.DOWN)}
name="arrow-down"
disabled={props.isLast}
/>
<IconButton
tooltip="Move up"
onClick={() => props.onReorderFilter(REORDER_DIRECTION.UP)}
name="arrow-up"
disabled={props.isFirst}
/>
<IconButton
tooltip="Delete Group By"
name="trash-alt"
onClick={() => props.onDelete()}
size="lg"
variant="destructive"
/>
</HorizontalGroup>
);
}
177 changes: 177 additions & 0 deletions bitmovin-analytics-datasource/src/components/FilterRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import React, { useState } from 'react';
import { difference } from 'lodash';
import { SelectableValue } from '@grafana/data';
import { Box, IconButton, VerticalGroup } from '@grafana/ui';

import { QueryFilter, QueryFilterOperator, QueryFilterValue } from '../types/queryFilter';
import { QueryAdAttribute, SELECTABLE_QUERY_AD_ATTRIBUTES } from '../types/queryAdAttributes';
import { QueryAttribute, SELECTABLE_QUERY_ATTRIBUTES } from '../types/queryAttributes';
import { REORDER_DIRECTION } from './GroupByInput';
import { FilterInput } from './FilterInput';
import { convertFilterValueToProperType } from '../utils/filterUtils';

type Props = {
readonly isAdAnalytics: boolean;
readonly onChange: (newFilters: QueryFilter[]) => void;
};

export function FilterRow(props: Props) {
const [selectedAttributes, setSelectedAttributes] = useState<
Array<SelectableValue<QueryAdAttribute | QueryAttribute>>
>([]);
const [selectedOperators, setSelectedOperators] = useState<Array<SelectableValue<QueryFilterOperator>>>([]);
const [values, setValues] = useState<QueryFilterValue[]>([]);
const [parsingValueErrors, setParsingValueErrors] = useState<string[]>([]);

const addFilterInput = () => {
setSelectedAttributes((prevState) => [...prevState, {}]);
setSelectedOperators((prevState) => [...prevState, {}]);
setValues((prevState) => [...prevState, '']);
setParsingValueErrors((prevState) => [...prevState, '']);
};

const deleteFilterInput = (index: number) => {
const newSelectedAttributes = [...selectedAttributes];
newSelectedAttributes.splice(index, 1);

const newSelectedOperators = [...selectedOperators];
newSelectedOperators.splice(index, 1);

const newValues = [...values];
newValues.splice(index, 1);

const newParsingValueErrors = [...parsingValueErrors];
newParsingValueErrors.splice(index, 1);

setSelectedAttributes(newSelectedAttributes);
setSelectedOperators(newSelectedOperators);
setValues(newValues);
setParsingValueErrors(newParsingValueErrors);
//TODOMY before calling this I need to make sure that the filter is actually complete and all the values are set...
props.onChange(mapFiltersToQueryFilters(newSelectedAttributes, newSelectedOperators, newValues));
};

const onAttributesChange = (index: number, newAttribute: SelectableValue<QueryAttribute | QueryAdAttribute>) => {
const newSelectedAttributes = [...selectedAttributes];
newSelectedAttributes.splice(index, 1, newAttribute);
setSelectedAttributes(newSelectedAttributes);

props.onChange(mapFiltersToQueryFilters(newSelectedAttributes, selectedOperators, values));
};

const onOperatorsChange = (index: number, newOperator: SelectableValue<QueryFilterOperator>) => {
const newSelectedOperators = [...selectedOperators];
newSelectedOperators.splice(index, 1, newOperator);
setSelectedOperators(newSelectedOperators);

//TODOMY here I also need to check the value and ,aybe throw an error

//TODOMY difference between checking if value is valid and actually parsing it ?

props.onChange(mapFiltersToQueryFilters(selectedAttributes, newSelectedOperators, values));
};

const onValuesChange = (index: number, newValue: string) => {
try {
const convertedValue = convertFilterValueToProperType(
newValue,
selectedAttributes[index].value!,
selectedOperators[index].value!,
props.isAdAnalytics
);
console.log(convertedValue, convertedValue);

const newValues = [...values];
newValues.splice(index, 1, convertedValue);
console.log(newValues);
setValues(newValues);

const newParsingValueErrors = [...parsingValueErrors];
newParsingValueErrors.splice(index, 1, '');
setParsingValueErrors(newParsingValueErrors);

props.onChange(mapFiltersToQueryFilters(selectedAttributes, selectedOperators, newValues));
} catch (e: any) {
const errorMessage = e.message;
const newParsingValueErrors = [...parsingValueErrors];
newParsingValueErrors.splice(index, 1, errorMessage);
setParsingValueErrors(newParsingValueErrors);
}
};

const mapFilterAttributesToSelectableValue = (): Array<SelectableValue<QueryAttribute | QueryAdAttribute>> => {
if (props.isAdAnalytics) {
return difference(SELECTABLE_QUERY_AD_ATTRIBUTES, selectedAttributes);
} else {
return difference(SELECTABLE_QUERY_ATTRIBUTES, selectedAttributes);
}
};

const mapFiltersToQueryFilters = (
selectedAttributes: Array<SelectableValue<QueryAttribute | QueryAdAttribute>>,
selectedOperators: Array<SelectableValue<QueryFilterOperator>>,
values: QueryFilterValue[]
): QueryFilter[] => {
const queryFilters: QueryFilter[] = [];
for (let i = 0; i < selectedAttributes.length; i++) {
queryFilters.push({
name: selectedAttributes[i].value!,
operator: selectedOperators[i].value!,
value: values[i],
});
}
console.log(queryFilters);
return queryFilters;
};

const reorderFilter = (direction: REORDER_DIRECTION, index: number) => {
const newIndex = direction === REORDER_DIRECTION.UP ? index - 1 : index + 1;

const newSelectedAttributes = [...selectedAttributes];
const attributeToMove = newSelectedAttributes[index];
newSelectedAttributes.splice(index, 1);
newSelectedAttributes.splice(newIndex, 0, attributeToMove);

const newSelectedOperators = [...selectedOperators];
const operatorToMove = newSelectedOperators[index];
newSelectedOperators.splice(index, 1);
newSelectedOperators.splice(newIndex, 0, operatorToMove);

const newValues = [...values];
const valueToMove = newValues[index];
newValues.splice(index, 1);
newValues.splice(newIndex, 0, valueToMove);

setSelectedAttributes(newSelectedAttributes);
setSelectedOperators(newSelectedOperators);
setValues(newValues);

props.onChange(mapFiltersToQueryFilters(newSelectedAttributes, newSelectedOperators, newValues));
};

return (
<VerticalGroup>
{selectedAttributes.map((attribute, index, array) => (
<FilterInput
key={index}
isAdAnalytics={props.isAdAnalytics}
selectableFilterAttributes={mapFilterAttributesToSelectableValue()}
onAttributeChange={(newValue: SelectableValue<QueryAdAttribute | QueryAttribute>) =>
onAttributesChange(index, newValue)
}
onOperatorChange={(newValue: SelectableValue<QueryFilterOperator>) => onOperatorsChange(index, newValue)}
onValueChange={(newValue: string) => onValuesChange(index, newValue)}
onDelete={() => deleteFilterInput(index)}
isFirst={index === 0}
isLast={index === array.length - 1}
onReorderFilter={(direction: REORDER_DIRECTION) => reorderFilter(direction, index)}
parsingValueError={parsingValueErrors[index] === '' ? undefined : parsingValueErrors[index]}
/>
))}

<Box paddingTop={selectedAttributes.length === 0 ? 0.5 : 0}>
<IconButton name="plus-square" tooltip="Add Group By" onClick={() => addFilterInput()} size="xl" />
</Box>
</VerticalGroup>
);
}
10 changes: 10 additions & 0 deletions bitmovin-analytics-datasource/src/components/QueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { isMetric, SELECTABLE_METRICS } from '../types/metric';
import { GroupByRow } from './GroupByRow';
import { OrderByRow } from './OrderByRow';
import { QueryOrderBy } from '../types/queryOrderBy';
import { QueryFilter } from '../types/queryFilter';
import { FilterRow } from './FilterRow';

enum LoadingState {
Default = 'DEFAULT',
Expand Down Expand Up @@ -74,6 +76,11 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props)
onRunQuery();
};

const onFilterChange = (newFilters: QueryFilter[]) => {
onChange({ ...query, filters: newFilters });
onRunQuery();
};

const onFormatAsTimeSeriesChange = (event: ChangeEvent<HTMLInputElement>) => {
setIsTimeSeries(event.currentTarget.checked);
if (event.currentTarget.checked) {
Expand Down Expand Up @@ -150,6 +157,9 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props)
<InlineField label="Order By" labelWidth={20}>
<OrderByRow isAdAnalytics={datasource.adAnalytics ? true : false} onChange={onOrderByChange} />
</InlineField>
<InlineField label="Filter" labelWidth={20}>
<FilterRow isAdAnalytics={datasource.adAnalytics ? true : false} onChange={onFilterChange} />
</InlineField>
<InlineField label="Format as time series" labelWidth={20}>
<InlineSwitch value={isTimeSeries} onChange={onFormatAsTimeSeriesChange}></InlineSwitch>
</InlineField>
Expand Down
Loading

0 comments on commit ed1af68

Please sign in to comment.