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

Scenes performance: Measurement #858

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/scenes-app/src/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,6 @@ export function getDemos(): DemoDescriptor[] {
{ title: 'Interop with hooks and context', getPage: getInteropDemo },
{ title: 'Url sync test', getPage: getUrlSyncTest },
{ title: 'Machine Learning', getPage: getMlDemo },
{ title: 'Events on the Scene Graph', getPage: getSceneGraphEventsDemo},
{ title: 'Events on the Scene Graph', getPage: getSceneGraphEventsDemo },
].sort((a, b) => a.title.localeCompare(b.title));
}
140 changes: 76 additions & 64 deletions packages/scenes-app/src/demos/sceneGraphEvents.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react'
import React, { useEffect } from 'react';

import {
EmbeddedScene,
Expand All @@ -16,35 +16,23 @@ export function getSceneGraphEventsDemo(defaults: SceneAppPageState) {
...defaults,
subTitle: 'Illustrating how events traverse the scene graph',
getScene: () => {

const childrenKeys = Array.from("ABC");

const childrenKeys = Array.from('ABC');

const parentScene = new ExampleSceneObject({
key: "parentScene",
children:
[
...childrenKeys.map(key => new ExampleSceneObject(
{ key }
)),
new ExampleSceneObject(
{
key: "SUB",
children: childrenKeys.map(
(key) => new ExampleSceneObject(
{ key: `SUB-${key}` }
)
)
}
)
]
key: 'parentScene',
children: [
...childrenKeys.map((key) => new ExampleSceneObject({ key })),
new ExampleSceneObject({
key: 'SUB',
children: childrenKeys.map((key) => new ExampleSceneObject({ key: `SUB-${key}` })),
}),
],
});

return new EmbeddedScene({
key: "The Embedded Scene",
body: parentScene
})

key: 'The Embedded Scene',
body: parentScene,
});
},
});
}
Expand All @@ -55,15 +43,14 @@ interface ExampleState extends SceneObjectState {
}

class ExampleSceneObject extends SceneObjectBase<ExampleState> {

constructor(state: Partial<ExampleState>) {
const behaviors = state.$behaviors || [];
const children = state.children || [];
super({
...state,
children,
$behaviors: [...behaviors, listen],
})
});
}

static Component = ExampleSceneComponent;
Expand All @@ -72,15 +59,14 @@ class ExampleSceneObject extends SceneObjectBase<ExampleState> {
const listen = (sceneObject: ExampleSceneObject) => {
sceneObject.subscribeToEvent(ExampleEvent, (event) => {
sceneObject.setState({ recentEvent: event.payload });
})
}
});
};

class ExampleEvent extends BusEventWithPayload<string | undefined> {
public static type = 'example-event';
}

function ExampleSceneComponent({ model }: { model: ExampleSceneObject }) {

const { key, children, recentEvent } = model.useState();

const [eventFlash, setEventFlash] = React.useState(false);
Expand All @@ -91,43 +77,69 @@ function ExampleSceneComponent({ model }: { model: ExampleSceneObject }) {
setTimeout(() => setEventFlash(false), 500);
});
return subscription.unsubscribe;
}, [model, setEventFlash])
}, [model, setEventFlash]);

const theme = useTheme2();

const parentState = model.parent?.useState();

const triggerNonBubbleEvent = () => model.publishEvent(new ExampleEvent(`Non-bubble by ${key}`), false)
const triggerBubbleEvent = () => model.publishEvent(new ExampleEvent(`Bubble by ${key}`), true)
const clearDescendentEvents = () => sceneGraph.findDescendents(model, ExampleSceneObject).forEach(scene => scene.setState({ recentEvent: '' }))

