Skip to content

Commit 4ac04c7

Browse files
talissoncostaclaude
andcommitted
feat(demo): add GitHub Pages demo with mock data
Add static demo deployment for showcasing the plugin: - Vite config for standalone demo build - Demo app with MSW mock handlers - Demo banner indicating mock data usage - GitHub Actions workflow for auto-deploy to Pages - Build script: yarn build:demo Demo URL: https://flagsmith.github.io/flagsmith-backstage-plugin/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent eda8b84 commit 4ac04c7

File tree

10 files changed

+383
-8
lines changed

10 files changed

+383
-8
lines changed

.eslintignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Exclude demo build files from linting
2+
demo/
3+
dist-demo/
4+
vite.config.demo.ts

.github/workflows/deploy-demo.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Deploy Demo
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: "pages"
15+
cancel-in-progress: false
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@v4
23+
24+
- name: Setup Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: '22'
28+
cache: 'yarn'
29+
30+
- name: Install dependencies
31+
run: yarn install --frozen-lockfile
32+
33+
- name: Build demo
34+
run: yarn build:demo
35+
36+
- name: Setup Pages
37+
uses: actions/configure-pages@v4
38+
39+
- name: Upload artifact
40+
uses: actions/upload-pages-artifact@v3
41+
with:
42+
path: ./dist-demo
43+
44+
deploy:
45+
needs: build
46+
runs-on: ubuntu-latest
47+
environment:
48+
name: github-pages
49+
url: ${{ steps.deployment.outputs.page_url }}
50+
steps:
51+
- name: Deploy to GitHub Pages
52+
id: deployment
53+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ node_modules/
99
# Production
1010
/build
1111
/dist
12+
/dist-demo
1213
/dist-types
1314
/lib
1415
*.tsbuildinfo

demo/App.tsx

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import React, { useState } from 'react';
2+
import {
3+
Box,
4+
Tabs,
5+
Tab,
6+
ThemeProvider,
7+
CssBaseline,
8+
createTheme,
9+
AppBar,
10+
Toolbar,
11+
Typography,
12+
Container,
13+
} from '@material-ui/core';
14+
import { EntityProvider } from '@backstage/plugin-catalog-react';
15+
import { Entity } from '@backstage/catalog-model';
16+
import { TestApiProvider } from '@backstage/test-utils';
17+
import { discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
18+
import { DemoBanner } from './DemoBanner';
19+
import { FlagsTab } from '../src/components/FlagsTab';
20+
import { FlagsmithOverviewCard } from '../src/components/FlagsmithOverviewCard';
21+
import { FlagsmithUsageCard } from '../src/components/FlagsmithUsageCard';
22+
23+
// Mock entity with Flagsmith annotations
24+
const mockEntity: Entity = {
25+
apiVersion: 'backstage.io/v1alpha1',
26+
kind: 'Component',
27+
metadata: {
28+
name: 'demo-service',
29+
description: 'A demo service with Flagsmith feature flags integration',
30+
annotations: {
31+
'flagsmith.com/project-id': '31465',
32+
'flagsmith.com/org-id': '24242',
33+
},
34+
},
35+
spec: {
36+
type: 'service',
37+
lifecycle: 'production',
38+
owner: 'guests',
39+
},
40+
};
41+
42+
// Mock Discovery API (returns the MSW-intercepted URL)
43+
const mockDiscoveryApi = {
44+
getBaseUrl: async (_pluginId: string) => {
45+
// Return a URL that MSW will intercept
46+
return `${window.location.origin}/api`;
47+
},
48+
};
49+
50+
// Mock Fetch API (uses native fetch)
51+
const mockFetchApi = {
52+
fetch: async (url: string, init?: RequestInit) => {
53+
return fetch(url, init);
54+
},
55+
};
56+
57+
// Light theme similar to Backstage
58+
const theme = createTheme({
59+
palette: {
60+
type: 'light',
61+
primary: {
62+
main: '#0AC2A3',
63+
},
64+
secondary: {
65+
main: '#7B51FB',
66+
},
67+
background: {
68+
default: '#f5f5f5',
69+
paper: '#ffffff',
70+
},
71+
},
72+
typography: {
73+
fontFamily: 'Roboto, sans-serif',
74+
},
75+
});
76+
77+
interface TabPanelProps {
78+
children?: React.ReactNode;
79+
index: number;
80+
value: number;
81+
}
82+
83+
const TabPanel = ({ children, value, index }: TabPanelProps) => (
84+
<div role="tabpanel" hidden={value !== index}>
85+
{value === index && <Box>{children}</Box>}
86+
</div>
87+
);
88+
89+
export const App = () => {
90+
const [tabValue, setTabValue] = useState(0);
91+
92+
return (
93+
<ThemeProvider theme={theme}>
94+
<CssBaseline />
95+
<TestApiProvider
96+
apis={[
97+
[discoveryApiRef, mockDiscoveryApi],
98+
[fetchApiRef, mockFetchApi],
99+
]}
100+
>
101+
<EntityProvider entity={mockEntity}>
102+
<Box sx={{ flexGrow: 1 }}>
103+
{/* Demo Banner */}
104+
<DemoBanner />
105+
106+
{/* App Header */}
107+
<AppBar position="static" style={{ backgroundColor: '#1F1F1F' }}>
108+
<Toolbar>
109+
<Typography variant="h6" style={{ flexGrow: 1 }}>
110+
Flagsmith Backstage Plugin Demo
111+
</Typography>
112+
</Toolbar>
113+
<Tabs
114+
value={tabValue}
115+
onChange={(_e, newValue) => setTabValue(newValue)}
116+
indicatorColor="primary"
117+
textColor="inherit"
118+
style={{ backgroundColor: 'rgba(255,255,255,0.1)' }}
119+
>
120+
<Tab label="Feature Flags" />
121+
<Tab label="Overview Card" />
122+
<Tab label="Usage Card" />
123+
</Tabs>
124+
</AppBar>
125+
126+
{/* Tab Content */}
127+
<Container maxWidth="lg" style={{ marginTop: 24 }}>
128+
<TabPanel value={tabValue} index={0}>
129+
<FlagsTab />
130+
</TabPanel>
131+
132+
<TabPanel value={tabValue} index={1}>
133+
<Box style={{ maxWidth: 600 }}>
134+
<FlagsmithOverviewCard />
135+
</Box>
136+
</TabPanel>
137+
138+
<TabPanel value={tabValue} index={2}>
139+
<Box style={{ maxWidth: 600 }}>
140+
<FlagsmithUsageCard />
141+
</Box>
142+
</TabPanel>
143+
</Container>
144+
</Box>
145+
</EntityProvider>
146+
</TestApiProvider>
147+
</ThemeProvider>
148+
);
149+
};

demo/DemoBanner.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Box, Typography, Link } from '@material-ui/core';
2+
import { makeStyles } from '@material-ui/core/styles';
3+
import InfoIcon from '@material-ui/icons/Info';
4+
import GitHubIcon from '@material-ui/icons/GitHub';
5+
6+
const useStyles = makeStyles(() => ({
7+
banner: {
8+
backgroundColor: '#7B51FB',
9+
color: '#fff',
10+
padding: '12px 24px',
11+
display: 'flex',
12+
alignItems: 'center',
13+
justifyContent: 'center',
14+
gap: 12,
15+
},
16+
link: {
17+
color: '#fff',
18+
display: 'inline-flex',
19+
alignItems: 'center',
20+
gap: 4,
21+
'&:hover': {
22+
opacity: 0.9,
23+
},
24+
},
25+
icon: {
26+
fontSize: 20,
27+
},
28+
}));
29+
30+
export const DemoBanner = () => {
31+
const classes = useStyles();
32+
return (
33+
<Box className={classes.banner}>
34+
<InfoIcon className={classes.icon} />
35+
<Typography variant="body2">
36+
This is a demo using mock data.
37+
</Typography>
38+
<Link
39+
href="https://github.com/Flagsmith/flagsmith-backstage-plugin"
40+
target="_blank"
41+
rel="noopener noreferrer"
42+
className={classes.link}
43+
underline="always"
44+
>
45+
<GitHubIcon className={classes.icon} />
46+
View on GitHub
47+
</Link>
48+
</Box>
49+
);
50+
};

