Skip to content

Commit

Permalink
Export DataTree to csv through FileDialogService
Browse files Browse the repository at this point in the history
Using Theia Backend to write files instead

Users can choose file name/path in both browser & electron

Show success/error message after

Signed-off-by: hriday-panchasara <hriday.panchasara@ericsson.com>
  • Loading branch information
hriday-panchasara committed Mar 17, 2023
1 parent 84f8d2f commit b7cc4f1
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 15 deletions.
7 changes: 6 additions & 1 deletion packages/base/src/signals/signal-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export declare interface SignalManager {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fireUnPinView(output: OutputDescriptor, payload?: any): void;
fireOverviewOutputSelectedSignal(payload: { traceId: string, outputDescriptor: OutputDescriptor}): void;
fireSaveDatatreeAsCsv(payload: {traceId: string, data: string}): void;
}

export const Signals = {
Expand Down Expand Up @@ -60,7 +61,8 @@ export const Signals = {
PIN_VIEW: 'view pinned',
UNPIN_VIEW: 'view unpinned',
OPEN_OVERVIEW_OUTPUT: 'open overview output',
OVERVIEW_OUTPUT_SELECTED: 'overview output selected'
OVERVIEW_OUTPUT_SELECTED: 'overview output selected',
SAVE_DATATREE_CSV: 'save datatree as csv'
};

export class SignalManager extends EventEmitter implements SignalManager {
Expand Down Expand Up @@ -141,6 +143,9 @@ export class SignalManager extends EventEmitter implements SignalManager {
fireOverviewOutputSelectedSignal(payload: { traceId: string, outputDescriptor: OutputDescriptor}): void {
this.emit(Signals.OVERVIEW_OUTPUT_SELECTED, payload);
}
fireSaveDatatreeAsCsv(payload: {traceId: string, data: string}): void {
this.emit(Signals.SAVE_DATATREE_CSV, payload);
}
}

let instance: SignalManager = new SignalManager();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getAllExpandedNodeIds } from './utils/filter-tree/utils';
import { TreeNode } from './utils/filter-tree/tree-node';
import ColumnHeader from './utils/filter-tree/column-header';
import debounce from 'lodash.debounce';
import { signalManager } from 'traceviewer-base/lib/signals/signal-manager';

type DataTreeOutputProps = AbstractOutputProps & {
};
Expand Down Expand Up @@ -215,17 +216,10 @@ export class DataTreeOutputComponent extends AbstractOutputComponent<AbstractOut
csvArray.push(row.join(','));
}
const tableString = csvArray.join('\n');

const link = document.createElement('a');
link.setAttribute('href', `data:text/csv;charset=utf-8,${encodeURIComponent(tableString)}`);
link.setAttribute('download', (this.props.traceName ?? 'export') + ' - ' + this.props.outputDescriptor.name + '.csv');

link.style.display = 'none';
document.body.appendChild(link);

link.click();

