Skip to content

Commit

Permalink
feat(system): Filter system metadata by workspace and variable chaini…
Browse files Browse the repository at this point in the history
…ng (#43)

Co-authored-by: Melissa Hilliard <63749096+MelissaHilliard@users.noreply.github.com>
  • Loading branch information
cameronwaterman and MelissaHilliard authored Oct 9, 2023
1 parent 5c9ff7c commit 14de6ed
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 36 deletions.
3 changes: 2 additions & 1 deletion src/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export function useWorkspaceOptions<DSType extends DataSourceBase<any>>(datasour
export function getVariableOptions<DSType extends DataSourceBase<any>>(datasource: DSType) {
return datasource.templateSrv
.getVariables()
.map((variable) => ({ label: '$' + variable.name, value: '$' + variable.name }));
.filter((variable: any) => !variable.datasource || variable.datasource.uid !== datasource.uid)
.map(variable => ({ label: '$' + variable.name, value: '$' + variable.name }));
}

export function getWorkspaceName(workspaces: Workspace[], id: string) {
Expand Down
24 changes: 23 additions & 1 deletion src/datasources/system/SystemDataSource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ beforeEach(() => {
[ds, backendSrv, templateSrv] = setupDataSource(SystemDataSource);
});

const buildQuery = getQueryBuilder<SystemQuery>()({ systemName: '' });
const buildQuery = getQueryBuilder<SystemQuery>()({ systemName: '', workspace: '' });

test('query for summary counts', async () => {
backendSrv.fetch
Expand Down Expand Up @@ -114,6 +114,28 @@ test('queries for system variable values - single workspace', async () => {
expect(backendSrv.fetch.mock.lastCall?.[0].data).toHaveProperty('filter', 'workspace = "1"');
});

test('attempts to replace variables in metadata query', async () => {
const workspaceVariable = '$workspace';
backendSrv.fetch.mockReturnValue(createFetchResponse({ data: fakeSystems }));
templateSrv.replace.calledWith(workspaceVariable).mockReturnValue('1');

await ds.query(buildQuery({ queryKind: SystemQueryType.Metadata, systemName: 'system', workspace: workspaceVariable }));

expect(templateSrv.replace).toHaveBeenCalledTimes(2);
expect(templateSrv.replace.mock.calls[1][0]).toBe(workspaceVariable);
});

test('attempts to replace variables in metricFindQuery', async () => {
const workspaceVariable = '$workspace';
backendSrv.fetch.mockReturnValue(createFetchResponse({ data: fakeSystems.map(({ id, alias }) => ({ id, alias })) }));
templateSrv.replace.calledWith(workspaceVariable).mockReturnValue('1');

await ds.metricFindQuery({ workspace: workspaceVariable });

expect(templateSrv.replace).toHaveBeenCalledTimes(1);
expect(templateSrv.replace.mock.calls[0][0]).toBe(workspaceVariable);
});

const fakeSystems: SystemMetadata[] = [
{
id: 'system-1',
Expand Down
11 changes: 8 additions & 3 deletions src/datasources/system/SystemDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class SystemDataSource extends DataSourceBase<SystemQuery> {
defaultQuery = {
queryKind: SystemQueryType.Summary,
systemName: '',
workspace: ''
};

async runQuery(query: SystemQuery, options: DataQueryRequest): Promise<DataFrameDTO> {
Expand All @@ -33,7 +34,11 @@ export class SystemDataSource extends DataSourceBase<SystemQuery> {
],
};
} else {
const metadata = await this.getSystemMetadata(this.templateSrv.replace(query.systemName, options.scopedVars));
const metadata = await this.getSystemMetadata(
this.templateSrv.replace(query.systemName, options.scopedVars),
defaultProjection,
this.templateSrv.replace(query.workspace, options.scopedVars)
);
const workspaces = await this.getWorkspaces();
return {
refId: query.refId,
Expand All @@ -59,7 +64,7 @@ export class SystemDataSource extends DataSourceBase<SystemQuery> {
async getSystemMetadata(systemFilter: string, projection = defaultProjection, workspace?: string) {
const filters = [
systemFilter && `id = "${systemFilter}" || alias = "${systemFilter}"`,
workspace && `workspace = "${workspace}"`,
workspace && !systemFilter && `workspace = "${workspace}"`,
];
const response = await this.post<{ data: SystemMetadata[] }>(this.baseUrl + '/query-systems', {
filter: filters.filter(Boolean).join(' '),
Expand All @@ -70,7 +75,7 @@ export class SystemDataSource extends DataSourceBase<SystemQuery> {
}

async metricFindQuery({ workspace }: SystemVariableQuery): Promise<MetricFindValue[]> {
const metadata = await this.getSystemMetadata('', ['id', 'alias'], workspace);
const metadata = await this.getSystemMetadata('', ['id', 'alias'], this.templateSrv.replace(workspace));
return metadata.map(frame => ({ text: frame.alias, value: frame.id }));
}

Expand Down
4 changes: 2 additions & 2 deletions src/datasources/system/components/SystemQueryEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ it('renders with query defaults', () => {
});

it('renders with saved metadata query', async () => {
render({ queryKind: SystemQueryType.Metadata, systemName: 'my-system'});
render({ queryKind: SystemQueryType.Metadata, systemName: 'my-system', workspace: '' });

expect(screen.getByRole('radio', { name: 'Metadata' })).toBeChecked();
expect(screen.queryByLabelText('System')).toHaveValue('my-system');
});

it('updates when user interacts with fields', async () => {
const [onChange] = render({ queryKind: SystemQueryType.Summary, systemName: '' });
const [onChange] = render({ queryKind: SystemQueryType.Summary, systemName: '', workspace: '' });

// User changes query type
await userEvent.click(screen.getByRole('radio', { name: 'Metadata' }));
Expand Down
47 changes: 35 additions & 12 deletions src/datasources/system/components/SystemQueryEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React, { FormEvent } from 'react';
import { AutoSizeInput, RadioButtonGroup } from '@grafana/ui';
import { QueryEditorProps } from '@grafana/data';
import { AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui';
import { QueryEditorProps, SelectableValue } from '@grafana/data';
import { SystemDataSource } from '../SystemDataSource';
import { SystemQueryType, SystemQuery } from '../types';
import { enumToOptions } from 'core/utils';
import { enumToOptions, useWorkspaceOptions } from 'core/utils';
import { InlineField } from 'core/components/InlineField';

type Props = QueryEditorProps<SystemDataSource, SystemQuery>;

export function SystemQueryEditor({ query, onChange, onRunQuery, datasource }: Props) {
query = datasource.prepareQuery(query);

const workspaces = useWorkspaceOptions(datasource);

const onQueryTypeChange = (value: SystemQueryType) => {
onChange({ ...query, queryKind: value });
onRunQuery();
Expand All @@ -20,6 +23,11 @@ export function SystemQueryEditor({ query, onChange, onRunQuery, datasource }: P
onRunQuery();
};

const onWorkspaceChange = (option?: SelectableValue<string>) => {
onChange({ ...query, workspace: option?.value ?? '' });
onRunQuery();
};

return (
<>
<InlineField label="Query type" labelWidth={14} tooltip={tooltips.queryType}>
Expand All @@ -30,15 +38,29 @@ export function SystemQueryEditor({ query, onChange, onRunQuery, datasource }: P
/>
</InlineField>
{query.queryKind === SystemQueryType.Metadata && (
<InlineField label="System" labelWidth={14} tooltip={tooltips.system}>
<AutoSizeInput
defaultValue={query.systemName}
maxWidth={80}
minWidth={20}
onCommitChange={onSystemChange}
placeholder="All systems"
/>
</InlineField>
<>
<InlineField label="System" labelWidth={14} tooltip={tooltips.system}>
<AutoSizeInput
defaultValue={query.systemName}
maxWidth={80}
minWidth={20}
onCommitChange={onSystemChange}
placeholder="All systems"
/>
</InlineField>
{query.systemName === '' && (
<InlineField label="Workspace" labelWidth={14} tooltip={tooltips.workspace}>
<Select
isClearable
isLoading={workspaces.loading}
onChange={onWorkspaceChange}
options={workspaces.value}
placeholder="Any workspace"
value={query.workspace}
/>
</InlineField>
)}
</>
)}
</>
);
Expand All @@ -49,4 +71,5 @@ const tooltips = {
Summary allows you to visualize the number of disconnected and connected systems.`,
system: `Query for a specific system by its name or ID. If left blank, the plugin returns all
available systems. You can enter a variable into this field.`,
workspace: `The workspace to search for the system specified.`,
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ test('user selects new workspace', async () => {
await select(screen.getByRole('combobox'), 'Other workspace', { container: document.body });
expect(onChange).toHaveBeenCalledWith({ workspace: '2' });
});

test('populates workspace drop-down with variables', async () => {
render(<SystemVariableQueryEditor {...{ onChange, datasource, query: { workspace: '$test_var' } }} />);

await workspacesLoaded();

expect(screen.getByText('$test_var')).toBeInTheDocument();
});
31 changes: 16 additions & 15 deletions src/datasources/system/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,30 @@ export enum SystemQueryType {
}

export interface SystemQuery extends DataQuery {
queryKind: SystemQueryType,
systemName: string
queryKind: SystemQueryType;
systemName: string;
workspace: string;
}

export interface SystemVariableQuery {
workspace: string;
}

export interface SystemSummary {
connectedCount: number,
disconnectedCount: number
connectedCount: number;
disconnectedCount: number;
}

export interface SystemMetadata {
id: string,
alias: string,
state: string,
locked: boolean,
systemStartTime: string,
model: string,
vendor: string,
osFullName: string,
ip4Interfaces: Record<string, string[]>,
ip6Interfaces: Record<string, string[]>,
workspace: string
id: string;
alias: string;
state: string;
locked: boolean;
systemStartTime: string;
model: string;
vendor: string;
osFullName: string;
ip4Interfaces: Record<string, string[]>;
ip6Interfaces: Record<string, string[]>;
workspace: string;
}
22 changes: 20 additions & 2 deletions src/test/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DataQueryRequest, DataSourceApi, DataSourceInstanceSettings, QueryEditorProps, dateTime } from '@grafana/data';
import { DataQueryRequest, DataSourceApi, DataSourceInstanceSettings, LoadingState, QueryEditorProps, TypedVariableModel, dateTime } from '@grafana/data';
import { BackendSrv, BackendSrvRequest, FetchResponse, TemplateSrv } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { render } from '@testing-library/react';
Expand All @@ -7,6 +7,24 @@ import _ from 'lodash';
import React from 'react';
import { Observable, of, throwError } from 'rxjs';

const mockVariables: TypedVariableModel[] = [{
type: 'textbox',
name: 'test_var',
current: { text: '123', value: '123', selected: false },
originalQuery: '',
options: [],
query: '',
id: 'test_var',
rootStateKey: '',
global: false,
hide: 0,
skipUrlSync: false,
index: 0,
state: LoadingState.Done,
error: null,
description: null
}]

export function setupDataSource<T>(
ctor: new (instanceSettings: DataSourceInstanceSettings, backendSrv: BackendSrv, templateSrv: TemplateSrv) => T
) {
Expand All @@ -20,7 +38,7 @@ export function setupDataSource<T>(
);
const mockTemplateSrv = mock<TemplateSrv>({
replace: calledWithFn({ fallbackMockImplementation: target => target ?? '' }),
getVariables: calledWithFn({ fallbackMockImplementation: () => [] })
getVariables: calledWithFn({ fallbackMockImplementation: () => mockVariables })
});
const ds = new ctor({ url: '' } as DataSourceInstanceSettings, mockBackendSrv, mockTemplateSrv);
return [ds, mockBackendSrv, mockTemplateSrv] as const;
Expand Down

0 comments on commit 14de6ed

Please sign in to comment.