Skip to content

Commit 646e6c9

Browse files
authored
Added telemetry support. (#448)
* Added telemetry support. * Fixed lint errors * Refactored to use a logger hook. * Corrected the handler and token names. * Corrected the plugin id to match the token. * Updated names for hook and handler
1 parent 3065ebb commit 646e6c9

16 files changed

+312
-114
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ dmypy.json
123123
# job outputs in local development env
124124
dev/jobs
125125

126+
# Notebook files created in dev folder
127+
dev/*.ipynb
128+
126129
# jupyter releaser local checkout
127130
.jupyter_releaser_checkout
128131

src/components/create-schedule-options.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React, { ChangeEvent } from 'react';
33
import { FormControlLabel, InputLabel, Radio, RadioGroup } from '@mui/material';
44
import Stack from '@mui/system/Stack';
55

6-
import { useTranslator } from '../hooks';
6+
import { useEventLogger, useTranslator } from '../hooks';
77
import { ICreateJobModel } from '../model';
88
import { ScheduleInputs } from './schedule-inputs';
99
import { Scheduler } from '../tokens';
@@ -26,10 +26,15 @@ export function CreateScheduleOptions(
2626

2727
const labelId = `${props.id}-label`;
2828

29+
const log = useEventLogger();
30+
2931
const handleScheduleOptionsChange = (
3032
event: ChangeEvent<HTMLInputElement>,
3133
value: string
3234
) => {
35+
log(
36+
`create-job.job-type.${value === 'Job' ? 'run-now' : 'run-on-schedule'}`
37+
);
3338
const name = event.target.name;
3439
props.handleModelChange({ ...props.model, [name]: value });
3540
};

src/components/job-definition-row.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import TableRow from '@mui/material/TableRow';
1313
import TableCell from '@mui/material/TableCell';
1414

1515
import { Scheduler, SchedulerService } from '../handler';
16-
import { useTranslator } from '../hooks';
16+
import { useEventLogger, useTranslator } from '../hooks';
1717
import { TranslationBundle } from '@jupyterlab/translation';
1818
import { ConfirmDeleteButton } from './confirm-buttons';
1919

@@ -94,10 +94,14 @@ export function buildJobDefinitionRow(
9494
ss: SchedulerService,
9595
handleApiError: (error: string | null) => void
9696
): JSX.Element {
97+
const log = useEventLogger();
9798
const cellContents: React.ReactNode[] = [
9899
// name
99100
<Link
100-
onClick={() => openJobDefinitionDetail(jobDef.job_definition_id)}
101+
onClick={() => {
102+
log('job-definition-list.open-detail');
103+
openJobDefinitionDetail(jobDef.job_definition_id);
104+
}}
101105
title={`Open detail view for "${jobDef.name}"`}
102106
>
103107
{jobDef.name}
@@ -110,6 +114,7 @@ export function buildJobDefinitionRow(
110114
<PauseButton
111115
jobDef={jobDef}
112116
clickHandler={async () => {
117+
log('job-definition-list.pause');
113118
handleApiError(null);
114119
ss.pauseJobDefinition(jobDef.job_definition_id)
115120
.then(_ => {
@@ -123,6 +128,7 @@ export function buildJobDefinitionRow(
123128
<ResumeButton
124129
jobDef={jobDef}
125130
clickHandler={async () => {
131+
log('job-definition-list.resume');
126132
handleApiError(null);
127133
ss.resumeJobDefinition(jobDef.job_definition_id)
128134
.then(_ => {
@@ -136,6 +142,7 @@ export function buildJobDefinitionRow(
136142
<ConfirmDeleteButton
137143
name={jobDef.name}
138144
clickHandler={async () => {
145+
log('job-definition-list.delete');
139146
handleApiError(null);
140147
ss.deleteJobDefinition(jobDef.job_definition_id)
141148
.then(_ => {

src/components/job-file-link.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22

33
import { Scheduler } from '../handler';
4-
import { useTranslator } from '../hooks';
4+
import { useEventLogger, useTranslator } from '../hooks';
55

66
import { JupyterFrontEnd } from '@jupyterlab/application';
77

@@ -11,6 +11,7 @@ export interface IJobFileLinkProps {
1111
jobFile: Scheduler.IJobFile;
1212
app: JupyterFrontEnd;
1313
children?: React.ReactNode;
14+
parentComponentName?: string;
1415
}
1516

1617
export function JobFileLink(props: IJobFileLinkProps): JSX.Element | null {
@@ -20,6 +21,8 @@ export function JobFileLink(props: IJobFileLinkProps): JSX.Element | null {
2021
return null;
2122
}
2223

24+
const log = useEventLogger();
25+
2326
const fileBaseName = props.jobFile.file_path.split('/').pop();
2427

2528
const title =
@@ -38,6 +41,15 @@ export function JobFileLink(props: IJobFileLinkProps): JSX.Element | null {
3841
| React.MouseEvent<HTMLAnchorElement, MouseEvent>
3942
) => {
4043
e.preventDefault();
44+
if (props.parentComponentName) {
45+
log(
46+
`${props.parentComponentName}.${
47+
props.jobFile.file_format === 'input'
48+
? 'open-input-file'
49+
: 'open-output-file'
50+
}`
51+
);
52+
}
4153
props.app.commands.execute('docmanager:open', {
4254
path: props.jobFile.file_path
4355
});

src/components/job-row.tsx

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ConfirmDeleteButton, ConfirmButton } from './confirm-buttons';
55
import { JobFileLink } from './job-file-link';
66
import { CommandIDs } from '..';
77
import { Scheduler } from '../handler';
8-
import { useTranslator } from '../hooks';
8+
import { useEventLogger, useTranslator } from '../hooks';
99
import { ICreateJobModel } from '../model';
1010
import DownloadIcon from '@mui/icons-material/Download';
1111
import StopIcon from '@mui/icons-material/Stop';
@@ -16,6 +16,7 @@ function StopButton(props: {
1616
clickHandler: () => void;
1717
}): JSX.Element | null {
1818
const trans = useTranslator('jupyterlab');
19+
const log = useEventLogger();
1920
const buttonTitle = props.job.name
2021
? trans.__('Stop "%1"', props.job.name)
2122
: trans.__('Stop job');
@@ -26,7 +27,10 @@ function StopButton(props: {
2627
>
2728
<ConfirmButton
2829
name={buttonTitle}
29-
onConfirm={props.clickHandler}
30+
onConfirm={() => {
31+
log('job-list.stop-confirm');
32+
props.clickHandler();
33+
}}
3034
confirmationText={trans.__('Stop')}
3135
icon={<StopIcon fontSize="small" />}
3236
remainAfterConfirmation
@@ -60,7 +64,11 @@ function JobFiles(props: {
6064
{props.job.job_files
6165
.filter(jobFile => jobFile.file_format !== 'input' && jobFile.file_path)
6266
.map(jobFile => (
63-
<JobFileLink jobFile={jobFile} app={props.app} />
67+
<JobFileLink
68+
jobFile={jobFile}
69+
app={props.app}
70+
parentComponentName="jobs-list"
71+
/>
6472
))}
6573
</>
6674
);
@@ -76,6 +84,7 @@ type DownloadFilesButtonProps = {
7684
function DownloadFilesButton(props: DownloadFilesButtonProps) {
7785
const [downloading, setDownloading] = useState(false);
7886
const trans = useTranslator('jupyterlab');
87+
const log = useEventLogger();
7988

8089
return (
8190
<IconButton
@@ -84,13 +93,15 @@ function DownloadFilesButton(props: DownloadFilesButtonProps) {
8493
disabled={downloading}
8594
onClick={async () => {
8695
setDownloading(true);
96+
log('jobs-list.download');
8797
props.app.commands
8898
.execute(CommandIDs.downloadFiles, {
8999
id: props.job.job_id,
90100
redownload: false
91101
})
92102
.then(_ =>
93103
new Promise(res => setTimeout(res, 5000)).then(_ => {
104+
log('jobs-list.download');
94105
setDownloading(false);
95106
props.reload();
96107
})
@@ -120,16 +131,24 @@ export function buildJobRow(
120131
jobFile => jobFile.file_format === 'input' && jobFile.file_path
121132
);
122133
const trans = useTranslator('jupyterlab');
134+
const log = useEventLogger();
123135

124136
const cellContents: React.ReactNode[] = [
125137
<Link
126-
onClick={() => showDetailView(job.job_id)}
138+
onClick={() => {
139+
log('jobs-list.open-detail');
140+
showDetailView(job.job_id);
141+
}}
127142
title={trans.__('Open detail view for "%1"', job.name)}
128143
>
129144
{job.name}
130145
</Link>,
131146
inputFile ? (
132-
<JobFileLink app={app} jobFile={inputFile}>
147+
<JobFileLink
148+
app={app}
149+
jobFile={inputFile}
150+
parentComponentName="jobs-list"
151+
>
133152
{job.input_filename}
134153
</JobFileLink>
135154
) : (
@@ -153,6 +172,7 @@ export function buildJobRow(
153172
<ConfirmDeleteButton
154173
name={job.name}
155174
clickHandler={() => {
175+
log('jobs-list.delete');
156176
app.commands
157177
.execute(CommandIDs.deleteJob, {
158178
id: job.job_id
@@ -163,13 +183,14 @@ export function buildJobRow(
163183
/>
164184
<StopButton
165185
job={job}
166-
clickHandler={() =>
186+
clickHandler={() => {
187+
log('jobs-list.stop');
167188
app.commands
168189
.execute(CommandIDs.stopJob, {
169190
id: job.job_id
170191
})
171-
.catch((e: Error) => setDisplayError(e.message))
172-
}
192+
.catch((e: Error) => setDisplayError(e.message));
193+
}}
173194
/>
174195
</Stack>
175196
];

src/context.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
22
import React from 'react';
33

4+
export type Logger = (eventName: string) => void;
5+
export const LogContext = React.createContext<Logger>((eventName: string) => {
6+
/*noop*/
7+
});
8+
49
// Context to be overridden with JupyterLab context
510
const TranslatorContext = React.createContext<ITranslator>(nullTranslator);
611
export default TranslatorContext;

