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

PTV-1905 - add service widget support #3421

Open
wants to merge 103 commits into
base: develop
Choose a base branch
from
Open

PTV-1905 - add service widget support #3421

wants to merge 103 commits into from

Conversation

eapearson
Copy link
Contributor

@eapearson eapearson commented Jan 18, 2024

Description of PR purpose/changes

WIP - this is a speculative PR. It is without new tests, which will be added before this is submitted for official review.

At present, I'll be iterating on the PR in order to ensure it is tidy and well-explained, and get through the code quality checks.

For now, I'd like to use this as a basis for temporary CI deployments (from the PR or PTV-1905 branch) so we can exercise service widgets in CI.

Changes should not affect anything other than service widgets. Some changes cut across the codebase, but by far most changes only affect the new nbextensions/serviceWidget cell.

Below is an extensive write-up of the changes:

Changes to Enable Service Widgets in the Narrative

This document summarizes changes required to the Narrative to enable the usage of service widgets.

Summary of Changes

  • added service widget cell in nbextensions/serviceWidget, src/biokbase/cells/service_widget.py, kbase-extension/scss/partials/_serviceWidgetCell.scss, pythonInterop.js
  • There is more cell support used by the service widget cell kbase-extension/static/kbase/js/util/cellSupport/ has CellBase.js and CellManager.js, moving sharable cell code out of the specific implementation
  • A small addition to cellUtils.js to make life a bit easier.
  • add preact and htm dependencies in package.json, narrative_paths.js
  • added a few supportive preact components kbase-extension/static/kbase/js/preactComponents- ErrorAlert, Loading, etc.
  • Added nbextensions/serviceWidgetCell/util/SendCannel and nbextensions/serviceWidgetCell/util/ReceiveChannel to provide for serviceWidgetCell <-> serviceWidget comm through the window "message" event, aka, postMessage().
  • To support app output rendering for service widgets, modified appCellWidget.js
  • To support data viewer rendering for service widgets, modified kbaseNarrativeWorkspace.js and narrativeViewers.js.
  • To add support for the developer menu:
    • added:
      • AddServiceWidget, AddServiceWidgetData classes, which render bits for bootstrapDialog.js
      • modified bootstrapDialog to add a size option, and added support the 'show.bs.modal' event
      • modified kbaseNarrative to add developer menu dialogs and developer detection
      • added markup for developer menu to narrative_header.html

Details

Wherein we shall dive deeper into the following areas of implementation:

  • The service widget cell itself
  • Support for service widget serving as data object viewer
  • Support for service widget serving as an app output viewer
  • Support for developer mode and developer tools

Implement service widget cell

The service widget cell is implemented as a notebook extension in the directory nbextensions/serviceWidgetCell.

Python and Javascript injection

In addition to the files in this directory, python injection support is required for a new cell type. pythonInterop.py gains a new function, buildServiceWidgetRunner, to generate the Python snippet for the cell. This snippet relies upon the implementation in the new function render_app in a new module biokbase.cells.service_widget. This function,in turn, generates the Javascript to be injected into the code cell output area. Finally, the injected Javascript invokes the Root widget in nbextensions/serviceWidgetCell/widgets/Root.js, from which the rest of the components are rendered.

"cell" implementation

It is worth noting that the cell implementation is divided into two main parts - the notebook cell implementation, which knows how to control the cell itself, and the widget implementation.

The top level of the cell's Javascript implementation is in innbextensions/serviceWidgetCell/ServiceWidgetCell.js and utils/cellSupport/CellManager.js. ServiceWidgetCell is a subclass ofCellBase in util/cellSupport/CellBase.js, which provides the bulk of the logic.

The "manager" is responsible for integration with the notebook extension mechanism and notebook events. The "cell" is responsible for per-cell KBase behavior as well as specific behavior for the service widget cell.

"widget" implementation

The cell ui, what we think of as the "widget", begins life in the Root widget in nbextensions/serviceWidgetCell/widgets/Root.js. The Root widget is primarily responsible for bridging between the Narrative and the service widget implementation. It prepares props for and then inserts components/Main.js, which is implemented in preact in htm and provides a react component Main.

