Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,19 @@ metadata:
name: my-service
annotations:
flagsmith.com/project-id: '12345'
flagsmith.com/org-id: '67890' # Optional - defaults to first organization
spec:
type: service
owner: team-a
```

> **Note:** The organization ID is automatically derived from the project data.

## Getting your Flagsmith credentials

1. Log in to your [Flagsmith dashboard](https://app.flagsmith.com)
2. Go to **Organisation Settings** > **API Keys**
3. Create or copy your **Admin API Key**
4. Find your **Project ID** and **Organisation ID** in the URL or project settings
4. Find your **Project ID** in the URL or project settings

## Development

Expand Down
23 changes: 22 additions & 1 deletion dev/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { PropsWithChildren } from 'react';
import { createDevApp } from '@backstage/dev-utils';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { Entity } from '@backstage/catalog-model';
import { Box, Grid } from '@material-ui/core';
import { setupWorker } from 'msw';
import { FlagsTab } from '../src/components/FlagsTab';
import { FlagsmithOverviewCard } from '../src/components/FlagsmithOverviewCard';
Expand All @@ -23,7 +24,6 @@ const mockEntity: Entity = {
description: 'A demo service with Flagsmith feature flags integration',
annotations: {
'flagsmith.com/project-id': '31465',
'flagsmith.com/org-id': '24242',
},
},
spec: {
Expand Down Expand Up @@ -64,4 +64,25 @@ createDevApp()
title: 'Overview Cards',
path: '/flagsmith-cards',
})
.addPage({
element: (
<EntityWrapper>
<Box p={3}>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<FlagsmithOverviewCard />
</Grid>
<Grid item xs={12} md={6}>
<FlagsmithUsageCard />
</Grid>
</Grid>
<Box mt={3}>
<FlagsTab />
</Box>
</Box>
</EntityWrapper>
),
title: 'Complete View',
path: '/flagsmith-complete',
})
.render();
17 changes: 6 additions & 11 deletions src/components/FlagsmithUsageCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ export const FlagsmithUsageCard = () => {
const { entity } = useEntity();

const projectId = entity.metadata.annotations?.['flagsmith.com/project-id'];
const orgId = entity.metadata.annotations?.['flagsmith.com/org-id'];

const { project, usageData, totalFlags, loading, error } = useFlagsmithUsage(
projectId,
orgId,
);
const { project, usageData, totalFlags, loading, error } = useFlagsmithUsage(projectId);

const usageUrl = `${FLAGSMITH_DASHBOARD_URL}/organisation/${orgId}/usage`;
// Derive organization ID from project data for the dashboard link
const orgId = project?.organisation;
const usageUrl = orgId ? `${FLAGSMITH_DASHBOARD_URL}/organisation/${orgId}/usage` : undefined;

if (loading) {
return (
Expand All @@ -40,10 +38,7 @@ export const FlagsmithUsageCard = () => {
if (error) {
return (
<InfoCard title="Flags Usage Data (30 Days)">
<ErrorState
message={error}
hint={!orgId ? 'Add a flagsmith.com/organization-id annotation to this entity.' : undefined}
/>
<ErrorState message={error} />
</InfoCard>
);
}
Expand All @@ -57,7 +52,7 @@ export const FlagsmithUsageCard = () => {
title="Flags Usage Data (30 Days)"
subheader={subheader}
action={
orgId && (
usageUrl && (
<Box className={classes.headerActions}>
<FlagsmithLink href={usageUrl} iconOnly tooltip="View Usage Analytics" />
</Box>
Expand Down
11 changes: 6 additions & 5 deletions src/hooks/useFlagsmithUsage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,29 @@ describe('useFlagsmithUsage', () => {
jest.clearAllMocks();
});

it('returns error when projectId or orgId is missing', async () => {
const { result } = renderHook(() => useFlagsmithUsage(undefined, '1'), { wrapper });
it('returns error when projectId is missing', async () => {
const { result } = renderHook(() => useFlagsmithUsage(undefined), { wrapper });

await waitFor(() => expect(result.current.loading).toBe(false));

expect(result.current.error).toContain('Missing Flagsmith project ID');
});

it('fetches usage data and calculates totalFlags', async () => {
const mockProject = { id: 123, name: 'Test', organisation: 1 };
it('fetches usage data and derives orgId from project', async () => {
const mockProject = { id: 123, name: 'Test', organisation: 456 };
const mockUsage = [{ flags: 100, day: '2024-01-01' }, { flags: 200, day: '2024-01-02' }];

mockFetch
.mockResolvedValueOnce({ ok: true, json: async () => mockProject })
.mockResolvedValueOnce({ ok: true, json: async () => mockUsage });

const { result } = renderHook(() => useFlagsmithUsage('123', '1'), { wrapper });
const { result } = renderHook(() => useFlagsmithUsage('123'), { wrapper });

await waitFor(() => expect(result.current.loading).toBe(false));

expect(result.current.project).toEqual(mockProject);
expect(result.current.usageData).toEqual(mockUsage);
expect(result.current.totalFlags).toBe(300);
expect(result.current.error).toBeNull();
});
});
11 changes: 6 additions & 5 deletions src/hooks/useFlagsmithUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export interface UseFlagsmithUsageResult {

export function useFlagsmithUsage(
projectId: string | undefined,
orgId: string | undefined,
): UseFlagsmithUsageResult {
const discoveryApi = useApi(discoveryApiRef);
const fetchApi = useApi(fetchApiRef);
Expand All @@ -33,19 +32,21 @@ export function useFlagsmithUsage(
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (!projectId || !orgId) {
setError('Missing Flagsmith project ID or organization ID in entity annotations');
if (!projectId) {
setError('Missing Flagsmith project ID in entity annotations');
setLoading(false);
return;
}

const fetchData = async () => {
try {
// Fetch project data to get the organization ID
const projectData = await client.getProject(parseInt(projectId, 10));
setProject(projectData);

// Derive organization ID from project data
const usage = await client.getUsageData(
parseInt(orgId, 10),
projectData.organisation,
parseInt(projectId, 10),
);
setUsageData(usage);
Expand All @@ -57,7 +58,7 @@ export function useFlagsmithUsage(
};

fetchData();
}, [projectId, orgId, client]);
}, [projectId, client]);

const totalFlags = usageData.reduce((sum, day) => sum + (day.flags ?? 0), 0);

Expand Down