Skip to content

Commit

Permalink
Feat/AN-4110 implement group by selection (#68)
Browse files Browse the repository at this point in the history
* implements license fetching and interval selection

* add tests for intervalUtils

* fix ceiling of timestamp for DAY interval

* add tests

* fix linting

* delete unnecessary export of enum

* implement metric selection

* implement dimension selection

* fix linting errors

* add metric support

* add metric to query and check for null instead of undefined

* implements groupBy selection

* fix linting

* implements reordering of GroupBys and fixes position of Add Button

* make props readonly
  • Loading branch information
MGJamJam authored May 7, 2024
1 parent 119e9b4 commit 813b386
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 8 deletions.
48 changes: 48 additions & 0 deletions bitmovin-analytics-datasource/src/components/GroupByInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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';

export enum REORDER_DIRECTION {
UP,
DOWN,
}

type Props = {
readonly groupBy: SelectableValue<QueryAttribute | QueryAdAttribute>;
readonly selectableGroupBys: Array<SelectableValue<QueryAttribute | QueryAdAttribute>>;
readonly onDelete: () => void;
readonly onChange: (newValue: SelectableValue<QueryAdAttribute | QueryAttribute>) => void;
readonly isFirst: boolean;
readonly isLast: boolean;
readonly onReorderGroupBy: (direction: REORDER_DIRECTION) => void;
};

export function GroupByInput(props: Props) {
return (
<HorizontalGroup>
<Select value={props.groupBy} onChange={props.onChange} options={props.selectableGroupBys} width={30} />
<IconButton
tooltip="Move down"
onClick={() => props.onReorderGroupBy(REORDER_DIRECTION.DOWN)}
name="arrow-down"
disabled={props.isLast}
/>
<IconButton
tooltip="Move up"
onClick={() => props.onReorderGroupBy(REORDER_DIRECTION.UP)}
name="arrow-up"
disabled={props.isFirst}
/>
<IconButton
tooltip="Delete Group By"
name="trash-alt"
onClick={() => props.onDelete()}
size="lg"
variant="destructive"
/>
</HorizontalGroup>
);
}
88 changes: 88 additions & 0 deletions bitmovin-analytics-datasource/src/components/GroupByRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useState } from 'react';
import { SelectableValue } from '@grafana/data';
import { Box, IconButton, VerticalGroup } from '@grafana/ui';
import { difference } from 'lodash';

import { QueryAdAttribute, SELECTABLE_QUERY_AD_ATTRIBUTES } from '../types/queryAdAttributes';
import { QueryAttribute, SELECTABLE_QUERY_ATTRIBUTES } from '../types/queryAttributes';
import { GroupByInput, REORDER_DIRECTION } from './GroupByInput';

type Props = {
readonly isAdAnalytics: boolean;
readonly onChange: (newGroupBys: QueryAdAttribute[] | QueryAttribute[]) => void;
};

