Skip to content

Commit

Permalink
[POFSP-222] Fix regressions in events table and annotations query (#305)
Browse files Browse the repository at this point in the history
* Fix regressions in the events table and annotations query
* Re-added hints/examples for the annotation query syntax
  • Loading branch information
polomani authored Nov 8, 2023
1 parent 6529dca commit 3297bf3
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 6 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@

This article documents the ongoing improvements we're making to the **Cognite Data Source for Grafana**.


## 4.0.1 - November 7th, 2023

### Bug fixes
- Patched regressions introduced after the migration to React:
* Events table `activeAtTime` filter is now working as before
* Annotations filters are applied correctly as before
* Added back hints and examples for annotation query
- Multiple dependencies have been updated to fix security vulnerabilities

## 4.0.0 - October 12th, 2023

### Features
- Migrate Annotation editor from Angular to React
- Bumped minimum Grafana version requirement to v10
- Events are returned in dataframe format

## 3.1.0 - May 10th, 2023

### Features
- Added support for the new version of CDF Data Models (GraphQL)
- Added an option to sort Events table

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": "@cognite/cognite-grafana-datasource",
"version": "4.0.0",
"version": "4.0.1",
"description": "Cognite Data Fusion datasource",
"repository": "https://github.com/cognitedata/cognite-grafana-datasource",
"author": "Cognite AS",
Expand Down
164 changes: 164 additions & 0 deletions src/__tests__/datasource.events.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,43 @@ describe('events datasource test', () => {
},
}

it('simple event query outside of time range', async () => {

fetchItemsMock.mockResolvedValueOnce([])

const query: CogniteQuery = {
...eventQueryBase,
eventQuery: {
...eventQueryBase.eventQuery,
activeAtTimeRange: false,
}
}
const res = await eventsDatasource.fetchEventTargets(
[query],
[startTime, endTime]
);

expect(fetchItemsMock).toHaveBeenCalledTimes(1)
expect(connector.fetchItems).toHaveBeenCalledWith({
data: {
filter: {
},
limit: 1000,
},
headers: undefined,
method: "POST",
path: "/events/list"
})

expect(res).toEqual([
{
fields: [],
length: 0,
name: "Events",
refId: undefined,
},
]);
});

it('simple event query with sort', async () => {

Expand Down Expand Up @@ -134,4 +171,131 @@ describe('events datasource test', () => {
},
]);
});

it('annotations event query (empty)', async () => {

fetchItemsMock.mockResolvedValueOnce([])

const query: CogniteQuery = {
...defaultCogniteQuery,
refId: "Anno",
tab: null,
query: "events{}",
}
const res = await eventsDatasource.fetchEventTargets(
[query],
[startTime, endTime]
);

expect(fetchItemsMock).toHaveBeenCalledTimes(1)
expect(connector.fetchItems).toHaveBeenCalledWith({
data: {
filter: {
activeAtTime: {
max: endTime,
min: startTime,
},
},
limit: 1000,
},
headers: undefined,
method: "POST",
path: "/events/list"
})

expect(res).toEqual([
{
fields: [],
length: 0,
name: "Events",
refId: "Anno",
},
]);
});

it('annotations event query (with end time override)', async () => {

fetchItemsMock.mockResolvedValueOnce([])

const query: CogniteQuery = {
...defaultCogniteQuery,
refId: "Anno",
tab: null,
query: "events{endTime={isNull=false}}",
}
const res = await eventsDatasource.fetchEventTargets(
[query],
[startTime, endTime]
);

expect(fetchItemsMock).toHaveBeenCalledTimes(1)
expect(connector.fetchItems).toHaveBeenCalledWith({
data: {
filter: {
activeAtTime: {
max: endTime,
min: startTime,
},
endTime: {
isNull: false
}
},
limit: 1000,
},
headers: undefined,
method: "POST",
path: "/events/list"
})

expect(res).toEqual([
{
fields: [],
length: 0,
name: "Events",
refId: "Anno",
},
]);
});

