From e259d5c33fc965088619b9019ab31e3fe39659c5 Mon Sep 17 00:00:00 2001 From: rozetko Date: Wed, 28 Dec 2022 14:46:46 +0300 Subject: [PATCH 1/8] run prettier --- .../components/Panel.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 5ebbd74..9d4e364 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -25,11 +25,11 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan const [dataFrame, setDataFrame] = useState(null); useEffect(() => { - if(tasks === null) { + if (tasks === null) { return; } const dataFrame = getDataFrameForTable(tasks, setTasks); - setDataFrame(dataFrame) + setDataFrame(dataFrame); }, [tasks]); useEffect(() => { @@ -38,11 +38,13 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan return makeRequest('/tasks', {}); } - getTasks().then(tasks => setTasks(tasks)).catch(err => console.error(err)); + getTasks() + .then((tasks) => setTasks(tasks)) + .catch((err) => console.error(err)); }, []); function onAddTaskClick(): void { - if(tasks === null) { + if (tasks === null) { return; } const configs = [...tasks, createConfigItem()]; @@ -51,9 +53,10 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan return (
- {dataFrame === null ? + {dataFrame === null ? ( // TODO: if datasource responds with error, display the error - : + + ) : (
@@ -68,7 +71,7 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan - } + )} ); } -- 2.34.1 From fd80fa4f97d75c433b962fd43e0d806e1b0afd49 Mon Sep 17 00:00:00 2001 From: rozetko Date: Wed, 28 Dec 2022 14:47:21 +0300 Subject: [PATCH 2/8] /api/tasks -> /api/task --- src/panels/corpglory-dataexporter-panel/components/Panel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 9d4e364..2fca72c 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -35,7 +35,7 @@ export function Panel({ options, data, width, height, timeRange, onChangeTimeRan useEffect(() => { // TODO: move this function to Network Service async function getTasks(): Promise { - return makeRequest('/tasks', {}); + return makeRequest('/task', {}); } getTasks() -- 2.34.1 From 64aa8b8f8b791308376b8aaeaacdc82b0206792d Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 30 Dec 2022 03:34:54 +0300 Subject: [PATCH 3/8] a lot of refactoring && it works --- package.json | 1 + src/icons.ts | 8 + .../components/Panel.tsx | 577 ++++++++++-------- .../corpglory-dataexporter-panel/types.ts | 27 +- src/plugin_state.ts | 6 +- .../{network_service.ts => api_service.ts} | 2 +- src/services/grafana_backend_service.ts | 11 + src/utils/index.ts | 21 + yarn.lock | 4 + 9 files changed, 375 insertions(+), 282 deletions(-) create mode 100644 src/icons.ts rename src/services/{network_service.ts => api_service.ts} (91%) create mode 100644 src/services/grafana_backend_service.ts 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" -- 2.34.1 From e1f8b071edcd14f7a8ea20ef3fadb1ae37fca967 Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 30 Dec 2022 14:19:35 +0300 Subject: [PATCH 4/8] lint:fix --- .../components/Panel.tsx | 31 ++++++------------- .../corpglory-dataexporter-panel/types.ts | 2 +- src/services/grafana_backend_service.ts | 2 +- src/utils/index.ts | 2 +- 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 7735c21..cf2d4b4 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -28,7 +28,6 @@ import * as _ from 'lodash'; interface Props extends PanelProps {} export function Panel({ width, height, timeRange }: Props) { - // TODO: Dashboard type const [dashboard, setDashboard] = useState(null); const [datasources, setDatasources] = useState(null); @@ -45,8 +44,6 @@ export function Panel({ width, height, timeRange }: Props) { async function getCurrentDashboard(): Promise { const currentDashboardUid = getDashboardUid(window.location.toString()); - console.log(currentDashboardUid) - return getDashboardByUid(currentDashboardUid); } @@ -78,22 +75,17 @@ export function Panel({ width, height, timeRange }: Props) { 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 }); + 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(() => { @@ -116,13 +108,12 @@ export function Panel({ width, height, timeRange }: Props) { }, []); useEffect(() => { - if(queries === null) { + 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`); @@ -170,9 +161,7 @@ export function Panel({ width, height, timeRange }: Props) { if (queries === null) { return; } - setQueries(queries.map( - (query: QueryTableRowConfig) => ({ ...query, selected: false }) - )); + setQueries(queries.map((query: QueryTableRowConfig) => ({ ...query, selected: false }))); } function openDatasourceModal(): void { diff --git a/src/panels/corpglory-dataexporter-panel/types.ts b/src/panels/corpglory-dataexporter-panel/types.ts index 6c69928..2e82846 100644 --- a/src/panels/corpglory-dataexporter-panel/types.ts +++ b/src/panels/corpglory-dataexporter-panel/types.ts @@ -1,4 +1,4 @@ -import { DataQuery, DataSourceRef, DataSourceSettings, PanelModel } from "@grafana/data"; +import { DataQuery, DataSourceRef, DataSourceSettings, PanelModel } from '@grafana/data'; export interface PanelOptions {} diff --git a/src/services/grafana_backend_service.ts b/src/services/grafana_backend_service.ts index ba753d2..565c9f0 100644 --- a/src/services/grafana_backend_service.ts +++ b/src/services/grafana_backend_service.ts @@ -1,4 +1,4 @@ -import { getBackendSrv } from "@grafana/runtime"; +import { getBackendSrv } from '@grafana/runtime'; export async function getDatasources() { const backendSrv = getBackendSrv(); diff --git a/src/utils/index.ts b/src/utils/index.ts index cc67caf..43ac013 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -13,7 +13,7 @@ export function getDashboardUid(url: string): string { } else { return matches[1]; } -}; +} export function convertTimestampToDate(timestamp: number): string { const options: Intl.DateTimeFormatOptions = { -- 2.34.1 From 62459e4c35c533e14d00f26ba625aea41d20e969 Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 30 Dec 2022 14:57:28 +0300 Subject: [PATCH 5/8] it works --- .../components/Panel.tsx | 35 ++++++++++--------- .../corpglory-dataexporter-panel/types.ts | 5 ++- src/services/api_service.ts | 6 ++++ 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index cf2d4b4..0caac7e 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -3,7 +3,7 @@ import { PanelOptions, TaskTableRowConfig, QueryTableRowConfig, DatasourceType } 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 { getTasks, queryApi } from '../../../services/api_service'; import { getDashboardByUid, getDatasources } from '../../../services/grafana_backend_service'; import { contextSrv } from 'grafana/app/core/core'; @@ -21,13 +21,15 @@ import { DataQuery, DataSourceSettings, } from '@grafana/data'; +import { RefreshEvent } from '@grafana/runtime'; import React, { useState, useEffect } from 'react'; import * as _ from 'lodash'; interface Props extends PanelProps {} -export function Panel({ width, height, timeRange }: Props) { +export function Panel({ width, height, timeRange, eventBus }: Props) { + // TODO: Dashboard type const [dashboard, setDashboard] = useState(null); const [datasources, setDatasources] = useState(null); @@ -96,16 +98,7 @@ export function Panel({ width, height, timeRange }: Props) { setTasksDataFrame(dataFrame); }, [tasks]); - useEffect(() => { - // TODO: move this function to API Service - async function getTasks(): Promise { - return queryApi('/task', {}); - } - - getTasks() - .then((tasks) => setTasks(tasks)) - .catch((err) => console.error(err)); - }, []); + useEffect(refresh, []); useEffect(() => { if (queries === null) { @@ -114,6 +107,15 @@ export function Panel({ width, height, timeRange }: Props) { setQueriesDataFrame(getDataFrameForQueriesTable(queries)); }, [queries]); + function refresh(): void { + console.log('rfrsh') + getTasks() + .then((tasks) => setTasks(tasks)) + .catch((err) => console.error(err)); + } + + eventBus.subscribe(RefreshEvent, refresh); + function getDatasourceByUid(uid?: string): DataSourceSettings | undefined { if (_.isNil(uid)) { console.warn(`uid is required to get datasource`); @@ -137,7 +139,6 @@ export function Panel({ width, height, timeRange }: Props) { // 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', @@ -145,13 +146,13 @@ export function Panel({ width, height, timeRange }: Props) { from: timerange[0] * 1000, to: timerange[1] * 1000, // @ts-ignore - username: contextSrv.user.username, + username: contextSrv.user.name, tasks: selectedQueries, url: window.location.toString(), }, }); - // TODO: refresh panel (or get websocket events) + refresh(); onCloseModal(); unselectAllQueries(); @@ -241,7 +242,7 @@ export function Panel({ width, height, timeRange }: Props) { { name: 'User', type: FieldType.string, - values: _.map(configs, (config) => config.user), + values: _.map(configs, (config) => config.username), }, { name: 'Datasource', @@ -256,7 +257,7 @@ export function Panel({ width, height, timeRange }: Props) { { name: 'Progress', type: FieldType.string, - values: _.map(configs, (config) => `${config.progress}%`), + values: _.map(configs, (config) => `${(config.progress * 100).toFixed(0)}%`), }, { name: 'Status', diff --git a/src/panels/corpglory-dataexporter-panel/types.ts b/src/panels/corpglory-dataexporter-panel/types.ts index 2e82846..c83ef92 100644 --- a/src/panels/corpglory-dataexporter-panel/types.ts +++ b/src/panels/corpglory-dataexporter-panel/types.ts @@ -4,13 +4,12 @@ export interface PanelOptions {} export type TaskTableRowConfig = { timestamp: number; - // TODO: rename user to username - user: string; + username: string; datasourceRef: DataSourceRef; rowsCount: number; progress: number; status: string; - downloadLink: string; + filename?: string; }; export type QueryTableRowConfig = Omit & { diff --git a/src/services/api_service.ts b/src/services/api_service.ts index cdc90d4..84b2fc4 100644 --- a/src/services/api_service.ts +++ b/src/services/api_service.ts @@ -1,3 +1,5 @@ +import { TaskTableRowConfig } from '../panels/corpglory-dataexporter-panel/types'; + import axios from 'axios'; export const API_HOST = `${window.location.protocol}//${window.location.host}/`; @@ -39,3 +41,7 @@ export const queryApi = async (path: string, config: RequestConfig) => return response.data as RT; }; + +export async function getTasks(): Promise { + return queryApi('/task', {}); +} -- 2.34.1 From f5bd4a5b724ea931f9b07a83778041ad2fc37d4a Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 30 Dec 2022 15:15:00 +0300 Subject: [PATCH 6/8] download works --- .../components/Panel.tsx | 26 +++++++++++++------ src/services/api_service.ts | 23 ++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 0caac7e..2bbb81b 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -3,7 +3,7 @@ import { PanelOptions, TaskTableRowConfig, QueryTableRowConfig, DatasourceType } 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 { getTasks, queryApi } from '../../../services/api_service'; +import { getStaticFile, getTasks, queryApi } from '../../../services/api_service'; import { getDashboardByUid, getDatasources } from '../../../services/grafana_backend_service'; import { contextSrv } from 'grafana/app/core/core'; @@ -195,7 +195,7 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { targetBlank: false, title: 'Select', url: '#', - onClick: (event: DataLinkClickEvent) => onDatasourceSelectClick(event, configs), + onClick: (event: DataLinkClickEvent) => onDatasourceSelectClick(event), }, ], }, @@ -276,9 +276,9 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { links: [ { targetBlank: false, - title: 'CSV link', - // TODO: config.downloadLink - url: 'https://chartwerk.io/', + title: 'Download', + url: '#', + onClick: (event: DataLinkClickEvent) => onDownloadClick(event), }, ], }, @@ -297,7 +297,7 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { targetBlank: false, title: 'Delete', url: '#', - onClick: (event: DataLinkClickEvent) => onDeleteClick(event, configs), + onClick: (event: DataLinkClickEvent) => onDeleteClick(event), }, ], }, @@ -317,14 +317,24 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { return dataFrames[0]; } - function onDeleteClick(e: DataLinkClickEvent, tasks: TaskTableRowConfig[]): void { + function onDeleteClick(e: DataLinkClickEvent): 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 { + function onDownloadClick(e: DataLinkClickEvent): void { + // TODO: make DELETE request to the api + const rowIndex = e.origin.rowIndex; + const task = _.find(tasks, (task, idx) => idx !== rowIndex); + getStaticFile(task?.filename); + } + + function onDatasourceSelectClick(e: DataLinkClickEvent): void { + if (queries === null) { + return; + } const rowIndex = e.origin.rowIndex; const updatedQueries = _.clone(queries); updatedQueries[rowIndex].selected = !updatedQueries[rowIndex].selected; diff --git a/src/services/api_service.ts b/src/services/api_service.ts index 84b2fc4..43dc643 100644 --- a/src/services/api_service.ts +++ b/src/services/api_service.ts @@ -1,6 +1,7 @@ import { TaskTableRowConfig } from '../panels/corpglory-dataexporter-panel/types'; import axios from 'axios'; +import * as _ from 'lodash'; export const API_HOST = `${window.location.protocol}//${window.location.host}/`; export const API_PROXY_PREFIX = 'api/plugin-proxy/corpglory-dataexporter-app'; @@ -45,3 +46,25 @@ export const queryApi = async (path: string, config: RequestConfig) => export async function getTasks(): Promise { return queryApi('/task', {}); } + +export async function getStaticFile(filename?: string): Promise { + if (_.isEmpty(filename)) { + console.warn(`can't download file without name`); + return; + } + const respData = await queryApi(`/static/${filename}.csv`, {}); + // TODO: check if resp exists + // create file link in browser's memory + const href = URL.createObjectURL(new Blob([respData], { type: 'text/csv' })); + + // create "a" HTML element with href to file & click + const link = document.createElement('a'); + link.href = href; + link.setAttribute('download', `${filename}.csv`); + document.body.appendChild(link); + link.click(); + + // clean up "a" element & remove ObjectURL + document.body.removeChild(link); + URL.revokeObjectURL(href); +} -- 2.34.1 From 6acc0e2d7f5574f81516d411a6261bb1375a4e87 Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 30 Dec 2022 15:32:37 +0300 Subject: [PATCH 7/8] make Delete work --- .../components/Panel.tsx | 24 +++++++++++++------ src/services/api_service.ts | 8 +++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 2bbb81b..0c1b1d6 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -3,7 +3,7 @@ import { PanelOptions, TaskTableRowConfig, QueryTableRowConfig, DatasourceType } 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 { getStaticFile, getTasks, queryApi } from '../../../services/api_service'; +import { deleteTask, getStaticFile, getTasks, queryApi } from '../../../services/api_service'; import { getDashboardByUid, getDatasources } from '../../../services/grafana_backend_service'; import { contextSrv } from 'grafana/app/core/core'; @@ -108,9 +108,17 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { }, [queries]); function refresh(): void { - console.log('rfrsh') getTasks() - .then((tasks) => setTasks(tasks)) + .then((tasks) => { + setTasks(tasks); + for (let task of tasks) { + // TODO: ExportStatus enum + if (task.status === 'exporting') { + setTimeout(refresh, 1000); + return; + } + } + }) .catch((err) => console.error(err)); } @@ -317,17 +325,19 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { return dataFrames[0]; } - function onDeleteClick(e: DataLinkClickEvent): void { - // TODO: make DELETE request to the api + async function onDeleteClick(e: DataLinkClickEvent): Promise { const rowIndex = e.origin.rowIndex; + + const task = _.find(tasks, (task, idx) => idx === rowIndex); + await deleteTask(task?.filename); + const filteredTasks = _.filter(tasks, (task, idx) => idx !== rowIndex); setTasks(filteredTasks); } function onDownloadClick(e: DataLinkClickEvent): void { - // TODO: make DELETE request to the api const rowIndex = e.origin.rowIndex; - const task = _.find(tasks, (task, idx) => idx !== rowIndex); + const task = _.find(tasks, (task, idx) => idx === rowIndex); getStaticFile(task?.filename); } diff --git a/src/services/api_service.ts b/src/services/api_service.ts index 43dc643..a803327 100644 --- a/src/services/api_service.ts +++ b/src/services/api_service.ts @@ -47,6 +47,14 @@ export async function getTasks(): Promise { return queryApi('/task', {}); } +export async function deleteTask(filename?: string): Promise { + if (_.isEmpty(filename)) { + console.warn(`can't delete task without filename`); + return; + } + await queryApi('/task', { method: 'DELETE', data: { filename } }); +} + export async function getStaticFile(filename?: string): Promise { if (_.isEmpty(filename)) { console.warn(`can't download file without name`); -- 2.34.1 From 1e2413dac49a5dd41b10894547aacf2a7da9bc4a Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 30 Dec 2022 15:38:53 +0300 Subject: [PATCH 8/8] eslint disable --- .../corpglory-dataexporter-panel/components/Panel.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 0c1b1d6..9c7494a 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -29,7 +29,6 @@ import * as _ from 'lodash'; interface Props extends PanelProps {} export function Panel({ width, height, timeRange, eventBus }: Props) { - // TODO: Dashboard type const [dashboard, setDashboard] = useState(null); const [datasources, setDatasources] = useState(null); @@ -88,7 +87,7 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { setQueries(queries); }); - }, [dashboard, datasources]); + }, [dashboard, datasources]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (tasks === null) { @@ -96,16 +95,16 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { } const dataFrame = getDataFrameForTaskTable(tasks); setTasksDataFrame(dataFrame); - }, [tasks]); + }, [tasks]); // eslint-disable-line react-hooks/exhaustive-deps - useEffect(refresh, []); + useEffect(refresh, []); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (queries === null) { return; } setQueriesDataFrame(getDataFrameForQueriesTable(queries)); - }, [queries]); + }, [queries]); // eslint-disable-line react-hooks/exhaustive-deps function refresh(): void { getTasks() -- 2.34.1