neighbour-hoods / sensemaker-lite

11 stars 1 forks source link

Create Widget Registry API #90

Closed adaburrows closed 11 months ago

adaburrows commented 11 months ago

We need an API to register the widgets provided by each applet, so that we can easily look up all the provided widgets and the ranges they support. Basically, we will take the components registered in the applet as below and put them into the API so we can know which applet provides which widget and what range it applies to.

The future applet interface will look like so:

const feedApplet: NeighbourhoodApplet = {
  appletConfig: {
    "name": "todo_applet",
    "resource_defs": [{
      "role_name": "todo_lists",
      "zome_name": "todo",
      "name": "task_item",
      "base_types": [{ "entry_index": 0, "zome_index": 0, "visibility": { "Public": null } }],
    }],
    // This is going to be changed in the next release, or whenever we figure out how contexts are configured by the CA
    "cultural_contexts": [mostImportantTasksContext, hottestTasksContext]
  },
  appletRenderers: {
    "full": CustomApp
  },
  resourceRenderers: {
    "task_item": ResourceRenderer
  },
  assessmentWidgets: {
    "importance": {
      name: "Importance Ranker",
      range: {
        min: 0,
        max: 10
      },
      component: ImportanceWidget,
      kind: "input"
    }
  }
}

We should have a AssessmentWidgetRegistration object that gets created upon applet registration from the above entries for each widget entry:

type AssessmentWidgetRegistration = {
  applet_eh: EntryHash,
  widget_key: string,
  name: string,
  range_eh: EntryHash,
  kind: "input" | "output"
}

The important thing is that we return an array of all of the registered widgets for the config screen, regardless of whatapplet they came from and that we can look up the component from the matrix store based on the information stored in the AssessmentWidgetRegistration.

nick-stebbings commented 11 months ago

@adaburrows Am I correct in my understanding that the Assessment Widgets are not now bound to a dimension? Just a dimension kind of input/output?

If so, does this entail a revision of the work @weswalla did to create

pub struct DimensionAppletWidgetBinding {
    pub dimension_eh: EntryHash,
    // id of the AppletConfig stored in companion zome
    pub applet_id: EntryHash,
    // name of the component as exposed by the applet interface
    pub component_name: String,
}

which does in fact bind the dimensions to the widgets?

Furthermore.. is the component_name from above, and the widget_key from your initial comment refer to the same thing? If not, what iswidget_key?

adaburrows commented 11 months ago

Let me try and provide the context for this. When the applet is registered, we should be creating entries in this new API. It will replace/augment the in-memory widget registry that is in the SensemakerStore. Currently, when apps are registered they are only placing info about their components into memory and on reload, everything about them is gone (which is rather terrible).

This API will provide the list of all the components and provide a way to look them up from the applet matrix entries. It will also provide a way of displaying the components that are available in the widget tray configuration screen.

The mapping between dimensions and widgets is stored in the AssessmentWidgetConfiguration. So to display the whole tray, we need to fetch the AssessmentWidgetConfiguration and then fetch the associated applets from the matrix store and then fetch the components from the applet based on the widget_key.

So the example of:

const feedApplet: NeighbourhoodApplet = {
  //...
  assessmentWidgets: {
    "importance": { // widget_key
      name: "Importance Ranker",
      range: {
        min: 0,
        max: 10
      },
      component: ImportanceWidget,
      kind: "input"
    }
  }
}

// Registration type in TS, should be analogous in Rust

type AssessmentWidgetRegistration = {
  applet_eh: EntryHash, // Applet entry hash
  widget_key: string,  // the "importance" bit above
  name: string, // The "Importance Ranker" bit form above
  range_eh: EntryHash, // The EntryHash for the created Range object (should probably just be the range object when returned from the API)
  kind: "input" | "output"
}

// API

registerWidget(widget_registration: AssessmentWidgetRegistration);
getRegisteredWidgets(): Record<EntryHashB64, AssessmentWidgetRegistration);

