Browse Source

seems to work now

pull/3/head
rozetko 2 years ago
parent
commit
45f5da66a3
  1. 2
      package.json
  2. 83
      src/routes/tasks.ts
  3. 53
      src/services/exporter.ts
  4. 97
      src/types/index.ts
  5. 10
      src/types/target.ts
  6. 8
      yarn.lock

2
package.json

@ -11,7 +11,7 @@
"license": "ISC", "license": "ISC",
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@corpglory/tsdb-kit": "^2.0.1", "@corpglory/tsdb-kit": "^2.0.2",
"axios": "^1.2.1", "axios": "^1.2.1",
"express": "^4.18.2", "express": "^4.18.2",
"fast-csv": "^4.3.6", "fast-csv": "^4.3.6",

83
src/routes/tasks.ts

@ -2,7 +2,7 @@ import { Target } from '../types/target';
import { exporterFactory } from '../services/exporter.factory'; import { exporterFactory } from '../services/exporter.factory';
import { EXPORTED_PATH } from '../config'; import { EXPORTED_PATH } from '../config';
import { Datasource } from '@corpglory/tsdb-kit'; import { Task, TaskTableRowConfig } from '../types';
import * as express from 'express'; import * as express from 'express';
@ -13,55 +13,76 @@ type TRequest = {
body: { body: {
from: string, from: string,
to: string, to: string,
data: Array<{ username: string,
panelUrl: string, tasks: Task[],
panelTitle: string, url: string,
panelId: number, },
datasourceRequest: Datasource,
datasourceName: string,
target: object,
}>,
user: string,
}
}; };
async function getTasks(req, res) { async function getTasks(req, res) {
res.status(200).send([{ const resp: TaskTableRowConfig[] = [];
timestamp: 12343567, fs.readdir(EXPORTED_PATH, (err, items) => {
user: 'admin', if(err) {
datasource: 'postgres', console.error(err);
rowsCount: 2345, res.status(500).send('Something went wrong');
progress: 100, } else {
status: 'Success', for(let item of items) {
}]); let file = path.parse(item);
if(file.ext !== '.json') {
continue;
}
// TODO: read async
let data = fs.readFileSync(path.join(EXPORTED_PATH, item), 'utf8');
let status = JSON.parse(data);
let requestedUrl = `http://${req.headers.host}`;
let downloadLink = '';
let deleteLink = '';
if(status.status === 'finished') {
downloadLink = `<a class="download-csv" href="${requestedUrl}/static/${file.name}.csv" target="_blank"><i class="fa fa-download"></i></a>`;
}
resp.push({
timestamp: status.time,
user: status.user,
datasourceRef: status.datasourceRef,
rowsCount: status.exportedRows,
progress: status.progress,
status: status.status,
downloadLink,
});
}
res.status(200).send(resp);
}
});
} }
async function addTask(req: TRequest, res) { async function addTask(req: TRequest, res) {
const body = req.body; const body = req.body;
const clientUrl = body.url;
const from = parseInt(body.from); const from = parseInt(body.from);
const to = parseInt(body.to); const to = parseInt(body.to);
const data = body.data; const username = body.username;
const user = body.user; const tasks = body.tasks;
const datasourceUrl = `${new URL(clientUrl).origin}/api/ds/query`;
if(isNaN(from) || isNaN(to)) { if(isNaN(from) || isNaN(to)) {
res.status(400).send('Range error: please fill both "from" and "to" fields'); res.status(400).send('Range error: please fill both "from" and "to" fields');
} else if(from >= to) { } else if(from >= to) {
res.status(400).send('Range error: "from" should be less than "to"'); res.status(400).send('Range error: "from" should be less than "to"');
} else { } else {
const names = data.map(item => item.datasourceName).join(', '); const names = tasks.map(item => item.datasource.name).join(', ');
res.status(200).send(`Exporting ${names} data from ${new Date(from).toLocaleString()} to ${new Date(to).toLocaleString()}`); res.status(200).send(`Exporting ${names} data from ${new Date(from).toLocaleString()} to ${new Date(to).toLocaleString()}`);
const targets = data.map(item => new Target( const targets = tasks.map((task: Task) => new Target(
item.panelUrl, task.panel,
item.panelTitle, task.datasource,
item.panelId,
item.datasourceRequest,
[item.target],
item.datasourceName,
)); ));
const exporter = exporterFactory.getExporter(); const exporter = exporterFactory.getExporter();
exporter.export(targets, user, from, to); exporter.export(targets, datasourceUrl, username, from, to);
} }
} }