export function GroupByRow(props: Props) {
const [selectedGroupBys, setSelectedGroupBys] = useState<Array<SelectableValue<QueryAdAttribute | QueryAttribute>>>(
[]
);

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

const deleteGroupByInput = (index: number) => {
const newSelectedGroupBys = [...selectedGroupBys];
newSelectedGroupBys.splice(index, 1);

setSelectedGroupBys(newSelectedGroupBys);

const groupBys = newSelectedGroupBys.map((groupBy) => groupBy.value);
props.onChange(groupBys as QueryAttribute[] | QueryAdAttribute[]);
};

const onSelectedGroupByChange = (
index: number,
newSelectedGroupBy: SelectableValue<QueryAttribute | QueryAdAttribute>
) => {
const newSelectedGroupBys = [...selectedGroupBys];
newSelectedGroupBys.splice(index, 1, newSelectedGroupBy);
setSelectedGroupBys(newSelectedGroupBys);

const groupBys = newSelectedGroupBys.map((groupBy) => groupBy.value);
props.onChange(groupBys as QueryAttribute[] | QueryAdAttribute[]);
};

const reorderGroupBy = (direction: REORDER_DIRECTION, index: number) => {
const newSelectedGroupBys = [...selectedGroupBys];
const groupByToMove = newSelectedGroupBys[index];
newSelectedGroupBys.splice(index, 1);

const newIndex = direction === REORDER_DIRECTION.UP ? index - 1 : index + 1;
newSelectedGroupBys.splice(newIndex, 0, groupByToMove);
setSelectedGroupBys(newSelectedGroupBys);

const groupBys = newSelectedGroupBys.map((groupBy) => groupBy.value);
props.onChange(groupBys as QueryAttribute[] | QueryAdAttribute[]);
};

const addGroupByInput = () => {
setSelectedGroupBys((prevState) => [...prevState, { name: '', label: '' }]);
};

return (
<VerticalGroup>
{selectedGroupBys?.map((item, index, groupBys) => (
<GroupByInput
key={index}
groupBy={item}
onChange={(newValue: SelectableValue<QueryAdAttribute | QueryAttribute>) =>
onSelectedGroupByChange(index, newValue)
}
selectableGroupBys={mapGroupBysToSelectableValue()}
onDelete={() => deleteGroupByInput(index)}
isFirst={index === 0}
isLast={index === groupBys.length - 1}
onReorderGroupBy={(direction: REORDER_DIRECTION) => reorderGroupBy(direction, index)}
/>
))}
<Box paddingTop={selectedGroupBys.length === 0 ? 0.5 : 0}>
<IconButton name="plus-square" tooltip="Add Group By" onClick={() => addGroupByInput()} size="xxl" />
</Box>
</VerticalGroup>
);
}
21 changes: 15 additions & 6 deletions bitmovin-analytics-datasource/src/components/QueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ 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 { SELECTABLE_QUERY_AD_ATTRIBUTES } from '../types/queryAdAttributes';
import { 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';

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

const onGroupByChange = (newGroupBys: QueryAdAttribute[] | QueryAttribute[]) => {
onChange({ ...query, groupBy: newGroupBys });
onRunQuery();
};

const onFormatAsTimeSeriesChange = (event: ChangeEvent<HTMLInputElement>) => {
setIsTimeSeries(event.currentTarget.checked);
if (event.currentTarget.checked) {
Expand All @@ -83,7 +89,7 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props)
<Select
defaultValue={DEFAULT_SELECTABLE_QUERY_INTERVAL}
onChange={(item) => onIntervalChange(item)}
width={40}
width={30}
options={SELECTABLE_QUERY_INTERVALS}
/>
</InlineField>
Expand All @@ -103,7 +109,7 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props)
>
<Select
onChange={onLicenseChange}
width={40}
width={30}
options={selectableLicenses}
noOptionsMessage="No Analytics Licenses found"
isLoading={licenseLoadingState === LoadingState.Loading}
Expand All @@ -115,22 +121,25 @@ export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props)
<Select
defaultValue={DEFAULT_SELECTABLE_AGGREGATION}
onChange={(item) => onAggregationChange(item)}
width={40}
width={30}
options={SELECTABLE_AGGREGATIONS}
/>
</InlineField>
)}
<InlineField label="Dimension" labelWidth={20}>
<Select
onChange={onDimensionChange}
width={40}
width={30}
options={
datasource.adAnalytics
? SELECTABLE_QUERY_AD_ATTRIBUTES
: SELECTABLE_QUERY_ATTRIBUTES.concat(SELECTABLE_METRICS)
}
/>
</InlineField>
<InlineField label="Group By" labelWidth={20}>
<GroupByRow isAdAnalytics={datasource.adAnalytics ? true : false} onChange={onGroupByChange} />
</InlineField>
<InlineField label="Format as time series" labelWidth={20}>
<InlineSwitch value={isTimeSeries} onChange={onFormatAsTimeSeriesChange}></InlineSwitch>
</InlineField>
Expand Down
4 changes: 2 additions & 2 deletions bitmovin-analytics-datasource/src/datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,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;
Expand Down Expand Up @@ -72,7 +72,7 @@ export class DataSource extends DataSourceApi<BitmovinAnalyticsDataQuery, MyData
value: 0,
},
],
groupBy: [],
groupBy: target.groupBy,
orderBy: interval
? [
{
Expand Down
1 change: 1 addition & 0 deletions bitmovin-analytics-datasource/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface BitmovinAnalyticsDataQuery extends DataQuery {
aggregation?: Aggregation;
metric?: Metric;
dimension?: QueryAttribute | QueryAdAttribute;
groupBy: QueryAttribute[] | QueryAdAttribute[];
}

export const DEFAULT_QUERY: Partial<BitmovinAnalyticsDataQuery> = {};
Expand Down

0 comments on commit 813b386

Please sign in to comment.