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

feat(asset): Innovation Days: Simple/Advanced option for query builder in List Assets #112

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions src/datasources/asset/AssetConfigEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ export const AssetConfigEditor: React.FC<Props> = ({ options, onOptionsChange })
</InlineField>
<Tag name='Beta' colorIndex={5} />
</InlineSegmentGroup>
<InlineSegmentGroup>
<InlineField label="Advanced Filter" labelWidth={25}>
<InlineSwitch
value={options.jsonData?.featureToggles?.advancedFilter ?? AssetFeatureTogglesDefaults.advancedFilter}
onChange={handleFeatureChange('advancedFilter')} />
</InlineField>
<Tag name='Beta' colorIndex={5} />
</InlineSegmentGroup>
</>
</>
);
Expand Down
5 changes: 4 additions & 1 deletion src/datasources/asset/AssetDataSource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AssetQueryType } from "./types/types";
import { AssetPresenceWithSystemConnectionModel, AssetsResponse } from "datasources/asset-common/types";
import { ListAssetsQuery } from "./types/ListAssets.types";
import { AssetVariableQuery } from "./types/AssetVariableQuery.types";
import { QueryBuilderType } from "./constants/constants";

let ds: AssetDataSource, backendSrv: MockProxy<BackendSrv>
let assetOptions = {
Expand Down Expand Up @@ -306,10 +307,10 @@ const assetsWithoutNameResponseMock: AssetsResponse =
"totalCount": 2
}


const assetMetadataQueryMock: ListAssetsQuery = {
type: AssetQueryType.ListAssets,
filter: 'Location.MinionId == "123"',
queryBuilderType: QueryBuilderType.Simple,
refId: ''
}

