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

Persistence Manager #733

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
28 changes: 20 additions & 8 deletions client-app/src/examples/portfolio/GridPanelModel.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import {HoistModel, managed} from '@xh/hoist/core';
import {bindable, makeObservable} from '@xh/hoist/mobx';
import {action, bindable, makeObservable} from '@xh/hoist/mobx';
import {GridModel, TreeStyle} from '@xh/hoist/cmp/grid';
import {PERSIST_MAIN} from './AppModel';
import {mktValCol, nameCol, pnlCol} from '../../core/columns';
import {PortfolioPanelModel} from './PortfolioPanelModel';
import {capitalize} from 'lodash';

export class GridPanelModel extends HoistModel {
@bindable loadTimestamp: number;

@managed
gridModel: GridModel;
@managed gridModel: GridModel;

parentModel: PortfolioPanelModel;

Expand All @@ -22,16 +20,30 @@ export class GridPanelModel extends HoistModel {
return this.parentModel.groupingChooserModel.value.map(it => capitalize(it)).join(' › ');
}

constructor({parentModel}) {
constructor({persistWith, parentModel}) {
super();
makeObservable(this);
this.parentModel = parentModel;
this.gridModel = this.createGridModel();
this.gridModel = this.createGridModel(persistWith);
}

private createGridModel() {
@action
updateState(newState) {
const {gridModel} = this;
const gridPm = gridModel.persistenceModel;
gridPm.state = newState.portfolioAppGridState;

gridPm.updateGridColumns();
gridPm.updateGridSort();
}

//------------------
// Implementation
//------------------

private createGridModel(persistWith) {
return new GridModel({
persistWith: PERSIST_MAIN,
persistWith: {path: 'portfolioAppGridState', ...persistWith},
treeMode: true,
treeStyle: TreeStyle.HIGHLIGHTS_AND_BORDERS,
sortBy: 'pnl|desc|abs',
Expand Down
11 changes: 11 additions & 0 deletions client-app/src/examples/portfolio/PortfolioPanel.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import {hframe} from '@xh/hoist/cmp/layout';
import {creates, hoistCmp} from '@xh/hoist/core';
import {panel} from '@xh/hoist/desktop/cmp/panel';
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
import {detailPanel} from './detail/DetailPanel';
import {gridPanel} from './GridPanel';
import {mapPanel} from './MapPanel';
import {PortfolioPanelModel} from './PortfolioPanelModel';
import {persistenceManager} from '@xh/hoist/desktop/cmp/persistenceManager';

export const portfolioPanel = hoistCmp.factory({
model: creates(PortfolioPanelModel),

render() {
return panel({
tbar: tbar(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the tbar component is so minimal and doesn't react to any state changes, I would just specify the tbar items inline (i.e. tbar: [persistenceManager()])

mask: 'onLoad',
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably want to mask while the PersistenceManagerModel is loading views for the first time. Otherwise, this will render unmasked with whatever the default view is (in code) and then jump to the persisted view once it loads

items: [hframe(gridPanel(), mapPanel()), detailPanel()]
});
}
});

const tbar = hoistCmp.factory<PortfolioPanelModel>({
render() {
return toolbar({
item: persistenceManager()
});
}
});
86 changes: 79 additions & 7 deletions client-app/src/examples/portfolio/PortfolioPanelModel.ts
Copy link
Contributor

Choose a reason for hiding this comment

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

In general, this is an annoying amount of boilerplate you had to write! I wonder if there'd be a way to generalize this and build a higher order component / model to avoid some of this. Just brainstorming - what if the PersistenceManager component was a container that accepted a single child component that it would not render until it was done initializing. Big issue here is that ideally the child would create its model, so that it would not be constructed until the manager finished initializing. BUT we would need a reference to that model in order to wire it. Wonder if we could use modelRef to do that... but we wouldn't want whatever we do to be even more complicated / harder to reason about than what you have here...

Sorry for the flight of ideas - will think more on this, and maybe we can brainstorm together.

Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import {HoistModel, managed, XH} from '@xh/hoist/core';
import {Store} from '@xh/hoist/data';
import {logInfo} from '@xh/hoist/utils/js';
import {GridPanelModel} from './GridPanelModel';
import {round} from 'lodash';
import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
import {PERSIST_MAIN} from './AppModel';
import {waitFor} from '@xh/hoist/promise';
import {wait, waitFor} from '@xh/hoist/promise';
import {SECONDS} from '@xh/hoist/utils/datetime';
import {PersistenceManagerModel} from '@xh/hoist/desktop/cmp/persistenceManager';
import {DetailPanelModel} from './detail/DetailPanelModel';
import {runInAction} from '@xh/hoist/mobx';

export class PortfolioPanelModel extends HoistModel {
@managed session;

@managed groupingChooserModel = this.createGroupingChooserModel();
@managed persistenceManagerModel: PersistenceManagerModel;
@managed groupingChooserModel: GroupingChooserModel;
@managed store = this.createStore();
@managed gridPanelModel = new GridPanelModel({parentModel: this});
@managed gridPanelModel: GridPanelModel;
@managed detailPanelModel: DetailPanelModel;

get prefKey(): string {
return 'portfolioExample';
}

get selectedPosition() {
return this.gridPanelModel.selectedRecord;
Expand All @@ -21,16 +30,40 @@ export class PortfolioPanelModel extends HoistModel {
constructor() {
super();
const wsService = XH.webSocketService;

this.persistenceManagerModel = new PersistenceManagerModel({
type: 'portfoioExample',
noun: 'portfoio',
Copy link
Contributor

Choose a reason for hiding this comment

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

sp: portfolio :)

canManageGlobal: () => XH.getUser().hasRole('HOIST_ADMIN'),
onChangeAsync: () => this.onViewChangeAsync(),
newObjectFnAsync: async () => ({
portfolioAppGridState: {},
portfolioAppDetailState: {},
groupingChooser: {value: ['region', 'sector', 'symbol'], favorites: []}
}),
persistWith: {prefKey: this.prefKey}
});

this.groupingChooserModel = this.createGroupingChooserModel();
this.detailPanelModel = this.createDetailPanelModel();
this.gridPanelModel = this.createGridPanelModel();

this.addReaction({
track: () => [this.groupingChooserModel.value, wsService.connected],
run: () => this.loadAsync()
});
this.addReaction({
track: () => this.selectedPosition,
run: position => {
this.detailPanelModel.positionId = position?.id ?? null;
},
debounce: 300
});
}

override async doLoadAsync(loadSpec) {
const wsService = XH.webSocketService,
{store, groupingChooserModel, gridPanelModel} = this,
dims = groupingChooserModel.value;
{store, gridPanelModel} = this;

let {session} = this;
session?.destroy();
Expand All @@ -41,6 +74,9 @@ export class PortfolioPanelModel extends HoistModel {
);
if (loadSpec.isStale) return;

const dims = this.persistenceManagerModel.provider.getData().groupingChooser?.value;
if (!dims) return;

session = await XH.portfolioService.getLivePositionsAsync(dims, 'mainApp').catchDefault();

store.loadData([session.initialPositions.root]);
Expand Down Expand Up @@ -79,7 +115,43 @@ export class PortfolioPanelModel extends HoistModel {
return new GroupingChooserModel({
dimensions: ['fund', 'model', 'region', 'sector', 'symbol', 'trader'],
initialValue: ['region', 'sector', 'symbol'],
persistWith: PERSIST_MAIN
persistWith: {...this.persistenceManagerModel.provider},
allowEmpty: true
});
}

private createGridPanelModel() {
return new GridPanelModel({
persistWith: this.persistenceManagerModel.provider,
parentModel: this
});
}

private createDetailPanelModel() {
return new DetailPanelModel({
persistWith: this.persistenceManagerModel.provider,
parentModel: this
});
}

private async onViewChangeAsync() {
const start = Date.now();

await wait(); // allow masking to start

const {persistenceManagerModel, gridPanelModel, detailPanelModel, groupingChooserModel} =
this,
newState = persistenceManagerModel.provider.getData();
groupingChooserModel.setValue(newState.groupingChooser?.value ?? []);
gridPanelModel.updateState(newState);

runInAction(() => {
const detailPm = detailPanelModel.ordersPanelModel.gridModel.persistenceModel;
detailPm.state = newState.portfolioAppDetailState;
detailPm.updateGridColumns();
detailPm.updateGridSort();
});

logInfo(`Rebuilt view | took ${Date.now() - start}ms`, this);
}
}
4 changes: 2 additions & 2 deletions client-app/src/examples/portfolio/detail/DetailPanel.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {hframe, placeholder} from '@xh/hoist/cmp/layout';
import {hoistCmp, creates} from '@xh/hoist/core';
import {hoistCmp, uses} from '@xh/hoist/core';
import {panel} from '@xh/hoist/desktop/cmp/panel';
import {Icon} from '@xh/hoist/icon/Icon';
import {chartsPanel} from './charts/ChartsPanel';
import {DetailPanelModel} from './DetailPanelModel';
import {ordersPanel} from './OrdersPanel';

export const detailPanel = hoistCmp.factory({
model: creates(DetailPanelModel),
model: uses(DetailPanelModel),

render({model}) {
const {panelSizingModel, positionId} = model;
Expand Down
21 changes: 7 additions & 14 deletions client-app/src/examples/portfolio/detail/DetailPanelModel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {HoistModel, lookup, managed} from '@xh/hoist/core';
import {HoistModel, managed} from '@xh/hoist/core';
import {observable, makeObservable} from '@xh/hoist/mobx';
import {PanelModel} from '@xh/hoist/desktop/cmp/panel';
import {OrdersPanelModel} from './OrdersPanelModel';
Expand All @@ -8,8 +8,7 @@ import {PortfolioPanelModel} from '../PortfolioPanelModel';
export class DetailPanelModel extends HoistModel {
@observable positionId = null;

@lookup(PortfolioPanelModel) parentModel;
@managed ordersPanelModel = new OrdersPanelModel(this);
@managed ordersPanelModel: OrdersPanelModel;

@managed panelSizingModel = new PanelModel({
defaultSize: 400,
Expand All @@ -20,22 +19,16 @@ export class DetailPanelModel extends HoistModel {
persistWith: PERSIST_DETAIL
});

parentModel: PortfolioPanelModel;

get collapsed() {
return this.panelSizingModel.collapsed;
}

constructor() {
constructor({persistWith, parentModel}) {
super();
makeObservable(this);
}

override onLinked() {
this.addReaction({
track: () => this.parentModel.selectedPosition,
run: position => {
this.positionId = position?.id ?? null;
},
debounce: 300
});
this.parentModel = parentModel;
this.ordersPanelModel = new OrdersPanelModel({persistWith, parentModel: this});
}
}
4 changes: 2 additions & 2 deletions client-app/src/examples/portfolio/detail/OrdersPanelModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class OrdersPanelModel extends HoistModel {
return this.parentModel.positionId;
}

constructor(parentModel) {
constructor({persistWith, parentModel}) {
super();

this.parentModel = parentModel;
Expand All @@ -42,7 +42,7 @@ export class OrdersPanelModel extends HoistModel {
enableExport: true,
rowBorders: true,
showHover: true,
persistWith: {...PERSIST_DETAIL, path: 'ordersGrid'},
persistWith: {path: 'portfolioAppDetailState', ...persistWith},
columns: [
{...symbolCol, pinned: true},
{...closingPriceSparklineCol},
Expand Down
Loading