demo/index.html

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Flagsmith Backstage Plugin Demo</title>
7+
<link
8+
rel="stylesheet"
9+
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
10+
/>
11+
<style>
12+
* {
13+
margin: 0;
14+
padding: 0;
15+
box-sizing: border-box;
16+
}
17+
body {
18+
font-family: 'Roboto', sans-serif;
19+
background-color: #f5f5f5;
20+
}
21+
</style>
22+
</head>
23+
<body>
24+
<div id="root"></div>
25+
<script type="module" src="./main.tsx"></script>
26+
</body>
27+
</html>

demo/main.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom/client';
3+
import { setupWorker } from 'msw';
4+
import { handlers } from '../dev/mockHandlers';
5+
import { App } from './App';
6+
7+
// Start MSW worker for API mocking
8+
const worker = setupWorker(...handlers);
9+
10+
worker.start({
11+
onUnhandledRequest: 'bypass',
12+
serviceWorker: {
13+
url: '/flagsmith-backstage-plugin/mockServiceWorker.js',
14+
},
15+
}).then(() => {
16+
ReactDOM.createRoot(document.getElementById('root')!).render(
17+
<React.StrictMode>
18+
<App />
19+
</React.StrictMode>,
20+
);
21+
});

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"postpack": "backstage-cli package postpack",
2727
"tsc": "tsc || test -f dist-types/src/index.d.ts",
2828
"build:all": "yarn tsc && backstage-cli package build",
29+
"build:demo": "vite build --config vite.config.demo.ts && cp public/mockServiceWorker.js dist-demo/",
30+
"preview:demo": "vite preview --config vite.config.demo.ts",
2931
"prepare": "husky"
3032
},
3133
"lint-staged": {
@@ -64,12 +66,14 @@
6466
"@testing-library/jest-dom": "^6.0.0",
6567
"@testing-library/react": "^14.0.0",
6668
"@testing-library/user-event": "^14.0.0",
69+
"@vitejs/plugin-react": "^4.3.4",
6770
"husky": "^9.1.7",
6871
"lint-staged": "^16.2.7",
6972
"msw": "^1.0.0",
7073
"react": "^18.0.0",
7174
"react-dom": "^18.0.0",
72-
"react-router-dom": "^6.0.0"
75+
"react-router-dom": "^6.0.0",
76+
"vite": "^6.0.5"
7377
},
7478
"files": [
7579
"dist"

vite.config.demo.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { defineConfig } from 'vite';
2+
import react from '@vitejs/plugin-react';
3+
import { resolve } from 'path';
4+
5+
export default defineConfig({
6+
plugins: [react()],
7+
root: 'demo',
8+
base: '/flagsmith-backstage-plugin/',
9+
build: {
10+
outDir: '../dist-demo',
11+
emptyOutDir: true,
12+
},
13+
resolve: {
14+
alias: {
15+
'@': resolve(__dirname, 'src'),
16+
},
17+
},
18+
optimizeDeps: {
19+
include: ['react', 'react-dom', 'msw'],
20+
},
21+
});

0 commit comments

Comments
 (0)