Expand Down Expand Up @@ -362,6 +363,7 @@ describe('queries', () => {
const query: AssetVariableQuery = {
filter: '',
type: AssetQueryType.None,
queryBuilderType: QueryBuilderType.Simple,
refId: ""
}

Expand All @@ -382,6 +384,7 @@ describe('queries', () => {
const query: AssetVariableQuery = {
filter: '',
type: AssetQueryType.None,
queryBuilderType: QueryBuilderType.Simple,
refId: ""
}

Expand Down
3 changes: 2 additions & 1 deletion src/datasources/asset/AssetDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ListAssetsQuery } from './types/ListAssets.types';
import { ListAssetsDataSource } from './data-sources/list-assets/ListAssetsDataSource';
import { AssetSummaryDataSource } from './data-sources/asset-summary/AssetSummaryDataSource';
import { AssetModel } from 'datasources/asset-common/types';
import { QUERY_LIMIT } from './constants/constants';
import { QUERY_LIMIT, QueryBuilderType } from './constants/constants';
import { transformComputedFieldsQuery } from 'core/query-builder.utils';
import { AssetVariableQuery } from './types/AssetVariableQuery.types';

Expand All @@ -44,6 +44,7 @@ export class AssetDataSource extends DataSourceBase<AssetQuery, AssetDataSourceO

defaultQuery = {
type: AssetQueryType.None,
queryBuilderType: QueryBuilderType.Simple,
};

async runQuery(query: AssetQuery, options: DataQueryRequest): Promise<DataFrameDTO> {
Expand Down
2 changes: 2 additions & 0 deletions src/datasources/asset/components/AssetQueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function AssetQueryEditor({ query, onChange, onRunQuery, datasource }: Pr
assetList: datasource.instanceSettings.jsonData?.featureToggles?.assetList ?? AssetFeatureTogglesDefaults.assetList,
calibrationForecast: datasource.instanceSettings.jsonData?.featureToggles?.calibrationForecast ?? AssetFeatureTogglesDefaults.calibrationForecast,
assetSummary: datasource.instanceSettings.jsonData?.featureToggles?.assetSummary ?? AssetFeatureTogglesDefaults.assetSummary,
advancedFilter: datasource.instanceSettings.jsonData?.featureToggles?.advancedFilter ?? AssetFeatureTogglesDefaults.advancedFilter,
});

const handleQueryChange = useCallback((value: AssetQuery, runQuery = false): void => {
Expand Down Expand Up @@ -78,6 +79,7 @@ export function AssetQueryEditor({ query, onChange, onRunQuery, datasource }: Pr
query={query as ListAssetsQuery}
handleQueryChange={handleQueryChange}
datasource={datasource.getListAssetsSource()}
complexFilterEnabled={assetFeatures.current.advancedFilter}
/>
)}
{((assetFeatures.current.calibrationForecast && queryType === AssetQueryType.CalibrationForecast) || (query.type === AssetQueryType.CalibrationForecast)) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AssetSummaryResponse } from 'datasources/asset/types/AssetSummaryQuery.
import { AssetDataSourceOptions, AssetQuery, AssetQueryType } from 'datasources/asset/types/types';
import { AssetSummaryDataSource } from '../../../data-sources/asset-summary/AssetSummaryDataSource';
import { assetSummaryFields } from '../../../constants/AssetSummaryQuery.constants';
import { QueryBuilderType } from 'datasources/asset/constants/constants';

describe('AssetSummaryDataSource', () => {
let dataSource: AssetSummaryDataSource;
Expand All @@ -25,7 +26,7 @@ describe('AssetSummaryDataSource', () => {
});

it('should process metadata query correctly', async () => {
const query: AssetQuery = { refId: 'A', type: AssetQueryType.AssetSummary, };
const query: AssetQuery = { refId: 'A', type: AssetQueryType.AssetSummary, queryBuilderType: QueryBuilderType.Simple, };

jest.spyOn(dataSource, 'getAssetSummary').mockResolvedValue(assetSummary);
const result = await dataSource.processSummaryQuery(query);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { CalibrationForecastDataSource } from '../../../data-sources/calibration
import { AssetQueryEditor } from '../../AssetQueryEditor';
import { AssetDataSource } from '../../../AssetDataSource';
import { AssetQueryType } from '../../../types/types';
import { QueryBuilderType } from 'datasources/asset/constants/constants';

class FakeAssetCalibrationDataSource extends CalibrationForecastDataSource {}

Expand Down Expand Up @@ -72,6 +73,7 @@ describe('CalibrationForecastEditor', () => {
refId: '',
groupBy: [],
type: AssetQueryType.CalibrationForecast,
queryBuilderType: QueryBuilderType.Simple,
} as CalibrationForecastQuery);

// User selects group by location
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export const CalibrationForecastQueryBuilder: React.FC<CalibrationForecastQueryB
messages={queryBuilderMessages}
onChange={onChange}
value={sanitizedFilter}
style={{width: '520px'}}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { setupRenderer } from '../../../../../test/fixtures';
import { ListAssetsQuery } from '../../../types/ListAssets.types';
import { AssetFeatureTogglesDefaults } from 'datasources/asset/types/types';
import { ListAssetsDataSource } from '../../../data-sources/list-assets/ListAssetsDataSource';
import { QueryBuilderType } from 'datasources/asset/constants/constants';

const fakeSystems: SystemMetadata[] = [
{
Expand Down Expand Up @@ -52,7 +53,7 @@ it('does not render when feature is not enabled', async () => {

it('renders the query builder', async () => {
assetDatasourceOptions.featureToggles.assetList = true;
render({} as ListAssetsQuery);
render({queryBuilderType: QueryBuilderType.Simple} as ListAssetsQuery);

await waitFor(() => expect(screen.getAllByText('Property').length).toBe(1));
await waitFor(() => expect(screen.getAllByText('Operator').length).toBe(1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ type Props = {
query: ListAssetsQuery;
handleQueryChange: (value: AssetQuery, runQuery: boolean) => void;
datasource: ListAssetsDataSource;
complexFilterEnabled: boolean;
};

export function ListAssetsEditor({ query, handleQueryChange, datasource }: Props) {
export function ListAssetsEditor({ query, handleQueryChange, datasource, complexFilterEnabled }: Props) {
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
const [systems, setSystems] = useState<SystemMetadata[]>([]);
const [areDependenciesLoaded, setAreDependenciesLoaded] = useState<boolean>(false);
Expand Down Expand Up @@ -50,6 +51,9 @@ export function ListAssetsEditor({ query, handleQueryChange, datasource }: Props
globalVariableOptions={datasource.globalVariableOptions()}
areDependenciesLoaded={areDependenciesLoaded}
onChange={(event: any) => onParameterChange(event)}
query={query}
handleQueryChange={handleQueryChange}
complexFilterEnabled={complexFilterEnabled}
></AssetQueryBuilder>

</InlineField>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@ import { AssetQueryBuilder } from './AssetQueryBuilder';
import { render } from '@testing-library/react';
import { QueryBuilderOption, Workspace } from 'core/types';
import { SystemMetadata } from 'datasources/system/types';
import { QueryBuilderType } from 'datasources/asset/constants/constants';

describe('AssetQueryBuilder', () => {
describe('useEffects', () => {
let reactNode: ReactNode;

const containerClass = 'smart-filter-group-condition-container';

function renderElement(workspaces: Workspace[], systems: SystemMetadata[], filter?: string, globalVariableOptions: QueryBuilderOption[] = []) {
function renderElement(workspaces: Workspace[], systems: SystemMetadata[], filter?: string, globalVariableOptions: QueryBuilderOption[] = [], complexFilterEnabled = true, queryBuilderType = QueryBuilderType.Simple) {
reactNode = React.createElement(AssetQueryBuilder, {
workspaces,
systems,
filter,
globalVariableOptions,
onChange: jest.fn(),
areDependenciesLoaded: true,
query: {queryBuilderType: queryBuilderType} as any,
handleQueryChange: jest.fn(),
complexFilterEnabled: complexFilterEnabled,
});
const renderResult = render(reactNode);
return {
Expand All @@ -30,6 +34,14 @@ describe('AssetQueryBuilder', () => {
const { renderResult, conditionsContainer } = renderElement([], [], '');
expect(conditionsContainer.length).toBe(1);
expect(renderResult.findByLabelText('Empty condition row')).toBeTruthy();
expect(renderResult.queryByText('Advanced')).not.toBeNull();
});

it('should not render complex option if complexFilterEnabled is disabled', () => {
const { renderResult, conditionsContainer } = renderElement([], [], '', [], false);
expect(conditionsContainer.length).toBe(1);
expect(renderResult.findByLabelText('Empty condition row')).toBeTruthy();
expect(renderResult.queryByText('Advanced')).toBeNull();
});

it('should select workspace in query builder', () => {
Expand Down Expand Up @@ -82,5 +94,14 @@ describe('AssetQueryBuilder', () => {
expect(conditionsContainer?.length).toBe(1);
expect(conditionsContainer.item(0)?.innerHTML).not.toContain('alert(\'Workspace\')');
})

it('should render the filter when advanced option is selected', () => {
const workspace = { id: '1', name: 'Selected workspace' } as Workspace;
const system = { id: '1', alias: 'Selected system' } as SystemMetadata;

const { renderResult } = renderElement([workspace], [system], 'Location = "1"', [], true, QueryBuilderType.Advanced);

expect(renderResult.queryByText('Location = "1"')).not.toBeNull();
})
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';
import { QueryBuilder, QueryBuilderCustomOperation, QueryBuilderProps } from 'smart-webcomponents-react/querybuilder';
import { useTheme2 } from '@grafana/ui';
import { RadioButtonGroup, TextArea, useTheme2 } from '@grafana/ui';

import 'smart-webcomponents-react/source/styles/smart.dark-orange.css';
import 'smart-webcomponents-react/source/styles/smart.orange.css';
Expand All @@ -14,7 +14,9 @@ import { expressionBuilderCallback, expressionReaderCallback } from 'core/query-
import { SystemMetadata } from 'datasources/system/types';
import { QBField } from '../../../../types/CalibrationForecastQuery.types';
import { ListAssetsFields, ListAssetsStaticFields } from '../../../../constants/ListAssets.constants';
import { filterXSSField, filterXSSLINQExpression } from 'core/utils';
import { enumToOptions, filterXSSField, filterXSSLINQExpression } from 'core/utils';
import { QueryBuilderType } from 'datasources/asset/constants/constants';
import { ListAssetsQuery } from 'datasources/asset/types/ListAssets.types';

type AssetCalibrationQueryBuilderProps = QueryBuilderProps &
React.HTMLAttributes<Element> & {
Expand All @@ -23,6 +25,9 @@ type AssetCalibrationQueryBuilderProps = QueryBuilderProps &
systems: SystemMetadata[];
globalVariableOptions: QueryBuilderOption[];
areDependenciesLoaded: boolean;
query: ListAssetsQuery;
handleQueryChange: ( value: ListAssetsQuery, runQuery: boolean ) => void;
complexFilterEnabled: boolean;
};

export const AssetQueryBuilder: React.FC<AssetCalibrationQueryBuilderProps> = ({
Expand All @@ -32,6 +37,9 @@ export const AssetQueryBuilder: React.FC<AssetCalibrationQueryBuilderProps> = ({
systems,
globalVariableOptions,
areDependenciesLoaded,
query,
handleQueryChange,
complexFilterEnabled
}) => {
const theme = useTheme2();
document.body.setAttribute('theme', theme.isDark ? 'dark-orange' : 'orange');
Expand All @@ -43,6 +51,16 @@ export const AssetQueryBuilder: React.FC<AssetCalibrationQueryBuilderProps> = ({
return filterXSSLINQExpression(filter);
}, [filter])

const onFilterChange = useCallback((event: ChangeEvent<HTMLTextAreaElement>) =>
{
handleQueryChange({...query, filter: event.target.value}, false);
}, [query, handleQueryChange])

const handleQueryBuilderTypeChange = useCallback((newQueryBuilderType: QueryBuilderType) =>
{
handleQueryChange({...query, queryBuilderType: newQueryBuilderType}, false);
}, [query, handleQueryChange])

const workspaceField = useMemo(() => {
const workspaceField = ListAssetsFields.WORKSPACE;

Expand Down Expand Up @@ -154,12 +172,29 @@ export const AssetQueryBuilder: React.FC<AssetCalibrationQueryBuilderProps> = ({
}, [workspaceField, locationField, calibrationDueDateField, areDependenciesLoaded, globalVariableOptions]);

return (
<QueryBuilder
customOperations={operations}
fields={fields}
messages={queryBuilderMessages}
onChange={onChange}
value={sanitizedFilter}
/>
<>
{ complexFilterEnabled &&
<div style={{alignSelf: 'flex-end', textAlign: 'right', marginBottom: '4px'}}>
<RadioButtonGroup
options={enumToOptions(QueryBuilderType)}
onChange={handleQueryBuilderTypeChange}
value={query.queryBuilderType}
/>
</div>
}
{ (!complexFilterEnabled || query.queryBuilderType === QueryBuilderType.Simple) &&
<QueryBuilder
customOperations={operations}
fields={fields}
messages={queryBuilderMessages}
onChange={onChange}
value={sanitizedFilter}
style={{width: '520px'}}
/>
}
{ complexFilterEnabled && query.queryBuilderType === QueryBuilderType.Advanced &&
<TextArea style={{width: "520px"}} value={sanitizedFilter} onChange={onFilterChange} />
}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Workspace } from 'core/types';
import { setupRenderer } from 'test/fixtures';
import { ListAssetsDataSource } from '../../data-sources/list-assets/ListAssetsDataSource';
import { AssetDataSource } from 'datasources/asset/AssetDataSource';
import { QueryBuilderType } from 'datasources/asset/constants/constants';

const fakeSystems: SystemMetadata[] = [
{
Expand Down Expand Up @@ -53,7 +54,7 @@ class FakeAssetDataSource extends AssetDataSource {
const render = setupRenderer(AssetVariableQueryEditor, FakeAssetDataSource, () => {});

it('renders the variable query builder', async () => {
render({ refId: '', type: AssetQueryType.ListAssets, filter: "" } as AssetQuery);
render({ refId: '', type: AssetQueryType.ListAssets, filter: "", queryBuilderType: QueryBuilderType.Simple, } as AssetQuery);

await waitFor(() => expect(screen.getAllByText('Property').length).toBe(1));
await waitFor(() => expect(screen.getAllByText('Operator').length).toBe(1));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { QueryEditorProps } from "@grafana/data";
import { AssetDataSourceOptions, AssetQuery } from '../../../asset/types/types';
import { AssetDataSourceOptions, AssetFeatureToggles, AssetFeatureTogglesDefaults, AssetQuery } from '../../../asset/types/types';
import { AssetDataSource } from '../../AssetDataSource'
import { FloatingError } from '../../../../core/errors';
import { AssetQueryBuilder } from '../editors/list-assets/query-builder/AssetQueryBuilder';
Expand All @@ -10,7 +10,14 @@ import { AssetVariableQuery } from '../../../asset/types/AssetVariableQuery.type

type Props = QueryEditorProps<AssetDataSource, AssetQuery, AssetDataSourceOptions>;

export function AssetVariableQueryEditor({ datasource, query, onChange }: Props) {
export function AssetVariableQueryEditor ( { datasource, query, onRunQuery, onChange }: Props )
{
const assetFeatures = useRef<AssetFeatureToggles>({
assetList: datasource.instanceSettings.jsonData?.featureToggles?.assetList ?? AssetFeatureTogglesDefaults.assetList,
calibrationForecast: datasource.instanceSettings.jsonData?.featureToggles?.calibrationForecast ?? AssetFeatureTogglesDefaults.calibrationForecast,
assetSummary: datasource.instanceSettings.jsonData?.featureToggles?.assetSummary ?? AssetFeatureTogglesDefaults.assetSummary,
advancedFilter: datasource.instanceSettings.jsonData?.featureToggles?.advancedFilter ?? AssetFeatureTogglesDefaults.advancedFilter,
});
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
const [systems, setSystems] = useState<SystemMetadata[]>([]);
const [areDependenciesLoaded, setAreDependenciesLoaded] = useState<boolean>(false);
Expand All @@ -31,15 +38,25 @@ export function AssetVariableQueryEditor({ datasource, query, onChange }: Props)
}
}

const handleQueryChange = useCallback((value: AssetQuery, runQuery = false): void => {
onChange(value);
if (runQuery) {
onRunQuery();
}
}, [onChange, onRunQuery]);

return (
<div style={{ width: "525px" }}>
<div style={{ width: "520px" }}>
<AssetQueryBuilder
filter={assetVariableQuery.filter}
workspaces={workspaces}
systems={systems}
globalVariableOptions={assetListDatasource.current.globalVariableOptions()}
areDependenciesLoaded={areDependenciesLoaded}
onChange={(event: any) => onParameterChange(event)}
query={assetVariableQuery}
handleQueryChange={ handleQueryChange }
complexFilterEnabled={assetFeatures.current.advancedFilter}
></AssetQueryBuilder>
<FloatingError message={assetListDatasource.current.error} />
</div>
Expand Down
Loading