|
|
@ -1,4 +1,4 @@ |
|
|
|
import { PanelOptions, ExportTask, DashboardQuery, DatasourceType, ExportStatus } from '../types'; |
|
|
|
import { PanelOptions, ExportTask, DashboardQuery, DatasourceType, ExportStatus, PanelStatus } from '../types'; |
|
|
|
|
|
|
|
|
|
|
|
import { convertTimestampToDate, getDashboardUid } from '../../../utils'; |
|
|
|
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 { CLOSE_ICON_BASE_64, DOWNLOAD_ICON_BASE_64, SELECT_ICON_BASE_64, UNSELECT_ICON_BASE_64 } from '../../../icons'; |
|
|
@ -31,12 +31,16 @@ import { |
|
|
|
DataQuery, |
|
|
|
DataQuery, |
|
|
|
DataSourceSettings, |
|
|
|
DataSourceSettings, |
|
|
|
TimeRange, |
|
|
|
TimeRange, |
|
|
|
|
|
|
|
OrgRole, |
|
|
|
} from '@grafana/data'; |
|
|
|
} from '@grafana/data'; |
|
|
|
import { RefreshEvent } from '@grafana/runtime'; |
|
|
|
import { RefreshEvent } from '@grafana/runtime'; |
|
|
|
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from 'react'; |
|
|
|
import React, { useState, useEffect } from 'react'; |
|
|
|
import * as _ from 'lodash'; |
|
|
|
import * as _ from 'lodash'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const PANEL_ID = 'corpglory-dataexporter-panel'; |
|
|
|
|
|
|
|
const APP_ID = 'corpglory-dataexporter-app'; |
|
|
|
|
|
|
|
|
|
|
|
interface Props extends PanelProps<PanelOptions> {} |
|
|
|
interface Props extends PanelProps<PanelOptions> {} |
|
|
|
|
|
|
|
|
|
|
|
export function Panel({ width, height, timeRange, eventBus }: Props) { |
|
|
|
export function Panel({ width, height, timeRange, eventBus }: Props) { |
|
|
@ -54,6 +58,13 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { |
|
|
|
|
|
|
|
|
|
|
|
const [selectedTimeRange, setTimeRange] = useState<TimeRange>(timeRange); |
|
|
|
const [selectedTimeRange, setTimeRange] = useState<TimeRange>(timeRange); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [panelStatus, setPanelStatus] = useState<PanelStatus>(PanelStatus.LOADING); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (contextSrv.user.orgRole !== OrgRole.Admin) { |
|
|
|
|
|
|
|
// TODO: it shouldn't be overriten
|
|
|
|
|
|
|
|
setPanelStatus(PanelStatus.PERMISSION_ERROR); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
async function getCurrentDashboard(): Promise<any> { |
|
|
|
async function getCurrentDashboard(): Promise<any> { |
|
|
|
const currentDashboardUid = getDashboardUid(window.location.toString()); |
|
|
|
const currentDashboardUid = getDashboardUid(window.location.toString()); |
|
|
@ -80,8 +91,7 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { |
|
|
|
const queries: DashboardQuery[] = []; |
|
|
|
const queries: DashboardQuery[] = []; |
|
|
|
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
// @ts-ignore
|
|
|
|
// TODO: move plugin id to const
|
|
|
|
if (panel.type === PANEL_ID) { |
|
|
|
if (panel.type === 'corpglory-dataexporter-panel') { |
|
|
|
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -123,6 +133,7 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { |
|
|
|
getTasks() |
|
|
|
getTasks() |
|
|
|
.then((tasks) => { |
|
|
|
.then((tasks) => { |
|
|
|
setTasks(tasks); |
|
|
|
setTasks(tasks); |
|
|
|
|
|
|
|
setPanelStatus(PanelStatus.OK); |
|
|
|
for (let task of tasks) { |
|
|
|
for (let task of tasks) { |
|
|
|
if (task.progress?.status === ExportStatus.EXPORTING) { |
|
|
|
if (task.progress?.status === ExportStatus.EXPORTING) { |
|
|
|
setTimeout(refresh, 1000); |
|
|
|
setTimeout(refresh, 1000); |
|
|
@ -130,7 +141,10 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
.catch((err) => console.error(err)); |
|
|
|
.catch((err) => { |
|
|
|
|
|
|
|
setPanelStatus(PanelStatus.DATASOURCE_ERROR); |
|
|
|
|
|
|
|
console.error('some error', err); |
|
|
|
|
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
eventBus.subscribe(RefreshEvent, refresh); |
|
|
|
eventBus.subscribe(RefreshEvent, refresh); |
|
|
@ -164,8 +178,8 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { |
|
|
|
from: timerange[0] * 1000, |
|
|
|
from: timerange[0] * 1000, |
|
|
|
to: timerange[1] * 1000, |
|
|
|
to: timerange[1] * 1000, |
|
|
|
}, |
|
|
|
}, |
|
|
|
queries: selectedQueries |
|
|
|
queries: selectedQueries, |
|
|
|
} |
|
|
|
}; |
|
|
|
// TODO: move this function to API Service
|
|
|
|
// TODO: move this function to API Service
|
|
|
|
await queryApi('/task', { |
|
|
|
await queryApi('/task', { |
|
|
|
method: 'POST', |
|
|
|
method: 'POST', |
|
|
@ -189,6 +203,7 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function openDatasourceModal(): void { |
|
|
|
function openDatasourceModal(): void { |
|
|
|
|
|
|
|
setTimeRange(timeRange); |
|
|
|
setModalVisibility(true); |
|
|
|
setModalVisibility(true); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -270,7 +285,7 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { |
|
|
|
{ |
|
|
|
{ |
|
|
|
name: 'Datasource', |
|
|
|
name: 'Datasource', |
|
|
|
type: FieldType.string, |
|
|
|
type: FieldType.string, |
|
|
|
values: _.map(tasks, (task) => task.queries.map(query => query.datasource?.name).join(',')), |
|
|
|
values: _.map(tasks, (task) => task.queries.map((query) => query.datasource?.name).join(',')), |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
name: 'Exported Rows', |
|
|
|
name: 'Exported Rows', |
|
|
@ -373,65 +388,86 @@ export function Panel({ width, height, timeRange, eventBus }: Props) { |
|
|
|
|
|
|
|
|
|
|
|
const styles = useStyles2(getStyles); |
|
|
|
const styles = useStyles2(getStyles); |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
const loadingDiv = <LoadingPlaceholder text="Loading..."></LoadingPlaceholder>; |
|
|
|
|
|
|
|
// TODO: add styles
|
|
|
|
|
|
|
|
const datasourceErrorDiv = ( |
|
|
|
|
|
|
|
<div> |
|
|
|
|
|
|
|
<p>Datasource is unavailable.</p> |
|
|
|
|
|
|
|
<div> |
|
|
|
|
|
|
|
Click <a href={`/plugins/${APP_ID}`}>here</a> to configure DataExporter. |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
{/* TODO: display error message? */} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
const permissionErrorDiv = ( |
|
|
|
|
|
|
|
<div> |
|
|
|
|
|
|
|
<p>Permission Error.</p> |
|
|
|
|
|
|
|
<div> DataExporter panel availabel only for Admins </div> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
const mainDiv = ( |
|
|
|
<div> |
|
|
|
<div> |
|
|
|
{tasksDataFrame === null ? ( |
|
|
|
<Table width={width} height={height - 40} data={tasksDataFrame as DataFrame} /> |
|
|
|
// TODO: if datasource responds with error, display the error
|
|
|
|
<HorizontalGroup justify="flex-end"> |
|
|
|
<LoadingPlaceholder text="Loading..."></LoadingPlaceholder> |
|
|
|
<Button |
|
|
|
) : ( |
|
|
|
variant="primary" |
|
|
|
<div> |
|
|
|
aria-label="Rich history button" |
|
|
|
<Table width={width} height={height - 40} data={tasksDataFrame} /> |
|
|
|
icon="plus" |
|
|
|
<HorizontalGroup justify="flex-end"> |
|
|
|
style={{ marginTop: '8px' }} |
|
|
|
<Button |
|
|
|
onClick={openDatasourceModal} |
|
|
|
variant="primary" |
|
|
|
> |
|
|
|
aria-label="Rich history button" |
|
|
|
Add Task |
|
|
|
icon="plus" |
|
|
|
</Button> |
|
|
|
style={{ marginTop: '8px' }} |
|
|
|
<Modal title="Select Queries" isOpen={isModalOpen} onDismiss={onCloseModal} className={styles.calendarModal}> |
|
|
|
onClick={openDatasourceModal} |
|
|
|
{queriesDataFrame === null ? ( |
|
|
|
> |
|
|
|
// TODO: if datasource responds with error, display the error
|
|
|
|
Add Task |
|
|
|
<LoadingPlaceholder text="Loading..."></LoadingPlaceholder> |
|
|
|
</Button> |
|
|
|
) : ( |
|
|
|
<Modal |
|
|
|
<div> |
|
|
|
title="Select Queries" |
|
|
|
<VerticalGroup spacing="xs"> |
|
|
|
isOpen={isModalOpen} |
|
|
|
<HorizontalGroup justify="flex-start" spacing="md"> |
|
|
|
onDismiss={onCloseModal} |
|
|
|
<TimeRangeInput |
|
|
|
className={styles.calendarModal} |
|
|
|
value={selectedTimeRange} |
|
|
|
> |
|
|
|
onChange={(newTimeRange) => { |
|
|
|
{queriesDataFrame === null ? ( |
|
|
|
setTimeRange(newTimeRange); |
|
|
|
// TODO: if datasource responds with error, display the error
|
|
|
|
}} |
|
|
|
<LoadingPlaceholder text="Loading..."></LoadingPlaceholder> |
|
|
|
/> |
|
|
|
) : ( |
|
|
|
</HorizontalGroup> |
|
|
|
<div> |
|
|
|
<Table width={width / 2 - 20} height={height - 40} data={queriesDataFrame} /> |
|
|
|
<VerticalGroup spacing="xs"> |
|
|
|
<HorizontalGroup justify="flex-end" spacing="md"> |
|
|
|
<HorizontalGroup justify="flex-start" spacing="md"> |
|
|
|
<Button |
|
|
|
<TimeRangeInput |
|
|
|
variant="primary" |
|
|
|
value={selectedTimeRange} |
|
|
|
aria-label="Add task button" |
|
|
|
onChange={(newTimeRange) => { |
|
|
|
onClick={onAddTaskClick} |
|
|
|
setTimeRange(newTimeRange); |
|
|
|
// TODO: move to function
|
|
|
|
}} |
|
|
|
disabled={!queries?.filter((query: DashboardQuery) => query.selected)?.length} |
|
|
|
/> |
|
|
|
> |
|
|
|
</HorizontalGroup> |
|
|
|
Add Task |
|
|
|
<Table width={width / 2 - 20} height={height - 40} data={queriesDataFrame} /> |
|
|
|
</Button> |
|
|
|
<HorizontalGroup justify="flex-end" spacing="md"> |
|
|
|
</HorizontalGroup> |
|
|
|
<Button |
|
|
|
</VerticalGroup> |
|
|
|
variant="primary" |
|
|
|
</div> |
|
|
|
aria-label="Add task button" |
|
|
|
)} |
|
|
|
onClick={onAddTaskClick} |
|
|
|
</Modal> |
|
|
|
// TODO: move to function
|
|
|
|
</HorizontalGroup> |
|
|
|
disabled={!queries?.filter((query: DashboardQuery) => query.selected)?.length} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
Add Task |
|
|
|
|
|
|
|
</Button> |
|
|
|
|
|
|
|
</HorizontalGroup> |
|
|
|
|
|
|
|
</VerticalGroup> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
</Modal> |
|
|
|
|
|
|
|
</HorizontalGroup> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function renderSwitch(panelStatus: PanelStatus): JSX.Element { |
|
|
|
|
|
|
|
switch (panelStatus) { |
|
|
|
|
|
|
|
case PanelStatus.LOADING: |
|
|
|
|
|
|
|
return loadingDiv; |
|
|
|
|
|
|
|
case PanelStatus.DATASOURCE_ERROR: |
|
|
|
|
|
|
|
return datasourceErrorDiv; |
|
|
|
|
|
|
|
case PanelStatus.PERMISSION_ERROR: |
|
|
|
|
|
|
|
return permissionErrorDiv; |
|
|
|
|
|
|
|
case PanelStatus.OK: |
|
|
|
|
|
|
|
return mainDiv; |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
return datasourceErrorDiv; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return <div>{renderSwitch(panelStatus)}</div>; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const getStyles = () => ({ |
|
|
|
const getStyles = () => ({ |
|
|
|