I've tried to get preact into the codebase before, several years ago, and, well, I'm going to try again! For those of us doing react (which is all of us), the data flow and composition is more familiar, compared to jquery or kbwidgets, not to knock those. If need by, I could re-implement in jquery.

As mentioned above, the cell's widget starts with nbextensions/serviceWidgetCell/widgets/Root.js. This is simply reponsible for inserting the widget into the DOM and rendering the top level react widget.

This top level react component, nbextensions/serviceWidgetCell/components/Main.js, contains most of the logic to implement the service widget, including communication with the widget (via postMessage), preparation of widget parameters, and insertion of the component which implements the iframe.

Finally a component implements the specially-formed iframe in nbextensions/serviceWidgetCell/components/IFrame.js. This is essentially a simple wrapper which, given a set of props, knows how to create the appropriate iframe markup.

Communication between Narrative and embedded service widget app

A service widget cell supports communication with a service widget app embedded in an iframe. To facilitate this, a pair of classes, SendChannel in nbextensions/serviceWidgetCell/SendChannel.js and ReceiveChannel in nbextensions/serviceWidgetCell/ReceiveChannel.js are utilized. These are based on the same class I've used for years in kbase-ui to facilitate communication with the plugins using "window messages" via the postMessage api. However, unlike the kkbase-ui plugin implementation, this requires two channels (using the same channel id). This is because the service widget may be running on a different host than the Narrative, and browser security policy prevents Javascript in one window from listening for messages on another window if their origins differ. This will be true in prod.

So, the comunication is split into a sender and receiver in order to be able to handle cross-origin communication.

The Narrative <-> Service Widget Cell communication is required to implement the invocation of service widgets, as well as additional features.

Additional features include:

  • propagate click from the iframe to the host; necessary to enable cell selection when clicking on the widget, or to close open dropdown, etc. - basically things listening for clicks to propagate that would be blocked by the iframe eating them.
  • set the cell title and/or subtitle - form some widgets the proper title or subtitle won't be known until the widget runs

More advanced uses include:

  • widget state persistence (stored in cell metadata)
  • widget sets desired size, cell resizes to accomodate

Miscellaneous Support Javascript

With preact available, a few general purpose components were added - Loading, ErrorAlert, and RotatedTable.

Basically, any repeated code was moved into a separate component.

The Loading component shows a loaindg spinner with an optional message. The ErrorAlert shows a simple error message in an error alert, and RotatedTable implements a simple two-column table, in which the first column is the header, and the second the value.

In summary, all files added or changed to implement it

TODO: I need to redo this table. It (a) doesn't seem to work very well (indentation keeps disappearing) and (b) is now a bit out of date.

Path Type Reason(s)
kbase-extension
static
kbase
js
common
pythonInterop.js Updated generate service widget python snippet
util
cellSupport New
CellBase.js New Generic KBase cell support; subclassed per cell extension
CellManager.js New General purpose KBase cell manager support; used directly; handles cell lifecycle, calling specific cell methods which must be implemented.
preactComponents
ErrorAlert.css
ErrorAlert.js Displays a error message in a Bootstrap Alert component with the "danger" style variant
Loading.js Displays a spinner icon with an optional message
Loading.styles.js
nbextension
serviceWidgetCell New implementation directory for the service widget cell
components New contains preact components
IFrame.css New
IFrame.js New Implements an iframe with specific support for service widgets
Main.css New
Main.js New Manages the iframe; prepares correct props, sets up communication
util New
ReceiveChannel.js New Implements window messaging receiving and handling w/postMessage
SendChannel.js New Implements window message sending w/postMessage
widgets New
Root.css New
Root.js New Main entry point for the cell itself; responsible for mounting the Main component with parameters passed from the cell.
constants.js New
main.js New main entry point for the nbextension api; all logic is in ServiceWidgetCellManager.js
readme.md New
ServiceWidgetCell.css New
ServiceWidgetCell.js New Implementation of the service widget cell support.

