diff --git a/package.json b/package.json index 067893d..a3e3480 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^14.4.3", "@types/glob": "^8.0.0", + "@types/grafana": "github:CorpGlory/types-grafana", "@types/jest": "^27.4.1", "@types/lodash-es": "^4.17.6", "@types/node": "^17.0.19", diff --git a/src/icons.ts b/src/icons.ts new file mode 100644 index 0000000..af34bb9 --- /dev/null +++ b/src/icons.ts @@ -0,0 +1,8 @@ +export const CLOSE_ICON_BASE_64 = + 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACJSURBVHgB7ZPRCcAgDESv0lWc0zhC53OIjmAVUpCSamL76YEoyfFIiAGWfldK6SwnKHyhep9xJ3iPcqgH5RyxV1VlBWYJypXVHMEiCaqBbRhAy3W3B76j954wq6ZSVZsOY+WXt6i9l2ymGTlUq0VpOcIqaQC96Zth01DN1zBBefVI4SNp9Za+6wLcH6DKFrfpxgAAAABJRU5ErkJggg=='; +export const DOWNLOAD_ICON_BASE_64 = + 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADlSURBVHgB3ZPNDYJAEIVnN96lBA7A2RIsATuQiiwBSrAD7MAjCZBACXqFwPomMRtEwMVwgZeQhfn5mNnMEG1CaZoeiqKwTWJ3JkFCiEtVVU+8+r9iJRlKSrk3iqOFtWJglmXHf3yDwCRJbBxxnudh34cRYluMMbKGcgWNCIlnjEuIJ1JK2WzDWeKb7YHjONEsYBf6kTABY+mWuYX+3Xiex9UFU7D3FllfwLqueQvi/h8ZCtBprDLY703T6A0yWj2ArmSoxedQV4jSSz5xj4pmCi0/NKfrwNz5bdtaNENciOu6N1qNXhzZXHMb9Q+nAAAAAElFTkSuQmCC'; +export const UNSELECT_ICON_BASE_64 = + 'PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3QgeD0iMC41IiB5PSIwLjUiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgcng9IjEuNSIgZmlsbD0iIzExMTIxNiIgc3Ryb2tlPSIjMkQyRTM0Ii8+Cjwvc3ZnPgo='; +export const SELECT_ICON_BASE_64 = + 'PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiByeD0iMiIgZmlsbD0iIzRBNzFEMiIvPgo8cGF0aCBkPSJNNS45ODI2NCAxMi41MjE3QzUuNzczOTUgMTIuNTIxNyA1LjU4MjY0IDEyLjQ1MjIgNS40MjYxMiAxMi4yOTU2TDEuOTY1MjUgOC44MzQ3NkMxLjY1MjIxIDguNTIxNzIgMS42NTIyMSA4LjAzNDc2IDEuOTY1MjUgNy43MjE3MkMyLjI3ODI5IDcuNDA4NjcgMi43NjUyNSA3LjQwODY3IDMuMDc4MjkgNy43MjE3Mkw2LjAwMDAzIDEwLjYyNjFMMTIuOTM5MiAzLjcwNDMzQzEzLjI1MjIgMy4zOTEyOCAxMy43MzkyIDMuMzkxMjggMTQuMDUyMiAzLjcwNDMzQzE0LjM2NTMgNC4wMTczNyAxNC4zNjUzIDQuNTA0MzMgMTQuMDUyMiA0LjgxNzM3TDYuNTU2NTYgMTIuMjk1NkM2LjM4MjY0IDEyLjQ1MjIgNi4xOTEzNCAxMi41MjE3IDUuOTgyNjQgMTIuNTIxN1YxMi41MjE3WiIgZmlsbD0iI0ZFRkZGRiIvPgo8L3N2Zz4K'; diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 08b6b1f..7735c21 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -1,7 +1,12 @@ -import { PanelOptions, TaskTableRowConfig, DatasourceTableRowConfig } from '../types'; +import { PanelOptions, TaskTableRowConfig, QueryTableRowConfig, DatasourceType } from '../types'; -import { getBackendSrv } from '@grafana/runtime'; -import { makeRequest } from 'services/network_service'; +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 { @@ -12,6 +17,9 @@ import { createTheme, DataFrame, DataLinkClickEvent, + PanelModel, + DataQuery, + DataSourceSettings, } from '@grafana/data'; import React, { useState, useEffect } from 'react'; @@ -19,22 +27,87 @@ import * as _ from 'lodash'; interface Props extends PanelProps {} -export function Panel({ options, data, width, height, timeRange, onChangeTimeRange }: Props) { +export function Panel({ width, height, timeRange }: Props) { + + // TODO: Dashboard type + const [dashboard, setDashboard] = useState(null); + const [datasources, setDatasources] = useState(null); + const [tasks, setTasks] = useState(null); + const [queries, setQueries] = useState(null); + const [tasksDataFrame, setTasksDataFrame] = useState(null); + const [queriesDataFrame, setQueriesDataFrame] = useState(null); + + const [isModalOpen, setModalVisibility] = useState(false); + + useEffect(() => { + async function getCurrentDashboard(): Promise { + 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, setTasks); + const dataFrame = getDataFrameForTaskTable(tasks); setTasksDataFrame(dataFrame); }, [tasks]); useEffect(() => { - // TODO: move this function to Network Service + // TODO: move this function to API Service async function getTasks(): Promise { - return makeRequest('/task', {}); + return queryApi('/task', {}); } getTasks() @@ -42,61 +115,64 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan .catch((err) => console.error(err)); }, []); - const datasourceConfigs: DatasourceTableRowConfig[] = []; - const [datasources, setDatasources] = useState(datasourceConfigs); - const datasourceDataFrame = getDataFrameForDatasourceTable(datasources, setDatasources); + useEffect(() => { + if(queries === null) { + return; + } + setQueriesDataFrame(getDataFrameForQueriesTable(queries)); + }, [queries]); - const [isModalOpen, setModalVisibility] = useState(false); - const timestampRange: [number, number] = [timeRange.from.unix(), timeRange.to.unix()]; - const backendSrv = getBackendSrv(); - // @ts-ignore - backendSrv.getInspectorStream().subscribe({ - next: (resp: any) => { - const queries = resp?.config?.data?.queries; - if (_.isEmpty(queries)) { - return; - } + 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); - const datasource = queries[0].datasource.type; - const refId = queries[0].refId; - 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]); + if (!datasource) { + console.warn(`can't find datasource "${uid}"`); } + return datasource; + } + + async function onAddTaskClick(): Promise { + 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(); - unselectAllDatasources(); + unselectAllQueries(); } - function unselectAllDatasources(): void { - const updatedDatasources = _.clone(datasources); - for (const ds of updatedDatasources) { - ds.select = false; + function unselectAllQueries(): void { + if (queries === null) { + return; } - setDatasources(updatedDatasources); + setQueries(queries.map( + (query: QueryTableRowConfig) => ({ ...query, selected: false }) + )); } function openDatasourceModal(): void { @@ -105,7 +181,164 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan function onCloseModal(): void { 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 ( @@ -126,18 +359,27 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan > Add Task - - - - - + + {queriesDataFrame === null ? ( + // TODO: if datasource responds with error, display the error + + ) : ( +
+
+ + + + + )} @@ -145,206 +387,3 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan ); } - -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'; diff --git a/src/panels/corpglory-dataexporter-panel/types.ts b/src/panels/corpglory-dataexporter-panel/types.ts index 9b2a525..6c69928 100644 --- a/src/panels/corpglory-dataexporter-panel/types.ts +++ b/src/panels/corpglory-dataexporter-panel/types.ts @@ -1,20 +1,29 @@ +import { DataQuery, DataSourceRef, DataSourceSettings, PanelModel } from "@grafana/data"; + export interface PanelOptions {} export type TaskTableRowConfig = { timestamp: number; + // TODO: rename user to username user: string; - datasource: string; + datasourceRef: DataSourceRef; rowsCount: number; progress: number; status: string; + downloadLink: string; }; -export type DatasourceTableRowConfig = { - select: boolean; - panel: string; - refId: string; - datasource: string; - rawSql: string; - uid: string; - timerange: [number, number]; +export type QueryTableRowConfig = Omit & { + selected: boolean; + panel: PanelModel; + datasource: DataSourceSettings; }; + +export enum DatasourceType { + INFLUXDB = 'influxdb', + GRAPHITE = 'graphite', + PROMETHEUS = 'prometheus', + POSTGRES = 'postgres', + ELASTICSEARCH = 'elasticsearch', + MYSQL = 'mysql', +} diff --git a/src/plugin_state.ts b/src/plugin_state.ts index 1c65bbf..0812be3 100644 --- a/src/plugin_state.ts +++ b/src/plugin_state.ts @@ -1,4 +1,4 @@ -import { makeRequest } from './services/network_service'; +import { queryApi } from './services/api_service'; import { DataExporterAppPluginMeta, DataExporterPluginMetaJSONData, @@ -114,7 +114,7 @@ class PluginState { // TODO: try to disable success alerts from Grafana API const { key: grafanaToken } = await this.createGrafanaToken(); await this.updateGrafanaPluginSettings({ secureJsonData: { grafanaToken } }); - const dataExporterAPIResponse = await makeRequest(`/connect`, { + const dataExporterAPIResponse = await queryApi(`/connect`, { method: 'POST', }); return { grafanaToken, dataExporterAPIResponse }; @@ -165,7 +165,7 @@ class PluginState { dataExporterApiUrl: string ): Promise => { try { - const resp = await makeRequest(`/status`, { + const resp = await queryApi(`/status`, { method: 'GET', }); diff --git a/src/services/network_service.ts b/src/services/api_service.ts similarity index 91% rename from src/services/network_service.ts rename to src/services/api_service.ts index 577860e..cdc90d4 100644 --- a/src/services/network_service.ts +++ b/src/services/api_service.ts @@ -24,7 +24,7 @@ interface RequestConfig { validateStatus?: (status: number) => boolean; } -export const makeRequest = async (path: string, config: RequestConfig) => { +export const queryApi = async (path: string, config: RequestConfig) => { const { method = 'GET', params, data, validateStatus } = config; const url = `${API_PROXY_PREFIX}${API_PATH_PREFIX}${path}`; diff --git a/src/services/grafana_backend_service.ts b/src/services/grafana_backend_service.ts new file mode 100644 index 0000000..ba753d2 --- /dev/null +++ b/src/services/grafana_backend_service.ts @@ -0,0 +1,11 @@ +import { getBackendSrv } from "@grafana/runtime"; + +export async function getDatasources() { + const backendSrv = getBackendSrv(); + return backendSrv.get(`/api/datasources`); +} + +export async function getDashboardByUid(uid: string) { + const backendSrv = getBackendSrv(); + return backendSrv.get(`/api/dashboards/uid/${uid}`); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 32d5f31..cc67caf 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,3 +5,24 @@ import appEvents from 'grafana/app/core/app_events'; export function openNotification(message: React.ReactNode) { appEvents.emit(AppEvents.alertSuccess, [message]); } + +export function getDashboardUid(url: string): string { + const matches = new URL(url).pathname.match(/\/d\/([^/]+)/); + if (!matches) { + throw new Error(`Couldn't parse uid from ${url}`); + } else { + return matches[1]; + } +}; + +export 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); +} diff --git a/yarn.lock b/yarn.lock index 45dfbae..5dfa76b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3592,6 +3592,10 @@ dependencies: "@types/node" "*" +"@types/grafana@github:CorpGlory/types-grafana": + version "4.6.3" + resolved "https://codeload.github.com/CorpGlory/types-grafana/tar.gz/4beede5fa0e5bdebdb23b09e4e1e214dcc4c267f" + "@types/history@^4.7.11": version "4.7.11" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"