|
|
@ -1,7 +1,12 @@ |
|
|
|
import { PanelOptions, TaskTableRowConfig, DatasourceTableRowConfig } from '../types'; |
|
|
|
import { PanelOptions, TaskTableRowConfig, QueryTableRowConfig, DatasourceType } from '../types'; |
|
|
|
|
|
|
|
|
|
|
|
import { getBackendSrv } from '@grafana/runtime'; |
|
|
|
import { convertTimestampToDate, getDashboardUid } from '../../../utils'; |
|
|
|
import { makeRequest } from 'services/network_service'; |
|
|
|
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 { Table, Button, HorizontalGroup, Modal, LoadingPlaceholder } from '@grafana/ui'; |
|
|
|
import { |
|
|
|
import { |
|
|
@ -12,6 +17,9 @@ import { |
|
|
|
createTheme, |
|
|
|
createTheme, |
|
|
|
DataFrame, |
|
|
|
DataFrame, |
|
|
|
DataLinkClickEvent, |
|
|
|
DataLinkClickEvent, |
|
|
|
|
|
|
|
PanelModel, |
|
|
|
|
|
|
|
DataQuery, |
|
|
|
|
|
|
|
DataSourceSettings, |
|
|
|
} from '@grafana/data'; |
|
|
|
} from '@grafana/data'; |
|
|
|
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from 'react'; |
|
|
|
import React, { useState, useEffect } from 'react'; |
|
|
@ -19,22 +27,87 @@ import * as _ from 'lodash'; |
|
|
|
|
|
|
|
|
|
|
|
interface Props extends PanelProps<PanelOptions> {} |
|
|
|
interface Props extends PanelProps<PanelOptions> {} |
|
|
|
|
|
|
|
|
|
|
|
export function Panel({ options, data, width, height, timeRange, onChangeTimeRange }: Props) { |
|
|
|
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 [tasks, setTasks] = useState<TaskTableRowConfig[] | null>(null); |
|
|
|
|
|
|
|
const [queries, setQueries] = useState<QueryTableRowConfig[] | null>(null); |
|
|
|
|
|
|
|
|
|
|
|
const [tasksDataFrame, setTasksDataFrame] = useState<DataFrame | 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(() => { |
|
|
|
useEffect(() => { |
|
|
|
if (tasks === null) { |
|
|
|
if (tasks === null) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
const dataFrame = getDataFrameForTaskTable(tasks, setTasks); |
|
|
|
const dataFrame = getDataFrameForTaskTable(tasks); |
|
|
|
setTasksDataFrame(dataFrame); |
|
|
|
setTasksDataFrame(dataFrame); |
|
|
|
}, [tasks]); |
|
|
|
}, [tasks]); |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
// TODO: move this function to Network Service
|
|
|
|
// TODO: move this function to API Service
|
|
|
|
async function getTasks(): Promise<TaskTableRowConfig[]> { |
|
|
|
async function getTasks(): Promise<TaskTableRowConfig[]> { |
|
|
|
return makeRequest<TaskTableRowConfig[]>('/task', {}); |
|
|
|
return queryApi<TaskTableRowConfig[]>('/task', {}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
getTasks() |
|
|
|
getTasks() |
|
|
@ -42,61 +115,64 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan |
|
|
|
.catch((err) => console.error(err)); |
|
|
|
.catch((err) => console.error(err)); |
|
|
|
}, []); |
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const datasourceConfigs: DatasourceTableRowConfig[] = []; |
|
|
|
useEffect(() => { |
|
|
|
const [datasources, setDatasources] = useState(datasourceConfigs); |
|
|
|
if(queries === null) { |
|
|
|
const datasourceDataFrame = getDataFrameForDatasourceTable(datasources, setDatasources); |
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
setQueriesDataFrame(getDataFrameForQueriesTable(queries)); |
|
|
|
|
|
|
|
}, [queries]); |
|
|
|
|
|
|
|
|
|
|
|
const [isModalOpen, setModalVisibility] = useState(false); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const timestampRange: [number, number] = [timeRange.from.unix(), timeRange.to.unix()]; |
|
|
|
function getDatasourceByUid(uid?: string): DataSourceSettings | undefined { |
|
|
|
const backendSrv = getBackendSrv(); |
|
|
|
if (_.isNil(uid)) { |
|
|
|
// @ts-ignore
|
|
|
|
console.warn(`uid is required to get datasource`); |
|
|
|
backendSrv.getInspectorStream().subscribe({ |
|
|
|
return undefined; |
|
|
|
next: (resp: any) => { |
|
|
|
} |
|
|
|
const queries = resp?.config?.data?.queries; |
|
|
|
if (datasources === null) { |
|
|
|
if (_.isEmpty(queries)) { |
|
|
|
console.warn(`there is no datasources yet`); |
|
|
|
return; |
|
|
|
return undefined; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const datasource = _.find(datasources, (datasource: DataSourceSettings) => datasource.uid === uid); |
|
|
|
|
|
|
|
|
|
|
|
const datasource = queries[0].datasource.type; |
|
|
|
if (!datasource) { |
|
|
|
const refId = queries[0].refId; |
|
|
|
console.warn(`can't find datasource "${uid}"`); |
|
|
|
const rawSql = queries[0].rawSql; |
|
|
|
|
|
|
|
const uid = queries[0].datasource.uid; |
|
|
|
|
|
|
|
// TODO: it works only with sql (rawSql will be empty in prometheus)
|
|
|
|
|
|
|
|
const isDatasourceExist = _.some( |
|
|
|
|
|
|
|
datasources, |
|
|
|
|
|
|
|
(ds: DatasourceTableRowConfig) => |
|
|
|
|
|
|
|
ds.datasource === datasource && ds.refId === refId && _.isEqual(ds.rawSql, rawSql) && ds.uid === uid |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
if (isDatasourceExist) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const newConfig = createDatasourceConfig({ datasource, refId, rawSql, uid }, timestampRange); |
|
|
|
|
|
|
|
setDatasources([...datasources, newConfig]); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onAddTaskClick(): void { |
|
|
|
|
|
|
|
const selectedDatasources = _.filter(datasources, (datasource: DatasourceTableRowConfig) => datasource.select); |
|
|
|
|
|
|
|
const newTasks = _.map(selectedDatasources, (datasource: DatasourceTableRowConfig) => |
|
|
|
|
|
|
|
createTaskFromDatasource(datasource) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
if (_.isEmpty(tasks)) { |
|
|
|
|
|
|
|
setTasks([...newTasks]); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
setTasks([...(tasks as TaskTableRowConfig[]), ...newTasks]); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(); |
|
|
|
onCloseModal(); |
|
|
|
unselectAllDatasources(); |
|
|
|
unselectAllQueries(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function unselectAllDatasources(): void { |
|
|
|
function unselectAllQueries(): void { |
|
|
|
const updatedDatasources = _.clone(datasources); |
|
|
|
if (queries === null) { |
|
|
|
for (const ds of updatedDatasources) { |
|
|
|
return; |
|
|
|
ds.select = false; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
setDatasources(updatedDatasources); |
|
|
|
setQueries(queries.map( |
|
|
|
|
|
|
|
(query: QueryTableRowConfig) => ({ ...query, selected: false }) |
|
|
|
|
|
|
|
)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function openDatasourceModal(): void { |
|
|
|
function openDatasourceModal(): void { |
|
|
@ -105,7 +181,164 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan |
|
|
|
|
|
|
|
|
|
|
|
function onCloseModal(): void { |
|
|
|
function onCloseModal(): void { |
|
|
|
setModalVisibility(false); |
|
|
|
setModalVisibility(false); |
|
|
|
unselectAllDatasources(); |
|
|
|
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 ( |
|
|
|
return ( |
|
|
@ -126,18 +359,27 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan |
|
|
|
> |
|
|
|
> |
|
|
|
Add Task |
|
|
|
Add Task |
|
|
|
</Button> |
|
|
|
</Button> |
|
|
|
<Modal title="Select Datasources" isOpen={isModalOpen} onDismiss={onCloseModal}> |
|
|
|
<Modal title="Select Queries" isOpen={isModalOpen} onDismiss={onCloseModal}> |
|
|
|
<Table width={width / 2 - 20} height={height - 40} data={datasourceDataFrame} /> |
|
|
|
{queriesDataFrame === null ? ( |
|
|
|
<HorizontalGroup justify="flex-end"> |
|
|
|
// TODO: if datasource responds with error, display the error
|
|
|
|
<Button |
|
|
|
<LoadingPlaceholder text="Loading..."></LoadingPlaceholder> |
|
|
|
variant="primary" |
|
|
|
) : ( |
|
|
|
aria-label="Add task button" |
|
|
|
<div> |
|
|
|
style={{ marginTop: '8px' }} |
|
|
|
<Table width={width / 2 - 20} height={height - 40} data={queriesDataFrame} /> |
|
|
|
onClick={onAddTaskClick} |
|
|
|
<HorizontalGroup justify="flex-end"> |
|
|
|
> |
|
|
|
<Button |
|
|
|
Add Task |
|
|
|
variant="primary" |
|
|
|
</Button> |
|
|
|
aria-label="Add task button" |
|
|
|
</HorizontalGroup> |
|
|
|
style={{ marginTop: '8px' }} |
|
|
|
|
|
|
|
onClick={onAddTaskClick} |
|
|
|
|
|
|
|
// TODO: move to function
|
|
|
|
|
|
|
|
disabled={!queries?.filter((query: QueryTableRowConfig) => query.selected)?.length} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
Add Task |
|
|
|
|
|
|
|
</Button> |
|
|
|
|
|
|
|
</HorizontalGroup> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
)} |
|
|
|
</Modal> |
|
|
|
</Modal> |
|
|
|
</HorizontalGroup> |
|
|
|
</HorizontalGroup> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -145,206 +387,3 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function getDataFrameForTaskTable(configs: TaskTableRowConfig[], setTasks: any): 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) => config.datasource), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
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, (config) => `data:image/png;base64,${DOWNLOAD_ICON_BASE_64}`), |
|
|
|
|
|
|
|
config: { |
|
|
|
|
|
|
|
custom: { |
|
|
|
|
|
|
|
filterable: false, |
|
|
|
|
|
|
|
displayMode: 'image', |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
links: [ |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
targetBlank: false, |
|
|
|
|
|
|
|
title: 'CSV link', |
|
|
|
|
|
|
|
url: 'https://chartwerk.io/', |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
name: 'Delete task', |
|
|
|
|
|
|
|
type: FieldType.string, |
|
|
|
|
|
|
|
values: _.map(configs, (config) => `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, setTasks), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dataFrames = applyFieldOverrides({ |
|
|
|
|
|
|
|
data: [dataFrame], |
|
|
|
|
|
|
|
fieldConfig: { |
|
|
|
|
|
|
|
overrides: [], |
|
|
|
|
|
|
|
defaults: {}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
theme: createTheme(), |
|
|
|
|
|
|
|
replaceVariables: (value: string) => value, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
return dataFrames[0]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getDataFrameForDatasourceTable(configs: DatasourceTableRowConfig[], setDatasources: any): DataFrame { |
|
|
|
|
|
|
|
const dataFrame = toDataFrame({ |
|
|
|
|
|
|
|
name: 'A', |
|
|
|
|
|
|
|
fields: [ |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
name: 'Select', |
|
|
|
|
|
|
|
type: FieldType.string, |
|
|
|
|
|
|
|
values: _.map( |
|
|
|
|
|
|
|
configs, |
|
|
|
|
|
|
|
(config) => `data:image/svg+xml;base64,${config.select ? 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, setDatasources), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
name: 'Panel', |
|
|
|
|
|
|
|
type: FieldType.string, |
|
|
|
|
|
|
|
values: _.map(configs, (config) => config.panel), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
name: 'RefId', |
|
|
|
|
|
|
|
type: FieldType.string, |
|
|
|
|
|
|
|
values: _.map(configs, (config) => config.refId), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
name: 'Datasource', |
|
|
|
|
|
|
|
type: FieldType.string, |
|
|
|
|
|
|
|
values: _.map(configs, (config) => config.datasource), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dataFrames = applyFieldOverrides({ |
|
|
|
|
|
|
|
data: [dataFrame], |
|
|
|
|
|
|
|
fieldConfig: { |
|
|
|
|
|
|
|
overrides: [], |
|
|
|
|
|
|
|
defaults: {}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
theme: createTheme(), |
|
|
|
|
|
|
|
replaceVariables: (value: string) => value, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
return dataFrames[0]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onDeleteClick(e: DataLinkClickEvent, tasks: TaskTableRowConfig[], setTasks: any): void { |
|
|
|
|
|
|
|
const rowIndex = e.origin.rowIndex; |
|
|
|
|
|
|
|
const filteredTasks = _.filter(tasks, (task, idx) => idx !== rowIndex); |
|
|
|
|
|
|
|
setTasks(filteredTasks); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onDatasourceSelectClick( |
|
|
|
|
|
|
|
e: DataLinkClickEvent, |
|
|
|
|
|
|
|
datasources: DatasourceTableRowConfig[], |
|
|
|
|
|
|
|
setDatasources: any |
|
|
|
|
|
|
|
): void { |
|
|
|
|
|
|
|
const rowIndex = e.origin.rowIndex; |
|
|
|
|
|
|
|
const updatedDatasources = _.clone(datasources); |
|
|
|
|
|
|
|
updatedDatasources[rowIndex].select = !updatedDatasources[rowIndex].select; |
|
|
|
|
|
|
|
setDatasources(updatedDatasources); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createTaskFromDatasource(config: DatasourceTableRowConfig): TaskTableRowConfig { |
|
|
|
|
|
|
|
return { |
|
|
|
|
|
|
|
timestamp: Date.now(), |
|
|
|
|
|
|
|
user: 'admin', |
|
|
|
|
|
|
|
datasource: config.datasource, |
|
|
|
|
|
|
|
rowsCount: 3214, |
|
|
|
|
|
|
|
progress: 100, |
|
|
|
|
|
|
|
status: 'finished', |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createDatasourceConfig(obj: any, timerange: [number, number]): DatasourceTableRowConfig { |
|
|
|
|
|
|
|
return { |
|
|
|
|
|
|
|
select: false, |
|
|
|
|
|
|
|
panel: 'Panel', |
|
|
|
|
|
|
|
refId: obj.refId, |
|
|
|
|
|
|
|
datasource: obj.datasource, |
|
|
|
|
|
|
|
rawSql: obj.rawSql, |
|
|
|
|
|
|
|
uid: obj.uid, |
|
|
|
|
|
|
|
timerange, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function convertTimestampToDate(timestamp: number): string { |
|
|
|
|
|
|
|
const options: Intl.DateTimeFormatOptions = { |
|
|
|
|
|
|
|
year: 'numeric', |
|
|
|
|
|
|
|
month: 'short', |
|
|
|
|
|
|
|
day: 'numeric', |
|
|
|
|
|
|
|
hour: 'numeric', |
|
|
|
|
|
|
|
minute: 'numeric', |
|
|
|
|
|
|
|
second: 'numeric', |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
return new Date(timestamp).toLocaleString('en-GB', options); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const CLOSE_ICON_BASE_64 = |
|
|
|
|
|
|
|
'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACJSURBVHgB7ZPRCcAgDESv0lWc0zhC53OIjmAVUpCSamL76YEoyfFIiAGWfldK6SwnKHyhep9xJ3iPcqgH5RyxV1VlBWYJypXVHMEiCaqBbRhAy3W3B76j954wq6ZSVZsOY+WXt6i9l2ymGTlUq0VpOcIqaQC96Zth01DN1zBBefVI4SNp9Za+6wLcH6DKFrfpxgAAAABJRU5ErkJggg=='; |
|
|
|
|
|
|
|
const DOWNLOAD_ICON_BASE_64 = |
|
|
|
|
|
|
|
'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADlSURBVHgB3ZPNDYJAEIVnN96lBA7A2RIsATuQiiwBSrAD7MAjCZBACXqFwPomMRtEwMVwgZeQhfn5mNnMEG1CaZoeiqKwTWJ3JkFCiEtVVU+8+r9iJRlKSrk3iqOFtWJglmXHf3yDwCRJbBxxnudh34cRYluMMbKGcgWNCIlnjEuIJ1JK2WzDWeKb7YHjONEsYBf6kTABY+mWuYX+3Xiex9UFU7D3FllfwLqueQvi/h8ZCtBprDLY703T6A0yWj2ArmSoxedQV4jSSz5xj4pmCi0/NKfrwNz5bdtaNENciOu6N1qNXhzZXHMb9Q+nAAAAAElFTkSuQmCC'; |
|
|
|
|
|
|
|
const UNSELECT_ICON_BASE_64 = |
|
|
|
|
|
|
|
'PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3QgeD0iMC41IiB5PSIwLjUiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgcng9IjEuNSIgZmlsbD0iIzExMTIxNiIgc3Ryb2tlPSIjMkQyRTM0Ii8+Cjwvc3ZnPgo='; |
|
|
|
|
|
|
|
const SELECT_ICON_BASE_64 = |
|
|
|
|
|
|
|
'PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiByeD0iMiIgZmlsbD0iIzRBNzFEMiIvPgo8cGF0aCBkPSJNNS45ODI2NCAxMi41MjE3QzUuNzczOTUgMTIuNTIxNyA1LjU4MjY0IDEyLjQ1MjIgNS40MjYxMiAxMi4yOTU2TDEuOTY1MjUgOC44MzQ3NkMxLjY1MjIxIDguNTIxNzIgMS42NTIyMSA4LjAzNDc2IDEuOTY1MjUgNy43MjE3MkMyLjI3ODI5IDcuNDA4NjcgMi43NjUyNSA3LjQwODY3IDMuMDc4MjkgNy43MjE3Mkw2LjAwMDAzIDEwLjYyNjFMMTIuOTM5MiAzLjcwNDMzQzEzLjI1MjIgMy4zOTEyOCAxMy43MzkyIDMuMzkxMjggMTQuMDUyMiAzLjcwNDMzQzE0LjM2NTMgNC4wMTczNyAxNC4zNjUzIDQuNTA0MzMgMTQuMDUyMiA0LjgxNzM3TDYuNTU2NTYgMTIuMjk1NkM2LjM4MjY0IDEyLjQ1MjIgNi4xOTEzNCAxMi41MjE3IDUuOTgyNjQgMTIuNTIxN1YxMi41MjE3WiIgZmlsbD0iI0ZFRkZGRiIvPgo8L3N2Zz4K'; |
|
|
|
|
|
|
|