Service Widget as Data Object Viewer

Enabling service widgets to serve as an object viewer requires changes to the existing Javascript module widgets/narrative_core/kbaseNarrativeWorkspace.js, fixes to widgets/narrative_core/narrativeViewers.js, and, for supported types, modifications to viewer specifications in https://github.com/kbase/NarrativeViewers.

Javascript changes

In kbaseNarrativeWorkspace.js, the buildViewerCell kbwidget method is modified to determine the narrative viewer before inserting the data cell. In the current implementation of data cells, which have not been touched, the viewer is determined by the python code. However, in order to insert a serviceWidget cell rather than a data cell, we need to inspect the viewer spec first. This is because we need to determine whether the requested viewer should be served as a dynamic service widget, or let the existing mechanism handle it. And we need to do this before inserting the cell.

The narrative viewer specs are cached, although the first time a data object is inserted into the Narrative in a given session, there will be some delay as it is fetched from the Narrative Method Store.

buildViewerCell uses the existing loadViewerInfo function in the narrativeViewers module. The relevant function, loadViewerInfo, was not used in the codebase. I suspect it was used at one point, and then the functionality moved into the backend python handler for the data cell. loadViewerInfo was updated to utilize the app panel's version tag, so that one can utilize a dev-tagged viewer. This allows us to have existing viewers operate as normal through the release tag, but if a user (in CI at least, when we are testing) selects the dev tag, use a newer narrative viewer spec that has been updated to invoke a service widget. (This is of course contingent upon how the NarrativeViewers "app" is managed via the catalog.)

This introduces an inefficiency, as the Javascript now looks up the viewer info, and the data viewer cell's Python code also looks up the viewer info. I think we could re-use the Javscript lookup for the data viewer cell as well, but that change is out of scope for now.

The viewer spec's widget.output property contains a special value for service widgets ServiceWidget. It acts as a pseudo-widget, as it does not correspond to an actual AMD viewer module like the extant data widget mechanism does. It is used for conditionally branching to code which prepares and invokes a service widget, as opposed to a standard viewer.

The service widget itself is agnostic with respect to how the service widget will be used. We don't want to put viewer logic into the service widget code itself. Thus, buildViewerCell is reponsible for packaging up the relevant widget invocation information, based on the viewer spec and the target object, so that the service widget cell can serve as a viewer cell. This informaiton includes the module name for the dynamic service, the widget name within the dynamic service, and the parameters, specifically the object ref.

Service Widget NarrativeViewer Changes

To enable a service widget to serve as a data object viewer for a given type, the viewer spec for the type must be modified in a specific manner.

  • The output widget specified in spec.json's widgets.output must be "ServiceWidget"
  • The output mapping in spec.json must provide the module and widget names:
    • Service widget module name in a constant_value with a target_property of "service_module_name"
    • Widget name in a constant_value with a target_property of "widget_name"

For example,

{
  "name" : "View Media",
  "ver" : "1.0.0",
  "authors" : [ ],
  "contact" : "https://www.kbase.us/support/",
  "visible" : true,
  "categories" : ["viewers"],
  "app_type" : "viewer",
  "widgets" : {
    "input" : null,
    "output" : "ServiceWidget"
  },
  "parameters" : [ ],
  "behavior" : {
    "none" : {
      "output_mapping" : [
        {
          "constant_value": "eapearsonWidgetTest10",
          "target_property": "service_module_name"
        },
        {
          "constant_value": "media_viewer_py",
          "target_property": "widget_name"
        }
      ]
    }
  }
}

Note that no parameters are required. The data object's ref is automatically generated within buildViewerCell and passed to the widget. Thus, if you are modifying an existing viewer spec to use a service widget, you may safely remove any parameters.

The display.yaml requires no changes to support service widgets, other than any description changes that may reflect changed functionality in the viewer. Of course, if parameters were removed from spec.json, they need to be removed from display.yaml as well.

Service Widget as App Output Viewer

Service widgets may also serve as viewers for app output.

As for data viewers, enabling service widgets requires changes to the existing mechanism for handling app output.

