diff --git a/README.md b/README.md index 7910e5b8b..d328c4a63 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,3 @@ Now, you will see 3 sections: **Opened experiments**, **Available analysis** and ### Navigation and zooming There is only a limited number of such operations and they are only implemented in the Time Graph views (the ones looking like Gantt charts). For Zoom-in/out use CTRL+mouse wheel. Or use left mouse drag on time axis on top. Navigating the trace you can use the scrollbar at the bottom of the experiment container. - -### Time Graph Tooltip -Currently, the **Time Graph Tooltip** is populated when selecting a state in a Time Graph view. - diff --git a/browser-app/package.json b/browser-app/package.json index af8e33ef4..b3b2b3440 100644 --- a/browser-app/package.json +++ b/browser-app/package.json @@ -29,7 +29,7 @@ "@theia/vsx-registry": "latest", "@theia/keymaps": "latest", "@theia/getting-started": "latest", - "trace-viewer": "0.0.0" + "theia-trace-viewer": "0.0.0" }, "devDependencies": { "@theia/cli": "latest" diff --git a/electron-app/package.json b/electron-app/package.json index ade5d5516..cc4b70041 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -38,7 +38,7 @@ "@theia/keymaps": "latest", "@theia/getting-started": "latest", "@theia/electron": "latest", - "trace-viewer": "0.0.0" + "theia-trace-viewer": "0.0.0" }, "devDependencies": { "@theia/cli": "latest", diff --git a/package.json b/package.json index 53fcf680a..1780b96f6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,9 @@ { "private": true, "scripts": { + "reinstall": "rimraf ./**/node_modules && yarn install", "prepare": "lerna run prepare", + "build": "lerna run build", "rebuild:browser": "theia rebuild:browser", "rebuild:electron": "theia rebuild:electron", "start:browser": "yarn rebuild:browser ; yarn --cwd browser-app start", @@ -11,13 +13,16 @@ "start:server": "./trace-compass-server/tracecompass-server", "start-all:browser": "yarn start:server & yarn start:browser", "start-all:electron": "yarn start:server & yarn start:electron", - "lint": "yarn workspace trace-viewer run lint", - "test": "echo test" + "lint": "lerna run lint", + "test": "lerna run test" }, "devDependencies": { - "lerna": "2.4.0" + "lerna": "2.4.0", + "rimraf": "^3.0.2" }, "workspaces": [ + "packages/base", + "packages/react-components", "viewer-prototype", "browser-app", "electron-app" diff --git a/packages/base/.eslintrc.js b/packages/base/.eslintrc.js new file mode 100644 index 000000000..a6115b869 --- /dev/null +++ b/packages/base/.eslintrc.js @@ -0,0 +1,26 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + parser: "@typescript-eslint/parser", // Specifies the ESLint parser + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: "module", // Allows for the use of imports + tsconfigRootDir: __dirname, + project: 'tsconfig.json', + projectFolderIgnoreList: [ + '/lib/' + ] + }, + extends: [ + 'plugin:@typescript-eslint/recommended', + '../../configs/base.eslintrc.json', + '../../configs/warnings.eslintrc.json', + '../../configs/errors.eslintrc.json' + ], + ignorePatterns: [ + 'node_modules', + 'lib', + '.eslintrc.js', + 'plugins' + ] +}; diff --git a/packages/base/package.json b/packages/base/package.json new file mode 100644 index 000000000..b9699ac02 --- /dev/null +++ b/packages/base/package.json @@ -0,0 +1,34 @@ +{ + "name": "@trace-viewer/base", + "version": "0.0.0", + "description": "Trace Compass base package, contains trace management utilities", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/theia-ide/theia-trace-extension" + }, + "files": [ + "lib", + "src" + ], + "dependencies": { + "tsp-typescript-client": "next" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^3.4.0", + "@typescript-eslint/parser": "^3.4.0", + "eslint": "^7.3.0", + "eslint-plugin-import": "^2.21.2", + "eslint-plugin-no-null": "^1.0.2", + "eslint-plugin-react": "^7.20.0", + "rimraf": "latest", + "typescript": "latest" + }, + "scripts": { + "prepare": "yarn clean && yarn build", + "clean": "rimraf lib", + "build": "tsc", + "watch": "tsc -w", + "lint": "eslint ." + } +} diff --git a/viewer-prototype/src/common/experiment-manager.ts b/packages/base/src/experiment-manager.ts similarity index 81% rename from viewer-prototype/src/common/experiment-manager.ts rename to packages/base/src/experiment-manager.ts index aaf5eacb0..3260d37a9 100644 --- a/viewer-prototype/src/common/experiment-manager.ts +++ b/packages/base/src/experiment-manager.ts @@ -1,27 +1,21 @@ import { Trace } from 'tsp-typescript-client/lib/models/trace'; -import { Emitter } from '@theia/core'; import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { Query } from 'tsp-typescript-client/lib/models/query/query'; -import { injectable, inject } from 'inversify'; import { OutputDescriptor } from 'tsp-typescript-client/lib/models/output-descriptor'; import { Experiment } from 'tsp-typescript-client/lib/models/experiment'; import { TspClientResponse } from 'tsp-typescript-client/lib/protocol/tsp-client-response'; +import { signalManager, Signals } from './signal-manager'; -@injectable() export class ExperimentManager { - // Open signal - private experimentOpenedEmitter = new Emitter(); - public experimentOpenedSignal = this.experimentOpenedEmitter.event; - - // Close signal - private experimentClosedEmitter = new Emitter(); - public experimentClosedSignal = this.experimentClosedEmitter.event; private fOpenExperiments: Map = new Map(); + private fTspClient: TspClient; - private constructor( - @inject(TspClient) private tspClient: TspClient - ) { } + constructor( + tspClient: TspClient + ) { + this.fTspClient = tspClient; + } /** * Get an array of opened experiments @@ -30,7 +24,7 @@ export class ExperimentManager { async getOpenedExperiments(): Promise { const openedExperiments: Array = []; // Look on the server for opened experiments - const experimentResponse = await this.tspClient.fetchExperiments(); + const experimentResponse = await this.fTspClient.fetchExperiments(); if (experimentResponse.isOk()) { openedExperiments.push(...experimentResponse.getModel()); } @@ -47,7 +41,7 @@ export class ExperimentManager { // If the experiment is undefined, check on the server if (!experiment) { - const experimentResponse = await this.tspClient.fetchExperiment(experimentUUID); + const experimentResponse = await this.fTspClient.fetchExperiment(experimentUUID); if (experimentResponse.isOk()) { experiment = experimentResponse.getModel(); } @@ -63,7 +57,7 @@ export class ExperimentManager { // Check if the experiment is opened const experiment = this.fOpenExperiments.get(experimentUUID); if (experiment) { - const outputsResponse = await this.tspClient.experimentOutputs(experiment.UUID); + const outputsResponse = await this.fTspClient.experimentOutputs(experiment.UUID); return outputsResponse.getModel(); } return undefined; @@ -83,14 +77,14 @@ export class ExperimentManager { traceURIs.push(traces[i].UUID); } - const experimentResponse = await this.tspClient.createExperiment(new Query({ + const experimentResponse = await this.fTspClient.createExperiment(new Query({ 'name': name, 'traces': traceURIs })); const opendExperiment = experimentResponse.getModel(); if (opendExperiment && experimentResponse.isOk()) { this.addExperiment(opendExperiment); - this.experimentOpenedEmitter.fire(opendExperiment); + signalManager().emit(Signals.EXPERIMENT_OPENED, {experiment: opendExperiment}); return opendExperiment; } else if (opendExperiment && experimentResponse.getStatusCode() === 409) { // Repost with a suffix as long as there are conflicts @@ -104,13 +98,13 @@ export class ExperimentManager { let conflictResolutionResponse = experimentResponse; let i = 1; while (conflictResolutionResponse.getStatusCode() === 409) { - conflictResolutionResponse = await handleConflict(this.tspClient, i); + conflictResolutionResponse = await handleConflict(this.fTspClient, i); i++; } const experiment = conflictResolutionResponse.getModel(); if (experiment && conflictResolutionResponse.isOk()) { this.addExperiment(experiment); - this.experimentOpenedEmitter.fire(experiment); + signalManager().emit(Signals.EXPERIMENT_OPENED, {experiment: experiment}); return experiment; } } @@ -126,7 +120,7 @@ export class ExperimentManager { async updateExperiment(experimentUUID: string): Promise { const currentExperiment = this.fOpenExperiments.get(experimentUUID); if (currentExperiment) { - const experimentResponse = await this.tspClient.fetchExperiment(currentExperiment.UUID); + const experimentResponse = await this.fTspClient.fetchExperiment(currentExperiment.UUID); const experiment = experimentResponse.getModel(); if (experiment && experimentResponse.isOk) { this.fOpenExperiments.set(experimentUUID, experiment); @@ -144,10 +138,10 @@ export class ExperimentManager { async closeExperiment(experimentUUID: string): Promise { const experimentToClose = this.fOpenExperiments.get(experimentUUID); if (experimentToClose) { - await this.tspClient.deleteExperiment(experimentUUID); + await this.fTspClient.deleteExperiment(experimentUUID); const deletedExperiment = this.removeExperiment(experimentUUID); if (deletedExperiment) { - this.experimentClosedEmitter.fire(deletedExperiment); + signalManager().emit(Signals.EXPERIMENT_CLOSED, {experiment: deletedExperiment}); } } } diff --git a/packages/base/src/message-manager.ts b/packages/base/src/message-manager.ts new file mode 100644 index 000000000..7778b013b --- /dev/null +++ b/packages/base/src/message-manager.ts @@ -0,0 +1,39 @@ +export enum MessageCategory { + TRACE_CONTEXT, + SERVER_MESSAGE, + SERVER_STATUS, +} + +export enum MessageSeverity { + ERROR, + WARNING, + INFO, + DEBUG +} + +export interface StatusMessage { + text: string; + category?: MessageCategory; + severity?: MessageSeverity; +} + +export declare interface MessageManager { + + addStatusMessage(messageKey: string, message: StatusMessage): void; + removeStatusMessage(messageKey: string): void; + +} + +export class MessageManager implements MessageManager { + + addStatusMessage(messageKey: string, {text, + category = MessageCategory.SERVER_MESSAGE, + severity = MessageSeverity.INFO }: StatusMessage): void { + console.log('New status message', messageKey, text, category, severity); + } + + removeStatusMessage(messageKey: string): void { + console.log('Removing status message status message', messageKey); + } + +} diff --git a/packages/base/src/signal-manager.ts b/packages/base/src/signal-manager.ts new file mode 100644 index 000000000..d4dc1a38c --- /dev/null +++ b/packages/base/src/signal-manager.ts @@ -0,0 +1,30 @@ +import { EventEmitter } from 'events'; + +export declare interface SignalManager { + + fireTooltipSignal(payload: { [key: string]: string }): void; + +} + +export const Signals = { + TRACE_OPENED : 'trace opened', + TRACE_CLOSED : 'trace closed', + EXPERIMENT_OPENED: 'experiment opened', + EXPERIMENT_CLOSED: 'experiment closed' +}; + +export class SignalManager extends EventEmitter implements SignalManager { + + fireTooltipSignal(payload: { [key: string]: string; }): void { + /* To be implemented by extending clases */ + } + +} + +let instance: SignalManager = new SignalManager(); + +export const setSignalManagerInstance = (sm: SignalManager) => { + instance = sm; +}; + +export const signalManager = (): SignalManager => instance; diff --git a/viewer-prototype/src/common/trace-manager.ts b/packages/base/src/trace-manager.ts similarity index 75% rename from viewer-prototype/src/common/trace-manager.ts rename to packages/base/src/trace-manager.ts index 714c28a89..9f23ecf5b 100644 --- a/viewer-prototype/src/common/trace-manager.ts +++ b/packages/base/src/trace-manager.ts @@ -1,26 +1,21 @@ + import { Trace } from 'tsp-typescript-client/lib/models/trace'; -import { Path, Emitter } from '@theia/core'; import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { Query } from 'tsp-typescript-client/lib/models/query/query'; -import { injectable, inject } from 'inversify'; import { OutputDescriptor } from 'tsp-typescript-client/lib/models/output-descriptor'; import { TspClientResponse } from 'tsp-typescript-client/lib/protocol/tsp-client-response'; +import { signalManager, Signals } from './signal-manager'; -@injectable() export class TraceManager { - // Open signal - private traceOpenedEmitter = new Emitter(); - public traceOpenedSignal = this.traceOpenedEmitter.event; - - // Close signal - private traceClosedEmitter = new Emitter(); - public traceClosedSignal = this.traceClosedEmitter.event; private fOpenTraces: Map = new Map(); + private fTspClient: TspClient; - private constructor( - @inject(TspClient) private tspClient: TspClient - ) { } + constructor( + tspClient: TspClient + ) { + this.fTspClient = tspClient; + } /** * Get an array of opened traces @@ -29,7 +24,7 @@ export class TraceManager { async getOpenedTraces(): Promise { const openedTraces: Array = []; // Look on the server for opened trace - const tracesResponse = await this.tspClient.fetchTraces(); + const tracesResponse = await this.fTspClient.fetchTraces(); if (tracesResponse.isOk()) { openedTraces.push(...tracesResponse.getModel()); } @@ -46,7 +41,7 @@ export class TraceManager { // If the trace is undefined, check on the server if (!trace) { - const traceResponse = await this.tspClient.fetchTrace(traceUUID); + const traceResponse = await this.fTspClient.fetchTrace(traceUUID); if (traceResponse.isOk()) { trace = traceResponse.getModel(); } @@ -62,7 +57,7 @@ export class TraceManager { // Check if the trace is opened const trace = this.fOpenTraces.get(traceUUID); if (trace) { - const outputsResponse = await this.tspClient.experimentOutputs(trace.UUID); + const outputsResponse = await this.fTspClient.experimentOutputs(trace.UUID); return outputsResponse.getModel(); } return undefined; @@ -74,21 +69,16 @@ export class TraceManager { * @param traceName Optional name for the trace. If not specified the URI name is used * @returns The opened trace */ - async openTrace(traceURI: Path, traceName?: string): Promise { - let name = traceURI.name; - if (traceName) { - name = traceName; - } - - const tracePath = traceURI.toString(); - const traceResponse = await this.tspClient.openTrace(new Query({ - 'name': name, - 'uri': tracePath + async openTrace(traceURI: string, traceName?: string): Promise { + const traceResponse = await this.fTspClient.openTrace(new Query({ + 'name': traceName, + 'uri': traceURI })); + const openedTrace = traceResponse.getModel(); if (openedTrace && traceResponse.isOk()) { this.addTrace(openedTrace); - this.traceOpenedEmitter.fire(openedTrace); + signalManager().emit(Signals.TRACE_OPENED, {trace: openedTrace}); return openedTrace; } else if (openedTrace && traceResponse.getStatusCode() === 409) { // Repost with a suffix as long as there are conflicts @@ -96,19 +86,19 @@ export class TraceManager { const suffix = '(' + tryNb + ')'; return tspClient.openTrace(new Query({ 'name': name + suffix, - 'uri': tracePath + 'uri': traceURI })); }; let conflictResolutionResponse = traceResponse; let i = 1; while (conflictResolutionResponse.getStatusCode() === 409) { - conflictResolutionResponse = await handleConflict(this.tspClient, i); + conflictResolutionResponse = await handleConflict(this.fTspClient, i); i++; } const trace = conflictResolutionResponse.getModel(); if (trace && conflictResolutionResponse.isOk()) { this.addTrace(trace); - this.traceOpenedEmitter.fire(trace); + signalManager().emit(Signals.TRACE_OPENED, {trace: openedTrace}); return trace; } } @@ -124,7 +114,7 @@ export class TraceManager { async updateTrace(traceUUID: string): Promise { const currentTrace = this.fOpenTraces.get(traceUUID); if (currentTrace) { - const traceResponse = await this.tspClient.fetchTrace(currentTrace.UUID); + const traceResponse = await this.fTspClient.fetchTrace(currentTrace.UUID); const trace = traceResponse.getModel(); if (trace && traceResponse.isOk) { this.fOpenTraces.set(traceUUID, trace); @@ -142,10 +132,10 @@ export class TraceManager { async closeTrace(traceUUID: string): Promise { const traceToClose = this.fOpenTraces.get(traceUUID); if (traceToClose) { - await this.tspClient.deleteTrace(traceUUID); + await this.fTspClient.deleteTrace(traceUUID); const deletedTrace = this.removeTrace(traceUUID); if (deletedTrace) { - this.traceClosedEmitter.fire(deletedTrace); + signalManager().emit(Signals.TRACE_CLOSED, {trace: deletedTrace}); } } } diff --git a/viewer-prototype/src/common/utils/time-range.ts b/packages/base/src/utils/time-range.ts similarity index 100% rename from viewer-prototype/src/common/utils/time-range.ts rename to packages/base/src/utils/time-range.ts diff --git a/viewer-prototype/src/common/utils/value-hash.ts b/packages/base/src/utils/value-hash.ts similarity index 100% rename from viewer-prototype/src/common/utils/value-hash.ts rename to packages/base/src/utils/value-hash.ts diff --git a/packages/base/tsconfig.json b/packages/base/tsconfig.json new file mode 100644 index 000000000..c4adb97b2 --- /dev/null +++ b/packages/base/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "strict": true, + "sourceMap": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "module": "commonjs", + "target": "ES2020", + "outDir": "lib", + "declaration": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/packages/react-components/.eslintrc.js b/packages/react-components/.eslintrc.js new file mode 100644 index 000000000..04794618d --- /dev/null +++ b/packages/react-components/.eslintrc.js @@ -0,0 +1,37 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + parser: "@typescript-eslint/parser", // Specifies the ESLint parser + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: "module", // Allows for the use of imports + ecmaFeatures: { + jsx: true // Allows for the parsing of JSX + } + }, + settings: { + react: { + version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use + } + }, + extends: [ + 'plugin:react/recommended', + 'plugin:@typescript-eslint/recommended', + '../../configs/base.eslintrc.json', + '../../configs/warnings.eslintrc.json', + '../../configs/errors.eslintrc.json' + ], + ignorePatterns: [ + 'node_modules', + 'lib', + '.eslintrc.js', + 'plugins' + ], + parserOptions: { + tsconfigRootDir: __dirname, + project: 'tsconfig.json', + projectFolderIgnoreList: [ + '/lib/' + ] + } +}; diff --git a/packages/react-components/package.json b/packages/react-components/package.json new file mode 100644 index 000000000..92c4a2173 --- /dev/null +++ b/packages/react-components/package.json @@ -0,0 +1,55 @@ +{ + "name": "@trace-viewer/react-components", + "version": "0.0.0", + "description": "Trace Compass react components", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/theia-ide/theia-trace-extension" + }, + "files": [ + "lib", + "src" + ], + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.17", + "@fortawesome/free-solid-svg-icons": "^5.8.1", + "@fortawesome/react-fontawesome": "^0.1.4", + "@trace-viewer/base": "0.0.0", + "ag-grid-community": "^20.2.0", + "ag-grid-react": "^20.2.0", + "chart.js": "^2.8.0", + "lodash": "^4.17.15", + "react-chartjs-2": "^2.7.6", + "react-grid-layout": "^0.16.6", + "react-modal": "^3.8.1", + "react-virtualized": "^9.21.0", + "semantic-ui-css": "^2.4.1", + "semantic-ui-react": "^0.86.0", + "timeline-chart": "next", + "tsp-typescript-client": "next" + }, + "devDependencies": { + "@types/chart.js": "^2.7.52", + "@types/lodash": "^4.14.142", + "@types/react-grid-layout": "^0.16.7", + "@types/react-modal": "^3.8.2", + "@types/react-virtualized": "^9.21.1", + "@typescript-eslint/eslint-plugin": "^3.4.0", + "@typescript-eslint/parser": "^3.4.0", + "eslint": "^7.3.0", + "eslint-plugin-import": "^2.21.2", + "eslint-plugin-no-null": "^1.0.2", + "eslint-plugin-react": "^7.20.0", + "rimraf": "latest", + "typescript": "latest" + }, + "scripts": { + "prepare": "yarn clean && yarn build", + "clean": "rimraf lib", + "build": "tsc && yarn run assets", + "watch": "tsc -w", + "lint": "eslint .", + "assets": "mkdir -p ./lib/style && cp -r ./src/style/* ./lib/style" + } +} diff --git a/viewer-prototype/src/browser/trace-viewer/components/abstract-output-component.tsx b/packages/react-components/src/components/abstract-output-component.tsx similarity index 95% rename from viewer-prototype/src/browser/trace-viewer/components/abstract-output-component.tsx rename to packages/react-components/src/components/abstract-output-component.tsx index 5e21992b4..5286a8055 100644 --- a/viewer-prototype/src/browser/trace-viewer/components/abstract-output-component.tsx +++ b/packages/react-components/src/components/abstract-output-component.tsx @@ -4,7 +4,7 @@ import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { TimeGraphUnitController } from 'timeline-chart/lib/time-graph-unit-controller'; -import { TimeRange } from '../../../common/utils/time-range'; +import { TimeRange } from '@trace-viewer/base/lib/utils/time-range'; import { OutputComponentStyle } from './utils/output-component-style'; import { OutputStyleModel } from 'tsp-typescript-client/lib/models/styles'; @@ -46,10 +46,11 @@ export abstract class AbstractOutputComponent

; } - renderTitleBar(): React.ReactNode { + private renderTitleBar(): React.ReactNode { const outputName = this.props.outputDescriptor.name; return