Skip to content

feat(workorders): Use users class to add lookup and process workorders data #228

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

Open
wants to merge 4 commits into
base: users/manisha/add-users-class
Choose a base branch
from
Open
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
127 changes: 125 additions & 2 deletions src/datasources/work-orders/WorkOrdersDataSource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,32 @@ import { BackendSrv } from '@grafana/runtime';
import { MockProxy } from 'jest-mock-extended';
import { setupDataSource, requestMatching, createFetchResponse, createFetchError } from 'test/fixtures';
import { WorkOrdersDataSource } from './WorkOrdersDataSource';
import { OrderByOptions, OutputType, State, Type, WorkOrderPropertiesOptions, WorkOrdersResponse } from './types';
import {
OrderByOptions,
OutputType,
State,
Type,
WorkOrder,
WorkOrderPropertiesOptions,
WorkOrdersResponse,
} from './types';
import { DataQueryRequest } from '@grafana/data';

let datastore: WorkOrdersDataSource, backendServer: MockProxy<BackendSrv>;
jest.mock('shared/Users', () => {
return {
Users: jest.fn().mockImplementation(() => ({
usersMapCache: Promise.resolve(
new Map([
['1', 'user 1'],
['2', 'user 2'],
['3', 'user 3'],
['4', 'user 4'],
])
),
})),
};
});

