Skip to content

Commit

Permalink
feat(asset): add asset data source (#62)
Browse files Browse the repository at this point in the history
Co-authored-by: Carson Moore <carson.moore@ni.com>
  • Loading branch information
TigranVardanyan and mure authored Jul 16, 2024
1 parent 0e230a3 commit b641456
Show file tree
Hide file tree
Showing 28 changed files with 1,089 additions and 52 deletions.
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
* @mure @cameronwaterman

/src/datasources/asset @CiprianAnton @kkerezsi
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Tools](https://grafana.github.io/plugin-tools/).
- [Notebooks](src/datasources/notebook/)
- [Systems](src/datasources/system/)
- [Tags](src/datasources/tag/)
- [Assets](src/datasources/asset/)

### Panels

Expand Down
6 changes: 5 additions & 1 deletion provisioning/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ datasources:
- name: SystemLink Workspaces
type: ni-slworkspace-datasource
uid: workspace
<<: *config
<<: *config
- name: SystemLink Assets
type: ni-slasset-datasource
uid: asset
<<: *config
10 changes: 9 additions & 1 deletion src/core/DataSourceBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@grafana/data';
import { BackendSrv, BackendSrvRequest, TemplateSrv, isFetchError } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { Workspace } from './types';
import { QuerySystemsResponse, QuerySystemsRequest, Workspace } from './types';
import { sleep } from './utils';
import { lastValueFrom } from 'rxjs';

Expand All @@ -21,7 +21,9 @@ export abstract class DataSourceBase<TQuery extends DataQuery> extends DataSourc
}

abstract defaultQuery: Partial<TQuery> & Omit<TQuery, 'refId'>;

abstract runQuery(query: TQuery, options: DataQueryRequest): Promise<DataFrameDTO>;

abstract shouldRunQuery(query: TQuery): boolean;

query(request: DataQueryRequest<TQuery>): Promise<DataQueryResponse> {
Expand Down Expand Up @@ -70,4 +72,10 @@ export abstract class DataSourceBase<TQuery extends DataQuery> extends DataSourc

return (DataSourceBase.Workspaces = response.workspaces);
}

async getSystems(body: QuerySystemsRequest): Promise<QuerySystemsResponse> {
return await this.post<QuerySystemsResponse>(
this.instanceSettings.url + '/nisysmgmt/v1/query-systems', body
)
}
}
107 changes: 107 additions & 0 deletions src/core/errors.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { render, screen } from '@testing-library/react'
import { FetchError } from '@grafana/runtime';
import { act } from 'react-dom/test-utils';
import { FloatingError, parseErrorMessage } from './errors';
import { SystemLinkError } from "./types";
import React from 'react';
import { errorCodes } from "../datasources/data-frame/constants";

test('renders with error message', () => {
render(<FloatingError message='error msg'/>)

expect(screen.getByText('error msg')).toBeInTheDocument()
})

test('does not render without error message', () => {
const { container } = render(<FloatingError message=''/>)

expect(container.innerHTML).toBeFalsy()
})

test('hides after timeout', () => {
jest.useFakeTimers();

const { container } = render(<FloatingError message='error msg'/>)
act(() => jest.runAllTimers())

expect(container.innerHTML).toBeFalsy()
})

test('parses error message', () => {
const errorMock: Error = {
name: 'error',
message: 'error message'
}

const result = parseErrorMessage(errorMock)

expect(result).toBe(errorMock.message)
})

test('parses fetch error message', () => {
const fetchErrorMock: FetchError = {
status: 404,
data: { message: 'error message' },
config: { url: 'URL' }
}

const result = parseErrorMessage(fetchErrorMock as any)

expect(result).toBe(fetchErrorMock.data.message)
})

test('parses fetch error status text', () => {
const fetchErrorMock: FetchError = {
status: 404,
data: {},
statusText: 'statusText',
config: { url: 'URL' }
}

const result = parseErrorMessage(fetchErrorMock as any)

expect(result).toBe(fetchErrorMock.statusText)
})

test('parses SystemLink error code', () => {
const systemLinkError: SystemLinkError = {
error: {
name: 'name',
args: [],
code: -255130,
message: 'error message'
}
}
const fetchErrorMock: FetchError = {
status: 404,
data: systemLinkError,
statusText: 'statusText',
config: { url: 'URL' }
}

const result = parseErrorMessage(fetchErrorMock as any)

expect(result).toBe(errorCodes[fetchErrorMock.data.error.code] ?? fetchErrorMock.data.error.message)
})

test('parses SystemLink error message', () => {
const systemLinkError: SystemLinkError = {
error: {
name: 'name',
args: [],
code: 123,
message: 'error message'
}
}
const fetchErrorMock: FetchError = {
status: 404,
data: systemLinkError,
statusText: 'statusText',
config: { url: 'URL' }
}

const result = parseErrorMessage(fetchErrorMock as any)

expect(result).toBe(errorCodes[fetchErrorMock.data.error.code] ?? fetchErrorMock.data.error.message)

})
6 changes: 3 additions & 3 deletions src/datasources/data-frame/errors.tsx → src/core/errors.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { isFetchError } from '@grafana/runtime';
import { Alert } from '@grafana/ui';
import { errorCodes } from './constants';
import { errorCodes } from '../datasources/data-frame/constants';
import React, { useState, useEffect } from 'react';
import { useTimeoutFn } from 'react-use';
import { isSystemLinkError } from './types';
import { isSystemLinkError } from './utils';

export const FloatingError = ({ message = '' }) => {
const [hide, setHide] = useState(false);
Expand All @@ -19,7 +19,7 @@ export const FloatingError = ({ message = '' }) => {
return <Alert title={message} elevated style={{ position: 'absolute', top: 0, right: 0, width: '50%' }} />;
};

export const parseErrorMessage = (error: Error) => {
export const parseErrorMessage = (error: Error): string | undefined => {
if (isFetchError(error)) {
if (isSystemLinkError(error.data)) {
return errorCodes[error.data.error.code] ?? error.data.error.message;
Expand Down
30 changes: 28 additions & 2 deletions src/core/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
import { SystemMetadata } from "../datasources/system/types";

export interface Workspace {
name: string;
id: string;
id: string,
name: string,
default: boolean,
enabled: boolean,
}

export interface QuerySystemsRequest {
skip?: number,
take?: number,
filter?: string,
projection?: string,
orderBy?: string
}

export interface QuerySystemsResponse {
data: SystemMetadata[]
count: number
}

export type DeepPartial<T> = {
[Key in keyof T]?: DeepPartial<T[Key]>;
};

export interface SystemLinkError {
error: {
args: string[];
code: number;
message: string;
name: string;
}
}
6 changes: 5 additions & 1 deletion src/core/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SelectableValue } from '@grafana/data';
import { useAsync } from 'react-use';
import { DataSourceBase } from './DataSourceBase';
import { Workspace } from './types';
import { SystemLinkError, Workspace } from './types';
import { TemplateSrv } from '@grafana/runtime';

export function enumToOptions<T>(stringEnum: { [name: string]: T }): Array<SelectableValue<T>> {
Expand Down Expand Up @@ -82,3 +82,7 @@ export function replaceVariables(values: string[], templateSrv: TemplateSrv) {
// Dedupe and flatten
return [...new Set(replaced.flat())];
}

export function isSystemLinkError(error: any): error is SystemLinkError {
return Boolean(error?.error?.code) && Boolean(error?.error?.name);
}
Loading

0 comments on commit b641456

Please sign in to comment.