Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event table sorting #239

Merged
merged 7 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintcache

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ yarn-error.log*

.idea
.ionide/
.eslintcache
3 changes: 2 additions & 1 deletion src/__tests__/cdf.client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,14 @@ describe('CDF client', () => {
{ id: 2, name: 'name2' },
];
const columns = ['name', 'metadata.prop', 'startTime'];
const table = convertItemsToTable(items, columns);
const table = convertItemsToTable(items, columns, "table");
expect(table).toEqual({
rows: [
['name1', 1, new Date(100)],
['name2', undefined, undefined],
],
type: 'table',
name: "table",
columns: [
{
text: 'name',
Expand Down
145 changes: 145 additions & 0 deletions src/__tests__/datasource.events.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { Connector } from '../connector';
import { defaultQuery, CogniteQuery, Tab } from '../types';
import { EventsDatasource } from 'datasources';

const defaultCogniteQuery = defaultQuery as CogniteQuery;

describe('events datasource test', () => {
let connector: Connector;
let eventsDatasource: EventsDatasource;
const startTime = 1549336675000;
const endTime = 1549338475000;
const fetchItemsMock = jest.fn()

beforeEach(() => {
jest.resetAllMocks();
connector = {
fetchData: jest.fn(),
fetchItems: fetchItemsMock,
fetchAndPaginate: jest.fn(),

isTemplatesEnabled: () => true,
isEventsAdvancedFilteringEnabled: () => true,
isFlexibleDataModellingEnabled: () => true,
isExtractionPipelinesEnabled: () => true,
} as unknown as Connector;
eventsDatasource = new EventsDatasource(connector);
});

const eventQueryBase: CogniteQuery = {
...defaultCogniteQuery,
tab: Tab.Event,
eventQuery: {
expr: "events{}",
columns: ['externalId'],
activeAtTimeRange: true,
},
}


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

fetchItemsMock.mockResolvedValueOnce([])

const query: CogniteQuery = {
...eventQueryBase,
eventQuery: {
...eventQueryBase.eventQuery,
sort: [{
property: "startTime",
order: "asc"
}]
}
}
const res = await eventsDatasource.fetchEventTargets(
[query],
[startTime, endTime]
);

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

expect(res).toEqual([
{
columns: [
{
text: "externalId",
},
],
name: "events",
rows: [],
type: "table",
},
]);
});

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

fetchItemsMock.mockResolvedValueOnce([])

const query: CogniteQuery = {
...eventQueryBase,
eventQuery: {
...eventQueryBase.eventQuery,
sort: [{
property: "metadata.sourceId",
order: "asc"
}]
},
}
const res = await eventsDatasource.fetchEventTargets(
[query],
[startTime, endTime]
);

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

expect(res).toEqual([
{
columns: [
{
text: "externalId",
},
],
name: "events",
rows: [],
type: "table",
},
]);
});
});
2 changes: 1 addition & 1 deletion src/cdf/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ export const targetToIdEither = (obj: CogniteTargetObj) => {
export const convertItemsToTable = (
items: Resource[],
columns: string[],
name = undefined
name: string
): TableData => {
const rows = items.map((item) =>
columns.map((field) => {
Expand Down
17 changes: 16 additions & 1 deletion src/cdf/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,23 @@ export type EventsFilterTimeParams =
| Pick<EventsFilterRequestParams, 'activeAtTime'>
| Pick<EventsFilterRequestParams, 'startTime' | 'endTime'>;

export interface FilterRequest<Filter> extends Limit, Cursor {

export interface EventSortRequestParam {
property: string[], order?: 'asc' | 'desc', nulls?: 'first' | 'last' | 'auto'
}

export interface FilterRequest<Filter, SortType = {}> extends Limit, Cursor {
filter?: Filter;
sort?: SortType;
advancedFilter?: any;
}

export interface AggregateRequest<Filter> extends Limit, Cursor {
filter?: Filter;
aggregate: string;
properties: Array<{
property?: string[];
}>;
advancedFilter?: any;
}

Expand Down
91 changes: 89 additions & 2 deletions src/components/eventsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import {
Switch,
Segment,
Tooltip,
Select,
} from '@grafana/ui';
import jsonlint from 'jsonlint-mod';
import { EventQuery, SelectedProps, EditorProps } from '../types';
import { EventFields } from '../constants';
import { EventQuery, SelectedProps, EditorProps, EventsOrderDirection } from '../types';
import { EventFields, EventSortByFields } from '../constants';
import CogniteDatasource from '../datasource';
import { EventQueryHelp, EventAdvancedFilterHelp } from './queryHelp';
import { InlineButton } from './inlineButton';
Expand Down Expand Up @@ -45,6 +46,7 @@ const ActiveAtTimeRangeCheckbox = (props: SelectedProps) => {
const ColumnsPicker = ({ query, onQueryChange }: SelectedProps) => {
const options = EventFields.map((value) => ({ value, label: value }));
const { columns, aggregate } = query.eventQuery;

useEffect(() => {
if (aggregate?.withAggregate) {
['count', 'map'].map((v) => !columns.includes(v) && columns.push(v));
Expand Down Expand Up @@ -103,6 +105,90 @@ const ColumnsPicker = ({ query, onQueryChange }: SelectedProps) => {
</div>
);
};

const OrderDirectionEditor = (
{ onChange, direction = "asc" }:
{ direction: EventsOrderDirection, onChange: (val: EventsOrderDirection) => void }
) => {
const options = [{ label: "ascending", value: "asc" }, { label: "descending", value: "desc" }]

return (
<div className="gf-form">
<InlineFormLabel width={6}>Order</InlineFormLabel>
<Select
onChange={({ value }) => onChange(value as EventsOrderDirection)}
options={options}
menuPosition="fixed"
value={direction}
className="cognite-dropdown width-10"
/>
</div>
);
};


const SortByPicker = ({ query, onQueryChange }: SelectedProps ) => {
const options = EventSortByFields.map((value) => ({ value, label: value }));
const { sort = [] } = query.eventQuery;

const onEventQueryChange = (e: Partial<EventQuery>) => {
onQueryChange({
eventQuery: {
...query.eventQuery,
...e,
},
});
};

return (
<div className="gf-form">
<InlineFormLabel
tooltip="Property to sort on. To access metadata property, use 'metadata.propertyName'"
width={7}
>
Sort by
</InlineFormLabel>
<div className="gf-form" style={{ flexWrap: 'wrap' }}>
{sort.map((val, key) => (
<>
<Segment
value={val.property}
options={options}
onChange={({ value }) => {
onEventQueryChange({
sort: sort.map((old, i) => (i === key ? { ...old, property: value } : old)),
});
}}
allowCustomValue
/>
<OrderDirectionEditor direction={val.order} onChange={value => {
onEventQueryChange({
sort: sort.map((old, i) => (i === key ? { ...old, order: value } : old)),
});
}} />
<InlineButton
onClick={() => {
onEventQueryChange({
sort: sort.filter((_, i) => i !== key),
});
}}
iconName="times"
/>
</>
))}
{sort?.length < 2 ? <InlineButton
onClick={() => {
onEventQueryChange({
sort: [...sort, { property: 'type', order: 'asc' }],
});
}}
iconName="plus-circle"
/> : null}
</div>
</div>
);
};

const ActiveAggregateCheckbox = ({ query, onQueryChange }: SelectedProps) => {
return (
<div className="gf-form gf-form-inline">
Expand Down Expand Up @@ -291,6 +377,7 @@ export function EventsTab(
</div>
<ActiveAtTimeRangeCheckbox {...{ query, onQueryChange }} />
<ColumnsPicker {...{ query, onQueryChange }} />
<SortByPicker {...{ query, onQueryChange }} />
{datasource.connector.isEventsAdvancedFilteringEnabled() && (
<AdvancedEventFilter {...{ query, onQueryChange }} />
)}
Expand Down
17 changes: 14 additions & 3 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ export const DateFields = [
'createdTime',
'startTime',
'endTime',
'lastSeen',
'lastFailure',
'lastSuccess',
];
const commonFields = ['id', 'externalId'];
export const EventFields = [
Expand All @@ -50,6 +47,17 @@ export const EventFields = [
'dataSetId',
...DateFields,
];
export const EventSortByFields = [
'dataSetId',
'description',
'externalId',
'metadata',
'source',
'subtype',
'type',
...DateFields,
'_score_',
];
const createFields = (parent, field) => `${parent}-${field}`;
const metaFields = [
'Dockerfile',
Expand Down Expand Up @@ -78,6 +86,9 @@ export const ExtractionPipelinesFields = [
...metaFields,
...notificationConfigFields,
...DateFields,
'lastSeen',
'lastFailure',
'lastSuccess',
];
export const EVENTS_PAGE_LIMIT = 1000;

Expand Down
Loading