Skip to content

Commit

Permalink
PlainReact: Expose scene features through contexts and hooks and norm…
Browse files Browse the repository at this point in the history
…al react components (#734)

Co-authored-by: Oscar Kilhed <[email protected]>
  • Loading branch information
torkelo and oscarkilhed authored Jun 5, 2024
1 parent 207b3f5 commit 80f7384
Show file tree
Hide file tree
Showing 77 changed files with 2,876 additions and 933 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/node-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ jobs:
uses: ./.github/actions/yarn-nm-install

- name: Test library
run: yarn run lerna run test
run: yarn test

- name: Typecheck library
run: yarn run lerna run typecheck
run: yarn typecheck

- name: Build library
run: yarn run lerna run build --ignore website
run: yarn build

release:
name: Release
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ node_modules
build

.env

# turbo
.turbo
2 changes: 2 additions & 0 deletions docusaurus/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"dependencies": {
"@docusaurus/core": "2.4.0",
"@docusaurus/preset-classic": "2.4.0",
"@grafana/scenes": "workspace:*",
"@grafana/scenes-react": "workspace:*",
"@iconscout/unicons": "^4.0.8",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
"packages": [
"packages/*"
]
}
}
19 changes: 11 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@
"prepare": "husky install",
"packages:publish": "lerna exec --no-private -- npm publish",
"docs": "yarn workspace website run start --port 8080",
"docs:build": "yarn workspace website run build",
"test:lib": "lerna run test --scope '@grafana/scenes' --",
"dev:lib": "lerna run dev --scope '@grafana/scenes' --",
"dev:app": "lerna run dev --scope 'scenes-app' --",
"test": "lerna run test --scope '@grafana/scenes' -- --watch",
"typecheck": "lerna run typecheck"
"docs:build": "yarn turbo build --filter=website",
"build": "yarn turbo build --filter=!website",
"test:scenes": "yarn turbo test --filter=@grafana/scenes -- --watch",
"test:scenes-react": "yarn turbo test --filter=@grafana/scenes-react -- --watch",
"dev": "yarn turbo dev --filter=!website",
"test": "yarn turbo test --filter=!website",
"typecheck": "yarn turbo typecheck --filter=!website"
},
"resolutions": {
"@types/react": "18.2.74"
},
"packageManager": "[email protected]",
"workspaces": [
".",
"packages/*",
"docusaurus/website"
],
Expand All @@ -51,7 +51,10 @@
"@auto-it/released": "^11.0.7",
"@testing-library/react": "^14.1.2",
"auto": "^11.0.7",
"eslint": "^8.57.0",
"lerna": "^6.5.1",
"lint-staged": "^13.2.0"
"lint-staged": "^13.2.0",
"prettier": "^3.2.5",
"turbo": "latest"
}
}
1 change: 1 addition & 0 deletions packages/scenes-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"@grafana/data": "^10.4.1",
"@grafana/runtime": "^10.4.1",
"@grafana/scenes": "workspace:*",
"@grafana/scenes-react": "workspace:*",
"@grafana/schema": "^10.4.1",
"@grafana/ui": "^10.4.1",
"@types/lodash": "latest",
Expand Down
2 changes: 2 additions & 0 deletions packages/scenes-app/src/components/Routes/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { prefixRoute } from '../../utils/utils.routing';
import { ROUTES } from '../../constants';
import { DemoListPage } from '../../pages/DemoListPage';
import GrafanaMonitoringApp from '../../monitoring-app/GrafanaMonitoringApp';
import { ReactDemoPage } from '../../react-demo/Home';