document.body.removeChild(link);
signalManager().fireSaveDatatreeAsCsv({
traceId: this.props.traceId,
data: tableString
});
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Disposable, DisposableCollection, MessageService, Path } from '@theia/core';
import { Disposable, DisposableCollection, MessageService, Path, URI } from '@theia/core';
import { ApplicationShell, Message, StatusBar, WidgetManager, StatefulWidget } from '@theia/core/lib/browser';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { inject, injectable, postConstruct } from 'inversify';
Expand All @@ -19,6 +19,7 @@ import { TraceExplorerContribution } from '../trace-explorer/trace-explorer-cont
import { MarkerSet } from 'tsp-typescript-client/lib/models/markerset';
import { BackendFileService } from '../../common/backend-file-service';
import { CancellationTokenSource } from '@theia/core';
import { FileDialogService, SaveFileDialogProps } from '@theia/filesystem/lib/browser';
import * as React from 'react';
import 'animate.css';
import { DEFAULT_OVERVIEW_OUTPUT_NAME, TRACE_OVERVIEW_DEFAULT_VIEW_KEY,
Expand Down Expand Up @@ -70,6 +71,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget {
private onExperimentSelected = (experiment: Experiment): Promise<void> => this.doHandleExperimentSelectedSignal(experiment);
private onCloseExperiment = (UUID: string): void => this.doHandleCloseExperimentSignal(UUID);
private onMarkerCategoryClosedSignal = (payload: { traceViewerId: string, markerCategory: string }) => this.doHandleMarkerCategoryClosedSignal(payload);
private onSaveDatatreeCSV= (payload: {traceId: string, data: string}): Promise<void> => this.doHandleSaveDatatreeCSVSignal(payload);

private overviewOutputDescriptor: OutputDescriptor | undefined;
private prevOverviewOutputDescriptor: OutputDescriptor | undefined;
Expand All @@ -85,6 +87,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget {
@inject(WidgetManager) protected readonly widgetManager!: WidgetManager;
@inject(ThemeService) protected readonly themeService: ThemeService;
@inject(OverviewPreferences) protected overviewPreferences: OverviewPreferences;
@inject(FileDialogService) protected readonly fileDialogService: FileDialogService;

@postConstruct()
async init(): Promise<void> {
Expand Down Expand Up @@ -146,6 +149,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget {
signalManager().on(Signals.MARKER_CATEGORY_CLOSED, this.onMarkerCategoryClosedSignal);
signalManager().on(Signals.OPEN_OVERVIEW_OUTPUT, this.onTraceOverviewOpened);
signalManager().on(Signals.OVERVIEW_OUTPUT_SELECTED, this.onTraceOverviewOutputSelected);
signalManager().on(Signals.SAVE_DATATREE_CSV, this.onSaveDatatreeCSV);
}

protected updateBackgroundTheme(): void {
Expand All @@ -160,6 +164,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget {
signalManager().off(Signals.CLOSE_TRACEVIEWERTAB, this.onCloseExperiment);
signalManager().off(Signals.OPEN_OVERVIEW_OUTPUT, this.onTraceOverviewOpened);
signalManager().off(Signals.OVERVIEW_OUTPUT_SELECTED, this.onTraceOverviewOutputSelected);
signalManager().off(Signals.SAVE_DATATREE_CSV, this.onSaveDatatreeCSV);
}

async initialize(): Promise<void> {
Expand Down Expand Up @@ -418,6 +423,28 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget {
}
}

private async doHandleSaveDatatreeCSVSignal(payload: {traceId: string, data: string}) {
if (this.openedExperiment && payload && payload.traceId === this.openedExperiment.UUID) {
const props: SaveFileDialogProps = {
inputValue: (this.openedExperiment !== undefined ? (this.openedExperiment.name) : 'trace')+'.csv',
filters: {
'CSV Files': ['csv']
},
title: 'Save Statistics as CSV'
};

const uri: URI | undefined = await this.fileDialogService.showSaveDialog(props);
if (uri) {
const resolve = await this.backendFileService.writeToFile(uri, payload.data);
if (resolve === 'success') {
this.messageService.info('CSV saved successfully');
} else {
this.messageService.error(`Failed to save trace CSV: ${resolve}`);
}
}
}
}

private doHandleMarkerCategoryClosedSignal(payload: { traceViewerId: string, markerCategory: string }) {
const traceViewerId = payload.traceViewerId;
const markerCategory = payload.markerCategory;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { CancellationToken } from '@theia/core';
import { CancellationToken, URI } from '@theia/core';

export const backendFileServicePath = '/services/theia-trace-extension/backend-service';
export const BackendFileService = Symbol('BackendFileService');

export interface BackendFileService {
findTraces(uri: string, cancellationToken: CancellationToken): Promise<string[]>;
writeToFile(uri: URI, data: string): Promise<string>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@ import { injectable } from 'inversify';
import fs = require('fs');
import { Dirent } from 'fs';
import { Path } from '@theia/core/lib/common/path';
import { CancellationToken } from '@theia/core';
import { CancellationToken, URI } from '@theia/core';
import { BackendFileService } from '../common/backend-file-service';
import { FileUri } from '@theia/core/lib/node';

@injectable()
export class BackendFileServiceImpl implements BackendFileService {

async writeToFile(uri: URI, data: string): Promise<string> {
fs.writeFile(uri.path.fsPath(), data, err => {
if (err) {
throw new Error(err.message);
}
});
return 'success';
}

async findTraces(path: string, cancellationToken: CancellationToken): Promise<string[]> {
/*
* On Windows, Theia returns a path that starts with "/" (e.g "/c:/"), causing fsPromise.stat
Expand Down

0 comments on commit b7cc4f1

Please sign in to comment.