53
src/services/exporter.ts

@ -3,6 +3,7 @@ import { URL } from 'url';
import { apiKeys } from '../config'; import { apiKeys } from '../config';
import { promisify } from '../utils'; import { promisify } from '../utils';
import { ExportStatus } from '../types/export-status'; import { ExportStatus } from '../types/export-status';
import { DataSourceRef } from '../types';
import { QueryConfig, queryByConfig } from '@corpglory/tsdb-kit'; import { QueryConfig, queryByConfig } from '@corpglory/tsdb-kit';
// TODO: export QueryType directly from @corpglory/tsdb-kit // TODO: export QueryType directly from @corpglory/tsdb-kit
@ -21,11 +22,12 @@ export class Exporter {
private exportedRows = 0; private exportedRows = 0;
private createdTimestamp: number; private createdTimestamp: number;
private user: string; private user: string;
private datasource: string; private datasourceRef: DataSourceRef;
private initCsvStream() { private initCsvStream() {
// @ts-ignore const csvStream = csv.parse({ headers: true })
const csvStream = csv.createWriteStream({ headers: true }); .on('error', error => console.error(error));
const writableStream = fs.createWriteStream(this.getFilePath('csv')); const writableStream = fs.createWriteStream(this.getFilePath('csv'));
csvStream.pipe(writableStream); csvStream.pipe(writableStream);
@ -46,7 +48,7 @@ export class Exporter {
exportedRows: this.exportedRows, exportedRows: this.exportedRows,
progress: progress.toLocaleString('en', { style: 'percent' }), progress: progress.toLocaleString('en', { style: 'percent' }),
status, status,
datasourceName: this.datasource, datasourceRef: this.datasourceRef,
}; };
await promisify(fs.writeFile, this.getFilePath('json'), JSON.stringify(data), 'utf8') await promisify(fs.writeFile, this.getFilePath('json'), JSON.stringify(data), 'utf8')
@ -56,16 +58,31 @@ export class Exporter {
} }
} }
public async export(data: Target[], user: string, from: number, to: number) { public async export(data: Target[], datasourceUrl: string, user: string, from: number, to: number) {
this.user = user; this.user = user;
this.validateTargets(data); this.validateTargets(datasourceUrl, data);
const targets = data.map(target => ({
...target, // console.log('ds', data[0].datasource)
metric: new QueryConfig(QueryType.GRAFANA, target.datasource, target.targets) const targets = data.map(target => {
})); console.log({
...target.datasource,
url: datasourceUrl
})
return {
...target,
metric: new QueryConfig(
QueryType.GRAFANA,
{
...target.datasource,
url: datasourceUrl
},
target.panel.targets
)
}
});
this.datasource = data.length === 1 ? data[0].datasourceName : 'all'; this.datasourceRef = data.length === 1 ? data[0].datasource : { uid: 'all', type: 'all' };
const stream = this.initCsvStream(); const stream = this.initCsvStream();
const days = Math.ceil((to - from) / MS_IN_DAY); const days = Math.ceil((to - from) / MS_IN_DAY);
@ -81,13 +98,13 @@ export class Exporter {
const values = {}; const values = {};
for(const [index, target] of targets.entries()) { for(const [index, target] of targets.entries()) {
const host = new URL(target.panelUrl).origin; const host = new URL(datasourceUrl).origin;
const apiKey = apiKeys[host]; const apiKey = apiKeys[host];
const datasourceMetrics = await queryByConfig(target.metric, target.panelUrl, from, to, apiKey); const datasourceMetrics = await queryByConfig(target.metric, datasourceUrl, from, to, apiKey);
const column = `${target.panelId}` + const column = `${target.panel.id}` +
`-${target.panelTitle.replace(' ', '-')}-${datasourceMetrics.columns[1]}`; `-${target.panel.title.replace(' ', '-')}-${datasourceMetrics.columns[1]}`;
columns.push(column); columns.push(column);
@ -120,13 +137,13 @@ export class Exporter {
stream.end(); stream.end();
} }
private validateTargets(targets: Target[]) { private validateTargets(datasourceUrl, targets: Target[]) {
if(!targets || !Array.isArray(targets)) { if(!targets || !Array.isArray(targets)) {
throw new Error('Incorrect targets format'); throw new Error('Incorrect targets format');
} }
for(const target of targets) { for(const target of targets) {
const host = new URL(target.panelUrl).origin; const host = new URL(datasourceUrl).origin;
const apiKey = apiKeys[host]; const apiKey = apiKeys[host];
if(apiKey === undefined || apiKey === '') { if(apiKey === undefined || apiKey === '') {
@ -156,7 +173,7 @@ export class Exporter {
if(this.createdTimestamp === undefined) { if(this.createdTimestamp === undefined) {
this.createdTimestamp = moment().valueOf(); this.createdTimestamp = moment().valueOf();
} }
return `${this.createdTimestamp}.${this.datasource}.${extension}`; return `${this.createdTimestamp}.${this.datasourceRef.uid}.${extension}`;
} }
private getFilePath(extension) { private getFilePath(extension) {

97
src/types/index.ts

@ -0,0 +1,97 @@
import { DatasourceType } from '@corpglory/tsdb-kit';
export interface DataSourceRef {
/** The plugin type-id */
type?: string;
/** Specific datasource instance */
uid?: string;
}
export interface DataQuery {
/**
* A - Z
*/
refId: string;
/**
* true if query is disabled (ie should not be returned to the dashboard)
*/
hide?: boolean;
/**
* Unique, guid like, string used in explore mode
*/
key?: string;
/**
* Specify the query flavor
*/
queryType?: string;
/**
* For mixed data sources the selected datasource is on the query level.
* For non mixed scenarios this is undefined.
*/
datasource?: DataSourceRef | null;
}
/**
* Data Source instance edit model. This is returned from:
* /api/datasources
*/
export interface DataSourceSettings {
id: number;
uid: string;
orgId: number;
name: string;
typeLogoUrl: string;
type: DatasourceType;
typeName: string;
access: string;
url: string;
user: string;
database: string;
basicAuth: boolean;
basicAuthUser: string;
isDefault: boolean;
jsonData: any;
secureJsonData?: any;
secureJsonFields: any;
readOnly: boolean;
withCredentials: boolean;
version?: number;
accessControl?: any;
}
export interface PanelModel {
/** ID of the panel within the current dashboard */
id: number;
/** Panel title */
title?: string;
/** Description */
description?: string;
/** Panel options */
options: any;
/** Field options configuration */
fieldConfig: any;
/** Version of the panel plugin */
pluginVersion?: string;
/** The datasource used in all targets */
datasource?: DataSourceRef | null;
/** The queries in a panel */
targets?: DataQuery[];
/** alerting v1 object */
alert?: any;
}
export type Task = DataQuery & {
selected: boolean;
panel: PanelModel;
datasource: DataSourceSettings;
};
export type TaskTableRowConfig = {
timestamp: number;
user: string;
datasourceRef: DataSourceRef;
rowsCount: number;
progress: number;
status: string;
downloadLink: string;
};

10
src/types/target.ts

@ -1,12 +1,8 @@
import { Datasource } from '@corpglory/tsdb-kit'; import { DataSourceSettings, PanelModel } from '.';
export class Target { export class Target {
constructor( constructor(
public panelUrl: string, public panel: PanelModel,
public panelTitle: string, public datasource: DataSourceSettings,
public panelId: number,
public datasource: Datasource,
public targets: Array<object>,
public datasourceName: string,
) {} ) {}
} }

8
yarn.lock

@ -2,10 +2,10 @@
# yarn lockfile v1 # yarn lockfile v1
"@corpglory/tsdb-kit@^2.0.1": "@corpglory/tsdb-kit@^2.0.2":
version "2.0.1" version "2.0.2"
resolved "https://registry.yarnpkg.com/@corpglory/tsdb-kit/-/tsdb-kit-2.0.1.tgz#13435a69b2bb8c9890b838a06038daf76e96ec39" resolved "https://registry.yarnpkg.com/@corpglory/tsdb-kit/-/tsdb-kit-2.0.2.tgz#0bbba2b344b651e374a1f89967abee7ef3172f30"
integrity sha512-qIoCy0DXjPFkAE/G9URNVJ56vfU9SPyPoNR2wJSRut6vA6eLP8kljFf4GQE/7/yFpvulHI9+RT9UrlhImWbEZA== integrity sha512-USXpz9kXcHavJ0kZAuf64wE+zhmHfI9EKplyXtAAEL9FYZ3YErR2Awz7mAma6juKrkDELMtW/V0tx1e//0aRaw==
dependencies: dependencies:
axios "^0.18.0" axios "^0.18.0"
moment "^2.22.2" moment "^2.22.2"

Loading…
Cancel
Save