return <div style={{ border: `${theme.colors.border.strong} 4px solid`, borderRadius: 8, padding: 4, margin: 8 }}>
<h2>I am: <em>{key}</em></h2>
<h4>My parent is: <em>{parentState?.key}</em></h4>
<Stack direction={'column'} alignItems={'flex-start'} >
<Button fullWidth={false} onClick={triggerBubbleEvent}>Bubble</Button>
<Button onClick={triggerNonBubbleEvent}>Non-Bubble</Button>
<Button onClick={()=>model.setState({recentEvent: ''})}>Clear</Button>
<Button onClick={clearDescendentEvents}>Clear Descendents</Button>
</Stack>
{
<div style={{ margin: 4, border: `${theme.colors.border.weak} solid 2px`, transitionDuration: '200ms', background: eventFlash ? theme.colors.action.selected : theme.colors.background.primary, transform: eventFlash ? 'scale(1.0, 1.5)' : '' }}>
{recentEvent && <>
<h3>Event:</h3>
<h5>{recentEvent}</h5>
</>
}
</div>
}
{children.length > 0 &&
<div style={{ border: `${theme.colors.border.medium} 3px solid`, borderRadius: 4, padding: 4, margin: 4, display: 'inline-block' }}>
Children of {key}:
<Stack>
{
children.map(child => <child.Component key={child.state.key} model={child} />)
}
</Stack>
</div>
}
</div>
const triggerNonBubbleEvent = () => model.publishEvent(new ExampleEvent(`Non-bubble by ${key}`), false);
const triggerBubbleEvent = () => model.publishEvent(new ExampleEvent(`Bubble by ${key}`), true);
const clearDescendentEvents = () =>
sceneGraph.findDescendents(model, ExampleSceneObject).forEach((scene) => scene.setState({ recentEvent: '' }));

