Skip to content
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

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open

Tickets/dm 46955 #36

wants to merge 16 commits into from

Conversation

athornton
Copy link
Member

No description provided.

@athornton
Copy link
Member Author

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.

@athornton
Copy link
Member Author

athornton commented Nov 27, 2024

Now I don't know what to do. jlpm lint:check runs fine locally. I installed on Amazon Linux, and it showed me that some lines were too long. I don't know why MacOS and Linux differ in this regard.

@athornton athornton force-pushed the tickets/DM-46955 branch 3 times, most recently from 8ed4a88 to 13f5afb Compare November 27, 2024 18:58
Copy link
Member

@stvoutsin stvoutsin left a 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."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Rubin query parms."""
"""Rubin query params."""

if (name === null) {
name = '<unnamed>';
}
console.log(`Building hierarchy ${name}`);
Copy link
Member

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}');

Copy link
Member

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.

Copy link
Member Author

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
Comment on lines 210 to 247
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 */
});
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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}`);
}
}

Copy link
Member

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');
Copy link
Member

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'
Copy link
Member

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?

Copy link
Member Author

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');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
console.log('Created TutorialsHierary from response');
console.log('Created TutorialsHierarchy from response');

src/tutorials.ts Outdated
Comment on lines 150 to 163
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
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 {

Copy link
Member

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(
Copy link
Member

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 initialization
  • buildSubmenus: Submenu creation
  • addMenuEntries: Menu entry creation

entries: { [name: string]: TutorialsEntry } | null = null;
subhierarchies: { [name: string]: TutorialsHierarchy } | null = null;

constructor(inp: ITutorialsHierarchyResponse, name: string | null = null) {
Copy link
Member

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) {
Copy link
Member

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)}`);
       ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants