delimiter && download icon && editor permission #15

Merged
vargburz merged 2 commits from selector-with-delimeter into master 2 years ago
  1. 2
      package.json
  2. 6
      src/icons.ts
  3. 150
      src/panels/corpglory-dataexporter-panel/components/Panel.tsx
  4. 1
      src/panels/corpglory-dataexporter-panel/types.ts

2
package.json

@ -1,6 +1,6 @@
{
"name": "corpglory-dataexporter-app",
"version": "1.0.2",
"version": "1.0.3",
"description": "",
"scripts": {
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx --max-warnings=0 ./src",

6
src/icons.ts

@ -6,5 +6,9 @@ export const UNSELECT_ICON_BASE_64 =
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3QgeD0iMC41IiB5PSIwLjUiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgcng9IjEuNSIgZmlsbD0iIzExMTIxNiIgc3Ryb2tlPSIjMkQyRTM0Ii8+Cjwvc3ZnPgo=';
export const SELECT_ICON_BASE_64 =
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiByeD0iMiIgZmlsbD0iIzRBNzFEMiIvPgo8cGF0aCBkPSJNNS45ODI2NCAxMi41MjE3QzUuNzczOTUgMTIuNTIxNyA1LjU4MjY0IDEyLjQ1MjIgNS40MjYxMiAxMi4yOTU2TDEuOTY1MjUgOC44MzQ3NkMxLjY1MjIxIDguNTIxNzIgMS42NTIyMSA4LjAzNDc2IDEuOTY1MjUgNy43MjE3MkMyLjI3ODI5IDcuNDA4NjcgMi43NjUyNSA3LjQwODY3IDMuMDc4MjkgNy43MjE3Mkw2LjAwMDAzIDEwLjYyNjFMMTIuOTM5MiAzLjcwNDMzQzEzLjI1MjIgMy4zOTEyOCAxMy43MzkyIDMuMzkxMjggMTQuMDUyMiAzLjcwNDMzQzE0LjM2NTMgNC4wMTczNyAxNC4zNjUzIDQuNTA0MzMgMTQuMDUyMiA0LjgxNzM3TDYuNTU2NTYgMTIuMjk1NkM2LjM4MjY0IDEyLjQ1MjIgNi4xOTEzNCAxMi41MjE3IDUuOTgyNjQgMTIuNTIxN1YxMi41MjE3WiIgZmlsbD0iI0ZFRkZGRiIvPgo8L3N2Zz4K';
export const DOWNLOADING_ICON_BASE_64 =
export const PRELOADER_ICON_BASE_64 =
'data:image/gif;base64,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';
export const DOWNLOAD_ICON_BASE_64 =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADlSURBVHgB3ZPNDYJAEIVnN96lBA7A2RIsATuQiiwBSrAD7MAjCZBACXqFwPomMRtEwMVwgZeQhfn5mNnMEG1CaZoeiqKwTWJ3JkFCiEtVVU+8+r9iJRlKSrk3iqOFtWJglmXHf3yDwCRJbBxxnudh34cRYluMMbKGcgWNCIlnjEuIJ1JK2WzDWeKb7YHjONEsYBf6kTABY+mWuYX+3Xiex9UFU7D3FllfwLqueQvi/h8ZCtBprDLY703T6A0yWj2ArmSoxedQV4jSSz5xj4pmCi0/NKfrwNz5bdtaNENciOu6N1qNXhzZXHMb9Q+nAAAAAElFTkSuQmCC';
export const DISABLED_DOWNLOAD_ICON_BASE_64 =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAPNJREFUOE9jZKAyYKSyeQxEGXjr1i0DVlbWD4qKig8IOYAoA4NDI/6DDFq7egVB9QQVgAwaNRBvvOAMw9u3bzuoqqoewBaGyHLopmM18Pr16wo1dY33YTGLHCkwdk9Xu6CiouIHogwEKbp161ZCZXXdfGz+62xvSVRRUVmATQ5vssFmKD7DQBbADbx//77Anz9/DGDhBrMd2VBshkFz0QOY9+EGwjRiyw137twJYGBgEMDmTVCYIltElIH40gn9DASlrYqq2v2EShNs8m0tjYHq6uobUCIFxAGF1b9//wRIMZSRkfEBckQSVdqQZAEpiolRCwC5RpEVeMR8nwAAAABJRU5ErkJggg==';

150
src/panels/corpglory-dataexporter-panel/components/Panel.tsx

@ -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,6 +48,8 @@ import {
DataSourceSettings,
TimeRange,
OrgRole,
SelectableValue,
AppEvents,
} from '@grafana/data';
import { RefreshEvent } from '@grafana/runtime';
@ -70,6 +74,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 +90,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,6 +213,7 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
to: timerange[1] * 1000,
},
queries: selectedQueries,
csvDelimiter,
};
// TODO: move this function to API Service
await queryApi('/task', {
@ -300,6 +307,40 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
const dataFrame = toDataFrame({
name: 'A',
fields: [
{
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',
},
links: [
{
targetBlank: false,
title: 'Download',
url: '#',
onClick: (event: DataLinkClickEvent) => onDownloadClick(event),
},
],
},
},
{
name: 'Status Updated At',
type: FieldType.number,
@ -320,11 +361,6 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
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 +382,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}`);
}
@ -369,6 +405,14 @@ export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
filterable: false,
displayMode: 'image',
},
links: [
{
targetBlank: false,
title: 'Download',
url: '#',
onClick: (event: DataLinkClickEvent) => onDeleteClick(event),
},
],
},
},
],
@ -384,55 +428,35 @@ 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.alertError, ['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.alertError, ['Data Exporter', 'Active task can`t be downloaded']);
return;
}
if (task.progress?.status === ExportStatus.ERROR) {
appEvents.emit(AppEvents.alertError, ['Data Exporter', 'Failed task can`t be downloaded']);
return;
}
setTaskIdBeingDownloaded(task.id as string);
await getStaticFile(task?.id);
setTaskIdBeingDownloaded(null);
@ -448,6 +472,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,7 +498,7 @@ 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 = (
@ -495,7 +525,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"

1
src/panels/corpglory-dataexporter-panel/types.ts

@ -40,6 +40,7 @@ export type ExportTask = {
from: number;
to: number;
};
csvDelimiter: string;
progress?: ExportProgress;
id?: string;
};

Loading…
Cancel
Save