Skip to content
This repository has been archived by the owner on Jul 19, 2023. It is now read-only.

Commit

Permalink
Merge pull request #611 from eh-am/feat/add-single-view-ui
Browse files Browse the repository at this point in the history
feat(frontend): add single view page
  • Loading branch information
petethepig authored Apr 10, 2023
2 parents 5a1bfac + e2f66eb commit a2a8308
Show file tree
Hide file tree
Showing 21 changed files with 11,406 additions and 618 deletions.
51 changes: 51 additions & 0 deletions .github/workflows/frontend.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: frontend
on:
push:
branches:
- main
pull_request:

concurrency:
# Cancel any running workflow for the same branch when new commits are pushed.
# We group both by ref_name (available when CI is triggered by a push to a branch/tag)
# and head_ref (available when CI is triggered by a PR).
group: "frontend-${{ github.ref_name }}-${{ github.head_ref }}"
cancel-in-progress: true

jobs:
type-check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18.15.0
cache: yarn
- run: yarn --frozen-lockfile
- name: Run type-check
run: yarn type-check
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18.15.0
cache: yarn
- run: yarn --frozen-lockfile
- name: Run build
run: yarn build
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18.15.0
cache: yarn
- run: yarn --frozen-lockfile
- name: Run tests
run: yarn test
40 changes: 39 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,60 @@
"license": "AGPL-3.0-only",
"private": true,
"scripts": {
"test": "jest",
"type-check": "tsc -p tsconfig.json --noEmit",
"build": "NODE_ENV=production webpack --progress --config scripts/webpack/webpack.prod.js",
"dev": "NODE_ENV=development webpack serve --progress --color --config scripts/webpack/webpack.dev.js"
},
"devDependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@types/color": "^3.0.2",
"@types/d3-scale": "^4.0.2",
"@types/history": "4.7.11",
"@types/jest": "^29.5.0",
"@types/jquery": "^3.5.13",
"@types/lodash.debounce": "^4.0.6",
"@types/prismjs": "^1.26.0",
"@types/react": "^18.0.0",
"@types/react-datepicker": "^4.3.4",
"@types/react-dom": "^18.0.11",
"@types/react-helmet": "^6.1.5",
"@types/react-outside-click-handler": "^1.3.1",
"copy-webpack-plugin": "^11.0.0",
"esbuild-loader": "^3.0.1",
"expose-loader": "^4.1.0",
"html-webpack-plugin": "^5.5.0",
"jest": "^29.5.0",
"mini-css-extract-plugin": "^2.7.5",
"react-svg-loader": "^3.0.3",
"sass": "^1.60.0",
"tsconfig-paths-webpack-plugin": "^4.0.1",
"webpack": "^5.77.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.13.1",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@fortawesome/react-fontawesome": "~0.1.11",
"@mui/base": "^5.0.0-alpha.98",
"@mui/material": "^5.10.11",
"@react-hook/resize-observer": "^1.2.4",
"@react-hook/window-size": "^3.0.7",
"@reduxjs/toolkit": "^1.6.2",
"@szhsin/react-menu": "3.5.2",
"graphviz-react": "^1.2.5",
"jquery": "^3.6.4",
"pyroscope-oss": "git+https://github.com/pyroscope-io/pyroscope.git#e7cf318",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-datepicker": "^4.7.0",
"react-debounce-input": "^3.2.5",
"react-dom": "^18.2.0",
"react-flot": "^1.3.0",
"react-helmet": "^6.1.0",
"react-notifications-component": "~3.1.0",
"react-outside-click-handler": "^1.3.0",
"react-redux": "^7.2.1",
"react-textarea-autosize": "8.3.0"
}
}
20 changes: 18 additions & 2 deletions public/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './jquery-import';
import { Provider } from 'react-redux';
import store, { persistor } from './redux/store';
import '@webapp/../sass/profile.scss';
import '@szhsin/react-menu/dist/index.css';
import Notifications from '@webapp/ui/Notifications';

const root = ReactDOM.createRoot(document.getElementById('reactRoot'));
root.render(<div></div>);
import { SingleView } from './pages/SingleView';

const container = document.getElementById('reactRoot') as HTMLElement;
const root = ReactDOM.createRoot(container);

root.render(
<Provider store={store}>
<Notifications />

<SingleView />
</Provider>
);
5 changes: 5 additions & 0 deletions public/app/example.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe('noop', () => {
it('works', () => {
expect(true).toBe(true);
});
});
15 changes: 15 additions & 0 deletions public/app/hooks/loadAppNames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useEffect } from 'react';
import { useAppDispatch } from '@webapp/redux/hooks';
import { reloadAppNames } from '@webapp/redux/reducers/continuous';

