-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tickets/dm 46955 #36
base: main
Are you sure you want to change the base?
Tickets/dm 46955 #36
Conversation
aa116a2
to
fb819b1
Compare
Run on IDF as Experimental Weekly 2024_47_tut_exp I've left an awful lot of console logging in here that I will delete before I merge, but you may find it helpful if you play with the extension in the RSP, because it lets you trace the construction of the hierarchy and the menu from it. |
|
8ed4a88
to
13f5afb
Compare
13f5afb
to
b974a33
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great and also looks like a lot of work!
I've only reviewed the Typescript side, and only have some small suggestions based on my somewhat limited JS/Typescript experience so take with a grain of salt
super().initialize() | ||
self._client = RSPClient(base_path="times-square/api/v1/") | ||
|
||
@property | ||
def rubinquery(self) -> dict[str, str]: | ||
"""Rubin query parms.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"""Rubin query parms.""" | |
"""Rubin query params.""" |
if (name === null) { | ||
name = '<unnamed>'; | ||
} | ||
console.log(`Building hierarchy ${name}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there is a lot of logging which you may or may not want to display depending on if you are in prod or dev, you could setup a custom logger (or reuse something already out there like Pino).
Example:
function logMessage(level: 'info' | 'warn' | 'error', message: string, ...optionalParams: any[]): void {
const logLevel = process.env.LOG_LEVEL || 'info';
const levels = {
info: 1,
warn: 2,
error: 3,
};
if (levels[level] >= levels[logLevel]) {
if (level === 'info') {
console.info(message, ...optionalParams);
} else if (level === 'warn') {
console.warn(message, ...optionalParams);
} else if (level === 'error') {
console.error(message, ...optionalParams);
}
}
}
Which you could then use as:
logMessage('info', 'Building hierarchy ${name}');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also just noticed your message that logging would be removed so you may want to skip this, although perhaps you may still want to leave some logging in there that you can turn it off/on via an env var?
May be useful in case things break and you won't have to add logging again from the start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, sort of. But I don't have process.env in a useful fashion where the UI is running. But that's why I have the environment extension...but I need to refactor the loading so I can load the environment before initializing the other extensions, and then figure out a way to stash it so they can get to it.
That feels like the big tech-debt refactor I'm putting off, though. For the time being I'll probably get it debugged and then throw away a bunch of the console.log statements.
src/tutorials.ts
Outdated
function overwriteDialog( | ||
dest: string, | ||
manager: IDocumentManager | ||
): Promise<any> { | ||
const options = { | ||
title: 'Target file exists', | ||
body: `Overwrite file '${dest}' ?`, | ||
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'OVERWRITE' })] | ||
}; | ||
console.log('Showing overwrite dialog'); | ||
return showDialog(options).then(result => { | ||
if (!result) { | ||
console.log('No result from queryDialog'); | ||
return new Promise((res, rej) => { | ||
/* Nothing */ | ||
}); | ||
} | ||
console.log('Result from overwriteDialog: ', result); | ||
if (!result.button) { | ||
console.log('No result.button from overwriteDialog'); | ||
return new Promise((res, rej) => { | ||
/* Nothing */ | ||
}); | ||
} | ||
if (result.button.label === 'OVERWRITE') { | ||
console.log( | ||
'Got result ', | ||
result.button.label, | ||
' from overwriteDialog: OVERWRITE' | ||
); | ||
return Promise.resolve(result.button.label); | ||
} | ||
console.log('Did not get overwriteDialog: OVERWRITE'); | ||
return new Promise((res, rej) => { | ||
/* Nothing */ | ||
}); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function overwriteDialog( | |
dest: string, | |
manager: IDocumentManager | |
): Promise<any> { | |
const options = { | |
title: 'Target file exists', | |
body: `Overwrite file '${dest}' ?`, | |
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'OVERWRITE' })] | |
}; | |
console.log('Showing overwrite dialog'); | |
return showDialog(options).then(result => { | |
if (!result) { | |
console.log('No result from queryDialog'); | |
return new Promise((res, rej) => { | |
/* Nothing */ | |
}); | |
} | |
console.log('Result from overwriteDialog: ', result); | |
if (!result.button) { | |
console.log('No result.button from overwriteDialog'); | |
return new Promise((res, rej) => { | |
/* Nothing */ | |
}); | |
} | |
if (result.button.label === 'OVERWRITE') { | |
console.log( | |
'Got result ', | |
result.button.label, | |
' from overwriteDialog: OVERWRITE' | |
); | |
return Promise.resolve(result.button.label); | |
} | |
console.log('Did not get overwriteDialog: OVERWRITE'); | |
return new Promise((res, rej) => { | |
/* Nothing */ | |
}); | |
}); | |
} | |
interface DialogResult { | |
button?: { | |
label: string; | |
}; | |
} | |
async function overwriteDialog(dest: string, manager: IDocumentManager): Promise<string | void> { | |
const dialogOptions = { | |
title: 'Target file exists', | |
body: `Overwrite file '${dest}' ?`, | |
buttons: [ | |
Dialog.cancelButton(), | |
Dialog.okButton({ label: 'OVERWRITE' }) | |
] | |
}; | |
try { | |
console.log('Showing overwrite dialog'); | |
const result: DialogResult = await showDialog(dialogOptions); | |
if (!result) { | |
console.log('No result from queryDialog'); | |
return; | |
} | |
console.log('Result from overwriteDialog: ', result); | |
if (!result.button) { | |
console.log('No result.button from overwriteDialog'); | |
return; | |
} | |
if (result.button.label === 'OVERWRITE') { | |
console.log( | |
'Got result ', | |
result.button.label, | |
' from overwriteDialog: OVERWRITE' | |
); | |
return result.button.label; | |
} | |
console.log('Did not get overwriteDialog: OVERWRITE'); | |
return; | |
} catch (error) { | |
console.error('Error showing overwrite dialog:', error); | |
throw new Error(`Failed to show overwrite dialog: ${error.message}`); | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think simply returning (void) may be better here then promises that don't resolve.
You could also use Promise.resolve() or even a Promise.reject() if you want your function to return a promise, but I don't think it's needed?
Note: I have not tested this
const tutorialsmenu = new Menu({ commands }); | ||
tutorialsmenu.title.label = 'Tutorials'; | ||
parentmenu = tutorialsmenu; | ||
console.log('set up top level Tutorials menu'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think debug
may be better for these types of messages, which some browser will hide in the console by default
enum Dispositions { | ||
PROMPT = 'prompt', | ||
OVERWRITE = 'overwrite', | ||
ABORT = 'abort' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like ABORT
is used atm, but may be in future versions in which case it's fine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, at the moment there's no notion of a server-side cleanup if the user cancels, but there could be. I think in the current design we don't persist anything into the user's file space unless we have confirmed (or confirmation wasn't needed). But I can see that changing, especially as I figure out the ancillary-file stuff.
src/tutorials.ts
Outdated
console.log(`Response: ${JSON.stringify(data, undefined, 2)}`); | ||
const h_i = data as ITutorialsHierarchyResponse; | ||
const tut = new TutorialsHierarchy(h_i); | ||
console.log('Created TutorialsHierary from response'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
console.log('Created TutorialsHierary from response'); | |
console.log('Created TutorialsHierarchy from response'); |
src/tutorials.ts
Outdated
function apiPostTutorialsEntry( | ||
settings: ServerConnection.ISettings, | ||
docManager: IDocumentManager, | ||
entry: TutorialsEntry | ||
): void { | ||
/** | ||
* Make a request to our endpoint to copy a file into place and open it | ||
* | ||
* @param settings - the settings for the current notebook server | ||
* | ||
* @param entry - the entry corresponding to the file to work with | ||
* | ||
* @returns a Promise resolved with the JSON response | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function apiPostTutorialsEntry( | |
settings: ServerConnection.ISettings, | |
docManager: IDocumentManager, | |
entry: TutorialsEntry | |
): void { | |
/** | |
* Make a request to our endpoint to copy a file into place and open it | |
* | |
* @param settings - the settings for the current notebook server | |
* | |
* @param entry - the entry corresponding to the file to work with | |
* | |
* @returns a Promise resolved with the JSON response | |
*/ | |
/** | |
* Make a request to our endpoint to copy a file into place and open it. | |
* Handles file overwrite confirmation if needed and opens the file on success. | |
* | |
* @param settings - the settings for the current notebook server | |
* @param docManager - the document manager instance | |
* @param entry - the entry corresponding to the file to work with | |
*/ | |
function apiPostTutorialsEntry( | |
settings: ServerConnection.ISettings, | |
docManager: IDocumentManager, | |
entry: TutorialsEntry | |
): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in general the Javascript convention is jsdoc before function
const svcManager = app.serviceManager; | ||
const settings = svcManager.serverSettings; | ||
|
||
function buildTutorialsMenu( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a future improvement but buildTutorialsMenu
could potentially be broken down to smaller single-responsibility methods?
Example:
initializeMenu
: Menu initializationbuildSubmenus
: Submenu creationaddMenuEntries
: Menu entry creation
entries: { [name: string]: TutorialsEntry } | null = null; | ||
subhierarchies: { [name: string]: TutorialsHierarchy } | null = null; | ||
|
||
constructor(inp: ITutorialsHierarchyResponse, name: string | null = null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be personal preference but I'd use methods outside of the constructor like:
constructor(
input: ITutorialsHierarchyResponse,
private readonly name: string = '<unnamed>'
) {
console.log(`Building hierarchy ${this.name}`);
this.processEntries(input.entries);
this.processSubhierarchies(input.subhierarchies);
console.log(`Hierarchy ${this.name} built`);
}
} | ||
console.log(`Building hierarchy ${name}`); | ||
if (inp.entries !== null) { | ||
for (const entry in inp.entries) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Object.entries may be useful for iteration, up to you though:
Example:
private processEntries(entries: Record<string, TutorialsEntry> | null): void {
if (!entries) return;
for (const [entryName, entry] of Object.entries(entries)) {
...
console.log(`Adding entry ${entryName}: ${JSON.stringify(entry, null, 2)}`);
...
No description provided.