Skip to content

Commit

Permalink
asyncQueryDataSupport: always run queries asynchronously (#30)
Browse files Browse the repository at this point in the history
- Removes feature toggle for async queries
  • Loading branch information
idastambuk authored Mar 12, 2024
1 parent 6e35a83 commit b6f265f
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 88 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project will be documented in this file.

## v0.2.0

- Remove athenaAsyncQueryDataSupport and redshiftAsyncQueryData feature toggle-related code

## v0.1.11

- Support Node 18 (#25)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@grafana/async-query-data",
"version": "0.1.11",
"version": "0.2.0",
"description": "Async query support for Grafana",
"main": "dist/index.js",
"scripts": {
Expand Down
32 changes: 8 additions & 24 deletions src/DatasourceWithAsyncBackend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,28 +62,23 @@ const defaultRequest = {
startTime: 0,
};

const setupDatasourceWithAsyncBackend = ({
settings = defaultInstanceSettings,
asyncQueryDataSupport = true,
}: {
settings?: DataSourceInstanceSettings<{}>;
asyncQueryDataSupport?: boolean;
}) => new DatasourceWithAsyncBackend<DataQuery>(settings, asyncQueryDataSupport);
const setupDatasourceWithAsyncBackend = (settings: DataSourceInstanceSettings = defaultInstanceSettings) =>
new DatasourceWithAsyncBackend<DataQuery>(settings);

describe('DatasourceWithAsyncBackend', () => {
// beforeAll(() => {
// queryMock.mockClear();
// });

it('can store running queries', () => {
const ds = setupDatasourceWithAsyncBackend({});
const ds = setupDatasourceWithAsyncBackend();

ds.storeQuery(defaultQuery, { queryID: '123' });
expect(ds.getQuery(defaultQuery)).toEqual({ queryID: '123' });
});

it('can remove running queries', () => {
const ds = setupDatasourceWithAsyncBackend({});
const ds = setupDatasourceWithAsyncBackend();

ds.storeQuery(defaultQuery, { queryID: '123' });
expect(ds.getQuery(defaultQuery)).toEqual({ queryID: '123' });
Expand All @@ -92,15 +87,15 @@ describe('DatasourceWithAsyncBackend', () => {
});

it('can cancel running queries', () => {
const ds = setupDatasourceWithAsyncBackend({});
const ds = setupDatasourceWithAsyncBackend();

ds.storeQuery(defaultQuery, { queryID: '123' });
ds.cancel(defaultQuery);
expect(ds.getQuery(defaultQuery)).toEqual({ queryID: '123', shouldCancel: true });
});

it('can queue individual queries to run asynchronously if feature toggle asyncQueryDataSupport is `true`', () => {
const ds = setupDatasourceWithAsyncBackend({ asyncQueryDataSupport: true });
it('will queue individual queries to run asynchronously', () => {
const ds = setupDatasourceWithAsyncBackend();

ds.doSingle = jest.fn().mockReturnValue(Promise.resolve({ data: [] }));
expect(ds.doSingle).not.toHaveBeenCalled();
Expand All @@ -110,19 +105,8 @@ describe('DatasourceWithAsyncBackend', () => {
expect(ds.doSingle).toHaveBeenCalledWith(defaultQuery2, defaultRequest);
});

it('can run queries synchronously if feature toggle asyncQueryDataSupport is `false`', () => {
const ds = setupDatasourceWithAsyncBackend({ asyncQueryDataSupport: false });

ds.doSingle = jest.fn();
expect(ds.doSingle).not.toHaveBeenCalled();
ds.query(defaultRequest);
expect(ds.doSingle).not.toHaveBeenCalled();
expect(queryMock).toHaveBeenCalledTimes(1);
expect(queryMock).toHaveBeenCalledWith(defaultRequest);
});

it('uses the datasource id for the request id', () => {
const ds = setupDatasourceWithAsyncBackend({ asyncQueryDataSupport: true });
const ds = setupDatasourceWithAsyncBackend();
expect(getRequestLooperMock).not.toHaveBeenCalled();
ds.doSingle(defaultQuery, defaultRequest);
expect(getRequestLooperMock).toHaveBeenCalledTimes(1);
Expand Down
32 changes: 13 additions & 19 deletions src/DatasourceWithAsyncBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,36 +39,30 @@ const isCustomMeta = (meta: unknown): meta is CustomMeta => {

export class DatasourceWithAsyncBackend<
TQuery extends DataQuery = DataQuery,
TOptions extends DataSourceJsonData = DataSourceJsonData
TOptions extends DataSourceJsonData = DataSourceJsonData,
> extends DataSourceWithBackend<TQuery, TOptions> {
private runningQueries: { [hash: string]: RunningQueryInfo } = {};
private requestCounter = 100;
private asyncQueryDataSupport: boolean;
private requestIdPrefix: number;

constructor(instanceSettings: DataSourceInstanceSettings<TOptions>, asyncQueryDataSupport = false) {
constructor(instanceSettings: DataSourceInstanceSettings<TOptions>) {
super(instanceSettings);
this.requestIdPrefix = instanceSettings.id;
this.asyncQueryDataSupport = asyncQueryDataSupport;
}

query(request: DataQueryRequest<TQuery>): Observable<DataQueryResponse> {
if (this.asyncQueryDataSupport) {
const targets = this.filterQuery ? request.targets.filter(this.filterQuery) : request.targets;
if (!targets.length) {
return of({ data: [] });
}
const all: Array<Observable<DataQueryResponse>> = [];
for (let target of targets) {
if (target.hide) {
continue;
}
all.push(this.doSingle(target, request));
const targets = this.filterQuery ? request.targets.filter(this.filterQuery) : request.targets;
if (!targets.length) {
return of({ data: [] });
}
const all: Array<Observable<DataQueryResponse>> = [];
for (let target of targets) {
if (target.hide) {
continue;
}
return merge(...all);
} else {
return super.query(request);
all.push(this.doSingle(target, request));
}
return merge(...all);
}

storeQuery(target: TQuery, queryInfo: RunningQueryInfo) {
Expand Down Expand Up @@ -123,7 +117,7 @@ export class DatasourceWithAsyncBackend<
const [_query] = targets;
const query: TQuery & DataQueryMeta = {
..._query,
...(this.asyncQueryDataSupport ? { meta: { queryFlow: 'async' } } : {}),
meta: { queryFlow: 'async' },
};

const data = {
Expand Down
33 changes: 12 additions & 21 deletions src/RunQueryButtons.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {fireEvent, render, screen} from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';

import { DataQuery } from '@grafana/data';
import { RunQueryButtons, RunQueryButtonsProps } from './RunQueryButtons';
Expand All @@ -15,38 +15,29 @@ const getDefaultProps = (overrides?: Partial<RunQueryButtonsProps<DataQuery>>) =
};

describe('RunQueryButtons', () => {
it('renders the `Run` and `Stop` buttons', () => {
const props = getDefaultProps();
render(<RunQueryButtons {...props} />);
const runButton = screen.getByRole('button', { name: 'Run query' });
expect(runButton).toBeInTheDocument();
const stopButton = screen.queryByRole('button', { name: 'Stop query' });
expect(stopButton).toBeInTheDocument();
});

it('disable the run button if the if the enableRun button is false', () => {
const props = getDefaultProps({ enableRun: false});
const props = getDefaultProps({ enableRun: false });
render(<RunQueryButtons {...props} />);
const runButton = screen.getByRole('button', { name: 'Run query' });
expect(runButton).toBeDisabled();
});

it('run button should be enabled if the enableRun button is true', () => {
const props = getDefaultProps({ enableRun: true});
const props = getDefaultProps({ enableRun: true });
render(<RunQueryButtons {...props} />);
const runButton = screen.getByRole('button', { name: 'Run query' });
expect(runButton).not.toBeDisabled();
});

it('only renders the `Run` button if onCancelQuery is undefined', () => {
const props = getDefaultProps({ onCancelQuery: undefined });
render(<RunQueryButtons {...props} />);
const runButton = screen.getByRole('button', { name: 'Run query' });
expect(runButton).toBeInTheDocument();
const stopButton = screen.queryByRole('button', { name: 'Stop query' });
expect(stopButton).not.toBeInTheDocument();
});

it('renders the `Run` and `Stop` buttons if onCancelQuery defined', () => {
const props = getDefaultProps();
render(<RunQueryButtons {...props} />);
const runButton = screen.getByRole('button', { name: 'Run query' });
expect(runButton).toBeInTheDocument();
const stopButton = screen.queryByRole('button', { name: 'Stop query' });
expect(stopButton).toBeInTheDocument();
});

it('Stop query button should be disabled until run button is clicked', () => {
const props = getDefaultProps();
render(<RunQueryButtons {...props} />);
Expand Down
44 changes: 21 additions & 23 deletions src/RunQueryButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DataQuery, LoadingState } from '@grafana/data';
export interface RunQueryButtonsProps<TQuery extends DataQuery> {
enableRun?: boolean;
onRunQuery: () => void;
onCancelQuery?: (query: TQuery) => void;
onCancelQuery: (query: TQuery) => void;
query: TQuery;
state?: LoadingState;
}
Expand Down Expand Up @@ -40,27 +40,25 @@ export const RunQueryButtons = <TQuery extends DataQuery>(props: RunQueryButtons
: undefined;

return (
<>
<Button
variant={props.enableRun ? 'primary' : 'secondary'}
size="sm"
onClick={onRunQuery}
icon={running && !stopping ? 'fa fa-spinner' : undefined}
disabled={state === LoadingState.Loading || !props.enableRun}
>
Run query
</Button>
{onCancelQuery &&
<Button
variant={running && !stopping ? 'primary' : 'secondary'}
size="sm"
disabled={!running || stopping}
icon={stopping ? 'fa fa-spinner' : undefined}
onClick={onCancelQuery}
>
Stop query
</Button>
}
</>
<>
<Button
variant={props.enableRun ? 'primary' : 'secondary'}
size="sm"
onClick={onRunQuery}
icon={running && !stopping ? 'fa fa-spinner' : undefined}
disabled={state === LoadingState.Loading || !props.enableRun}
>
Run query
</Button>
<Button
variant={running && !stopping ? 'primary' : 'secondary'}
size="sm"
disabled={!running || stopping}
icon={stopping ? 'fa fa-spinner' : undefined}
onClick={onCancelQuery}
>
Stop query
</Button>
</>
);
};

0 comments on commit b6f265f

Please sign in to comment.