export const Routes = () => {
return (
<Switch>
{/* Default page */}
<Route path={prefixRoute(`${ROUTES.Demos}`)} component={DemoListPage} />
<Route path={prefixRoute(`${ROUTES.GrafanaMonitoring}`)} component={GrafanaMonitoringApp} />
<Route path={prefixRoute(`${ROUTES.ReactDemo}`)} component={ReactDemoPage} />
<Redirect to={prefixRoute(ROUTES.Demos)} />
</Switch>
);
Expand Down
1 change: 1 addition & 0 deletions packages/scenes-app/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum ROUTES {
Home = '',
Demos = 'demos',
GrafanaMonitoring = 'grafana-monitoring',
ReactDemo = 'react-only',
}

export const DATASOURCE_REF = {
Expand Down
2 changes: 2 additions & 0 deletions packages/scenes-app/src/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { getInteractiveTableDemo } from './interactiveTableDemo';
import { getVariableRepeaterDemo } from './variableRepeater';
import { getQueryControllerDemo } from './queryController';
import { getDynamicDataLayersDemo } from './dynamicDataLayers';
import { getInteropDemo } from './interopDemo';

export interface DemoDescriptor {
title: string;
Expand Down Expand Up @@ -79,5 +80,6 @@ export function getDemos(): DemoDescriptor[] {
{ title: 'Vertical controls layout', getPage: getVerticalControlsLayoutDemo },
{ title: 'Interactive table with expandable rows', getPage: getInteractiveTableDemo },
{ title: 'Query controller demo', getPage: getQueryControllerDemo },
{ title: 'Interop with hooks and context', getPage: getInteropDemo },
].sort((a, b) => a.title.localeCompare(b.title));
}
51 changes: 51 additions & 0 deletions packages/scenes-app/src/demos/interopDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
EmbeddedScene,
SceneAppPage,
SceneAppPageState,
SceneComponentProps,
SceneFlexItem,
SceneFlexLayout,
SceneObjectBase,
SceneObjectState,
VizPanel,
} from '@grafana/scenes';
import { getEmbeddedSceneDefaults, getQueryRunnerWithRandomWalkQuery } from './utils';
import React from 'react';
import { useTimeRange } from '@grafana/scenes-react';

export function getInteropDemo(defaults: SceneAppPageState) {
return new SceneAppPage({
...defaults,
subTitle: 'Testing using the hooks and plain react components from normal scene',
getScene: () => {
return new EmbeddedScene({
...getEmbeddedSceneDefaults(),
// context: new SceneContextObject({}),
key: 'Flex layout embedded scene',
body: new SceneFlexLayout({
direction: 'column',
children: [
new SceneFlexItem({
body: new VizPanel({
title: 'Graph',
pluginId: 'timeseries',
$data: getQueryRunnerWithRandomWalkQuery({}),
}),
}),
new SceneFlexItem({
body: new CustomSceneObject({}),
}),
],
}),
});
},
});
}

class CustomSceneObject extends SceneObjectBase<SceneObjectState> {
static Component = ({ model }: SceneComponentProps<CustomSceneObject>) => {
const [timeRange, _] = useTimeRange();

return <div>Time hook: {timeRange.from.toString()}</div>;
};
}
6 changes: 3 additions & 3 deletions packages/scenes-app/src/demos/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
SceneTimePicker,
SceneTimeRange,
VariableValueSelectors,
SceneDataQuery,
} from '@grafana/scenes';
import { DATASOURCE_REF } from '../constants';
import { DataQueryExtended } from '@grafana/scenes/src/querying/SceneQueryRunner';

export function getQueryRunnerWithRandomWalkQuery(
overrides?: Partial<any>,
Expand Down Expand Up @@ -51,7 +51,7 @@ export function getRowWithText(text: string) {
});
}

