Skip to content

Commit 4e25dbf

Browse files
talissoncostaclaude
andcommitted
Add dev preview with mock data and Codespaces support
- Add MSW mock handlers for Flagsmith API responses - Create mock entity with Flagsmith annotations - Update dev/index.tsx with entity provider and mock setup - Add app-config.yaml for dev server configuration - Add public/mockServiceWorker.js for MSW browser support - Add .devcontainer for GitHub Codespaces preview - Add react-dom and react-router-dom dev dependencies Preview the plugin: - Run `yarn start` locally - Or use GitHub Codespaces from the PR 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent a4bdcb5 commit 4e25dbf

File tree

7 files changed

+705
-5
lines changed

7 files changed

+705
-5
lines changed

.devcontainer/devcontainer.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "Flagsmith Backstage Plugin",
3+
"image": "mcr.microsoft.com/devcontainers/typescript-node:22",
4+
"postCreateCommand": "yarn install",
5+
"forwardPorts": [3000],
6+
"portsAttributes": {
7+
"3000": {
8+
"label": "Plugin Preview",
9+
"onAutoForward": "openBrowser"
10+
}
11+
},
12+
"customizations": {
13+
"vscode": {
14+
"extensions": [
15+
"esbenp.prettier-vscode",
16+
"dbaeumer.vscode-eslint"
17+
],
18+
"settings": {
19+
"editor.formatOnSave": true
20+
}
21+
}
22+
},
23+
"postStartCommand": "echo '🚀 Run: yarn start' && echo '📍 Then open the forwarded port 3000'"
24+
}

app-config.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
app:
2+
title: Flagsmith Plugin Dev
3+
baseUrl: http://localhost:3000
4+
5+
backend:
6+
baseUrl: http://localhost:7007
7+
listen:
8+
port: 7007
9+
10+
# Mock proxy endpoint - requests are intercepted by MSW
11+
proxy:
12+
endpoints:
13+
'/flagsmith':
14+
target: 'http://localhost:7007'

dev/index.tsx

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,66 @@
11
import { createDevApp } from '@backstage/dev-utils';
2-
import { flagsmithPlugin } from '../src/plugin';
2+
import { EntityProvider } from '@backstage/plugin-catalog-react';
3+
import { Entity } from '@backstage/catalog-model';
4+
import { setupWorker } from 'msw';
5+
import { PropsWithChildren } from 'react';
6+
import { flagsmithPlugin, FlagsTab, FlagsmithOverviewCard, FlagsmithUsageCard } from '../src';
7+
import { handlers } from './mockHandlers';
8+
9+
// Start MSW worker for API mocking
10+
const worker = setupWorker(...handlers);
11+
worker.start({
12+
onUnhandledRequest: 'bypass',
13+
});
14+
15+
// Mock entity with Flagsmith annotations
16+
const mockEntity: Entity = {
17+
apiVersion: 'backstage.io/v1alpha1',
18+
kind: 'Component',
19+
metadata: {
20+
name: 'demo-service',
21+
description: 'A demo service with Flagsmith feature flags integration',
22+
annotations: {
23+
'flagsmith.com/project-id': '31465',
24+
'flagsmith.com/org-id': '24242',
25+
},
26+
},
27+
spec: {
28+
type: 'service',
29+
lifecycle: 'production',
30+
owner: 'guests',
31+
},
32+
};
33+
34+
// Wrapper component to provide entity context
35+
const EntityWrapper = ({ children }: PropsWithChildren<{}>) => (
36+
<EntityProvider entity={mockEntity}>{children}</EntityProvider>
37+
);
338

439
createDevApp()
540
.registerPlugin(flagsmithPlugin)
41+
.addPage({
42+
element: (
43+
<EntityWrapper>
44+
<FlagsTab />
45+
</EntityWrapper>
46+
),
47+
title: 'Feature Flags',
48+
path: '/flagsmith',
49+
})
50+
.addPage({
51+
element: (
52+
<EntityWrapper>
53+
<div style={{ padding: 20, display: 'flex', gap: 20, flexWrap: 'wrap' }}>
54+
<div style={{ flex: '1 1 400px', maxWidth: 600 }}>
55+
<FlagsmithOverviewCard />
56+
</div>
57+
<div style={{ flex: '1 1 400px', maxWidth: 600 }}>
58+
<FlagsmithUsageCard />
59+
</div>
60+
</div>
61+
</EntityWrapper>
62+
),
63+
title: 'Overview Cards',
64+
path: '/flagsmith-cards',
65+
})
666
.render();