This functionality resides within the app cell itself - specifically the createOutputCell function in nbextensions/appCell2/appCellWidget.js.

Similar to the data viewer cell, the implementation of the output viewer hinges on interceding before the cell type currently handling app output is inserted into the Narrative.

And also similar to the data viewer cell, the app output specification requires a specific format to indicate that a service widget should be used, and provides the service widget's dynamic service module name and widget name.

Summary of file changes

  • nbextensions/appCell2/appCellWidget.js modified to support inserting a service widget if the app spec specifies it
  • kbase-extension/static/kbase/js/util/icon.js modified to add an app output icon based on the app icon itself

Javascript Changes

In the app cell execution lifecycle, an output cell may be inserted after the app has successfully completed. Whether to insert an output widget, which one to insert, and what parameters to provide it are all specified in the ui configuration for the app itself. We'll discuss that in the section below.

It is in the app cell implementation that the output cell is inserted into the Narrative, just below the app cell itself. The code responsible for this is in the createOutputCell function in nbextensions/appCell2/appCellWidget.js.

The code previously had simply inserted an output cell. To support inserting a widget cell instead required few changes, as all relevant information was already available. The widget name is inspected, and if it matches "ServiceWidget" will generate a serviceWidget cell specification rather than an output widget.

Icon Changes

Icon support in jsutils/icons.js was extended to provide support for an "app output" icon. The current app output icon is a left-pointing arrow. In order to better associate the output cell with the app cell that generated it, the icon for app output viewers implemented as service widgets is a compound icon with the original app icon on the left, and on the right a "right-pointing" arrow.

This change is speculative. I feel we need a better way of associating an output cell with it's "parent" app cell, but I'm not sure this is it.

App Requirements

As described above, the Javascript implementation of app output cell injection is straightforward, requiring few effective lines of code to be changed or added. These changes, however, rely upon a specific structure in the app's ui configuration. These changes are very similar to that for data viewers.

In the ui/narrative/methods/APP_NAME (where APP_NAME is the name of the app you are implementing) directory of https://github.com/kbase/NarrativeViewers, the spec.json file has the following requirements:

  • The widget output provided at widgets.output must be "ServiceWidget"
  • The output mapping provided at behavior.service-mapping.output_mapping must provide:
    • Service widget module name in a constant_value with a target_property of "service_module_name"
    • Widget name in a constant_value with a target_property of "widget_name"
  • In addition, the output mapping should provide a title and subtitle for the resulting cell. These are targeted at the cell itself, and not the service widget. They can be any type of output mapping, but it is probably most appropriate for them to be provided as app output (i.e. emitted by the app after it has run), as this allows them to be created on the fly by the widget app itself:
    • The cell title is provided by the target_property "title"
    • The cell subtitle is provided by the target_property "subtitle"

All other outputs, other than the module name, widget name, title, and subtitle, are passed directly to the service widget as parameters.

