Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/AN-4111 implement filter selection #70

Merged
merged 16 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bitmovin-analytics-datasource/src/components/ConfigEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { ChangeEvent, useEffect } from 'react';
import { DataSourceHttpSettings, FieldSet, InlineField, InlineSwitch, Input } from '@grafana/ui';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { MyDataSourceOptions } from '../types';
import { BitmovinDataSourceOptions } from '../types/grafanaTypes';

interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions> {}
interface Props extends DataSourcePluginOptionsEditorProps<BitmovinDataSourceOptions> {}

export function ConfigEditor(props: Props) {
const { onOptionsChange, options } = props;
Expand Down
57 changes: 57 additions & 0 deletions bitmovin-analytics-datasource/src/components/FilterInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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 { 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 addFilterDisabled: boolean;
readonly onAddFilter: () => void;
readonly parsingValueError: string | undefined;
};

export function FilterInput(props: Props) {
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}
/>
<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="Add Filter"
onClick={props.onAddFilter}
name="check"
size="xl"
variant="primary"
disabled={props.addFilterDisabled}
/>
<IconButton tooltip="Delete Filter" name="trash-alt" onClick={props.onDelete} size="lg" variant="destructive" />
</HorizontalGroup>
);
}
168 changes: 168 additions & 0 deletions bitmovin-analytics-datasource/src/components/FilterRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import React, { useState } from 'react';
import { difference, isEmpty } from 'lodash';
import { SelectableValue } from '@grafana/data';
import { Box, HorizontalGroup, IconButton, InlineLabel, 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 { FilterInput } from './FilterInput';
import { convertFilterValueToProperType } from '../utils/filterUtils';

type Filter = {
selectedAttribute: SelectableValue<QueryAdAttribute | QueryAttribute>;
selectedOperator: SelectableValue<QueryFilterOperator>;
rawFilterValue: string;
convertedFilterValue: QueryFilterValue;
parsingValueError: string;
};

const mapFilterAttributesToSelectableValue = (
filters: Filter[],
isAdAnalytics: boolean
): Array<SelectableValue<QueryAttribute | QueryAdAttribute>> => {
const selectedAttributes = filters.map((filter) => filter.selectedAttribute);
if (isAdAnalytics) {
return difference(SELECTABLE_QUERY_AD_ATTRIBUTES, selectedAttributes);
} else {
return difference(SELECTABLE_QUERY_ATTRIBUTES, selectedAttributes);
}
};

const mapFiltersToQueryFilters = (filters: Filter[]): QueryFilter[] => {
return filters.map((filter) => {
return {
name: filter.selectedAttribute.value!,
operator: filter.selectedOperator.value!,
value: filter.convertedFilterValue,
} as QueryFilter;
});
};

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

export function FilterRow(props: Props) {
const [filters, setFilters] = useState<Filter[]>([]);

const addFilterInput = () => {
setFilters((prevState) => [
...prevState,
{
selectedAttribute: {},
selectedOperator: {},
rawFilterValue: '',
convertedFilterValue: '',
parsingValueError: '',
} as Filter,
]);
};

const onAddFilter = (index: number) => {
const filter = filters[index];
try {
const convertedValue = convertFilterValueToProperType(
filter.rawFilterValue,
filter.selectedAttribute.value!,
filter.selectedAttribute.label!,
filter.selectedOperator.value!,
props.isAdAnalytics
);

const newFilter = { ...filter, convertedFilterValue: convertedValue, parsingValueError: '' } as Filter;

const newFilters = [...filters];
newFilters.splice(index, 1, newFilter);

setFilters(newFilters);

props.onChange(mapFiltersToQueryFilters(newFilters));
} catch (e: unknown) {
if (e instanceof Error) {
const errorMessage = e.message;
const newFilter = { ...filter, parsingValueError: errorMessage } as Filter;

const newFilters = [...filters];
newFilters.splice(index, 1, newFilter);

setFilters(newFilters);
}
}
};

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

setFilters(newFilters);

props.onChange(mapFiltersToQueryFilters(newFilters));
};

const onAttributesChange = (index: number, newAttribute: SelectableValue<QueryAttribute | QueryAdAttribute>) => {
const filter = filters[index];
const newFilter = { ...filter, selectedAttribute: newAttribute } as Filter;
const newFilters = [...filters];
newFilters.splice(index, 1, newFilter);

setFilters(newFilters);
};

const onOperatorsChange = (index: number, newOperator: SelectableValue<QueryFilterOperator>) => {
const filter = filters[index];
const newFilter = { ...filter, selectedOperator: newOperator } as Filter;
const newFilters = [...filters];
newFilters.splice(index, 1, newFilter);

setFilters(newFilters);
};

const onValuesChange = (index: number, newValue: string) => {
const filter = filters[index];
const newFilter = { ...filter, rawFilterValue: newValue };
const newFilters = [...filters];
newFilters.splice(index, 1, newFilter);

setFilters(newFilters);
};

return (
<VerticalGroup>
{filters.length !== 0 && (
<HorizontalGroup spacing={'none'}>
<InlineLabel width={30} tooltip="">
Dimension
</InlineLabel>
<InlineLabel width={15} tooltip="">
Operator
</InlineLabel>
<InlineLabel width={30} tooltip="">
Value
</InlineLabel>
</HorizontalGroup>
)}
{filters.map((filter, index, filtersArray) => (
<FilterInput
key={index}
isAdAnalytics={props.isAdAnalytics}
selectableFilterAttributes={mapFilterAttributesToSelectableValue(filtersArray, props.isAdAnalytics)}
onAttributeChange={(newValue: SelectableValue<QueryAdAttribute | QueryAttribute>) =>
onAttributesChange(index, newValue)
}
onOperatorChange={(newValue: SelectableValue<QueryFilterOperator>) => onOperatorsChange(index, newValue)}
onValueChange={(newValue: string) => onValuesChange(index, newValue)}
onDelete={() => deleteFilterInput(index)}
addFilterDisabled={isEmpty(filter.selectedAttribute) || isEmpty(filter.selectedOperator)}
onAddFilter={() => onAddFilter(index)}
parsingValueError={isEmpty(filter.parsingValueError) ? undefined : filter.parsingValueError}
/>
))}

<Box paddingTop={filters.length === 0 ? 0.5 : 0}>
<IconButton name="plus-square" tooltip="Add Filter" onClick={() => addFilterInput()} size="xl" />
</Box>
</VerticalGroup>
);
}
16 changes: 8 additions & 8 deletions bitmovin-analytics-datasource/src/components/GroupByInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { HorizontalGroup, IconButton, Select } from '@grafana/ui';

