@ -11,21 +11,23 @@ import {
import { convertTimestampToDate , convertTimeZoneTypeToName , getCurrentDashboardUid } from '../../../utils' ;
import {
CLOSE_ICON_BASE_64 ,
DOWNLOADING_ICON_BASE_64 ,
OPTIONS_ICON_BASE_64 ,
PRELOADER_ICON_BASE_64 ,
SELECT_ICON_BASE_64 ,
UNSELECT_ICON_BASE_64 ,
DOWNLOAD_ICON_BASE_64 ,
DISABLED_DOWNLOAD_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 {
Table ,
Button ,
Select ,
HorizontalGroup ,
VerticalGroup ,
Modal ,
@ -46,11 +48,14 @@ import {
DataSourceSettings ,
TimeRange ,
OrgRole ,
SelectableValue ,
AppEvents ,
} from '@grafana/data' ;
import { RefreshEvent } from '@grafana/runtime' ;
import React , { useState , useEffect } from 'react' ;
import * as _ from 'lodash' ;
import PluginState from 'plugin_state' ;
const PANEL_ID = 'corpglory-dataexporter-panel' ;
const APP_ID = 'corpglory-dataexporter-app' ;
@ -70,6 +75,8 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
const [ isModalOpen , setModalVisibility ] = useState < boolean > ( false ) ;
const [ csvDelimiter , setCsvDelimiter ] = useState < string > ( ',' ) ;
const [ selectedTimeRange , setTimeRange ] = useState < TimeRange > ( timeRange ) ;
const [ panelStatus , setPanelStatus ] = useState < PanelStatus > ( PanelStatus . LOADING ) ;
@ -84,7 +91,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
const timeZoneName = convertTimeZoneTypeToName ( timeZone ) ;
if ( contextSrv . user . orgRole !== OrgRole . Admin ) {
if ( contextSrv . user . orgRole === OrgRole . Viewer ) {
setPanelStatusWithValidate ( PanelStatus . PERMISSION_ERROR ) ;
}
@ -207,7 +214,9 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
to : timerange [ 1 ] * 1000 ,
} ,
queries : selectedQueries ,
csvDelimiter ,
} ;
const token = await PluginState . createGrafanaToken ( ) ;
// TODO: move this function to API Service
await queryApi ( '/task' , {
method : 'POST' ,
@ -215,6 +224,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
task ,
url : window.location.toString ( ) ,
timeZoneName ,
apiKey : token.key ,
} ,
} ) ;
@ -254,6 +264,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
custom : {
filterable : false ,
displayMode : 'image' ,
cellOptions : { type : 'image' } ,
} ,
links : [
{
@ -301,9 +312,39 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
name : 'A' ,
fields : [
{
name : 'Status Updated At' ,
type : FieldType . number ,
values : _.map ( sortedTasks , ( task ) = > convertTimestampToDate ( task . progress ? . time ) ) ,
name : 'Download' ,
type : FieldType . string ,
values : _.map ( sortedTasks , ( task ) = > {
switch ( task . progress ? . status ) {
case ExportStatus . FINISHED :
if ( task . id === taskIdBeingDownloaded ) {
return PRELOADER_ICON_BASE_64 ;
} else {
return DOWNLOAD_ICON_BASE_64 ;
}
case ExportStatus . ERROR :
return DISABLED_DOWNLOAD_ICON_BASE_64 ;
case ExportStatus . EXPORTING :
return PRELOADER_ICON_BASE_64 ;
default :
throw new Error ( ` Unknown exporting status: ${ task . progress ? . status } ` ) ;
}
} ) ,
config : {
custom : {
filterable : false ,
displayMode : 'image' ,
cellOptions : { type : 'image' } ,
} ,
links : [
{
targetBlank : false ,
title : 'Download' ,
url : '#' ,
onClick : ( event : DataLinkClickEvent ) = > onDownloadClick ( event ) ,
} ,
] ,
} ,
} ,
{
name : 'From' ,
@ -315,16 +356,6 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
type : FieldType . number ,
values : _.map ( sortedTasks , ( task ) = > convertTimestampToDate ( task . timeRange . to ) ) ,
} ,
{
name : 'User' ,
type : FieldType . string ,
values : _.map ( sortedTasks , ( task ) = > task . username ) ,
} ,
{
name : 'Datasource' ,
type : FieldType . string ,
values : _.map ( sortedTasks , ( task ) = > task . queries . map ( ( query ) = > query . datasource ? . name ) . join ( ',' ) ) ,
} ,
{
name : 'Exported Rows' ,
type : FieldType . number ,
@ -346,20 +377,20 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
values : _.map ( sortedTasks , ( task ) = > task . progress ? . errorMessage || '-' ) ,
} ,
{
name : 'Actions ' ,
name : 'Delete ' ,
type : FieldType . string ,
values : _.map ( sortedTasks , ( task ) = > {
switch ( task . progress ? . status ) {
case ExportStatus . FINISHED :
if ( task . id === taskIdBeingDownloaded ) {
return DOWNLOADING _ICON_BASE_64;
return PRELOADER _ICON_BASE_64;
} else {
return OPTIONS _ICON_BASE_64;
return CLOSE _ICON_BASE_64;
}
case ExportStatus . ERROR :
return CLOSE_ICON_BASE_64 ;
case ExportStatus . EXPORTING :
return '' ;
return PRELOADER_ICON_BASE_64 ;
default :
throw new Error ( ` Unknown exporting status: ${ task . progress ? . status } ` ) ;
}
@ -368,7 +399,16 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
custom : {
filterable : false ,
displayMode : 'image' ,
cellOptions : { type : 'image' } ,
} ,
links : [
{
targetBlank : false ,
title : 'Delete' ,
url : '#' ,
onClick : ( event : DataLinkClickEvent ) = > onDeleteClick ( event ) ,
} ,
] ,
} ,
} ,
] ,
@ -384,57 +424,37 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
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 ( taskToDelete : ExportTask ) : Promise < void > {
async function onDeleteClick ( event : DataLinkClickEvent ) : Promise < void > {
const rowIndex = event . origin . rowIndex ;
const sortedTasks = _ . orderBy ( tasks , ( task ) = > task . progress ? . time , 'desc' ) ;
const taskToDelete = sortedTasks [ rowIndex ] ;
if ( taskToDelete . progress ? . status === ExportStatus . EXPORTING || taskToDelete . id === taskIdBeingDownloaded ) {
appEvents . emit ( AppEvents . alertWarning , [ 'Data Exporter' , 'Active task can`t be deleted' ] ) ;
return ;
}
await deleteTask ( taskToDelete ? . id ) ;
const filteredTasks = _ . filter ( tasks , ( task ) = > task . id !== taskToDelete . id ) ;
setTasks ( filteredTasks ) ;
}
async function onDownloadClick ( task : ExportTask ) : Promise < void > {
async function onDownloadClick ( event : DataLinkClickEvent ) : Promise < void > {
const rowIndex = event . origin . rowIndex ;
const sortedTasks = _ . orderBy ( tasks , ( task ) = > task . progress ? . time , 'desc' ) ;
const task = sortedTasks [ rowIndex ] ;
if ( task . progress ? . status === ExportStatus . EXPORTING || task . id === taskIdBeingDownloaded ) {
appEvents . emit ( AppEvents . alertWarning , [ 'Data Exporter' , 'Active task can`t be downloaded' ] ) ;
return ;
}
if ( task . progress ? . status === ExportStatus . ERROR ) {
appEvents . emit ( AppEvents . alertWarning , [ 'Data Exporter' , 'Failed task can`t be downloaded' ] ) ;
return ;
}
setTaskIdBeingDownloaded ( task . id as string ) ;
await getStaticFile ( task ? . id ) ;
await getStaticFile ( task ? . filename ) ;
setTaskIdBeingDownloaded ( null ) ;
}
@ -448,6 +468,12 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
setQueries ( updatedQueries ) ;
}
const delimeterOptions : SelectableValue [ ] = [
{ value : ',' , label : 'Comma: ","' } ,
{ value : ';' , label : 'Semicolon: ";"' } ,
{ value : '\t' , label : 'Tabulator: "\\t"' } ,
] ;
const styles = useStyles2 ( getStyles ) ;
const loadingDiv = < LoadingPlaceholder text = "Loading..." > < / LoadingPlaceholder > ;
@ -468,12 +494,12 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
const permissionErrorDiv = (
< div >
< p > Permission Error . < / p >
< div > DataExporter panel available only for Admins . < / div >
< div > DataExporter panel available only for Admins and Editors . < / div >
< / div >
) ;
const mainDiv = (
< div >
< Table width = { width } height = { height - 40 } data = { tasksDataFrame as DataFrame } / >
{ tasksDataFrame && < Table width = { width } height = { height - 40 } data = { tasksDataFrame as DataFrame } / > }
< HorizontalGroup justify = "flex-end" >
< Button variant = "primary" icon = "plus" style = { { marginTop : '8px' } } onClick = { openDatasourceModal } >
Add Task
@ -495,7 +521,17 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
/ >
< / HorizontalGroup >
< Table width = { width / 2 - 20 } height = { height - 40 } data = { queriesDataFrame } / >
< HorizontalGroup justify = "flex-end" spacing = "md" >
< HorizontalGroup justify = "space-between" spacing = "md" >
< HorizontalGroup justify = "flex-end" spacing = "md" >
< span > CSV delimiter : < / span >
< Select
options = { delimeterOptions }
value = { csvDelimiter }
onChange = { ( el ) = > {
setCsvDelimiter ( el . value ) ;
} }
/ >
< / HorizontalGroup >
< Button
variant = "primary"
aria - label = "Add task button"