For example, from an example app in CI (https://github.com/eapearson/eapearsonNarrativeViewersTest):

{
    "ver": "0.0.2",
    "authors": [
        "eapearson"
    ],
    "contact": "[email protected]",
    "categories": ["active"],
    "widgets": {
        "input": null,
        "output": "ServiceWidget"
    },
    "parameters": [ 
        {
            "id": "genome_ref",
            "optional": false,
            "advanced": false,
            "allow_multiple": false,
            "default_values": [ "" ],
            "field_type": "text",
            "text_options": {
                "valid_ws_types": ["KBaseGenomes.Genome"]
            }
        },
        {
            "id": "scientific_name",
            "optional": true,
            "advanced": false,
            "allow_multiple": false,
            "default_values": [ "" ],
            "field_type": "text"
        },
        {
            "id": "output_genome_name",
            "optional": false,
            "advanced": false,
            "allow_multiple": false,
            "default_values": [""],
            "field_type": "text",
            "text_options": {
                "valid_ws_types" : [ "KBaseGenomes.Genome" ],
                "is_output_name":true
            }
        }
    ],
    "behavior": {
        "service-mapping": {
            "url": "",
            "name": "eapearsonDemoGenomeEditClassification",
            "method": "update_genome_classification_metadata",
            "input_mapping": [
                {
                    "narrative_system_variable": "workspace_id",
                    "target_property": "workspace_id"
                }, 
                {
                    "input_parameter": "genome_ref",
                    "target_property": "genome_ref"
                }, 
                {
                    "input_parameter": "scientific_name",
                    "target_property": "scientific_name"
                }
            ],
            "output_mapping": [
                {
                    "service_method_output_path": [0, "output_genome_ref"],
                    "target_property": "output_genome_ref"
                },
                {
                    "service_method_output_path": [0, "change_log"],
                    "target_property": "change_log"
                },
                {
                    "service_method_output_path": [0, "title"],
                    "target_property": "title"
                },
                {
                    "service_method_output_path": [0, "subtitle"],
                    "target_property": "subtitle"
                },
                {
                    "constant_value": "eapearsonWidgetTest10",
                    "target_property": "service_module_name"
                },
                {
                    "constant_value": "demo_genome_edit_classification_viewer",
                    "target_property": "widget_name"
                }
            ]
        }
    },
    "job_id_output_field": "docker"
}

Developer Tools

In early development, in the days before the data viewer and app output integrations had been added, a developer tool was created to enable direct insertion of service widgets into a Narrative.

Feature detection

The developer tool is enabled through the existing feature detection mechanism, with one enhancement - supplying a set of enabled features in the URL. This addition allows one to enable a feature on the fly, rather than having to modify config/features.json (or use Jupyter userSettings - but I don't know if we support it).

The code for supporting feature detection and the developer feature in particular, is present in common/ui.js. However, the UI object requires a DOM node and where we need feature detection it doesn't make any sense to pass a DOM node to the UI object.

So the necessary code was moved to common/runtime.js, which seems a more appropriate home, as the Runtime object does not require a DOM node. A few other small changes were made to the runtime module, for the sake of simplifying the code and module.

The existing code in ui.js was refactored to call the code in runtime.js. I looked for existing usages of the feature detection code, and it wasn't fully clear how it was used, although it is not used widely, and some of the code that uses it is not currently in use.

Below are the existing features and their usage. Some use the features configuration directly and are unafected by the feature api changes. Such calls look like Config.get('features').staticNarratives.

Two utilize the feature api calls, but appear to be unused. Well, isDeveloper() and ifAdvanced() are utilized in a few locations in the view cell widget code, but the view cell widget is not used.

Feature Usage
stagingDataViewer Config
jgiPublicStaging unused
advanced isAdvanced() unused
ifAdvanced() used in viewCellWidget but code using it is not displayed
developer isDeveloper() in view cell
config('features.developer') also used in viewCellWidget.js, code using them is not displayed
lazyWidgetRender config
batchAppMode config
hierarchicalDataView config
staticNarratives config

In any case, this can be discussed.

It isn't clear to me that the Jupyter userSettings feature is used - I don't know how it could be because it would need to be saved in the user's KBase user profile to be persistent.

In our case, the feature we are interested in is "developer", which we can enable in the URL

https://ci.kbase.us/narrative/71571?features=developer

Or in kbase-extension/static/kbase/config/feature-config.json:

{
    "developer": true
}

Ad-hoc service widget tool

In developer mode, a new "developer" menu item appears on the top horizontal menu. It drops down two menu items:

  • Insert Service Widget Object Viewer
  • Insert Generi Service Widget Object

Selecting either of these will display a dialog box in which the service widget parameters are entered.

Implementing this involved the following file changes:

  • kbase-extension/kbase_templates/narrative_header.html adds the new dropdown menu. It is hidden by default, will be shown if the narrative is loaded with the 'developer' flag set
  • kbase-extension/static/kbase/js/kbaseNarrative.js provides support for enabling the menu, as well as handling the menu items, as it does for other header menu items.
  • static/kbase/js/util/bootstrapDialog.js received an update to support both the dialog size as well as the show.bs.modal event. These changes are required in order to show and handle the service widget developer tool pop-ups.

Other developer affordances

In addition, because I needed to insert and re-run service widget cells repeatedly, and manipulate cells extensively, I added a few conveniences that are enabled in developer mode:

  • apply min/max to all cells in the same state with Alt+click
  • new toolbar icons for:
    • Re-run code
    • Open/close code view
    • delete cell
    • move cell to top
    • move cell to bottom

Maybe we won't keep these? Can always remove from the PR later.

  • Please include a summary of the change and which issue is fixed.
  • Please also include relevant motivation and context.
  • List any dependencies that are required for this change.

Jira Ticket / Issue

Related Jira ticket: https://kbase-jira.atlassian.net/browse/DATAUP-X

  • Added the Jira Ticket to the title of the PR (e.g. DATAUP-69 Adds a PR template)

Testing Instructions

  • Details for how to test the PR:
  • Tests pass locally and in GitHub Actions
  • Changes available by spinning up a local narrative and navigating to X to see Y

Dev Checklist:

  • My code follows the guidelines at https://sites.google.com/lbl.gov/trussresources/home?authuser=0
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • (JavaScript) I have run Prettier and ESLint on changed code manually or with a git precommit hook
  • (Python) I have run Black and Flake8 on changed Python code manually or with a git precommit hook
  • Any dependent changes have been merged and published in downstream modules

Updating Version and Release Notes (if applicable)

@eapearson
Copy link
Contributor Author

eapearson commented Jan 19, 2024

Hi @briehl , this is the WIP PR for adding service widgets to the Narrative.
I'll ping you in slack as well.
Although a fair number of files, although most of them are new and part of the service widget cell and friends.
The most controversial aspect may be that I'm attempting to use preact again. If that seems too much (but I'd urge you to try to humor me :) ), I can indeed rewrite them as jquery widgets.