import { QueryAttribute } from '../types/queryAttributes';
import { QueryAdAttribute } from '../types/queryAdAttributes';
import { isEmpty } from 'lodash';

export enum REORDER_DIRECTION {
UP,
Expand All @@ -23,7 +24,12 @@ type Props = {
export function GroupByInput(props: Props) {
return (
<HorizontalGroup>
<Select value={props.groupBy} onChange={props.onChange} options={props.selectableGroupBys} width={30} />
<Select
value={isEmpty(props.groupBy) ? undefined : props.groupBy}
onChange={props.onChange}
options={props.selectableGroupBys}
width={30}
/>
<IconButton
tooltip="Move down"
onClick={() => props.onReorderGroupBy(REORDER_DIRECTION.DOWN)}
Expand All @@ -36,13 +42,7 @@ export function GroupByInput(props: Props) {
name="arrow-up"
disabled={props.isFirst}
/>
<IconButton
tooltip="Delete Group By"
name="trash-alt"
onClick={() => props.onDelete()}
size="lg"
variant="destructive"
/>
<IconButton tooltip="Delete Group By" name="trash-alt" onClick={props.onDelete} size="lg" variant="destructive" />
</HorizontalGroup>
);
}
25 changes: 14 additions & 11 deletions bitmovin-analytics-datasource/src/components/GroupByRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ import { QueryAdAttribute, SELECTABLE_QUERY_AD_ATTRIBUTES } from '../types/query
import { QueryAttribute, SELECTABLE_QUERY_ATTRIBUTES } from '../types/queryAttributes';
import { GroupByInput, REORDER_DIRECTION } from './GroupByInput';

const mapGroupBysToSelectableValue = (
selectedGroupBys: Array<SelectableValue<QueryAdAttribute | QueryAttribute>>,
isAdAnalytics: boolean
): Array<SelectableValue<QueryAttribute | QueryAdAttribute>> => {
if (isAdAnalytics) {
return difference(SELECTABLE_QUERY_AD_ATTRIBUTES, selectedGroupBys);
} else {
return difference(SELECTABLE_QUERY_ATTRIBUTES, selectedGroupBys);
}
};

type Props = {
readonly isAdAnalytics: boolean;
readonly onChange: (newGroupBys: QueryAdAttribute[] | QueryAttribute[]) => void;
Expand All @@ -17,14 +28,6 @@ export function GroupByRow(props: Props) {
[]
);

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

const deleteGroupByInput = (index: number) => {
const newSelectedGroupBys = [...selectedGroupBys];
newSelectedGroupBys.splice(index, 1);
Expand Down Expand Up @@ -66,17 +69,17 @@ export function GroupByRow(props: Props) {

return (
<VerticalGroup>
{selectedGroupBys.map((item, index, groupBys) => (
{selectedGroupBys.map((item, index, selectedGroupBysArray) => (
<GroupByInput
key={index}
groupBy={item}
onChange={(newValue: SelectableValue<QueryAdAttribute | QueryAttribute>) =>
onSelectedGroupByChange(index, newValue)
}
selectableGroupBys={mapGroupBysToSelectableValue()}
selectableGroupBys={mapGroupBysToSelectableValue(selectedGroupBysArray, props.isAdAnalytics)}
onDelete={() => deleteGroupByInput(index)}
isFirst={index === 0}
isLast={index === groupBys.length - 1}
isLast={index === selectedGroupBysArray.length - 1}
onReorderGroupBy={(direction: REORDER_DIRECTION) => reorderGroupBy(direction, index)}
/>
))}
Expand Down
11 changes: 3 additions & 8 deletions bitmovin-analytics-datasource/src/components/OrderByInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { QueryAttribute } from '../types/queryAttributes';
import { QueryAdAttribute } from '../types/queryAdAttributes';
import { QuerySortOrder } from '../types/queryOrderBy';
import { REORDER_DIRECTION } from './GroupByInput';
import { isEmpty } from 'lodash';

type Props = {
readonly isAdAnalytics: boolean;
Expand All @@ -29,7 +30,7 @@ export function OrderByInput(props: Props) {
return (
<HorizontalGroup spacing="xs">
<Select
value={props.attribute}
value={isEmpty(props.attribute) ? undefined : props.attribute}
onChange={(value) => props.onAttributeChange(value)}
options={props.selectableOrderByAttributes}
width={30}
Expand All @@ -51,13 +52,7 @@ export function OrderByInput(props: Props) {
name="arrow-up"
disabled={props.isFirst}
/>
<IconButton
tooltip="Delete Group By"
name="trash-alt"
onClick={() => props.onDelete()}
size="lg"
variant="destructive"
/>
<IconButton tooltip="Delete Order By" name="trash-alt" onClick={props.onDelete} size="lg" variant="destructive" />
</HorizontalGroup>
);
}
Loading
Loading