src/hooks.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ import { useContext } from 'react';
22

33
import { TranslationBundle } from '@jupyterlab/translation';
44

5-
import TranslatorContext from './context';
5+
import TranslatorContext, { Logger, LogContext } from './context';
66

77
export function useTranslator(bundleId: string): TranslationBundle {
88
const translator = useContext(TranslatorContext);
99
return translator.load(bundleId);
1010
}
11+
12+
export function useEventLogger(): Logger {
13+
const logger: Logger = useContext(LogContext);
14+
return logger;
15+
}

src/index.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ const schedulerPlugin: JupyterFrontEndPlugin<void> = {
5050
INotebookTracker,
5151
ITranslator,
5252
ILayoutRestorer,
53-
Scheduler.IAdvancedOptions
53+
Scheduler.IAdvancedOptions,
54+
Scheduler.TelemetryHandler
5455
],
5556
optional: [ILauncher],
5657
autoStart: true,
@@ -67,6 +68,22 @@ const advancedOptions: JupyterFrontEndPlugin<Scheduler.IAdvancedOptions> = {
6768
}
6869
};
6970

71+
// Default Telemetry Handler
72+
const telemetryHandler = async (
73+
eventLog: Scheduler.IEventLog
74+
): Promise<void> => {
75+
console.log(JSON.stringify(eventLog, undefined, 4));
76+
};
77+
78+
const telemetry: JupyterFrontEndPlugin<Scheduler.TelemetryHandler> = {
79+
id: '@jupyterlab/scheduler:TelemetryHandler',
80+
autoStart: true,
81+
provides: Scheduler.TelemetryHandler,
82+
activate: (app: JupyterFrontEnd) => {
83+
return telemetryHandler;
84+
}
85+
};
86+
7087
function getSelectedItem(widget: FileBrowser | null): Contents.IModel | null {
7188
if (widget === null) {
7289
return null;
@@ -127,6 +144,7 @@ async function activatePlugin(
127144
translator: ITranslator,
128145
restorer: ILayoutRestorer,
129146
advancedOptions: Scheduler.IAdvancedOptions,
147+
telemetryHandler: Scheduler.TelemetryHandler,
130148
launcher: ILauncher | null
131149
): Promise<void> {
132150
const trans = translator.load('jupyterlab');
@@ -170,6 +188,7 @@ async function activatePlugin(
170188
jobsPanel = new NotebookJobsPanel({
171189
app,
172190
translator,
191+
telemetryHandler,
173192
advancedOptions: advancedOptions
174193
});
175194
// Create new main area widget
@@ -290,7 +309,8 @@ async function activatePlugin(
290309

291310
const plugins: JupyterFrontEndPlugin<any>[] = [
292311
schedulerPlugin,
293-
advancedOptions
312+
advancedOptions,
313+
telemetry
294314
];
295315

296316
export { JobsView };

src/mainviews/create-job-from-definition.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Heading } from '../components/heading';
44
import { Cluster } from '../components/cluster';
55
import { ParametersPicker } from '../components/parameters-picker';
66
import { Scheduler, SchedulerService } from '../handler';
7-
import { useTranslator } from '../hooks';
7+
import { useEventLogger, useTranslator } from '../hooks';
88
import { ICreateJobModel, IJobParameter, JobsView } from '../model';
99
import { Scheduler as SchedulerTokens } from '../tokens';
1010

@@ -198,6 +198,8 @@ export function CreateJobFromDefinition(
198198
const cantSubmit = trans.__('One or more of the fields has an error.');
199199
const createError: string | undefined = props.model.createError;
200200

201+
const log = useEventLogger();
202+
201203
return (
202204
<Box sx={{ p: 4 }}>
203205
<form className={`${formPrefix}form`} onSubmit={e => e.preventDefault()}>
@@ -227,14 +229,18 @@ export function CreateJobFromDefinition(
227229
<>
228230
<Button
229231
variant="outlined"
230-
onClick={e => props.showListView(JobsView.ListJobs)}
232+
onClick={e => {
233+
log('create-job-from-definition.cancel');
234+
props.showListView(JobsView.ListJobs);
235+
}}
231236
>
232237
{trans.__('Cancel')}
233238
</Button>
234239

235240
<Button
236241
variant="contained"
237242
onClick={(e: React.MouseEvent) => {
243+
log('create-job-from-definition.create');
238244
submitCreateJobRequest(e);
239245
return false;
240246
}}

0 commit comments

Comments
 (0)