describe('WorkOrdersDataSource', () => {
const mockWorkOrders: WorkOrdersResponse = {
Expand Down Expand Up @@ -42,7 +64,12 @@ describe('WorkOrdersDataSource', () => {

describe('runQuery', () => {
test('processes work orders query when outputType is Properties', async () => {
const mockQuery = { refId: 'A', outputType: OutputType.Properties, queryBy: 'filter', properties: [WorkOrderPropertiesOptions.WORKSPACE] };
const mockQuery = {
refId: 'A',
outputType: OutputType.Properties,
queryBy: 'filter',
properties: [WorkOrderPropertiesOptions.WORKSPACE],
};

jest.spyOn(datastore, 'queryWorkordersData').mockResolvedValue(mockWorkOrders.workOrders);

Expand All @@ -62,6 +89,102 @@ describe('WorkOrdersDataSource', () => {
expect(result.fields).toEqual([{ name: 'Total count', values: [42] }]);
expect(result.refId).toEqual('B');
});

it('should convert user Ids to user names for assigned to field', async () => {
const query = {
refId: 'A',
outputType: OutputType.Properties,
properties: [WorkOrderPropertiesOptions.ASSIGNED_TO],
orderBy: OrderByOptions.UPDATED_AT,
recordCount: 10,
descending: true,
};

const workOrdersResponse = [
{ id: '1', assignedTo: '1' },
{ id: '2', assignedTo: '2' },
];

jest.spyOn(datastore, 'queryWorkordersData').mockResolvedValue(workOrdersResponse as WorkOrder[]);

const result = await datastore.runQuery(query, {} as DataQueryRequest);

expect(result.fields).toHaveLength(1);
expect(result.fields[0].name).toEqual('Assigned to');
expect(result.fields[0].values).toEqual(['user 1', 'user 2']);
});

it('should convert user Ids to user names for created by field', async () => {
const query = {
refId: 'A',
outputType: OutputType.Properties,
properties: [WorkOrderPropertiesOptions.CREATED_BY],
orderBy: OrderByOptions.UPDATED_AT,
recordCount: 10,
descending: true,
};

const workOrdersResponse = [
{ id: '1', createdBy: '2' },
{ id: '2', createdBy: '3' },
];

jest.spyOn(datastore, 'queryWorkordersData').mockResolvedValue(workOrdersResponse as WorkOrder[]);

const result = await datastore.runQuery(query, {} as DataQueryRequest);

expect(result.fields).toHaveLength(1);
expect(result.fields[0].name).toEqual('Created by');
expect(result.fields[0].values).toEqual(['user 2', 'user 3']);
});

it('should convert user Ids to user names for requested by field', async () => {
const query = {
refId: 'A',
outputType: OutputType.Properties,
properties: [WorkOrderPropertiesOptions.REQUESTED_BY],
orderBy: OrderByOptions.UPDATED_AT,
recordCount: 10,
descending: true,
};

const workOrdersResponse = [
{ id: '1', requestedBy: '3' },
{ id: '2', requestedBy: '4' },
];

jest.spyOn(datastore, 'queryWorkordersData').mockResolvedValue(workOrdersResponse as WorkOrder[]);

const result = await datastore.runQuery(query, {} as DataQueryRequest);

expect(result.fields).toHaveLength(1);
expect(result.fields[0].name).toEqual('Requested by');
expect(result.fields[0].values).toEqual(['user 3', 'user 4']);
});

it('should convert user Ids to user names for updated by field', async () => {
const query = {
refId: 'A',
outputType: OutputType.Properties,
properties: [WorkOrderPropertiesOptions.UPDATED_BY],
orderBy: OrderByOptions.UPDATED_AT,
recordCount: 10,
descending: true,
};

const workOrdersResponse = [
{ id: '1', updatedBy: '4' },
{ id: '2', updatedBy: '1' },
];

jest.spyOn(datastore, 'queryWorkordersData').mockResolvedValue(workOrdersResponse as WorkOrder[]);

const result = await datastore.runQuery(query, {} as DataQueryRequest);

expect(result.fields).toHaveLength(1);
expect(result.fields[0].name).toEqual('Updated by');
expect(result.fields[0].values).toEqual(['user 4', 'user 1']);
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test to verify the fallback to userId

});

describe('queryWorkordersData', () => {
Expand Down
35 changes: 28 additions & 7 deletions src/datasources/work-orders/WorkOrdersDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { DataSourceInstanceSettings, DataQueryRequest, DataFrameDTO, FieldType,
import { BackendSrv, TemplateSrv, getBackendSrv, getTemplateSrv } from '@grafana/runtime';
import { DataSourceBase } from 'core/DataSourceBase';
import { WorkOrdersQuery, OutputType, WorkOrderPropertiesOptions, OrderByOptions, WorkOrder, WorkOrderProperties, QueryWorkOrdersRequestBody, WorkOrdersResponse } from './types';
import { Users } from 'shared/Users';

export class WorkOrdersDataSource extends DataSourceBase<WorkOrdersQuery> {
readonly usersObj = new Users(this.instanceSettings, this.backendSrv)
constructor(
readonly instanceSettings: DataSourceInstanceSettings,
readonly backendSrv: BackendSrv = getBackendSrv(),
Expand Down Expand Up @@ -57,17 +59,26 @@ export class WorkOrdersDataSource extends DataSourceBase<WorkOrdersQuery> {
query.descending
);

const mappedFields = query.properties?.map(property => {
const mappedFields = await Promise.all(query.properties?.map(async property => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid Promise.all, query and store them before this implementation, refer here.

const field = WorkOrderProperties[property];
const fieldType = this.isTimeField(field.value) ? FieldType.time : FieldType.string;
const fieldName = field.label;

// TODO: Add mapping for other field types
const fieldValue = workOrders.map(data => data[field.field as unknown as keyof WorkOrder]);

return { name: fieldName, values: fieldValue, type: fieldType };
});

const fieldValues = await Promise.all(workOrders.map(async workOrder => {
// TODO: Add mapping for other field types
switch (field.value) {
case WorkOrderPropertiesOptions.ASSIGNED_TO:
case WorkOrderPropertiesOptions.CREATED_BY:
case WorkOrderPropertiesOptions.REQUESTED_BY:
case WorkOrderPropertiesOptions.UPDATED_BY:
return await this.getUserName(workOrder[field.field] as string).then(name => name || workOrder[field.field] || '');
default:
return workOrder[field.field] ?? '';
}
})
)
return { name: fieldName, values: fieldValues, type: fieldType };
}) ?? [])
return {
refId: query.refId,
name: query.refId,
Expand Down Expand Up @@ -130,5 +141,15 @@ export class WorkOrdersDataSource extends DataSourceBase<WorkOrdersQuery> {

return timeFields.includes(field);
}

private async getUserName(userId: string): Promise<string> {
if (!userId) {
return '';
}
const usersMap = await this.usersObj.usersMapCache;
const userName = usersMap.get(userId);
return userName ?? userId;
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ const mockOnChange = jest.fn();
const mockOnRunQuery = jest.fn();
const mockDatasource = {
prepareQuery: jest.fn((query: WorkOrdersQuery) => query),
usersObj: jest.fn().mockImplementation(() => ({
usersCache: Promise.resolve(
[{ id: '1', name: 'User 1' },
{ id: '2', name: 'User 2' },]
),
})),
} as unknown as WorkOrdersDataSource;

const defaultProps: QueryEditorProps<WorkOrdersDataSource, WorkOrdersQuery> = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { QueryEditorProps, SelectableValue } from '@grafana/data';
import { WorkOrdersDataSource } from '../WorkOrdersDataSource';
import { OrderBy, OutputType, WorkOrderProperties, WorkOrderPropertiesOptions, WorkOrdersQuery } from '../types';
Expand All @@ -11,11 +11,21 @@ import {
VerticalGroup
} from '@grafana/ui';
import './WorkOrdersQueryEditor.scss';
import { User } from 'shared/types/QueryUsers.types';

type Props = QueryEditorProps<WorkOrdersDataSource, WorkOrdersQuery>;

export function WorkOrdersQueryEditor({ query, onChange, onRunQuery, datasource }: Props) {
query = datasource.prepareQuery(query);
const [users, setUsers] = useState<User[]|null>(null);
useEffect(() => {
const loadUsers = async () => {
const usersList = await datasource.usersObj.usersCache;
setUsers(usersList);
};

loadUsers();
}, [datasource.usersObj]);

useEffect(() => {
handleQueryChange(query, true);
Expand Down Expand Up @@ -91,6 +101,7 @@ export function WorkOrdersQueryEditor({ query, onChange, onRunQuery, datasource
<InlineField label="Query By" labelWidth={25} tooltip={tooltips.queryBy}>
<WorkOrdersQueryBuilder
filter={query.queryBy}
users={users}
globalVariableOptions={[]}
onChange={(event: any) => onQueryByChange(event.detail.linq)}
></WorkOrdersQueryBuilder>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,25 @@ import { QueryBuilderOption } from 'core/types';
import React, { ReactNode } from 'react';
import { render } from '@testing-library/react';
import { WorkOrdersQueryBuilder } from './WorkOrdersQueryBuilder';
import { User } from 'shared/types/QueryUsers.types';

describe('WorkOrdersQueryBuilder', () => {
let reactNode: ReactNode;
const containerClass = 'smart-filter-group-condition-container';
const mockUser = {
id: '1',
firstName: 'User',
lastName: '1',
email: '',
properties: {},
keywords: [],
created: '',
updated: '',
orgId: ''
};

function renderElement(filter: string, globalVariableOptions: QueryBuilderOption[] = []) {
reactNode = React.createElement(WorkOrdersQueryBuilder, { filter, globalVariableOptions, onChange: jest.fn() });
function renderElement(filter: string, users: User[] = [], globalVariableOptions: QueryBuilderOption[] = []) {
reactNode = React.createElement(WorkOrdersQueryBuilder, { filter,users, globalVariableOptions, onChange: jest.fn() });
const renderResult = render(reactNode);
return {
renderResult,
Expand All @@ -22,4 +34,32 @@ describe('WorkOrdersQueryBuilder', () => {
expect(conditionsContainer.length).toBe(1);
expect(renderResult.findByLabelText('Empty condition row')).toBeTruthy();
});

it('should select assigned to in query builder', () => {
const { conditionsContainer } = renderElement('assignedTo = "1"', [mockUser]);

expect(conditionsContainer?.length).toBe(1);
expect(conditionsContainer.item(0)?.textContent).toContain("User 1");
});

it('should select created by in query builder', () => {
const { conditionsContainer } = renderElement('createdBy = "1"', [mockUser]);

expect(conditionsContainer?.length).toBe(1);
expect(conditionsContainer.item(0)?.textContent).toContain("User 1");
});

it('should select requested by in query builder', () => {
const { conditionsContainer } = renderElement('requestedBy = "1"', [mockUser]);

expect(conditionsContainer?.length).toBe(1);
expect(conditionsContainer.item(0)?.textContent).toContain("User 1");
});

it('should select updated by in query builder', () => {
const { conditionsContainer } = renderElement('updatedBy = "1"', [mockUser]);

expect(conditionsContainer?.length).toBe(1);
expect(conditionsContainer.item(0)?.textContent).toContain("User 1");
});
});
Loading