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

Load and save variable filters #293

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

Conversation

kentarolim10
Copy link
Contributor

Description

  • Creates settings that stores filters for variable name and type
  • Loads settings on startup
  • Saves filters onto settings

Improvement for #165

Copy link

github-actions bot commented Dec 6, 2023

Binder 👈 Launch a Binder on branch kentarolim10/jupyterlab-variableInspector/add-persistent-variable-filtering

@fcollonval fcollonval added the enhancement New feature or request label Dec 7, 2023
Copy link
Member

@fcollonval fcollonval left a comment

Choose a reason for hiding this comment

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

Thanks @kentarolim10

I have some comments to improve the structure but the current logic is correct.

src/index.ts Outdated
Comment on lines 54 to 60
let settings: ISettingRegistry.ISettings;

try {
settings = await settingRegistry.load(variableinspector.id);
} catch (error) {
console.error('Failed to load settings for the Git Extension', error);
}
Copy link
Member

Choose a reason for hiding this comment

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

Good practice wants that asynchronous code should not block the activation of the plugin as it may delay the full application start up.

A better pattern is therefore to use a Promise approach:

Suggested change
let settings: ISettingRegistry.ISettings;
try {
settings = await settingRegistry.load(variableinspector.id);
} catch (error) {
console.error('Failed to load settings for the Git Extension', error);
}
Promise.all([settingRegistry.load(variableinspector.id), app.restored])
.then(settings => {
// ... here use the settings
})
.catch(error => {
console.error('Failed to load settings for the Git Extension', error);
});

src/index.ts Outdated

/**
* Create and track a new inspector.
*/
function newPanel(): VariableInspectorPanel {
const panel = new VariableInspectorPanel();
const panel = new VariableInspectorPanel(settings);
Copy link
Member

Choose a reason for hiding this comment

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

This is not the best approach as it add an opinionated interface on how the filter state is stored. To ease code maintenance, extensibility and testing, the best is to not passed the settings object but rather to add filter setters on the panel.

Now the trouble raises from how to pass information from the settings only available within the promise to the panel.

The best solution would be to refactor substantially the code as it is a bad idea to embed the view panel in the manager.

But let's go for a simpler fix for now. You could use a PromiseDelegate like this:

const settings = new PromiseDelegate<ISettingRegistry.ISettings>();

Promise.all([settingRegistry.load(variableinspector.id), app.restored])
.then(settings_ => {
  settings.resolve(settings_);
})
.catch(error => {
  console.error('Failed to load settings for the Git Extension', error);
  settings.reject(error);
});

Then change the command execute to be async:

      execute: async () => {
        if (!manager.panel || manager.panel.isDisposed) {
          await settings; // Wait for the settings to be loaded
          manager.panel = newPanel();
          // Best would be to use a single array of filter object: {filter: '', type: ''}[]
          // to clarify the binding between the type and the filter
          manager.panel.filters = settings.filters;
        }
        if (!manager.panel.isAttached) {
          labShell.add(manager.panel, 'main');
        }
        if (manager.source) {
          manager.source.performInspection();
        }
        labShell.activateById(manager.panel.id);
      }

Copy link
Member

@fcollonval fcollonval left a comment

Choose a reason for hiding this comment

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

Thanks @kentarolim10 for working on this.

Here are some additional suggestions. Do you have time to finish this PR?

Comment on lines +60 to +61
.then(returnArray => {
return settings.resolve(returnArray[0]);
Copy link
Member

Choose a reason for hiding this comment

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

To make the code more readable, you could use arg expansion:

Suggested change
.then(returnArray => {
return settings.resolve(returnArray[0]);
.then(([settings_]) => {
return settings.resolve(settings_);

Comment on lines +90 to +99
set settingsPromise(
settingPromise: PromiseDelegate<ISettingRegistry.ISettings>
) {
this._settingsPromise = settingPromise;
this._settingsPromise.promise.then(settings => {
this._settings = settings;
this.initalizeFilterButtons();
});
}

Copy link
Member

Choose a reason for hiding this comment

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

This is not a great pattern as we should avoid making this class aware of the settings registry.

To do this, this class need to have public setter for the filters and a signal that is emitted when they change. This signal will be used to store new filters to the settings.

The pseudo code would look like that:

// index.ts

panel.filters = settings.composite['filters];
panel.filtersChanged.connect((_, newFilters) => { settings.set('filters', newFilters); });

// variableinspector.ts

class VariableInspectorPanel {
  private _filtersChanged = new Signal<this, string[]>(this);
  private _filters = new Array<string>();

  set filters(value: string[]) {
    this._filters = value;
    this._filtersChanged.emit(this._filters);
  }

  get filterChanged(): ISignal<this, string[]> {
    return this._filtersChanged;
  }
}

}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@fcollonval Sorry for the delay. I was just wondering how we are able to access the settings in the index for the two lines.

panel.filters = settings.composite['filters];
panel.filtersChanged.connect((_, newFilters) => { settings.set('filters', newFilters); });

Currently, the settings are a promise in which we load the settings.

const settings = new PromiseDelegate<ISettingRegistry.ISettings>();

Promise.all([settingRegistry.load(variableinspector.id), app.restored])
      .then(([_settings]) => {
        return settings.resolve(_settings);
      })
      .catch(error => {
        console.error('Failed to load settings for the Git Extension', error);
        settings.reject(error);
      });

However, when we try to access the settings with the filter, the promise has not resolved yet. How do we go about this?

Copy link
Member

Choose a reason for hiding this comment

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

Hey @kentarolim10 - nice to read you.


A common pattern is to call a onSettingsChanged method in the plugin activation function that propagate the settings to the appropriate objects when they changed; see e.g. https://github.com/jupyterlab-contrib/search-replace/blob/8f4bdff704339cb189e4d54beb230a1e510000f7/src/index.ts#L75

In this case to set the settings on all variable-inspector panels, you could make use of the tracker.forEach method.

@kentarolim10
Copy link
Contributor Author

Thanks @kentarolim10 for working on this.

Here are some additional suggestions. Do you have time to finish this PR?

Yes I do, I'm just studying for finals right now so I will try and get it done next week.

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

Successfully merging this pull request may close these issues.

None yet

2 participants