dev/mockHandlers.ts

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import { rest } from 'msw';
2+
3+
// Mock data that represents realistic Flagsmith responses
4+
const mockProject = {
5+
id: 31465,
6+
name: 'Demo Project',
7+
organisation: 24242,
8+
created_date: '2024-01-15T10:00:00Z',
9+
};
10+
11+
const mockEnvironments = [
12+
{
13+
id: 101,
14+
name: 'Development',
15+
api_key: 'dev_api_key_123',
16+
project: 31465,
17+
},
18+
{
19+
id: 102,
20+
name: 'Staging',
21+
api_key: 'staging_api_key_456',
22+
project: 31465,
23+
},
24+
{
25+
id: 103,
26+
name: 'Production',
27+
api_key: 'prod_api_key_789',
28+
project: 31465,
29+
},
30+
];
31+
32+
const mockFeatures = [
33+
{
34+
id: 1001,
35+
name: 'dark_mode',
36+
description: 'Enable dark mode theme for the application',
37+
created_date: '2024-02-01T09:00:00Z',
38+
project: 31465,
39+
default_enabled: true,
40+
type: 'FLAG',
41+
is_archived: false,
42+
tags: ['ui', 'theme'],
43+
owners: [{ id: 1, name: 'John Doe', email: '[email protected]' }],
44+
},
45+
{
46+
id: 1002,
47+
name: 'new_checkout_flow',
48+
description: 'A/B test for the new checkout experience',
49+
created_date: '2024-03-10T14:30:00Z',
50+
project: 31465,
51+
default_enabled: false,
52+
type: 'FLAG',
53+
is_archived: false,
54+
tags: ['checkout', 'experiment'],
55+
owners: [{ id: 2, name: 'Jane Smith', email: '[email protected]' }],
56+
},
57+
{
58+
id: 1003,
59+
name: 'api_rate_limit',
60+
description: 'API rate limiting configuration',
61+
created_date: '2024-01-20T11:15:00Z',
62+
project: 31465,
63+
default_enabled: true,
64+
type: 'CONFIG',
65+
is_archived: false,
66+
tags: ['api', 'performance'],
67+
owners: [],
68+
},
69+
{
70+
id: 1004,
71+
name: 'beta_features',
72+
description: 'Enable beta features for selected users',
73+
created_date: '2024-04-05T16:45:00Z',
74+
project: 31465,
75+
default_enabled: false,
76+
type: 'FLAG',
77+
is_archived: false,
78+
tags: ['beta'],
79+
owners: [{ id: 1, name: 'John Doe', email: '[email protected]' }],
80+
},
81+
{
82+
id: 1005,
83+
name: 'maintenance_mode',
84+
description: 'Put the application in maintenance mode',
85+
created_date: '2024-02-28T08:00:00Z',
86+
project: 31465,
87+
default_enabled: false,
88+
type: 'FLAG',
89+
is_archived: false,
90+
tags: ['ops'],
91+
owners: [],
92+
},
93+
];
94+
95+
const mockFeatureVersions: Record<number, any[]> = {
96+
1001: [
97+
{
98+
uuid: 'v1-dark-mode-uuid',
99+
is_live: true,
100+
live_from: '2024-02-01T10:00:00Z',
101+
published: true,
102+
published_by: 'John Doe',
103+
},
104+
],
105+
1002: [
106+
{
107+
uuid: 'v1-checkout-uuid',
108+
is_live: true,
109+
live_from: '2024-03-15T09:00:00Z',
110+
published: true,
111+
published_by: 'Jane Smith',
112+
},
113+
],
114+
1003: [
115+
{
116+
uuid: 'v1-rate-limit-uuid',
117+
is_live: true,
118+
live_from: '2024-01-21T00:00:00Z',
119+
published: true,
120+
published_by: 'System',
121+
},
122+
],
123+
1004: [
124+
{
125+
uuid: 'v1-beta-uuid',
126+
is_live: true,
127+
live_from: '2024-04-10T12:00:00Z',
128+
published: true,
129+
published_by: 'John Doe',
130+
},
131+
],
132+
1005: [
133+
{
134+
uuid: 'v1-maintenance-uuid',
135+
is_live: true,
136+
live_from: '2024-03-01T00:00:00Z',
137+
published: true,
138+
published_by: 'Admin',
139+
},
140+
],
141+
};
142+
143+
const mockFeatureStates: Record<string, any[]> = {
144+
'v1-dark-mode-uuid': [
145+
{ id: 2001, enabled: true, feature_segment: null, feature_state_value: null },
146+
{ id: 2002, enabled: true, feature_segment: 501, feature_state_value: null }, // Segment override
147+
],
148+
'v1-checkout-uuid': [
149+
{ id: 2003, enabled: false, feature_segment: null, feature_state_value: null },
150+
{ id: 2004, enabled: true, feature_segment: 502, feature_state_value: null }, // Beta users segment
151+
],
152+
'v1-rate-limit-uuid': [
153+
{ id: 2005, enabled: true, feature_segment: null, feature_state_value: '1000' },
154+
],
155+
'v1-beta-uuid': [
156+
{ id: 2006, enabled: false, feature_segment: null, feature_state_value: null },
157+
{ id: 2007, enabled: true, feature_segment: 503, feature_state_value: null }, // Beta testers
158+
{ id: 2008, enabled: true, feature_segment: 504, feature_state_value: null }, // Internal users
159+
],
160+
'v1-maintenance-uuid': [
161+
{ id: 2009, enabled: false, feature_segment: null, feature_state_value: null },
162+
],
163+
};
164+
165+
const mockUsageData = [
166+
{
167+
flags: 15420,
168+
identities: 3250,
169+
traits: 8900,
170+
environment_document: 450,
171+
day: new Date(Date.now() - 6 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
172+
labels: { client_application_name: 'web-app', client_application_version: '1.0.0', user_agent: null },
173+
},
174+
{
175+
flags: 16800,
176+
identities: 3400,
177+
traits: 9200,
178+
environment_document: 480,
179+
day: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
180+
labels: { client_application_name: 'web-app', client_application_version: '1.0.0', user_agent: null },
181+
},
182+
{
183+
flags: 14200,
184+
identities: 3100,
185+
traits: 8500,
186+
environment_document: 420,
187+
day: new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
188+
labels: { client_application_name: 'web-app', client_application_version: '1.0.0', user_agent: null },
189+
},
190+
{
191+
flags: 17500,
192+
identities: 3600,
193+
traits: 9800,
194+
environment_document: 510,
195+
day: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
196+
labels: { client_application_name: 'web-app', client_application_version: '1.0.0', user_agent: null },
197+
},
198+
{
199+
flags: 18200,
200+
identities: 3750,
201+
traits: 10100,
202+
environment_document: 530,
203+
day: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
204+
labels: { client_application_name: 'web-app', client_application_version: '1.0.0', user_agent: null },
205+
},
206+
{
207+
flags: 16900,
208+
identities: 3500,
209+
traits: 9400,
210+
environment_document: 490,
211+
day: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
212+
labels: { client_application_name: 'web-app', client_application_version: '1.0.0', user_agent: null },
213+
},
214+
{
215+
flags: 15800,
216+
identities: 3300,
217+
traits: 9000,
218+
environment_document: 460,
219+
day: new Date().toISOString().split('T')[0],
220+
labels: { client_application_name: 'web-app', client_application_version: '1.0.0', user_agent: null },
221+
},
222+
];
223+
224+
export const handlers = [
225+
// Get project
226+
rest.get('*/proxy/flagsmith/projects/:projectId/', (req, res, ctx) => {
227+
return res(ctx.json(mockProject));
228+
}),
229+
230+
// Get project environments
231+
rest.get('*/proxy/flagsmith/projects/:projectId/environments/', (req, res, ctx) => {
232+
return res(ctx.json({ results: mockEnvironments }));
233+
}),
234+
235+
// Get project features
236+
rest.get('*/proxy/flagsmith/projects/:projectId/features/', (req, res, ctx) => {
237+
return res(ctx.json({ results: mockFeatures }));
238+
}),
239+
240+
// Get feature versions (lazy loading)
241+
rest.get('*/proxy/flagsmith/environments/:envId/features/:featureId/versions/', (req, res, ctx) => {
242+
const featureId = parseInt(req.params.featureId as string, 10);
243+
const versions = mockFeatureVersions[featureId] || [];
244+
return res(ctx.json({ results: versions }));
245+
}),
246+
247+
// Get feature states (lazy loading)
248+
rest.get('*/proxy/flagsmith/environments/:envId/features/:featureId/versions/:versionUuid/featurestates/', (req, res, ctx) => {
249+
const versionUuid = req.params.versionUuid as string;
250+
const states = mockFeatureStates[versionUuid] || [];
251+
return res(ctx.json(states));
252+
}),
253+
254+
// Get usage data
255+
rest.get('*/proxy/flagsmith/organisations/:orgId/usage-data/', (req, res, ctx) => {
256+
return res(ctx.json(mockUsageData));
257+
}),
258+
];

package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"react": "^16.13.1 || ^17.0.0 || ^18.0.0"
4949
},
5050
"devDependencies": {
51+
"@backstage/catalog-model": "^1.7.6",
5152
"@backstage/cli": "^0.34.4",
5253
"@backstage/core-app-api": "^1.19.1",
5354
"@backstage/dev-utils": "^1.1.15",
@@ -58,9 +59,14 @@
5859
"husky": "^9.1.7",
5960
"lint-staged": "^16.2.7",
6061
"msw": "^1.0.0",
61-
"react": "^16.13.1 || ^17.0.0 || ^18.0.0"
62+
"react": "^18.0.0",
63+
"react-dom": "^18.0.0",
64+
"react-router-dom": "^6.0.0"
6265
},
6366
"files": [
6467
"dist"
65-
]
66-
}
68+
],
69+
"msw": {
70+
"workerDirectory": "public"
71+
}
72+
}

0 commit comments

Comments
 (0)