-
Notifications
You must be signed in to change notification settings - Fork 356
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
chore: introduce and use observables with improved update checking #8405
Conversation
✅ Deploy Preview for determined-ui ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
#resourcePools: WritableObservable<Loadable<ResourcePool[]>> = observable(NotLoaded); | ||
#unboundResourcePools: WritableObservable<Loadable<ResourcePool[]>> = observable(NotLoaded); | ||
#resourcePoolBindings: WritableObservable<Map<string, number[]>> = observable(Map()); | ||
#agents = deepObservable<Loadable<Agent[]>>(NotLoaded); |
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.
With the introduction of asValueObject
and immutableObservable
it does open a question of whether we should be using List
from immutable.js.
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.
the top value being observed here isn't an immutable collection, though -- it's a Loadable of an immutable collection. The issue here is that neither observable type works for that combo. deepObservable
will trip over the immutable value because two collections that have the same values can have different state, and without a way of describing a immutable collection in io-ts
, there isn't a way of detecting that when putting together the equality function in asValueObject
.
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.
We should add the equals
and hashCode
methods to Loadable
. Since it's a class we can do that easily.
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.
without a way of describing a immutable collection in io-ts
Worth noting that this would be pretty easy to add. Obviously out of scope for this PR but I think it would increase consistency.
|
||
public readonly agents = this.#agents.readOnly(); | ||
public readonly resourcePoolBindings = this.#resourcePoolBindings.readOnly(); | ||
public readonly resourcePoolBindings = this.#resourcePoolBindings.select((bindings) => | ||
bindings.map((workspaceIds) => workspaceIds.toJS()), |
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.
Any particular reason you didn't want to return an OrderedSet?
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.
For this pr, I wanted to make sure the only changes were to the bookkeeping done inside the stores -- external types should remain the same so we know that if bugs occur it's because of the update checking, not the type change.
@@ -1,47 +1,45 @@ | |||
import { Loadable, Loaded, NotLoaded } from 'hew/utils/loadable'; |
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.
@mapmeld Can you review this file?
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.
OK I checked permissions were working as expected on OSS (Model Registry) and EE (adding and viewing members of workspaces as admin or general user)
const otherExtra = asValueObject(codec, { ...testVal, extra: 2 } as any); | ||
const map = Map({ key: valueObj }); | ||
|
||
describe('asValueObject', () => { |
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.
These tests are a good case for fast-check.
webui/react/src/types.ts
Outdated
export type JsonArray = Array<Json>; | ||
export const JsonArrayC: t.Type<JsonArray> = t.recursion('JsonArray', () => t.array(JsonC)); |
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.
The runtime types should have the same name as the compile-time types.
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.
Rebase off main to fix the build error with Json
webui/react/src/types.ts
Outdated
t.record(t.string, Json), | ||
); | ||
|
||
// export type Json = string | number | boolean | null | JsonArray | JsonObject; |
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.
remember to remove comment
this introduces two patterns that make it easier for developers to work with observable values without introducing extra renders and events: 1) `deepObservable` and `immutableObservable`: These observables do an extra check before setting the observable value. `deepObservable` does a deep equality check, and `immutableObservable` uses `Immutable.is`. If the old and new values are the same via this check, we throw out the new value. 2) `asValueObject`: the `immutableObservable` type from above by itself isn't enough to solve the problem for our usage of immutables, becuase we use plain javascript objects and arrays inside immutable objects. By default, immutable uses reference equality to determine if two immutable collections are the same, e.g. ```ts import { is, Map } from 'immutable' const map = Map({ 'hi': 'hello' }) is(map.set('hi', 'hello'), map) // true const newMap = map.set('bye', {}) is(newMap.set('bye', {}), newMap) // false ``` however, if an object implements the functions `hashCode: () => number` and `equals: (v: unknown) => boolean`, `Immutable.is` will use those functions to determine whether or not two objects are the same. To facilitate doing this, we introduce `asValueObject`. `asValueObject` takes a codec and a object implementing that codec and returns a version of the value implementing `ValueObject`. The resulting value object uses the codec to determine which values on the object to check. Checking happens as deep as the codec goes.
5322110
to
646fb94
Compare
Description
this introduces two patterns that make it easier for developers to work with observable values without introducing extra renders and events:
deepObservable
andimmutableObservable
: These observables do anextra check before setting the observable value.
deepObservable
does adeep equality check, and
immutableObservable
usesImmutable.is
. Ifthe old and new values are the same via this check, we throw out the new
value.
asValueObject
: theimmutableObservable
type fromabove by itself isn't enough to solve the problem for our usage of
immutables, because we use plain javascript objects and arrays inside
immutable objects. By default, immutable uses reference equality to
determine if two immutable collections are the same, e.g.
however, if an object implements the functions
hashCode: () => number
and
equals: (v: unknown) => boolean
,Immutable.is
will use thosefunctions to determine whether or not two objects are the same. To
facilitate doing this, we introduce
asValueObject
.asValueObject
takes a codec and a object implementing that codec and returns a version
of the value implementing
ValueObject
. The resulting value object usesthe codec to determine which values on the object to check against and
how to hash the object. Checking happens as deep as the codec goes,
falling back to value equality, so props that aren't in the codec aren't
checked and props that have indeterminate types (like
any
,unknown
,or
never
) will use===
to check equality.Test Plan
as an admin, visit the cluster overview page:
as an admin, visit the user management page:
when viewing the workspace list:
Commentary (optional)
A minor caveat with
asValueObject
is that it works via spreading the input object -- as such, it requires the input codec to be an object type or interface, not an array. This means that observables that use immutable types of things that are arrays are still uncovered. The user settings store is working around this by checking manually, and the cluster and workspace stores work around this by using immutable versions of the data structures they were using in the first place.Checklist
docs/release-notes/
.See Release Note for details.
Ticket
WEB-1762