From 8eeaf714945531b0a2c872dd08c4999c1c07e2bd Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 20 Jan 2023 18:17:57 +0300 Subject: [PATCH 1/7] update queries list on Add Task click --- .../components/Panel.tsx | 48 ++++---- .../corpglory-dataexporter-panel/types.ts | 114 ++++++++++++++++++ 2 files changed, 139 insertions(+), 23 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 60ef817..f5e31ce 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -1,4 +1,4 @@ -import { PanelOptions, ExportTask, DashboardQuery, DatasourceType, ExportStatus, PanelStatus } from '../types'; +import { PanelOptions, ExportTask, DashboardQuery, DatasourceType, ExportStatus, PanelStatus, Dashboard } from '../types'; import { convertTimestampToDate, convertTimeZoneTypeToName, getDashboardUid } from '../../../utils'; import { CLOSE_ICON_BASE_64, DOWNLOAD_ICON_BASE_64, SELECT_ICON_BASE_64, UNSELECT_ICON_BASE_64 } from '../../../icons'; @@ -45,7 +45,7 @@ interface Props extends PanelProps {} export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { // TODO: Dashboard type - const [dashboard, setDashboard] = useState(null); + const [dashboard, setDashboard] = useState(null); const [datasources, setDatasources] = useState(null); const [tasks, setTasks] = useState(null); @@ -93,10 +93,28 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { }, []); useEffect(() => { + if (tasks === null) { + return; + } + const dataFrame = getDataFrameForTaskTable(tasks); + setTasksDataFrame(dataFrame); + }, [tasks]); // eslint-disable-line react-hooks/exhaustive-deps + + useEffect(fetchTasks, []); // eslint-disable-line react-hooks/exhaustive-deps + + useEffect(() => { + if (queries === null) { + return; + } + setQueriesDataFrame(getDataFrameForQueriesTable(queries)); + }, [queries]); // eslint-disable-line react-hooks/exhaustive-deps + + async function updateQueries(dashboard: Dashboard | null, datasources: DataSourceSettings[] | null): Promise { if (!dashboard || !datasources) { + console.warn(`Can't update queries if there is no dashboard or datasources`); return; } - dashboard.panels.forEach((panel: PanelModel) => { + dashboard.panels?.forEach((panel: PanelModel) => { const queries: DashboardQuery[] = []; // @ts-ignore @@ -118,24 +136,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { setQueries(queries); }); - }, [dashboard, datasources]); // eslint-disable-line react-hooks/exhaustive-deps - - useEffect(() => { - if (tasks === null) { - return; - } - const dataFrame = getDataFrameForTaskTable(tasks); - setTasksDataFrame(dataFrame); - }, [tasks]); // eslint-disable-line react-hooks/exhaustive-deps - - useEffect(fetchTasks, []); // eslint-disable-line react-hooks/exhaustive-deps - - useEffect(() => { - if (queries === null) { - return; - } - setQueriesDataFrame(getDataFrameForQueriesTable(queries)); - }, [queries]); // eslint-disable-line react-hooks/exhaustive-deps + } function fetchTasks(): void { getTasks() @@ -212,7 +213,8 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { setQueries(queries.map((query: DashboardQuery) => ({ ...query, selected: false }))); } - function openDatasourceModal(): void { + function openDatasourceModal(dashboard: Dashboard | null, datasources: DataSourceSettings[] | null): void { + updateQueries(dashboard, datasources); setTimeRange(timeRange); setModalVisibility(true); } @@ -439,7 +441,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { aria-label="Rich history button" icon="plus" style={{ marginTop: '8px' }} - onClick={openDatasourceModal} + onClick={() => openDatasourceModal(dashboard, datasources)} > Add Task diff --git a/src/panels/corpglory-dataexporter-panel/types.ts b/src/panels/corpglory-dataexporter-panel/types.ts index 1af9a8d..1010848 100644 --- a/src/panels/corpglory-dataexporter-panel/types.ts +++ b/src/panels/corpglory-dataexporter-panel/types.ts @@ -49,3 +49,117 @@ export type DashboardQuery = { panel: PanelModel; datasource: DataSourceSettings; }; + +export interface Dashboard { + /** + * TODO docs + */ + annotations?: { + list: Array; + }; + /** + * Description of dashboard. + */ + description?: string; + /** + * Whether a dashboard is editable or not. + */ + editable: boolean; + /** + * TODO docs + */ + fiscalYearStartMonth?: number; + gnetId?: string; + graphTooltip: any; + /** + * Unique numeric identifier for the dashboard. + * TODO must isolate or remove identifiers local to a Grafana instance...? + */ + id?: number; + /** + * TODO docs + */ + links?: Array; + /** + * TODO docs + */ + liveNow?: boolean; + panels?: Array; + /** + * TODO docs + */ + refresh?: (string | false); + /** + * Version of the JSON schema, incremented each time a Grafana update brings + * changes to said schema. + * TODO this is the existing schema numbering system. It will be replaced by Thema's themaVersion + */ + schemaVersion: number; + /** + * Theme of dashboard. + */ + style: ('light' | 'dark'); + /** + * Tags associated with dashboard. + */ + tags?: Array; + /** + * TODO docs + */ + templating?: { + list: Array; + }; + /** + * Time range for dashboard, e.g. last 6 hours, last 7 days, etc + */ + time?: { + from: string; + to: string; + }; + /** + * TODO docs + * TODO this appears to be spread all over in the frontend. Concepts will likely need tidying in tandem with schema changes + */ + timepicker?: { + /** + * Whether timepicker is collapsed or not. + */ + collapse: boolean; + /** + * Whether timepicker is enabled or not. + */ + enable: boolean; + /** + * Whether timepicker is visible or not. + */ + hidden: boolean; + /** + * Selectable intervals for auto-refresh. + */ + refresh_intervals: Array; + /** + * TODO docs + */ + time_options: Array; + }; + /** + * Timezone of dashboard, + */ + timezone?: ('browser' | 'utc' | string); + /** + * Title of dashboard. + */ + title?: string; + /** + * Unique dashboard identifier that can be generated by anyone. string (8-40) + */ + uid?: string; + /** + * Version of the dashboard, incremented each time the dashboard is updated. + */ + version?: number; + /** + * TODO docs + */ + weekStart?: string; +} From a4e4dc637f5a4d02bfa69c515355416e4a74e274 Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 20 Jan 2023 18:18:46 +0300 Subject: [PATCH 2/7] fix queries list containing queries only from one panel --- .../corpglory-dataexporter-panel/components/Panel.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index f5e31ce..6bcf69f 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -114,8 +114,9 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { console.warn(`Can't update queries if there is no dashboard or datasources`); return; } + + const queries: DashboardQuery[] = []; dashboard.panels?.forEach((panel: PanelModel) => { - const queries: DashboardQuery[] = []; // @ts-ignore if (panel.type === PANEL_ID) { @@ -133,9 +134,8 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { } queries.push({ selected: false, target, panel, datasource }); }); - - setQueries(queries); }); + setQueries(queries); } function fetchTasks(): void { From d0319ab96b434781fb4c12da4582fba21da05571 Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 20 Jan 2023 18:20:59 +0300 Subject: [PATCH 3/7] hotfix --- .../corpglory-dataexporter-panel/components/Panel.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 6bcf69f..d1896d1 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -109,7 +109,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { setQueriesDataFrame(getDataFrameForQueriesTable(queries)); }, [queries]); // eslint-disable-line react-hooks/exhaustive-deps - async function updateQueries(dashboard: Dashboard | null, datasources: DataSourceSettings[] | null): Promise { + async function updateQueries(): Promise { if (!dashboard || !datasources) { console.warn(`Can't update queries if there is no dashboard or datasources`); return; @@ -213,8 +213,8 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { setQueries(queries.map((query: DashboardQuery) => ({ ...query, selected: false }))); } - function openDatasourceModal(dashboard: Dashboard | null, datasources: DataSourceSettings[] | null): void { - updateQueries(dashboard, datasources); + function openDatasourceModal(): void { + updateQueries(); setTimeRange(timeRange); setModalVisibility(true); } @@ -441,7 +441,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { aria-label="Rich history button" icon="plus" style={{ marginTop: '8px' }} - onClick={() => openDatasourceModal(dashboard, datasources)} + onClick={openDatasourceModal} > Add Task From 731e22cb28a93e61cd7046daf61737442b64b639 Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 20 Jan 2023 18:21:21 +0300 Subject: [PATCH 4/7] lint:fix --- .../components/Panel.tsx | 19 +++++++++--------- .../corpglory-dataexporter-panel/types.ts | 20 +++++++++---------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index d1896d1..f305e97 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -1,4 +1,12 @@ -import { PanelOptions, ExportTask, DashboardQuery, DatasourceType, ExportStatus, PanelStatus, Dashboard } from '../types'; +import { + PanelOptions, + ExportTask, + DashboardQuery, + DatasourceType, + ExportStatus, + PanelStatus, + Dashboard, +} from '../types'; import { convertTimestampToDate, convertTimeZoneTypeToName, getDashboardUid } from '../../../utils'; import { CLOSE_ICON_BASE_64, DOWNLOAD_ICON_BASE_64, SELECT_ICON_BASE_64, UNSELECT_ICON_BASE_64 } from '../../../icons'; @@ -117,7 +125,6 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { const queries: DashboardQuery[] = []; dashboard.panels?.forEach((panel: PanelModel) => { - // @ts-ignore if (panel.type === PANEL_ID) { return; @@ -436,13 +443,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
- diff --git a/src/panels/corpglory-dataexporter-panel/types.ts b/src/panels/corpglory-dataexporter-panel/types.ts index 1010848..f369b40 100644 --- a/src/panels/corpglory-dataexporter-panel/types.ts +++ b/src/panels/corpglory-dataexporter-panel/types.ts @@ -55,7 +55,7 @@ export interface Dashboard { * TODO docs */ annotations?: { - list: Array; + list: any[]; }; /** * Description of dashboard. @@ -79,16 +79,16 @@ export interface Dashboard { /** * TODO docs */ - links?: Array; + links?: any[]; /** * TODO docs */ liveNow?: boolean; - panels?: Array; + panels?: PanelModel[]; /** * TODO docs */ - refresh?: (string | false); + refresh?: string | false; /** * Version of the JSON schema, incremented each time a Grafana update brings * changes to said schema. @@ -98,16 +98,16 @@ export interface Dashboard { /** * Theme of dashboard. */ - style: ('light' | 'dark'); + style: 'light' | 'dark'; /** * Tags associated with dashboard. */ - tags?: Array; + tags?: string[]; /** * TODO docs */ templating?: { - list: Array; + list: any[]; }; /** * Time range for dashboard, e.g. last 6 hours, last 7 days, etc @@ -136,16 +136,16 @@ export interface Dashboard { /** * Selectable intervals for auto-refresh. */ - refresh_intervals: Array; + refresh_intervals: string[]; /** * TODO docs */ - time_options: Array; + time_options: string[]; }; /** * Timezone of dashboard, */ - timezone?: ('browser' | 'utc' | string); + timezone?: 'browser' | 'utc' | string; /** * Title of dashboard. */ From 4d68bb29c6d1919fa8eb9d661589f4e8a006f1b6 Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 20 Jan 2023 18:39:25 +0300 Subject: [PATCH 5/7] hotfix --- src/panels/corpglory-dataexporter-panel/components/Panel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index f305e97..9d85bee 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -52,7 +52,6 @@ const APP_ID = 'corpglory-dataexporter-app'; interface Props extends PanelProps {} export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { - // TODO: Dashboard type const [dashboard, setDashboard] = useState(null); const [datasources, setDatasources] = useState(null); From 57b80672fba8a372ed366a7a91a722f0876a3eda Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 20 Jan 2023 18:50:20 +0300 Subject: [PATCH 6/7] bind tasks to a dashboard --- .../corpglory-dataexporter-panel/components/Panel.tsx | 11 ++++++++--- src/panels/corpglory-dataexporter-panel/types.ts | 1 + src/services/api_service.ts | 4 ++-- src/utils/index.ts | 6 ++++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 9d85bee..82488a1 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -8,7 +8,7 @@ import { Dashboard, } from '../types'; -import { convertTimestampToDate, convertTimeZoneTypeToName, getDashboardUid } from '../../../utils'; +import { convertTimestampToDate, convertTimeZoneTypeToName, getCurrentDashboardUid } from '../../../utils'; import { CLOSE_ICON_BASE_64, DOWNLOAD_ICON_BASE_64, SELECT_ICON_BASE_64, UNSELECT_ICON_BASE_64 } from '../../../icons'; import { deleteTask, getStaticFile, getTasks, queryApi } from '../../../services/api_service'; @@ -83,7 +83,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { useEffect(() => { async function getCurrentDashboard(): Promise { - const currentDashboardUid = getDashboardUid(window.location.toString()); + const currentDashboardUid = getCurrentDashboardUid(); return getDashboardByUid(currentDashboardUid); } @@ -145,7 +145,9 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { } function fetchTasks(): void { - getTasks() + const dashboardUid = getCurrentDashboardUid(); + + getTasks(dashboardUid) .then((tasks) => { setTasks(tasks); setPanelStatusWithValidate(PanelStatus.OK); @@ -187,7 +189,10 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { const selectedQueries = _.filter(queries, (query: DashboardQuery) => query.selected); const timerange: [number, number] = [selectedTimeRange.from.unix(), selectedTimeRange.to.unix()]; + const dashboardUid = getCurrentDashboardUid(); + const task: ExportTask = { + dashboardUid, // @ts-ignore username: contextSrv.user.name, timeRange: { diff --git a/src/panels/corpglory-dataexporter-panel/types.ts b/src/panels/corpglory-dataexporter-panel/types.ts index f369b40..5d10ebc 100644 --- a/src/panels/corpglory-dataexporter-panel/types.ts +++ b/src/panels/corpglory-dataexporter-panel/types.ts @@ -33,6 +33,7 @@ export type ExportProgress = { }; export type ExportTask = { + dashboardUid: string; username: string; queries: DashboardQuery[]; timeRange: { diff --git a/src/services/api_service.ts b/src/services/api_service.ts index d12acd0..ce0ee36 100644 --- a/src/services/api_service.ts +++ b/src/services/api_service.ts @@ -43,8 +43,8 @@ export const queryApi = async (path: string, config: RequestConfig) => return response.data as RT; }; -export async function getTasks(): Promise { - return queryApi('/task', {}); +export async function getTasks(dashboardUid: string): Promise { + return queryApi('/task', { params: { dashboardUid } }); } export async function deleteTask(taskId?: string): Promise { diff --git a/src/utils/index.ts b/src/utils/index.ts index 07acebd..cfa615f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,10 +6,12 @@ export function openNotification(message: React.ReactNode) { appEvents.emit(AppEvents.alertSuccess, [message]); } -export function getDashboardUid(url: string): string { +export function getCurrentDashboardUid(): string { + const url = window.location.toString(); + const matches = new URL(url).pathname.match(/\/d\/([^/]+)/); if (!matches) { - throw new Error(`Couldn't parse uid from ${url}`); + throw new Error(`Can't get current dashboard uid. If it's a new dashboard, please save it first.`); } else { return matches[1]; } From b6041c00f70b3f9d8b24dff9c6021745f1682117 Mon Sep 17 00:00:00 2001 From: rozetko Date: Fri, 20 Jan 2023 21:13:00 +0300 Subject: [PATCH 7/7] do not display unsupported actions for tasks && display success alert on download start --- src/icons.ts | 4 +- .../components/Panel.tsx | 102 +++++++++++------- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/src/icons.ts b/src/icons.ts index af34bb9..72e4d55 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -1,7 +1,7 @@ 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 OPTIONS_ICON_BASE_64 = + 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAS1BMVEUAAADH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0NnH0Nn///+uWGxHAAAAF3RSTlMAABI4MAgNNbzy7JcQG6zvoPxPbavwDHoS9oEAAAABYktHRBibaYUeAAAACXBIWXMAAABgAAAAYADwa0LPAAAAB3RJTUUH5wEUFy4lfOQAfAAAAFdJREFUGNPFjkkKwCAQBEcd912TzP9/GglIQvBuXQoamm6ArbCHv7hAOaS0VkMSBWeAxjofIKacU4TgnTUIhYhqgz5EHVodKutw1o98vvU5dH2Hlpe2cgMuNAUd58WuNgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMy0wMS0yMFQyMDo0NjozNyswMzowMDMOisYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjMtMDEtMjBUMjA6NDY6MzcrMDM6MDBCUzJ6AAAAAElFTkSuQmCC'; export const UNSELECT_ICON_BASE_64 = 'PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3QgeD0iMC41IiB5PSIwLjUiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgcng9IjEuNSIgZmlsbD0iIzExMTIxNiIgc3Ryb2tlPSIjMkQyRTM0Ii8+Cjwvc3ZnPgo='; export const SELECT_ICON_BASE_64 = diff --git a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx index 82488a1..90514ac 100644 --- a/src/panels/corpglory-dataexporter-panel/components/Panel.tsx +++ b/src/panels/corpglory-dataexporter-panel/components/Panel.tsx @@ -9,12 +9,12 @@ import { } from '../types'; import { convertTimestampToDate, convertTimeZoneTypeToName, getCurrentDashboardUid } from '../../../utils'; -import { CLOSE_ICON_BASE_64, DOWNLOAD_ICON_BASE_64, SELECT_ICON_BASE_64, UNSELECT_ICON_BASE_64 } from '../../../icons'; +import { CLOSE_ICON_BASE_64, OPTIONS_ICON_BASE_64, SELECT_ICON_BASE_64, UNSELECT_ICON_BASE_64 } from '../../../icons'; import { deleteTask, getStaticFile, getTasks, queryApi } from '../../../services/api_service'; import { getDashboardByUid, getDatasources } from '../../../services/grafana_backend_service'; -import { contextSrv } from 'grafana/app/core/core'; +import { appEvents, contextSrv } from 'grafana/app/core/core'; import { css } from '@emotion/css'; import { @@ -40,6 +40,7 @@ import { DataSourceSettings, TimeRange, OrgRole, + AppEvents, } from '@grafana/data'; import { RefreshEvent } from '@grafana/runtime'; @@ -297,7 +298,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { name: 'A', fields: [ { - name: 'Time', + name: 'Status Updated At', type: FieldType.number, values: _.map(sortedTasks, (task) => convertTimestampToDate(task.progress?.time)), }, @@ -342,41 +343,25 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { values: _.map(sortedTasks, (task) => task.progress?.errorMessage || '-'), }, { - name: 'Download CSV', + name: 'Actions', type: FieldType.string, - values: _.map(sortedTasks, () => `data:image/png;base64,${DOWNLOAD_ICON_BASE_64}`), + values: _.map(sortedTasks, (task) => { + switch (task.progress?.status) { + case ExportStatus.FINISHED: + return `data:image/png;base64,${OPTIONS_ICON_BASE_64}`; + case ExportStatus.ERROR: + return `data:image/png;base64,${CLOSE_ICON_BASE_64}`; + case ExportStatus.EXPORTING: + return ``; + default: + throw new Error(`Unknown exporting status: ${task.progress?.status}`); + } + }), config: { custom: { filterable: false, displayMode: 'image', }, - links: [ - { - targetBlank: false, - title: 'Download', - url: '#', - onClick: (event: DataLinkClickEvent) => onDownloadClick(event), - }, - ], - }, - }, - { - name: 'Delete task', - type: FieldType.string, - values: _.map(tasks, () => `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), - }, - ], }, }, ], @@ -391,22 +376,57 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) { theme: createTheme(), replaceVariables: (value: string) => value, }); + + // @ts-expect-error + dataFrames[0].fields[9].getLinks = (cell: { valueRowIndex: number }) => { + const rowIndex = cell.valueRowIndex; + const task = _.find(sortedTasks, (task, idx) => idx === rowIndex); + if (!task) { + return; + } + switch (task.progress?.status) { + case ExportStatus.FINISHED: + return [ + { + targetBlank: false, + title: 'Download', + url: '#', + onClick: () => onDownloadClick(task), + }, + { + targetBlank: false, + title: 'Delete', + url: '#', + onClick: () => onDeleteClick(task), + }, + ]; + case ExportStatus.ERROR: + return [ + { + targetBlank: false, + title: 'Delete', + url: '#', + onClick: () => onDeleteClick(task), + }, + ]; + case ExportStatus.EXPORTING: + return []; + default: + throw new Error(`Unknown exporting status: ${task.progress?.status}`); + } + }; return dataFrames[0]; } - async function onDeleteClick(e: DataLinkClickEvent): Promise { - const rowIndex = e.origin.rowIndex; - - const task = _.find(tasks, (task, idx) => idx === rowIndex); - await deleteTask(task?.id); + async function onDeleteClick(taskToDelete: ExportTask): Promise { + await deleteTask(taskToDelete?.id); - const filteredTasks = _.filter(tasks, (task, idx) => idx !== rowIndex); + const filteredTasks = _.filter(tasks, (task) => task.id !== taskToDelete.id); setTasks(filteredTasks); } - function onDownloadClick(e: DataLinkClickEvent): void { - const rowIndex = e.origin.rowIndex; - const task = _.find(tasks, (task, idx) => idx === rowIndex); + function onDownloadClick(task: ExportTask): void { + appEvents.emit(AppEvents.alertSuccess, ['CSV has started downloading...']); getStaticFile(task?.id); }