diff --git a/bitmovin-analytics-datasource/example_dashboard.json b/bitmovin-analytics-datasource/example_dashboard.json index 3b8f84c..a5cf0e0 100644 --- a/bitmovin-analytics-datasource/example_dashboard.json +++ b/bitmovin-analytics-datasource/example_dashboard.json @@ -19,15 +19,12 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 2, + "id": 19, "links": [], "liveNow": false, "panels": [ { - "datasource": { - "type": "bitmovin-analytics-datasource", - "uid": "PC6526C9D339F63A2" - }, + "datasource": {}, "fieldConfig": { "defaults": { "color": { @@ -104,14 +101,13 @@ }, "targets": [ { - "aggregation": "count", - "aliasBy": "Total", + "alias": "Total", "datasource": { "type": "bitmovin-analytics-datasource", "uid": "PC6526C9D339F63A2" }, "dimension": "IMPRESSION_ID", - "filters": [ + "filter": [ { "name": "VIDEO_STARTUPTIME", "operator": "GT", @@ -121,18 +117,19 @@ "groupBy": [], "hide": false, "interval": "AUTO", + "license": "", "orderBy": [], + "queryAggregationMethod": "count", "refId": "A" }, { - "aggregation": "count", - "aliasBy": "Error", + "alias": "Error", "datasource": { "type": "bitmovin-analytics-datasource", "uid": "PC6526C9D339F63A2" }, "dimension": "IMPRESSION_ID", - "filters": [ + "filter": [ { "name": "VIDEO_STARTUPTIME", "operator": "GT", @@ -147,7 +144,9 @@ "groupBy": [], "hide": false, "interval": "AUTO", + "license": "", "orderBy": [], + "queryAggregationMethod": "count", "refId": "B" } ], @@ -155,10 +154,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "bitmovin-analytics-datasource", - "uid": "PC6526C9D339F63A2" - }, + "datasource": {}, "fieldConfig": { "defaults": { "color": { @@ -235,19 +231,20 @@ }, "targets": [ { - "aggregation": "count", - "aliasBy": "", + "alias": "", "datasource": { "type": "bitmovin-analytics-datasource", "uid": "PC6526C9D339F63A2" }, "dimension": "IMPRESSION_ID", - "filters": [], + "filter": [], "groupBy": [ "BROWSER" ], "interval": "AUTO", + "license": "", "orderBy": [], + "queryAggregationMethod": "count", "refId": "A" } ], @@ -255,10 +252,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "bitmovin-analytics-datasource", - "uid": "PC6526C9D339F63A2" - }, + "datasource": {}, "fieldConfig": { "defaults": { "color": { @@ -335,13 +329,12 @@ }, "targets": [ { - "aggregation": "count", "datasource": { "type": "bitmovin-analytics-datasource", "uid": "PC6526C9D339F63A2" }, "dimension": "IMPRESSION_ID", - "filters": [ + "filter": [ { "name": "PLAYER_STARTUPTIME", "operator": "GT", @@ -352,7 +345,9 @@ "COUNTRY" ], "interval": "AUTO", + "license": "", "orderBy": [], + "queryAggregationMethod": "count", "refId": "A" } ], @@ -360,10 +355,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "bitmovin-analytics-datasource", - "uid": "PC6526C9D339F63A2" - }, + "datasource": {}, "fieldConfig": { "defaults": { "color": { @@ -440,13 +432,12 @@ }, "targets": [ { - "aggregation": "count", "datasource": { "type": "bitmovin-analytics-datasource", "uid": "PC6526C9D339F63A2" }, "dimension": "IMPRESSION_ID", - "filters": [ + "filter": [ { "name": "PLAYER_STARTUPTIME", "operator": "GT", @@ -457,7 +448,9 @@ "PLAYER_TECH" ], "interval": "AUTO", + "license": "", "orderBy": [], + "queryAggregationMethod": "count", "refId": "A" } ], @@ -465,10 +458,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "bitmovin-analytics-datasource", - "uid": "PC6526C9D339F63A2" - }, + "datasource": {}, "fieldConfig": { "defaults": { "color": { @@ -545,13 +535,12 @@ }, "targets": [ { - "aggregation": "count", "datasource": { "type": "bitmovin-analytics-datasource", "uid": "PC6526C9D339F63A2" }, "dimension": "IMPRESSION_ID", - "filters": [ + "filter": [ { "name": "PLAYER_STARTUPTIME", "operator": "GT", @@ -562,7 +551,9 @@ "OPERATINGSYSTEM" ], "interval": "AUTO", + "license": "", "orderBy": [], + "queryAggregationMethod": "count", "refId": "A" } ], @@ -570,10 +561,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "bitmovin-analytics-datasource", - "uid": "PC6526C9D339F63A2" - }, + "datasource": {}, "fieldConfig": { "defaults": { "color": { @@ -591,7 +579,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -631,13 +620,12 @@ "pluginVersion": "10.3.3", "targets": [ { - "aggregation": "count", "datasource": { "type": "bitmovin-analytics-datasource", "uid": "PC6526C9D339F63A2" }, "dimension": "IMPRESSION_ID", - "filters": [ + "filter": [ { "name": "PLAYER_STARTUPTIME", "operator": "GT", @@ -647,8 +635,10 @@ "groupBy": [ "BROWSER" ], + "license": "", "limit": 10, "orderBy": [], + "queryAggregationMethod": "count", "refId": "A" } ], @@ -656,10 +646,7 @@ "type": "table" }, { - "datasource": { - "type": "bitmovin-analytics-datasource", - "uid": "PC6526C9D339F63A2" - }, + "datasource": {}, "fieldConfig": { "defaults": { "color": { @@ -677,7 +664,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -717,13 +705,12 @@ "pluginVersion": "10.3.3", "targets": [ { - "aggregation": "count", "datasource": { "type": "bitmovin-analytics-datasource", "uid": "PC6526C9D339F63A2" }, "dimension": "IMPRESSION_ID", - "filters": [ + "filter": [ { "name": "PLAYER_STARTUPTIME", "operator": "GT", @@ -733,8 +720,10 @@ "groupBy": [ "COUNTRY" ], + "license": "", "limit": 10, "orderBy": [], + "queryAggregationMethod": "count", "refId": "A" } ], @@ -742,10 +731,7 @@ "type": "table" }, { - "datasource": { - "type": "bitmovin-analytics-datasource", - "uid": "PC6526C9D339F63A2" - }, + "datasource": {}, "fieldConfig": { "defaults": { "color": { @@ -763,7 +749,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -803,13 +790,12 @@ "pluginVersion": "10.3.3", "targets": [ { - "aggregation": "count", "datasource": { "type": "bitmovin-analytics-datasource", "uid": "PC6526C9D339F63A2" }, "dimension": "IMPRESSION_ID", - "filters": [ + "filter": [ { "name": "PLAYER_STARTUPTIME", "operator": "GT", @@ -819,8 +805,10 @@ "groupBy": [ "OPERATINGSYSTEM" ], + "license": "", "limit": 10, "orderBy": [], + "queryAggregationMethod": "count", "refId": "A" } ], @@ -828,10 +816,7 @@ "type": "table" }, { - "datasource": { - "type": "bitmovin-analytics-datasource", - "uid": "PC6526C9D339F63A2" - }, + "datasource": {}, "fieldConfig": { "defaults": { "color": { @@ -849,7 +834,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -883,13 +869,12 @@ "pluginVersion": "10.3.3", "targets": [ { - "aggregation": "count", "datasource": { "type": "bitmovin-analytics-datasource", "uid": "PC6526C9D339F63A2" }, "dimension": "IMPRESSION_ID", - "filters": [ + "filter": [ { "name": "PLAYER_STARTUPTIME", "operator": "GT", @@ -899,8 +884,10 @@ "groupBy": [ "PLAYER_TECH" ], + "license": "", "limit": 10, "orderBy": [], + "queryAggregationMethod": "count", "refId": "A" } ], @@ -915,13 +902,13 @@ "list": [] }, "time": { - "from": "now-6h", + "from": "now-24h", "to": "now" }, "timepicker": {}, "timezone": "", "title": "Bitmovin Analytics", "uid": "f8bfe39e-39f4-401b-8f2d-59f01c8ef93a", - "version": 22, + "version": 2, "weekStart": "" } \ No newline at end of file diff --git a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx index 5ff92a0..708cec8 100644 --- a/bitmovin-analytics-datasource/src/components/QueryEditor.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryEditor.tsx @@ -1,13 +1,18 @@ import React, { ChangeEvent, useEffect, useMemo, useState } from 'react'; -import { FieldSet, InlineField, InlineSwitch, Input, Select } from '@grafana/ui'; +import { FieldSet, HorizontalGroup, InlineField, InlineSwitch, Input, Select } from '@grafana/ui'; import type { QueryEditorProps, SelectableValue } from '@grafana/data'; import { defaults } from 'lodash'; import { DataSource } from '../datasource'; -import { BitmovinDataSourceOptions, BitmovinAnalyticsDataQuery, DEFAULT_QUERY } from '../types/grafanaTypes'; +import { + BitmovinDataSourceOptions, + BitmovinAnalyticsDataQuery, + DEFAULT_QUERY, + OldBitmovinAnalyticsDataQuery, +} from '../types/grafanaTypes'; import { fetchLicenses } from '../utils/licenses'; import { DEFAULT_SELECTABLE_QUERY_INTERVAL, SELECTABLE_QUERY_INTERVALS } from '../utils/intervalUtils'; -import { SELECTABLE_AGGREGATIONS } from '../types/aggregations'; +import { SELECTABLE_AGGREGATION_METHODS } from '../types/aggregationMethod'; import { QueryAdAttribute, SELECTABLE_QUERY_AD_ATTRIBUTES } from '../types/queryAdAttributes'; import { QueryAttribute, SELECTABLE_QUERY_ATTRIBUTES } from '../types/queryAttributes'; import { isMetric, SELECTABLE_METRICS } from '../types/metric'; @@ -24,16 +29,25 @@ enum LoadingState { Error = 'ERROR', } -type Props = QueryEditorProps; +type Props = QueryEditorProps< + DataSource, + BitmovinAnalyticsDataQuery | OldBitmovinAnalyticsDataQuery, + BitmovinDataSourceOptions +>; export function QueryEditor(props: Props) { + const query = defaults(props.query, DEFAULT_QUERY); const [selectableLicenses, setSelectableLicenses] = useState([]); const [licenseLoadingState, setLicenseLoadingState] = useState(LoadingState.Default); const [licenseErrorMessage, setLicenseErrorMessage] = useState(''); - const [isTimeSeries, setIsTimeSeries] = useState(!!props.query.interval); - const isDimensionMetricSelected = useMemo(() => { - return props.query.metric !== undefined; - }, [props.query.metric]); + const [isTimeSeries, setIsTimeSeries] = useState(query.resultFormat === 'time_series'); + const [percentileValue, setPercentileValue] = useState(query.percentileValue); + const isMetricSelected = useMemo(() => { + return query.dimension ? isMetric(query.dimension) : false; + }, [query.dimension]); + const isPercentileSelected = useMemo(() => { + return query.metric === 'percentile'; + }, [query.metric]); /** Fetch Licenses */ useEffect(() => { @@ -49,24 +63,18 @@ export function QueryEditor(props: Props) { }); }, [props.datasource.apiKey, props.datasource.baseUrl]); - const query = defaults(props.query, DEFAULT_QUERY); - const handleLicenseChange = (item: SelectableValue) => { - props.onChange({ ...query, licenseKey: item.value }); + props.onChange({ ...query, license: item.value }); props.onRunQuery(); }; const handleAggregationChange = (item: SelectableValue) => { - props.onChange({ ...query, aggregation: item.value, metric: undefined }); + props.onChange({ ...query, metric: item.value }); props.onRunQuery(); }; const handleDimensionChange = (item: SelectableValue) => { - if (isMetric(item.value)) { - props.onChange({ ...query, aggregation: undefined, dimension: undefined, metric: item.value }); - } else { - props.onChange({ ...query, dimension: item.value, metric: undefined }); - } + props.onChange({ ...query, dimension: item.value }); props.onRunQuery(); }; @@ -81,7 +89,7 @@ export function QueryEditor(props: Props) { }; const handleQueryFilterChange = (newFilters: QueryFilter[]) => { - props.onChange({ ...query, filters: newFilters }); + props.onChange({ ...query, filter: newFilters }); props.onRunQuery(); }; @@ -94,9 +102,9 @@ export function QueryEditor(props: Props) { const handleFormatAsTimeSeriesChange = (event: ChangeEvent) => { setIsTimeSeries(event.currentTarget.checked); if (event.currentTarget.checked) { - props.onChange({ ...query, interval: 'AUTO' }); + props.onChange({ ...query, interval: 'AUTO', resultFormat: 'time_series' }); } else { - props.onChange({ ...query, interval: undefined }); + props.onChange({ ...query, interval: undefined, resultFormat: 'table' }); } props.onRunQuery(); }; @@ -107,7 +115,22 @@ export function QueryEditor(props: Props) { }; const handleAliasByBlur = (event: ChangeEvent) => { - props.onChange({ ...query, aliasBy: event.target.value }); + props.onChange({ ...query, alias: event.target.value }); + props.onRunQuery(); + }; + + const handlePercentileValueChange = (event: ChangeEvent) => { + let percentile = parseInt(event.target.value, 10); + if (percentile < 0) { + percentile = 0; + } else if (percentile > 99) { + percentile = 99; + } + setPercentileValue(percentile); + }; + + const handlePercentileBlur = () => { + props.onChange({ ...query, percentileValue: percentileValue }); props.onRunQuery(); }; @@ -138,7 +161,7 @@ export function QueryEditor(props: Props) { required > handleAggregationChange(item)} - width={30} - options={SELECTABLE_AGGREGATIONS} + + {!isMetricSelected && ( + + - - )} + )} + + diff --git a/bitmovin-analytics-datasource/src/components/QueryFilterInput.tsx b/bitmovin-analytics-datasource/src/components/QueryFilterInput.tsx index 398aef4..a10e787 100644 --- a/bitmovin-analytics-datasource/src/components/QueryFilterInput.tsx +++ b/bitmovin-analytics-datasource/src/components/QueryFilterInput.tsx @@ -6,7 +6,7 @@ import type { SelectableValue } from '@grafana/data'; import { QueryAttribute, SELECTABLE_QUERY_ATTRIBUTES } from '../types/queryAttributes'; import { QueryAdAttribute, SELECTABLE_QUERY_AD_ATTRIBUTES } from '../types/queryAdAttributes'; import { differenceWith } from 'lodash'; -import { convertFilterValueToProperType, mapQueryFilterValueToRawFilterValue } from 'utils/filterUtils'; +import { convertFilterValueToProperType } from 'utils/filterUtils'; interface QueryFilterInputProps { /** `undefined` when component is used to create new filter (no values yet) */ @@ -64,7 +64,7 @@ export function QueryFilterInput(props: Readonly) { setDerivedQueryFilterState((prevState) => ({ ...prevState, dirty: true, - inputValue: value, + value: value, inputValueError: undefined, })); } @@ -91,8 +91,8 @@ export function QueryFilterInput(props: Readonly) { } try { - const validQueryFilterValue = convertFilterValueToProperType( - derivedQueryFilterState.inputValue, + convertFilterValueToProperType( + derivedQueryFilterState.value!, derivedQueryFilterState.attribute!, derivedQueryFilterState.operator!, props.isAdAnalytics @@ -101,7 +101,7 @@ export function QueryFilterInput(props: Readonly) { props.onChange({ name: derivedQueryFilterState.attribute!, operator: derivedQueryFilterState.operator!, - value: validQueryFilterValue, + value: derivedQueryFilterState.value!, }); } catch (e: unknown) { setDerivedQueryFilterState((prevState) => ({ @@ -151,7 +151,7 @@ export function QueryFilterInput(props: Readonly) { theme="error" > handleInputValueChange(e.currentTarget.value)} invalid={derivedQueryFilterState.inputValueError != null} type="text" @@ -192,7 +192,6 @@ type DerivedQueryFilterState = { value: undefined | QueryFilter['value']; /** `true` if some values have been changed by inputs */ dirty: boolean; - inputValue: string; /** `undefined` when input value is valid */ inputValueError: undefined | string; }; @@ -205,7 +204,6 @@ function buildInitialDerivedQueryFilterState(queryFilter: undefined | QueryFilte operatorError: undefined, value: queryFilter?.value, dirty: false, - inputValue: mapQueryFilterValueToRawFilterValue(queryFilter?.value ?? null), inputValueError: undefined, }; } diff --git a/bitmovin-analytics-datasource/src/datasource.ts b/bitmovin-analytics-datasource/src/datasource.ts index 2544d83..c79dc8f 100644 --- a/bitmovin-analytics-datasource/src/datasource.ts +++ b/bitmovin-analytics-datasource/src/datasource.ts @@ -12,7 +12,12 @@ import { getBackendSrv } from '@grafana/runtime'; import { filter } from 'lodash'; import { catchError, lastValueFrom, map, Observable, of } from 'rxjs'; -import { BitmovinDataSourceOptions, BitmovinAnalyticsDataQuery, DEFAULT_QUERY } from './types/grafanaTypes'; +import { + BitmovinDataSourceOptions, + BitmovinAnalyticsDataQuery, + DEFAULT_QUERY, + OldBitmovinAnalyticsDataQuery, +} from './types/grafanaTypes'; import { MixedDataRowList, NumberDataRowList, @@ -21,27 +26,32 @@ import { transformTableData, } from './utils/dataUtils'; import { calculateQueryInterval, QueryInterval } from './utils/intervalUtils'; -import { Metric } from './types/metric'; -import { Aggregation } from './types/aggregations'; -import { QueryFilter } from './types/queryFilter'; +import { isMetric, Metric } from './types/metric'; +import { AggregationMethod } from './types/aggregationMethod'; +import { ProperTypedQueryFilter } from './types/queryFilter'; import { QueryAttribute } from './types/queryAttributes'; import { QueryAdAttribute } from './types/queryAdAttributes'; import { QueryOrderBy } from './types/queryOrderBy'; +import { convertFilterValueToProperType } from './utils/filterUtils'; type BitmovinAnalyticsRequestQuery = { licenseKey: string; start: Date; end: Date; - filters: QueryFilter[]; + filters: ProperTypedQueryFilter[]; groupBy: Array; orderBy: QueryOrderBy[]; dimension?: QueryAttribute | QueryAdAttribute; metric?: Metric; interval?: QueryInterval; limit?: number; + percentile?: number; }; -export class DataSource extends DataSourceApi { +export class DataSource extends DataSourceApi< + BitmovinAnalyticsDataQuery | OldBitmovinAnalyticsDataQuery, + BitmovinDataSourceOptions +> { baseUrl: string; apiKey: string; tenantOrgId?: string; @@ -78,26 +88,47 @@ export class DataSource extends DataSourceApi !t.hide)); const promises = enabledQueries.map(async (target) => { - const interval = target.interval - ? calculateQueryInterval(target.interval!, from.getTime(), to.getTime()) - : undefined; + const interval = + target.resultFormat === 'time_series' && target.interval + ? calculateQueryInterval(target.interval, from.getTime(), to.getTime()) + : undefined; + + let aggregationMethod: AggregationMethod | undefined = target.metric; + const percentileValue = aggregationMethod === 'percentile' ? target.percentileValue : undefined; + + let metric: Metric | undefined = undefined; + let dimension: QueryAttribute | QueryAdAttribute | undefined = undefined; + if (target.dimension) { + if (isMetric(target.dimension)) { + metric = target.dimension as Metric; + } else { + dimension = target.dimension as QueryAttribute | QueryAdAttribute; + } + } + + const filters: ProperTypedQueryFilter[] = target.filter.map((filter) => { + return { + name: filter.name, + operator: filter.operator, + value: convertFilterValueToProperType(filter.value, filter.name, filter.operator, !!this.adAnalytics), + }; + }); const query: BitmovinAnalyticsRequestQuery = { - filters: target.filters, + filters: filters, groupBy: target.groupBy, orderBy: target.orderBy, - dimension: target.dimension, - metric: target.metric, + dimension: dimension, + metric: metric, start: from, end: to, - licenseKey: target.licenseKey, + licenseKey: target.license, interval: interval, - limit: target.limit, + limit: this.parseLimit(target.limit), + percentile: percentileValue, }; - const response = await lastValueFrom( - this.request(this.getRequestUrl(target.metric, target.aggregation), 'POST', query) - ); + const response = await lastValueFrom(this.request(this.getRequestUrl(metric, aggregationMethod), 'POST', query)); const dataRows: MixedDataRowList = response.data.data.result.rows; const dataRowCount: number = response.data.data.result.rowCount; @@ -138,7 +169,7 @@ export class DataSource extends DataSourceApi ({ data })); } - getRequestUrl(metric?: Metric, aggregation?: Aggregation): string { + /** needed because of old plugin logic where limit was saved as string and not as number */ + parseLimit(limit: number | string | undefined): undefined | number { + if (limit == null) { + return undefined; + } + + if (Number.isInteger(limit)) { + return limit as number; + } else { + return parseInt(limit as string, 10); + } + } + + getRequestUrl(metric?: Metric, aggregation?: AggregationMethod): string { let url = '/analytics'; if (this.adAnalytics === true) { url += '/ads'; diff --git a/bitmovin-analytics-datasource/src/module.ts b/bitmovin-analytics-datasource/src/module.ts index 917913c..c9ae72d 100644 --- a/bitmovin-analytics-datasource/src/module.ts +++ b/bitmovin-analytics-datasource/src/module.ts @@ -2,10 +2,16 @@ import { DataSourcePlugin } from '@grafana/data'; import { DataSource } from './datasource'; import { ConfigEditor } from './components/ConfigEditor'; import { QueryEditor } from './components/QueryEditor'; -import { BitmovinAnalyticsDataQuery, BitmovinDataSourceOptions } from './types/grafanaTypes'; +import { + BitmovinAnalyticsDataQuery, + BitmovinDataSourceOptions, + OldBitmovinAnalyticsDataQuery, +} from './types/grafanaTypes'; -export const plugin = new DataSourcePlugin( - DataSource -) +export const plugin = new DataSourcePlugin< + DataSource, + BitmovinAnalyticsDataQuery | OldBitmovinAnalyticsDataQuery, + BitmovinDataSourceOptions +>(DataSource) .setConfigEditor(ConfigEditor) .setQueryEditor(QueryEditor); diff --git a/bitmovin-analytics-datasource/src/types/aggregationMethod.ts b/bitmovin-analytics-datasource/src/types/aggregationMethod.ts new file mode 100644 index 0000000..f31929d --- /dev/null +++ b/bitmovin-analytics-datasource/src/types/aggregationMethod.ts @@ -0,0 +1,22 @@ +import type { SelectableValue } from '@grafana/data'; + +const AGGREGATION_METHODS = [ + 'count', + 'sum', + 'avg', + 'min', + 'max', + 'stddev', + 'percentile', + 'variance', + 'median', +] as const; + +export type AggregationMethod = (typeof AGGREGATION_METHODS)[number]; + +export const SELECTABLE_AGGREGATION_METHODS: Array> = AGGREGATION_METHODS.map( + (aggregation) => ({ + value: aggregation, + label: aggregation, + }) +); diff --git a/bitmovin-analytics-datasource/src/types/aggregations.ts b/bitmovin-analytics-datasource/src/types/aggregations.ts deleted file mode 100644 index b69001c..0000000 --- a/bitmovin-analytics-datasource/src/types/aggregations.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { SelectableValue } from '@grafana/data'; - -const AGGREGATIONS = ['count', 'sum', 'avg', 'min', 'max', 'stddev', 'percentile', 'variance', 'median'] as const; - -export type Aggregation = (typeof AGGREGATIONS)[number]; - -export const SELECTABLE_AGGREGATIONS: Array> = AGGREGATIONS.map((aggregation) => ({ - value: aggregation, - label: aggregation, -})); diff --git a/bitmovin-analytics-datasource/src/types/grafanaTypes.ts b/bitmovin-analytics-datasource/src/types/grafanaTypes.ts index 045ec5d..ea5d671 100644 --- a/bitmovin-analytics-datasource/src/types/grafanaTypes.ts +++ b/bitmovin-analytics-datasource/src/types/grafanaTypes.ts @@ -1,34 +1,57 @@ import { DataSourceJsonData } from '@grafana/data'; import { DataQuery } from '@grafana/schema'; import { QueryInterval } from '../utils/intervalUtils'; -import { Aggregation } from './aggregations'; import { QueryAttribute } from './queryAttributes'; import { QueryAdAttribute } from './queryAdAttributes'; import { Metric } from './metric'; import { QueryOrderBy } from './queryOrderBy'; import { QueryFilter } from './queryFilter'; +import { AggregationMethod } from './aggregationMethod'; + +type ResultFormat = 'table' | 'time_series'; /** * These are the options configurable via the QueryEditor * */ export interface BitmovinAnalyticsDataQuery extends DataQuery { - licenseKey: string; + license: string; interval?: QueryInterval | 'AUTO'; - aggregation?: Aggregation; - metric?: Metric; - dimension?: QueryAttribute | QueryAdAttribute; + metric?: AggregationMethod; + dimension?: QueryAttribute | QueryAdAttribute | Metric; groupBy: Array; orderBy: QueryOrderBy[]; limit?: number; - filters: QueryFilter[]; - aliasBy?: string; + filter: QueryFilter[]; + alias?: string; + percentileValue?: number; + resultFormat: ResultFormat; +} + +/** + * @deprecated + * These are the options query options of the old Angular based plugin + * */ +export interface OldBitmovinAnalyticsDataQuery extends DataQuery { + license: string; + interval?: QueryInterval | 'AUTO'; + metric?: AggregationMethod; + dimension?: QueryAttribute | QueryAdAttribute | Metric; + groupBy: Array; + orderBy: QueryOrderBy[]; + limit?: string; + filter: QueryFilter[]; + alias?: string; + percentileValue: number; + resultFormat: ResultFormat; } export const DEFAULT_QUERY: Partial = { - licenseKey: '', + license: '', orderBy: [], groupBy: [], - filters: [], + filter: [], + resultFormat: 'time_series', + interval: 'AUTO', }; /** diff --git a/bitmovin-analytics-datasource/src/types/metric.ts b/bitmovin-analytics-datasource/src/types/metric.ts index 9ef61f1..a3a1532 100644 --- a/bitmovin-analytics-datasource/src/types/metric.ts +++ b/bitmovin-analytics-datasource/src/types/metric.ts @@ -1,6 +1,6 @@ import { SelectableValue } from '@grafana/data'; -const METRICS = ['avg-concurrentviewers', 'max-concurrentviewers', 'avg-dropped-frames'] as const; +const METRICS = ['AVG_CONCURRENTVIEWERS', 'MAX_CONCURRENTVIEWERS', 'AVG-DROPPED-FRAMES'] as const; export type Metric = (typeof METRICS)[number]; diff --git a/bitmovin-analytics-datasource/src/types/queryFilter.ts b/bitmovin-analytics-datasource/src/types/queryFilter.ts index 727be73..3bd64c0 100644 --- a/bitmovin-analytics-datasource/src/types/queryFilter.ts +++ b/bitmovin-analytics-datasource/src/types/queryFilter.ts @@ -9,10 +9,20 @@ export type QueryFilterOperator = (typeof QUERY_FILTER_OPERATORS)[number]; export const SELECTABLE_QUERY_FILTER_OPERATORS: Array> = QUERY_FILTER_OPERATORS.map((o) => ({ value: o, label: o })); +/** This type is needed because of legacy reasons. + * In the angular plugin the value was saved as a string in a dashboard JSON file. */ export type QueryFilter = { name: QueryAdAttribute | QueryAttribute; operator: QueryFilterOperator; - value: QueryFilterValue; + value: string; }; -export type QueryFilterValue = boolean | number | string | string[] | null; +/** QueryFilter type with the correct value type that is accepted by the Bitmovin API */ +export type ProperTypedQueryFilter = { + name: QueryAdAttribute | QueryAttribute; + operator: QueryFilterOperator; + value: OutputQueryFilterValue; +}; + +/** Correct Filter value type that is accepted by the Bitmovin API */ +export type OutputQueryFilterValue = boolean | number | string | string[] | null; diff --git a/bitmovin-analytics-datasource/src/utils/filterUtils.test.ts b/bitmovin-analytics-datasource/src/utils/filterUtils.test.ts index 7315396..f20cb65 100644 --- a/bitmovin-analytics-datasource/src/utils/filterUtils.test.ts +++ b/bitmovin-analytics-datasource/src/utils/filterUtils.test.ts @@ -1,4 +1,4 @@ -import { convertFilterValueToProperType, mapQueryFilterValueToRawFilterValue } from './filterUtils'; +import { convertFilterValueToProperType } from './filterUtils'; describe('convertFilterValueToProperType', () => { it('should return null if rawValue is empty and attribute a NullFilter', () => { @@ -100,53 +100,3 @@ describe('convertFilterValueToProperType', () => { ); }); }); - -describe('mapQueryFilterValueToRawFilterValue', () => { - it('should return empty string for null filter value', () => { - //arrange && act - const result = mapQueryFilterValueToRawFilterValue(null); - - //assert - expect(result).toEqual(''); - }); - - it('should return boolean as string', () => { - //arrange && act - const result = mapQueryFilterValueToRawFilterValue(true); - - //assert - expect(result).toEqual('true'); - }); - - it('should return integer as string', () => { - //arrange && act - const result = mapQueryFilterValueToRawFilterValue(23); - - //assert - expect(result).toEqual('23'); - }); - - it('should return float as string', () => { - //arrange && act - const result = mapQueryFilterValueToRawFilterValue(23.5); - - //assert - expect(result).toEqual('23.5'); - }); - - it('should return string as string', () => { - //arrange && act - const result = mapQueryFilterValueToRawFilterValue('de'); - - //assert - expect(result).toEqual('de'); - }); - - it('should return string array as string', () => { - //arrange && act - const result = mapQueryFilterValueToRawFilterValue(['Firefox', 'Opera']); - - //assert - expect(result).toEqual('["Firefox","Opera"]'); - }); -}); diff --git a/bitmovin-analytics-datasource/src/utils/filterUtils.ts b/bitmovin-analytics-datasource/src/utils/filterUtils.ts index 572c436..f27f7ef 100644 --- a/bitmovin-analytics-datasource/src/utils/filterUtils.ts +++ b/bitmovin-analytics-datasource/src/utils/filterUtils.ts @@ -1,24 +1,9 @@ import { isEmpty } from 'lodash'; import { QueryAdAttribute } from '../types/queryAdAttributes'; -import { QueryFilterOperator, QueryFilterValue } from '../types/queryFilter'; +import { QueryFilterOperator, OutputQueryFilterValue } from '../types/queryFilter'; import { QueryAttribute } from '../types/queryAttributes'; -/** - * 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)) { - return JSON.stringify(filterValue); - } else { - return filterValue.toString(); - } -}; - const isNullFilter = (filterAttribute: QueryAttribute | QueryAdAttribute): boolean => { switch (filterAttribute) { case 'CDN_PROVIDER': @@ -182,14 +167,14 @@ const convertFilter = (rawValue: string, filterAttribute: QueryAttribute) => { * @param {QueryAttribute | QueryAdAttribute} filterAttribute The filter attribute. * @param {QueryFilterOperator} filterOperator The filter operator. * @param {boolean} isAdAnalytics If Ad Analytics are queried. - * @returns {QueryFilterValue} The correctly converted Filter Value. + * @returns {OutputQueryFilterValue} The correctly converted Filter Value. * */ export const convertFilterValueToProperType = ( rawValue: string, filterAttribute: QueryAttribute | QueryAdAttribute, filterOperator: QueryFilterOperator, isAdAnalytics: boolean -): QueryFilterValue => { +): OutputQueryFilterValue => { if (isEmpty(rawValue) && isNullFilter(filterAttribute)) { return null; }