mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 01:11:25 +00:00
Add more Telemetry to Data Explorer (#242)
* Add Telemetry to command bar buttons * Count and report # of files/notebooks/directories in myNotebook to telemetry * Add resource tree clicks to Telemetry * Log to Telemetry: opened notebook cell counts by type, kernelspec name * Fix unit test * Move Telemetry processor call in notebook traceNotebookTelemetry action from reducer to epic. Use action to trace other info. * Fix react duplicate key error * Log notebook cell context menu actions * Reformat and cleanup * Move resource tree tracing code out of render(). Only call once when tree is updated * Fix build issues
This commit is contained in:
@@ -32,24 +32,27 @@ import {
|
||||
import { message, JupyterMessage, Channels, createMessage, childOf, ofMessageType } from "@nteract/messaging";
|
||||
import { sessions, kernels } from "rx-jupyter";
|
||||
import { RecordOf } from "immutable";
|
||||
import { AnyAction } from "redux";
|
||||
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import * as CdbActions from "./actions";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action as TelemetryAction } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { CdbAppState } from "./types";
|
||||
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
||||
import * as TextFile from "./contents/file/text-file";
|
||||
import { NotebookUtil } from "../NotebookUtil";
|
||||
import { FileSystemUtil } from "../FileSystemUtil";
|
||||
import * as cdbActions from "../NotebookComponent/actions";
|
||||
import { Areas } from "../../../Common/Constants";
|
||||
|
||||
interface NotebookServiceConfig extends JupyterServerConfig {
|
||||
userPuid?: string;
|
||||
}
|
||||
|
||||
const logToTelemetry = (state: CdbAppState, title: string, error?: string) => {
|
||||
const logFailureToTelemetry = (state: CdbAppState, title: string, error?: string) => {
|
||||
TelemetryProcessor.traceFailure(TelemetryAction.NotebookErrorNotification, {
|
||||
databaseAccountName: state.cdb.databaseAccountName,
|
||||
defaultExperience: state.cdb.defaultExperience,
|
||||
@@ -311,7 +314,7 @@ export const launchWebSocketKernelEpic = (
|
||||
kernelSpecToLaunch = currentKernelspecs.defaultKernelName;
|
||||
const msg = `No kernelspec name specified to launch, using default kernel: ${kernelSpecToLaunch}`;
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
||||
logToTelemetry(state$.value, "Launching alternate kernel", msg);
|
||||
logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
|
||||
} else {
|
||||
return of(
|
||||
actions.launchKernelFailed({
|
||||
@@ -337,7 +340,7 @@ export const launchWebSocketKernelEpic = (
|
||||
msg += ` Using default kernel: ${kernelSpecToLaunch}`;
|
||||
}
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
||||
logToTelemetry(state$.value, "Launching alternate kernel", msg);
|
||||
logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
|
||||
}
|
||||
|
||||
const sessionPayload = {
|
||||
@@ -634,7 +637,7 @@ const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservab
|
||||
const title = "Kernel restart";
|
||||
const msg = "Kernel successfully restarted";
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
||||
logToTelemetry(state$.value, title, msg);
|
||||
logFailureToTelemetry(state$.value, title, msg);
|
||||
break;
|
||||
}
|
||||
case actions.RESTART_KERNEL_FAILED:
|
||||
@@ -645,7 +648,7 @@ const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservab
|
||||
const title = "Save failure";
|
||||
const msg = `Failed to save notebook: ${(action as actions.SaveFailed).payload.error}`;
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||
logToTelemetry(state$.value, title, msg);
|
||||
logFailureToTelemetry(state$.value, title, msg);
|
||||
break;
|
||||
}
|
||||
case actions.FETCH_CONTENT_FAILED: {
|
||||
@@ -654,7 +657,7 @@ const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservab
|
||||
const title = "Fetching content failure";
|
||||
const msg = `Failed to fetch notebook content: ${filepath}, error: ${typedAction.payload.error}`;
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||
logToTelemetry(state$.value, title, msg);
|
||||
logFailureToTelemetry(state$.value, title, msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -679,7 +682,7 @@ const handleKernelConnectionLostEpic = (
|
||||
|
||||
const msg = "Notebook was disconnected from kernel";
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||
logToTelemetry(state, "Error", "Kernel connection error");
|
||||
logFailureToTelemetry(state, "Error", "Kernel connection error");
|
||||
|
||||
const host = selectors.currentHost(state);
|
||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host as RecordOf<JupyterHostRecordProps>);
|
||||
@@ -692,7 +695,7 @@ const handleKernelConnectionLostEpic = (
|
||||
const msg =
|
||||
"Restarted kernel too many times. Please reload the page to enable Data Explorer to restart the kernel automatically.";
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||
logToTelemetry(state, "Kernel restart error", msg);
|
||||
logFailureToTelemetry(state, "Kernel restart error", msg);
|
||||
|
||||
const explorer = window.dataExplorer;
|
||||
if (explorer) {
|
||||
@@ -844,6 +847,105 @@ const closeContentFailedToFetchEpic = (
|
||||
);
|
||||
};
|
||||
|
||||
const traceNotebookTelemetryEpic = (
|
||||
action$: Observable<cdbActions.TraceNotebookTelemetryAction>,
|
||||
state$: StateObservable<CdbAppState>
|
||||
): Observable<{}> => {
|
||||
return action$.pipe(
|
||||
ofType(cdbActions.TRACE_NOTEBOOK_TELEMETRY),
|
||||
mergeMap((action: cdbActions.TraceNotebookTelemetryAction) => {
|
||||
const state = state$.value;
|
||||
|
||||
TelemetryProcessor.trace(action.payload.action, action.payload.actionModifier, {
|
||||
...action.payload.data,
|
||||
databaseAccountName: state.cdb.databaseAccountName,
|
||||
defaultExperience: state.cdb.defaultExperience,
|
||||
dataExplorerArea: Areas.Notebook
|
||||
});
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Log notebook information to telemetry
|
||||
* # raw cells, # markdown cells, # code cells, total
|
||||
* @param action$
|
||||
* @param state$
|
||||
*/
|
||||
const traceNotebookInfoEpic = (
|
||||
action$: Observable<actions.FetchContentFulfilled>,
|
||||
state$: StateObservable<AppState>
|
||||
): Observable<{} | cdbActions.TraceNotebookTelemetryAction> => {
|
||||
return action$.pipe(
|
||||
ofType(actions.FETCH_CONTENT_FULFILLED),
|
||||
mergeMap((action: { payload: any }) => {
|
||||
const state = state$.value;
|
||||
const contentRef = action.payload.contentRef;
|
||||
const model = selectors.model(state, { contentRef });
|
||||
|
||||
// If it's not a notebook, we shouldn't be here
|
||||
if (!model || model.type !== "notebook") {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
const dataToLog = {
|
||||
nbCodeCells: 0,
|
||||
nbRawCells: 0,
|
||||
nbMarkdownCells: 0,
|
||||
nbCells: 0
|
||||
};
|
||||
for (let [id, cell] of selectors.notebook.cellMap(model)) {
|
||||
switch (cell.cell_type) {
|
||||
case "code":
|
||||
dataToLog.nbCodeCells++;
|
||||
break;
|
||||
case "markdown":
|
||||
dataToLog.nbMarkdownCells++;
|
||||
break;
|
||||
case "raw":
|
||||
dataToLog.nbRawCells++;
|
||||
break;
|
||||
}
|
||||
dataToLog.nbCells++;
|
||||
}
|
||||
|
||||
return of(
|
||||
cdbActions.traceNotebookTelemetry({
|
||||
action: TelemetryAction.NotebooksFetched,
|
||||
actionModifier: ActionModifiers.Mark,
|
||||
data: dataToLog
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Log Kernel spec to start
|
||||
* @param action$
|
||||
* @param state$
|
||||
*/
|
||||
const traceNotebookKernelEpic = (
|
||||
action$: Observable<AnyAction>,
|
||||
state$: StateObservable<AppState>
|
||||
): Observable<cdbActions.TraceNotebookTelemetryAction> => {
|
||||
return action$.pipe(
|
||||
ofType(actions.LAUNCH_KERNEL_SUCCESSFUL),
|
||||
mergeMap((action: { payload: any; type: string }) => {
|
||||
return of(
|
||||
cdbActions.traceNotebookTelemetry({
|
||||
action: TelemetryAction.NotebooksKernelSpecName,
|
||||
actionModifier: ActionModifiers.Mark,
|
||||
data: {
|
||||
kernelSpecName: action.payload.kernel.name
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const allEpics = [
|
||||
addInitialCodeCellEpic,
|
||||
focusInitialCodeCellEpic,
|
||||
@@ -856,5 +958,8 @@ export const allEpics = [
|
||||
executeFocusedCellAndFocusNextEpic,
|
||||
closeUnsupportedMimetypesEpic,
|
||||
closeContentFailedToFetchEpic,
|
||||
restartWebSocketKernelEpic
|
||||
restartWebSocketKernelEpic,
|
||||
traceNotebookTelemetryEpic,
|
||||
traceNotebookInfoEpic,
|
||||
traceNotebookKernelEpic
|
||||
];
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
|
||||
import { Action } from "redux";
|
||||
import { Areas } from "../../../Common/Constants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as cdbActions from "./actions";
|
||||
import { CdbRecord } from "./types";
|
||||
|
||||
@@ -72,17 +70,6 @@ export const cdbReducer = (state: CdbRecord, action: Action) => {
|
||||
return state.set("hoveredCellId", typedAction.payload.cellId);
|
||||
}
|
||||
|
||||
case cdbActions.TRACE_NOTEBOOK_TELEMETRY: {
|
||||
const typedAction = action as cdbActions.TraceNotebookTelemetryAction;
|
||||
TelemetryProcessor.trace(typedAction.payload.action, typedAction.payload.actionModifier, {
|
||||
...typedAction.payload.data,
|
||||
databaseAccountName: state.databaseAccountName,
|
||||
defaultExperience: state.defaultExperience,
|
||||
dataExplorerArea: Areas.Notebook
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
case cdbActions.UPDATE_NOTEBOOK_PARENT_DOM_ELTS: {
|
||||
const typedAction = action as cdbActions.UpdateNotebookParentDomEltAction;
|
||||
var parentEltsMap = state.get("currentNotebookParentElements");
|
||||
|
||||
Reference in New Issue
Block a user