export function getPromQueryInstant(query: Partial<DataQueryExtended>): SceneQueryRunner {
export function getPromQueryInstant(query: Partial<SceneDataQuery>): SceneQueryRunner {
return new SceneQueryRunner({
datasource: { uid: 'gdev-prometheus' },
queries: [
Expand All @@ -66,7 +66,7 @@ export function getPromQueryInstant(query: Partial<DataQueryExtended>): SceneQue
});
}

export function getPromQueryTimeSeries(query: Partial<DataQueryExtended>): SceneQueryRunner {
export function getPromQueryTimeSeries(query: Partial<SceneDataQuery>): SceneQueryRunner {
return new SceneQueryRunner({
datasource: { uid: 'gdev-prometheus' },
queries: [
Expand Down
7 changes: 7 additions & 0 deletions packages/scenes-app/src/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
"path": "/a/%PLUGIN_ID%/grafana-monitoring",
"role": "Admin",
"addToNav": true
},
{
"type": "page",
"name": "React only demo",
"path": "/a/%PLUGIN_ID%/react-only",
"role": "Admin",
"addToNav": true
}
],
"dependencies": {
Expand Down
68 changes: 68 additions & 0 deletions packages/scenes-app/src/react-demo/DrilldownDemoPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { VizConfigBuilders } from '@grafana/scenes';
import { Breadcrumb, BreadcrumbProvider, VizPanel, useQueryRunner } from '@grafana/scenes-react';
import React from 'react';
import { DATASOURCE_REF } from '../constants';
import { PageWrapper } from './PageWrapper';
import { DemoVizLayout, urlBase } from './utils';
import { Route, Switch, useParams } from 'react-router-dom';
import { PlainGraphWithRandomWalk } from './PlainGraphWithRandomWalk';

export function DrilldownDemoPage() {
/**
* The breadcrumb provider should really be wrapping all routes, but placing it here just to show the concept and so far this is the only pages that use it.
* It needs to be above the PageWrapper as it's the the component that uses the BreadcrumbContext
*/
return (
<BreadcrumbProvider>
<Breadcrumb text="Drilldown demo" path={`${urlBase}/drilldown`} />
<Switch>
<Route path={`${urlBase}/drilldown`} component={DrilldownHome} exact />
<Route path={`${urlBase}/drilldown/lib/:lib`} component={DrilldownLibraryPage} />
</Switch>
</BreadcrumbProvider>
);
}

export function DrilldownHome() {
const dataProvider = useQueryRunner({
queries: [
{
scenarioId: 'csv_file',
refId: 'A',
csvFileName: 'js_libraries.csv',
},
],
datasource: DATASOURCE_REF,
});

return (
<PageWrapper title="Drilldown demo" subTitle="The top level page (for the demo)">
<DemoVizLayout>
<VizPanel title="JS Libraries" dataProvider={dataProvider} viz={tableWithDrilldown} />
</DemoVizLayout>
</PageWrapper>
);
}

export function DrilldownLibraryPage() {
const libraryName = useParams<{ lib: string }>().lib;

return (
<PageWrapper title={`Library: ${libraryName}`} subTitle="Library details drilldown page">
<DemoVizLayout>
<PlainGraphWithRandomWalk title={`${libraryName} trends`} />
</DemoVizLayout>
</PageWrapper>
);
}

export const tableWithDrilldown = VizConfigBuilders.table()
.setOverrides((b) =>
b.matchFieldsWithName('Library').overrideLinks([
{
title: 'Go to library details',
url: '${__url.path}/lib/${__value.text}',
},
])
)
.build();
55 changes: 55 additions & 0 deletions packages/scenes-app/src/react-demo/DynamicQueriesPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useQueryRunner, VizPanel } from '@grafana/scenes-react';
import { Field, Select, Stack } from '@grafana/ui';
import React, { useMemo, useState } from 'react';
import { DATASOURCE_REF } from '../constants';
import { PageWrapper } from './PageWrapper';
import { toOption } from '@grafana/data';
import { SceneDataQuery } from '@grafana/scenes';
import { plainGraph } from './visualizations';
import { DemoVizLayout } from './utils';

export function DynamicQueriesPage() {
const scenarios = ['Slow query', 'Random walk'].map(toOption);
const [scenario, setScenario] = useState<string>('Random walk');
const queries = useMemo(() => buildQueriesForScenario(scenario), [scenario]);

const dataProvider = useQueryRunner({ queries: queries, maxDataPoints: 100, datasource: DATASOURCE_REF });

return (
<PageWrapper title="Dynamic queriues" subTitle="Rebuild queries based on some user input / state">
<Stack direction="column">
<Stack>
<Field label="Query scenario">
<Select value={scenario} options={scenarios} onChange={(x) => setScenario(x.value!)} />
</Field>
</Stack>
<DemoVizLayout>
<VizPanel title={scenario} dataProvider={dataProvider} viz={plainGraph} />
</DemoVizLayout>
</Stack>
</PageWrapper>
);
}

function buildQueriesForScenario(scenario: string): SceneDataQuery[] {
switch (scenario) {
case 'Random walk':
return [
{
refId: 'A',
scenarioId: 'random_walk',
alias: 'random walk',
},
];
case 'Slow query':
default:
return [
{
refId: 'A',
scenarioId: 'slow_query',
alias: 'Slow query',
stringInput: '2s',
},
];
}
}
23 changes: 23 additions & 0 deletions packages/scenes-app/src/react-demo/DynamicVariablesPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CustomVariable, VariableSelect } from '@grafana/scenes-react';
import { Stack } from '@grafana/ui';
import React from 'react';
import { PageWrapper } from './PageWrapper';
import { PlainGraphWithRandomWalk } from './PlainGraphWithRandomWalk';
import { DemoVizLayout } from './utils';

export function DynamicVariablesPage() {
return (
<PageWrapper title="Dynamic variables" subTitle="Variables added via react rendering">
<CustomVariable name="job" query="A, B, C" initialValue="A">
<Stack direction="column">
<Stack>
<VariableSelect name="job" />
</Stack>
<DemoVizLayout>
<PlainGraphWithRandomWalk title={'Testing job = $job'} queryAlias="job = $job" />
</DemoVizLayout>
</Stack>
</CustomVariable>
</PageWrapper>
);
}
Loading

0 comments on commit 80f7384

Please sign in to comment.