return (
<div style={{ border: `${theme.colors.border.strong} 4px solid`, borderRadius: 8, padding: 4, margin: 8 }}>
<h2>
I am: <em>{key}</em>
</h2>
<h4>
My parent is: <em>{parentState?.key}</em>
</h4>
<Stack direction={'column'} alignItems={'flex-start'}>
<Button fullWidth={false} onClick={triggerBubbleEvent}>
Bubble
</Button>
<Button onClick={triggerNonBubbleEvent}>Non-Bubble</Button>
<Button onClick={() => model.setState({ recentEvent: '' })}>Clear</Button>
<Button onClick={clearDescendentEvents}>Clear Descendents</Button>
</Stack>
{
<div
style={{
margin: 4,
border: `${theme.colors.border.weak} solid 2px`,
transitionDuration: '200ms',
background: eventFlash ? theme.colors.action.selected : theme.colors.background.primary,
transform: eventFlash ? 'scale(1.0, 1.5)' : '',
}}
>
{recentEvent && (
<>
<h3>Event:</h3>
<h5>{recentEvent}</h5>
</>
)}
</div>
}
{children.length > 0 && (
<div
style={{
border: `${theme.colors.border.medium} 3px solid`,
borderRadius: 4,
padding: 4,
margin: 4,
display: 'inline-block',
}}
>
Children of {key}:
<Stack>
{children.map((child) => (
<child.Component key={child.state.key} model={child} />
))}
</Stack>
</div>
)}
</div>
);
}
10 changes: 5 additions & 5 deletions packages/scenes-app/src/react-demo/DynamicVariablesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ export function DynamicVariablesPage() {
</DemoVizLayout>
</Stack>
</DataSourceVariable>
<QueryVariable
name="job2"
<QueryVariable
name="job2"
initialValue="A"
query={{ query: '*', refId: 'A' }}
datasource={{ uid: 'gdev-testdata' }}
regex={regexQueryVar}
query={{ query: '*', refId: 'A' }}
datasource={{ uid: 'gdev-testdata' }}
regex={regexQueryVar}
hide={hide}
refresh={VariableRefresh.onTimeRangeChanged}
includeAll={includeAll}
Expand Down
4 changes: 2 additions & 2 deletions packages/scenes-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#### UrlSync: Support browser history steps, remove singleton ([#878](https://github.com/grafana/scenes/pull/878))

getUrlSyncManager is no longer exported as UrlSyncManager is now no longer global singleton but local to the UrlSyncContextProvider.
getUrlSyncManager is no longer exported as UrlSyncManager is now no longer global singleton but local to the UrlSyncContextProvider.
If you called getUrlSyncManager().getUrlState that util function is available via the exported object sceneUtils.

---
Expand Down Expand Up @@ -152,4 +152,4 @@ If you called getUrlSyncManager().getUrlState that util function is available vi

---

Work in progress
Work in progress
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jest.mock('@grafana/runtime', () => ({

const DataSourceVariableClass = jest.requireActual('@grafana/scenes').DataSourceVariable;

describe('DatasourceVariable', () => {
describe('DatasourceVariable', () => {
beforeEach(() => {
jest.spyOn(DataSourceVariableClass.prototype, 'getValueOptions').mockImplementation(() => of([]));
});
Expand All @@ -64,7 +64,7 @@ describe('DatasourceVariable', () => {

const { rerender } = render(
<TestContextProvider value={scene}>
<DataSourceVariable name="dsVar" pluginId="grafana-testdata-datasource" label='test1'>
<DataSourceVariable name="dsVar" pluginId="grafana-testdata-datasource" label="test1">
<VariableControl name="dsVar" />
</DataSourceVariable>
</TestContextProvider>
Expand All @@ -78,7 +78,7 @@ describe('DatasourceVariable', () => {

rerender(
<TestContextProvider value={scene}>
<DataSourceVariable name="dsVar" pluginId="grafana-testdata-datasource" label='test2'>
<DataSourceVariable name="dsVar" pluginId="grafana-testdata-datasource" label="test2">
<VariableControl name="dsVar" />
</DataSourceVariable>
</TestContextProvider>
Expand All @@ -87,5 +87,5 @@ describe('DatasourceVariable', () => {
expect(variable).toBeDefined();
expect(variable.state.label).toBe('test2');
expect(screen.getByText('test2')).toBeInTheDocument();
})
});
});
16 changes: 8 additions & 8 deletions packages/scenes-react/src/variables/DataSourceVariable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ export function DataSourceVariable({
let variable: DataSourceVariableObject | undefined = scene.findVariable(name);

if (!variable) {
variable = new DataSourceVariableObject({
variable = new DataSourceVariableObject({
pluginId,
regex,
name,
label,
value: initialValue,
value: initialValue,
isMulti,
hide,
hide,
includeAll,
});
});
}

useEffect(() => {
Expand All @@ -55,7 +55,7 @@ export function DataSourceVariable({
}

if (
variable.state.pluginId === pluginId &&
variable.state.pluginId === pluginId &&
variable.state.regex === regex &&
variable.state.label === label &&
variable.state.hide === hide &&
Expand All @@ -68,12 +68,12 @@ export function DataSourceVariable({
pluginId,
regex,
label,
hide,
hide,
includeAll,
})
});

variable.refreshOptions();
}, [hide, includeAll, label, pluginId, regex, variable, variableAdded])
}, [hide, includeAll, label, pluginId, regex, variable, variableAdded]);

// Need to block child rendering until the variable is added so that child components like RVariableSelect find the variable
if (!variableAdded) {
Expand Down
27 changes: 22 additions & 5 deletions packages/scenes-react/src/variables/QueryVariable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { of } from 'rxjs';

const QueryVariableClass = jest.requireActual('@grafana/scenes').QueryVariable;

describe('QueryVariable', () => {
describe('QueryVariable', () => {
beforeEach(() => {
jest.spyOn(QueryVariableClass.prototype, 'getValueOptions').mockImplementation(() => of([]));
});
Expand All @@ -19,7 +19,12 @@ describe('QueryVariable', () => {

render(
<TestContextProvider value={scene}>
<QueryVariable name="queryVar" query={{ query: '*', refId: 'A' }} datasource={{ uid: 'gdev-testdata' }} initialValue="A">
<QueryVariable
name="queryVar"
query={{ query: '*', refId: 'A' }}
datasource={{ uid: 'gdev-testdata' }}
initialValue="A"
>
<VariableControl name="queryVar" />
</QueryVariable>
</TestContextProvider>
Expand All @@ -36,7 +41,13 @@ describe('QueryVariable', () => {

const { rerender } = render(
<TestContextProvider value={scene}>
<QueryVariable name="queryVar" query={{ query: '*', refId: 'A' }} datasource={{ uid: 'gdev-testdata' }} initialValue="A" label="test1">
<QueryVariable
name="queryVar"
query={{ query: '*', refId: 'A' }}
datasource={{ uid: 'gdev-testdata' }}
initialValue="A"
label="test1"
>
<VariableControl name="queryVar" />
</QueryVariable>
</TestContextProvider>
Expand All @@ -50,7 +61,13 @@ describe('QueryVariable', () => {

rerender(
<TestContextProvider value={scene}>
<QueryVariable name="queryVar" query={{ query: '*', refId: 'A' }} datasource={{ uid: 'gdev-testdata' }}initialValue="A" label="test2">
<QueryVariable
name="queryVar"
query={{ query: '*', refId: 'A' }}
datasource={{ uid: 'gdev-testdata' }}
initialValue="A"
label="test2"
>
<VariableControl name="queryVar" />
</QueryVariable>
</TestContextProvider>
Expand All @@ -59,5 +76,5 @@ describe('QueryVariable', () => {
expect(variable).toBeDefined();
expect(variable.state.label).toBe('test2');
expect(screen.getByText('test2')).toBeInTheDocument();
})
});
});
Loading
Loading