it('annotations event query (with filters)', async () => {

fetchItemsMock.mockResolvedValueOnce([])

const query: CogniteQuery = {
...defaultCogniteQuery,
refId: "Anno",
tab: null,
query: "events{subtype='test'}",
}
const res = await eventsDatasource.fetchEventTargets(
[query],
[startTime, endTime]
);

expect(fetchItemsMock).toHaveBeenCalledTimes(1)
expect(connector.fetchItems).toHaveBeenCalledWith({
data: {
filter: {
activeAtTime: {
max: endTime,
min: startTime,
},
subtype: 'test'
},
limit: 1000,
},
headers: undefined,
method: "POST",
path: "/events/list"
})

expect(res).toEqual([
{
fields: [],
length: 0,
name: "Events",
refId: "Anno",
},
]);
});
});
43 changes: 43 additions & 0 deletions src/components/annotationsQueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,48 @@ type AnnotationQueryEditorProps<TQuery extends DataQuery> = QueryEditorProps<any
onAnnotationChange?: (annotation: AnnotationQuery<TQuery>) => void;
};

const help = (
<pre>
Annotation query uses the <a className="query-keyword" href="https://docs.cognite.com/api/v1/#operation/advancedListEvents" target="_blank" rel="noreferrer">events/list</a> endpoint to fetch data.
<br />
<br />
Use <code className="query-keyword">&apos;=&apos;</code> operator to provide parameters for the request.
<br />
Format: <code className="query-keyword">{`events{param=number, ...}`}</code>
<br />
Example: <code className="query-keyword">{`events{externalIdPrefix='PT', type='WORKORDER', assetSubtreeIds=[{id=12}, {externalId='external'}]}`}</code>
<br />
<br />

By default, the query displays all events that are active in the time range.
<br />
You can customize this with the additional time filters <code className="query-keyword">startTime</code>, <code className="query-keyword">endTime</code>.
<br />
This example shows how to display all finished events that started in the current time range:
<br />
<code className="query-keyword">{`events{startTime={min=$__from}, endTime={isNull=false}}`}</code>
<br />
<br />

You can specify additional client-side filtering with the <code className="query-keyword">&apos;=~&apos;</code>, <code className="query-keyword">&apos;!~&apos;</code> and <code className="query-keyword">&apos;!=&apos;</code> operators. Comma between multiple filters acts as logic <code className="query-keyword">AND</code>.<br />
Format:<br />
<code className="query-keyword">&apos;=~&apos;</code> – regex equality, returns results satisfying the regular expression.
<br />
<code className="query-keyword">&apos;!~&apos;</code> – regex inequality, excludes results satisfying the regular expression.
<br />
<code className="query-keyword">&apos;!=&apos;</code> – strict inequality, returns items where a property doesn&apos;t equal a given value.
<br />
Example: <code className="query-keyword">{`events{type='WORKORDER', subtype=~'SUB.*'}`}</code>
<br />
<br />
Templating is available by using the <code className="query-keyword">$variable</code> syntax.
<br />
Example: <code className="query-keyword">{`events{type='WORKORDER', subtype=$variable}`}</code>.
<br />
To learn more about the querying capabilities of Cognite Data Source for Grafana, please visit our <a className="query-keyword" href="https://docs.cognite.com/cdf/dashboards/guides/grafana/getting_started.html">documentation</a>.
</pre>
);

export class AnnotationsQueryEditor extends React.PureComponent<AnnotationQueryEditorProps<CogniteQuery>, AnnotationQueryData> {

defaults = {
Expand Down Expand Up @@ -58,6 +100,7 @@ export class AnnotationsQueryEditor extends React.PureComponent<AnnotationQueryE
</div>
<div className="gf-form--grow">
{this.state.error ? <pre className="gf-formatted-error">{this.state.error}</pre> : null}
{help}
</div>
</div>

Expand Down
14 changes: 9 additions & 5 deletions src/datasources/EventsDatasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,19 @@ export class EventsDatasource {
}

async fetchEventsForTarget(
{ refId, eventQuery, query }: CogniteQuery,
target: CogniteQuery,
[rangeStart, rangeEnd]: Tuple<number>
) {
const timeFrame = {
const { refId, eventQuery, query } = target;
const isAnnotation = isAnnotationTarget(target);
const activeAtTimeRange = isAnnotation || target.eventQuery?.activeAtTimeRange;
const timeFrame = activeAtTimeRange ? {
activeAtTime: { min: rangeStart, max: rangeEnd },
};
const finalEventQuery = eventQuery || {
} : {};
const finalEventQuery = isAnnotation ? {
expr: query,
}
} : eventQuery

try {
const { items, hasMore } = await this.fetchEvents(finalEventQuery, timeFrame);
if (hasMore) {
Expand Down

0 comments on commit 3297bf3

Please sign in to comment.