You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

389 lines
11 KiB

import { PanelOptions, TaskTableRowConfig, QueryTableRowConfig, DatasourceType } from '../types';
import { convertTimestampToDate, getDashboardUid } from '../../../utils';
import { CLOSE_ICON_BASE_64, DOWNLOAD_ICON_BASE_64, SELECT_ICON_BASE_64, UNSELECT_ICON_BASE_64 } from '../../../icons';
import { queryApi } from '../../../services/api_service';
import { getDashboardByUid, getDatasources } from '../../../services/grafana_backend_service';
import { contextSrv } from 'grafana/app/core/core';
import { Table, Button, HorizontalGroup, Modal, LoadingPlaceholder } from '@grafana/ui';
import {
PanelProps,
toDataFrame,
FieldType,
applyFieldOverrides,
createTheme,
DataFrame,
DataLinkClickEvent,
PanelModel,
DataQuery,
DataSourceSettings,
} from '@grafana/data';
import React, { useState, useEffect } from 'react';
import * as _ from 'lodash';
interface Props extends PanelProps<PanelOptions> {}
export function Panel({ width, height, timeRange }: Props) {
// TODO: Dashboard type
const [dashboard, setDashboard] = useState<any | null>(null);
const [datasources, setDatasources] = useState<DataSourceSettings[] | null>(null);
const [tasks, setTasks] = useState<TaskTableRowConfig[] | null>(null);
const [queries, setQueries] = useState<QueryTableRowConfig[] | null>(null);
const [tasksDataFrame, setTasksDataFrame] = useState<DataFrame | null>(null);
const [queriesDataFrame, setQueriesDataFrame] = useState<DataFrame | null>(null);
const [isModalOpen, setModalVisibility] = useState<boolean>(false);
useEffect(() => {
async function getCurrentDashboard(): Promise<any> {
const currentDashboardUid = getDashboardUid(window.location.toString());
console.log(currentDashboardUid)
return getDashboardByUid(currentDashboardUid);
}
getCurrentDashboard()
.then((dash) => setDashboard(dash.dashboard))
.catch((err) => console.error(err));
}, []);
useEffect(() => {
getDatasources()
.then((datasources) => setDatasources(datasources))
.catch((err) => console.error(err));
}, []);
useEffect(() => {
if (!dashboard || !datasources) {
return;
}
dashboard.panels.forEach((panel: PanelModel) => {
const queries: QueryTableRowConfig[] = [];
// @ts-ignore
// TODO: move plugin id to const
if (panel.type === 'corpglory-dataexporter-panel') {
return;
}
if (!_.includes(_.values(DatasourceType), panel.datasource?.type)) {
return;
}
console.log(panel)
panel.targets?.forEach(
(target: DataQuery) => {
console.log('uid',target.datasource?.uid)
const datasource = getDatasourceByUid(target.datasource?.uid);
if (!datasource) {
return;
}
queries.push({ ...target, selected: false, panel, datasource });
}
);
console.log('q', queries)
setQueries(queries);
})
}, [dashboard, datasources]);
useEffect(() => {
if (tasks === null) {
return;
}
const dataFrame = getDataFrameForTaskTable(tasks);
setTasksDataFrame(dataFrame);
}, [tasks]);
useEffect(() => {
// TODO: move this function to API Service
async function getTasks(): Promise<TaskTableRowConfig[]> {
return queryApi<TaskTableRowConfig[]>('/task', {});
}
getTasks()
.then((tasks) => setTasks(tasks))
.catch((err) => console.error(err));
}, []);
useEffect(() => {
if(queries === null) {
return;
}
setQueriesDataFrame(getDataFrameForQueriesTable(queries));
}, [queries]);
function getDatasourceByUid(uid?: string): DataSourceSettings | undefined {
if (_.isNil(uid)) {
console.warn(`uid is required to get datasource`);
return undefined;
}
if (datasources === null) {
console.warn(`there is no datasources yet`);
return undefined;
}
const datasource = _.find(datasources, (datasource: DataSourceSettings) => datasource.uid === uid);
if (!datasource) {
console.warn(`can't find datasource "${uid}"`);
}
return datasource;
}
async function onAddTaskClick(): Promise<void> {
const selectedQueries = _.filter(queries, (query: QueryTableRowConfig) => query.selected);
// TODO: timerange picker
const timerange: [number, number] = [timeRange.from.unix(), timeRange.to.unix()];
// TODO: move this function to API Service
await queryApi('/task', {
method: 'POST',
data: {
from: timerange[0] * 1000,
to: timerange[1] * 1000,
// @ts-ignore
username: contextSrv.user.username,
tasks: selectedQueries,
url: window.location.toString(),
},
});
// TODO: refresh panel (or get websocket events)
onCloseModal();
unselectAllQueries();
}
function unselectAllQueries(): void {
if (queries === null) {
return;
}
setQueries(queries.map(
(query: QueryTableRowConfig) => ({ ...query, selected: false })
));
}
function openDatasourceModal(): void {
setModalVisibility(true);
}
function onCloseModal(): void {
setModalVisibility(false);
unselectAllQueries();
}
function getDataFrameForQueriesTable(configs: QueryTableRowConfig[]): DataFrame {
const dataFrame = toDataFrame({
name: 'A',
fields: [
{
name: 'Select',
type: FieldType.string,
values: _.map(
configs,
(config) => `data:image/svg+xml;base64,${config.selected ? SELECT_ICON_BASE_64 : UNSELECT_ICON_BASE_64}`
),
config: {
custom: {
filterable: false,
displayMode: 'image',
},
links: [
{
targetBlank: false,
title: 'Select',
url: '#',
onClick: (event: DataLinkClickEvent) => onDatasourceSelectClick(event, configs),
},
],
},
},
{
name: 'Panel',
type: FieldType.string,
values: _.map(configs, (config) => config.panel.title),
},
{
name: 'RefId',
type: FieldType.string,
values: _.map(configs, (config) => config.refId),
},
{
name: 'Datasource',
type: FieldType.string,
values: _.map(configs, (config) => config.datasource.name),
},
],
});
const dataFrames = applyFieldOverrides({
data: [dataFrame],
fieldConfig: {
overrides: [],
defaults: {},
},
theme: createTheme(),
replaceVariables: (value: string) => value,
});
return dataFrames[0];
}
function getDataFrameForTaskTable(configs: TaskTableRowConfig[]): DataFrame {
const dataFrame = toDataFrame({
name: 'A',
fields: [
{
name: 'Time',
type: FieldType.number,
values: _.map(configs, (config) => convertTimestampToDate(config.timestamp)),
},
{
name: 'User',
type: FieldType.string,
values: _.map(configs, (config) => config.user),
},
{
name: 'Datasource',
type: FieldType.string,
values: _.map(configs, (config) => getDatasourceByUid(config.datasourceRef?.uid)?.name),
},
{
name: 'Exported Rows',
type: FieldType.number,
values: _.map(configs, (config) => config.rowsCount),
},
{
name: 'Progress',
type: FieldType.string,
values: _.map(configs, (config) => `${config.progress}%`),
},
{
name: 'Status',
type: FieldType.string,
values: _.map(configs, (config) => config.status),
},
{
name: 'Download CSV',
type: FieldType.string,
values: _.map(configs, () => `data:image/png;base64,${DOWNLOAD_ICON_BASE_64}`),
config: {
custom: {
filterable: false,
displayMode: 'image',
},
links: [
{
targetBlank: false,
title: 'CSV link',
// TODO: config.downloadLink
url: 'https://chartwerk.io/',
},
],
},
},
{
name: 'Delete task',
type: FieldType.string,
values: _.map(configs, () => `data:image/png;base64,${CLOSE_ICON_BASE_64}`),
config: {
custom: {
filterable: false,
displayMode: 'image',
},
links: [
{
targetBlank: false,
title: 'Delete',
url: '#',
onClick: (event: DataLinkClickEvent) => onDeleteClick(event, configs),
},
],
},
},
],
});
const dataFrames = applyFieldOverrides({
data: [dataFrame],
fieldConfig: {
overrides: [],
defaults: {},
},
theme: createTheme(),
replaceVariables: (value: string) => value,
});
return dataFrames[0];
}
function onDeleteClick(e: DataLinkClickEvent, tasks: TaskTableRowConfig[]): void {
// TODO: make DELETE request to the api
const rowIndex = e.origin.rowIndex;
const filteredTasks = _.filter(tasks, (task, idx) => idx !== rowIndex);
setTasks(filteredTasks);
}
function onDatasourceSelectClick(e: DataLinkClickEvent, queries: QueryTableRowConfig[]): void {
const rowIndex = e.origin.rowIndex;
const updatedQueries = _.clone(queries);
updatedQueries[rowIndex].selected = !updatedQueries[rowIndex].selected;
setQueries(updatedQueries);
}
return (
<div>
{tasksDataFrame === null ? (
// TODO: if datasource responds with error, display the error
<LoadingPlaceholder text="Loading..."></LoadingPlaceholder>
) : (
<div>
<Table width={width} height={height - 40} data={tasksDataFrame} />
<HorizontalGroup justify="flex-end">
<Button
variant="primary"
aria-label="Rich history button"
icon="plus"
style={{ marginTop: '8px' }}
onClick={openDatasourceModal}
>
Add Task
</Button>
<Modal title="Select Queries" isOpen={isModalOpen} onDismiss={onCloseModal}>
{queriesDataFrame === null ? (
// TODO: if datasource responds with error, display the error
<LoadingPlaceholder text="Loading..."></LoadingPlaceholder>
) : (
<div>
<Table width={width / 2 - 20} height={height - 40} data={queriesDataFrame} />
<HorizontalGroup justify="flex-end">
<Button
variant="primary"
aria-label="Add task button"
style={{ marginTop: '8px' }}
onClick={onAddTaskClick}
// TODO: move to function
disabled={!queries?.filter((query: QueryTableRowConfig) => query.selected)?.length}
>
Add Task
</Button>
</HorizontalGroup>
</div>
)}
</Modal>
</HorizontalGroup>
</div>
)}
</div>
);
}