We should probably also just change the newly implemented API from:

#[derive(Debug, Clone, Serialize, Deserialize, SerializedBytes)]
#[serde(rename_all = "camelCase")]
pub struct DimensionStandaloneWidgetBinding {
    pub dimension_eh: EntryHash,
    // id of the widget CustomElement stored in companion zome
    pub widget_eh: EntryHash,
}

#[derive(Debug, Clone, Serialize, Deserialize, SerializedBytes)]
#[serde(rename_all = "camelCase")]
pub struct DimensionAppletWidgetBinding {
    pub dimension_eh: EntryHash,
    // id of the AppletConfig stored in companion zome
    pub applet_id: EntryHash,
    // name of the component as exposed by the applet interface
    pub component_name: String,
}

to

#[derive(Debug, Clone, Serialize, Deserialize, SerializedBytes)]
#[serde(rename_all = "camelCase")]
pub struct DimensionWidgetBinding {
    pub dimension_eh: EntryHash,
    // Widget Registration object that has all data needed to load the component from the matrix
    pub widget_registry_eh: EntryHash,
}
nick-stebbings commented 11 months ago

@adaburrows I have a question re: applet config widget registration:

Are we to do a staged applet registration so that we can create Range entries before registering widgets?

type AssessmentWidgetRegistration = {
  applet_eh: EntryHash, // Applet entry hash
  widget_key: string,  // the "importance" bit above
  name: string, // The "Importance Ranker" bit form above
  range_eh: EntryHash, // The EntryHash for the created Range object (should probably just be the range object when returned from the API)
  kind: "input" | "output"
}

necessarily requires range_ehs, I am assuming at least some of them will be created from the same config. Also I am not sure about the comment.. does that mean the return object has both range and range eh? Like https://github.com/neighbour-hoods/sensemaker-lite/blob/74469cddd23967bd72850ded91fab91498eb8282/client/src/widgets/widget-registry.ts#L4-L14

adaburrows commented 11 months ago

Our current convention would require that:

export interface AssessmentWidgetRegistrationInput { 
   applet_eh: EntryHash, // Applet entry hash 
   widget_key: string,  // keyof an AssessmentWidgetConfigDict 
   name: string, 
   range: Range,
   kind: AssessmentWidgetKind 
 }

and therefore

export interface AssessmentWidgetRegistration { 
   applet_eh: EntryHash, // Applet entry hash 
   widget_key: string,  // keyof an AssessmentWidgetConfigDict 
   name: string, 
   range_eh: EntryHash, 
   kind: AssessmentWidgetKind 
 }

The when everything is queried, we should return the same AssessmentWidgetRegistrationInputs that we originally received so that the front end doesn't have to query the ranges separately.

However, if we can just store the ranges together in a nested object, that would also be acceptable, since those ranges have no use anywhere but the front end code.

nick-stebbings commented 11 months ago

@adaburrows So is the data flow like this?

Create - SensemakerService: AssessmentWidgetRegistrationInput -> ZomeFn: creates and returns AssessmentWidgetRegistration (TryInto, using hash_entry on the provided Range, and if TryInto fails then create_entry on the range)

Get - SensemakerService: EntryHash -> ZomeFn: returns AssessmentWidgetRegistrationInput (get on the EntryHash for the AssessmentWidgetRegistration, then get on the range_eh that was stored in the AssessmentWidgetRegistration to give the actual range again)

nick-stebbings commented 11 months ago

And it would make sense to discuss the update flow also.. since if the update parameters are the same as I was expecting

export type AssessmentWidgetRegistrationUpdateInput = {
  assessmentRegistrationEh: EntryHash,
  assessmentRegistrationUpdate: AssessmentWidgetRegistrationInput
}

then the implementation above would mean an update to the range values becomes more complicated: an update to a range that doesn't exist yet would mean a range creation at the same time. Makes more sense to me that you would update with a range_eh and not a full Range