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
5 changed files with 75 additions and 41 deletions
@@ -27,7 +27,7 @@ export abstract class FunctionalVector<T = any> implements Vector<T>, Iterable<T
return vectorator(this).map(transform); return vectorator(this).map(transform);
} }
filter<V>(predicate: (item: T) => V) { filter(predicate: (item: T) => boolean): T[] {
return vectorator(this).filter(predicate); return vectorator(this).filter(predicate);
} }
@@ -65,7 +65,8 @@ export function vectorator<T>(vector: Vector<T>) {
return result; 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[] = []; const result: T[] = [];
for (const val of this) { for (const val of this) {
if (predicate(val)) { if (predicate(val)) {
@@ -12,11 +12,12 @@ import {
stylesFactory, stylesFactory,
} from '@grafana/ui'; } from '@grafana/ui';
import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { ResourceCards } from './ResourceCards'; import { ResourceCards } from './ResourceCards';
import SVG from 'react-inlinesvg'; import SVG from 'react-inlinesvg';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { getPublicOrAbsoluteUrl } from '../resource'; import { getPublicOrAbsoluteUrl } from '../resource';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { FileElement, GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource';
interface Props { interface Props {
value?: string; //img/icons/unicons/0-plus.svg value?: string; //img/icons/unicons/0-plus.svg
@@ -24,6 +25,13 @@ interface Props {
mediaType: 'icon' | 'image'; mediaType: 'icon' | 'image';
} }
interface ResourceItem {
label: string;
value: string;
search: string;
imgUrl: string;
}
export function ResourcePicker(props: Props) { export function ResourcePicker(props: Props) {
const { value, onChange, mediaType } = props; const { value, onChange, mediaType } = props;
const folders = (mediaType === 'icon' ? ['img/icons/unicons', 'img/icons/iot'] : ['img/bg']).map((v) => ({ 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: 'Select', active: true },
// { label: 'Upload', active: false }, // { label: 'Upload', active: false },
]); ]);
const [directoryIndex, setDirectoryIndex] = useState<SelectableValue[]>([]); const [directoryIndex, setDirectoryIndex] = useState<ResourceItem[]>([]);
const [defaultList, setDefaultList] = useState<SelectableValue[]>([]); const [filteredIndex, setFilteredIndex] = useState<ResourceItem[]>([]);
const theme = useTheme2(); const theme = useTheme2();
const styles = getStyles(theme); const styles = getStyles(theme);
useEffect(() => { useEffect(() => {
// we don't want to load everything before picking a folder // we don't want to load everything before picking a folder
if (currentFolder) { const folder = currentFolder?.value;
getBackendSrv() if (folder) {
.get(`public/${currentFolder?.value}/index.json`) const filter =
.then((data) => { mediaType === 'icon'
const cards = data.files.map((v: string) => ({ ? (item: FileElement) => item.name.endsWith('.svg')
value: v, : (item: FileElement) => item.name.endsWith('.png') || item.name.endsWith('.gif');
label: v,
imgUrl: `public/${currentFolder?.value}/${v}`, getDatasourceSrv()
})); .get('-- Grafana --')
setDirectoryIndex(cards); .then((ds) => {
setDefaultList(cards); (ds as GrafanaDatasource).listFiles(folder).subscribe({
}) next: (frame) => {
.catch((e) => console.error(e)); const cards: ResourceItem[] = [];
} else { frame.forEach((item) => {
return; 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>) => { const onChangeSearch = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.value) { let query = e.currentTarget.value;
const filtered = directoryIndex.filter((card) => if (query) {
card.value query = query.toLowerCase();
// exclude file type (.svg) in the search setFilteredIndex(directoryIndex.filter((card) => card.search.includes(query)));
.substr(0, card.value.length - 4)
.toLocaleLowerCase()
.includes(e.target.value.toLocaleLowerCase())
);
setDirectoryIndex(filtered);
} else { } else {
setDirectoryIndex(defaultList); setFilteredIndex(directoryIndex);
} }
}; };
const imgSrc = getPublicOrAbsoluteUrl(value!); const imgSrc = getPublicOrAbsoluteUrl(value!);
@@ -106,9 +123,9 @@ export function ResourcePicker(props: Props) {
<div className={styles.tabContent}> <div className={styles.tabContent}>
<Select options={folders} onChange={setCurrentFolder} value={currentFolder} /> <Select options={folders} onChange={setCurrentFolder} value={currentFolder} />
<Input placeholder="Search" onChange={onChangeSearch} /> <Input placeholder="Search" onChange={onChangeSearch} />
{directoryIndex ? ( {filteredIndex ? (
<div className={styles.cardsWrapper}> <div className={styles.cardsWrapper}>
<ResourceCards cards={directoryIndex} onChange={onChange} currentFolder={currentFolder} /> <ResourceCards cards={filteredIndex} onChange={onChange} currentFolder={currentFolder} />
</div> </div>
) : ( ) : (
<Spinner /> <Spinner />
@@ -3,6 +3,7 @@ import { DataSourceWithBackend, getBackendSrv, getGrafanaLiveSrv, getTemplateSrv
import { import {
AnnotationQuery, AnnotationQuery,
AnnotationQueryRequest, AnnotationQueryRequest,
DataFrameView,
DataQueryRequest, DataQueryRequest,
DataQueryResponse, DataQueryResponse,
DataSourceInstanceSettings, DataSourceInstanceSettings,
@@ -17,6 +18,7 @@ import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery, GrafanaQue
import AnnotationQueryEditor from './components/AnnotationQueryEditor'; import AnnotationQueryEditor from './components/AnnotationQueryEditor';
import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv'; import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
import { isString } from 'lodash'; import { isString } from 'lodash';
import { map } from 'rxjs/operators';
let counter = 100; let counter = 100;
@@ -124,6 +126,23 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
return of(); // nothing 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) { metricFindQuery(options: any) {
return Promise.resolve([]); return Promise.resolve([]);
} }
@@ -183,3 +202,8 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
return Promise.resolve(); return Promise.resolve();
} }
} }
export interface FileElement {
name: string;
['media-type']: string;
}
-4
View File
@@ -1,4 +0,0 @@
{
"name": "bg",
"files": ["p0.png", "p1.png", "p2.png", "p3.png", "p4.png", "p5.png", "p6.png"]
}
-4
View File
@@ -1,4 +0,0 @@
{
"name": "line",
"files": ["faucet.svg", "pump.svg"]
}