From 2b6ab5966aa48fadef73cd094be2c4fb1c092e4e Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Mon, 20 May 2024 17:02:22 -0300 Subject: [PATCH 01/13] make GroupByRow controlled --- .../src/components/GroupByInput.tsx | 4 +- .../src/components/GroupByRow.tsx | 78 ++++++++++--------- .../src/components/QueryEditor.tsx | 6 +- .../src/datasource.ts | 2 +- 4 files changed, 50 insertions(+), 40 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/GroupByInput.tsx b/bitmovin-analytics-datasource/src/components/GroupByInput.tsx index 2a0e2f7..8d2670d 100644 --- a/bitmovin-analytics-datasource/src/components/GroupByInput.tsx +++ b/bitmovin-analytics-datasource/src/components/GroupByInput.tsx @@ -15,7 +15,7 @@ type Props = { readonly groupBy: SelectableValue; readonly selectableGroupBys: Array>; readonly onDelete: () => void; - readonly onChange: (newValue: SelectableValue) => void; + readonly onChange: (newValue: QueryAdAttribute | QueryAttribute) => void; readonly isFirst: boolean; readonly isLast: boolean; readonly onReorderGroupBy: (direction: REORDER_DIRECTION) => void; @@ -26,7 +26,7 @@ export function GroupByInput(props: Props) { From 4da38868d8b1c0b4ead6126c429f95853ae066f0 Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Mon, 20 May 2024 17:06:43 -0300 Subject: [PATCH 03/13] make FilterByRow controlled --- .../src/components/FilterInput.tsx | 34 +++-- .../src/components/FilterRow.tsx | 119 +++++++++--------- .../src/components/QueryEditor.tsx | 38 +++++- .../src/utils/filterUtils.ts | 10 ++ 4 files changed, 130 insertions(+), 71 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/FilterInput.tsx b/bitmovin-analytics-datasource/src/components/FilterInput.tsx index ba4a747..c7eec8d 100644 --- a/bitmovin-analytics-datasource/src/components/FilterInput.tsx +++ b/bitmovin-analytics-datasource/src/components/FilterInput.tsx @@ -1,13 +1,31 @@ import React from 'react'; -import { SelectableValue } from '@grafana/data'; +import type { SelectableValue } from '@grafana/data'; import { HorizontalGroup, IconButton, Input, Select, Tooltip } from '@grafana/ui'; +import { isEmpty } from 'lodash'; -import { QueryAttribute } from '../types/queryAttributes'; -import { QueryAdAttribute } from '../types/queryAdAttributes'; +import { QueryAttribute, SELECTABLE_QUERY_ATTRIBUTES } from '../types/queryAttributes'; +import { QueryAdAttribute, SELECTABLE_QUERY_AD_ATTRIBUTES } from '../types/queryAdAttributes'; import { QueryFilterOperator, SELECTABLE_QUERY_FILTER_OPERATORS } from '../types/queryFilter'; +import type { FilterRowData } from './QueryEditor'; + +const mapAttributeToSelectableValue = ( + attribute: QueryAttribute | QueryAdAttribute, + isAdAnalytics: boolean +): SelectableValue => { + if (isAdAnalytics) { + return SELECTABLE_QUERY_AD_ATTRIBUTES.filter((selectableValue) => selectableValue.value === attribute); + } else { + return SELECTABLE_QUERY_ATTRIBUTES.filter((selectableValue) => selectableValue.value === attribute); + } +}; + +const mapOperatorToSelectableOperator = (operator: QueryFilterOperator): SelectableValue => { + return SELECTABLE_QUERY_FILTER_OPERATORS.filter((selectableOperator) => selectableOperator.value === operator); +}; type Props = { readonly isAdAnalytics: boolean; + readonly filter: FilterRowData; readonly selectableFilterAttributes: Array>; readonly onAttributeChange: (newValue: SelectableValue) => void; readonly onOperatorChange: (newValue: SelectableValue) => void; @@ -15,29 +33,31 @@ type Props = { readonly onDelete: () => void; readonly addFilterDisabled: boolean; readonly onAddFilter: () => void; - readonly parsingValueError: string | undefined; }; export function FilterInput(props: Props) { return ( props.onOperatorChange(value)} options={SELECTABLE_QUERY_FILTER_OPERATORS} width={15} /> props.onValueChange(value.currentTarget.value)} width={30} diff --git a/bitmovin-analytics-datasource/src/components/FilterRow.tsx b/bitmovin-analytics-datasource/src/components/FilterRow.tsx index 0344fec..bc94324 100644 --- a/bitmovin-analytics-datasource/src/components/FilterRow.tsx +++ b/bitmovin-analytics-datasource/src/components/FilterRow.tsx @@ -1,39 +1,39 @@ -import React, { useState } from 'react'; -import { difference, isEmpty } from 'lodash'; +import React from 'react'; +import { differenceWith, 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 { QueryFilter, QueryFilterOperator } 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; - selectedOperator: SelectableValue; - rawFilterValue: string; - convertedFilterValue: QueryFilterValue; - parsingValueError: string; -}; +import type { FilterRowData } from './QueryEditor'; const mapFilterAttributesToSelectableValue = ( - filters: Filter[], + filters: FilterRowData[], isAdAnalytics: boolean ): Array> => { - const selectedAttributes = filters.map((filter) => filter.selectedAttribute); if (isAdAnalytics) { - return difference(SELECTABLE_QUERY_AD_ATTRIBUTES, selectedAttributes); + return differenceWith( + SELECTABLE_QUERY_AD_ATTRIBUTES, + filters, + (selectableValue, selectedValue) => selectableValue.value === selectedValue.attribute + ); } else { - return difference(SELECTABLE_QUERY_ATTRIBUTES, selectedAttributes); + return differenceWith( + SELECTABLE_QUERY_ATTRIBUTES, + filters, + (selectableValue, selectedValue) => selectableValue.value === selectedValue.attribute + ); } }; -const mapFiltersToQueryFilters = (filters: Filter[]): QueryFilter[] => { +const mapFilterRowsToQueryFilters = (filters: FilterRowData[]): QueryFilter[] => { return filters.map((filter) => { return { - name: filter.selectedAttribute.value!, - operator: filter.selectedOperator.value!, + name: filter.attribute, + operator: filter.operator, value: filter.convertedFilterValue, } as QueryFilter; }); @@ -42,95 +42,94 @@ const mapFiltersToQueryFilters = (filters: Filter[]): QueryFilter[] => { type Props = { readonly isAdAnalytics: boolean; readonly onChange: (newFilters: QueryFilter[]) => void; + readonly onFilterRowChange: (newFilters: FilterRowData[]) => void; + readonly filters: FilterRowData[]; }; export function FilterRow(props: Props) { - const [filters, setFilters] = useState([]); - const addFilterInput = () => { - setFilters((prevState) => [ - ...prevState, - { - selectedAttribute: {}, - selectedOperator: {}, - rawFilterValue: '', - convertedFilterValue: '', - parsingValueError: '', - } as Filter, - ]); + const newFilters = [...props.filters]; + newFilters.push({ + attribute: {}, + operator: {}, + rawFilterValue: '', + convertedFilterValue: '', + parsingValueError: '', + } as FilterRowData); + props.onFilterRowChange(newFilters); }; const onAddFilter = (index: number) => { - const filter = filters[index]; + const filter = props.filters[index]; try { const convertedValue = convertFilterValueToProperType( filter.rawFilterValue, - filter.selectedAttribute.value!, - filter.selectedAttribute.label!, - filter.selectedOperator.value!, + filter.attribute, + filter.attribute, + filter.operator, props.isAdAnalytics ); - const newFilter = { ...filter, convertedFilterValue: convertedValue, parsingValueError: '' } as Filter; + const newFilter = { ...filter, convertedFilterValue: convertedValue, parsingValueError: '' } as FilterRowData; - const newFilters = [...filters]; + const newFilters = [...props.filters]; newFilters.splice(index, 1, newFilter); - setFilters(newFilters); + props.onFilterRowChange(newFilters); - props.onChange(mapFiltersToQueryFilters(newFilters)); + props.onChange(mapFilterRowsToQueryFilters(newFilters)); } catch (e: unknown) { if (e instanceof Error) { const errorMessage = e.message; - const newFilter = { ...filter, parsingValueError: errorMessage } as Filter; + const newFilter = { ...filter, parsingValueError: errorMessage } as FilterRowData; - const newFilters = [...filters]; + const newFilters = [...props.filters]; newFilters.splice(index, 1, newFilter); - setFilters(newFilters); + props.onFilterRowChange(newFilters); } } }; const deleteFilterInput = (index: number) => { - const newFilters = [...filters]; + const newFilters = [...props.filters]; newFilters.splice(index, 1); - setFilters(newFilters); + props.onFilterRowChange(newFilters); - props.onChange(mapFiltersToQueryFilters(newFilters)); + props.onChange(mapFilterRowsToQueryFilters(newFilters)); }; const onAttributesChange = (index: number, newAttribute: SelectableValue) => { - const filter = filters[index]; - const newFilter = { ...filter, selectedAttribute: newAttribute } as Filter; - const newFilters = [...filters]; + const filter = props.filters[index]; + const newFilter = { ...filter, attribute: newAttribute.value } as FilterRowData; + const newFilters = [...props.filters]; newFilters.splice(index, 1, newFilter); - setFilters(newFilters); + props.onFilterRowChange(newFilters); }; const onOperatorsChange = (index: number, newOperator: SelectableValue) => { - const filter = filters[index]; - const newFilter = { ...filter, selectedOperator: newOperator } as Filter; - const newFilters = [...filters]; + const filter = props.filters[index]; + const newFilter = { ...filter, operator: newOperator.value } as FilterRowData; + const newFilters = [...props.filters]; newFilters.splice(index, 1, newFilter); - setFilters(newFilters); + props.onFilterRowChange(newFilters); }; const onValuesChange = (index: number, newValue: string) => { - const filter = filters[index]; + const filter = props.filters[index]; const newFilter = { ...filter, rawFilterValue: newValue }; - const newFilters = [...filters]; + const newFilters = [...props.filters]; newFilters.splice(index, 1, newFilter); - setFilters(newFilters); + props.onFilterRowChange(newFilters); }; return ( - {filters.length !== 0 && ( + {props.filters.length !== 0 && ( Dimension @@ -143,10 +142,11 @@ export function FilterRow(props: Props) { )} - {filters.map((filter, index, filtersArray) => ( + {props.filters.map((filter, index, filtersArray) => ( ) => onAttributesChange(index, newValue) @@ -154,13 +154,12 @@ export function FilterRow(props: Props) { onOperatorChange={(newValue: SelectableValue) => onOperatorsChange(index, newValue)} onValueChange={(newValue: string) => onValuesChange(index, newValue)} onDelete={() => deleteFilterInput(index)} - addFilterDisabled={isEmpty(filter.selectedAttribute) || isEmpty(filter.selectedOperator)} + addFilterDisabled={isEmpty(filter.attribute) || isEmpty(filter.operator)} onAddFilter={() => onAddFilter(index)} - parsingValueError={isEmpty(filter.parsingValueError) ? undefined : filter.parsingValueError} /> ))} - + addFilterInput()} size="xl" /> diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index baa5628..a138a88 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -13,9 +13,10 @@ import { QueryAttribute, SELECTABLE_QUERY_ATTRIBUTES } from '../types/queryAttri 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 type { QueryOrderBy } from '../types/queryOrderBy'; +import type { QueryFilter, QueryFilterOperator, QueryFilterValue } from '../types/queryFilter'; import { FilterRow } from './FilterRow'; +import { mapFilterValueToRawFilterValue } from '../utils/filterUtils'; enum LoadingState { Default = 'DEFAULT', @@ -24,6 +25,14 @@ enum LoadingState { Error = 'ERROR', } +export type FilterRowData = { + attribute: QueryAdAttribute | QueryAttribute; + operator: QueryFilterOperator; + rawFilterValue: string; + convertedFilterValue: QueryFilterValue; + parsingValueError: string; +}; + type Props = QueryEditorProps; export function QueryEditor(props: Props) { @@ -32,6 +41,7 @@ export function QueryEditor(props: Props) { const [licenseErrorMessage, setLicenseErrorMessage] = useState(''); const [isTimeSeries, setIsTimeSeries] = useState(true); const [isDimensionMetricSelected, setIsDimensionMetricSelected] = useState(false); + const [filterRows, setFilterRows] = useState([]); useEffect(() => { setLicenseLoadingState(LoadingState.Loading); @@ -44,7 +54,18 @@ export function QueryEditor(props: Props) { setLicenseLoadingState(LoadingState.Error); setLicenseErrorMessage(e.status + ' ' + e.statusText); }); - }, [props.datasource.apiKey, props.datasource.baseUrl]); + + const filterRows = props.query.filters.map((filter) => { + return { + attribute: filter.name, + operator: filter.operator, + rawFilterValue: mapFilterValueToRawFilterValue(filter.value), + convertedFilterValue: filter.value, + parsingValueError: '', + } as FilterRowData; + }); + setFilterRows(filterRows); + }, [props.datasource.apiKey, props.datasource.baseUrl, props.query.filters]); const query = defaults(props.query, DEFAULT_QUERY); @@ -79,6 +100,10 @@ export function QueryEditor(props: Props) { props.onRunQuery(); }; + const handleFilterRowChange = (newFilters: FilterRowData[]) => { + setFilterRows(newFilters); + }; + const handleFilterChange = (newFilters: QueryFilter[]) => { props.onChange({ ...query, filters: newFilters }); props.onRunQuery(); @@ -162,7 +187,12 @@ export function QueryEditor(props: Props) { /> - + { + if (filterValue == null) { + return ''; + } else if (Array.isArray(filterValue)) { + return JSON.stringify(filterValue); + } else { + return filterValue.toString(); + } +}; + const isNullFilter = (filterAttribute: QueryAttribute | QueryAdAttribute): boolean => { switch (filterAttribute) { case QUERY_ATTRIBUTES.CDN_PROVIDER: From 58cc290c0b5b0432fbc92819d84857900fb2b9ef Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Mon, 20 May 2024 17:08:19 -0300 Subject: [PATCH 04/13] make simple selection controlled --- .../src/components/QueryEditor.tsx | 15 +++++++++++---- .../src/types/aggregations.ts | 4 +++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index a138a88..ca3fa05 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -1,6 +1,6 @@ import React, { ChangeEvent, useEffect, useState } from 'react'; import { FieldSet, InlineField, InlineSwitch, Input, Select } from '@grafana/ui'; -import { QueryEditorProps, SelectableValue } from '@grafana/data'; +import type { QueryEditorProps, SelectableValue } from '@grafana/data'; import { defaults } from 'lodash'; import { DataSource } from '../datasource'; @@ -162,6 +162,7 @@ export function QueryEditor(props: Props) { required > handleAggregationChange(item)} width={30} options={SELECTABLE_AGGREGATIONS} /> + - + {isTimeSeries && renderTimeSeriesOption()} - + diff --git a/bitmovin-analytics-datasource/src/types/aggregations.ts b/bitmovin-analytics-datasource/src/types/aggregations.ts index 72072ad..8c26f9f 100644 --- a/bitmovin-analytics-datasource/src/types/aggregations.ts +++ b/bitmovin-analytics-datasource/src/types/aggregations.ts @@ -1,6 +1,8 @@ +import { SelectableValue } from '@grafana/data'; + export type Aggregation = 'count' | 'sum' | 'avg' | 'min' | 'max' | 'stddev' | 'percentile' | 'variance' | 'median'; -export const SELECTABLE_AGGREGATIONS: Array<{ value: Aggregation; label: string }> = [ +export const SELECTABLE_AGGREGATIONS: Array> = [ { value: 'count', label: 'Count' }, { value: 'sum', label: 'Sum' }, { value: 'avg', label: 'Avg' }, From 021267e289475f551149d564d5d43f728023e003 Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Mon, 20 May 2024 17:08:37 -0300 Subject: [PATCH 05/13] fix groupBy types --- bitmovin-analytics-datasource/src/components/QueryEditor.tsx | 2 +- bitmovin-analytics-datasource/src/types/grafanaTypes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index ca3fa05..59a82f3 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -90,7 +90,7 @@ export function QueryEditor(props: Props) { props.onRunQuery(); }; - const handleGroupByChange = (newGroupBys: QueryAdAttribute[] | QueryAttribute[]) => { + const handleGroupByChange = (newGroupBys: Array) => { props.onChange({ ...query, groupBy: newGroupBys }); props.onRunQuery(); }; diff --git a/bitmovin-analytics-datasource/src/types/grafanaTypes.ts b/bitmovin-analytics-datasource/src/types/grafanaTypes.ts index e5d3f2b..cc8665d 100644 --- a/bitmovin-analytics-datasource/src/types/grafanaTypes.ts +++ b/bitmovin-analytics-datasource/src/types/grafanaTypes.ts @@ -17,7 +17,7 @@ export interface BitmovinAnalyticsDataQuery extends DataQuery { aggregation?: Aggregation; metric?: Metric; dimension?: QueryAttribute | QueryAdAttribute; - groupBy: QueryAttribute[] | QueryAdAttribute[]; + groupBy: Array; orderBy: QueryOrderBy[]; limit?: number; filters: QueryFilter[]; From 39193ad8eb33328fc05d01d4be01836bbb7f0370 Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Mon, 20 May 2024 17:09:03 -0300 Subject: [PATCH 06/13] fix interval default value bug --- bitmovin-analytics-datasource/src/components/QueryEditor.tsx | 2 +- bitmovin-analytics-datasource/src/types/grafanaTypes.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index 59a82f3..7d61c10 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -39,7 +39,7 @@ export function QueryEditor(props: Props) { const [selectableLicenses, setSelectableLicenses] = useState([]); const [licenseLoadingState, setLicenseLoadingState] = useState(LoadingState.Default); const [licenseErrorMessage, setLicenseErrorMessage] = useState(''); - const [isTimeSeries, setIsTimeSeries] = useState(true); + const [isTimeSeries, setIsTimeSeries] = useState(!!props.query.interval); const [isDimensionMetricSelected, setIsDimensionMetricSelected] = useState(false); const [filterRows, setFilterRows] = useState([]); diff --git a/bitmovin-analytics-datasource/src/types/grafanaTypes.ts b/bitmovin-analytics-datasource/src/types/grafanaTypes.ts index cc8665d..045ec5d 100644 --- a/bitmovin-analytics-datasource/src/types/grafanaTypes.ts +++ b/bitmovin-analytics-datasource/src/types/grafanaTypes.ts @@ -26,7 +26,6 @@ export interface BitmovinAnalyticsDataQuery extends DataQuery { export const DEFAULT_QUERY: Partial = { licenseKey: '', - interval: 'AUTO', orderBy: [], groupBy: [], filters: [], From 72d1ebdc16c687d055293c22350af91278f9cfaf Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Wed, 5 Jun 2024 11:34:02 -0300 Subject: [PATCH 07/13] rename filterRow prop --- bitmovin-analytics-datasource/src/components/FilterRow.tsx | 6 +++--- .../src/components/QueryEditor.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/FilterRow.tsx b/bitmovin-analytics-datasource/src/components/FilterRow.tsx index bc94324..da6cfa5 100644 --- a/bitmovin-analytics-datasource/src/components/FilterRow.tsx +++ b/bitmovin-analytics-datasource/src/components/FilterRow.tsx @@ -41,7 +41,7 @@ const mapFilterRowsToQueryFilters = (filters: FilterRowData[]): QueryFilter[] => type Props = { readonly isAdAnalytics: boolean; - readonly onChange: (newFilters: QueryFilter[]) => void; + readonly onQueryFilterChange: (newFilters: QueryFilter[]) => void; readonly onFilterRowChange: (newFilters: FilterRowData[]) => void; readonly filters: FilterRowData[]; }; @@ -77,7 +77,7 @@ export function FilterRow(props: Props) { props.onFilterRowChange(newFilters); - props.onChange(mapFilterRowsToQueryFilters(newFilters)); + props.onQueryFilterChange(mapFilterRowsToQueryFilters(newFilters)); } catch (e: unknown) { if (e instanceof Error) { const errorMessage = e.message; @@ -97,7 +97,7 @@ export function FilterRow(props: Props) { props.onFilterRowChange(newFilters); - props.onChange(mapFilterRowsToQueryFilters(newFilters)); + props.onQueryFilterChange(mapFilterRowsToQueryFilters(newFilters)); }; const onAttributesChange = (index: number, newAttribute: SelectableValue) => { diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index 7d61c10..c2c2ea8 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -104,7 +104,7 @@ export function QueryEditor(props: Props) { setFilterRows(newFilters); }; - const handleFilterChange = (newFilters: QueryFilter[]) => { + const handleQueryFilterChange = (newFilters: QueryFilter[]) => { props.onChange({ ...query, filters: newFilters }); props.onRunQuery(); }; @@ -196,7 +196,7 @@ export function QueryEditor(props: Props) { From 26d00ad59e04f95b4b301f95702f4104e7ab5544 Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Wed, 5 Jun 2024 11:34:10 -0300 Subject: [PATCH 08/13] delete TODOs --- bitmovin-analytics-datasource/src/components/GroupByRow.tsx | 1 - bitmovin-analytics-datasource/src/components/OrderByRow.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/GroupByRow.tsx b/bitmovin-analytics-datasource/src/components/GroupByRow.tsx index 97e8b6e..e90ba1e 100644 --- a/bitmovin-analytics-datasource/src/components/GroupByRow.tsx +++ b/bitmovin-analytics-datasource/src/components/GroupByRow.tsx @@ -71,7 +71,6 @@ export function GroupByRow(props: Props) { const addGroupByInput = () => { const newDefaultSelectedValue = getSelectableGroupByOptions(props.groupBys, props.isAdAnalytics)[0].value!; - //TODOMY decide on whats the best approach here with the new selected Value, it shouldn't run when adding a new default value, or should it? props.onChange([...props.groupBys, newDefaultSelectedValue]); }; diff --git a/bitmovin-analytics-datasource/src/components/OrderByRow.tsx b/bitmovin-analytics-datasource/src/components/OrderByRow.tsx index 66e5cd3..9708168 100644 --- a/bitmovin-analytics-datasource/src/components/OrderByRow.tsx +++ b/bitmovin-analytics-datasource/src/components/OrderByRow.tsx @@ -9,7 +9,6 @@ import type { QueryOrderBy, QuerySortOrder } from '../types/queryOrderBy'; import { OrderByInput } from './OrderByInput'; import { REORDER_DIRECTION } from './GroupByInput'; -//TODOMY should the selectable not be only available from the Group By attributes? const getSelectableOrderByOptions = ( selectedOrderBys: QueryOrderBy[], isAdAnalytics: boolean @@ -84,7 +83,6 @@ export function OrderByRow(props: Props) { const addOrderByInput = () => { const newDefaultSelectedValue = getSelectableOrderByOptions(props.orderBys, props.isAdAnalytics)[0].value!; - //TODOMY decide on whats the best approach here with the new selected Value, it shouldn't run when adding a new default value, or should it? props.onChange([...props.orderBys, { name: newDefaultSelectedValue, order: 'ASC' }]); }; From dc691b25f11a052ee2f9b997b4b58778064db44b Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Thu, 6 Jun 2024 08:07:05 -0300 Subject: [PATCH 09/13] split useEffect and useMemo for isMetricSelected --- .../src/components/QueryEditor.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index c2c2ea8..c6d9358 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, useEffect, useState } from 'react'; +import React, { ChangeEvent, useEffect, useMemo, useState } from 'react'; import { FieldSet, InlineField, InlineSwitch, Input, Select } from '@grafana/ui'; import type { QueryEditorProps, SelectableValue } from '@grafana/data'; import { defaults } from 'lodash'; @@ -40,8 +40,10 @@ export function QueryEditor(props: Props) { const [licenseLoadingState, setLicenseLoadingState] = useState(LoadingState.Default); const [licenseErrorMessage, setLicenseErrorMessage] = useState(''); const [isTimeSeries, setIsTimeSeries] = useState(!!props.query.interval); - const [isDimensionMetricSelected, setIsDimensionMetricSelected] = useState(false); const [filterRows, setFilterRows] = useState([]); + const isDimensionMetricSelected = useMemo(() => { + return props.query.metric != undefined; + }, [props.query.metric]); useEffect(() => { setLicenseLoadingState(LoadingState.Loading); @@ -54,7 +56,9 @@ export function QueryEditor(props: Props) { setLicenseLoadingState(LoadingState.Error); setLicenseErrorMessage(e.status + ' ' + e.statusText); }); + }, [props.datasource.apiKey, props.datasource.baseUrl]); + useEffect(() => { const filterRows = props.query.filters.map((filter) => { return { attribute: filter.name, @@ -65,7 +69,7 @@ export function QueryEditor(props: Props) { } as FilterRowData; }); setFilterRows(filterRows); - }, [props.datasource.apiKey, props.datasource.baseUrl, props.query.filters]); + }, [props.query.filters]); const query = defaults(props.query, DEFAULT_QUERY); @@ -81,11 +85,9 @@ export function QueryEditor(props: Props) { const handleDimensionChange = (item: SelectableValue) => { if (isMetric(item.value)) { - setIsDimensionMetricSelected(true); props.onChange({ ...query, aggregation: undefined, dimension: undefined, metric: item.value }); } else { - setIsDimensionMetricSelected(false); - props.onChange({ ...query, dimension: item.value }); + props.onChange({ ...query, dimension: item.value, metric: undefined }); } props.onRunQuery(); }; @@ -183,7 +185,7 @@ export function QueryEditor(props: Props) { )} props.onAttributeChange(value)} + value={ + props.filter.attribute + ? mapAttributeToSelectableValue(props.filter.attribute, props.isAdAnalytics) + : undefined + } + onChange={(selectableValue) => props.onAttributeChange(selectableValue)} options={props.selectableFilterAttributes} width={30} /> @@ -65,11 +68,11 @@ export function FilterInput(props: Props) { diff --git a/bitmovin-analytics-datasource/src/components/FilterRow.tsx b/bitmovin-analytics-datasource/src/components/FilterRow.tsx index da6cfa5..fc337cf 100644 --- a/bitmovin-analytics-datasource/src/components/FilterRow.tsx +++ b/bitmovin-analytics-datasource/src/components/FilterRow.tsx @@ -1,14 +1,21 @@ -import React from 'react'; -import { differenceWith, isEmpty } from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { differenceWith } from 'lodash'; import { SelectableValue } from '@grafana/data'; import { Box, HorizontalGroup, IconButton, InlineLabel, VerticalGroup } from '@grafana/ui'; -import { QueryFilter, QueryFilterOperator } from '../types/queryFilter'; +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'; -import type { FilterRowData } from './QueryEditor'; +import { convertFilterValueToProperType, mapQueryFilterValueToRawFilterValue } from '../utils/filterUtils'; + +export type FilterRowData = { + attribute: QueryAdAttribute | QueryAttribute | undefined; + operator: QueryFilterOperator | undefined; + rawFilterValue: string; + convertedFilterValue: QueryFilterValue; + parsingValueError: string; +}; const mapFilterAttributesToSelectableValue = ( filters: FilterRowData[], @@ -32,104 +39,119 @@ const mapFilterAttributesToSelectableValue = ( const mapFilterRowsToQueryFilters = (filters: FilterRowData[]): QueryFilter[] => { return filters.map((filter) => { return { - name: filter.attribute, - operator: filter.operator, + name: filter.attribute!, + operator: filter.operator!, value: filter.convertedFilterValue, - } as QueryFilter; + }; }); }; type Props = { readonly isAdAnalytics: boolean; readonly onQueryFilterChange: (newFilters: QueryFilter[]) => void; - readonly onFilterRowChange: (newFilters: FilterRowData[]) => void; - readonly filters: FilterRowData[]; + readonly filters: QueryFilter[]; }; export function FilterRow(props: Props) { + const [filterInputs, setFilterInputs] = useState([]); + + /** Map QueryFilters to FilterRowData */ + useEffect(() => { + const filterRows = props.filters.map((filter) => { + return { + attribute: filter.name, + operator: filter.operator, + rawFilterValue: mapQueryFilterValueToRawFilterValue(filter.value), + convertedFilterValue: filter.value, + parsingValueError: '', + }; + }); + setFilterInputs(filterRows); + }, [props.filters]); + const addFilterInput = () => { - const newFilters = [...props.filters]; - newFilters.push({ - attribute: {}, - operator: {}, + const newFilterInputs = [...filterInputs]; + newFilterInputs.push({ + attribute: undefined, + operator: undefined, rawFilterValue: '', convertedFilterValue: '', parsingValueError: '', - } as FilterRowData); - props.onFilterRowChange(newFilters); + }); + setFilterInputs(newFilterInputs); }; - const onAddFilter = (index: number) => { - const filter = props.filters[index]; + const onSaveFilter = (index: number) => { + const filter = filterInputs[index]; try { const convertedValue = convertFilterValueToProperType( filter.rawFilterValue, - filter.attribute, - filter.attribute, - filter.operator, + filter.attribute!, + filter.attribute!, + filter.operator!, props.isAdAnalytics ); - const newFilter = { ...filter, convertedFilterValue: convertedValue, parsingValueError: '' } as FilterRowData; + const newFilter: FilterRowData = { ...filter, convertedFilterValue: convertedValue, parsingValueError: '' }; - const newFilters = [...props.filters]; + const newFilters = [...filterInputs]; newFilters.splice(index, 1, newFilter); - props.onFilterRowChange(newFilters); + setFilterInputs(filterInputs); props.onQueryFilterChange(mapFilterRowsToQueryFilters(newFilters)); } catch (e: unknown) { if (e instanceof Error) { const errorMessage = e.message; - const newFilter = { ...filter, parsingValueError: errorMessage } as FilterRowData; + const newFilter: FilterRowData = { ...filter, parsingValueError: errorMessage }; - const newFilters = [...props.filters]; + const newFilters = [...filterInputs]; newFilters.splice(index, 1, newFilter); - props.onFilterRowChange(newFilters); + setFilterInputs(newFilters); } } }; const deleteFilterInput = (index: number) => { - const newFilters = [...props.filters]; + const newFilters = [...filterInputs]; newFilters.splice(index, 1); - props.onFilterRowChange(newFilters); + setFilterInputs(newFilters); props.onQueryFilterChange(mapFilterRowsToQueryFilters(newFilters)); }; const onAttributesChange = (index: number, newAttribute: SelectableValue) => { - const filter = props.filters[index]; - const newFilter = { ...filter, attribute: newAttribute.value } as FilterRowData; - const newFilters = [...props.filters]; + const filter = filterInputs[index]; + const newFilter: FilterRowData = { ...filter, attribute: newAttribute.value! }; + const newFilters = [...filterInputs]; newFilters.splice(index, 1, newFilter); - props.onFilterRowChange(newFilters); + setFilterInputs(newFilters); }; const onOperatorsChange = (index: number, newOperator: SelectableValue) => { - const filter = props.filters[index]; - const newFilter = { ...filter, operator: newOperator.value } as FilterRowData; - const newFilters = [...props.filters]; + const filter = filterInputs[index]; + const newFilter: FilterRowData = { ...filter, operator: newOperator.value! }; + const newFilters = [...filterInputs]; newFilters.splice(index, 1, newFilter); - props.onFilterRowChange(newFilters); + setFilterInputs(newFilters); }; const onValuesChange = (index: number, newValue: string) => { - const filter = props.filters[index]; - const newFilter = { ...filter, rawFilterValue: newValue }; - const newFilters = [...props.filters]; + const filter = filterInputs[index]; + const newFilter: FilterRowData = { ...filter, rawFilterValue: newValue }; + const newFilters = [...filterInputs]; newFilters.splice(index, 1, newFilter); - props.onFilterRowChange(newFilters); + setFilterInputs(newFilters); }; return ( - {props.filters.length !== 0 && ( + {filterInputs.length > 0 && ( Dimension @@ -142,7 +164,7 @@ export function FilterRow(props: Props) { )} - {props.filters.map((filter, index, filtersArray) => ( + {filterInputs.map((filter, index, filtersArray) => ( ) => onOperatorsChange(index, newValue)} onValueChange={(newValue: string) => onValuesChange(index, newValue)} onDelete={() => deleteFilterInput(index)} - addFilterDisabled={isEmpty(filter.attribute) || isEmpty(filter.operator)} - onAddFilter={() => onAddFilter(index)} + onSaveFilter={() => onSaveFilter(index)} /> ))} - + addFilterInput()} size="xl" /> diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index 5702e8f..a208f84 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -14,9 +14,8 @@ import { isMetric, SELECTABLE_METRICS } from '../types/metric'; import { GroupByRow } from './GroupByRow'; import { OrderByRow } from './OrderByRow'; import type { QueryOrderBy } from '../types/queryOrderBy'; -import type { QueryFilter, QueryFilterOperator, QueryFilterValue } from '../types/queryFilter'; +import type { QueryFilter } from '../types/queryFilter'; import { FilterRow } from './FilterRow'; -import { mapFilterValueToRawFilterValue } from '../utils/filterUtils'; enum LoadingState { Default = 'DEFAULT', @@ -25,14 +24,6 @@ enum LoadingState { Error = 'ERROR', } -export type FilterRowData = { - attribute: QueryAdAttribute | QueryAttribute; - operator: QueryFilterOperator; - rawFilterValue: string; - convertedFilterValue: QueryFilterValue; - parsingValueError: string; -}; - type Props = QueryEditorProps; export function QueryEditor(props: Props) { @@ -40,11 +31,11 @@ export function QueryEditor(props: Props) { const [licenseLoadingState, setLicenseLoadingState] = useState(LoadingState.Default); const [licenseErrorMessage, setLicenseErrorMessage] = useState(''); const [isTimeSeries, setIsTimeSeries] = useState(!!props.query.interval); - const [filterRows, setFilterRows] = useState([]); const isDimensionMetricSelected = useMemo(() => { return props.query.metric !== undefined; }, [props.query.metric]); + /** Fetch Licenses */ useEffect(() => { setLicenseLoadingState(LoadingState.Loading); fetchLicenses(props.datasource.apiKey, props.datasource.baseUrl) @@ -58,19 +49,6 @@ export function QueryEditor(props: Props) { }); }, [props.datasource.apiKey, props.datasource.baseUrl]); - useEffect(() => { - const filterRows = props.query.filters.map((filter) => { - return { - attribute: filter.name, - operator: filter.operator, - rawFilterValue: mapFilterValueToRawFilterValue(filter.value), - convertedFilterValue: filter.value, - parsingValueError: '', - } as FilterRowData; - }); - setFilterRows(filterRows); - }, [props.query.filters]); - const query = defaults(props.query, DEFAULT_QUERY); const handleLicenseChange = (item: SelectableValue) => { @@ -102,10 +80,6 @@ export function QueryEditor(props: Props) { props.onRunQuery(); }; - const handleFilterRowChange = (newFilters: FilterRowData[]) => { - setFilterRows(newFilters); - }; - const handleQueryFilterChange = (newFilters: QueryFilter[]) => { props.onChange({ ...query, filters: newFilters }); props.onRunQuery(); @@ -199,8 +173,7 @@ export function QueryEditor(props: Props) { diff --git a/bitmovin-analytics-datasource/src/utils/filterUtils.ts b/bitmovin-analytics-datasource/src/utils/filterUtils.ts index ca3b9ed..3c20fc0 100644 --- a/bitmovin-analytics-datasource/src/utils/filterUtils.ts +++ b/bitmovin-analytics-datasource/src/utils/filterUtils.ts @@ -4,7 +4,12 @@ import { QUERY_AD_ATTRIBUTES, QueryAdAttribute } from '../types/queryAdAttribute import { QUERY_FILTER_OPERATORS, QueryFilterOperator, QueryFilterValue } from '../types/queryFilter'; import { QUERY_ATTRIBUTES, QueryAttribute } from '../types/queryAttributes'; -export const mapFilterValueToRawFilterValue = (filterValue: QueryFilterValue) => { +/** + * Convert QueryFilter value to string to correctly display the value in the Input Element. + * @param {QueryFilterValue} filterValue the filter value with the from our API expected correct type + * @return {string} the rawFilterValue as a string + * */ +export const mapQueryFilterValueToRawFilterValue = (filterValue: QueryFilterValue): string => { if (filterValue == null) { return ''; } else if (Array.isArray(filterValue)) { From 70a7b029e6026bcd04af4bf6c896fc8c83e8266b Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Fri, 7 Jun 2024 11:35:52 -0300 Subject: [PATCH 12/13] implement PR feedback --- .../src/components/FilterInput.tsx | 6 +++--- .../src/components/GroupByInput.tsx | 2 +- .../src/components/OrderByInput.tsx | 2 +- bitmovin-analytics-datasource/src/components/OrderByRow.tsx | 4 ++-- bitmovin-analytics-datasource/src/types/aggregations.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/FilterInput.tsx b/bitmovin-analytics-datasource/src/components/FilterInput.tsx index dcd7c36..dc34f7b 100644 --- a/bitmovin-analytics-datasource/src/components/FilterInput.tsx +++ b/bitmovin-analytics-datasource/src/components/FilterInput.tsx @@ -48,8 +48,8 @@ export function FilterInput(props: Props) { width={30} /> props.onChange(value.value!)} + onChange={(selectableValue) => props.onChange(selectableValue.value!)} options={props.selectableGroupBys} width={30} /> diff --git a/bitmovin-analytics-datasource/src/components/OrderByInput.tsx b/bitmovin-analytics-datasource/src/components/OrderByInput.tsx index 04d47d7..3bc2590 100644 --- a/bitmovin-analytics-datasource/src/components/OrderByInput.tsx +++ b/bitmovin-analytics-datasource/src/components/OrderByInput.tsx @@ -31,7 +31,7 @@ export function OrderByInput(props: Props) {