From 33a6c7d999c3abba284b763c25b480495e9c6a7d Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Thu, 2 May 2024 07:20:52 -0300 Subject: [PATCH 01/18] implements license fetching and interval selection --- .../src/components/QueryEditor.tsx | 99 ++++++++++++++++--- .../src/datasource.ts | 54 +++++----- bitmovin-analytics-datasource/src/types.ts | 9 +- .../src/utils/dataUtils.ts | 16 ++- .../src/utils/intervalUtils.ts | 72 ++++++++++++++ .../src/utils/licenses.ts | 61 ++++++++++++ 6 files changed, 263 insertions(+), 48 deletions(-) create mode 100644 bitmovin-analytics-datasource/src/utils/licenses.ts diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index f9b0647..1baf383 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -1,32 +1,99 @@ -import React, { ChangeEvent } from 'react'; -import { InlineField, Input } from '@grafana/ui'; -import { QueryEditorProps } from '@grafana/data'; +import React, { ChangeEvent, useEffect, useState } from 'react'; +import { FieldSet, InlineField, InlineSwitch, Select } from '@grafana/ui'; +import { QueryEditorProps, SelectableValue } from '@grafana/data'; + import { DataSource } from '../datasource'; import { MyDataSourceOptions, MyQuery } from '../types'; +import { fetchLicenses } from '../utils/licenses'; +import { DEFAULT_SELECTABLE_QUERY_INTERVAL, SELECTABLE_QUERY_INTERVALS } from '../utils/intervalUtils'; type Props = QueryEditorProps; -export function QueryEditor({ query, onChange, onRunQuery }: Props) { - const onQueryTextChange = (event: ChangeEvent) => { - onChange({ ...query, queryText: event.target.value }); +export enum LoadingState { + Default = 'DEFAULT', + Loading = 'LOADING', + Success = 'SUCCESS', + Error = 'ERROR', +} + +export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props) { + const [selectableLicenses, setSelectableLicenses] = useState([]); + const [licenseLoadingState, setLicenseLoadingState] = useState(LoadingState.Default); + const [licenseErrorMessage, setLicenseErrorMessage] = useState(''); + const [isTimeSeries, setIsTimeSeries] = useState(true); + + useEffect(() => { + setLicenseLoadingState(LoadingState.Loading); + fetchLicenses(datasource.apiKey, datasource.baseUrl) + .then((licenses) => { + setSelectableLicenses(licenses); + setLicenseLoadingState(LoadingState.Success); + }) + .catch((e) => { + setLicenseLoadingState(LoadingState.Error); + setLicenseErrorMessage(e.status + ' ' + e.statusText); + }); + }, []); + + const onLicenseChange = (item: SelectableValue) => { + onChange({ ...query, licenseKey: item.value }); + onRunQuery(); }; - const onConstantChange = (event: ChangeEvent) => { - onChange({ ...query, constant: parseFloat(event.target.value) }); - // executes the query + const onFormatAsTimeSeriesChange = (event: ChangeEvent) => { + setIsTimeSeries(event.currentTarget.checked); + if (event.currentTarget.checked) { + onChange({ ...query, interval: 'AUTO' }); + } else { + onChange({ ...query, interval: undefined }); + } onRunQuery(); }; - const { queryText, constant } = query; + const onIntervalChange = (item: SelectableValue) => { + onChange({ ...query, interval: item.value }); + onRunQuery(); + }; + + const renderTimeSeriesOption = () => { + return ( + <> + + - - - - +
+ + onMetricChange(item)} + width={40} + options={SELECTABLE_AGGREGATIONS} + /> + diff --git a/bitmovin-analytics-datasource/src/datasource.ts b/bitmovin-analytics-datasource/src/datasource.ts index 917d2b4..9be8ea3 100644 --- a/bitmovin-analytics-datasource/src/datasource.ts +++ b/bitmovin-analytics-datasource/src/datasource.ts @@ -54,6 +54,7 @@ export class DataSource extends DataSourceApi { const to = range!.to.toDate(); const promises = options.targets.map(async (target) => { + const urlAppendix = target.aggregation; const interval = target.interval ? calculateQueryInterval(target.interval!, from.getTime(), to.getTime()) : undefined; @@ -82,7 +83,7 @@ export class DataSource extends DataSourceApi { interval: interval, }; - const response = await lastValueFrom(this.request(this.getRequestUrl(), 'POST', query)); + const response = await lastValueFrom(this.request(this.getRequestUrl() + urlAppendix, 'POST', query)); const dataRows: MixedDataRowList = response.data.data.result.rows; const columnLabels: Array<{ key: string; label: string }> = response.data.data.result.columnLabels; @@ -121,10 +122,10 @@ export class DataSource extends DataSourceApi { getRequestUrl(): string { if (this.adAnalytics === true) { - return '/analytics/ads/queries'; + return '/analytics/ads/queries/'; } - return '/analytics/queries/count'; + return '/analytics/queries/'; } request(url: string, method: string, payload?: any): Observable> { diff --git a/bitmovin-analytics-datasource/src/types.ts b/bitmovin-analytics-datasource/src/types.ts index 84ceeee..b2831fc 100644 --- a/bitmovin-analytics-datasource/src/types.ts +++ b/bitmovin-analytics-datasource/src/types.ts @@ -1,10 +1,12 @@ import { DataSourceJsonData } from '@grafana/data'; import { DataQuery } from '@grafana/schema'; import { QueryInterval } from './utils/intervalUtils'; +import { Aggregation } from './types/aggregations'; export interface MyQuery extends DataQuery { licenseKey: string; interval?: QueryInterval | 'AUTO'; + aggregation: Aggregation; } export const DEFAULT_QUERY: Partial = {}; diff --git a/bitmovin-analytics-datasource/src/types/aggregations.ts b/bitmovin-analytics-datasource/src/types/aggregations.ts new file mode 100644 index 0000000..273a437 --- /dev/null +++ b/bitmovin-analytics-datasource/src/types/aggregations.ts @@ -0,0 +1,15 @@ +export type Aggregation = 'count' | 'sum' | 'avg' | 'min' | 'max' | 'stddev' | 'percentile' | 'variance' | 'median'; + +export const SELECTABLE_AGGREGATIONS: Array<{ value: Aggregation; label: string }> = [ + { value: 'count', label: 'Count' }, + { value: 'sum', label: 'Sum' }, + { value: 'avg', label: 'Avg' }, + { value: 'min', label: 'Min' }, + { value: 'max', label: 'Max' }, + { value: 'stddev', label: 'Stddev' }, + { value: 'percentile', label: 'Percentile' }, + { value: 'variance', label: 'Variance' }, + { value: 'median', label: 'Median' }, +]; + +export const DEFAULT_SELECTABLE_AGGREGATION = SELECTABLE_AGGREGATIONS[0]; From 737bdd015287b2c808e6d1320a4dad40de1ede4e Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Thu, 2 May 2024 16:36:11 -0300 Subject: [PATCH 08/18] implement dimension selection --- .../src/components/QueryEditor.tsx | 14 + .../src/datasource.ts | 6 +- bitmovin-analytics-datasource/src/types.ts | 3 + .../src/types/queryAdAttributes.ts | 301 ++++++++++++++++++ .../src/types/queryAttributes.ts | 277 ++++++++++++++++ 5 files changed, 599 insertions(+), 2 deletions(-) create mode 100644 bitmovin-analytics-datasource/src/types/queryAdAttributes.ts create mode 100644 bitmovin-analytics-datasource/src/types/queryAttributes.ts diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index 5f52b62..87e02c6 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -7,6 +7,8 @@ import { MyDataSourceOptions, MyQuery } from '../types'; import { fetchLicenses } from '../utils/licenses'; import { DEFAULT_SELECTABLE_QUERY_INTERVAL, SELECTABLE_QUERY_INTERVALS } from '../utils/intervalUtils'; import { DEFAULT_SELECTABLE_AGGREGATION, SELECTABLE_AGGREGATIONS } from '../types/aggregations'; +import { SELECTABLE_QUERY_AD_ATTRIBUTES } from '../types/queryAdAttributes'; +import { SELECTABLE_QUERY_ATTRIBUTES } from '../types/queryAttributes'; enum LoadingState { Default = 'DEFAULT', @@ -46,6 +48,11 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props) onRunQuery(); }; + const onDimensionChange = (item: SelectableValue) => { + onChange({ ...query, dimension: item.value }); + onRunQuery(); + }; + const onFormatAsTimeSeriesChange = (event: ChangeEvent) => { setIsTimeSeries(event.currentTarget.checked); if (event.currentTarget.checked) { @@ -103,6 +110,13 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props) options={SELECTABLE_AGGREGATIONS} /> + + onMetricChange(item)} - width={40} - options={SELECTABLE_AGGREGATIONS} - /> - + {!isDimensionMetricSelected && ( + + diff --git a/bitmovin-analytics-datasource/src/datasource.ts b/bitmovin-analytics-datasource/src/datasource.ts index fbf980c..d1584e8 100644 --- a/bitmovin-analytics-datasource/src/datasource.ts +++ b/bitmovin-analytics-datasource/src/datasource.ts @@ -14,12 +14,15 @@ import { transformGroupedTimeSeriesData, transformSimpleTimeSeries, transformTab import { calculateQueryInterval, QueryInterval } from './utils/intervalUtils'; import { QueryAttribute } from './types/queryAttributes'; import { QueryAdAttribute } from './types/queryAdAttributes'; +import { Metric } from './types/metric'; +import { Aggregation } from './types/aggregations'; type AnalyticsQuery = { filters: Array<{ name: string; operator: string; value: number }>; groupBy: string[]; orderBy: Array<{ name: string; order: string }>; - dimension: QueryAttribute | QueryAdAttribute; + dimension?: QueryAttribute | QueryAdAttribute; + metric?: Metric; start: Date; end: Date; licenseKey: string; @@ -56,7 +59,6 @@ export class DataSource extends DataSourceApi { const to = range!.to.toDate(); const promises = options.targets.map(async (target) => { - const urlAppendix = target.aggregation; const interval = target.interval ? calculateQueryInterval(target.interval!, from.getTime(), to.getTime()) : undefined; @@ -85,7 +87,9 @@ export class DataSource extends DataSourceApi { interval: interval, }; - const response = await lastValueFrom(this.request(this.getRequestUrl() + urlAppendix, 'POST', query)); + const response = await lastValueFrom( + this.request(this.getRequestUrl(target.metric, target.aggregation), 'POST', query) + ); const dataRows: MixedDataRowList = response.data.data.result.rows; const columnLabels: Array<{ key: string; label: string }> = response.data.data.result.columnLabels; @@ -122,12 +126,17 @@ export class DataSource extends DataSourceApi { return Promise.all(promises).then((data) => ({ data })); } - getRequestUrl(): string { + getRequestUrl(metric?: Metric, aggregation?: Aggregation): string { + let url = '/analytics'; if (this.adAnalytics === true) { - return '/analytics/ads/queries/'; + url += '/ads'; } - return '/analytics/queries/'; + if (metric !== undefined) { + return url + '/metrics/' + metric; + } + + return url + '/queries/' + aggregation; } request(url: string, method: string, payload?: any): Observable> { diff --git a/bitmovin-analytics-datasource/src/types.ts b/bitmovin-analytics-datasource/src/types.ts index 72b5fcc..d53deec 100644 --- a/bitmovin-analytics-datasource/src/types.ts +++ b/bitmovin-analytics-datasource/src/types.ts @@ -4,12 +4,14 @@ import { QueryInterval } from './utils/intervalUtils'; import { Aggregation } from './types/aggregations'; import { QueryAttribute } from './types/queryAttributes'; import { QueryAdAttribute } from './types/queryAdAttributes'; +import { Metric } from './types/metric'; export interface MyQuery extends DataQuery { licenseKey: string; interval?: QueryInterval | 'AUTO'; - aggregation: Aggregation; - dimension: QueryAttribute | QueryAdAttribute; + aggregation?: Aggregation; + metric?: Metric; + dimension?: QueryAttribute | QueryAdAttribute; } export const DEFAULT_QUERY: Partial = {}; diff --git a/bitmovin-analytics-datasource/src/types/metric.ts b/bitmovin-analytics-datasource/src/types/metric.ts new file mode 100644 index 0000000..37743ba --- /dev/null +++ b/bitmovin-analytics-datasource/src/types/metric.ts @@ -0,0 +1,19 @@ +import { SelectableValue } from '@grafana/data'; + +export enum METRICS { + AVG_CONCURRENTVIEWERS = 'avg-concurrentviewers', + MAX_CONCURRENTVIEWERS = 'max-concurrentviewers', + AVG_DROPPED_FRAMES = 'avg-dropped-frames', +} + +export type Metric = (typeof METRICS)[keyof typeof METRICS]; + +export const SELECTABLE_METRICS: Array> = [ + { value: METRICS.AVG_CONCURRENTVIEWERS, label: 'Avg Concurrent Viewers' }, + { value: METRICS.MAX_CONCURRENTVIEWERS, label: 'Max Concurrent Viewers' }, + { value: METRICS.AVG_DROPPED_FRAMES, label: 'Avg Dropped Frames' }, +]; + +export const isMetric = (value: string): boolean => { + return Object.values(METRICS).includes(value as Metric); +}; From f4c55a6c42be1d0f1b51950b0c4356c62867d676 Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Fri, 3 May 2024 08:23:58 -0300 Subject: [PATCH 11/18] add metric to query and check for null instead of undefined --- bitmovin-analytics-datasource/src/datasource.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bitmovin-analytics-datasource/src/datasource.ts b/bitmovin-analytics-datasource/src/datasource.ts index d1584e8..54f3027 100644 --- a/bitmovin-analytics-datasource/src/datasource.ts +++ b/bitmovin-analytics-datasource/src/datasource.ts @@ -81,6 +81,7 @@ export class DataSource extends DataSourceApi { ] : [], dimension: target.dimension, + metric: target.metric, start: from, end: to, licenseKey: target.licenseKey, @@ -132,7 +133,7 @@ export class DataSource extends DataSourceApi { url += '/ads'; } - if (metric !== undefined) { + if (metric != null) { return url + '/metrics/' + metric; } From 40f0da90db5fe47ba1453a93352ae085d385f2bf Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Fri, 3 May 2024 11:52:07 -0300 Subject: [PATCH 12/18] implements groupBy selection --- .../src/components/GroupByInput.tsx | 22 ++++++ .../src/components/GroupByRow.tsx | 69 +++++++++++++++++++ .../src/components/QueryEditor.tsx | 21 ++++-- .../src/datasource.ts | 4 +- bitmovin-analytics-datasource/src/types.ts | 1 + 5 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 bitmovin-analytics-datasource/src/components/GroupByInput.tsx create mode 100644 bitmovin-analytics-datasource/src/components/GroupByRow.tsx diff --git a/bitmovin-analytics-datasource/src/components/GroupByInput.tsx b/bitmovin-analytics-datasource/src/components/GroupByInput.tsx new file mode 100644 index 0000000..b0d3588 --- /dev/null +++ b/bitmovin-analytics-datasource/src/components/GroupByInput.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { HorizontalGroup, IconButton, Select } from '@grafana/ui'; +import { QueryAttribute } from '../types/queryAttributes'; +import { QueryAdAttribute } from '../types/queryAdAttributes'; +import { SelectableValue } from '@grafana/data'; + +type Props = { + groupBy: SelectableValue; + selectableGroupBys: Array>; + onDelete: () => void; + onChange: (newValue: SelectableValue) => void; +}; +export function GroupByInput(props: Props) { + //TODOMY delete margin between select and icon + //TODOMY border around the icon + return ( + + onIntervalChange(item)} - width={40} + width={30} options={SELECTABLE_QUERY_INTERVALS} /> @@ -103,7 +109,7 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props) > + + + diff --git a/bitmovin-analytics-datasource/src/datasource.ts b/bitmovin-analytics-datasource/src/datasource.ts index 54f3027..db37e2d 100644 --- a/bitmovin-analytics-datasource/src/datasource.ts +++ b/bitmovin-analytics-datasource/src/datasource.ts @@ -19,7 +19,7 @@ import { Aggregation } from './types/aggregations'; type AnalyticsQuery = { filters: Array<{ name: string; operator: string; value: number }>; - groupBy: string[]; + groupBy: QueryAttribute[] | QueryAdAttribute[]; orderBy: Array<{ name: string; order: string }>; dimension?: QueryAttribute | QueryAdAttribute; metric?: Metric; @@ -71,7 +71,7 @@ export class DataSource extends DataSourceApi { value: 0, }, ], - groupBy: [], + groupBy: target.groupBy, orderBy: interval ? [ { diff --git a/bitmovin-analytics-datasource/src/types.ts b/bitmovin-analytics-datasource/src/types.ts index d53deec..a25440d 100644 --- a/bitmovin-analytics-datasource/src/types.ts +++ b/bitmovin-analytics-datasource/src/types.ts @@ -12,6 +12,7 @@ export interface MyQuery extends DataQuery { aggregation?: Aggregation; metric?: Metric; dimension?: QueryAttribute | QueryAdAttribute; + groupBy: QueryAttribute[] | QueryAdAttribute[]; } export const DEFAULT_QUERY: Partial = {}; From 862cb9248a65b54527f1fa92c455224f0528f75e Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Mon, 6 May 2024 11:19:01 -0300 Subject: [PATCH 13/18] fix linting --- .../src/components/GroupByRow.tsx | 7 ++++--- .../src/components/QueryEditor.tsx | 18 +++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/GroupByRow.tsx b/bitmovin-analytics-datasource/src/components/GroupByRow.tsx index b0779e3..4f910a3 100644 --- a/bitmovin-analytics-datasource/src/components/GroupByRow.tsx +++ b/bitmovin-analytics-datasource/src/components/GroupByRow.tsx @@ -8,7 +8,7 @@ import { difference } from 'lodash'; type Props = { readonly isAdAnalytics: boolean; - readonly onChange: (newGroupBys: Array | Array) => void; + readonly onChange: (newGroupBys: QueryAdAttribute[] | QueryAttribute[]) => void; }; export function GroupByRow(props: Props) { @@ -31,7 +31,7 @@ export function GroupByRow(props: Props) { setSelectedGroupBys(newSelectedGroupBys); const groupBys = newSelectedGroupBys.map((groupBy) => groupBy.value); - props.onChange(groupBys as Array | Array); + props.onChange(groupBys as QueryAttribute[] | QueryAdAttribute[]); }; const onSelectedGroupByChange = ( @@ -43,7 +43,7 @@ export function GroupByRow(props: Props) { setSelectedGroupBys(newSelectedGroupBys); const groupBys = newSelectedGroupBys.map((groupBy) => groupBy.value); - props.onChange(groupBys as Array | Array); + props.onChange(groupBys as QueryAttribute[] | QueryAdAttribute[]); }; const addGroupByInput = () => { @@ -55,6 +55,7 @@ export function GroupByRow(props: Props) { {selectedGroupBys?.map((item, index) => ( ) => onSelectedGroupByChange(index, newValue) diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index 6e10bd1..9f4701d 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -7,8 +7,8 @@ import { MyDataSourceOptions, BitmovinAnalyticsDataQuery } from '../types'; import { fetchLicenses } from '../utils/licenses'; import { DEFAULT_SELECTABLE_QUERY_INTERVAL, SELECTABLE_QUERY_INTERVALS } from '../utils/intervalUtils'; import { DEFAULT_SELECTABLE_AGGREGATION, SELECTABLE_AGGREGATIONS } from '../types/aggregations'; -import {QueryAdAttribute, SELECTABLE_QUERY_AD_ATTRIBUTES} from '../types/queryAdAttributes'; -import {QueryAttribute, SELECTABLE_QUERY_ATTRIBUTES} from '../types/queryAttributes'; +import { QueryAdAttribute, SELECTABLE_QUERY_AD_ATTRIBUTES } from '../types/queryAdAttributes'; +import { QueryAttribute, SELECTABLE_QUERY_ATTRIBUTES } from '../types/queryAttributes'; import { isMetric, SELECTABLE_METRICS } from '../types/metric'; import { GroupByRow } from './GroupByRow'; @@ -62,10 +62,10 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props) onRunQuery(); }; - const onGroupByChange = (newGroupBys: Array | Array) => { - onChange({ ...query, groupBy: newGroupBys }); - onRunQuery(); - }; + const onGroupByChange = (newGroupBys: QueryAdAttribute[] | QueryAttribute[]) => { + onChange({ ...query, groupBy: newGroupBys }); + onRunQuery(); + }; const onFormatAsTimeSeriesChange = (event: ChangeEvent) => { setIsTimeSeries(event.currentTarget.checked); @@ -137,9 +137,9 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props) } /> - - - + + + From 1abcfadce76d1916e8a0169f334c551ae0969ff9 Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Tue, 7 May 2024 08:49:32 -0300 Subject: [PATCH 14/18] implements reordering of GroupBys and fixes position of Add Button --- .../src/components/GroupByInput.tsx | 33 +++++++++++++++--- .../src/components/GroupByRow.tsx | 34 ++++++++++++++----- .../src/components/QueryEditor.tsx | 8 ++--- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/GroupByInput.tsx b/bitmovin-analytics-datasource/src/components/GroupByInput.tsx index b0d3588..56de55d 100644 --- a/bitmovin-analytics-datasource/src/components/GroupByInput.tsx +++ b/bitmovin-analytics-datasource/src/components/GroupByInput.tsx @@ -1,22 +1,47 @@ import React from 'react'; +import { SelectableValue } from '@grafana/data'; import { HorizontalGroup, IconButton, Select } from '@grafana/ui'; + import { QueryAttribute } from '../types/queryAttributes'; import { QueryAdAttribute } from '../types/queryAdAttributes'; -import { SelectableValue } from '@grafana/data'; type Props = { groupBy: SelectableValue; selectableGroupBys: Array>; onDelete: () => void; onChange: (newValue: SelectableValue) => void; + isFirst: boolean; + isLast: boolean; + onReorderGroupBy: (direction: REORDER_DIRECTION) => void; }; + +export enum REORDER_DIRECTION { + UP, + DOWN, +} export function GroupByInput(props: Props) { - //TODOMY delete margin between select and icon - //TODOMY border around the icon return ( onIntervalChange(item)} - width={40} + width={30} options={SELECTABLE_QUERY_INTERVALS} /> @@ -109,7 +109,7 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props) > Date: Tue, 7 May 2024 08:54:26 -0300 Subject: [PATCH 15/18] make props readonly --- .../src/components/GroupByInput.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/bitmovin-analytics-datasource/src/components/GroupByInput.tsx b/bitmovin-analytics-datasource/src/components/GroupByInput.tsx index 56de55d..1ac2561 100644 --- a/bitmovin-analytics-datasource/src/components/GroupByInput.tsx +++ b/bitmovin-analytics-datasource/src/components/GroupByInput.tsx @@ -5,20 +5,21 @@ import { HorizontalGroup, IconButton, Select } from '@grafana/ui'; import { QueryAttribute } from '../types/queryAttributes'; import { QueryAdAttribute } from '../types/queryAdAttributes'; -type Props = { - groupBy: SelectableValue; - selectableGroupBys: Array>; - onDelete: () => void; - onChange: (newValue: SelectableValue) => void; - isFirst: boolean; - isLast: boolean; - onReorderGroupBy: (direction: REORDER_DIRECTION) => void; -}; - export enum REORDER_DIRECTION { UP, DOWN, } + +type Props = { + readonly groupBy: SelectableValue; + readonly selectableGroupBys: Array>; + readonly onDelete: () => void; + readonly onChange: (newValue: SelectableValue) => void; + readonly isFirst: boolean; + readonly isLast: boolean; + readonly onReorderGroupBy: (direction: REORDER_DIRECTION) => void; +}; + export function GroupByInput(props: Props) { return ( From 556fd9cdbb7f1c0b5cef711157294eb3ba2c7be8 Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Tue, 7 May 2024 13:48:13 -0300 Subject: [PATCH 16/18] implements order By selection --- .../src/components/GroupByRow.tsx | 4 +- .../src/components/OrderByInput.tsx | 63 +++++++++++ .../src/components/OrderByRow.tsx | 103 ++++++++++++++++++ .../src/components/QueryEditor.tsx | 10 ++ .../src/datasource.ts | 13 +-- bitmovin-analytics-datasource/src/types.ts | 2 + .../src/types/queryOrderBy.ts | 14 +++ 7 files changed, 197 insertions(+), 12 deletions(-) create mode 100644 bitmovin-analytics-datasource/src/components/OrderByInput.tsx create mode 100644 bitmovin-analytics-datasource/src/components/OrderByRow.tsx create mode 100644 bitmovin-analytics-datasource/src/types/queryOrderBy.ts diff --git a/bitmovin-analytics-datasource/src/components/GroupByRow.tsx b/bitmovin-analytics-datasource/src/components/GroupByRow.tsx index b6e7772..441a417 100644 --- a/bitmovin-analytics-datasource/src/components/GroupByRow.tsx +++ b/bitmovin-analytics-datasource/src/components/GroupByRow.tsx @@ -66,7 +66,7 @@ export function GroupByRow(props: Props) { return ( - {selectedGroupBys?.map((item, index, groupBys) => ( + {selectedGroupBys.map((item, index, groupBys) => ( ))} - addGroupByInput()} size="xxl" /> + addGroupByInput()} size="xl" /> ); diff --git a/bitmovin-analytics-datasource/src/components/OrderByInput.tsx b/bitmovin-analytics-datasource/src/components/OrderByInput.tsx new file mode 100644 index 0000000..c977a51 --- /dev/null +++ b/bitmovin-analytics-datasource/src/components/OrderByInput.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { SelectableValue } from '@grafana/data'; +import { HorizontalGroup, IconButton, RadioButtonGroup, Select } from '@grafana/ui'; + +import { QueryAttribute } from '../types/queryAttributes'; +import { QueryAdAttribute } from '../types/queryAdAttributes'; +import { QuerySortOrder } from '../types/queryOrderBy'; +import { REORDER_DIRECTION } from './GroupByInput'; + +type Props = { + readonly isAdAnalytics: boolean; + readonly attribute: QueryAttribute | QueryAdAttribute | ''; + readonly selectableGroupByAttributes: Array>; + readonly onAttributeChange: (newValue: QueryAttribute | QueryAdAttribute) => void; + readonly sortOrder: QuerySortOrder; + readonly onSortOrderChange: (newValue: QuerySortOrder) => void; + readonly onDelete: () => void; + readonly isFirst: boolean; + readonly isLast: boolean; + readonly onReorderGroupBy: (direction: REORDER_DIRECTION) => void; +}; + +const sortOrderOption: SelectableValue[] = [ + { value: 'ASC', description: 'Sort by ascending', icon: 'sort-amount-up' }, + { value: 'DESC', description: 'Sort by descending', icon: 'sort-amount-down' }, +]; + +export function OrderByInput(props: Props) { + return ( + + props.onAttributeChange(value.value as QueryAttribute | QueryAdAttribute)} - options={props.selectableGroupByAttributes} + onChange={(value) => props.onAttributeChange(value)} + options={props.selectableOrderByAttributes} width={30} /> props.onReorderGroupBy(REORDER_DIRECTION.DOWN)} + onClick={() => props.onReorderOrderBy(REORDER_DIRECTION.DOWN)} name="arrow-down" disabled={props.isLast} /> props.onReorderGroupBy(REORDER_DIRECTION.UP)} + onClick={() => props.onReorderOrderBy(REORDER_DIRECTION.UP)} name="arrow-up" disabled={props.isFirst} /> diff --git a/bitmovin-analytics-datasource/src/components/OrderByRow.tsx b/bitmovin-analytics-datasource/src/components/OrderByRow.tsx index 89b3086..61d0abd 100644 --- a/bitmovin-analytics-datasource/src/components/OrderByRow.tsx +++ b/bitmovin-analytics-datasource/src/components/OrderByRow.tsx @@ -1,11 +1,11 @@ import React, { useState } from 'react'; import { Box, IconButton, VerticalGroup } from '@grafana/ui'; import { SelectableValue } from '@grafana/data'; -import { cloneDeep, differenceWith } from 'lodash'; +import { difference } from 'lodash'; import { QueryAdAttribute, SELECTABLE_QUERY_AD_ATTRIBUTES } from '../types/queryAdAttributes'; import { QueryAttribute, SELECTABLE_QUERY_ATTRIBUTES } from '../types/queryAttributes'; -import { QueryOrderBy, QuerySortOrder, SelectableQueryOrderBy } from '../types/queryOrderBy'; +import { QueryOrderBy, QuerySortOrder } from '../types/queryOrderBy'; import { OrderByInput } from './OrderByInput'; import { REORDER_DIRECTION } from './GroupByInput'; @@ -15,87 +15,104 @@ type Props = { }; export function OrderByRow(props: Props) { - const [orderBys, setOrderBys] = useState([]); + const [selectedAttributes, setSelectedAttributes] = useState< + Array> + >([]); + const [selectedSortOrders, setSelectedSortOrders] = useState([]); const mapOrderBysToSelectableValue = (): Array> => { if (props.isAdAnalytics) { - const orderByAdAttributes = orderBys.map((orderBy) => orderBy.name as QueryAdAttribute); - return differenceWith( - SELECTABLE_QUERY_AD_ATTRIBUTES, - orderByAdAttributes, - (selectableAttribute, orderByAdAttribute) => selectableAttribute.value === orderByAdAttribute - ); + return difference(SELECTABLE_QUERY_AD_ATTRIBUTES, selectedAttributes); } else { - const orderByAttributes = orderBys.map((orderBy) => orderBy.name as QueryAdAttribute); - return differenceWith( - SELECTABLE_QUERY_ATTRIBUTES, - orderByAttributes, - (selectableAttribute, orderByAttribute) => selectableAttribute.value === orderByAttribute - ); + return difference(SELECTABLE_QUERY_ATTRIBUTES, selectedAttributes); } }; + const mapSelectedValuesToQueryOrderBy = ( + selectedAttributes: Array>, + selectedSortOrders: QuerySortOrder[] + ): QueryOrderBy[] => { + const queryOrderBys: QueryOrderBy[] = []; + for (let i = 0; i < selectedAttributes.length; i++) { + queryOrderBys.push({ + name: selectedAttributes[i].value!, + order: selectedSortOrders[i], + }); + } + return queryOrderBys; + }; + const deleteOrderByInput = (index: number) => { - const newOrderBys = [...orderBys]; - newOrderBys.splice(index, 1); + const newSelectedAttributes = [...selectedAttributes]; + newSelectedAttributes.splice(index, 1); + + const newSelectedSortOrders = [...selectedSortOrders]; + newSelectedSortOrders.splice(index, 1); - setOrderBys(newOrderBys); - props.onChange(newOrderBys as QueryOrderBy[]); + setSelectedAttributes(newSelectedAttributes); + setSelectedSortOrders(newSelectedSortOrders); + props.onChange(mapSelectedValuesToQueryOrderBy(newSelectedAttributes, newSelectedSortOrders)); }; - const onAttributesChange = (index: number, newAttribute: QueryAttribute | QueryAdAttribute) => { - const newOrderBys = [...orderBys]; - const newValue = { name: newAttribute, order: newOrderBys[index].order } as QueryOrderBy; - newOrderBys.splice(index, 1, newValue); - setOrderBys(newOrderBys); - props.onChange(newOrderBys as QueryOrderBy[]); + const onAttributesChange = (index: number, newAttribute: SelectableValue) => { + const newSelectedAttributes = [...selectedAttributes]; + newSelectedAttributes.splice(index, 1, newAttribute); + setSelectedAttributes(newSelectedAttributes); + + props.onChange(mapSelectedValuesToQueryOrderBy(newSelectedAttributes, selectedSortOrders)); }; const onSortOrdersChange = (index: number, newSortOrder: QuerySortOrder) => { - const newOrderBys = [...orderBys]; - const newValue = { name: newOrderBys[index].name, order: newSortOrder } as QueryOrderBy; - newOrderBys.splice(index, 1, newValue); - setOrderBys(newOrderBys); - props.onChange(newOrderBys as QueryOrderBy[]); + const newSelectedSortOrders = [...selectedSortOrders]; + newSelectedSortOrders.splice(index, 1, newSortOrder); + setSelectedSortOrders(newSelectedSortOrders); + + props.onChange(mapSelectedValuesToQueryOrderBy(selectedAttributes, newSelectedSortOrders)); }; const reorderOrderBy = (direction: REORDER_DIRECTION, index: number) => { - const clonedOrderbys = cloneDeep(orderBys); - const orderByToMove = clonedOrderbys[index]; - const orderBysWithoutOrderByToMove = clonedOrderbys.slice(0, index).concat(clonedOrderbys.slice(index + 1)); const newIndex = direction === REORDER_DIRECTION.UP ? index - 1 : index + 1; - const newOrderBys = orderBysWithoutOrderByToMove - .slice(0, newIndex) - .concat(orderByToMove) - .concat(orderBysWithoutOrderByToMove.slice(newIndex)); + const newSelectedAttributes = [...selectedAttributes]; + const attributeToMove = newSelectedAttributes[index]; + newSelectedAttributes.splice(index, 1); + newSelectedAttributes.splice(newIndex, 0, attributeToMove); + + const newSelectedSortOrders = [...selectedSortOrders]; + const sortOrderToMove = newSelectedSortOrders[index]; + newSelectedSortOrders.splice(index, 1); + newSelectedSortOrders.splice(newIndex, 0, sortOrderToMove); + + setSelectedAttributes(newSelectedAttributes); + setSelectedSortOrders(newSelectedSortOrders); - //TODOMY why is this not triggering a rerender? - setOrderBys(newOrderBys); - props.onChange(newOrderBys as QueryOrderBy[]); + props.onChange(mapSelectedValuesToQueryOrderBy(newSelectedAttributes, newSelectedSortOrders)); }; const addOrderByInput = () => { - setOrderBys((prevState) => [...prevState, { name: '', order: 'ASC' }]); + setSelectedAttributes((prevState) => [...prevState, {}]); + setSelectedSortOrders((prevState) => [...prevState, 'ASC']); }; return ( - {orderBys.map((item, index, orderBys) => ( + {selectedAttributes.map((attribute, index, array) => ( onAttributesChange(index, newValue)} - sortOrder={item.order} + selectableOrderByAttributes={mapOrderBysToSelectableValue()} + attribute={attribute} + onAttributeChange={(newValue: SelectableValue) => + onAttributesChange(index, newValue) + } + sortOrder={selectedSortOrders[index]} onSortOrderChange={(newValue: QuerySortOrder) => onSortOrdersChange(index, newValue)} onDelete={() => deleteOrderByInput(index)} isFirst={index === 0} - isLast={index === orderBys.length - 1} - onReorderGroupBy={(direction: REORDER_DIRECTION) => reorderOrderBy(direction, index)} + isLast={index === array.length - 1} + onReorderOrderBy={(direction: REORDER_DIRECTION) => reorderOrderBy(direction, index)} /> ))} - + addOrderByInput()} size="xl" /> diff --git a/bitmovin-analytics-datasource/src/types/queryOrderBy.ts b/bitmovin-analytics-datasource/src/types/queryOrderBy.ts index 80bf239..2b146c4 100644 --- a/bitmovin-analytics-datasource/src/types/queryOrderBy.ts +++ b/bitmovin-analytics-datasource/src/types/queryOrderBy.ts @@ -7,8 +7,3 @@ export type QueryOrderBy = { name: QueryAttribute | QueryAdAttribute; order: QuerySortOrder; }; - -export type SelectableQueryOrderBy = { - name: QueryAttribute | QueryAdAttribute | ''; - order: QuerySortOrder; -}; From abb48c04935297e3bd1476ea24730964d3a7ab93 Mon Sep 17 00:00:00 2001 From: MGJamJam Date: Tue, 7 May 2024 17:46:39 -0300 Subject: [PATCH 18/18] lint --- bitmovin-analytics-datasource/src/components/OrderByInput.tsx | 2 +- bitmovin-analytics-datasource/src/components/OrderByRow.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bitmovin-analytics-datasource/src/components/OrderByInput.tsx b/bitmovin-analytics-datasource/src/components/OrderByInput.tsx index 41311bc..033a3fc 100644 --- a/bitmovin-analytics-datasource/src/components/OrderByInput.tsx +++ b/bitmovin-analytics-datasource/src/components/OrderByInput.tsx @@ -20,7 +20,7 @@ type Props = { readonly onReorderOrderBy: (direction: REORDER_DIRECTION) => void; }; -const sortOrderOption: SelectableValue[] = [ +const sortOrderOption: Array> = [ { value: 'ASC', description: 'Sort by ascending', icon: 'sort-amount-up' }, { value: 'DESC', description: 'Sort by descending', icon: 'sort-amount-down' }, ]; diff --git a/bitmovin-analytics-datasource/src/components/OrderByRow.tsx b/bitmovin-analytics-datasource/src/components/OrderByRow.tsx index 61d0abd..9067925 100644 --- a/bitmovin-analytics-datasource/src/components/OrderByRow.tsx +++ b/bitmovin-analytics-datasource/src/components/OrderByRow.tsx @@ -97,6 +97,7 @@ export function OrderByRow(props: Props) { {selectedAttributes.map((attribute, index, array) => (