@briehl
Copy link
Member

briehl commented Jan 29, 2024

I think using preact is fine, spending time reworking into jquery isn't gonna help that much.

I'm still picking through reading this, as there's a lot to review. But we can get it up on CI anyway to keep you unblocked.

@eapearson
Copy link
Contributor Author

Thanks @briehl! The service widgets team will be elated!

I'll catch up the branch with develop later before we deploy it on CI, and get those merge conflicts resolved.

Also, I've been meaning to confirm this - it looks like each PR includes all_concat.css and all_concat.css.map? Wouldn't it be better to not include the built assets like that and to have the build create them? I looked briefly some weeks ago, and didn't see that this was done, and inspected a few PRs to see if the css file builds were included, and it looked like it.

Of course, I'm familiar with this practice. That is how old kbase-ui plugins worked, as there was no "build" for them on the github side.

Not a big deal, but I know the Narrative has some other files that get changed during development that should be kept out of commits, like the config and narrative version files.

c'mon peeps, lets start the (p)React train!
or at least try another idea.
@briehl
Copy link
Member

briehl commented Mar 14, 2024

@ialarmedalien I think we just updated to node v20.11.1 (the current LTS) on the image. I'm not sure about which npm version is required, but I'm guessing the recent LTS should work best.

@eapearson
Copy link
Contributor Author

eapearson commented Mar 14, 2024

@briehl is there a new base image? I just opened a terminal into the 7.1.0 image and see:

(venv) erikpearson@Eriks-MacBook-Pro narrative % docker run -it ghcr.io/kbase/narrative-base-image:7.1.0 bash
root@970e2b5fe08f:/# node --version
v16.17.0
root@970e2b5fe08f:/# npm --version
8.15.0
root@970e2b5fe08f:/# 

I was about to mention that the npm install is hanging in a the base image container ... but it just takes about 10-15 minutes to for msw (or rxjs subdependency). I had noticed that the image build appeared to be hanging, so opened a bash shell into a base image container, did a fresh install and didn't get much more information than that. npm install works fine on 8.19.4 macOS, but in the container has this hanging issue (which does eventually resolve.) I tried with the original package and lock file, and with one updated with prop types, which forces a lock file rebuild.

