diff --git a/frontend/src/component/personalDashboard/PersonalDashboard.tsx b/frontend/src/component/personalDashboard/PersonalDashboard.tsx
index d9c684f6de0c..8ede97688e97 100644
--- a/frontend/src/component/personalDashboard/PersonalDashboard.tsx
+++ b/frontend/src/component/personalDashboard/PersonalDashboard.tsx
@@ -22,6 +22,8 @@ import { WelcomeDialog } from './WelcomeDialog';
import { useLocalStorageState } from 'hooks/useLocalStorageState';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { ProjectSetupComplete } from './ProjectSetupComplete';
+import { usePersonalDashboard } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboard';
+import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
const ScreenExplanation = styled(Typography)(({ theme }) => ({
marginTop: theme.spacing(1),
@@ -136,6 +138,38 @@ const useProjects = () => {
return { projects, activeProject, setActiveProject };
};
+const FlagListItem: FC<{
+ flag: { name: string; project: string; type: string };
+ selected: boolean;
+ onClick: () => void;
+}> = ({ flag, selected, onClick }) => {
+ const IconComponent = getFeatureTypeIcons(flag.type);
+ return (
+
+
+
+
+ {flag.name}
+
+
+
+
+
+
+ );
+};
+
export const PersonalDashboard = () => {
const { user } = useAuthUser();
@@ -143,6 +177,14 @@ export const PersonalDashboard = () => {
const { projects, activeProject, setActiveProject } = useProjects();
+ const { personalDashboard } = usePersonalDashboard();
+ const [activeFlag, setActiveFlag] = useState(null);
+ useEffect(() => {
+ if (personalDashboard?.flags.length) {
+ setActiveFlag(personalDashboard.flags[0].name);
+ }
+ }, [JSON.stringify(personalDashboard)]);
+
const { project: activeProjectOverview, loading } =
useProjectOverview(activeProject);
@@ -256,11 +298,28 @@ export const PersonalDashboard = () => {
-
- You have not created or favorited any feature flags.
- Once you do, the will show up here.
-
+ {personalDashboard && personalDashboard.flags.length > 0 ? (
+
+ {personalDashboard.flags.map((flag) => (
+ setActiveFlag(flag.name)}
+ />
+ ))}
+
+ ) : (
+
+ You have not created or favorited any feature flags.
+ Once you do, they will show up here.
+
+ )}
+
Feature flag metrics
diff --git a/frontend/src/hooks/api/getters/usePersonalDashboard/usePersonalDashboard.ts b/frontend/src/hooks/api/getters/usePersonalDashboard/usePersonalDashboard.ts
new file mode 100644
index 000000000000..af855485b533
--- /dev/null
+++ b/frontend/src/hooks/api/getters/usePersonalDashboard/usePersonalDashboard.ts
@@ -0,0 +1,31 @@
+import useSWR from 'swr';
+import { formatApiPath } from 'utils/formatPath';
+import handleErrorResponses from '../httpErrorResponseHandler';
+import type { PersonalDashboardSchema } from 'openapi';
+
+export interface IPersonalDashboardOutput {
+ personalDashboard?: PersonalDashboardSchema;
+ refetch: () => void;
+ loading: boolean;
+ error?: Error;
+}
+
+export const usePersonalDashboard = (): IPersonalDashboardOutput => {
+ const { data, error, mutate } = useSWR(
+ formatApiPath('api/admin/personal-dashboard'),
+ fetcher,
+ );
+
+ return {
+ personalDashboard: data,
+ loading: !error && !data,
+ refetch: () => mutate(),
+ error,
+ };
+};
+
+const fetcher = (path: string) => {
+ return fetch(path)
+ .then(handleErrorResponses('Personal Dashboard'))
+ .then((res) => res.json());
+};
diff --git a/frontend/src/openapi/models/personalDashboardSchemaFlagsItem.ts b/frontend/src/openapi/models/personalDashboardSchemaFlagsItem.ts
index 0f2596dbc9e1..75a31ef0c58c 100644
--- a/frontend/src/openapi/models/personalDashboardSchemaFlagsItem.ts
+++ b/frontend/src/openapi/models/personalDashboardSchemaFlagsItem.ts
@@ -7,4 +7,6 @@
export type PersonalDashboardSchemaFlagsItem = {
/** The name of the flag */
name: string;
+ project: string;
+ type: string;
};
diff --git a/src/lib/features/personal-dashboard/fake-personal-dashboard-read-model.ts b/src/lib/features/personal-dashboard/fake-personal-dashboard-read-model.ts
index 59c60b7213d1..682988dc4578 100644
--- a/src/lib/features/personal-dashboard/fake-personal-dashboard-read-model.ts
+++ b/src/lib/features/personal-dashboard/fake-personal-dashboard-read-model.ts
@@ -1,9 +1,12 @@
-import type { IPersonalDashboardReadModel } from './personal-dashboard-read-model-type';
+import type {
+ IPersonalDashboardReadModel,
+ PersonalFeature,
+} from './personal-dashboard-read-model-type';
export class FakePersonalDashboardReadModel
implements IPersonalDashboardReadModel
{
- async getPersonalFeatures(userId: number): Promise<{ name: string }[]> {
+ async getPersonalFeatures(userId: number): Promise {
return [];
}
}
diff --git a/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts b/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts
index 260b0bac1e6c..830dc9629f72 100644
--- a/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts
+++ b/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts
@@ -55,9 +55,9 @@ test('should return personal dashboard with own flags and favorited flags', asyn
expect(body).toMatchObject({
projects: [],
flags: [
- { name: 'my_feature_c' },
- { name: 'my_feature_d' },
- { name: 'other_feature_b' },
+ { name: 'my_feature_d', type: 'release', project: 'default' },
+ { name: 'my_feature_c', type: 'release', project: 'default' },
+ { name: 'other_feature_b', type: 'release', project: 'default' },
],
});
});
diff --git a/src/lib/features/personal-dashboard/personal-dashboard-read-model-type.ts b/src/lib/features/personal-dashboard/personal-dashboard-read-model-type.ts
index 234b6878c1c2..f63e43ae080b 100644
--- a/src/lib/features/personal-dashboard/personal-dashboard-read-model-type.ts
+++ b/src/lib/features/personal-dashboard/personal-dashboard-read-model-type.ts
@@ -1,3 +1,5 @@
+export type PersonalFeature = { name: string; type: string; project: string };
+
export interface IPersonalDashboardReadModel {
- getPersonalFeatures(userId: number): Promise<{ name: string }[]>;
+ getPersonalFeatures(userId: number): Promise;
}
diff --git a/src/lib/features/personal-dashboard/personal-dashboard-read-model.ts b/src/lib/features/personal-dashboard/personal-dashboard-read-model.ts
index 8d6074aa2cc9..317113cd755c 100644
--- a/src/lib/features/personal-dashboard/personal-dashboard-read-model.ts
+++ b/src/lib/features/personal-dashboard/personal-dashboard-read-model.ts
@@ -1,5 +1,8 @@
import type { Db } from '../../db/db';
-import type { IPersonalDashboardReadModel } from './personal-dashboard-read-model-type';
+import type {
+ IPersonalDashboardReadModel,
+ PersonalFeature,
+} from './personal-dashboard-read-model-type';
export class PersonalDashboardReadModel implements IPersonalDashboardReadModel {
private db: Db;
@@ -8,15 +11,34 @@ export class PersonalDashboardReadModel implements IPersonalDashboardReadModel {
this.db = db;
}
- getPersonalFeatures(userId: number): Promise<{ name: string }[]> {
- return this.db<{ name: string }>('favorite_features')
+ async getPersonalFeatures(userId: number): Promise {
+ const result = await this.db<{
+ name: string;
+ type: string;
+ project: string;
+ }>('favorite_features')
+ .join('features', 'favorite_features.feature', 'features.name')
.where('favorite_features.user_id', userId)
- .select('feature as name')
+ .whereNull('features.archived_at')
+ .select(
+ 'features.name as name',
+ 'features.type',
+ 'features.project',
+ 'features.created_at',
+ )
.union(function () {
- this.select('name')
+ this.select('name', 'type', 'project', 'created_at')
.from('features')
- .where('features.created_by_user_id', userId);
+ .where('features.created_by_user_id', userId)
+ .whereNull('features.archived_at');
})
- .orderBy('name', 'asc');
+ .orderBy('created_at', 'desc')
+ .limit(100);
+
+ return result.map((row) => ({
+ name: row.name,
+ type: row.type,
+ project: row.project,
+ }));
}
}
diff --git a/src/lib/features/personal-dashboard/personal-dashboard-service.ts b/src/lib/features/personal-dashboard/personal-dashboard-service.ts
index e24b34a5a31e..4837d93324b5 100644
--- a/src/lib/features/personal-dashboard/personal-dashboard-service.ts
+++ b/src/lib/features/personal-dashboard/personal-dashboard-service.ts
@@ -1,4 +1,7 @@
-import type { IPersonalDashboardReadModel } from './personal-dashboard-read-model-type';
+import type {
+ IPersonalDashboardReadModel,
+ PersonalFeature,
+} from './personal-dashboard-read-model-type';
export class PersonalDashboardService {
private personalDashboardReadModel: IPersonalDashboardReadModel;
@@ -7,7 +10,7 @@ export class PersonalDashboardService {
this.personalDashboardReadModel = personalDashboardReadModel;
}
- getPersonalFeatures(userId: number): Promise<{ name: string }[]> {
+ getPersonalFeatures(userId: number): Promise {
return this.personalDashboardReadModel.getPersonalFeatures(userId);
}
}
diff --git a/src/lib/openapi/spec/personal-dashboard-schema.ts b/src/lib/openapi/spec/personal-dashboard-schema.ts
index 4c902a2455da..ec98f861e4c3 100644
--- a/src/lib/openapi/spec/personal-dashboard-schema.ts
+++ b/src/lib/openapi/spec/personal-dashboard-schema.ts
@@ -36,6 +36,16 @@ export const personalDashboardSchema = {
example: 'my-flag',
description: 'The name of the flag',
},
+ project: {
+ type: 'string',
+ example: 'my-project-id',
+ description: 'The id of the feature project',
+ },
+ type: {
+ type: 'string',
+ example: 'release',
+ description: 'The type of the feature flag',
+ },
},
},
description: 'A list of flags a user created or favorited',
diff --git a/src/server-dev.ts b/src/server-dev.ts
index 3517154721a7..f702cdbcc2ef 100644
--- a/src/server-dev.ts
+++ b/src/server-dev.ts
@@ -54,6 +54,7 @@ process.nextTick(async () => {
addonUsageMetrics: true,
onboardingMetrics: true,
onboardingUI: true,
+ personalDashboardUI: true,
},
},
authentication: {