-
-
Notifications
You must be signed in to change notification settings - Fork 130
Add admin dashboard, bulk actions, filters, and timeline #551
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import React from 'react'; | ||
| import { useFirestore, useFirestoreCollectionData } from 'reactfire'; | ||
| import Spinner from '@site/src/components/molecules/spinner'; | ||
|
|
||
| interface Props { | ||
| selectedRepoVersion: string | undefined; | ||
| } | ||
|
|
||
| const BuildStatusDashboard = ({ selectedRepoVersion }: Props) => { | ||
| if (!selectedRepoVersion) return null; | ||
|
|
||
| const ciBuilds = useFirestore() | ||
| .collection('ciBuilds') | ||
| .where('buildInfo.repoVersion', '==', selectedRepoVersion); | ||
|
|
||
| const { status, data } = useFirestoreCollectionData<{ [key: string]: any }>(ciBuilds); | ||
|
|
||
| if (status === 'loading') { | ||
| return ( | ||
| <div style={{ padding: '8px 0', display: 'flex', alignItems: 'center', gap: 8 }}> | ||
| <Spinner type="slow" /> Loading build stats... | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const builds = data || []; | ||
| const started = builds.filter((b) => b.status === 'started').length; | ||
| const failed = builds.filter((b) => b.status === 'failed').length; | ||
| const published = builds.filter((b) => b.status === 'published').length; | ||
| const maxedOut = builds.filter( | ||
| (b) => b.status === 'failed' && (b.meta?.failureCount || 0) >= 15, | ||
| ).length; | ||
| const total = builds.length; | ||
|
|
||
| const statStyle = (color: string): React.CSSProperties => ({ | ||
| display: 'inline-flex', | ||
| alignItems: 'center', | ||
| gap: 6, | ||
| padding: '4px 12px', | ||
| borderRadius: 6, | ||
| border: `1px solid ${color}33`, | ||
| background: `${color}11`, | ||
| fontSize: '0.85em', | ||
| fontWeight: 500, | ||
| }); | ||
|
|
||
| return ( | ||
| <div | ||
| style={{ | ||
| display: 'flex', | ||
| flexWrap: 'wrap', | ||
| gap: 8, | ||
| padding: '12px 0', | ||
| alignItems: 'center', | ||
| }} | ||
| > | ||
| <span style={statStyle('#666')}> | ||
| Total: <strong>{total}</strong> | ||
| </span> | ||
| <span style={statStyle('#22c55e')}> | ||
| Published: <strong>{published}</strong> | ||
| </span> | ||
| <span style={statStyle('#3b82f6')}> | ||
| In progress: <strong>{started}</strong> | ||
| </span> | ||
| <span style={statStyle('#ef4444')}> | ||
| Failed: <strong>{failed}</strong> | ||
| </span> | ||
| {maxedOut > 0 && ( | ||
| <span style={statStyle('#b45309')}> | ||
| Stuck (15+): <strong>{maxedOut}</strong> | ||
| </span> | ||
| )} | ||
| <span | ||
| style={{ | ||
| marginLeft: 'auto', | ||
| fontSize: '0.75em', | ||
| opacity: 0.6, | ||
| display: 'inline-flex', | ||
| alignItems: 'center', | ||
| gap: 4, | ||
| }} | ||
| > | ||
| <span | ||
| style={{ | ||
| width: 6, | ||
| height: 6, | ||
| borderRadius: '50%', | ||
| background: '#22c55e', | ||
| display: 'inline-block', | ||
| animation: 'pulse 2s infinite', | ||
| }} | ||
| /> | ||
| Live | ||
| </span> | ||
|
Comment on lines
+84
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Search for `@keyframes` pulse definition in the codebase
rg -n '@keyframes\s+pulse' --type=css --type=scssRepository: game-ci/documentation Length of output: 93 🏁 Script executed: #!/bin/bash
# Search for `@keyframes` pulse in all files
rg -n '@keyframes\s+pulse' .
# Also search for pulse animation in CSS/SCSS files
echo "=== Searching in .css and .scss files ==="
rg -n 'pulse' --type-list | grep -E 'css|style'Repository: game-ci/documentation Length of output: 513 🏁 Script executed: #!/bin/bash
# Search for CSS/style related files
fd -e css -e scss -e module.css
# Check the reviewed file for imports
echo "=== Checking imports in build-status-dashboard.tsx ==="
head -20 src/components/docs/versions/build-status-dashboard.tsx
# Search for animation definitions in global styles or Tailwind
echo "=== Searching for global CSS files ==="
fd -E node_modules -e css -e scss | grep -E '(global|tailwind|index)'Repository: game-ci/documentation Length of output: 1066 🏁 Script executed: #!/bin/bash
# Check if Tailwind is used and if pulse is a Tailwind animation
rg -n 'class=".*pulse' . --type-list | head -20
# Search broadly for "pulse" in the codebase
rg -n '"pulse"' . -A 2 -B 2 | head -50Repository: game-ci/documentation Length of output: 910 🏁 Script executed: #!/bin/bash
# Check the Spinner component and its styles
echo "=== spinner.module.scss ==="
cat src/components/molecules/spinner.module.scss
echo -e "\n=== Spinner component source ==="
cat src/components/molecules/spinner.tsx
# Also check custom.css for global animations
echo -e "\n=== custom.css ==="
cat src/css/custom.css | head -100Repository: game-ci/documentation Length of output: 3763 CSS animation The inline style references 🤖 Prompt for AI Agents |
||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default BuildStatusDashboard; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,9 @@ | |||||||||||||||||
| }: Props) => { | ||||||||||||||||||
| const { editorVersion, baseOs, targetPlatform } = ciBuild.buildInfo; | ||||||||||||||||||
| const { major, minor, patch } = repoVersionInfo; | ||||||||||||||||||
| const buildRepoVersion = ciBuild.buildInfo.repoVersion; | ||||||||||||||||||
| const jobRepoVersion = repoVersionInfo.version; | ||||||||||||||||||
| const hasRepoVersionDrift = jobRepoVersion !== buildRepoVersion; | ||||||||||||||||||
|
Comment on lines
19
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lint: use destructured variables over property access. Static analysis flags lines 21-22. Since 🔧 Proposed fix- const { editorVersion, baseOs, targetPlatform } = ciBuild.buildInfo;
- const { major, minor, patch } = repoVersionInfo;
- const buildRepoVersion = ciBuild.buildInfo.repoVersion;
- const jobRepoVersion = repoVersionInfo.version;
+ const { editorVersion, baseOs, targetPlatform, repoVersion: buildRepoVersion } = ciBuild.buildInfo;
+ const { major, minor, patch, version: jobRepoVersion } = repoVersionInfo;
const hasRepoVersionDrift = jobRepoVersion !== buildRepoVersion;📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: Code styles[failure] 22-22: [failure] 21-21: 🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| const reducer = (state, action) => { | ||||||||||||||||||
| const { tag, value } = action; | ||||||||||||||||||
|
|
@@ -72,6 +75,22 @@ | |||||||||||||||||
|
|
||||||||||||||||||
| return ( | ||||||||||||||||||
| <div {...rest}> | ||||||||||||||||||
| <h4>Operational diagnostics</h4> | ||||||||||||||||||
| <CodeBlock language="json"> | ||||||||||||||||||
| {JSON.stringify( | ||||||||||||||||||
| { | ||||||||||||||||||
| jobRepoVersion, | ||||||||||||||||||
| buildRepoVersion, | ||||||||||||||||||
| hasRepoVersionDrift, | ||||||||||||||||||
| recommendedAction: hasRepoVersionDrift | ||||||||||||||||||
| ? 'Do not keep retrying this build as-is. Inspect stale older-version jobs and supersede them before retrying current jobs.' | ||||||||||||||||||
| : 'Retry/reset/cleanup actions on this page are safe to use if the failure is still active.', | ||||||||||||||||||
| }, | ||||||||||||||||||
| null, | ||||||||||||||||||
| 2, | ||||||||||||||||||
| )} | ||||||||||||||||||
| </CodeBlock> | ||||||||||||||||||
| <br /> | ||||||||||||||||||
| <h4>CI Job identification</h4> | ||||||||||||||||||
| <CodeBlock language="json">{JSON.stringify(ciJob, null, 2)}</CodeBlock> | ||||||||||||||||||
| <br /> | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
React hooks rule violation: early return before hooks.
The conditional return on line 10 occurs before the
useFirestoreanduseFirestoreCollectionDatahooks are called on lines 12 and 16. This violates React's rules of hooks—hooks must be called unconditionally and in the same order on every render.🐛 Proposed fix: move early return after hooks
const BuildStatusDashboard = ({ selectedRepoVersion }: Props) => { - if (!selectedRepoVersion) return null; - const ciBuilds = useFirestore() .collection('ciBuilds') .where('buildInfo.repoVersion', '==', selectedRepoVersion); const { status, data } = useFirestoreCollectionData<{ [key: string]: any }>(ciBuilds); + if (!selectedRepoVersion) return null; + if (status === 'loading') {Note: You may also need to handle the case where
selectedRepoVersionis undefined in the Firestore query, or conditionally skip the query entirely using a pattern like passing a dummy value when undefined.🤖 Prompt for AI Agents