@eapearson
Copy link
Contributor Author

Actually, it was hanging on nodemon, but that is a transitive dependency of the catalog plugin, which I temporarily removed since it is for just one small yaml file, and then it began to hang on msw. I figured nodemon, being os-dependent, was the culprit, but not. It still could be an os-dependent binary component, though, as it does not hang on macos.

@eapearson
Copy link
Contributor Author

I forgot to mention that the extra detail inside the container is that "reify:rxjs" is revealed:

npm timing build:run:postinstall:node_modules/nodemon Completed in 142ms
(##################) ⠸ reify:rxjs: timing build:run:postinstall:node_modules/nodemon Completed in 142ms

and finally

npm timing npm Completed in 670141ms

many many people have reported this for node 16 and whatever npm shipped with it, but it really isn't worth pursuing if we're moving on to node 20 and npm 10.

@eapearson
Copy link
Contributor Author

@briehl So I went down this rabbit hole, and managed to get preact debug and devtool support configured for requirejs, preact_compat configured and working, and prop-types installed configured as well. Also preact devtools in the browser (FF). However, so far cannot get preact debug mode enabled, which is required for propTypes to be activated. I added a "gallery" page for the ErrorAlert component, and can get everything wired up so that it renders correctly, but just cannot get propTypes to do anything. There are other features of debug and devtool support, like detecting incorrect usage of a component. All of these are supported through detecting the conditions and issuing warnings to the console.

The preact devtool extension works and recognizes preact, so it is at least partially working.

One of the tricks seems to be to get the correct load order of debug and preact, and to satisfy the conditions for debug mode to activate itself, but so far no dice. It is not very well (extensively) documented, and certainly not for UMD/AMD usage. I'm going to stop this for now, but may contact the preact team to see if this use case is supported and, if so, how.

@eapearson
Copy link
Contributor Author

@briehl I can check in the changes with the seemingly correct dependencies, to preserve that work.

and rebuilt package-lock.json [PTV-2905]
@eapearson
Copy link
Contributor Author

eapearson commented Apr 2, 2024

@briehl I've asked in the preact slack about getting prop types working with the requirejs/amd scenario.
Support for this type of web app is getting very scant these days ... there is no mention in their docs any longer - the only non-bundle configuration they mention is via import and cdns.

So ... I'm not sure it is worth adding the dependencies. Maybe if I get a useful response we can add it.

@eapearson
Copy link
Contributor Author

@briehl Actually, I just got it working. The docs say that it should issue a console warning on a validation fail, so I was filtering the console on warnings, but then I saw someone say that it is actually issued as console.error, but with the message prefix of "Warning: ..." I would have surely have eventually noticed the errors...

E.g.

Failed prop type: Invalid prop title of type number supplied to ErrorAlert, expected string.

At least the error message is no longer prefixed with "Warning:" !

@eapearson
Copy link
Contributor Author

Now that it is working, I'll go back and add proptypes to all of the preact components.

@briehl
Copy link
Member

briehl commented Apr 2, 2024

@eapearson I'm still somewhat wrestling with dependencies and builds between the narrative-base-image and narrative repos. Hopefully those will get ironed out soon - right now there's some change that's affecting unit tests in an async/hard to repeat way.

Then we can see if (unit, at least) tests will pass and try to finalize a review here.

@eapearson
Copy link
Contributor Author

I'll keep poking at this every couple of days. Got through one batch of prop-types, will do another round next week.
It is easy to use and assess prop-types with small, simple components. We'll see with bigger ones.

@eapearson
Copy link
Contributor Author

Had another look at the object spread issue, since it is the source of most of the sonar issues. It ends up that esprima, which has been poorly maintained and has a longstanding bug wrt object spread, is used by two of our dependencies - terser and requirejs's r.js.

Terser has a solution, which is to use acorn for parsing. Requirejs, however, has not really been touched in years, so is very unlikely to ever be switched away from esprima, which itself is unlikely to ever be fixed.

Interestingly, eslint used to use esprima, but switched away to to espree/acorn.

@eapearson
Copy link
Contributor Author

@briehl prop-type support has been added to all the preact components. Tests to cover then can be added at some point - they will just have to test that the appropriate warning is issued to the console. For now, I added gallery pages for some of the affected components, so that at least one can poke at the components with props that violate the type specs, and manually inspect the warnings.

Copy link

sonarcloud bot commented Apr 30, 2024

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarCloud

Catch issues before they fail your Quality Gate with our IDE extension SonarLint

Copy link
Member

@briehl briehl left a comment

Choose a reason for hiding this comment

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

A long time coming, but I think - technically - this is great. I still kinda have issues with the overall design of the project and implications it'll have, but the code here in this PR isn't a reflection on that, and is fine. I have a few comments that I've built up over time spent reviewing, but I think they can be addressed in a future PR.

Two new external libraries are added; `preact` and `htm` have been added to support a
more familiar (dare we say "modern"?) style of component architecture.

> This decision can be reversed, and we can rewrite the components in jquery, but the
Copy link
Member

Choose a reason for hiding this comment

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

No need to rewrite, IMO.

}
renderContent() {
if (this.props.render) {
return this.props.render();
Copy link
Member

Choose a reason for hiding this comment

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

I might be misreading this, but will this cause some strange looping if the render prop is the same as the render function?

What's the render prop expected to be here?

Copy link
Member

Choose a reason for hiding this comment

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

I may have made this comment before you started adding PropTypes

require(['jquery', 'narrativeConfig', 'narrativeLogin'], ($, Config, Login) => {
console.log('Loading KBase Narrative setup routine.');

require(['jquery', 'narrativeConfig', 'narrativeLogin', 'preact_debug'], ($, Config, Login) => {
Copy link
Member

Choose a reason for hiding this comment

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

How does this differ from preact? Is it preferred over that?

Comment on lines 32 to 38
.gallery h2 {
}


.gallery h3 {
}

Copy link
Member

Choose a reason for hiding this comment

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

Are the empty ones necessary?

* @param {string} featureName The name of the given feature.
* @returns {boolean} Whether the given feature is enabled
*/
function isFeatureEnabled(featureName) {
Copy link
Member

Choose a reason for hiding this comment

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

This should figure out all enabled features in the factory constructor, and only do it once per session. Right now, if I make a cell, then delete the slug with the feature, and make another cell, they'll behave differently, which is kinda weird.

Also, it sorta hammers the browser to look up this stuff every time a new Runtime is needed.

console.warn('Already the first cell!');
return;
}
Jupyter.notebook.move_cell_up();
Copy link
Member

Choose a reason for hiding this comment

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

Why use move_cell_up / down here and move_selection_up / down above?
move_cell_up throws a deprecation warning anyway, and with the other changes, might as well change to move_selection_* here.

@@ -243,6 +414,24 @@ define([
}
}

const extraIcon = (() => {
Copy link
Member

Choose a reason for hiding this comment

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

What icon is this?

'preactComponents/RotatedTable',

// for effect
'css!./AddServiceWidgetDataViewer.css',
Copy link
Member

Choose a reason for hiding this comment

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

Is this needed? Shouldn't it just be part of the CSS stack?

const { Component } = preactCompat;
const html = htm.bind(h);

class ErrorAlert extends Component {
Copy link
Member

Choose a reason for hiding this comment

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

It would be ideal to go with functional components to be inline with Europa. I get that this would be a lot of changes, so it doesn't have to be done here. I also foresee having to do a lot of work to migrate to a new Narrative codebase (in pure React / TS) in the not-too-distant future, so maybe don't worry about it now.

Copy link
Member

Choose a reason for hiding this comment

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

Noting that any major narrative codebase changes will be to support the new versions of Jupyter Notebook / Jupyterlab

delete constantParams.service_module_name;
delete constantParams.widget_name;

const params = Object.assign({}, constantParams, { ref });
Copy link
Member

Choose a reason for hiding this comment

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

Why not just set constantParams[ref] = ref, and use constantParams below?

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