@lhci/server
Advanced tools
Comparing version 0.2.0 to 0.2.1-alpha.0
{ | ||
"name": "@lhci/server", | ||
"main": "./src/server.js", | ||
"version": "0.2.0", | ||
"version": "0.2.1-alpha.0", | ||
"license": "Apache-2.0", | ||
@@ -20,3 +20,3 @@ "repository": { | ||
"dependencies": { | ||
"@lhci/utils": "0.2.0", | ||
"@lhci/utils": "0.2.1-alpha.0", | ||
"body-parser": "^1.18.3", | ||
@@ -41,3 +41,3 @@ "compression": "^1.7.4", | ||
}, | ||
"gitHead": "2f0fb8e52eab3d73500a9b327c03c2fb48154b93" | ||
"gitHead": "926eb335f3acfd4851a19910854dbd098d5fea28" | ||
} |
@@ -28,2 +28,4 @@ /** | ||
runAt: {type: Sequelize.DATE()}, // should mostly be equal to createdAt but modifiable by the consumer | ||
committedAt: {type: Sequelize.DATE()}, | ||
ancestorCommittedAt: {type: Sequelize.DATE()}, | ||
}; | ||
@@ -30,0 +32,0 @@ |
@@ -18,2 +18,3 @@ /** | ||
const {errorMiddleware} = require('./api/express-utils.js'); | ||
const version = require('../package.json').version; | ||
@@ -42,2 +43,3 @@ const DIST_FOLDER = path.join(__dirname, '../dist'); | ||
app.use('/version', (_, res) => res.send(version)); | ||
app.use('/v1/projects', createProjectsRouter({storageMethod})); | ||
@@ -44,0 +46,0 @@ app.use('/app', express.static(DIST_FOLDER)); |
@@ -7,26 +7,32 @@ /** | ||
import {h} from 'preact'; | ||
import {h, Fragment} from 'preact'; | ||
import clsx from 'clsx'; | ||
import './dropdown.css'; | ||
/** @param {{options: Array<{value: string, label: string}>, value: string, setValue(v: string): void, className?: string, title?: string}} props */ | ||
/** @param {{options: Array<{value: string, label: string}>, value: string, setValue(v: string): void, className?: string, title?: string, label?: string}} props */ | ||
export const Dropdown = props => { | ||
const {options, value, setValue, className, title} = props; | ||
const {options, value, setValue, className, title, label} = props; | ||
return ( | ||
<div className={className} style={{position: 'relative'}} data-tooltip={title}> | ||
<select | ||
className={clsx('dropdown')} | ||
onChange={evt => { | ||
if (!(evt.target instanceof HTMLSelectElement)) return; | ||
setValue(evt.target.value); | ||
}} | ||
> | ||
{options.map(option => { | ||
return ( | ||
<option key={option.value} value={option.value} selected={option.value === value}> | ||
{option.label} | ||
</option> | ||
); | ||
})} | ||
</select> | ||
<div | ||
className={clsx('dropdown', className)} | ||
style={{position: 'relative'}} | ||
data-tooltip={title} | ||
> | ||
<label> | ||
{label ? <span className="dropdown__label">{label}</span> : <Fragment />} | ||
<select | ||
onChange={evt => { | ||
if (!(evt.target instanceof HTMLSelectElement)) return; | ||
setValue(evt.target.value); | ||
}} | ||
> | ||
{options.map(option => { | ||
return ( | ||
<option key={option.value} value={option.value} selected={option.value === value}> | ||
{option.label} | ||
</option> | ||
); | ||
})} | ||
</select> | ||
</label> | ||
<div className="dropdown__chevron" /> | ||
@@ -33,0 +39,0 @@ </div> |
@@ -7,9 +7,9 @@ /** | ||
import {h} from 'preact'; | ||
import {h, Fragment} from 'preact'; | ||
import clsx from 'clsx'; | ||
import './pill.css'; | ||
/** @param {{children: string|JSX.Element|JSX.Element[], className?: string, variant?: 'base'|'compare'|'master-branch'|'dev-branch', onClick?: () => void, solid?: boolean}} props */ | ||
/** @param {{children: string|JSX.Element|JSX.Element[], className?: string, variant?: 'base'|'compare'|'master-branch'|'dev-branch', onClick?: () => void, solid?: boolean, avatar?: Pick<LHCI.ServerCommand.Build, 'avatarUrl'|'author'>}} props */ | ||
export const Pill = props => { | ||
const {children, variant = 'base'} = props; | ||
const {children, avatar, variant = 'base'} = props; | ||
return ( | ||
@@ -23,2 +23,12 @@ <div | ||
> | ||
{avatar ? ( | ||
<img | ||
className="pill__avatar" | ||
title={avatar.author} | ||
alt={avatar.author} | ||
src={avatar.avatarUrl} | ||
/> | ||
) : ( | ||
<Fragment /> | ||
)} | ||
<span>{children}</span> | ||
@@ -25,0 +35,0 @@ </div> |
@@ -86,3 +86,4 @@ /** | ||
export function useProjectBuilds(projectId) { | ||
return useApiData('getBuilds', [projectId]); | ||
const options = useMemo(() => ({limit: 20}), []); | ||
return useApiData('getBuilds', [projectId, options]); | ||
} | ||
@@ -89,0 +90,0 @@ |
@@ -8,2 +8,3 @@ /** | ||
import {h, Fragment} from 'preact'; | ||
import clsx from 'clsx'; | ||
import * as _ from '@lhci/utils/src/lodash.js'; | ||
@@ -35,15 +36,17 @@ import './build-hash-selector.css'; | ||
return ( | ||
<li className="build-hash-selector__label-li"> | ||
<span className="build-hash-selector__selection" /> | ||
<GitViz | ||
branch={props.branch} | ||
withNode={false} | ||
withDevBranchArc={false} | ||
withDevLine={props.withDevLine} | ||
/> | ||
<span | ||
className={`build-hash-selector__branch-label build-hash-selector__branch-label--${variant}`} | ||
> | ||
{props.branch} | ||
</span> | ||
<li className="build-hash-selector__item build-hash-selector__label-li"> | ||
<div className="container"> | ||
<span className="build-hash-selector__selection" /> | ||
<GitViz | ||
branch={props.branch} | ||
withNode={false} | ||
withDevBranchArc={false} | ||
withDevLine={props.withDevLine} | ||
/> | ||
<span | ||
className={`build-hash-selector__branch-label build-hash-selector__branch-label--${variant}`} | ||
> | ||
{props.branch} | ||
</span> | ||
</div> | ||
</li> | ||
@@ -58,2 +61,4 @@ ); | ||
const isBaseBranch = build.id === (baseBuild && baseBuild.id); | ||
const isSelected = | ||
(selector === 'base' && isBaseBranch) || (selector === 'compare' && isCompareBranch); | ||
const variant = build.branch === 'master' ? 'master-branch' : 'dev-branch'; | ||
@@ -63,2 +68,5 @@ | ||
<li | ||
className={clsx('build-hash-selector__item', { | ||
'build-hash-selector__item--selected': isSelected, | ||
})} | ||
key={build.id} | ||
@@ -82,28 +90,34 @@ onClick={() => { | ||
> | ||
<span className="build-hash-selector__selection"> | ||
{isCompareBranch && ( | ||
<Pill variant="compare" solid> | ||
compare | ||
</Pill> | ||
)} | ||
{isBaseBranch && ( | ||
<Pill variant="base" solid> | ||
base | ||
</Pill> | ||
)} | ||
</span> | ||
<GitViz | ||
branch={build.branch} | ||
withNode | ||
withDevBranchArc={props.withDevBranchArc} | ||
withDevLine={props.withDevLine} | ||
/> | ||
<Pill variant={variant}> | ||
<span className="build-hash-selector__hash">{build.hash.slice(0, 8)}</span> | ||
</Pill>{' '} | ||
<img className="build-hash-selector__avatar" alt={build.author} src={build.avatarUrl} /> | ||
<span className="build-hash-selector__commit">{build.commitMessage}</span> | ||
<span className="build-hash-selector__links"> | ||
{build.externalBuildUrl ? <a href={build.externalBuildUrl}>View Build</a> : <Fragment />} | ||
</span> | ||
<div className="container"> | ||
<span className="build-hash-selector__selection"> | ||
{isCompareBranch && selector === 'base' && ( | ||
<Pill variant="compare" solid> | ||
compare | ||
</Pill> | ||
)} | ||
{isBaseBranch && selector === 'compare' && ( | ||
<Pill variant="base" solid> | ||
base | ||
</Pill> | ||
)} | ||
</span> | ||
<GitViz | ||
branch={build.branch} | ||
withNode | ||
withDevBranchArc={props.withDevBranchArc} | ||
withDevLine={props.withDevLine} | ||
/> | ||
<Pill variant={variant} avatar={build}> | ||
<span className="build-hash-selector__hash">{build.hash.slice(0, 8)}</span> | ||
</Pill>{' '} | ||
<span className="build-hash-selector__commit">{build.commitMessage}</span> | ||
<span className="build-hash-selector__links"> | ||
{build.externalBuildUrl ? <a href={build.externalBuildUrl}>View Build</a> : <Fragment />} | ||
</span> | ||
</div> | ||
{isSelected ? ( | ||
<i className="material-icons build-hash-selector__selector-selection">check</i> | ||
) : ( | ||
<Fragment /> | ||
)} | ||
</li> | ||
@@ -151,25 +165,23 @@ ); | ||
return ( | ||
<div className="container"> | ||
<ul className={`build-hash-selector__list build-hash-selector--${props.selector}`}> | ||
{builds.map((build, index) => ( | ||
<Fragment key={build.id}> | ||
<BuildLineItem | ||
key={build.id} | ||
build={build} | ||
compareBuild={props.build} | ||
baseBuild={props.ancestorBuild} | ||
selector={props.selector} | ||
withDevLine={index <= indexOfFirstDev && props.build.branch !== 'master'} | ||
withDevBranchArc={index === indexOfFirstDev + 1} | ||
/> | ||
{index === indexOfFirstDev && build.branch !== 'master' ? ( | ||
<LabelLineItem branch={build.branch} withDevLine={true} /> | ||
) : null} | ||
{index === builds.length - 1 ? ( | ||
<LabelLineItem branch={build.branch} withDevLine={false} /> | ||
) : null} | ||
</Fragment> | ||
))} | ||
</ul> | ||
</div> | ||
<ul className={`build-hash-selector__list build-hash-selector--${props.selector}`}> | ||
{builds.map((build, index) => ( | ||
<Fragment key={build.id}> | ||
<BuildLineItem | ||
key={build.id} | ||
build={build} | ||
compareBuild={props.build} | ||
baseBuild={props.ancestorBuild} | ||
selector={props.selector} | ||
withDevLine={index <= indexOfFirstDev && props.build.branch !== 'master'} | ||
withDevBranchArc={index === indexOfFirstDev + 1} | ||
/> | ||
{index === indexOfFirstDev && build.branch !== 'master' ? ( | ||
<LabelLineItem branch={build.branch} withDevLine={true} /> | ||
) : null} | ||
{index === builds.length - 1 ? ( | ||
<LabelLineItem branch={build.branch} withDevLine={false} /> | ||
) : null} | ||
</Fragment> | ||
))} | ||
</ul> | ||
); | ||
@@ -176,0 +188,0 @@ }; |
@@ -29,3 +29,3 @@ /** | ||
/** | ||
* @param {{build: LHCI.ServerCommand.Build | null, variant: 'base'|'compare', isOpen?: boolean, onClick?: () => void}} props | ||
* @param {{build: LHCI.ServerCommand.Build | null, variant: 'base'|'compare', isOpen?: boolean, isDimmed?: boolean, onClick?: () => void}} props | ||
*/ | ||
@@ -37,2 +37,3 @@ export const BuildSelectorPill = props => { | ||
'build-selector-pill--open': props.isOpen, | ||
'build-selector-pill--dim': props.isDimmed, | ||
})} | ||
@@ -39,0 +40,0 @@ onClick={props.onClick} |
@@ -9,3 +9,2 @@ /** | ||
import './build-view-options.css'; | ||
import {Dropdown} from '../../components/dropdown'; | ||
import {LhrViewerLink} from '../../components/lhr-viewer-link'; | ||
@@ -16,3 +15,3 @@ | ||
/** @param {{compareLhr: LH.Result, baseLhr?: LH.Result, percentAbsoluteDeltaThreshold: number, setPercentAbsoluteDeltaThreshold: (x: number) => void}} props */ | ||
/** @param {{compareLhr: LH.Result, baseLhr?: LH.Result}} props */ | ||
export const BuildViewOptions = props => { | ||
@@ -33,19 +32,4 @@ return ( | ||
</LhrViewerLink> | ||
<Dropdown | ||
className="build-view-options__dropdown" | ||
title="Set minimum delta threshold (% change)" | ||
value={props.percentAbsoluteDeltaThreshold.toString()} | ||
setValue={value => { | ||
props.setPercentAbsoluteDeltaThreshold(Number(value)); | ||
}} | ||
options={[ | ||
{value: '0', label: '0%'}, | ||
{value: '0.05', label: '5%'}, | ||
{value: '0.1', label: '10%'}, | ||
{value: '0.15', label: '15%'}, | ||
{value: '0.25', label: '25%'}, | ||
]} | ||
/> | ||
</div> | ||
); | ||
}; |
@@ -9,52 +9,78 @@ /** | ||
import {Paper} from '../../components/paper'; | ||
import './build-view-warnings.css'; | ||
import {LhrViewerLink} from '../../components/lhr-viewer-link'; | ||
/** @param {{build: LHCI.ServerCommand.Build, baseBuild: LHCI.ServerCommand.Build | null, baseLhr?: LH.Result, hasBaseOverride: boolean}} props */ | ||
/** @param {{build: LHCI.ServerCommand.Build, baseBuild: LHCI.ServerCommand.Build | null, auditGroups: Array<any>, lhr: LH.Result, baseLhr?: LH.Result, hasBaseOverride: boolean}} props */ | ||
export const BuildViewWarnings = props => { | ||
const {build, baseBuild, baseLhr, hasBaseOverride} = props; | ||
/** @type {Array<string>} */ | ||
const warningMessages = []; | ||
const {build, baseBuild, baseLhr, auditGroups, hasBaseOverride} = props; | ||
const lhrLinkEl = ( | ||
<Fragment> | ||
<LhrViewerLink className="build-view-empty__lhr-link" lhr={props.lhr}> | ||
jump straight to the Lighthouse report. | ||
</LhrViewerLink> | ||
</Fragment> | ||
); | ||
if (!baseBuild) { | ||
warningMessages.push('No base build could be found for this commit.'); | ||
return ( | ||
<Paper className="build-view__warning"> | ||
<i className="material-icons">sentiment_very_dissatisfied</i> | ||
<div> | ||
Oops, no base build could be found for this commit. Manually select a base build above, or{' '} | ||
{lhrLinkEl} | ||
</div> | ||
</Paper> | ||
); | ||
} | ||
if (baseBuild && build.hash === baseBuild.hash) { | ||
warningMessages.push( | ||
[ | ||
'This base build is the same commit as the compare.', | ||
'Select a different base build to explore differences.', | ||
].join(' ') | ||
if (build.hash === baseBuild.hash) { | ||
return ( | ||
<Paper className="build-view__warning"> | ||
<i className="material-icons">sentiment_very_dissatisfied</i> | ||
<div> | ||
Oops, this base build is the same commit as the compare. Select a different base build to | ||
explore differences, or {lhrLinkEl} | ||
</div> | ||
</Paper> | ||
); | ||
} | ||
if (baseBuild && !baseLhr) { | ||
warningMessages.push('This base build is missing a run for this URL.'); | ||
if (!baseLhr) { | ||
return ( | ||
<Paper className="build-view__warning"> | ||
<i className="material-icons">sentiment_very_dissatisfied</i> | ||
<div> | ||
Oops, this base build is missing a run for this URL. Select a different URL to explore | ||
differences, or {lhrLinkEl} | ||
</div> | ||
</Paper> | ||
); | ||
} | ||
if ( | ||
baseBuild && | ||
baseBuild.hash !== build.ancestorHash && | ||
!warningMessages.length && | ||
!hasBaseOverride | ||
) { | ||
warningMessages.push( | ||
[ | ||
'This base build is not the target ancestor of the compare.', | ||
'Differences may not be due to this specific commit.', | ||
].join(' ') | ||
if (baseBuild.hash !== build.ancestorHash && !hasBaseOverride) { | ||
return ( | ||
<Paper className="build-view__warning"> | ||
<i className="material-icons">warning</i> | ||
<div> | ||
This base build is not the exact ancestor of the compare. Differences may not be due to | ||
this specific commit. | ||
</div> | ||
</Paper> | ||
); | ||
} | ||
return ( | ||
<Fragment> | ||
{warningMessages.map(message => { | ||
return ( | ||
<Paper className="build-view__warning" key={message}> | ||
<i className="material-icons">warning</i> | ||
<div>{message}</div> | ||
</Paper> | ||
); | ||
})} | ||
</Fragment> | ||
); | ||
if (baseLhr && !auditGroups.length) { | ||
return ( | ||
<Paper className="build-view__warning"> | ||
<i className="material-icons">sentiment_satisified_alt</i> | ||
<div> | ||
Woah, no differences found! Switch base builds to explore other differences, or{' '} | ||
{lhrLinkEl} | ||
</div> | ||
</Paper> | ||
); | ||
} | ||
return <Fragment />; | ||
}; |
@@ -30,3 +30,2 @@ /** | ||
import {findAuditDiffs, getDiffSeverity} from '@lhci/utils/src/audit-diff-finder'; | ||
import {BuildViewEmpty} from './build-view-empty'; | ||
import {route} from 'preact-router'; | ||
@@ -92,3 +91,3 @@ import {BuildViewOptions} from './build-view-options'; | ||
/** @param {{selectedUrl: string, build: LHCI.ServerCommand.Build | null, lhr?: LH.Result, baseLhr?: LH.Result, urls: Array<string>}} props */ | ||
/** @param {{selectedUrl: string, selectedAuditId?: string | null, build: LHCI.ServerCommand.Build | null, lhr?: LH.Result, baseLhr?: LH.Result, urls: Array<string>, percentAbsoluteDeltaThreshold: number, setPercentAbsoluteDeltaThreshold: (x: number) => void}} props */ | ||
const BuildViewScoreAndUrl = props => { | ||
@@ -98,14 +97,29 @@ return ( | ||
<div className="container"> | ||
<Dropdown | ||
title="Comparison URL" | ||
className="build-view__url-dropdown" | ||
value={props.selectedUrl} | ||
setValue={url => { | ||
const to = new URL(window.location.href); | ||
to.searchParams.set('compareUrl', url); | ||
route(`${to.pathname}${to.search}`); | ||
}} | ||
options={props.urls.map(url => ({value: url, label: url}))} | ||
/> | ||
<BuildScoreComparison {...props} /> | ||
<div className="build-view__dropdowns"> | ||
<Dropdown | ||
label="URL" | ||
value={props.selectedUrl} | ||
setValue={url => { | ||
const to = new URL(window.location.href); | ||
to.searchParams.set('compareUrl', url); | ||
route(`${to.pathname}${to.search}`); | ||
}} | ||
options={props.urls.map(url => ({value: url, label: url}))} | ||
/> | ||
<Dropdown | ||
label="Threshold" | ||
value={props.percentAbsoluteDeltaThreshold.toString()} | ||
setValue={value => { | ||
props.setPercentAbsoluteDeltaThreshold(Number(value)); | ||
}} | ||
options={[ | ||
{value: '0', label: '0%'}, | ||
{value: '0.05', label: '5%'}, | ||
{value: '0.1', label: '10%'}, | ||
{value: '0.15', label: '15%'}, | ||
{value: '0.25', label: '25%'}, | ||
]} | ||
/> | ||
</div> | ||
{props.selectedAuditId ? <Fragment /> : <BuildScoreComparison {...props} />} | ||
</div> | ||
@@ -191,13 +205,6 @@ </div> | ||
<Fragment> | ||
{openBuildHash === null ? ( | ||
<Fragment /> | ||
) : ( | ||
<div className={`build-selector-header build-selector-header--${openBuildHash}`}> | ||
<i className="material-icons">arrow_back</i> | ||
<span>Select {_.startCase(openBuildHash)}</span> | ||
</div> | ||
)} | ||
<BuildSelectorPill | ||
build={props.ancestorBuild} | ||
variant="base" | ||
isDimmed={openBuildHash === 'compare'} | ||
isOpen={openBuildHash === 'base'} | ||
@@ -209,2 +216,3 @@ onClick={() => setOpenBuild(openBuildHash === 'base' ? null : 'base')} | ||
variant="compare" | ||
isDimmed={openBuildHash === 'base'} | ||
isOpen={openBuildHash === 'compare'} | ||
@@ -240,16 +248,17 @@ onClick={() => setOpenBuild(openBuildHash === 'compare' ? null : 'compare')} | ||
> | ||
{selectedAuditId ? ( | ||
<Fragment /> | ||
) : ( | ||
<BuildViewScoreAndUrl | ||
build={props.build} | ||
lhr={lhr} | ||
baseLhr={baseLhr} | ||
selectedUrl={selectedUrl} | ||
urls={availableUrls} | ||
/> | ||
)} | ||
<BuildViewScoreAndUrl | ||
build={props.build} | ||
lhr={lhr} | ||
baseLhr={baseLhr} | ||
selectedUrl={selectedUrl} | ||
selectedAuditId={selectedAuditId} | ||
urls={availableUrls} | ||
percentAbsoluteDeltaThreshold={percentAbsoluteDeltaThreshold} | ||
setPercentAbsoluteDeltaThreshold={setDiffThreshold} | ||
/> | ||
<div className="container"> | ||
<BuildViewWarnings | ||
lhr={lhr} | ||
build={props.build} | ||
auditGroups={auditGroups} | ||
baseBuild={props.ancestorBuild} | ||
@@ -266,8 +275,3 @@ baseLhr={baseLhr} | ||
<BuildViewLegend /> | ||
<BuildViewOptions | ||
compareLhr={lhr} | ||
baseLhr={baseLhr} | ||
percentAbsoluteDeltaThreshold={percentAbsoluteDeltaThreshold} | ||
setPercentAbsoluteDeltaThreshold={setDiffThreshold} | ||
/> | ||
<BuildViewOptions compareLhr={lhr} baseLhr={baseLhr} /> | ||
</div> | ||
@@ -284,3 +288,3 @@ )} | ||
) : ( | ||
<BuildViewEmpty lhr={lhr} /> | ||
<Fragment /> | ||
)} | ||
@@ -287,0 +291,0 @@ </div> |
@@ -18,2 +18,3 @@ /** | ||
import './project-dashboard.css'; | ||
import {Pill} from '../../components/pill'; | ||
@@ -35,4 +36,4 @@ /** @param {{project: LHCI.ServerCommand.Project, builds: Array<LHCI.ServerCommand.Build>, runUrl?: string, branch?: string}} props */ | ||
> | ||
<td className="build-list__avatar" data-tooltip={build.author}> | ||
<img src={build.avatarUrl} alt={build.author} /> | ||
<td className="build-list__hash" data-tooltip={build.author}> | ||
<Pill avatar={build}>{build.hash.slice(0, 8)}</Pill> | ||
</td> | ||
@@ -46,3 +47,2 @@ <td className="build-list__commit">{build.commitMessage}</td> | ||
</td> | ||
<td className="build-list__hash">{build.hash.slice(0, 8)}</td> | ||
<td className="build-list__date"> | ||
@@ -49,0 +49,0 @@ {new Date(build.runAt).toDateString().replace(/\w+ (.*) \d{4}/, '$1')}{' '} |
@@ -16,2 +16,4 @@ /** | ||
import './project-graphs.css'; | ||
import {Dropdown} from '../../components/dropdown'; | ||
import {route} from 'preact-router'; | ||
@@ -25,3 +27,3 @@ const COLORS = ['#4587f4', '#f44587', '#87f445']; | ||
/** @param {{statistics?: Array<StatisticWithBuild>}} props */ | ||
/** @param {{statistics?: Array<StatisticWithBuild>, builds: Array<LHCI.ServerCommand.Build>, branch: string}} props */ | ||
const Legend = props => { | ||
@@ -31,2 +33,3 @@ if (!props.statistics) return null; | ||
const urls = computeURLsFromStats(props.statistics); | ||
const branches = Array.from(new Set(props.builds.map(build => build.branch))); | ||
return ( | ||
@@ -42,2 +45,12 @@ <div className="dashboard-graphs__legend"> | ||
})} | ||
<Dropdown | ||
label="Branch" | ||
options={branches.map(branch => ({value: branch, label: branch}))} | ||
value={props.branch} | ||
setValue={value => { | ||
const url = new URL(window.location.href); | ||
url.searchParams.set('branch', value); | ||
route(`${url.pathname}${url.search}`); | ||
}} | ||
/> | ||
</div> | ||
@@ -56,2 +69,6 @@ ); | ||
render={allStats => { | ||
if (allStats.length === 0) { | ||
return <Paper className="dashboard-graph">No data to display</Paper>; | ||
} | ||
const urls = computeURLsFromStats(allStats); | ||
@@ -153,3 +170,11 @@ const matchingStats = allStats | ||
const {project, builds, branch = 'master'} = props; | ||
const buildIds = useMemo(() => builds.map(build => build.id), builds); | ||
const buildIds = useMemo( | ||
() => | ||
builds | ||
.filter(build => build.branch === branch) | ||
.sort((a, b) => new Date(b.runAt).getTime() - new Date(a.runAt).getTime()) | ||
.map(build => build.id) | ||
.slice(0, 8), | ||
[builds, branch] | ||
); | ||
const [loadingState, stats] = useBuildStatistics(project.id, buildIds); | ||
@@ -165,3 +190,3 @@ const statsWithBuildsUnfiltered = augmentStatsWithBuilds(stats, builds); | ||
<div className="dashboard-graphs"> | ||
<Legend statistics={statsWithBuilds} /> | ||
<Legend statistics={statsWithBuilds} builds={builds} branch={branch} /> | ||
<StatisticPlot | ||
@@ -168,0 +193,0 @@ title="Performance" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
5481703
10313
+ Added@lhci/utils@0.2.1-alpha.0(transitive)
- Removed@lhci/utils@0.2.0(transitive)
Updated@lhci/utils@0.2.1-alpha.0