export function loadAppNames() {
const dispatch = useAppDispatch();

useEffect(() => {
async function run() {
await dispatch(reloadAppNames());
}

run();
}, [dispatch]);
}
7 changes: 7 additions & 0 deletions public/app/jquery-import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import $ from 'jquery';
interface WindowWithJquery {
$: unknown;
jQuery: unknown;
}
(window as unknown as WindowWithJquery).$ = $;
(window as unknown as WindowWithJquery).jQuery = $;
12 changes: 12 additions & 0 deletions public/app/overrides/components/ExportData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
interface ExportDataProps {
flamebearer: unknown;
exportPNG: unknown;
exportJSON: unknown;
exportPprof: unknown;
exportHTML: unknown;
exportFlamegraphDotCom: unknown;
exportFlamegraphDotComFn: unknown;
}
export default function (props: ExportDataProps) {
return <></>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface ContextMenuProps {
click: {
/** The X position in the window where the click originated */
pageX: number;
/** The Y position in the window where the click originated */
pageY: number;
};
timestamp: number;
containerEl: HTMLElement;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect, useRef } from 'react';

// TooltipCallbackProps refers to the data available for the tooltip body construction
interface TooltipCallbackProps {
timeLabel: string;
values: Array<{
closest: number[];
color: number[];
// TODO: remove this
tagName: string;
}>;
// coordsToCanvasPos?: jquery.flot.axis['p2c'];
coordsToCanvasPos?: unknown;
canvasX?: number;
}

interface TimelineChartWrapperProps {
id: string;
timelineA: unknown;
height: unknown;
timezone: string;
title: React.ReactNode;
annotations: unknown;
ContextMenu: unknown;
selectionType: unknown;
onSelect: (from: string, until: string) => void;
onHoverDisplayTooltip?: React.FC<TooltipCallbackProps>;
}
export default function (props: TimelineChartWrapperProps) {
const ref = useRef<HTMLDivElement>(null);

// Since this element is inside a <Box>, also make the box hidden
useEffect(() => {
const parentElement = ref.current?.parentElement?.parentElement;
if (parentElement) {
parentElement.style.display = 'none';
}
}, [ref.current]);

return <div ref={ref}></div>;
}
12 changes: 12 additions & 0 deletions public/app/overrides/components/TimelineChart/Tooltip.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// TooltipCallbackProps refers to the data available for the tooltip body construction
export interface TooltipCallbackProps {
timeLabel: string;
values: Array<{
closest: number[];
color: number[];
// TODO: remove this
tagName: string;
}>;
coordsToCanvasPos?: any;
canvasX?: number;
}
37 changes: 37 additions & 0 deletions public/app/overrides/services/appNames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { App } from '@webapp/models/app';
import { Result } from '@webapp/util/fp';
import { z } from 'zod';
import type { ZodError } from 'zod';
import type { RequestError } from '@webapp/services/base';
import { parseResponse, request } from '@webapp/services/base';

const appNamesResponse = z.preprocess(
(arg) => {
if (!Array.isArray(arg)) {
return [];
}

return arg;
},
z.array(z.string()).transform((names) => {
return names.map((name) => {
return {
name,
spyName: 'gospy',
units: 'unknown',
};
});
})
);

export async function fetchApps(): Promise<
Result<App[], RequestError | ZodError>
> {
const response = await request('/pyroscope/label-values?label=__name__');

if (response.isOk) {
return parseResponse(response, appNamesResponse);
}

return Result.err<App[], RequestError>(response.error);
}
102 changes: 102 additions & 0 deletions public/app/overrides/services/render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Result } from '@webapp/util/fp';
import {
Profile,
Groups,
FlamebearerProfileSchema,
} from '@pyroscope/models/src';
import { z } from 'zod';
import type { ZodError } from 'zod';
import { buildRenderURL } from '@webapp/util/updateRequests';
import { Timeline, TimelineSchema } from '@webapp/models/timeline';
import { Annotation, AnnotationSchema } from '@webapp/models/annotation';
import type { RequestError } from '@webapp/services/base';
import { request } from '@webapp/services/base';

export interface RenderOutput {
profile: Profile;
timeline: Timeline;
groups?: Groups;
annotations: Annotation[];
}

// Default to empty array if not present
const defaultAnnotationsSchema = z.preprocess((a) => {
if (!a) {
return [];
}
return a;
}, z.array(AnnotationSchema));

interface renderSingleProps {
from: string;
until: string;
query: string;
refreshToken?: string;
maxNodes: string | number;
}
export async function renderSingle(
props: renderSingleProps,
controller?: {
signal?: AbortSignal;
}
): Promise<Result<RenderOutput, RequestError | ZodError>> {
const url = buildRenderURL(props);
// TODO
const response = await request(`/pyroscope/${url}&format=json`, {
signal: controller?.signal,
});

if (response.isErr) {
return Result.err<RenderOutput, RequestError>(response.error);
}

const parsed = FlamebearerProfileSchema.merge(
z.object({
timeline: TimelineSchema,
annotations: defaultAnnotationsSchema,
})
)
.merge(z.object({ telemetry: z.object({}).passthrough().optional() }))
.safeParse(response.value);

if (parsed.success) {
// TODO: strip timeline
const profile = parsed.data;
const { timeline, annotations } = parsed.data;

return Result.ok({
profile,
timeline,
annotations,
});
}

return Result.err(parsed.error);
}

export type RenderDiffResponse = Profile;
export interface RenderExploreOutput {
profile: Profile;
groups: Groups;
}

export async function renderDiff(
props: unknown,
controller?: {
signal?: AbortSignal;
}
) {
return Result.err<Profile, { message: string }>({
message: 'TODO: implement ',
});
}
export async function renderExplore(
props: unknown,
controller?: {
signal?: AbortSignal;
}
) {
return Result.err<RenderExploreOutput, { message: string }>({
message: 'TODO: implement ',
});
}
Loading

0 comments on commit a2a8308

Please sign in to comment.