Canvas: use grafana datasource to list resource files (#39204)

This commit is contained in:
Ryan McKinley 2021-09-15 14:48:11 -07:00 committed by GitHub
parent e31b8f4d33
commit cde133fe05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 41 deletions

View File

@ -27,7 +27,7 @@ export abstract class FunctionalVector<T = any> implements Vector<T>, Iterable<T
return vectorator(this).map(transform);
}
filter<V>(predicate: (item: T) => V) {
filter(predicate: (item: T) => boolean): T[] {
return vectorator(this).filter(predicate);
}
@ -65,7 +65,8 @@ export function vectorator<T>(vector: Vector<T>) {
return result;
},
filter<V>(predicate: (item: T) => V) {
/** Add a predicate where you return true if it should *keep* the value */
filter(predicate: (item: T) => boolean): T[] {
const result: T[] = [];
for (const val of this) {
if (predicate(val)) {

View File

@ -12,11 +12,12 @@ import {
stylesFactory,
} from '@grafana/ui';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { ResourceCards } from './ResourceCards';
import SVG from 'react-inlinesvg';
import { css } from '@emotion/css';
import { getPublicOrAbsoluteUrl } from '../resource';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { FileElement, GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource';
interface Props {
value?: string; //img/icons/unicons/0-plus.svg
@ -24,6 +25,13 @@ interface Props {
mediaType: 'icon' | 'image';
}
interface ResourceItem {
label: string;
value: string;
search: string;
imgUrl: string;
}
export function ResourcePicker(props: Props) {
const { value, onChange, mediaType } = props;
const folders = (mediaType === 'icon' ? ['img/icons/unicons', 'img/icons/iot'] : ['img/bg']).map((v) => ({
@ -36,43 +44,52 @@ export function ResourcePicker(props: Props) {
{ label: 'Select', active: true },
// { label: 'Upload', active: false },
]);
const [directoryIndex, setDirectoryIndex] = useState<SelectableValue[]>([]);
const [defaultList, setDefaultList] = useState<SelectableValue[]>([]);
const [directoryIndex, setDirectoryIndex] = useState<ResourceItem[]>([]);
const [filteredIndex, setFilteredIndex] = useState<ResourceItem[]>([]);
const theme = useTheme2();
const styles = getStyles(theme);
useEffect(() => {
// we don't want to load everything before picking a folder
if (currentFolder) {
getBackendSrv()
.get(`public/${currentFolder?.value}/index.json`)
.then((data) => {
const cards = data.files.map((v: string) => ({
value: v,
label: v,
imgUrl: `public/${currentFolder?.value}/${v}`,
}));
setDirectoryIndex(cards);
setDefaultList(cards);
})
.catch((e) => console.error(e));
} else {
return;
const folder = currentFolder?.value;
if (folder) {
const filter =
mediaType === 'icon'
? (item: FileElement) => item.name.endsWith('.svg')
: (item: FileElement) => item.name.endsWith('.png') || item.name.endsWith('.gif');
getDatasourceSrv()
.get('-- Grafana --')
.then((ds) => {
(ds as GrafanaDatasource).listFiles(folder).subscribe({
next: (frame) => {
const cards: ResourceItem[] = [];
frame.forEach((item) => {
if (filter(item)) {
const idx = item.name.lastIndexOf('.');
cards.push({
value: item.name,
label: item.name,
search: (idx ? item.name.substr(0, idx) : item.name).toLowerCase(),
imgUrl: `public/${folder}/${item.name}`,
});
}
});
setDirectoryIndex(cards);
setFilteredIndex(cards);
},
});
});
}
}, [currentFolder]);
}, [mediaType, currentFolder]);
const onChangeSearch = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.value) {
const filtered = directoryIndex.filter((card) =>
card.value
// exclude file type (.svg) in the search
.substr(0, card.value.length - 4)
.toLocaleLowerCase()
.includes(e.target.value.toLocaleLowerCase())
);
setDirectoryIndex(filtered);
let query = e.currentTarget.value;
if (query) {
query = query.toLowerCase();
setFilteredIndex(directoryIndex.filter((card) => card.search.includes(query)));
} else {
setDirectoryIndex(defaultList);
setFilteredIndex(directoryIndex);
}
};
const imgSrc = getPublicOrAbsoluteUrl(value!);
@ -106,9 +123,9 @@ export function ResourcePicker(props: Props) {
<div className={styles.tabContent}>
<Select options={folders} onChange={setCurrentFolder} value={currentFolder} />
<Input placeholder="Search" onChange={onChangeSearch} />
{directoryIndex ? (
{filteredIndex ? (
<div className={styles.cardsWrapper}>
<ResourceCards cards={directoryIndex} onChange={onChange} currentFolder={currentFolder} />
<ResourceCards cards={filteredIndex} onChange={onChange} currentFolder={currentFolder} />
</div>
) : (
<Spinner />

View File

@ -3,6 +3,7 @@ import { DataSourceWithBackend, getBackendSrv, getGrafanaLiveSrv, getTemplateSrv
import {
AnnotationQuery,
AnnotationQueryRequest,
DataFrameView,
DataQueryRequest,
DataQueryResponse,
DataSourceInstanceSettings,
@ -17,6 +18,7 @@ import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery, GrafanaQue
import AnnotationQueryEditor from './components/AnnotationQueryEditor';
import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
import { isString } from 'lodash';
import { map } from 'rxjs/operators';
let counter = 100;
@ -124,6 +126,23 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
return of(); // nothing
}
listFiles(path: string): Observable<DataFrameView<FileElement>> {
return this.query({
targets: [
{
refId: 'A',
queryType: GrafanaQueryType.List,
path,
},
],
} as any).pipe(
map((v) => {
const frame = v.data[0] ?? toDataFrame({});
return new DataFrameView<FileElement>(frame);
})
);
}
metricFindQuery(options: any) {
return Promise.resolve([]);
}
@ -183,3 +202,8 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
return Promise.resolve();
}
}
export interface FileElement {
name: string;
['media-type']: string;
}

View File

@ -1,4 +0,0 @@
{
"name": "bg",
"files": ["p0.png", "p1.png", "p2.png", "p3.png", "p4.png", "p5.png", "p6.png"]
}

View File

@ -1,4 +0,0 @@
{
"name": "line",
"files": ["faucet.svg", "pump.svg"]
}